├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── redismodule.h
├── rmutil
├── Makefile
├── sds.c
├── sds.h
├── sdsalloc.h
├── strings.c
├── strings.h
├── test_util.h
├── test_vector.c
├── util.c
├── util.h
├── vector.c
└── vector.h
└── src
├── Makefile
└── topk.c
/.gitignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | *.o
3 | *.xo
4 | *.so
5 | *.db
6 | .vscode
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 |
3 | Version 3, 19 November 2007
4 |
5 | Copyright © 2007 Free Software Foundation, Inc.
6 | Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
11 |
12 | The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
13 |
14 | When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
15 |
16 | Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
17 |
18 | A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
19 |
20 | The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
21 |
22 | An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
23 |
24 | The precise terms and conditions for copying, distribution and modification follow.
25 |
26 | TERMS AND CONDITIONS
27 |
28 | 0. Definitions.
29 |
30 | "This License" refers to version 3 of the GNU Affero General Public License.
31 |
32 | "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
33 |
34 | "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
35 |
36 | To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
37 |
38 | A "covered work" means either the unmodified Program or a work based on the Program.
39 |
40 | To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
41 |
42 | To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
43 |
44 | An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
45 |
46 | 1. Source Code.
47 |
48 | The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
49 |
50 | A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
51 |
52 | The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
53 |
54 | The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
55 |
56 | The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
57 |
58 | The Corresponding Source for a work in source code form is that same work.
59 |
60 | 2. Basic Permissions.
61 |
62 | All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
63 |
64 | You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
65 |
66 | Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
67 |
68 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
69 |
70 | No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
71 |
72 | When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
73 |
74 | 4. Conveying Verbatim Copies.
75 |
76 | You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
77 |
78 | You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
79 |
80 | 5. Conveying Modified Source Versions.
81 |
82 | You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
83 |
84 | a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
85 | b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
86 | c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
87 | d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
88 | A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
89 |
90 | 6. Conveying Non-Source Forms.
91 |
92 | You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
93 |
94 | a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
95 | b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
96 | c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
97 | d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
98 | e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
99 | A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
100 |
101 | A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
102 |
103 | "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
104 |
105 | If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
106 |
107 | The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
108 |
109 | Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
110 |
111 | 7. Additional Terms.
112 |
113 | "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
114 |
115 | When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
116 |
117 | Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
118 |
119 | a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
120 | b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
121 | c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
122 | d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
123 | e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
124 | f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
125 | All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
126 |
127 | If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
128 |
129 | Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
130 |
131 | 8. Termination.
132 |
133 | You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
134 |
135 | However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
136 |
137 | Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
138 |
139 | Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
140 |
141 | 9. Acceptance Not Required for Having Copies.
142 |
143 | You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
144 |
145 | 10. Automatic Licensing of Downstream Recipients.
146 |
147 | Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
148 |
149 | An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
150 |
151 | You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
152 |
153 | 11. Patents.
154 |
155 | A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
156 |
157 | A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
158 |
159 | Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
160 |
161 | In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
162 |
163 | If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
164 |
165 | If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
166 |
167 | A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
168 |
169 | Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
170 |
171 | 12. No Surrender of Others' Freedom.
172 |
173 | If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
174 |
175 | 13. Remote Network Interaction; Use with the GNU General Public License.
176 |
177 | Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
178 |
179 | Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
180 |
181 | 14. Revised Versions of this License.
182 |
183 | The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
184 |
185 | Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
186 |
187 | If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
188 |
189 | Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
190 |
191 | 15. Disclaimer of Warranty.
192 |
193 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
194 |
195 | 16. Limitation of Liability.
196 |
197 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
198 |
199 | 17. Interpretation of Sections 15 and 16.
200 |
201 | If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: librmutil topk
2 |
3 | clean:
4 | $(MAKE) -C rmutil clean
5 | $(MAKE) -C src clean
6 |
7 | .PHONY: topk
8 | topk:
9 | $(MAKE) -C src
10 |
11 | .PHONY: librmutil
12 | librmutil:
13 | $(MAKE) -C rmutil
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Update: this project has been suspended and superceded by http://rebloom.io
2 | ===
3 |
4 | TopK: an almost deterministic top k elements counter Redis module
5 | ===
6 |
7 | The basic algorithm (1st one): https://www.cs.berkeley.edu/~satishr/cs270/sp11/rough-notes/Streaming-two.pdf
8 |
9 | When TopK reaches cardinality of `k` and a new element needs to be added, the following eviction policy it practiced:
10 | 1. The frequencies of all existing observations are decreased by 1.
11 | 2. If there is an observation that can be evicted, it is replaced by the element.
12 |
13 | Quick start guide
14 | ---
15 |
16 | 1. Build a Redis server with support for modules.
17 | 2. Build the TopK module: `make`
18 | 3. To load a module, Start Redis with the `--loadmodule /path/to/module.so` option, add it as a directive to the configuration file or send a `
19 | MODULE LOAD` command.
20 |
21 | TopK API
22 | ---
23 |
24 | ### `TOPK.ADD key k elem [elem ...]`
25 |
26 | > Complexity: O(log(N)), where N is the size of the zset.
27 |
28 | Adds elements to a topk zset.
29 |
30 | `k` is the topk zset's maximal size. If `k` is zero, the topk zset has no size limit (sort of).
31 |
32 | Notes:
33 | - elem can't begin with the prefix _'TOPK'_.
34 | - Using different a `k` from call to call is perfectly possible, and could be even interesting.
35 | - Optimization in the variadic form: first incr existing elements, then do one sweep of decr, then remove < 1 as needed. This may screw the probabilities (someone can probably dis/prove), **so just use non variadic calls to play safe if you want.**
36 |
37 | An error is returned when:
38 | - The zset isn't a topk
39 | - The operation requires running `TOPK.SHRINK` before.
40 |
41 | **Reply:** Integer. If positive, it is the number of new elements added, if negative it is the number of elements removed due to k overflow, if 0 then only the offset was updated.
42 |
43 | ### `TOPK.PRANK key ele [ele ...]`
44 |
45 | > Complexity: nasty
46 |
47 | Returns the percentile rank for the element.
48 |
49 | **Reply:** Array of Integers, nil if key or element not found
50 |
51 | ### `TOPK.PRANGE key from to [DESC|ASC]`
52 | > Complexity: nasty too
53 |
54 | Returns the elements in the percentile range.
55 |
56 | Both `from` and `to` must be between 0 and 100, and `from` must be less than or equal to `to`. The optional switch determines the reply's sort order, where `DESC` (the default) means ordering from highest to lowest frequency.
57 |
58 | **Reply:** Array of strings.
59 |
60 | ### `TOPK.SHRINK key [k]`
61 |
62 | > Complexity: O(N) where N is the number of members in the zset.
63 |
64 | Resets the global offset after applying it to all members, trims size to `k` if specified and not zero.
65 |
66 | **Reply:** String, "OK"
67 |
68 | ### `TOPK.DEBUG command key [arg]`
69 |
70 | Debugging helper. `command` should be:
71 |
72 | * `MAKE` - requires an integer `arg`, fills topk `key` with 1..`arg` observations with respective frequencies.
73 | * `SHOW` - displays useful (?) information about the topk zset.
74 |
75 | Contributing
76 | ---
77 |
78 | Issue reports, pull and feature requests are welcome.
79 |
80 | License
81 | ---
82 |
83 | AGPLv3 - see [LICENSE](LICENSE)
84 |
--------------------------------------------------------------------------------
/redismodule.h:
--------------------------------------------------------------------------------
1 | #ifndef REDISMODULE_H
2 | #define REDISMODULE_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | /* ---------------- Defines common between core and modules --------------- */
9 |
10 | /* Error status return values. */
11 | #define REDISMODULE_OK 0
12 | #define REDISMODULE_ERR 1
13 |
14 | /* API versions. */
15 | #define REDISMODULE_APIVER_1 1
16 |
17 | /* API flags and constants */
18 | #define REDISMODULE_READ (1<<0)
19 | #define REDISMODULE_WRITE (1<<1)
20 |
21 | #define REDISMODULE_LIST_HEAD 0
22 | #define REDISMODULE_LIST_TAIL 1
23 |
24 | /* Key types. */
25 | #define REDISMODULE_KEYTYPE_EMPTY 0
26 | #define REDISMODULE_KEYTYPE_STRING 1
27 | #define REDISMODULE_KEYTYPE_LIST 2
28 | #define REDISMODULE_KEYTYPE_HASH 3
29 | #define REDISMODULE_KEYTYPE_SET 4
30 | #define REDISMODULE_KEYTYPE_ZSET 5
31 |
32 | /* Reply types. */
33 | #define REDISMODULE_REPLY_UNKNOWN -1
34 | #define REDISMODULE_REPLY_STRING 0
35 | #define REDISMODULE_REPLY_ERROR 1
36 | #define REDISMODULE_REPLY_INTEGER 2
37 | #define REDISMODULE_REPLY_ARRAY 3
38 | #define REDISMODULE_REPLY_NULL 4
39 |
40 | /* Postponed array length. */
41 | #define REDISMODULE_POSTPONED_ARRAY_LEN -1
42 |
43 | /* Expire */
44 | #define REDISMODULE_NO_EXPIRE -1
45 |
46 | /* Sorted set API flags. */
47 | #define REDISMODULE_ZADD_XX (1<<0)
48 | #define REDISMODULE_ZADD_NX (1<<1)
49 | #define REDISMODULE_ZADD_ADDED (1<<2)
50 | #define REDISMODULE_ZADD_UPDATED (1<<3)
51 | #define REDISMODULE_ZADD_NOP (1<<4)
52 |
53 | /* Hash API flags. */
54 | #define REDISMODULE_HASH_NONE 0
55 | #define REDISMODULE_HASH_NX (1<<0)
56 | #define REDISMODULE_HASH_XX (1<<1)
57 | #define REDISMODULE_HASH_CFIELDS (1<<2)
58 | #define REDISMODULE_HASH_EXISTS (1<<3)
59 |
60 | /* A special pointer that we can use between the core and the module to signal
61 | * field deletion, and that is impossible to be a valid pointer. */
62 | #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1)
63 |
64 | /* Error messages. */
65 | #define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value"
66 |
67 | #define REDISMODULE_POSITIVE_INFINITE (1.0/0.0)
68 | #define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0)
69 |
70 | /* ------------------------- End of common defines ------------------------ */
71 |
72 | #ifndef REDISMODULE_CORE
73 |
74 | typedef long long mstime_t;
75 |
76 | /* Incomplete structures for compiler checks but opaque access. */
77 | typedef struct RedisModuleCtx RedisModuleCtx;
78 | typedef struct RedisModuleKey RedisModuleKey;
79 | typedef struct RedisModuleString RedisModuleString;
80 | typedef struct RedisModuleCallReply RedisModuleCallReply;
81 |
82 | typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
83 |
84 | #define REDISMODULE_GET_API(name) \
85 | RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name))
86 |
87 | #define REDISMODULE_API_FUNC(x) (*x)
88 |
89 | int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
90 | int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
91 | int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
92 | int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
93 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);
94 | int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
95 | int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid);
96 | void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode);
97 | void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp);
98 | int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp);
99 | size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp);
100 | int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele);
101 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where);
102 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
103 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len);
104 | void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply);
105 | int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply);
106 | long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply);
107 | size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply);
108 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
109 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
110 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
111 | void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
112 | const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(RedisModuleString *str, size_t *len);
113 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
114 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);
115 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
116 | void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
117 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
118 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
119 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
120 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
121 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
122 | int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(RedisModuleString *str, long long *ll);
123 | int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(RedisModuleString *str, double *d);
124 | void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
125 | int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
126 | int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
127 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
128 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
129 | int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
130 | int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
131 | char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
132 | int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
133 | mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
134 | int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
135 | int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
136 | int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
137 | int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
138 | int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted);
139 | void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key);
140 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
141 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
142 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
143 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
144 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score);
145 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key);
146 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key);
147 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key);
148 | int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...);
149 | int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...);
150 | int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
151 | void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
152 | unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
153 |
154 | /* This is included inline inside each Redis module. */
155 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
156 | void *getapifuncptr = ((void**)ctx)[0];
157 | RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr;
158 | REDISMODULE_GET_API(CreateCommand);
159 | REDISMODULE_GET_API(SetModuleAttribs);
160 | REDISMODULE_GET_API(WrongArity);
161 | REDISMODULE_GET_API(ReplyWithLongLong);
162 | REDISMODULE_GET_API(ReplyWithError);
163 | REDISMODULE_GET_API(ReplyWithSimpleString);
164 | REDISMODULE_GET_API(ReplyWithArray);
165 | REDISMODULE_GET_API(ReplySetArrayLength);
166 | REDISMODULE_GET_API(ReplyWithStringBuffer);
167 | REDISMODULE_GET_API(ReplyWithString);
168 | REDISMODULE_GET_API(ReplyWithNull);
169 | REDISMODULE_GET_API(ReplyWithCallReply);
170 | REDISMODULE_GET_API(ReplyWithDouble);
171 | REDISMODULE_GET_API(ReplySetArrayLength);
172 | REDISMODULE_GET_API(GetSelectedDb);
173 | REDISMODULE_GET_API(SelectDb);
174 | REDISMODULE_GET_API(OpenKey);
175 | REDISMODULE_GET_API(CloseKey);
176 | REDISMODULE_GET_API(KeyType);
177 | REDISMODULE_GET_API(ValueLength);
178 | REDISMODULE_GET_API(ListPush);
179 | REDISMODULE_GET_API(ListPop);
180 | REDISMODULE_GET_API(StringToLongLong);
181 | REDISMODULE_GET_API(StringToDouble);
182 | REDISMODULE_GET_API(Call);
183 | REDISMODULE_GET_API(CallReplyProto);
184 | REDISMODULE_GET_API(FreeCallReply);
185 | REDISMODULE_GET_API(CallReplyInteger);
186 | REDISMODULE_GET_API(CallReplyType);
187 | REDISMODULE_GET_API(CallReplyLength);
188 | REDISMODULE_GET_API(CallReplyArrayElement);
189 | REDISMODULE_GET_API(CallReplyStringPtr);
190 | REDISMODULE_GET_API(CreateStringFromCallReply);
191 | REDISMODULE_GET_API(CreateString);
192 | REDISMODULE_GET_API(CreateStringFromLongLong);
193 | REDISMODULE_GET_API(FreeString);
194 | REDISMODULE_GET_API(StringPtrLen);
195 | REDISMODULE_GET_API(AutoMemory);
196 | REDISMODULE_GET_API(Replicate);
197 | REDISMODULE_GET_API(ReplicateVerbatim);
198 | REDISMODULE_GET_API(DeleteKey);
199 | REDISMODULE_GET_API(StringSet);
200 | REDISMODULE_GET_API(StringDMA);
201 | REDISMODULE_GET_API(StringTruncate);
202 | REDISMODULE_GET_API(GetExpire);
203 | REDISMODULE_GET_API(SetExpire);
204 | REDISMODULE_GET_API(ZsetAdd);
205 | REDISMODULE_GET_API(ZsetIncrby);
206 | REDISMODULE_GET_API(ZsetScore);
207 | REDISMODULE_GET_API(ZsetRem);
208 | REDISMODULE_GET_API(ZsetRangeStop);
209 | REDISMODULE_GET_API(ZsetFirstInScoreRange);
210 | REDISMODULE_GET_API(ZsetLastInScoreRange);
211 | REDISMODULE_GET_API(ZsetFirstInLexRange);
212 | REDISMODULE_GET_API(ZsetLastInLexRange);
213 | REDISMODULE_GET_API(ZsetRangeCurrentElement);
214 | REDISMODULE_GET_API(ZsetRangeNext);
215 | REDISMODULE_GET_API(ZsetRangePrev);
216 | REDISMODULE_GET_API(ZsetRangeEndReached);
217 | REDISMODULE_GET_API(HashSet);
218 | REDISMODULE_GET_API(HashGet);
219 | REDISMODULE_GET_API(IsKeysPositionRequest);
220 | REDISMODULE_GET_API(KeyAtPos);
221 | REDISMODULE_GET_API(GetClientId);
222 |
223 | RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
224 | return REDISMODULE_OK;
225 | }
226 |
227 | #else
228 |
229 | /* Things only defined for the modules core, not exported to modules
230 | * including this file. */
231 | #define RedisModuleString robj
232 |
233 | #endif /* REDISMODULE_CORE */
234 | #endif /* REDISMOUDLE_H */
235 |
--------------------------------------------------------------------------------
/rmutil/Makefile:
--------------------------------------------------------------------------------
1 | # set environment variable RM_INCLUDE_DIR to the location of redismodule.h
2 | ifndef RM_INCLUDE_DIR
3 | RM_INCLUDE_DIR=../
4 | endif
5 |
6 | CFLAGS = -g -fPIC -lc -lm -O3 -std=gnu99 -I$(RM_INCLUDE_DIR) -Wall -Wno-unused-function
7 | CC=gcc
8 |
9 | OBJS=util.o strings.o sds.o vector.o
10 |
11 | all: librmutil.a
12 |
13 | clean:
14 | rm -rf *.o *.a
15 |
16 | librmutil.a: $(OBJS)
17 | ar rcs $@ $^
18 |
19 | test_vector: test_vector.o vector.o
20 | $(CC) -Wall -o test_vector vector.o test_vector.o -lc -O0
21 | @(sh -c ./test_vector)
22 |
23 |
24 |
--------------------------------------------------------------------------------
/rmutil/sds.c:
--------------------------------------------------------------------------------
1 | /* SDSLib 2.0 -- A C dynamic strings library
2 | *
3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo
4 | * Copyright (c) 2015, Oran Agra
5 | * Copyright (c) 2015, Redis Labs, Inc
6 | * All rights reserved.
7 | *
8 | * Redistribution and use in source and binary forms, with or without
9 | * modification, are permitted provided that the following conditions are met:
10 | *
11 | * * Redistributions of source code must retain the above copyright notice,
12 | * this list of conditions and the following disclaimer.
13 | * * Redistributions in binary form must reproduce the above copyright
14 | * notice, this list of conditions and the following disclaimer in the
15 | * documentation and/or other materials provided with the distribution.
16 | * * Neither the name of Redis nor the names of its contributors may be used
17 | * to endorse or promote products derived from this software without
18 | * specific prior written permission.
19 | *
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 | * POSSIBILITY OF SUCH DAMAGE.
31 | */
32 |
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include "sds.h"
39 | #include "sdsalloc.h"
40 |
41 | static inline int sdsHdrSize(char type) {
42 | switch(type&SDS_TYPE_MASK) {
43 | case SDS_TYPE_5:
44 | return sizeof(struct sdshdr5);
45 | case SDS_TYPE_8:
46 | return sizeof(struct sdshdr8);
47 | case SDS_TYPE_16:
48 | return sizeof(struct sdshdr16);
49 | case SDS_TYPE_32:
50 | return sizeof(struct sdshdr32);
51 | case SDS_TYPE_64:
52 | return sizeof(struct sdshdr64);
53 | }
54 | return 0;
55 | }
56 |
57 | static inline char sdsReqType(size_t string_size) {
58 | if (string_size < 32)
59 | return SDS_TYPE_5;
60 | if (string_size < 0xff)
61 | return SDS_TYPE_8;
62 | if (string_size < 0xffff)
63 | return SDS_TYPE_16;
64 | if (string_size < 0xffffffff)
65 | return SDS_TYPE_32;
66 | return SDS_TYPE_64;
67 | }
68 |
69 | /* Create a new sds string with the content specified by the 'init' pointer
70 | * and 'initlen'.
71 | * If NULL is used for 'init' the string is initialized with zero bytes.
72 | *
73 | * The string is always null-termined (all the sds strings are, always) so
74 | * even if you create an sds string with:
75 | *
76 | * mystring = sdsnewlen("abc",3);
77 | *
78 | * You can print the string with printf() as there is an implicit \0 at the
79 | * end of the string. However the string is binary safe and can contain
80 | * \0 characters in the middle, as the length is stored in the sds header. */
81 | sds sdsnewlen(const void *init, size_t initlen) {
82 | void *sh;
83 | sds s;
84 | char type = sdsReqType(initlen);
85 | /* Empty strings are usually created in order to append. Use type 8
86 | * since type 5 is not good at this. */
87 | if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
88 | int hdrlen = sdsHdrSize(type);
89 | unsigned char *fp; /* flags pointer. */
90 |
91 | sh = s_malloc(hdrlen+initlen+1);
92 | if (!init)
93 | memset(sh, 0, hdrlen+initlen+1);
94 | if (sh == NULL) return NULL;
95 | s = (char*)sh+hdrlen;
96 | fp = ((unsigned char*)s)-1;
97 | switch(type) {
98 | case SDS_TYPE_5: {
99 | *fp = type | (initlen << SDS_TYPE_BITS);
100 | break;
101 | }
102 | case SDS_TYPE_8: {
103 | SDS_HDR_VAR(8,s);
104 | sh->len = initlen;
105 | sh->alloc = initlen;
106 | *fp = type;
107 | break;
108 | }
109 | case SDS_TYPE_16: {
110 | SDS_HDR_VAR(16,s);
111 | sh->len = initlen;
112 | sh->alloc = initlen;
113 | *fp = type;
114 | break;
115 | }
116 | case SDS_TYPE_32: {
117 | SDS_HDR_VAR(32,s);
118 | sh->len = initlen;
119 | sh->alloc = initlen;
120 | *fp = type;
121 | break;
122 | }
123 | case SDS_TYPE_64: {
124 | SDS_HDR_VAR(64,s);
125 | sh->len = initlen;
126 | sh->alloc = initlen;
127 | *fp = type;
128 | break;
129 | }
130 | }
131 | if (initlen && init)
132 | memcpy(s, init, initlen);
133 | s[initlen] = '\0';
134 | return s;
135 | }
136 |
137 | /* Create an empty (zero length) sds string. Even in this case the string
138 | * always has an implicit null term. */
139 | sds sdsempty(void) {
140 | return sdsnewlen("",0);
141 | }
142 |
143 | /* Create a new sds string starting from a null terminated C string. */
144 | sds sdsnew(const char *init) {
145 | size_t initlen = (init == NULL) ? 0 : strlen(init);
146 | return sdsnewlen(init, initlen);
147 | }
148 |
149 | /* Duplicate an sds string. */
150 | sds sdsdup(const sds s) {
151 | return sdsnewlen(s, sdslen(s));
152 | }
153 |
154 | /* Free an sds string. No operation is performed if 's' is NULL. */
155 | void sdsfree(sds s) {
156 | if (s == NULL) return;
157 | s_free((char*)s-sdsHdrSize(s[-1]));
158 | }
159 |
160 | /* Set the sds string length to the length as obtained with strlen(), so
161 | * considering as content only up to the first null term character.
162 | *
163 | * This function is useful when the sds string is hacked manually in some
164 | * way, like in the following example:
165 | *
166 | * s = sdsnew("foobar");
167 | * s[2] = '\0';
168 | * sdsupdatelen(s);
169 | * printf("%d\n", sdslen(s));
170 | *
171 | * The output will be "2", but if we comment out the call to sdsupdatelen()
172 | * the output will be "6" as the string was modified but the logical length
173 | * remains 6 bytes. */
174 | void sdsupdatelen(sds s) {
175 | int reallen = strlen(s);
176 | sdssetlen(s, reallen);
177 | }
178 |
179 | /* Modify an sds string in-place to make it empty (zero length).
180 | * However all the existing buffer is not discarded but set as free space
181 | * so that next append operations will not require allocations up to the
182 | * number of bytes previously available. */
183 | void sdsclear(sds s) {
184 | sdssetlen(s, 0);
185 | s[0] = '\0';
186 | }
187 |
188 | /* Enlarge the free space at the end of the sds string so that the caller
189 | * is sure that after calling this function can overwrite up to addlen
190 | * bytes after the end of the string, plus one more byte for nul term.
191 | *
192 | * Note: this does not change the *length* of the sds string as returned
193 | * by sdslen(), but only the free buffer space we have. */
194 | sds sdsMakeRoomFor(sds s, size_t addlen) {
195 | void *sh, *newsh;
196 | size_t avail = sdsavail(s);
197 | size_t len, newlen;
198 | char type, oldtype = s[-1] & SDS_TYPE_MASK;
199 | int hdrlen;
200 |
201 | /* Return ASAP if there is enough space left. */
202 | if (avail >= addlen) return s;
203 |
204 | len = sdslen(s);
205 | sh = (char*)s-sdsHdrSize(oldtype);
206 | newlen = (len+addlen);
207 | if (newlen < SDS_MAX_PREALLOC)
208 | newlen *= 2;
209 | else
210 | newlen += SDS_MAX_PREALLOC;
211 |
212 | type = sdsReqType(newlen);
213 |
214 | /* Don't use type 5: the user is appending to the string and type 5 is
215 | * not able to remember empty space, so sdsMakeRoomFor() must be called
216 | * at every appending operation. */
217 | if (type == SDS_TYPE_5) type = SDS_TYPE_8;
218 |
219 | hdrlen = sdsHdrSize(type);
220 | if (oldtype==type) {
221 | newsh = s_realloc(sh, hdrlen+newlen+1);
222 | if (newsh == NULL) return NULL;
223 | s = (char*)newsh+hdrlen;
224 | } else {
225 | /* Since the header size changes, need to move the string forward,
226 | * and can't use realloc */
227 | newsh = s_malloc(hdrlen+newlen+1);
228 | if (newsh == NULL) return NULL;
229 | memcpy((char*)newsh+hdrlen, s, len+1);
230 | s_free(sh);
231 | s = (char*)newsh+hdrlen;
232 | s[-1] = type;
233 | sdssetlen(s, len);
234 | }
235 | sdssetalloc(s, newlen);
236 | return s;
237 | }
238 |
239 | /* Reallocate the sds string so that it has no free space at the end. The
240 | * contained string remains not altered, but next concatenation operations
241 | * will require a reallocation.
242 | *
243 | * After the call, the passed sds string is no longer valid and all the
244 | * references must be substituted with the new pointer returned by the call. */
245 | sds sdsRemoveFreeSpace(sds s) {
246 | void *sh, *newsh;
247 | char type, oldtype = s[-1] & SDS_TYPE_MASK;
248 | int hdrlen;
249 | size_t len = sdslen(s);
250 | sh = (char*)s-sdsHdrSize(oldtype);
251 |
252 | type = sdsReqType(len);
253 | hdrlen = sdsHdrSize(type);
254 | if (oldtype==type) {
255 | newsh = s_realloc(sh, hdrlen+len+1);
256 | if (newsh == NULL) return NULL;
257 | s = (char*)newsh+hdrlen;
258 | } else {
259 | newsh = s_malloc(hdrlen+len+1);
260 | if (newsh == NULL) return NULL;
261 | memcpy((char*)newsh+hdrlen, s, len+1);
262 | s_free(sh);
263 | s = (char*)newsh+hdrlen;
264 | s[-1] = type;
265 | sdssetlen(s, len);
266 | }
267 | sdssetalloc(s, len);
268 | return s;
269 | }
270 |
271 | /* Return the total size of the allocation of the specifed sds string,
272 | * including:
273 | * 1) The sds header before the pointer.
274 | * 2) The string.
275 | * 3) The free buffer at the end if any.
276 | * 4) The implicit null term.
277 | */
278 | size_t sdsAllocSize(sds s) {
279 | size_t alloc = sdsalloc(s);
280 | return sdsHdrSize(s[-1])+alloc+1;
281 | }
282 |
283 | /* Return the pointer of the actual SDS allocation (normally SDS strings
284 | * are referenced by the start of the string buffer). */
285 | void *sdsAllocPtr(sds s) {
286 | return (void*) (s-sdsHdrSize(s[-1]));
287 | }
288 |
289 | /* Increment the sds length and decrements the left free space at the
290 | * end of the string according to 'incr'. Also set the null term
291 | * in the new end of the string.
292 | *
293 | * This function is used in order to fix the string length after the
294 | * user calls sdsMakeRoomFor(), writes something after the end of
295 | * the current string, and finally needs to set the new length.
296 | *
297 | * Note: it is possible to use a negative increment in order to
298 | * right-trim the string.
299 | *
300 | * Usage example:
301 | *
302 | * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
303 | * following schema, to cat bytes coming from the kernel to the end of an
304 | * sds string without copying into an intermediate buffer:
305 | *
306 | * oldlen = sdslen(s);
307 | * s = sdsMakeRoomFor(s, BUFFER_SIZE);
308 | * nread = read(fd, s+oldlen, BUFFER_SIZE);
309 | * ... check for nread <= 0 and handle it ...
310 | * sdsIncrLen(s, nread);
311 | */
312 | void sdsIncrLen(sds s, int incr) {
313 | unsigned char flags = s[-1];
314 | size_t len;
315 | switch(flags&SDS_TYPE_MASK) {
316 | case SDS_TYPE_5: {
317 | unsigned char *fp = ((unsigned char*)s)-1;
318 | unsigned char oldlen = SDS_TYPE_5_LEN(flags);
319 | assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));
320 | *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);
321 | len = oldlen+incr;
322 | break;
323 | }
324 | case SDS_TYPE_8: {
325 | SDS_HDR_VAR(8,s);
326 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
327 | len = (sh->len += incr);
328 | break;
329 | }
330 | case SDS_TYPE_16: {
331 | SDS_HDR_VAR(16,s);
332 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
333 | len = (sh->len += incr);
334 | break;
335 | }
336 | case SDS_TYPE_32: {
337 | SDS_HDR_VAR(32,s);
338 | assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
339 | len = (sh->len += incr);
340 | break;
341 | }
342 | case SDS_TYPE_64: {
343 | SDS_HDR_VAR(64,s);
344 | assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));
345 | len = (sh->len += incr);
346 | break;
347 | }
348 | default: len = 0; /* Just to avoid compilation warnings. */
349 | }
350 | s[len] = '\0';
351 | }
352 |
353 | /* Grow the sds to have the specified length. Bytes that were not part of
354 | * the original length of the sds will be set to zero.
355 | *
356 | * if the specified length is smaller than the current length, no operation
357 | * is performed. */
358 | sds sdsgrowzero(sds s, size_t len) {
359 | size_t curlen = sdslen(s);
360 |
361 | if (len <= curlen) return s;
362 | s = sdsMakeRoomFor(s,len-curlen);
363 | if (s == NULL) return NULL;
364 |
365 | /* Make sure added region doesn't contain garbage */
366 | memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
367 | sdssetlen(s, len);
368 | return s;
369 | }
370 |
371 | /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
372 | * end of the specified sds string 's'.
373 | *
374 | * After the call, the passed sds string is no longer valid and all the
375 | * references must be substituted with the new pointer returned by the call. */
376 | sds sdscatlen(sds s, const void *t, size_t len) {
377 | size_t curlen = sdslen(s);
378 |
379 | s = sdsMakeRoomFor(s,len);
380 | if (s == NULL) return NULL;
381 | memcpy(s+curlen, t, len);
382 | sdssetlen(s, curlen+len);
383 | s[curlen+len] = '\0';
384 | return s;
385 | }
386 |
387 | /* Append the specified null termianted C string to the sds string 's'.
388 | *
389 | * After the call, the passed sds string is no longer valid and all the
390 | * references must be substituted with the new pointer returned by the call. */
391 | sds sdscat(sds s, const char *t) {
392 | return sdscatlen(s, t, strlen(t));
393 | }
394 |
395 | /* Append the specified sds 't' to the existing sds 's'.
396 | *
397 | * After the call, the modified sds string is no longer valid and all the
398 | * references must be substituted with the new pointer returned by the call. */
399 | sds sdscatsds(sds s, const sds t) {
400 | return sdscatlen(s, t, sdslen(t));
401 | }
402 |
403 | /* Destructively modify the sds string 's' to hold the specified binary
404 | * safe string pointed by 't' of length 'len' bytes. */
405 | sds sdscpylen(sds s, const char *t, size_t len) {
406 | if (sdsalloc(s) < len) {
407 | s = sdsMakeRoomFor(s,len-sdslen(s));
408 | if (s == NULL) return NULL;
409 | }
410 | memcpy(s, t, len);
411 | s[len] = '\0';
412 | sdssetlen(s, len);
413 | return s;
414 | }
415 |
416 | /* Like sdscpylen() but 't' must be a null-termined string so that the length
417 | * of the string is obtained with strlen(). */
418 | sds sdscpy(sds s, const char *t) {
419 | return sdscpylen(s, t, strlen(t));
420 | }
421 |
422 | /* Helper for sdscatlonglong() doing the actual number -> string
423 | * conversion. 's' must point to a string with room for at least
424 | * SDS_LLSTR_SIZE bytes.
425 | *
426 | * The function returns the length of the null-terminated string
427 | * representation stored at 's'. */
428 | #define SDS_LLSTR_SIZE 21
429 | int sdsll2str(char *s, long long value) {
430 | char *p, aux;
431 | unsigned long long v;
432 | size_t l;
433 |
434 | /* Generate the string representation, this method produces
435 | * an reversed string. */
436 | v = (value < 0) ? -value : value;
437 | p = s;
438 | do {
439 | *p++ = '0'+(v%10);
440 | v /= 10;
441 | } while(v);
442 | if (value < 0) *p++ = '-';
443 |
444 | /* Compute length and add null term. */
445 | l = p-s;
446 | *p = '\0';
447 |
448 | /* Reverse the string. */
449 | p--;
450 | while(s < p) {
451 | aux = *s;
452 | *s = *p;
453 | *p = aux;
454 | s++;
455 | p--;
456 | }
457 | return l;
458 | }
459 |
460 | /* Identical sdsll2str(), but for unsigned long long type. */
461 | int sdsull2str(char *s, unsigned long long v) {
462 | char *p, aux;
463 | size_t l;
464 |
465 | /* Generate the string representation, this method produces
466 | * an reversed string. */
467 | p = s;
468 | do {
469 | *p++ = '0'+(v%10);
470 | v /= 10;
471 | } while(v);
472 |
473 | /* Compute length and add null term. */
474 | l = p-s;
475 | *p = '\0';
476 |
477 | /* Reverse the string. */
478 | p--;
479 | while(s < p) {
480 | aux = *s;
481 | *s = *p;
482 | *p = aux;
483 | s++;
484 | p--;
485 | }
486 | return l;
487 | }
488 |
489 | /* Create an sds string from a long long value. It is much faster than:
490 | *
491 | * sdscatprintf(sdsempty(),"%lld\n", value);
492 | */
493 | sds sdsfromlonglong(long long value) {
494 | char buf[SDS_LLSTR_SIZE];
495 | int len = sdsll2str(buf,value);
496 |
497 | return sdsnewlen(buf,len);
498 | }
499 |
500 | /* Like sdscatprintf() but gets va_list instead of being variadic. */
501 | sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
502 | va_list cpy;
503 | char staticbuf[1024], *buf = staticbuf, *t;
504 | size_t buflen = strlen(fmt)*2;
505 |
506 | /* We try to start using a static buffer for speed.
507 | * If not possible we revert to heap allocation. */
508 | if (buflen > sizeof(staticbuf)) {
509 | buf = s_malloc(buflen);
510 | if (buf == NULL) return NULL;
511 | } else {
512 | buflen = sizeof(staticbuf);
513 | }
514 |
515 | /* Try with buffers two times bigger every time we fail to
516 | * fit the string in the current buffer size. */
517 | while(1) {
518 | buf[buflen-2] = '\0';
519 | va_copy(cpy,ap);
520 | vsnprintf(buf, buflen, fmt, cpy);
521 | va_end(cpy);
522 | if (buf[buflen-2] != '\0') {
523 | if (buf != staticbuf) s_free(buf);
524 | buflen *= 2;
525 | buf = s_malloc(buflen);
526 | if (buf == NULL) return NULL;
527 | continue;
528 | }
529 | break;
530 | }
531 |
532 | /* Finally concat the obtained string to the SDS string and return it. */
533 | t = sdscat(s, buf);
534 | if (buf != staticbuf) s_free(buf);
535 | return t;
536 | }
537 |
538 | /* Append to the sds string 's' a string obtained using printf-alike format
539 | * specifier.
540 | *
541 | * After the call, the modified sds string is no longer valid and all the
542 | * references must be substituted with the new pointer returned by the call.
543 | *
544 | * Example:
545 | *
546 | * s = sdsnew("Sum is: ");
547 | * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
548 | *
549 | * Often you need to create a string from scratch with the printf-alike
550 | * format. When this is the need, just use sdsempty() as the target string:
551 | *
552 | * s = sdscatprintf(sdsempty(), "... your format ...", args);
553 | */
554 | sds sdscatprintf(sds s, const char *fmt, ...) {
555 | va_list ap;
556 | char *t;
557 | va_start(ap, fmt);
558 | t = sdscatvprintf(s,fmt,ap);
559 | va_end(ap);
560 | return t;
561 | }
562 |
563 | /* This function is similar to sdscatprintf, but much faster as it does
564 | * not rely on sprintf() family functions implemented by the libc that
565 | * are often very slow. Moreover directly handling the sds string as
566 | * new data is concatenated provides a performance improvement.
567 | *
568 | * However this function only handles an incompatible subset of printf-alike
569 | * format specifiers:
570 | *
571 | * %s - C String
572 | * %S - SDS string
573 | * %i - signed int
574 | * %I - 64 bit signed integer (long long, int64_t)
575 | * %u - unsigned int
576 | * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
577 | * %% - Verbatim "%" character.
578 | */
579 | sds sdscatfmt(sds s, char const *fmt, ...) {
580 | size_t initlen = sdslen(s);
581 | const char *f = fmt;
582 | int i;
583 | va_list ap;
584 |
585 | va_start(ap,fmt);
586 | f = fmt; /* Next format specifier byte to process. */
587 | i = initlen; /* Position of the next byte to write to dest str. */
588 | while(*f) {
589 | char next, *str;
590 | size_t l;
591 | long long num;
592 | unsigned long long unum;
593 |
594 | /* Make sure there is always space for at least 1 char. */
595 | if (sdsavail(s)==0) {
596 | s = sdsMakeRoomFor(s,1);
597 | }
598 |
599 | switch(*f) {
600 | case '%':
601 | next = *(f+1);
602 | f++;
603 | switch(next) {
604 | case 's':
605 | case 'S':
606 | str = va_arg(ap,char*);
607 | l = (next == 's') ? strlen(str) : sdslen(str);
608 | if (sdsavail(s) < l) {
609 | s = sdsMakeRoomFor(s,l);
610 | }
611 | memcpy(s+i,str,l);
612 | sdsinclen(s,l);
613 | i += l;
614 | break;
615 | case 'i':
616 | case 'I':
617 | if (next == 'i')
618 | num = va_arg(ap,int);
619 | else
620 | num = va_arg(ap,long long);
621 | {
622 | char buf[SDS_LLSTR_SIZE];
623 | l = sdsll2str(buf,num);
624 | if (sdsavail(s) < l) {
625 | s = sdsMakeRoomFor(s,l);
626 | }
627 | memcpy(s+i,buf,l);
628 | sdsinclen(s,l);
629 | i += l;
630 | }
631 | break;
632 | case 'u':
633 | case 'U':
634 | if (next == 'u')
635 | unum = va_arg(ap,unsigned int);
636 | else
637 | unum = va_arg(ap,unsigned long long);
638 | {
639 | char buf[SDS_LLSTR_SIZE];
640 | l = sdsull2str(buf,unum);
641 | if (sdsavail(s) < l) {
642 | s = sdsMakeRoomFor(s,l);
643 | }
644 | memcpy(s+i,buf,l);
645 | sdsinclen(s,l);
646 | i += l;
647 | }
648 | break;
649 | default: /* Handle %% and generally %. */
650 | s[i++] = next;
651 | sdsinclen(s,1);
652 | break;
653 | }
654 | break;
655 | default:
656 | s[i++] = *f;
657 | sdsinclen(s,1);
658 | break;
659 | }
660 | f++;
661 | }
662 | va_end(ap);
663 |
664 | /* Add null-term */
665 | s[i] = '\0';
666 | return s;
667 | }
668 |
669 | /* Remove the part of the string from left and from right composed just of
670 | * contiguous characters found in 'cset', that is a null terminted C string.
671 | *
672 | * After the call, the modified sds string is no longer valid and all the
673 | * references must be substituted with the new pointer returned by the call.
674 | *
675 | * Example:
676 | *
677 | * s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
678 | * s = sdstrim(s,"Aa. :");
679 | * printf("%s\n", s);
680 | *
681 | * Output will be just "Hello World".
682 | */
683 | sds sdstrim(sds s, const char *cset) {
684 | char *start, *end, *sp, *ep;
685 | size_t len;
686 |
687 | sp = start = s;
688 | ep = end = s+sdslen(s)-1;
689 | while(sp <= end && strchr(cset, *sp)) sp++;
690 | while(ep > sp && strchr(cset, *ep)) ep--;
691 | len = (sp > ep) ? 0 : ((ep-sp)+1);
692 | if (s != sp) memmove(s, sp, len);
693 | s[len] = '\0';
694 | sdssetlen(s,len);
695 | return s;
696 | }
697 |
698 | /* Turn the string into a smaller (or equal) string containing only the
699 | * substring specified by the 'start' and 'end' indexes.
700 | *
701 | * start and end can be negative, where -1 means the last character of the
702 | * string, -2 the penultimate character, and so forth.
703 | *
704 | * The interval is inclusive, so the start and end characters will be part
705 | * of the resulting string.
706 | *
707 | * The string is modified in-place.
708 | *
709 | * Example:
710 | *
711 | * s = sdsnew("Hello World");
712 | * sdsrange(s,1,-1); => "ello World"
713 | */
714 | void sdsrange(sds s, int start, int end) {
715 | size_t newlen, len = sdslen(s);
716 |
717 | if (len == 0) return;
718 | if (start < 0) {
719 | start = len+start;
720 | if (start < 0) start = 0;
721 | }
722 | if (end < 0) {
723 | end = len+end;
724 | if (end < 0) end = 0;
725 | }
726 | newlen = (start > end) ? 0 : (end-start)+1;
727 | if (newlen != 0) {
728 | if (start >= (signed)len) {
729 | newlen = 0;
730 | } else if (end >= (signed)len) {
731 | end = len-1;
732 | newlen = (start > end) ? 0 : (end-start)+1;
733 | }
734 | } else {
735 | start = 0;
736 | }
737 | if (start && newlen) memmove(s, s+start, newlen);
738 | s[newlen] = 0;
739 | sdssetlen(s,newlen);
740 | }
741 |
742 | /* Apply tolower() to every character of the sds string 's'. */
743 | void sdstolower(sds s) {
744 | int len = sdslen(s), j;
745 |
746 | for (j = 0; j < len; j++) s[j] = tolower(s[j]);
747 | }
748 |
749 | /* Apply toupper() to every character of the sds string 's'. */
750 | void sdstoupper(sds s) {
751 | int len = sdslen(s), j;
752 |
753 | for (j = 0; j < len; j++) s[j] = toupper(s[j]);
754 | }
755 |
756 | /* Compare two sds strings s1 and s2 with memcmp().
757 | *
758 | * Return value:
759 | *
760 | * positive if s1 > s2.
761 | * negative if s1 < s2.
762 | * 0 if s1 and s2 are exactly the same binary string.
763 | *
764 | * If two strings share exactly the same prefix, but one of the two has
765 | * additional characters, the longer string is considered to be greater than
766 | * the smaller one. */
767 | int sdscmp(const sds s1, const sds s2) {
768 | size_t l1, l2, minlen;
769 | int cmp;
770 |
771 | l1 = sdslen(s1);
772 | l2 = sdslen(s2);
773 | minlen = (l1 < l2) ? l1 : l2;
774 | cmp = memcmp(s1,s2,minlen);
775 | if (cmp == 0) return l1-l2;
776 | return cmp;
777 | }
778 |
779 | /* Split 's' with separator in 'sep'. An array
780 | * of sds strings is returned. *count will be set
781 | * by reference to the number of tokens returned.
782 | *
783 | * On out of memory, zero length string, zero length
784 | * separator, NULL is returned.
785 | *
786 | * Note that 'sep' is able to split a string using
787 | * a multi-character separator. For example
788 | * sdssplit("foo_-_bar","_-_"); will return two
789 | * elements "foo" and "bar".
790 | *
791 | * This version of the function is binary-safe but
792 | * requires length arguments. sdssplit() is just the
793 | * same function but for zero-terminated strings.
794 | */
795 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
796 | int elements = 0, slots = 5, start = 0, j;
797 | sds *tokens;
798 |
799 | if (seplen < 1 || len < 0) return NULL;
800 |
801 | tokens = s_malloc(sizeof(sds)*slots);
802 | if (tokens == NULL) return NULL;
803 |
804 | if (len == 0) {
805 | *count = 0;
806 | return tokens;
807 | }
808 | for (j = 0; j < (len-(seplen-1)); j++) {
809 | /* make sure there is room for the next element and the final one */
810 | if (slots < elements+2) {
811 | sds *newtokens;
812 |
813 | slots *= 2;
814 | newtokens = s_realloc(tokens,sizeof(sds)*slots);
815 | if (newtokens == NULL) goto cleanup;
816 | tokens = newtokens;
817 | }
818 | /* search the separator */
819 | if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
820 | tokens[elements] = sdsnewlen(s+start,j-start);
821 | if (tokens[elements] == NULL) goto cleanup;
822 | elements++;
823 | start = j+seplen;
824 | j = j+seplen-1; /* skip the separator */
825 | }
826 | }
827 | /* Add the final element. We are sure there is room in the tokens array. */
828 | tokens[elements] = sdsnewlen(s+start,len-start);
829 | if (tokens[elements] == NULL) goto cleanup;
830 | elements++;
831 | *count = elements;
832 | return tokens;
833 |
834 | cleanup:
835 | {
836 | int i;
837 | for (i = 0; i < elements; i++) sdsfree(tokens[i]);
838 | s_free(tokens);
839 | *count = 0;
840 | return NULL;
841 | }
842 | }
843 |
844 | /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
845 | void sdsfreesplitres(sds *tokens, int count) {
846 | if (!tokens) return;
847 | while(count--)
848 | sdsfree(tokens[count]);
849 | s_free(tokens);
850 | }
851 |
852 | /* Append to the sds string "s" an escaped string representation where
853 | * all the non-printable characters (tested with isprint()) are turned into
854 | * escapes in the form "\n\r\a...." or "\x".
855 | *
856 | * After the call, the modified sds string is no longer valid and all the
857 | * references must be substituted with the new pointer returned by the call. */
858 | sds sdscatrepr(sds s, const char *p, size_t len) {
859 | s = sdscatlen(s,"\"",1);
860 | while(len--) {
861 | switch(*p) {
862 | case '\\':
863 | case '"':
864 | s = sdscatprintf(s,"\\%c",*p);
865 | break;
866 | case '\n': s = sdscatlen(s,"\\n",2); break;
867 | case '\r': s = sdscatlen(s,"\\r",2); break;
868 | case '\t': s = sdscatlen(s,"\\t",2); break;
869 | case '\a': s = sdscatlen(s,"\\a",2); break;
870 | case '\b': s = sdscatlen(s,"\\b",2); break;
871 | default:
872 | if (isprint(*p))
873 | s = sdscatprintf(s,"%c",*p);
874 | else
875 | s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
876 | break;
877 | }
878 | p++;
879 | }
880 | return sdscatlen(s,"\"",1);
881 | }
882 |
883 | /* Helper function for sdssplitargs() that returns non zero if 'c'
884 | * is a valid hex digit. */
885 | int is_hex_digit(char c) {
886 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
887 | (c >= 'A' && c <= 'F');
888 | }
889 |
890 | /* Helper function for sdssplitargs() that converts a hex digit into an
891 | * integer from 0 to 15 */
892 | int hex_digit_to_int(char c) {
893 | switch(c) {
894 | case '0': return 0;
895 | case '1': return 1;
896 | case '2': return 2;
897 | case '3': return 3;
898 | case '4': return 4;
899 | case '5': return 5;
900 | case '6': return 6;
901 | case '7': return 7;
902 | case '8': return 8;
903 | case '9': return 9;
904 | case 'a': case 'A': return 10;
905 | case 'b': case 'B': return 11;
906 | case 'c': case 'C': return 12;
907 | case 'd': case 'D': return 13;
908 | case 'e': case 'E': return 14;
909 | case 'f': case 'F': return 15;
910 | default: return 0;
911 | }
912 | }
913 |
914 | /* Split a line into arguments, where every argument can be in the
915 | * following programming-language REPL-alike form:
916 | *
917 | * foo bar "newline are supported\n" and "\xff\x00otherstuff"
918 | *
919 | * The number of arguments is stored into *argc, and an array
920 | * of sds is returned.
921 | *
922 | * The caller should free the resulting array of sds strings with
923 | * sdsfreesplitres().
924 | *
925 | * Note that sdscatrepr() is able to convert back a string into
926 | * a quoted string in the same format sdssplitargs() is able to parse.
927 | *
928 | * The function returns the allocated tokens on success, even when the
929 | * input string is empty, or NULL if the input contains unbalanced
930 | * quotes or closed quotes followed by non space characters
931 | * as in: "foo"bar or "foo'
932 | */
933 | sds *sdssplitargs(const char *line, int *argc) {
934 | const char *p = line;
935 | char *current = NULL;
936 | char **vector = NULL;
937 |
938 | *argc = 0;
939 | while(1) {
940 | /* skip blanks */
941 | while(*p && isspace(*p)) p++;
942 | if (*p) {
943 | /* get a token */
944 | int inq=0; /* set to 1 if we are in "quotes" */
945 | int insq=0; /* set to 1 if we are in 'single quotes' */
946 | int done=0;
947 |
948 | if (current == NULL) current = sdsempty();
949 | while(!done) {
950 | if (inq) {
951 | if (*p == '\\' && *(p+1) == 'x' &&
952 | is_hex_digit(*(p+2)) &&
953 | is_hex_digit(*(p+3)))
954 | {
955 | unsigned char byte;
956 |
957 | byte = (hex_digit_to_int(*(p+2))*16)+
958 | hex_digit_to_int(*(p+3));
959 | current = sdscatlen(current,(char*)&byte,1);
960 | p += 3;
961 | } else if (*p == '\\' && *(p+1)) {
962 | char c;
963 |
964 | p++;
965 | switch(*p) {
966 | case 'n': c = '\n'; break;
967 | case 'r': c = '\r'; break;
968 | case 't': c = '\t'; break;
969 | case 'b': c = '\b'; break;
970 | case 'a': c = '\a'; break;
971 | default: c = *p; break;
972 | }
973 | current = sdscatlen(current,&c,1);
974 | } else if (*p == '"') {
975 | /* closing quote must be followed by a space or
976 | * nothing at all. */
977 | if (*(p+1) && !isspace(*(p+1))) goto err;
978 | done=1;
979 | } else if (!*p) {
980 | /* unterminated quotes */
981 | goto err;
982 | } else {
983 | current = sdscatlen(current,p,1);
984 | }
985 | } else if (insq) {
986 | if (*p == '\\' && *(p+1) == '\'') {
987 | p++;
988 | current = sdscatlen(current,"'",1);
989 | } else if (*p == '\'') {
990 | /* closing quote must be followed by a space or
991 | * nothing at all. */
992 | if (*(p+1) && !isspace(*(p+1))) goto err;
993 | done=1;
994 | } else if (!*p) {
995 | /* unterminated quotes */
996 | goto err;
997 | } else {
998 | current = sdscatlen(current,p,1);
999 | }
1000 | } else {
1001 | switch(*p) {
1002 | case ' ':
1003 | case '\n':
1004 | case '\r':
1005 | case '\t':
1006 | case '\0':
1007 | done=1;
1008 | break;
1009 | case '"':
1010 | inq=1;
1011 | break;
1012 | case '\'':
1013 | insq=1;
1014 | break;
1015 | default:
1016 | current = sdscatlen(current,p,1);
1017 | break;
1018 | }
1019 | }
1020 | if (*p) p++;
1021 | }
1022 | /* add the token to the vector */
1023 | vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
1024 | vector[*argc] = current;
1025 | (*argc)++;
1026 | current = NULL;
1027 | } else {
1028 | /* Even on empty input string return something not NULL. */
1029 | if (vector == NULL) vector = s_malloc(sizeof(void*));
1030 | return vector;
1031 | }
1032 | }
1033 |
1034 | err:
1035 | while((*argc)--)
1036 | sdsfree(vector[*argc]);
1037 | s_free(vector);
1038 | if (current) sdsfree(current);
1039 | *argc = 0;
1040 | return NULL;
1041 | }
1042 |
1043 | /* Modify the string substituting all the occurrences of the set of
1044 | * characters specified in the 'from' string to the corresponding character
1045 | * in the 'to' array.
1046 | *
1047 | * For instance: sdsmapchars(mystring, "ho", "01", 2)
1048 | * will have the effect of turning the string "hello" into "0ell1".
1049 | *
1050 | * The function returns the sds string pointer, that is always the same
1051 | * as the input pointer since no resize is needed. */
1052 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
1053 | size_t j, i, l = sdslen(s);
1054 |
1055 | for (j = 0; j < l; j++) {
1056 | for (i = 0; i < setlen; i++) {
1057 | if (s[j] == from[i]) {
1058 | s[j] = to[i];
1059 | break;
1060 | }
1061 | }
1062 | }
1063 | return s;
1064 | }
1065 |
1066 | /* Join an array of C strings using the specified separator (also a C string).
1067 | * Returns the result as an sds string. */
1068 | sds sdsjoin(char **argv, int argc, char *sep) {
1069 | sds join = sdsempty();
1070 | int j;
1071 |
1072 | for (j = 0; j < argc; j++) {
1073 | join = sdscat(join, argv[j]);
1074 | if (j != argc-1) join = sdscat(join,sep);
1075 | }
1076 | return join;
1077 | }
1078 |
1079 | /* Like sdsjoin, but joins an array of SDS strings. */
1080 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
1081 | sds join = sdsempty();
1082 | int j;
1083 |
1084 | for (j = 0; j < argc; j++) {
1085 | join = sdscatsds(join, argv[j]);
1086 | if (j != argc-1) join = sdscatlen(join,sep,seplen);
1087 | }
1088 | return join;
1089 | }
1090 |
1091 | /* Wrappers to the allocators used by SDS. Note that SDS will actually
1092 | * just use the macros defined into sdsalloc.h in order to avoid to pay
1093 | * the overhead of function calls. Here we define these wrappers only for
1094 | * the programs SDS is linked to, if they want to touch the SDS internals
1095 | * even if they use a different allocator. */
1096 | void *sds_malloc(size_t size) { return s_malloc(size); }
1097 | void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); }
1098 | void sds_free(void *ptr) { s_free(ptr); }
1099 |
1100 | #if defined(SDS_TEST_MAIN)
1101 | #include
1102 | #include "testhelp.h"
1103 | #include "limits.h"
1104 |
1105 | #define UNUSED(x) (void)(x)
1106 | int sdsTest(void) {
1107 | {
1108 | sds x = sdsnew("foo"), y;
1109 |
1110 | test_cond("Create a string and obtain the length",
1111 | sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
1112 |
1113 | sdsfree(x);
1114 | x = sdsnewlen("foo",2);
1115 | test_cond("Create a string with specified length",
1116 | sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
1117 |
1118 | x = sdscat(x,"bar");
1119 | test_cond("Strings concatenation",
1120 | sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
1121 |
1122 | x = sdscpy(x,"a");
1123 | test_cond("sdscpy() against an originally longer string",
1124 | sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
1125 |
1126 | x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
1127 | test_cond("sdscpy() against an originally shorter string",
1128 | sdslen(x) == 33 &&
1129 | memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
1130 |
1131 | sdsfree(x);
1132 | x = sdscatprintf(sdsempty(),"%d",123);
1133 | test_cond("sdscatprintf() seems working in the base case",
1134 | sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
1135 |
1136 | sdsfree(x);
1137 | x = sdsnew("--");
1138 | x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);
1139 | test_cond("sdscatfmt() seems working in the base case",
1140 | sdslen(x) == 60 &&
1141 | memcmp(x,"--Hello Hi! World -9223372036854775808,"
1142 | "9223372036854775807--",60) == 0)
1143 | printf("[%s]\n",x);
1144 |
1145 | sdsfree(x);
1146 | x = sdsnew("--");
1147 | x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
1148 | test_cond("sdscatfmt() seems working with unsigned numbers",
1149 | sdslen(x) == 35 &&
1150 | memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
1151 |
1152 | sdsfree(x);
1153 | x = sdsnew(" x ");
1154 | sdstrim(x," x");
1155 | test_cond("sdstrim() works when all chars match",
1156 | sdslen(x) == 0)
1157 |
1158 | sdsfree(x);
1159 | x = sdsnew(" x ");
1160 | sdstrim(x," ");
1161 | test_cond("sdstrim() works when a single char remains",
1162 | sdslen(x) == 1 && x[0] == 'x')
1163 |
1164 | sdsfree(x);
1165 | x = sdsnew("xxciaoyyy");
1166 | sdstrim(x,"xy");
1167 | test_cond("sdstrim() correctly trims characters",
1168 | sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
1169 |
1170 | y = sdsdup(x);
1171 | sdsrange(y,1,1);
1172 | test_cond("sdsrange(...,1,1)",
1173 | sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
1174 |
1175 | sdsfree(y);
1176 | y = sdsdup(x);
1177 | sdsrange(y,1,-1);
1178 | test_cond("sdsrange(...,1,-1)",
1179 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
1180 |
1181 | sdsfree(y);
1182 | y = sdsdup(x);
1183 | sdsrange(y,-2,-1);
1184 | test_cond("sdsrange(...,-2,-1)",
1185 | sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
1186 |
1187 | sdsfree(y);
1188 | y = sdsdup(x);
1189 | sdsrange(y,2,1);
1190 | test_cond("sdsrange(...,2,1)",
1191 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
1192 |
1193 | sdsfree(y);
1194 | y = sdsdup(x);
1195 | sdsrange(y,1,100);
1196 | test_cond("sdsrange(...,1,100)",
1197 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
1198 |
1199 | sdsfree(y);
1200 | y = sdsdup(x);
1201 | sdsrange(y,100,100);
1202 | test_cond("sdsrange(...,100,100)",
1203 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
1204 |
1205 | sdsfree(y);
1206 | sdsfree(x);
1207 | x = sdsnew("foo");
1208 | y = sdsnew("foa");
1209 | test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
1210 |
1211 | sdsfree(y);
1212 | sdsfree(x);
1213 | x = sdsnew("bar");
1214 | y = sdsnew("bar");
1215 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
1216 |
1217 | sdsfree(y);
1218 | sdsfree(x);
1219 | x = sdsnew("aar");
1220 | y = sdsnew("bar");
1221 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
1222 |
1223 | sdsfree(y);
1224 | sdsfree(x);
1225 | x = sdsnewlen("\a\n\0foo\r",7);
1226 | y = sdscatrepr(sdsempty(),x,sdslen(x));
1227 | test_cond("sdscatrepr(...data...)",
1228 | memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
1229 |
1230 | {
1231 | unsigned int oldfree;
1232 | char *p;
1233 | int step = 10, j, i;
1234 |
1235 | sdsfree(x);
1236 | sdsfree(y);
1237 | x = sdsnew("0");
1238 | test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0);
1239 |
1240 | /* Run the test a few times in order to hit the first two
1241 | * SDS header types. */
1242 | for (i = 0; i < 10; i++) {
1243 | int oldlen = sdslen(x);
1244 | x = sdsMakeRoomFor(x,step);
1245 | int type = x[-1]&SDS_TYPE_MASK;
1246 |
1247 | test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen);
1248 | if (type != SDS_TYPE_5) {
1249 | test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step);
1250 | oldfree = sdsavail(x);
1251 | }
1252 | p = x+oldlen;
1253 | for (j = 0; j < step; j++) {
1254 | p[j] = 'A'+j;
1255 | }
1256 | sdsIncrLen(x,step);
1257 | }
1258 | test_cond("sdsMakeRoomFor() content",
1259 | memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0);
1260 | test_cond("sdsMakeRoomFor() final length",sdslen(x)==101);
1261 |
1262 | sdsfree(x);
1263 | }
1264 | }
1265 | test_report()
1266 | return 0;
1267 | }
1268 | #endif
1269 |
1270 | #ifdef SDS_TEST_MAIN
1271 | int main(void) {
1272 | return sdsTest();
1273 | }
1274 | #endif
1275 |
--------------------------------------------------------------------------------
/rmutil/sds.h:
--------------------------------------------------------------------------------
1 | /* SDSLib 2.0 -- A C dynamic strings library
2 | *
3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo
4 | * Copyright (c) 2015, Oran Agra
5 | * Copyright (c) 2015, Redis Labs, Inc
6 | * All rights reserved.
7 | *
8 | * Redistribution and use in source and binary forms, with or without
9 | * modification, are permitted provided that the following conditions are met:
10 | *
11 | * * Redistributions of source code must retain the above copyright notice,
12 | * this list of conditions and the following disclaimer.
13 | * * Redistributions in binary form must reproduce the above copyright
14 | * notice, this list of conditions and the following disclaimer in the
15 | * documentation and/or other materials provided with the distribution.
16 | * * Neither the name of Redis nor the names of its contributors may be used
17 | * to endorse or promote products derived from this software without
18 | * specific prior written permission.
19 | *
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 | * POSSIBILITY OF SUCH DAMAGE.
31 | */
32 |
33 | #ifndef __SDS_H
34 | #define __SDS_H
35 |
36 | #define SDS_MAX_PREALLOC (1024*1024)
37 |
38 | #include
39 | #include
40 | #include
41 |
42 | typedef char *sds;
43 |
44 | /* Note: sdshdr5 is never used, we just access the flags byte directly.
45 | * However is here to document the layout of type 5 SDS strings. */
46 | struct __attribute__ ((__packed__)) sdshdr5 {
47 | unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
48 | char buf[];
49 | };
50 | struct __attribute__ ((__packed__)) sdshdr8 {
51 | uint8_t len; /* used */
52 | uint8_t alloc; /* excluding the header and null terminator */
53 | unsigned char flags; /* 3 lsb of type, 5 unused bits */
54 | char buf[];
55 | };
56 | struct __attribute__ ((__packed__)) sdshdr16 {
57 | uint16_t len; /* used */
58 | uint16_t alloc; /* excluding the header and null terminator */
59 | unsigned char flags; /* 3 lsb of type, 5 unused bits */
60 | char buf[];
61 | };
62 | struct __attribute__ ((__packed__)) sdshdr32 {
63 | uint32_t len; /* used */
64 | uint32_t alloc; /* excluding the header and null terminator */
65 | unsigned char flags; /* 3 lsb of type, 5 unused bits */
66 | char buf[];
67 | };
68 | struct __attribute__ ((__packed__)) sdshdr64 {
69 | uint64_t len; /* used */
70 | uint64_t alloc; /* excluding the header and null terminator */
71 | unsigned char flags; /* 3 lsb of type, 5 unused bits */
72 | char buf[];
73 | };
74 |
75 | #define SDS_TYPE_5 0
76 | #define SDS_TYPE_8 1
77 | #define SDS_TYPE_16 2
78 | #define SDS_TYPE_32 3
79 | #define SDS_TYPE_64 4
80 | #define SDS_TYPE_MASK 7
81 | #define SDS_TYPE_BITS 3
82 | #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
83 | #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
84 | #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
85 |
86 | static inline size_t sdslen(const sds s) {
87 | unsigned char flags = s[-1];
88 | switch(flags&SDS_TYPE_MASK) {
89 | case SDS_TYPE_5:
90 | return SDS_TYPE_5_LEN(flags);
91 | case SDS_TYPE_8:
92 | return SDS_HDR(8,s)->len;
93 | case SDS_TYPE_16:
94 | return SDS_HDR(16,s)->len;
95 | case SDS_TYPE_32:
96 | return SDS_HDR(32,s)->len;
97 | case SDS_TYPE_64:
98 | return SDS_HDR(64,s)->len;
99 | }
100 | return 0;
101 | }
102 |
103 | static inline size_t sdsavail(const sds s) {
104 | unsigned char flags = s[-1];
105 | switch(flags&SDS_TYPE_MASK) {
106 | case SDS_TYPE_5: {
107 | return 0;
108 | }
109 | case SDS_TYPE_8: {
110 | SDS_HDR_VAR(8,s);
111 | return sh->alloc - sh->len;
112 | }
113 | case SDS_TYPE_16: {
114 | SDS_HDR_VAR(16,s);
115 | return sh->alloc - sh->len;
116 | }
117 | case SDS_TYPE_32: {
118 | SDS_HDR_VAR(32,s);
119 | return sh->alloc - sh->len;
120 | }
121 | case SDS_TYPE_64: {
122 | SDS_HDR_VAR(64,s);
123 | return sh->alloc - sh->len;
124 | }
125 | }
126 | return 0;
127 | }
128 |
129 | static inline void sdssetlen(sds s, size_t newlen) {
130 | unsigned char flags = s[-1];
131 | switch(flags&SDS_TYPE_MASK) {
132 | case SDS_TYPE_5:
133 | {
134 | unsigned char *fp = ((unsigned char*)s)-1;
135 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
136 | }
137 | break;
138 | case SDS_TYPE_8:
139 | SDS_HDR(8,s)->len = newlen;
140 | break;
141 | case SDS_TYPE_16:
142 | SDS_HDR(16,s)->len = newlen;
143 | break;
144 | case SDS_TYPE_32:
145 | SDS_HDR(32,s)->len = newlen;
146 | break;
147 | case SDS_TYPE_64:
148 | SDS_HDR(64,s)->len = newlen;
149 | break;
150 | }
151 | }
152 |
153 | static inline void sdsinclen(sds s, size_t inc) {
154 | unsigned char flags = s[-1];
155 | switch(flags&SDS_TYPE_MASK) {
156 | case SDS_TYPE_5:
157 | {
158 | unsigned char *fp = ((unsigned char*)s)-1;
159 | unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
160 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
161 | }
162 | break;
163 | case SDS_TYPE_8:
164 | SDS_HDR(8,s)->len += inc;
165 | break;
166 | case SDS_TYPE_16:
167 | SDS_HDR(16,s)->len += inc;
168 | break;
169 | case SDS_TYPE_32:
170 | SDS_HDR(32,s)->len += inc;
171 | break;
172 | case SDS_TYPE_64:
173 | SDS_HDR(64,s)->len += inc;
174 | break;
175 | }
176 | }
177 |
178 | /* sdsalloc() = sdsavail() + sdslen() */
179 | static inline size_t sdsalloc(const sds s) {
180 | unsigned char flags = s[-1];
181 | switch(flags&SDS_TYPE_MASK) {
182 | case SDS_TYPE_5:
183 | return SDS_TYPE_5_LEN(flags);
184 | case SDS_TYPE_8:
185 | return SDS_HDR(8,s)->alloc;
186 | case SDS_TYPE_16:
187 | return SDS_HDR(16,s)->alloc;
188 | case SDS_TYPE_32:
189 | return SDS_HDR(32,s)->alloc;
190 | case SDS_TYPE_64:
191 | return SDS_HDR(64,s)->alloc;
192 | }
193 | return 0;
194 | }
195 |
196 | static inline void sdssetalloc(sds s, size_t newlen) {
197 | unsigned char flags = s[-1];
198 | switch(flags&SDS_TYPE_MASK) {
199 | case SDS_TYPE_5:
200 | /* Nothing to do, this type has no total allocation info. */
201 | break;
202 | case SDS_TYPE_8:
203 | SDS_HDR(8,s)->alloc = newlen;
204 | break;
205 | case SDS_TYPE_16:
206 | SDS_HDR(16,s)->alloc = newlen;
207 | break;
208 | case SDS_TYPE_32:
209 | SDS_HDR(32,s)->alloc = newlen;
210 | break;
211 | case SDS_TYPE_64:
212 | SDS_HDR(64,s)->alloc = newlen;
213 | break;
214 | }
215 | }
216 |
217 | sds sdsnewlen(const void *init, size_t initlen);
218 | sds sdsnew(const char *init);
219 | sds sdsempty(void);
220 | sds sdsdup(const sds s);
221 | void sdsfree(sds s);
222 | sds sdsgrowzero(sds s, size_t len);
223 | sds sdscatlen(sds s, const void *t, size_t len);
224 | sds sdscat(sds s, const char *t);
225 | sds sdscatsds(sds s, const sds t);
226 | sds sdscpylen(sds s, const char *t, size_t len);
227 | sds sdscpy(sds s, const char *t);
228 |
229 | sds sdscatvprintf(sds s, const char *fmt, va_list ap);
230 | #ifdef __GNUC__
231 | sds sdscatprintf(sds s, const char *fmt, ...)
232 | __attribute__((format(printf, 2, 3)));
233 | #else
234 | sds sdscatprintf(sds s, const char *fmt, ...);
235 | #endif
236 |
237 | sds sdscatfmt(sds s, char const *fmt, ...);
238 | sds sdstrim(sds s, const char *cset);
239 | void sdsrange(sds s, int start, int end);
240 | void sdsupdatelen(sds s);
241 | void sdsclear(sds s);
242 | int sdscmp(const sds s1, const sds s2);
243 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
244 | void sdsfreesplitres(sds *tokens, int count);
245 | void sdstolower(sds s);
246 | void sdstoupper(sds s);
247 | sds sdsfromlonglong(long long value);
248 | sds sdscatrepr(sds s, const char *p, size_t len);
249 | sds *sdssplitargs(const char *line, int *argc);
250 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
251 | sds sdsjoin(char **argv, int argc, char *sep);
252 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
253 |
254 | /* Low level functions exposed to the user API */
255 | sds sdsMakeRoomFor(sds s, size_t addlen);
256 | void sdsIncrLen(sds s, int incr);
257 | sds sdsRemoveFreeSpace(sds s);
258 | size_t sdsAllocSize(sds s);
259 | void *sdsAllocPtr(sds s);
260 |
261 | /* Export the allocator used by SDS to the program using SDS.
262 | * Sometimes the program SDS is linked to, may use a different set of
263 | * allocators, but may want to allocate or free things that SDS will
264 | * respectively free or allocate. */
265 | void *sds_malloc(size_t size);
266 | void *sds_realloc(void *ptr, size_t size);
267 | void sds_free(void *ptr);
268 |
269 | #ifdef REDIS_TEST
270 | int sdsTest(int argc, char *argv[]);
271 | #endif
272 |
273 | #endif
274 |
--------------------------------------------------------------------------------
/rmutil/sdsalloc.h:
--------------------------------------------------------------------------------
1 | /* SDSLib 2.0 -- A C dynamic strings library
2 | *
3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo
4 | * Copyright (c) 2015, Redis Labs, Inc
5 | * All rights reserved.
6 | *
7 | * Redistribution and use in source and binary forms, with or without
8 | * modification, are permitted provided that the following conditions are met:
9 | *
10 | * * Redistributions of source code must retain the above copyright notice,
11 | * this list of conditions and the following disclaimer.
12 | * * Redistributions in binary form must reproduce the above copyright
13 | * notice, this list of conditions and the following disclaimer in the
14 | * documentation and/or other materials provided with the distribution.
15 | * * Neither the name of Redis nor the names of its contributors may be used
16 | * to endorse or promote products derived from this software without
17 | * specific prior written permission.
18 | *
19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | * POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | /* SDS allocator selection.
33 | *
34 | * This file is used in order to change the SDS allocator at compile time.
35 | * Just define the following defines to what you want to use. Also add
36 | * the include of your alternate allocator if needed (not needed in order
37 | * to use the default libc allocator). */
38 |
39 | //#include "zmalloc.h"
40 | #define s_malloc malloc
41 | #define s_realloc realloc
42 | #define s_free free
43 |
--------------------------------------------------------------------------------
/rmutil/strings.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "strings.h"
5 |
6 |
7 | #include "sds.h"
8 |
9 | RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...) {
10 | sds s = sdsempty();
11 |
12 | va_list ap;
13 | va_start(ap, fmt);
14 | s = sdscatvprintf(s, fmt, ap);
15 | va_end(ap);
16 |
17 | RedisModuleString *ret = RedisModule_CreateString(ctx, (const char *)s, sdslen(s));
18 | sdsfree(s);
19 | return ret;
20 | }
21 |
22 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2) {
23 |
24 |
25 | const char *c1, *c2;
26 | size_t l1, l2;
27 | c1 = RedisModule_StringPtrLen(s1, &l1);
28 | c2 = RedisModule_StringPtrLen(s2, &l2);
29 |
30 | return strcmp(c1, c2) == 0;
31 | }
32 |
33 | void RMUtil_StringToLower(RedisModuleString *s) {
34 |
35 | size_t l;
36 | char *c = (char *)RedisModule_StringPtrLen(s, &l);
37 | size_t i;
38 | for (i = 0; i < l; i++) {
39 | *c = tolower(*c);
40 | ++c;
41 | }
42 |
43 | }
44 |
45 | void RMUtil_StringToUpper(RedisModuleString *s) {
46 | size_t l;
47 | char *c = (char *)RedisModule_StringPtrLen(s, &l);
48 | size_t i;
49 | for (i = 0; i < l; i++) {
50 | *c = toupper(*c);
51 | ++c;
52 | }
53 |
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/rmutil/strings.h:
--------------------------------------------------------------------------------
1 | #ifndef __RMUTIL_STRINGS_H__
2 | #define __RMUTIL_STRINGS_H__
3 |
4 | #include
5 |
6 | /*
7 | * Create a new RedisModuleString object from a printf-style format and arguments.
8 | * Note that RedisModuleString objects CANNOT be used as formatting arguments.
9 | */
10 | RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...);
11 |
12 | /* Return 1 if the two strings are equal. Case *sensitive* */
13 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2);
14 |
15 | /* Converts a redis string to lowercase in place without reallocating anything */
16 | void RMUtil_StringToLower(RedisModuleString *s);
17 |
18 | /* Converts a redis string to uppercase in place without reallocating anything */
19 | void RMUtil_StringToUpper(RedisModuleString *s);
20 | #endif
21 |
--------------------------------------------------------------------------------
/rmutil/test_util.h:
--------------------------------------------------------------------------------
1 | #ifndef __TEST_UTIL_H__
2 | #define __TEST_UTIL_H__
3 |
4 | #include "util.h"
5 | #include
6 | #include
7 | #include
8 |
9 |
10 | #define RMUtil_Test(f) \
11 | if (argc < 2 || RMUtil_ArgExists(__STRING(f), argv, argc, 1)) { \
12 | int rc = f(ctx); \
13 | if (rc != REDISMODULE_OK) { \
14 | RedisModule_ReplyWithError(ctx, "Test " __STRING(f) " FAILED"); \
15 | return REDISMODULE_ERR;\
16 | }\
17 | }
18 |
19 |
20 | #define RMUtil_Assert(expr) if (!(expr)) { fprintf (stderr, "Assertion '%s' Failed\n", __STRING(expr)); return REDISMODULE_ERR; }
21 |
22 | #define RMUtil_AssertReplyEquals(rep, cstr) RMUtil_Assert( \
23 | RMUtil_StringEquals(RedisModule_CreateStringFromCallReply(rep), RedisModule_CreateString(ctx, cstr, strlen(cstr))) \
24 | )
25 | #
26 |
27 | /**
28 | * Create an arg list to pass to a redis command handler manually, based on the format in fmt.
29 | * The accepted format specifiers are:
30 | * c - for null terminated c strings
31 | * s - for RedisModuleString* objects
32 | * l - for longs
33 | *
34 | * Example: RMUtil_MakeArgs(ctx, &argc, "clc", "hello", 1337, "world");
35 | *
36 | * Returns an array of RedisModuleString pointers. The size of the array is store in argcp
37 | */
38 | RedisModuleString **RMUtil_MakeArgs(RedisModuleCtx *ctx, int *argcp, const char *fmt, ...) {
39 |
40 | va_list ap;
41 | va_start(ap, fmt);
42 | RedisModuleString **argv = calloc(strlen(fmt), sizeof(RedisModuleString*));
43 | int argc = 0;
44 | const char *p = fmt;
45 | while(*p) {
46 | if (*p == 'c') {
47 | char *cstr = va_arg(ap,char*);
48 | argv[argc++] = RedisModule_CreateString(ctx, cstr, strlen(cstr));
49 | } else if (*p == 's') {
50 | argv[argc++] = va_arg(ap,void*);;
51 | } else if (*p == 'l') {
52 | long ll = va_arg(ap,long long);
53 | argv[argc++] = RedisModule_CreateStringFromLongLong(ctx, ll);
54 | } else {
55 | goto fmterr;
56 | }
57 | p++;
58 | }
59 | *argcp = argc;
60 |
61 | return argv;
62 | fmterr:
63 | free(argv);
64 | return NULL;
65 | }
66 |
67 | #endif
--------------------------------------------------------------------------------
/rmutil/test_vector.c:
--------------------------------------------------------------------------------
1 | #include "vector.h"
2 | #include
3 | #include "assert.h"
4 |
5 | int main(int argc, char **argv) {
6 |
7 |
8 | Vector *v = NewVector(int, 1);
9 |
10 | // Vector_Put(v, 0, 1);
11 | // Vector_Put(v, 1, 3);
12 | for (int i = 0; i < 10; i++) {
13 | Vector_Push(v, i);
14 | }
15 |
16 | for (int i = 0; i < Vector_Size(v); i++) {
17 | int n;
18 | int rc = Vector_Get(v, i, &n);
19 | printf("%d %d\n", rc, n);
20 | assert ( 1== rc );
21 | assert (n == i);
22 | }
23 |
24 | Vector_Free(v);
25 |
26 | v = NewVector(char *, 0);
27 | int N = 4;
28 | char *strings[4] = {"hello", "world", "foo", "bar"};
29 |
30 | for (int i = 0; i < N; i++) {
31 | Vector_Push(v, strings[i]);
32 | }
33 | assert(Vector_Size(v) == N);
34 | assert(Vector_Cap(v) >= N);
35 |
36 | for (size_t i = 0; i < Vector_Size(v); i++) {
37 | char *x;
38 | int rc = Vector_Get(v, i, &x);
39 | assert (rc == 1);
40 | assert (!strcmp(x, strings[i]));
41 | }
42 |
43 | int rc = Vector_Get(v, 100, NULL);
44 | assert (rc == 0);
45 |
46 | Vector_Free(v);
47 | printf("PASS!");
48 |
49 | return 0;
50 | //Vector_Push(v, "hello");
51 | //Vector_Push(v, "world");
52 | // char *x = NULL;
53 | // int rc = Vector_Getx(v, 0, &x);
54 | // printf("rc: %d got %s\n", rc, x);
55 |
56 | }
--------------------------------------------------------------------------------
/rmutil/util.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include "util.h"
11 |
12 | /**
13 | Check if an argument exists in an argument list (argv,argc), starting at offset.
14 | @return 0 if it doesn't exist, otherwise the offset it exists in
15 | */
16 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset) {
17 |
18 | for (; offset < argc; offset++) {
19 | size_t l;
20 | const char *carg = RedisModule_StringPtrLen(argv[offset], &l);
21 | if (carg != NULL && strncasecmp(carg, arg, l) == 0) {
22 | return offset;
23 | }
24 | }
25 | return 0;
26 | }
27 |
28 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx) {
29 |
30 | RedisModuleCallReply *r = RedisModule_Call(ctx, "INFO", "c", "all");
31 | if (r == NULL || RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) {
32 | return NULL;
33 | }
34 |
35 |
36 | int cap = 100; // rough estimate of info lines
37 | RMUtilInfo *info = malloc(sizeof(RMUtilInfo));
38 | info->entries = calloc(cap, sizeof(RMUtilInfoEntry));
39 |
40 |
41 | int i = 0;
42 | char *text = (char *)RedisModule_StringPtrLen(RedisModule_CreateStringFromCallReply(r), NULL);
43 | char *line = text;
44 | while (line) {
45 | char *line = strsep(&text, "\r\n");
46 | if (line == NULL) break;
47 |
48 | if (!(*line >= 'a' && *line <= 'z')) { //skip non entry lines
49 | continue;
50 | }
51 |
52 | char *key = strsep(&line, ":");
53 | info->entries[i].key = key;
54 | info->entries[i].val = line;
55 | printf("Got info '%s' = '%s'\n", key, line);
56 | i++;
57 | if (i >= cap) {
58 | cap *=2;
59 | info->entries = realloc(info->entries, cap*sizeof(RMUtilInfoEntry));
60 | }
61 | }
62 | info->numEntries = i;
63 |
64 | return info;
65 |
66 | }
67 | void RMUtilRedisInfo_Free(RMUtilInfo *info) {
68 |
69 | free(info->entries);
70 | free(info);
71 |
72 | }
73 |
74 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val) {
75 |
76 | const char *p = NULL;
77 | if (!RMUtilInfo_GetString(info, key, &p)) {
78 | return 0;
79 | }
80 |
81 | *val = strtoll(p, NULL, 10);
82 | if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN)) ||
83 | (errno != 0 && *val == 0)) {
84 | *val = -1;
85 | return 0;
86 | }
87 |
88 |
89 | return 1;
90 | }
91 |
92 |
93 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str) {
94 | int i;
95 | for (i = 0; i < info->numEntries; i++) {
96 | if (!strcmp(key, info->entries[i].key)) {
97 | *str = info->entries[i].val;
98 | return 1;
99 | }
100 | }
101 | return 0;
102 | }
103 |
104 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d) {
105 | const char *p = NULL;
106 | if (!RMUtilInfo_GetString(info, key, &p)) {
107 | printf("not found %s\n", key);
108 | return 0;
109 | }
110 |
111 | *d = strtod(p, NULL);
112 | printf("p: %s, d: %f\n",p,*d);
113 | if ((errno == ERANGE && (*d == HUGE_VAL || *d == -HUGE_VAL)) ||
114 | (errno != 0 && *d == 0)) {
115 | return 0;
116 | }
117 |
118 |
119 | return 1;
120 | }
121 |
122 |
123 | /*
124 | c -- pointer to a Null terminated C string pointer.
125 | s -- pointer to a RedisModuleString
126 | l -- pointer to Long long integer.
127 | d -- pointer to a Double
128 | * -- do not parse this argument at all
129 | */
130 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...) {
131 | va_list ap;
132 | va_start(ap, fmt);
133 | int rc = rmutil_vparseArgs(argv, argc, offset, fmt, ap);
134 | va_end(ap);
135 | return rc;
136 | }
137 |
138 |
139 | // Internal function that parses arguments based on the format described above
140 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap) {
141 |
142 | int i = offset;
143 | char *c = (char *)fmt;
144 | while (*c && i < argc) {
145 |
146 | // read c string
147 | if (*c == 'c') {
148 | char **p = va_arg(ap, char**);
149 | *p = (char *)RedisModule_StringPtrLen(argv[i], NULL);
150 | } else if (*c == 's') { //read redis string
151 |
152 | RedisModuleString **s = va_arg(ap, void*);
153 | *s = argv[i];
154 |
155 | } else if (*c == 'l') { //read long
156 | long long *l = va_arg(ap, long long *);
157 |
158 | if (RedisModule_StringToLongLong(argv[i], l) != REDISMODULE_OK) {
159 | return REDISMODULE_ERR;
160 | }
161 | } else if (*c == 'd') { //read double
162 | double *d = va_arg(ap, double *);
163 | if (RedisModule_StringToDouble(argv[i], d) != REDISMODULE_OK) {
164 | return REDISMODULE_ERR;
165 | }
166 | } else if (*c == '*') { //skip current arg
167 | //do nothing
168 | } else {
169 | return REDISMODULE_ERR; //WAT?
170 | }
171 | c++;
172 | i++;
173 | }
174 | // if the format is longer than argc, retun an error
175 | if (*c != 0) {
176 | return REDISMODULE_ERR;
177 | }
178 | return REDISMODULE_OK;
179 |
180 |
181 | }
182 |
183 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, ...) {
184 |
185 | int pos = RMUtil_ArgExists(token, argv, argc, 0);
186 | if (pos == 0) {
187 | return REDISMODULE_ERR;
188 | }
189 |
190 | va_list ap;
191 | va_start(ap, fmt);
192 | int rc = rmutil_vparseArgs(argv, argc, pos+1, fmt, ap);
193 | va_end(ap);
194 | return rc;
195 |
196 | }
197 |
198 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath(
199 | RedisModuleCallReply *rep, const char *path) {
200 | if (rep == NULL) return NULL;
201 |
202 | RedisModuleCallReply *ele = rep;
203 | const char *s = path;
204 | char *e;
205 | long idx;
206 | do {
207 | errno = 0;
208 | idx = strtol(s, &e, 10);
209 |
210 | if ((errno == ERANGE && (idx == LONG_MAX || idx == LONG_MIN)) ||
211 | (errno != 0 && idx == 0) ||
212 | (REDISMODULE_REPLY_ARRAY != RedisModule_CallReplyType(ele)) ||
213 | (s == e)) {
214 | ele = NULL;
215 | break;
216 | }
217 | s = e;
218 | ele = RedisModule_CallReplyArrayElement(ele, idx - 1);
219 |
220 | } while ((ele != NULL) && (*e != '\0'));
221 |
222 | return ele;
223 | }
--------------------------------------------------------------------------------
/rmutil/util.h:
--------------------------------------------------------------------------------
1 | #ifndef __UTIL_H__
2 | #define __UTIL_H__
3 |
4 | #include
5 | #include
6 |
7 | /// make sure the response is not NULL or an error, and if it is sends the error to the client and exit the current function
8 | #define RMUTIL_ASSERT_NOERROR(r) \
9 | if (r == NULL) { \
10 | return RedisModule_ReplyWithError(ctx,"ERR reply is NULL"); \
11 | } else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { \
12 | RedisModule_ReplyWithCallReply(ctx,r); \
13 | return REDISMODULE_ERR; \
14 | }
15 |
16 |
17 | #define __rmutil_register_cmd(ctx, cmd, f, mode) if (RedisModule_CreateCommand(ctx, cmd, f, mode, \
18 | 1, 1, 1) == REDISMODULE_ERR) return REDISMODULE_ERR;
19 |
20 | #define RMUtil_RegisterReadCmd(ctx, cmd, f) __rmutil_register_cmd(ctx, cmd, f, "readonly") }
21 |
22 | #define RMUtil_RegisterWriteCmd(ctx, cmd, f) __rmutil_register_cmd(ctx, cmd, f, "write")
23 |
24 | /* RedisModule utilities. */
25 |
26 | /** Return the offset of an arg if it exists in the arg list, or 0 if it's not there */
27 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset);
28 |
29 | /**
30 | Automatically conver the arg list to corresponding variable pointers according to a given format.
31 | You pass it the command arg list and count, the starting offset, a parsing format, and pointers to the variables.
32 | The format is a string consisting of the following identifiers:
33 |
34 | c -- pointer to a Null terminated C string pointer.
35 | s -- pointer to a RedisModuleString
36 | l -- pointer to Long long integer.
37 | d -- pointer to a Double
38 | * -- do not parse this argument at all
39 |
40 | Example: If I want to parse args[1], args[2] as a long long and double, I do:
41 | double d;
42 | long long l;
43 | RMUtil_ParseArgs(argv, argc, 1, "ld", &l, &d);
44 | */
45 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...);
46 |
47 | /**
48 | Same as RMUtil_ParseArgs, but only parses the arguments after `token`, if it was found.
49 | This is useful for optional stuff like [LIMIT [offset] [limit]]
50 | */
51 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, ...);
52 |
53 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap);
54 |
55 | // A single key/value entry in a redis info map
56 | typedef struct {
57 | const char *key;
58 | const char *val;
59 | } RMUtilInfoEntry;
60 |
61 | // Representation of INFO command response, as a list of k/v pairs
62 | typedef struct {
63 | RMUtilInfoEntry *entries;
64 | int numEntries;
65 | } RMUtilInfo;
66 |
67 | /**
68 | * Get redis INFO result and parse it as RMUtilInfo.
69 | * Returns NULL if something goes wrong.
70 | * The resulting object needs to be freed with RMUtilRedisInfo_Free
71 | */
72 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx);
73 |
74 | /**
75 | * Free an RMUtilInfo object and its entries
76 | */
77 | void RMUtilRedisInfo_Free(RMUtilInfo *info);
78 |
79 | /**
80 | * Get an integer value from an info object. Returns 1 if the value was found and
81 | * is an integer, 0 otherwise. the value is placed in 'val'
82 | */
83 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val);
84 |
85 | /**
86 | * Get a string value from an info object. The value is placed in str.
87 | * Returns 1 if the key was found, 0 if not
88 | */
89 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str);
90 |
91 | /**
92 | * Get a double value from an info object. Returns 1 if the value was found and is
93 | * a correctly formatted double, 0 otherwise. the value is placed in 'd'
94 | */
95 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d);
96 |
97 | /*
98 | * Returns a call reply array's element given by a space-delimited path. E.g.,
99 | * the path "1 2 3" will return the 3rd element from the 2 element of the 1st
100 | * element from an array (or NULL if not found)
101 | */
102 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath(
103 | RedisModuleCallReply *rep, const char *path);
104 |
105 |
106 | #endif
107 |
--------------------------------------------------------------------------------
/rmutil/vector.c:
--------------------------------------------------------------------------------
1 | #include "vector.h"
2 | #include
3 |
4 |
5 | int __vector_PushPtr(Vector *v, void *elem) {
6 | if (v->top == v->cap - 1) {
7 | Vector_Resize(v, v->cap ? v->cap*2 : 1);
8 | }
9 |
10 | __vector_PutPtr(v, v->top, elem);
11 | v->top++;
12 | return v->top;
13 | }
14 |
15 |
16 | int Vector_Get(Vector *v, size_t pos, void *ptr) {
17 | // return 0 if pos is out of bounds
18 | if (pos >= v->top) {
19 | return 0;
20 | }
21 |
22 | memcpy(ptr, v->data + (pos * v->elemSize), v->elemSize);
23 | return 1;
24 | }
25 |
26 |
27 | int __vector_PutPtr(Vector *v, size_t pos, void *elem) {
28 | // resize if pos is out of bounds
29 | if (pos >= v->cap) {
30 | Vector_Resize(v, pos+1);
31 | }
32 |
33 | memcpy(v->data + pos*v->elemSize, elem, v->elemSize);
34 | // move the end offset to pos if we grew
35 | if (pos > v->top) {
36 | v->top = pos;
37 | }
38 | return 1;
39 | }
40 |
41 |
42 | int Vector_Resize(Vector *v, size_t newcap) {
43 |
44 | int oldcap = v->cap;
45 | v->cap = newcap;
46 |
47 | v->data = realloc(v->data, v->cap*v->elemSize);
48 |
49 | // If we grew:
50 | // put all zeros at the newly realloc'd part of the vector
51 | if (newcap > oldcap) {
52 | int offset = oldcap*v->elemSize;
53 | memset(v->data + offset, 0, v->cap*v->elemSize - offset);
54 | }
55 | return v->cap;
56 | }
57 |
58 | inline int Vector_Size(Vector *v) {
59 | return v->top;
60 | }
61 |
62 | inline int Vector_Cap(Vector *v) {
63 | return v->cap;
64 | }
65 |
66 | Vector *__newVectorSize(size_t elemSize, size_t cap) {
67 |
68 | Vector *vec = malloc(sizeof(Vector));
69 | vec->data = calloc(cap, elemSize);
70 | vec->top = 0;
71 | vec->elemSize = elemSize;
72 | vec->cap = cap;
73 |
74 | return vec;
75 | }
76 |
77 | void Vector_Free(Vector *v) {
78 | free(v->data);
79 | free(v);
80 | }
81 |
--------------------------------------------------------------------------------
/rmutil/vector.h:
--------------------------------------------------------------------------------
1 | #ifndef __VECTOR_H__
2 | #define __VECTOR_H__
3 | #include
4 | #include
5 | #include
6 |
7 | /*
8 | * Generic resizable vector that can be used if you just want to store stuff
9 | * temporarily.
10 | * Works like C++ std::vector with an underlying resizable buffer
11 | */
12 | typedef struct {
13 | char *data;
14 | size_t elemSize;
15 | size_t cap;
16 | size_t top;
17 |
18 | } Vector;
19 |
20 | /* Create a new vector with element size. This should generally be used
21 | * internall by the NewVector macro */
22 | Vector *__newVectorSize(size_t elemSize, size_t cap);
23 |
24 | // Put a pointer in the vector. To be used internall by the library
25 | int __vector_PutPtr(Vector *v, size_t pos, void *elem);
26 |
27 | /*
28 | * Create a new vector for a given type and a given capacity.
29 | * e.g. NewVector(int, 0) - empty vector of ints
30 | */
31 | #define NewVector(type, cap) __newVectorSize(sizeof(type), cap)
32 |
33 | /*
34 | * get the element at index pos. The value is copied in to ptr. If pos is outside
35 | * the vector capacity, we return 0
36 | * otherwise 1
37 | */
38 | int Vector_Get(Vector *v, size_t pos, void *ptr);
39 |
40 | //#define Vector_Getx(v, pos, ptr) pos < v->cap ? 1 : 0; *ptr =
41 | //*(typeof(ptr))(v->data + v->elemSize*pos)
42 |
43 | /*
44 | * Put an element at pos.
45 | * Note: If pos is outside the vector capacity, we resize it accordingly
46 | */
47 | #define Vector_Put(v, pos, elem) __vector_PutPtr(v, pos, &(typeof(elem)){elem})
48 |
49 | /* Push an element at the end of v, resizing it if needed. This macro wraps
50 | * __vector_PushPtr */
51 | #define Vector_Push(v, elem) __vector_PushPtr(v, &(typeof(elem)){elem})
52 |
53 | int __vector_PushPtr(Vector *v, void *elem);
54 |
55 | /* resize capacity of v */
56 | int Vector_Resize(Vector *v, size_t newcap);
57 |
58 | /* return the used size of the vector, regardless of capacity */
59 | int Vector_Size(Vector *v);
60 |
61 | /* return the actual capacity */
62 | int Vector_Cap(Vector *v);
63 |
64 | /* free the vector and the underlying data. Does not release its elements if
65 | * they are pointers*/
66 | void Vector_Free(Vector *v);
67 |
68 | int __vecotr_PutPtr(Vector *v, size_t pos, void *elem);
69 |
70 | #endif
71 |
--------------------------------------------------------------------------------
/src/Makefile:
--------------------------------------------------------------------------------
1 | # set environment variable RM_INCLUDE_DIR to the location of redismodule.h
2 | ifndef RM_INCLUDE_DIR
3 | RM_INCLUDE_DIR=../
4 | endif
5 |
6 | ifndef RMUTIL_LIBDIR
7 | RMUTIL_LIBDIR=../rmutil/
8 | endif
9 |
10 | # find the OS
11 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
12 |
13 | # Compile flags for linux / osx
14 | ifeq ($(uname_S),Linux)
15 | SHOBJ_CFLAGS ?= -fno-common -g -ggdb
16 | SHOBJ_LDFLAGS ?= -shared
17 | else
18 | SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb
19 | SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
20 | endif
21 |
22 | CFLAGS = -I$(RM_INCLUDE_DIR) -g -fPIC -lc -lm -O3 -std=gnu99
23 | CC=gcc
24 | .SUFFIXES: .c .so .o
25 |
26 | MODULE = topk
27 |
28 | all: $(MODULE)
29 |
30 | $(MODULE): %: %.o
31 | $(LD) -o $@.so $< $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc
32 |
33 | clean:
34 | rm -rf *.so *.o
35 |
--------------------------------------------------------------------------------
/src/topk.c:
--------------------------------------------------------------------------------
1 | /*
2 | * topk - an almost deterministic top k elements counter Redis Module
3 | * Copyright (C) 2016 Redis Labs
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU Affero General Public License as
7 | * published by the Free Software Foundation, either version 3 of the
8 | * License, or (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU Affero General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Affero General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | /* Includes 'ustime' from redis/src/server.c
20 | * Copyright (c) 2009-2012, Salvatore Sanfilippo
21 | * All rights reserved.
22 | *
23 | * Redistribution and use in source and binary forms, with or without
24 | * modification, are permitted provided that the following conditions are met:
25 | *
26 | * * Redistributions of source code must retain the above copyright notice,
27 | * this list of conditions and the following disclaimer.
28 | * * Redistributions in binary form must reproduce the above copyright
29 | * notice, this list of conditions and the following disclaimer in the
30 | * documentation and/or other materials provided with the distribution.
31 | * * Neither the name of Redis nor the names of its contributors may be used
32 | * to endorse or promote products derived from this software without
33 | * specific prior written permission.
34 | *
35 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
38 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
39 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
40 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
41 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
42 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
43 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
44 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
45 | * POSSIBILITY OF SUCH DAMAGE.
46 | */
47 | #include
48 | #include
49 | #include "../redismodule.h"
50 | #include "../rmutil/util.h"
51 | #include "../rmutil/vector.h"
52 | #include "../rmutil/test_util.h"
53 |
54 | #define RLMODULE_NAME "TOPK"
55 | #define RLMODULE_VERSION "1.0.1"
56 | #define RLMODULE_PROTO "1.0"
57 | #define RLMODULE_DESC "Deterministic top k with percentiles"
58 |
59 | #define REDIS_LOG(str) fprintf(stderr, "topk.so: %s\n", str);
60 |
61 | #define TOPK_META_PREFIX "TOPK:1.0.1:1.0:"
62 | #define TOPK_META_SCORE -1.0
63 |
64 | #define TOPK_K_ERR "ERR k must be an integer greater than zero"
65 | #define TOPK_ELEM_ERR "ERR elements can not start with the 'TOPK' prefix"
66 | #define TOPK_MISSING_ERR "ERR missing TOPK meta element"
67 | #define TOPK_INVALID_ERR "ERR invalid TOPK meta element"
68 | #define TOPK_TOOMANY_ERR "ERR too many TOPK meta elements"
69 | #define TOPK_PREC_ERR "ERR loosing focus - call TOPK.SHRINK first"
70 | #define TOPK_ZCARD_ERR "ERR a valid TOPK zset must have ZCARD > 2"
71 |
72 | struct topk_meta_s {
73 | long long offset;
74 | long long sum;
75 | };
76 |
77 | typedef struct topk_meta_s topk_meta;
78 |
79 | struct zset_ele_s {
80 | RedisModuleString *ele;
81 | double score;
82 | };
83 |
84 | typedef struct zset_ele_s zset_ele;
85 |
86 | /* Return the UNIX time in microseconds */
87 | long long ustime(void) {
88 | struct timeval tv;
89 | long long ust;
90 |
91 | gettimeofday(&tv, NULL);
92 | ust = ((long long)tv.tv_sec) * 1000000;
93 | ust += tv.tv_usec;
94 | return ust;
95 | }
96 |
97 | /* Creates a new meta struct. */
98 | topk_meta *meta_new() {
99 | topk_meta *m;
100 | if ((m = malloc(sizeof(topk_meta))) == NULL) return NULL;
101 |
102 | m->offset = 0;
103 | m->sum = 0;
104 |
105 | return m;
106 | }
107 |
108 | /* Serializes the meta struct to a string buffer. */
109 | char *meta_dump(const topk_meta *meta, size_t *len) {
110 | size_t prelen = strlen(TOPK_META_PREFIX);
111 | size_t size = prelen + sizeof(topk_meta);
112 | char *dump = calloc(size, sizeof(char));
113 | if (dump == NULL) return NULL;
114 |
115 | memcpy(dump, TOPK_META_PREFIX, prelen);
116 | memcpy(&dump[prelen], meta, sizeof(topk_meta));
117 | *len = size;
118 | return dump;
119 | }
120 |
121 | /* Loads a meta struct from its serialized form. */
122 | topk_meta *meta_load(const char *ser, const size_t len) {
123 | size_t prelen = strlen(TOPK_META_PREFIX);
124 |
125 | /* Check buffer size. */
126 | if (len != prelen + sizeof(topk_meta)) return NULL;
127 |
128 | /* Check prefix. */
129 | if (strncmp(TOPK_META_PREFIX, ser, prelen)) return NULL;
130 |
131 | topk_meta *meta;
132 | if ((meta = malloc(sizeof(topk_meta))) == NULL) return NULL;
133 |
134 | memcpy(meta, &ser[prelen], sizeof(topk_meta));
135 |
136 | /* Sanity. */
137 | if ((meta->offset > 0) || (meta->sum < 1)) return NULL;
138 |
139 | return meta;
140 | }
141 |
142 | /* Helper: gets the meta from a zset and potentially removes it */
143 | topk_meta *GetMeta(RedisModuleCtx *ctx, RedisModuleKey *key, int remove) {
144 | topk_meta *meta = NULL;
145 |
146 | size_t size = RedisModule_ValueLength(key);
147 | if (size < 2) { /* TOPK's ZCARD must be 2 or above */
148 | RedisModule_ReplyWithError(ctx, TOPK_ZCARD_ERR);
149 | return NULL;
150 | }
151 |
152 | size_t meta_count = 0;
153 | double score;
154 | RedisModuleString *ele;
155 | RedisModule_ZsetFirstInScoreRange(key, REDISMODULE_NEGATIVE_INFINITE,
156 | TOPK_META_SCORE, 0, 0);
157 | while (!RedisModule_ZsetRangeEndReached(key)) {
158 | if (meta_count) { /* There can be only one. */
159 | free(meta);
160 | RedisModule_ReplyWithError(ctx, TOPK_TOOMANY_ERR);
161 | return NULL;
162 | }
163 |
164 | ele = RedisModule_ZsetRangeCurrentElement(key, &score);
165 |
166 | if (score != TOPK_META_SCORE) { /* The one has to have the right score. */
167 | RedisModule_ReplyWithError(ctx, TOPK_INVALID_ERR);
168 | return NULL;
169 | }
170 |
171 | size_t len;
172 | const char *ser = RedisModule_StringPtrLen(ele, &len);
173 | if ((meta = meta_load(ser, len)) ==
174 | NULL) { /* The one has to load successfully. */
175 | RedisModule_ReplyWithError(ctx, TOPK_INVALID_ERR);
176 | return NULL;
177 | }
178 |
179 | RedisModule_ZsetRangeNext(key);
180 | }
181 | RedisModule_ZsetRangeStop(key);
182 | if (meta == NULL) {
183 | RedisModule_ReplyWithError(ctx, TOPK_MISSING_ERR);
184 | return NULL;
185 | }
186 |
187 | if (remove) RedisModule_ZsetRem(key, ele, NULL);
188 |
189 | return meta;
190 | }
191 |
192 | /* Helper: sets meta to zset and frees it. */
193 | int SetMeta(RedisModuleCtx *ctx, RedisModuleKey *key, topk_meta *meta) {
194 | size_t len;
195 | char *dump = meta_dump(meta, &len);
196 | RedisModuleString *ele = RedisModule_CreateString(ctx, dump, len);
197 | RedisModule_ZsetAdd(key, TOPK_META_SCORE, ele, NULL);
198 | free(dump);
199 | free(meta);
200 | return 1;
201 | }
202 |
203 | /* TOPK.SHRINK key [k]
204 | * Complexity: O(N) where N is the number of members in the Sorted Set.
205 | * Resets the global offset after applying it to all members, trims to k if
206 | * specified and not zero.
207 | * Reply: String, "OK"
208 | */
209 | int TopKShrink_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
210 | int argc) {
211 | if ((argc != 2) && (argc != 3)) return RedisModule_WrongArity(ctx);
212 | RedisModule_AutoMemory(ctx);
213 |
214 | /* Open key and verify it is empty or a zset. */
215 | RedisModuleKey *key =
216 | RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
217 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY)
218 | return RedisModule_ReplyWithSimpleString(ctx, "OK");
219 | if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET)
220 | return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
221 |
222 | /* Get meta and size */
223 | topk_meta *meta;
224 | if ((meta = GetMeta(ctx, key, 1)) == NULL) return REDISMODULE_ERR;
225 | size_t size = RedisModule_ValueLength(key);
226 |
227 | /* Get k, if specified, else assume at least the current size. */
228 | long long k = 0;
229 | if ((argc == 3) &&
230 | (RedisModule_StringToLongLong(argv[2], &k) != REDISMODULE_OK) && (k < 0))
231 | return RedisModule_ReplyWithError(ctx, TOPK_K_ERR);
232 | if (k == 0) k = size;
233 |
234 | /* Adjust offset with overflow, if any. */
235 | meta->offset -= (k < size ? 0 : size - k);
236 |
237 | /* Decrement all scores by offset. */
238 | int flags;
239 | /* TODO: once RedisModule_ZsetRemCurrentElement and/or ZsetIncrCurrentElement
240 | * are implemented, switch to it. */
241 | Vector *vupd = NewVector(void *, 1);
242 | Vector *vrem = NewVector(void *, 1);
243 |
244 | /* Add to lupd all elements for update, and to lrem those with final scores
245 | * leq to offset. */
246 | double score;
247 | RedisModuleString *ele;
248 | RedisModule_ZsetFirstInScoreRange(key, 0.0, REDISMODULE_POSITIVE_INFINITE, 1,
249 | 0);
250 | while (!RedisModule_ZsetRangeEndReached(key)) {
251 | ele = RedisModule_ZsetRangeCurrentElement(key, &score);
252 | if (score + meta->offset < 1) {
253 | Vector_Push(vrem, ele);
254 | meta->sum -= score;
255 | } else
256 | Vector_Push(vupd, ele);
257 | RedisModule_ZsetRangeNext(key);
258 | }
259 | RedisModule_ZsetRangeStop(key);
260 |
261 | /* Update the sum with the to be updated elements' shift. */
262 | meta->sum += Vector_Size(vupd) * meta->offset;
263 |
264 | /* Now actually perform the removals. */
265 | int i;
266 | for (i = 0; i < Vector_Size(vrem); i++) {
267 | Vector_Get(vrem, i, &ele);
268 | RedisModule_ZsetRem(key, ele, NULL);
269 | }
270 | Vector_Free(vrem);
271 |
272 | /* Now actually perform the updates. */
273 | for (i = 0; i < Vector_Size(vupd); i++) {
274 | Vector_Get(vupd, i, &ele);
275 | flags = 0;
276 | RedisModule_ZsetIncrby(key, meta->offset, ele, &flags, &score);
277 | }
278 | Vector_Free(vupd);
279 |
280 | /* Reset the global offset. */
281 | meta->offset = 0;
282 | SetMeta(ctx, key, meta);
283 |
284 | RedisModule_ReplyWithSimpleString(ctx, "OK");
285 | return REDISMODULE_OK;
286 | }
287 |
288 | /* TOPK.ADD key k elem [elem ...]
289 | * Complexity: O(log(N)), where N is the size of the zset.
290 | * Adds elements to a TopK zset.
291 | * Notes:
292 | * - elem can't begin with the _'TOPK'_ prefix.
293 | * - If k is zero, the zset keeps growing.
294 | * - Using different k is perfectly possible and could be even interesting
295 | * - Optimization in the variadic form: first incr existing elements, then do
296 | * one sweep of decr, then remove < 1 as needed. This may screw the
297 | * probabilities (someone can probably dis/prove),
298 | * so just use non variadic calls to play safe if you want.
299 | * An error is returned when:
300 | * - The operation requires running `TOPK.SHRINK` before.
301 | * - The Sorted Set exists and doesn't sport the meta
302 | * Reply: Integer. If positive, it is the number of new elements added. If
303 | * negative, it is the number of elements removed due to k overflow. If 0, it
304 | * mean that only the offset/scores were updated.
305 | */
306 | int TopKAdd_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
307 | int argc) {
308 | if (argc < 4) return RedisModule_WrongArity(ctx);
309 |
310 | RedisModule_AutoMemory(ctx);
311 |
312 | /* Open key and verify it is empty or a zset. */
313 | RedisModuleKey *key =
314 | RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
315 | if ((RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY) &&
316 | (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET)) {
317 | return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
318 | }
319 |
320 | /* Get k. */
321 | long long k = 0;
322 | if ((RedisModule_StringToLongLong(argv[2], &k) != REDISMODULE_OK) || (k < 0))
323 | return RedisModule_ReplyWithError(ctx, TOPK_K_ERR);
324 |
325 | /* Check that no new element begins with TOPK_META_PREFIX */
326 | size_t lenpref = strlen(TOPK_META_PREFIX);
327 | size_t i;
328 | for (i = 3; i < argc; i++) {
329 | size_t len;
330 | const char *arg = RedisModule_StringPtrLen(argv[i], &len);
331 | if ((len >= lenpref) && (!strncasecmp(TOPK_META_PREFIX, arg, lenpref)))
332 | return RedisModule_ReplyWithError(ctx, TOPK_ELEM_ERR);
333 | }
334 |
335 | topk_meta *meta;
336 | double score;
337 | int flags = 0;
338 |
339 | /* Get or initialize meta. */
340 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY)
341 | meta = meta_new();
342 | else if ((meta = GetMeta(ctx, key, 1)) == NULL)
343 | return REDISMODULE_ERR;
344 |
345 | /* We don't want to get too close to double's precision, so only use up to 52
346 | * bits
347 | * or call for a shrink. This makes some assumptions about cases where
348 | * precision is lost:
349 | * - in the global offset due to a k overflow
350 | * - in topk elements with scores being updated
351 | * Also, we'll call for a shrink if the sum of scores reaches near the 64 bit
352 | * area.
353 | */
354 | long long maxp = (long long)1 << 51;
355 | if (meta->offset <= -maxp)
356 | return RedisModule_ReplyWithError(ctx, TOPK_PREC_ERR);
357 |
358 | size_t size = RedisModule_ValueLength(key);
359 | if (size > 0) {
360 | /* Get maximum score in zset. */
361 | RedisModule_ZsetLastInScoreRange(key, 1.0, REDISMODULE_POSITIVE_INFINITE, 0,
362 | 0);
363 | RedisModule_ZsetRangeCurrentElement(key, &score);
364 | RedisModule_ZsetRangeStop(key);
365 |
366 | if ((score >= maxp) || (meta->sum >= (maxp << 11)))
367 | return RedisModule_ReplyWithError(ctx, TOPK_PREC_ERR);
368 | }
369 |
370 | /* Iterate and incr existing elements, only mind the new ones. */
371 | Vector *add = NewVector(void *, 1);
372 | if (add == NULL) goto malloc_error;
373 | for (i = 3; i < argc; i++) {
374 | flags = REDISMODULE_ZADD_XX;
375 | RedisModule_ZsetIncrby(key, 1.0, argv[i], &flags, &score);
376 | if (flags == REDISMODULE_ZADD_NOP) {
377 | Vector_Push(add, argv[i]);
378 | // __vector_PushPtr(add,(void *)argv[i]);
379 | } else {
380 | meta->sum += 1;
381 | }
382 | }
383 | long long added = Vector_Size(add);
384 |
385 | /* Check if the zset exceeds k. */
386 | long long olen = RedisModule_ValueLength(key);
387 | long long ocap = k - olen;
388 | long long overflow = (k ? olen + added - k : 0);
389 | Vector *rem = NULL;
390 | long long remmed = 0;
391 | RedisModuleString *ele;
392 | zset_ele *zele;
393 | size_t j = overflow;
394 |
395 | /* An overflow means adjusting offset score and removing members beneath it.
396 | */
397 | if (overflow > 0) {
398 | meta->offset -= overflow;
399 |
400 | /* 1. No elements to remove
401 | * 2. Some (< overflow) elments to remove
402 | * 3. Overflow elements can be removed
403 | */
404 | /* Algorithm R. */
405 | rem = NewVector(zset_ele *, overflow);
406 | if (rem == NULL) goto malloc_error;
407 | RedisModule_ZsetFirstInScoreRange(key, 0.0, -meta->offset, 1, 0);
408 | i = 0;
409 | while (!RedisModule_ZsetRangeEndReached(key) && j) {
410 | zele = malloc(sizeof(zset_ele));
411 | if (zele == NULL) goto malloc_error;
412 | zele->ele = RedisModule_ZsetRangeCurrentElement(key, &zele->score);
413 | Vector_Push(rem, zele);
414 | i++;
415 | j--;
416 | RedisModule_ZsetRangeNext(key);
417 | }
418 | remmed = Vector_Size(rem);
419 |
420 | srand(ustime());
421 | i = overflow + 1;
422 | while (!RedisModule_ZsetRangeEndReached(key) && remmed) {
423 | j = rand() % i;
424 | if (j < overflow) {
425 | Vector_Get(rem, j, &zele);
426 | zele->ele = RedisModule_ZsetRangeCurrentElement(key, &zele->score);
427 | }
428 | i++;
429 | RedisModule_ZsetRangeNext(key);
430 | }
431 | RedisModule_ZsetRangeStop(key);
432 |
433 | /* Now actually perform the removals. */
434 | for (i = 0; i < remmed; i++) {
435 | Vector_Get(rem, i, &zele);
436 | RedisModule_ZsetRem(key, zele->ele, NULL);
437 | meta->sum -= (long long)zele->score;
438 | free(zele);
439 | }
440 | Vector_Free(rem);
441 |
442 | /* In case there is less capacity than new elements, R downsample the new. */
443 | long long ncap = ocap + remmed;
444 | for (i = ncap; ((i < added) && ncap); i++) {
445 | j = rand() % i;
446 | if (j < remmed) {
447 | Vector_Get(add, j, &ele);
448 | Vector_Get(add, i, &ele);
449 | Vector_Put(add, j, ele);
450 | } else {
451 | Vector_Get(add, i, &ele);
452 | }
453 | }
454 | added = ncap;
455 | }
456 |
457 | /* Conclude by adding all new elements. */
458 | score = -(meta->offset) + 1;
459 |
460 | for (i = 0; i < added; i++) {
461 | flags = REDISMODULE_ZADD_NX;
462 | Vector_Get(add, i, &ele);
463 | RedisModule_ZsetAdd(key, score, ele, &flags);
464 | meta->sum += score;
465 | }
466 | Vector_Free(add);
467 |
468 | /* Store meta. */
469 | SetMeta(ctx, key, meta);
470 |
471 | if (overflow > 0)
472 | RedisModule_ReplyWithLongLong(ctx, -remmed);
473 | else
474 | RedisModule_ReplyWithLongLong(ctx, added);
475 | return REDISMODULE_OK;
476 |
477 | malloc_error:
478 | SetMeta(ctx, key, meta);
479 | if (add != NULL) {
480 | Vector_Free(add);
481 | }
482 | if (rem != NULL) {
483 | Vector_Free(rem);
484 | }
485 | return RedisModule_ReplyWithError(ctx, "ERR could not allocate memory");
486 | }
487 |
488 | /* TOPK.PRANGE key from to [DESC|ASC]
489 | * Returns the elements in the percentile range.
490 | * Both `from` and `to` must be between 0 and 100, and `from` must be less than
491 | * or equal to `to`.
492 | * The optional switch determines the reply's sort order, where `DESC` (the
493 | * default) means ordering
494 | * from highest to lowest frequency.
495 | * Reply: Array of strings.
496 | */
497 | int TopKPRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
498 | int argc) {
499 | if ((argc != 4) && (argc != 5)) return RedisModule_WrongArity(ctx);
500 | RedisModule_AutoMemory(ctx);
501 |
502 | /* Open key and verify it is empty or a zset. */
503 | RedisModuleKey *key =
504 | RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
505 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY)
506 | return RedisModule_ReplyWithNull(ctx);
507 | if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET)
508 | return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
509 |
510 | /* Get from here to there. */
511 | long long from;
512 | if (((RedisModule_StringToLongLong(argv[2], &from)) != REDISMODULE_OK) ||
513 | (from < 0) || (from > 100)) {
514 | return RedisModule_ReplyWithError(
515 | ctx, "ERR from has to be an integer between 0 and 100");
516 | }
517 | long long to;
518 | if (((RedisModule_StringToLongLong(argv[3], &to)) != REDISMODULE_OK) ||
519 | (to < 0) || (to > 100)) {
520 | return RedisModule_ReplyWithError(
521 | ctx, "ERR to has to be an integer between 0 and 100");
522 | }
523 | if (from > to)
524 | return RedisModule_ReplyWithError(
525 | ctx, "ERR from has to be lower or equal to to");
526 |
527 | /* Determine the order. */
528 | int asc = 0;
529 | if (argc == 5) {
530 | size_t len;
531 | const char *str = RedisModule_StringPtrLen(argv[4], &len);
532 | if (!strcasecmp("asc", str))
533 | asc = 1;
534 | else if (!strcasecmp("desc", str))
535 | asc = 0;
536 | else
537 | return RedisModule_ReplyWithError(ctx, "ERR valid order is ASC or DESC");
538 | }
539 |
540 | /* Verify that this is a valid TOPK zset. */
541 | topk_meta *meta = GetMeta(ctx, key, 0);
542 | if (meta == NULL) return REDISMODULE_ERR;
543 |
544 | /* Make it so. */
545 | /* Sum the scores of elements until and excluding the 'from' percentile. */
546 | double score;
547 | RedisModuleString *ele;
548 | long long cl = 0;
549 | long long prank = 0;
550 | RedisModule_ZsetFirstInScoreRange(key, 0.0, REDISMODULE_POSITIVE_INFINITE, 1,
551 | 0);
552 | while ((!RedisModule_ZsetRangeEndReached(key)) && (prank < from)) {
553 | ele = RedisModule_ZsetRangeCurrentElement(key, &score);
554 | cl += (long long)score;
555 | prank = (((double)cl + score) / (double)meta->sum) * 100;
556 | RedisModule_ZsetRangeNext(key);
557 | }
558 |
559 | Vector *reply = NewVector(void *, 1);
560 | if (reply == NULL) goto malloc_error;
561 |
562 | while ((!RedisModule_ZsetRangeEndReached(key)) && (prank <= to)) {
563 | ele = RedisModule_ZsetRangeCurrentElement(key, &score);
564 | cl += (long long)score;
565 | prank = (((double)cl + score) / (double)meta->sum) * 100;
566 | Vector_Push(reply, ele);
567 | RedisModule_ZsetRangeNext(key);
568 | }
569 | RedisModule_ZsetRangeStop(key);
570 |
571 | /* Prepare the reply. */
572 | RedisModule_ReplyWithArray(ctx, Vector_Size(reply));
573 | int i;
574 | if (asc) {
575 | for (i = 0; i < Vector_Size(reply); i++) {
576 | Vector_Get(reply, i, &ele);
577 | RedisModule_ReplyWithString(ctx, ele);
578 | }
579 | } else {
580 | for (i = Vector_Size(reply); i > 0; i--) {
581 | Vector_Get(reply, i, &ele);
582 | RedisModule_ReplyWithString(ctx, ele);
583 | }
584 | }
585 |
586 | Vector_Free(reply);
587 | free(meta);
588 | return REDISMODULE_OK;
589 |
590 | malloc_error:
591 | if (reply != NULL) Vector_Free(reply);
592 | free(meta);
593 | return RedisModule_ReplyWithError(ctx,
594 | "ERR could not allocate memory for reply");
595 | }
596 |
597 | /* TOPK.PRANK key ele [ele ...]
598 | * Returns the percentile rank for the element.
599 | * Reply: Array of integers, nil if key or element not found
600 | */
601 | int TopKPRank_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
602 | int argc) {
603 | if (argc < 2) return RedisModule_WrongArity(ctx);
604 | RedisModule_AutoMemory(ctx);
605 |
606 | /* Open key and verify it is empty or a zset. */
607 | RedisModuleKey *key =
608 | RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
609 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY)
610 | return RedisModule_ReplyWithNull(ctx);
611 | if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET)
612 | return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
613 |
614 | /* Verify that this is a valid TOPK zset. */
615 | topk_meta *meta = GetMeta(ctx, key, 0);
616 | if (meta == NULL) return REDISMODULE_ERR;
617 | free(meta);
618 |
619 | RedisModule_ReplyWithArray(ctx, argc - 2);
620 | int i;
621 | for (i = 2; i < argc; i++) {
622 | /* https://en.wikipedia.org/wiki/Percentile_rank */
623 | double score;
624 | if (RedisModule_ZsetScore(key, argv[i], &score) != REDISMODULE_OK) {
625 | RedisModule_ReplyWithNull(ctx);
626 | continue;
627 | }
628 |
629 | /* TODO: if variadic, it makes sense to reduce the work by sorting args by
630 | * rank &
631 | * incrementally computing the sums */
632 | /* Sum the scores of elements just before the current. */
633 | double ts;
634 | long long cl = 0;
635 | RedisModule_ZsetFirstInScoreRange(key, 0.0, score, 1, 1);
636 | while (!RedisModule_ZsetRangeEndReached(key)) {
637 | RedisModule_ZsetRangeCurrentElement(key, &ts);
638 | cl += (long long)ts;
639 | RedisModule_ZsetRangeNext(key);
640 | }
641 | RedisModule_ZsetRangeStop(key);
642 |
643 | /* Sum the scores of the elements with the same score as current. */
644 | long long ce = 0;
645 | RedisModule_ZsetFirstInScoreRange(key, score, score, 0, 0);
646 | while (!RedisModule_ZsetRangeEndReached(key)) {
647 | RedisModule_ZsetRangeCurrentElement(key, &ts);
648 | ce += (long long)ts;
649 | RedisModule_ZsetRangeNext(key);
650 | }
651 | RedisModule_ZsetRangeStop(key);
652 |
653 | long long prank = (((double)cl + (double)ce) / (double)meta->sum) * 100;
654 | RedisModule_ReplyWithLongLong(ctx, prank);
655 | }
656 | return REDISMODULE_OK;
657 | }
658 |
659 | /* TOPK.DEBUG subcommand key [arg]
660 | * subcommands:
661 | * - MAKE: fills 'key' with 1..'arg' (has to be integer) observations with
662 | * matching freqs
663 | * - SHOW: shows meta information about 'key'
664 | */
665 | int TopKDebug_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
666 | int argc) {
667 | if ((argc < 3) || (argc > 4)) return RedisModule_WrongArity(ctx);
668 |
669 | RedisModule_AutoMemory(ctx);
670 |
671 | RedisModuleKey *key =
672 | RedisModule_OpenKey(ctx, argv[2], REDISMODULE_READ | REDISMODULE_WRITE);
673 |
674 | size_t len;
675 | const char *cmd = RedisModule_StringPtrLen(argv[1], &len);
676 | if (!strcasecmp("show", cmd)) {
677 | if (argc != 3) return RedisModule_WrongArity(ctx);
678 | if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY)
679 | return RedisModule_ReplyWithNull(ctx);
680 | if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET)
681 | return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
682 |
683 | topk_meta *meta = GetMeta(ctx, key, 0);
684 | if (meta == NULL) return REDISMODULE_ERR;
685 |
686 | char buff[128];
687 | sprintf(buff, "zset size: %ld (+1 meta), score offset: %lld, sum: %lld",
688 | RedisModule_ValueLength(key) - 1, meta->offset, meta->sum);
689 | RedisModule_ReplyWithSimpleString(ctx, buff);
690 | } else if (!strcasecmp("make", cmd)) {
691 | if (argc != 4) return RedisModule_WrongArity(ctx);
692 | if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY)
693 | return RedisModule_ReplyWithError(ctx, "ERR target key exists");
694 | long long size;
695 | if ((RedisModule_StringToLongLong(argv[3], &size) != REDISMODULE_OK) ||
696 | (size < 1)) {
697 | return RedisModule_ReplyWithError(ctx,
698 | "ERR size must be greater than zero");
699 | }
700 | topk_meta *meta = meta_new();
701 | while (size) {
702 | RedisModuleString *ele = RedisModule_CreateStringFromLongLong(ctx, size);
703 | RedisModule_ZsetAdd(key, (double)size, ele, NULL);
704 | meta->sum += size;
705 | size--;
706 | }
707 | SetMeta(ctx, key, meta);
708 | RedisModule_ReplyWithSimpleString(ctx, "OK");
709 | } else {
710 | return RedisModule_ReplyWithError(ctx, "ERR unknown debug subcommand");
711 | }
712 |
713 | return REDISMODULE_OK;
714 | }
715 |
716 |
717 | /* Unit test entry point for the module. */
718 | int TestModule(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
719 | RedisModule_AutoMemory(ctx);
720 |
721 | RedisModule_ReplyWithSimpleString(ctx, "PASS");
722 | return REDISMODULE_OK;
723 | }
724 |
725 | int RedisModule_OnLoad(RedisModuleCtx *ctx) {
726 | if (RedisModule_Init(ctx, RLMODULE_NAME, 1, REDISMODULE_APIVER_1) ==
727 | REDISMODULE_ERR)
728 | return REDISMODULE_ERR;
729 |
730 | /* Main commands. */
731 | if (RedisModule_CreateCommand(ctx, "topk.add", TopKAdd_RedisCommand,
732 | "write deny-oom fast", 1, 1,
733 | 1) == REDISMODULE_ERR)
734 | return REDISMODULE_ERR;
735 | if (RedisModule_CreateCommand(ctx, "topk.prank", TopKPRank_RedisCommand,
736 | "readonly", 1, 1, 1) == REDISMODULE_ERR)
737 | return REDISMODULE_ERR;
738 | if (RedisModule_CreateCommand(ctx, "topk.prange", TopKPRange_RedisCommand,
739 | "readonly", 1, 1, 1) == REDISMODULE_ERR)
740 | return REDISMODULE_ERR;
741 |
742 | /* Other commands. */
743 | if (RedisModule_CreateCommand(ctx, "topk.shrink", TopKShrink_RedisCommand,
744 | "write", 1, 1, 1) == REDISMODULE_ERR)
745 | return REDISMODULE_ERR;
746 | if (RedisModule_CreateCommand(ctx, "topk.debug", TopKDebug_RedisCommand,
747 | "readonly fast", 2, 2, 1) == REDISMODULE_ERR)
748 | return REDISMODULE_ERR;
749 |
750 | /* Testing. */
751 | if (RedisModule_CreateCommand(ctx, "topk.test", TestModule, "write", 1, 1,
752 | 1) == REDISMODULE_ERR)
753 | return REDISMODULE_ERR;
754 |
755 | return REDISMODULE_OK;
756 | }
757 |
--------------------------------------------------------------------------------