├── .clang-format ├── .github ├── .codespellrc ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── crash_report.md │ ├── feature_request.md │ └── other_stuff.md ├── wordlist.txt └── workflows │ ├── ci.yml │ ├── cmake.yml │ ├── docker-image.yml │ └── spell-check.yml ├── .gitignore ├── CMDDOC.md ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README-CN.md ├── README.md ├── dep ├── adlist.c ├── adlist.h ├── dict.c ├── dict.h ├── sds.c ├── sds.h ├── sdsalloc.h ├── siphash.c ├── skiplist.c ├── skiplist.h ├── util.c └── util.h ├── imgs └── tairzset_logo.jpg ├── src ├── CMakeLists.txt ├── redismodule.h ├── tairzset.c └── tairzset.h └── tests ├── tairzset-6.0.0.tcl └── tairzset.tcl /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | Language: Cpp 3 | IndentWidth: 4 4 | # Force pointers to the type for C++. 5 | DerivePointerAlignment: false 6 | PointerAlignment: Right 7 | AccessModifierOffset: -4 8 | BreakBeforeBinaryOperators: All 9 | ColumnLimit: 0 10 | -------------------------------------------------------------------------------- /.github/.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | quiet-level = 2 3 | count = 4 | skip = ./dep,./imgs 5 | ignore-words = ./.github/wordlist.txt 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Help us improve TairZset by reporting a bug 4 | title: '[BUG]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A short description of the bug. 13 | 14 | **To reproduce** 15 | 16 | Steps to reproduce the behavior and/or a minimal code sample. 17 | 18 | **Expected behavior** 19 | 20 | A description of what you expected to happen. 21 | 22 | **Additional information** 23 | 24 | Any additional information that is relevant to the problem. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/crash_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Crash report 3 | about: Submit a crash report 4 | title: '[CRASH]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Crash report** 11 | 12 | Paste the complete crash log between the quotes below. Please include a few lines from the log preceding the crash report to provide some context. 13 | 14 | ``` 15 | ``` 16 | 17 | **Additional information** 18 | 19 | 1. OS distribution and version 20 | 2. Steps to reproduce (if any) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature for TairZset 4 | title: '[NEW]' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **The problem/use-case that the feature addresses** 11 | 12 | A description of the problem that the feature will solve, or the use-case with which the feature will be used. 13 | 14 | **Description of the feature** 15 | 16 | A description of what you want to happen. 17 | 18 | **Alternatives you've considered** 19 | 20 | Any alternative solutions or features you've considered, including references to existing open and closed feature requests in this repository. 21 | 22 | **Additional information** 23 | 24 | Any additional information that is relevant to the feature request. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other_stuff.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other 3 | about: Can't find the right issue type? Use this one! 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/wordlist.txt: -------------------------------------------------------------------------------- 1 | ake 2 | bale 3 | fle 4 | fo 5 | gameboy 6 | mutli 7 | nd 8 | nees 9 | oll 10 | optin 11 | ot 12 | smove 13 | te 14 | tre 15 | cancelability 16 | ist -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | test-ubuntu-with-redis-5: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: clone and make redis 12 | run: | 13 | sudo apt-get install git 14 | git clone https://github.com/redis/redis 15 | cd redis 16 | git checkout 5.0 17 | make REDIS_CFLAGS='-Werror' BUILD_TLS=no 18 | - name: make tairzset 19 | run: | 20 | mkdir build 21 | cd build 22 | cmake ../ 23 | make 24 | - name: test 25 | run: | 26 | sudo apt-get install tcl8.6 tclx 27 | work_path=$(pwd) 28 | module_path=$work_path/lib 29 | sed -e "s#your_path#$module_path#g" tests/tairzset.tcl > redis/tests/unit/type/tairzset.tcl 30 | cd redis 31 | ./runtest --stack-logging --single unit/type/tairzset 32 | 33 | test-ubuntu-with-redis-6: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: clone and make redis 38 | run: | 39 | sudo apt-get install git 40 | git clone https://github.com/redis/redis 41 | cd redis 42 | git checkout 6.0 43 | make REDIS_CFLAGS='-Werror' BUILD_TLS=no 44 | - name: make tairzset 45 | run: | 46 | mkdir build 47 | cd build 48 | cmake ../ 49 | make 50 | - name: test 51 | run: | 52 | sudo apt-get install tcl8.6 tclx 53 | work_path=$(pwd) 54 | module_path=$work_path/lib 55 | sed -e "s#your_path#$module_path#g" tests/tairzset.tcl > redis/tests/unit/type/tairzset.tcl 56 | sed -e "s#your_path#$module_path#g" tests/tairzset-6.0.0.tcl > redis/tests/unit/type/tairzset-6.0.0.tcl 57 | cd redis 58 | ./runtest --stack-logging --single unit/type/tairzset 59 | ./runtest --stack-logging --single unit/type/tairzset-6.0.0 60 | 61 | test-ubuntu-with-redis-unstable: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v2 65 | - name: clone and make redis 66 | run: | 67 | sudo apt-get install git 68 | git clone https://github.com/redis/redis 69 | cd redis 70 | git checkout unstable 71 | make REDIS_CFLAGS='-Werror' BUILD_TLS=no 72 | - name: make tairzset 73 | run: | 74 | mkdir build 75 | cd build 76 | cmake ../ 77 | make 78 | - name: test 79 | run: | 80 | sudo apt-get install tcl8.6 tclx 81 | work_path=$(pwd) 82 | module_path=$work_path/lib 83 | sed -e "s#your_path#$module_path#g" tests/tairzset.tcl > redis/tests/unit/type/tairzset.tcl 84 | sed -e "s#your_path#$module_path#g" tests/tairzset-6.0.0.tcl > redis/tests/unit/type/tairzset-6.0.0.tcl 85 | cd redis 86 | ./runtest --stack-logging --single unit/type/tairzset 87 | ./runtest --stack-logging --single unit/type/tairzset-6.0.0 88 | 89 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | 23 | - name: Configure CMake 24 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 25 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 26 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 27 | 28 | - name: Build 29 | # Build your program with the given configuration 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Build the Docker image 18 | run: docker build . --file Dockerfile --tag tairmodule/tairzset:$(date +%s) 19 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | # A CI action that using codespell to check spell. 2 | # .github/.codespellrc is a config file. 3 | # .github/wordlist.txt is a list of words that will ignore word checks. 4 | # More details please check the following link: 5 | # https://github.com/codespell-project/codespell 6 | name: Spellcheck 7 | 8 | on: 9 | push: 10 | pull_request: 11 | 12 | jobs: 13 | build: 14 | name: Spellcheck 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Install prerequisites 22 | run: sudo pip install codespell==2.0.0 23 | 24 | - name: Spell check 25 | run: codespell --config=./.github/.codespellrc 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.xo 3 | *.o 4 | .vscode/ 5 | lib 6 | build 7 | -------------------------------------------------------------------------------- /CMDDOC.md: -------------------------------------------------------------------------------- 1 | ## Command introduction 2 | 3 | ### EXZADD 4 | #### Grammar and complexity: 5 | > EXZADD key [NX|XX] [CH] [INCR] score member [score member ...] 6 | > time complexity:O(N) 7 | 8 | #### Command Description: 9 | Adds all the specified members with the specified (multi)scores to the tairzset stored at key. It is possible to specify multiple score / member pairs. If a specified member is already a member of the tairzset, the score is updated and the element reinserted at the right position to ensure the correct ordering. 10 | 11 | If key does not exist, a new tairzset with the specified members as sole members is created, like if the tairzset was empty. If the key exists but does not hold a tairzset, an error is returned. 12 | 13 | The format of score must be `score1#score2#score3#...`, and the score format of all members in a tairzset must be the same. each score values should be the string representation of a double precision floating point number. +inf and -inf values are valid values as well. 14 | 15 | #### Options: 16 | EXZADD supports a list of options, specified after the name of the key and before the first score argument. Options are: 17 | 18 | XX: Only update elements that already exist. Don't add new elements. 19 | NX: Only add new elements. Don't update already existing elements. 20 | CH: Modify the return value from the number of new elements added, to the total number of elements changed (CH is an abbreviation of changed). Changed elements are new elements added and elements already existing for which the score was updated. So elements specified in the command line having the same score as they had in the past are not counted. Note: normally the return value of EXZADD only counts the number of new elements added. 21 | INCR: When this option is specified EXZADD acts like EXZINCRBY. Only one score-element pair can be specified in this mode. 22 | 23 | #### Return value 24 | Integer reply, specifically: 25 | 26 | When used without optional arguments, the number of elements added to the tairzset (excluding score updates). 27 | If the CH option is specified, the number of elements that were changed (added or updated). 28 | If the INCR option is specified, the return value will be Bulk string reply: 29 | 30 | The new score of member represented as string (`score1#score2#score3#...`), or nil if the operation was aborted (when called with either the XX or the NX option). 31 | 32 | ### EXZINCRBY 33 | #### Grammar and complexity: 34 | > EXZINCRBY key increment member 35 | > time complexity:O(log(N)) 36 | 37 | #### Command Description: 38 | 39 | Increments the score of member in the tairzset stored at key by increment. If member does not exist in the tairzset, it is added with increment as its score (as if its previous score was 0.0). If key does not exist, a new tairzset with the specified member as its sole member is created. 40 | 41 | An error is returned when key exists but does not hold a tairzset. 42 | 43 | The score value should be the string representation of a numeric value, and accepts double precision floating point numbers. It is possible to provide a negative value to decrement the score. 44 | #### Return value 45 | Bulk string reply: the new score of member (`score1#score2#score3#...`), represented as string. 46 | ### EXZSCORE 47 | #### Grammar and complexity: 48 | > EXZSCORE key member 49 | > time complexity:O(1) 50 | 51 | #### Command Description: 52 | Returns the score of member in the tairzset at key. 53 | 54 | If member does not exist in the tairzset, or key does not exist, nil is returned. 55 | #### Return value 56 | Bulk string reply: the score of member (a double precision floating point number), represented as string. 57 | ### EXZRANGE 58 | #### Grammar and complexity: 59 | > EXZRANGE [WITHSCORES] 60 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements returned. 61 | 62 | #### Command Description: 63 | Returns the specified range of elements in the tairzset stored at . 64 | 65 | #### Options: 66 | The order of elements is from the lowest to the highest score. Elements with the same score are ordered lexicographically. 67 | 68 | The optional REV argument reverses the ordering, so elements are ordered from highest to lowest score, and score ties are resolved by reverse lexicographical ordering. 69 | 70 | The optional WITHSCORES argument supplements the command's reply with the scores of elements returned. The returned list contains value1,score1,...,valueN,scoreN instead of value1,...,valueN. Client libraries are free to return a more appropriate data type (suggestion: an array with (value, score) arrays/tuples). 71 | 72 | #### Index ranges 73 | By default, the command performs an index range query. The and arguments represent zero-based indexes, where 0 is the first element, 1 is the next element, and so on. These arguments specify an inclusive range, so for example, EXZRANGE myzset 0 1 will return both the first and the second element of the tairzset. 74 | 75 | The indexes can also be negative numbers indicating offsets from the end of the tairzset, with -1 being the last element of the tairzset, -2 the penultimate element, and so on. 76 | 77 | Out of range indexes do not produce an error. 78 | 79 | If is greater than either the end index of the tairzset or , an empty list is returned. 80 | 81 | If is greater than the end index of the tairzset, Redis will use the last element of the tairzset. 82 | #### Return value 83 | Array reply: list of elements in the specified range (optionally with their scores, in case the WITHSCORES option is given). 84 | ### EXZREVRANGE 85 | #### Grammar and complexity: 86 | > EXZREVRANGE [WITHSCORES] 87 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements returned. 88 | #### Command Description: 89 | Returns the specified range of elements in the tairzset stored at key. The elements are considered to be ordered from the highest to the lowest score. Descending lexicographical order is used for elements with equal score. 90 | 91 | Apart from the reversed ordering, EXZREVRANGE is similar to EXZRANGE. 92 | #### Return value 93 | Array reply: list of elements in the specified range (optionally with their scores). 94 | ### EXZRANGEBYSCORE 95 | #### Grammar and complexity: 96 | > EXZRANGEBYSCORE [WITHSCORES] 97 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)). 98 | #### Command Description: 99 | Returns all the elements in the tairzset at key with a score between min and max (including elements with score equal to min or max). The elements are considered to be ordered from low to high scores. 100 | 101 | The elements having the same score are returned in lexicographical order (this follows from a property of the tairzset implementation in Redis and does not involve further computation). 102 | 103 | The optional WITHSCORES argument makes the command return both the element and its score, instead of the element alone. 104 | 105 | #### Exclusive intervals and infinity 106 | min and max can be -inf and +inf, so that you are not required to know the highest or lowest score in the tairzset to get all elements from or up to a certain score. 107 | 108 | By default, the interval specified by min and max is closed (inclusive). It is possible to specify an open interval (exclusive) by prefixing the score with the character (. For example: 109 | 110 | EXZRANGEBYSCORE zset (1 5 111 | Will return all elements with 1 < score <= 5 while: 112 | 113 | EXZRANGEBYSCORE zset (5 (10 114 | Will return all the elements with 5 < score < 10 (5 and 10 excluded). 115 | #### Return value 116 | Array reply: list of elements in the specified range (optionally with their scores). 117 | ### EXZREVRANGEBYSCORE 118 | #### Grammar and complexity: 119 | > EXZREVRANGEBYSCORE [WITHSCORES] 120 | > time complexity: O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)). 121 | #### Command Description: 122 | Returns all the elements in the tairzset at key with a score between max and min (including elements with score equal to max or min). In contrary to the default ordering of tairzsets, for this command the elements are considered to be ordered from high to low scores. 123 | 124 | The elements having the same score are returned in reverse lexicographical order. 125 | 126 | Apart from the reversed ordering, EXZREVRANGEBYSCORE is similar to EXZRANGEBYSCORE. 127 | #### Return value 128 | Array reply: list of elements in the specified score range (optionally with their scores). 129 | ### EXZRANGEBYLEX 130 | #### Grammar and complexity: 131 | > EXZRANGEBYLEX [LIMIT offset count] 132 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)). 133 | #### Command Description: 134 | When all the elements in a tairzset are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the tairzset at key with a value between min and max. 135 | 136 | If the elements in the tairzset have different scores, the returned elements are unspecified. 137 | 138 | The elements are considered to be ordered from lower to higher strings as compared byte-by-byte using the memcmp() C function. Longer strings are considered greater than shorter strings if the common part is identical. 139 | 140 | The optional LIMIT argument can be used to only get a range of the matching elements (similar to SELECT LIMIT offset, count in SQL). A negative count returns all elements from the offset. Keep in mind that if offset is large, the tairzset needs to be traversed for offset elements before getting to the elements to return, which can add up to O(N) time complexity. 141 | 142 | #### Return value 143 | Array reply: list of elements in the specified score range. 144 | ### EXZREVRANGEBYLEX 145 | #### Grammar and complexity: 146 | > EXZREVRANGEBYLEX [LIMIT offset count] 147 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)). 148 | #### Command Description: 149 | When all the elements in a tairzset are inserted with the same score, in order to force lexicographical ordering, this command returns all the elements in the tairzset at key with a value between max and min. 150 | 151 | Apart from the reversed ordering, EXZREVRANGEBYLEX is similar to EXZRANGEBYLEX. 152 | #### Return value 153 | Array reply: list of elements in the specified score range. 154 | ### EXZREM 155 | #### Grammar and complexity: 156 | > EXZREM key member [member ...] 157 | > time complexity:O(M*log(N)) with N being the number of elements in the tairzset and M the number of elements to be removed. 158 | 159 | #### Command Description: 160 | Removes the specified members from the tairzset stored at key. Non existing members are ignored. 161 | 162 | An error is returned when key exists and does not hold a tairzset. 163 | 164 | #### Return value 165 | Integer reply, specifically: 166 | 167 | The number of members removed from the tairzset, not including non existing members. 168 | ### EXZREMRANGEBYSCORE 169 | #### Grammar and complexity: 170 | > EXZREMRANGEBYSCORE key min max 171 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements removed by the operation. 172 | #### Command Description: 173 | Removes all elements in the tairzset stored at key with a score between min and max (inclusive). 174 | 175 | #### Return value 176 | Integer reply: the number of elements removed. 177 | ### EXZREMRANGEBYRANK 178 | #### Grammar and complexity: 179 | > EXZREMRANGEBYRANK key start stop 180 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements removed by the operation. 181 | 182 | #### Command Description: 183 | Removes all elements in the tairzset stored at key with rank between start and stop. Both start and stop are 0 -based indexes with 0 being the element with the lowest score. These indexes can be negative numbers, where they indicate offsets starting at the element with the highest score. For example: -1 is the element with the highest score, -2 the element with the second highest score and so forth. 184 | #### Return value 185 | Integer reply: the number of elements removed. 186 | ### EXZREMRANGEBYLEX 187 | #### Grammar and complexity: 188 | > EXZREMRANGEBYLEX key min max 189 | > time complexity:O(log(N)+M) with N being the number of elements in the tairzset and M the number of elements removed by the operation. 190 | 191 | #### Command Description: 192 | When all the elements in a tairzset are inserted with the same score, in order to force lexicographical ordering, this command removes all elements in the tairzset stored at key between the lexicographical range specified by min and max. 193 | 194 | The meaning of min and max are the same of the EXZRANGEBYLEX command. Similarly, this command actually removes the same elements that EXZRANGEBYLEX would return if called with the same min and max arguments. 195 | 196 | #### Return value 197 | Integer reply: the number of elements removed. 198 | ### EXZCARD 199 | #### Grammar and complexity: 200 | > EXZCARD key 201 | > time complexity:O(1) 202 | #### Command Description: 203 | Returns the tairzset cardinality (number of elements) of the tairzset stored at key. 204 | 205 | #### Return value 206 | Integer reply: the cardinality (number of elements) of the tairzset, or 0 if key does not exist. 207 | ### EXZRANK 208 | > EXZRANK key member [WITHSCORE] 209 | > time complexity:O(log(N)) 210 | #### Command Description: 211 | Returns the rank of member in the tairzset stored at key, with the scores ordered from low to high. The rank (or index) is 0-based, which means that the member with the lowest score has rank 0. 212 | 213 | The optional WITHSCORES argument makes the command return both the ranke and the score, instead of the rank alone. 214 | 215 | Use EXZREVRANK to get the rank of an element with the scores ordered from high to low. 216 | #### Return value 217 | If member exists in the tairzset, Integer reply: the rank of member. 218 | If member does not exist in the tairzset or key does not exist, Bulk string reply: nil. 219 | ### EXZREVRANK 220 | > EXZREVRANK key member [WITHSCORE] 221 | > time complexity:O(log(N)) 222 | 223 | #### Command Description: 224 | Returns the rank of member in the tairzset stored at key, with the scores ordered from high to low. The rank (or index) is 0-based, which means that the member with the highest score has rank 0. 225 | 226 | The optional WITHSCORES argument makes the command return both the ranke and the score, instead of the rank alone. 227 | 228 | Use EXZRANK to get the rank of an element with the scores ordered from low to high. 229 | 230 | #### Return value 231 | If member exists in the tairzset, Integer reply: the rank of member. 232 | If member does not exist in the tairzset or key does not exist, Bulk string reply: nil. 233 | ### EXZCOUNT 234 | > EXZCOUNT key min max 235 | > time complexity:O(log(N)) with N being the number of elements in the tairzset. 236 | 237 | ### EXZMSCORE 238 | > EXZMSCORE key member [member ...] 239 | > time complexity:O(N) where N is the number of members being requested. 240 | 241 | #### Command Description: 242 | 243 | Returns the scores associated with the specified members in the sorted set stored at key. For every member that does not exist in the sorted set, a nil value is returned. 244 | 245 | #### Return value 246 | 247 | Array reply: list of scores or nil associated with the specified member values (multiple double-precision floating-point numbers separated by #), represented as strings. 248 | 249 | #### Command Description: 250 | Returns the number of elements in the tairzset at key with a score between min and max. 251 | 252 | The min and max arguments have the same semantic as described for ZRANGEBYSCORE. 253 | 254 | Note: the command has a complexity of just O(log(N)) because it uses elements ranks (see ZRANK) to get an idea of the range. Because of this there is no need to do a work proportional to the size of the range.#### Options: 255 | 256 | #### Return value 257 | Integer reply: the number of elements in the specified score range. 258 | ### EXZLEXCOUNT 259 | > EXZLEXCOUNT key min max 260 | > time complexity:O(log(N)) with N being the number of elements in the tairzset. 261 | 262 | #### Command Description: 263 | When all the elements in a tairzset are inserted with the same score, in order to force lexicographical ordering, this command returns the number of elements in the tairzset at key with a value between min and max. 264 | 265 | The min and max arguments have the same meaning as described for EXZRANGEBYLEX. 266 | 267 | Note: the command has a complexity of just O(log(N)) because it uses elements ranks (see ZRANK) to get an idea of the range. Because of this there is no need to do a work proportional to the size of the range. 268 | #### Return value 269 | Integer reply: the number of elements in the specified score range. 270 | 271 | ### EXZRANDMEMBER 272 | > EXZRANDMEMBER key [count [WITHSCORES]] 273 | > time complexity:O(N) where N is the number of elements returned. 274 | 275 | #### Command Description: 276 | 277 | When called with just the key argument, return a random element from the sorted set value stored at key. 278 | 279 | If the provided count argument is positive, return an array of distinct elements. The array's length is either count or the sorted set's cardinality (EXZCARD), whichever is lower. 280 | 281 | If called with a negative count, the behavior changes and the command is allowed to return the same element multiple times. In this case, the number of returned elements is the absolute value of the specified count. 282 | 283 | The optional WITHSCORES modifier changes the reply so it includes the respective scores of the randomly selected elements from the sorted set. 284 | 285 | #### Return value 286 | 287 | Bulk string reply: without the additional count argument, the command returns a Bulk Reply with the randomly selected element, or nil when key does not exist. 288 | 289 | Array reply: when the additional count argument is passed, the command returns an array of elements, or an empty array when key does not exist. If the WITHSCORES modifier is used, the reply is a list elements and their scores from the sorted set. 290 | ### EXZSCAN 291 | > EXZSCAN key cursor [MATCH pattern] [COUNT count] 292 | > time complexity: O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection. 293 | 294 | #### Command Description: 295 | 296 | Iterates elements of TairZset types and their associated scores. 297 | 298 | EXSCAN is a cursor based iterator. This means that at every call of the command, the server returns an updated cursor that the user needs to use as the cursor argument in the next call. An iteration starts when the cursor is set to 0, and terminates when the cursor returned by the server is 0. 299 | 300 | While EXSCAN does not provide guarantees about the number of elements returned at every iteration, it is possible to empirically adjust the behavior of EXZSCAN using the COUNT option. Basically with COUNT the user specified the amount of work that should be done at every call in order to retrieve elements from the collection. This is *just a hint* for the implementation, however generally speaking this is what you could expect most of the times from the implementation. 301 | 302 | It is possible to only iterate elements matching a given glob-style pattern, similarly to the behavior of the KEYS command that takes a pattern as only argument. To do so, just append the MATCH arguments at the end of the EXZSCAN command. It is important to note that the MATCH filter is applied after elements are retrieved from the collection, just before returning data to the client. This means that if the pattern matches very little elements inside the collection, EXZSCAN will likely return no elements in most iterations. 303 | 304 | More detail information: https://redis.io/commands/scan/ 305 | 306 | #### Return value 307 | 308 | A two elements multi-bulk reply, where the first element is a string representing an unsigned 64 bit number (the cursor), and the second element is a multi-bulk with an array of elements. 309 | 310 | ### EXZUNIONSTORE 311 | 312 | > EXZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM | MIN | MAX] 313 | > time complexity: O(N)+O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set. 314 | 315 | #### Command Description: 316 | 317 | Computes the union of numkeys sorted sets given by the specified keys, and stores the result in destination. It is mandatory to provide the number of input keys (numkeys) before passing the input keys and the other (optional) arguments. 318 | 319 | By default, the resulting score of an element is the sum of its scores in the sorted sets where it exists. 320 | 321 | Using the WEIGHTS option, it is possible to specify a multiplication factor for each input sorted set. This means that the score of every element in every input sorted set is multiplied by this factor before being passed to the aggregation function. When WEIGHTS is not given, the multiplication factors default to 1. 322 | 323 | With the AGGREGATE option, it is possible to specify how the results of the union are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists. 324 | 325 | If one dimension of the multiple score of a member become NaN during WEIGHTS or AGGREGATE operations, the dimension of the score will be set to 0. 326 | 327 | If destination already exists, it is overwritten. 328 | 329 | #### Return value 330 | 331 | Integer reply: the number of elements in the resulting sorted set at destination. 332 | 333 | ### EXZUNION 334 | 335 | > EXZUNION numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM | MIN | MAX] [WITHSCORES] 336 | > time complexity: O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set. 337 | 338 | #### Command Description: 339 | 340 | This command is similar to ZUNIONSTORE, but instead of storing the resulting sorted set, it is returned to the client. 341 | 342 | For a description of the WEIGHTS and AGGREGATE options, see ZUNIONSTORE. 343 | 344 | #### Return value 345 | 346 | Array reply: the result of union (optionally with their scores, in case the WITHSCORES option is given). 347 | 348 | 349 | ### EXZINTERSTORE 350 | 351 | > EXZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM | MIN | MAX] 352 | > time complexity: O(NK)+O(Mlog(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set. 353 | 354 | #### Command Description: 355 | 356 | Computes the intersection of numkeys sorted sets given by the specified keys, and stores the result in destination. It is mandatory to provide the number of input keys (numkeys) before passing the input keys and the other (optional) arguments. 357 | 358 | By default, the resulting score of an element is the sum of its scores in the sorted sets where it exists. Because intersection requires an element to be a member of every given sorted set, this results in the score of every element in the resulting sorted set to be equal to the number of input sorted sets. 359 | 360 | For a description of the WEIGHTS and AGGREGATE options, see EXZUNIONSTORE. 361 | 362 | If one dimension of the multiple score of a member become NaN during WEIGHTS or AGGREGATE operations, the dimension of the score will be set to 0. 363 | 364 | If destination already exists, it is overwritten. 365 | 366 | #### Return value 367 | 368 | Integer reply: the number of elements in the resulting sorted set at destination. 369 | 370 | ### EXZINTER 371 | 372 | > EXZINTER numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM | MIN | MAX] [WITHSCORES] 373 | > time complexity: O(NK)+O(Mlog(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set. 374 | 375 | #### Command Descriptions: 376 | 377 | This command is similar to EXZINTERSTORE, but instead of storing the resulting sorted set, it is returned to the client. 378 | 379 | For a description of the WEIGHTS and AGGREGATE options, see EXZUNIONSTORE. 380 | 381 | #### Return value 382 | 383 | Array reply: the result of intersection (optionally with their scores, in case the WITHSCORES option is given). 384 | 385 | ### EXZINTERCARD 386 | 387 | > EXZINTERCARD numkeys key [key ...] [LIMIT limit] 388 | > time complexity: O(N*K) worst case with N being the smallest input sorted set, K being the number of input sorted sets. 389 | 390 | #### Command Descriptions: 391 | 392 | This command is similar to EXZINTER, but instead of returning the result set, it returns just the cardinality of the result. 393 | 394 | Keys that do not exist are considered to be empty sets. With one of the keys being an empty set, the resulting set is also empty (since set intersection with an empty set always results in an empty set). 395 | 396 | By default, the command calculates the cardinality of the intersection of all given sets. When provided with the optional LIMIT argument (which defaults to 0 and means unlimited), if the intersection cardinality reaches limit partway through the computation, the algorithm will exit and yield limit as the cardinality. Such implementation ensures a significant speedup for queries where the limit is lower than the actual intersection cardinality. 397 | 398 | #### Return value 399 | 400 | Integer reply: the number of elements in the resulting intersection. 401 | 402 | ### EXZDIFFSTORE 403 | 404 | > EXZDIFFSTORE destination numkeys key [key ...] 405 | > time complexity: O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set. 406 | 407 | #### Command Descriptions: 408 | 409 | Computes the difference between the first and all successive input sorted sets and stores the result in destination. The total number of input keys is specified by numkeys. 410 | 411 | Keys that do not exist are considered to be empty sets. 412 | 413 | If destination already exists, it is overwritten. 414 | 415 | #### Return value 416 | 417 | Integer reply: the number of elements in the resulting sorted set at destination. 418 | 419 | ### EXZDIFF 420 | 421 | > EXZDIFF numkeys key [key ...] [WITHSCORES] 422 | > time complexity: O(L + (N-K)log(N)) worst case where L is the total number of elements in all the sets, N is the size of the first set, and K is the size of the result set. 423 | 424 | #### Command Descriptions: 425 | 426 | This command is similar to EXZDIFFSTORE, but instead of storing the resulting sorted set, it is returned to the client. 427 | 428 | #### Return value 429 | 430 | Array reply: the result of the difference (optionally with their scores, in case the WITHSCORES option is given). 431 | 432 | 433 | ### EXZPOPMAX 434 | 435 | > EXZPOPMAX key [count] 436 | > time complexity: O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped. 437 | 438 | #### Command Descriptions: 439 | 440 | Removes and returns up to count members with the highest scores in the sorted set stored at key. 441 | 442 | When left unspecified, the default value for count is 1. Specifying a count value that is higher than the sorted set's cardinality will not produce an error. When returning multiple elements, the one with the highest score will be the first, followed by the elements with lower scores. 443 | 444 | #### Return value 445 | 446 | Array reply: list of popped elements and scores. 447 | 448 | ### EXZPOPMIN 449 | 450 | > EXZPOPMIN key [count] 451 | > time complexity: O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped. 452 | 453 | #### Command Descriptions: 454 | 455 | Removes and returns up to count members with the lowest scores in the sorted set stored at key. 456 | 457 | When left unspecified, the default value for count is 1. Specifying a count value that is higher than the sorted set's cardinality will not produce an error. When returning multiple elements, the one with the lowest score will be the first, followed by the elements with greater scores. 458 | 459 | #### Return value 460 | 461 | Array reply: list of popped elements and scores. 462 | 463 | ### EXBZPOPMIN 464 | 465 | > EXBZPOPMIN key [key ...] timeout 466 | > time complexity: O(log(N)) with N being the number of elements in the sorted set. 467 | > Available for: Redis 6.0.0+ 468 | 469 | `EXBZPOPMIN` is the blocking variant of the sorted set `EXZPOPMIN` primitive. 470 | 471 | It is the blocking version because it blocks the connection when there are no members to pop from any of the given sorted sets. A member with the lowest score is popped from first sorted set that is non-empty, with the given keys being checked in the order that they are given. 472 | 473 | The `timeout` argument is interpreted as a double value specifying the maximum number of seconds to block. A timeout of zero can be used to block indefinitely. 474 | 475 | See the [BLPOP documentation](https://redis.io/commands/blpop/) for the exact semantics, since `EXBZPOPMIN` is identical to `BLPOP` with the only difference being the data structure being popped from. 476 | 477 | #### Return value 478 | 479 | Array reply: specifically: 480 | 481 | - A nil multi-bulk when no element could be popped and the timeout expired. 482 | - A three-element multi-bulk with the first element being the name of the key where a member was popped, the second element is the popped member itself, and the third element is the score of the popped element. 483 | 484 | ### EXBZPOPMAX 485 | 486 | > EXBZPOPMAX key [key ...] timeout 487 | > time complexity: O(log(N)) with N being the number of elements in the sorted set. 488 | > Available for: Redis 6.0.0+ 489 | 490 | `EXBZPOPMAX` is the blocking variant of the sorted set `EXZPOPMAX` primitive. 491 | 492 | It is the blocking version because it blocks the connection when there are no members to pop from any of the given sorted sets. A member with the lowest score is popped from first sorted set that is non-empty, with the given keys being checked in the order that they are given. 493 | 494 | The `timeout` argument is interpreted as a double value specifying the maximum number of seconds to block. A timeout of zero can be used to block indefinitely. 495 | 496 | See the [EXBZPOPMIN documentation](###EXBZPOPMIN) for the exact semantics, since `EXBZPOPMAX` is identical to `EXBZPOPMIN` with the only difference being the data structure being popped from. 497 | 498 | #### Return value 499 | 500 | Array reply: specifically: 501 | 502 | - A nil multi-bulk when no element could be popped and the timeout expired. 503 | - A three-element multi-bulk with the first element being the name of the key where a member was popped, the second element is the popped member itself, and the third element is the score of the popped element. 504 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | 3 | project(tairzset_module) 4 | 5 | set(ROOT_DIR ${CMAKE_SOURCE_DIR}) 6 | 7 | set(CMAKE_C_STANDARD 99) 8 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -W -Wall -g -ggdb -std=c99 -O2 -Wno-strict-aliasing -Wno-typedef-redefinition -Wno-sign-compare -Wno-unused-parameter") 9 | 10 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 11 | set(CMAKE_C_VISIBILITY_PRESET hidden) 12 | 13 | SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) 14 | 15 | include_directories(${ROOT_DIR}/dep) 16 | aux_source_directory(${ROOT_DIR}/dep USRC) 17 | add_subdirectory(src) 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis 2 | 3 | ENV TAIRZSET_URL https://github.com/alibaba/TairZset.git 4 | RUN set -ex; \ 5 | \ 6 | BUILD_DEPS=' \ 7 | ca-certificates \ 8 | cmake \ 9 | gcc \ 10 | git \ 11 | g++ \ 12 | make \ 13 | '; \ 14 | apt-get update; \ 15 | apt-get install -y $BUILD_DEPS --no-install-recommends; \ 16 | rm -rf /var/lib/apt/lists/*; \ 17 | git clone "$TAIRZSET_URL"; \ 18 | cd TairZset; \ 19 | mkdir -p build; \ 20 | cd build; \ 21 | cmake ..; \ 22 | make -j; \ 23 | cd ..; \ 24 | cp lib/tairzset_module.so /usr/local/lib/; \ 25 | \ 26 | apt-get purge -y --auto-remove $BUILD_DEPS 27 | ADD http://download.redis.io/redis-stable/redis.conf /usr/local/etc/redis/redis.conf 28 | RUN sed -i '1i loadmodule /usr/local/lib/tairzset_module.so' /usr/local/etc/redis/redis.conf; \ 29 | chmod 644 /usr/local/etc/redis/redis.conf; \ 30 | sed -i 's/^bind 127.0.0.1/#bind 127.0.0.1/g' /usr/local/etc/redis/redis.conf; \ 31 | sed -i 's/^protected-mode yes/protected-mode no/g' /usr/local/etc/redis/redis.conf 32 | CMD [ "redis-server", "/usr/local/etc/redis/redis.conf" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Alibaba Tair Team 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | 2 | ![](https://img.shields.io/badge/license-Apache--2.0-green) 3 | ![](https://img.shields.io/badge/PRs-welcome-green) 4 | [![CMake](https://github.com/alibaba/TairZset/actions/workflows/cmake.yml/badge.svg)](https://github.com/alibaba/TairZset/actions/workflows/cmake.yml) 5 | [![CI](https://github.com/alibaba/TairZset/actions/workflows/ci.yml/badge.svg)](https://github.com/alibaba/TairZset/actions/workflows/ci.yml) 6 | [![Docker Image CI](https://github.com/alibaba/TairZset/actions/workflows/docker-image.yml/badge.svg)](https://github.com/alibaba/TairZset/actions/workflows/docker-image.yml) 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | ## 简介 [英文说明](README.md) 16 |      TairZset是基于redis module开发的一种数据结构,和redis原生的zset数据结构相比,TairZset不但和原生zset一样具有丰富的数据接口和高性能,还提供了(任意)多维排序的能力。 17 | 18 | ### 主要的特性如下: 19 | 20 | - 支持多维(最大255)score排序,且任意维度精度不丢失 21 | - 在多维score下仍然支持incrby语义 22 | - 语法和原生zset类似 23 | 24 | ### 排序规则: 25 | 对于多维score而言,左边的score优先级大于右边的score,以一个三维score为例:score1#score2#score3,tairzset在比较时,会先比较score1,只有score1相等时才会比较score2,否则就以score1的比较结果作为整个score的比较结果。同理,只有当score2相等时才会比较score3。 26 | 27 | ### 应用场景: 28 | - 游戏玩家之间排序 29 | - 直播间主播热度排序 30 | 31 | ## 快速开始 32 | ``` 33 | 127.0.0.1:6379> exzadd tairzsetkey 1.1 x 2.2 y 34 | (integer) 2 35 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 36 | 1) "x" 37 | 2) "1.1000000000000001" 38 | 3) "y" 39 | 4) "2.2000000000000002" 40 | 127.0.0.1:6379> exzincrby tairzsetkey 2 x 41 | "3.1000000000000001" 42 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 43 | 1) "y" 44 | 2) "2.2000000000000002" 45 | 3) "x" 46 | 4) "3.1000000000000001" 47 | 127.0.0.1:6379> exzadd tairzsetkey 3.3#3.3 z 48 | (error) ERR score is not a valid format 49 | 127.0.0.1:6379> del tairzsetkey 50 | (integer) 1 51 | 127.0.0.1:6379> exzadd tairzsetkey 1.1#3.3 x 2.2#2.2 y 3.3#1.1 z 52 | (integer) 3 53 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 54 | 1) "x" 55 | 2) "1.1000000000000001#3.2999999999999998" 56 | 3) "y" 57 | 4) "2.2000000000000002#2.2000000000000002" 58 | 5) "z" 59 | 6) "3.2999999999999998#1.1000000000000001" 60 | 127.0.0.1:6379> exzincrby tairzsetkey 2 y 61 | (error) ERR score is not a valid format 62 | 127.0.0.1:6379> exzincrby tairzsetkey 2#0 y 63 | "4.2000000000000002#2.2000000000000002" 64 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 65 | 1) "x" 66 | 2) "1.1000000000000001#3.2999999999999998" 67 | 3) "z" 68 | 4) "3.2999999999999998#1.1000000000000001" 69 | 5) "y" 70 | 6) "4.2000000000000002#2.2000000000000002" 71 | ``` 72 | 73 | ## Docker 74 | ``` 75 | docker run -p 6379:6379 tairmodule/tairzset:latest 76 | ``` 77 | 78 | ## 编译及使用 79 | 80 | ``` 81 | mkdir build 82 | cd build 83 | cmake ../ && make -j 84 | ``` 85 | 编译成功后会在lib目录下产生tairzset_module.so库文件 86 | 87 | ``` 88 | ./redis-server --loadmodule /path/to/tairzset_module.so 89 | ``` 90 | ## 测试方法 91 | 92 | 1. 修改`tests`目录下tairzset.tcl文件中的路径为`set testmodule [file your_path/tairzset_module.so]` 93 | 2. 将`tests`目录下tairzset.tcl文件路径加入到redis的test_helper.tcl的all_tests中 94 | 3. 在redis根目录下运行./runtest --single tairzset 95 | 96 | ## 客户端 97 | 98 | | language | GitHub | 99 | |----------|---| 100 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 101 | | Python |https://github.com/alibaba/tair-py| 102 | | Go |https://github.com/alibaba/tair-go| 103 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| 104 | 105 | ## API 106 | [参考这里](CMDDOC.md) 107 | 108 | ## 适用redis版本 109 | redis >= 5.0 110 | 111 | ## 我们的modules 112 | 113 | [TairHash](https://github.com/alibaba/TairHash): 和redis hash类似,但是可以为field设置expire和version,支持高效的主动过期和被动过期 114 | [TairZset](https://github.com/alibaba/TairZset): 和redis zset类似,但是支持多(最大255)维排序,同时支持incrby语义,非常适合游戏排行榜场景 115 | [TairString](https://github.com/alibaba/TairString): 和redis string类似,但是支持设置expire和version,并提供CAS/CAD等实用命令,非常适用于分布式锁等场景 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![](https://img.shields.io/badge/license-Apache--2.0-green) 3 | ![](https://img.shields.io/badge/PRs-welcome-green) 4 | [![CMake](https://github.com/alibaba/TairZset/actions/workflows/cmake.yml/badge.svg)](https://github.com/alibaba/TairZset/actions/workflows/cmake.yml) 5 | [![CI](https://github.com/alibaba/TairZset/actions/workflows/ci.yml/badge.svg)](https://github.com/alibaba/TairZset/actions/workflows/ci.yml) 6 | [![Docker Image CI](https://github.com/alibaba/TairZset/actions/workflows/docker-image.yml/badge.svg)](https://github.com/alibaba/TairZset/actions/workflows/docker-image.yml) 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | ## Introduction [Chinese](README-CN.md) 17 |      TairZset is a data structure developed based on the redis module. Compared with the native zset data structure of redis, TairZset not only has the same rich data interface and high performance as the native zset, but also provides (arbitrary) multi-score sorting capabilities. 18 | 19 | ### Features: 20 | 21 | - Support multi-score(Limited to 255)sorting, and the accuracy of any dimension is not lost 22 | - Incrby semantics is still supported under multi-score sorting 23 | - The syntax is similar to redis zset 24 | 25 | ### Sorting rules: 26 | For multi-dimensional scores, the priority of the score on the left is greater than the score on the right. Take a three-dimensional score as an example: score1#score2#score3. When comparing, tairzset will compare score1 first, and only compare score2 when score1 is equal, otherwise it will Take the comparison result of score1 as the comparison result of the entire score. In the same way, score3 will be compared only when score2 is equal. 27 | ### Application scenario: 28 | - Sorting among gamers 29 | - Anchor popularity ranking in live room 30 | 31 | ## Quick start 32 | ``` 33 | 127.0.0.1:6379> exzadd tairzsetkey 1.1 x 2.2 y 34 | (integer) 2 35 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 36 | 1) "x" 37 | 2) "1.1000000000000001" 38 | 3) "y" 39 | 4) "2.2000000000000002" 40 | 127.0.0.1:6379> exzincrby tairzsetkey 2 x 41 | "3.1000000000000001" 42 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 43 | 1) "y" 44 | 2) "2.2000000000000002" 45 | 3) "x" 46 | 4) "3.1000000000000001" 47 | 127.0.0.1:6379> exzadd tairzsetkey 3.3#3.3 z 48 | (error) ERR score is not a valid format 49 | 127.0.0.1:6379> del tairzsetkey 50 | (integer) 1 51 | 127.0.0.1:6379> exzadd tairzsetkey 1.1#3.3 x 2.2#2.2 y 3.3#1.1 z 52 | (integer) 3 53 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 54 | 1) "x" 55 | 2) "1.1000000000000001#3.2999999999999998" 56 | 3) "y" 57 | 4) "2.2000000000000002#2.2000000000000002" 58 | 5) "z" 59 | 6) "3.2999999999999998#1.1000000000000001" 60 | 127.0.0.1:6379> exzincrby tairzsetkey 2 y 61 | (error) ERR score is not a valid format 62 | 127.0.0.1:6379> exzincrby tairzsetkey 2#0 y 63 | "4.2000000000000002#2.2000000000000002" 64 | 127.0.0.1:6379> exzrange tairzsetkey 0 -1 withscores 65 | 1) "x" 66 | 2) "1.1000000000000001#3.2999999999999998" 67 | 3) "z" 68 | 4) "3.2999999999999998#1.1000000000000001" 69 | 5) "y" 70 | 6) "4.2000000000000002#2.2000000000000002" 71 | ``` 72 | 73 | ## Docker 74 | ``` 75 | docker run -p 6379:6379 tairmodule/tairzset:latest 76 | ``` 77 | ## Build 78 | 79 | ``` 80 | mkdir build 81 | cd build 82 | cmake ../ && make -j 83 | ``` 84 | then the tairzset_module.so library file will be generated in the lib directory 85 | 86 | ``` 87 | ./redis-server --loadmodule /path/to/tairzset_module.so 88 | ``` 89 | ## Test 90 | 1. Modify the path in the tairzset.tcl file in the `tests` directory to `set testmodule [file your_path/tairzset_module.so]` 91 | 2. Put tairzset.tcl or link it in redis/tests. 92 | 3. run ./runtest --single tairzset 93 | 94 | ## Client 95 | 96 | | language | GitHub | 97 | |----------|---| 98 | | Java |https://github.com/alibaba/alibabacloud-tairjedis-sdk| 99 | | Python |https://github.com/alibaba/tair-py| 100 | | Go |https://github.com/alibaba/tair-go| 101 | | .Net |https://github.com/alibaba/AlibabaCloud.TairSDK| 102 | 103 | ## API 104 | [Ref](CMDDOC.md) 105 | 106 | ## Applicable redis version 107 | redis >= 5.0 108 | 109 | ### Our modules 110 | [TairHash](https://github.com/alibaba/TairHash): A redis module, similar to redis hash, but you can set expire and version for the field 111 | [TairZset](https://github.com/alibaba/TairZset): A redis module, similar to redis zset, but you can set multiple scores for each member to support multi-dimensional sorting 112 | [TairString](https://github.com/alibaba/TairString): A redis module, similar to redis string, but you can set expire and version for the value. It also provides many very useful commands, such as cas/cad, etc. 113 | -------------------------------------------------------------------------------- /dep/adlist.c: -------------------------------------------------------------------------------- 1 | /* adlist.c - A generic doubly linked list implementation 2 | * 3 | * Copyright (c) 2006-2010, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | 32 | #include 33 | #include "adlist.h" 34 | 35 | #define REDISMODULE_API_FUNC(x) (*x) 36 | extern void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); 37 | extern void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); 38 | 39 | #define zfree RedisModule_Free 40 | #define zmalloc RedisModule_Alloc 41 | 42 | /* Create a new list. The created list can be freed with 43 | * AlFreeList(), but private value of every node need to be freed 44 | * by the user before to call AlFreeList(). 45 | * 46 | * On error, NULL is returned. Otherwise the pointer to the new list. */ 47 | list *m_listCreate(void) 48 | { 49 | struct list *list; 50 | 51 | if ((list = zmalloc(sizeof(*list))) == NULL) 52 | return NULL; 53 | list->head = list->tail = NULL; 54 | list->len = 0; 55 | list->dup = NULL; 56 | list->free = NULL; 57 | list->match = NULL; 58 | return list; 59 | } 60 | 61 | /* Remove all the elements from the list without destroying the list itself. */ 62 | void m_listEmpty(list *list) 63 | { 64 | unsigned long len; 65 | listNode *current, *next; 66 | 67 | current = list->head; 68 | len = list->len; 69 | while(len--) { 70 | next = current->next; 71 | if (list->free) list->free(current->value); 72 | zfree(current); 73 | current = next; 74 | } 75 | list->head = list->tail = NULL; 76 | list->len = 0; 77 | } 78 | 79 | /* Free the whole list. 80 | * 81 | * This function can't fail. */ 82 | void m_listRelease(list *list) 83 | { 84 | m_listEmpty(list); 85 | zfree(list); 86 | } 87 | 88 | /* Add a new node to the list, to head, containing the specified 'value' 89 | * pointer as value. 90 | * 91 | * On error, NULL is returned and no operation is performed (i.e. the 92 | * list remains unaltered). 93 | * On success the 'list' pointer you pass to the function is returned. */ 94 | list *m_listAddNodeHead(list *list, void *value) 95 | { 96 | listNode *node; 97 | 98 | if ((node = zmalloc(sizeof(*node))) == NULL) 99 | return NULL; 100 | node->value = value; 101 | if (list->len == 0) { 102 | list->head = list->tail = node; 103 | node->prev = node->next = NULL; 104 | } else { 105 | node->prev = NULL; 106 | node->next = list->head; 107 | list->head->prev = node; 108 | list->head = node; 109 | } 110 | list->len++; 111 | return list; 112 | } 113 | 114 | /* Add a new node to the list, to tail, containing the specified 'value' 115 | * pointer as value. 116 | * 117 | * On error, NULL is returned and no operation is performed (i.e. the 118 | * list remains unaltered). 119 | * On success the 'list' pointer you pass to the function is returned. */ 120 | list *m_listAddNodeTail(list *list, void *value) 121 | { 122 | listNode *node; 123 | 124 | if ((node = zmalloc(sizeof(*node))) == NULL) 125 | return NULL; 126 | node->value = value; 127 | if (list->len == 0) { 128 | list->head = list->tail = node; 129 | node->prev = node->next = NULL; 130 | } else { 131 | node->prev = list->tail; 132 | node->next = NULL; 133 | list->tail->next = node; 134 | list->tail = node; 135 | } 136 | list->len++; 137 | return list; 138 | } 139 | 140 | list *m_listInsertNode(list *list, listNode *old_node, void *value, int after) { 141 | listNode *node; 142 | 143 | if ((node = zmalloc(sizeof(*node))) == NULL) 144 | return NULL; 145 | node->value = value; 146 | if (after) { 147 | node->prev = old_node; 148 | node->next = old_node->next; 149 | if (list->tail == old_node) { 150 | list->tail = node; 151 | } 152 | } else { 153 | node->next = old_node; 154 | node->prev = old_node->prev; 155 | if (list->head == old_node) { 156 | list->head = node; 157 | } 158 | } 159 | if (node->prev != NULL) { 160 | node->prev->next = node; 161 | } 162 | if (node->next != NULL) { 163 | node->next->prev = node; 164 | } 165 | list->len++; 166 | return list; 167 | } 168 | 169 | /* Remove the specified node from the specified list. 170 | * It's up to the caller to free the private value of the node. 171 | * 172 | * This function can't fail. */ 173 | void m_listDelNode(list *list, listNode *node) 174 | { 175 | if (node->prev) 176 | node->prev->next = node->next; 177 | else 178 | list->head = node->next; 179 | if (node->next) 180 | node->next->prev = node->prev; 181 | else 182 | list->tail = node->prev; 183 | if (list->free) list->free(node->value); 184 | zfree(node); 185 | list->len--; 186 | } 187 | 188 | /* Returns a list iterator 'iter'. After the initialization every 189 | * call to listNext() will return the next element of the list. 190 | * 191 | * This function can't fail. */ 192 | listIter *m_listGetIterator(list *list, int direction) 193 | { 194 | listIter *iter; 195 | 196 | if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL; 197 | if (direction == AL_START_HEAD) 198 | iter->next = list->head; 199 | else 200 | iter->next = list->tail; 201 | iter->direction = direction; 202 | return iter; 203 | } 204 | 205 | /* Release the iterator memory */ 206 | void m_listReleaseIterator(listIter *iter) { 207 | zfree(iter); 208 | } 209 | 210 | /* Create an iterator in the list private iterator structure */ 211 | void m_listRewind(list *list, listIter *li) { 212 | li->next = list->head; 213 | li->direction = AL_START_HEAD; 214 | } 215 | 216 | void m_listRewindTail(list *list, listIter *li) { 217 | li->next = list->tail; 218 | li->direction = AL_START_TAIL; 219 | } 220 | 221 | /* Return the next element of an iterator. 222 | * It's valid to remove the currently returned element using 223 | * listDelNode(), but not to remove other elements. 224 | * 225 | * The function returns a pointer to the next element of the list, 226 | * or NULL if there are no more elements, so the classical usage patter 227 | * is: 228 | * 229 | * iter = listGetIterator(list,); 230 | * while ((node = listNext(iter)) != NULL) { 231 | * doSomethingWith(listNodeValue(node)); 232 | * } 233 | * 234 | * */ 235 | listNode *m_listNext(listIter *iter) 236 | { 237 | listNode *current = iter->next; 238 | 239 | if (current != NULL) { 240 | if (iter->direction == AL_START_HEAD) 241 | iter->next = current->next; 242 | else 243 | iter->next = current->prev; 244 | } 245 | return current; 246 | } 247 | 248 | /* Duplicate the whole list. On out of memory NULL is returned. 249 | * On success a copy of the original list is returned. 250 | * 251 | * The 'Dup' method set with listSetDupMethod() function is used 252 | * to copy the node value. Otherwise the same pointer value of 253 | * the original node is used as value of the copied node. 254 | * 255 | * The original list both on success or error is never modified. */ 256 | list *m_listDup(list *orig) 257 | { 258 | list *copy; 259 | listIter iter; 260 | listNode *node; 261 | 262 | if ((copy = m_listCreate()) == NULL) 263 | return NULL; 264 | copy->dup = orig->dup; 265 | copy->free = orig->free; 266 | copy->match = orig->match; 267 | m_listRewind(orig, &iter); 268 | while((node = m_listNext(&iter)) != NULL) { 269 | void *value; 270 | 271 | if (copy->dup) { 272 | value = copy->dup(node->value); 273 | if (value == NULL) { 274 | m_listRelease(copy); 275 | return NULL; 276 | } 277 | } else 278 | value = node->value; 279 | if (m_listAddNodeTail(copy, value) == NULL) { 280 | m_listRelease(copy); 281 | return NULL; 282 | } 283 | } 284 | return copy; 285 | } 286 | 287 | /* Search the list for a node matching a given key. 288 | * The match is performed using the 'match' method 289 | * set with listSetMatchMethod(). If no 'match' method 290 | * is set, the 'value' pointer of every node is directly 291 | * compared with the 'key' pointer. 292 | * 293 | * On success the first matching node pointer is returned 294 | * (search starts from head). If no matching node exists 295 | * NULL is returned. */ 296 | listNode *m_listSearchKey(list *list, void *key) 297 | { 298 | listIter iter; 299 | listNode *node; 300 | 301 | m_listRewind(list, &iter); 302 | while((node = m_listNext(&iter)) != NULL) { 303 | if (list->match) { 304 | if (list->match(node->value, key)) { 305 | return node; 306 | } 307 | } else { 308 | if (key == node->value) { 309 | return node; 310 | } 311 | } 312 | } 313 | return NULL; 314 | } 315 | 316 | /* Return the element at the specified zero-based index 317 | * where 0 is the head, 1 is the element next to head 318 | * and so on. Negative integers are used in order to count 319 | * from the tail, -1 is the last element, -2 the penultimate 320 | * and so on. If the index is out of range NULL is returned. */ 321 | listNode *m_listIndex(list *list, long index) { 322 | listNode *n; 323 | 324 | if (index < 0) { 325 | index = (-index)-1; 326 | n = list->tail; 327 | while(index-- && n) n = n->prev; 328 | } else { 329 | n = list->head; 330 | while(index-- && n) n = n->next; 331 | } 332 | return n; 333 | } 334 | 335 | /* Rotate the list removing the tail node and inserting it to the head. */ 336 | void m_listRotateTailToHead(list *list) { 337 | if (listLength(list) <= 1) return; 338 | 339 | /* Detach current tail */ 340 | listNode *tail = list->tail; 341 | list->tail = tail->prev; 342 | list->tail->next = NULL; 343 | /* Move it as head */ 344 | list->head->prev = tail; 345 | tail->prev = NULL; 346 | tail->next = list->head; 347 | list->head = tail; 348 | } 349 | 350 | /* Rotate the list removing the head node and inserting it to the tail. */ 351 | void m_listRotateHeadToTail(list *list) { 352 | if (listLength(list) <= 1) return; 353 | 354 | listNode *head = list->head; 355 | /* Detach current head */ 356 | list->head = head->next; 357 | list->head->prev = NULL; 358 | /* Move it as tail */ 359 | list->tail->next = head; 360 | head->next = NULL; 361 | head->prev = list->tail; 362 | list->tail = head; 363 | } 364 | 365 | /* Add all the elements of the list 'o' at the end of the 366 | * list 'l'. The list 'other' remains empty but otherwise valid. */ 367 | void m_listJoin(list *l, list *o) { 368 | if (o->head) 369 | o->head->prev = l->tail; 370 | 371 | if (l->tail) 372 | l->tail->next = o->head; 373 | else 374 | l->head = o->head; 375 | 376 | if (o->tail) l->tail = o->tail; 377 | l->len += o->len; 378 | 379 | /* Setup other as an empty list. */ 380 | o->head = o->tail = NULL; 381 | o->len = 0; 382 | } 383 | -------------------------------------------------------------------------------- /dep/adlist.h: -------------------------------------------------------------------------------- 1 | /* adlist.h - A generic doubly linked list implementation 2 | * 3 | * Copyright (c) 2006-2012, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __ADLIST_H__ 32 | #define __ADLIST_H__ 33 | 34 | /* Node, List, and Iterator are the only data structures used currently. */ 35 | 36 | typedef struct listNode { 37 | struct listNode *prev; 38 | struct listNode *next; 39 | void *value; 40 | } listNode; 41 | 42 | typedef struct listIter { 43 | listNode *next; 44 | int direction; 45 | } listIter; 46 | 47 | typedef struct list { 48 | listNode *head; 49 | listNode *tail; 50 | void *(*dup)(void *ptr); 51 | void (*free)(void *ptr); 52 | int (*match)(void *ptr, void *key); 53 | unsigned long len; 54 | } list; 55 | 56 | /* Functions implemented as macros */ 57 | #define listLength(l) ((l)->len) 58 | #define listFirst(l) ((l)->head) 59 | #define listLast(l) ((l)->tail) 60 | #define listPrevNode(n) ((n)->prev) 61 | #define listNextNode(n) ((n)->next) 62 | #define listNodeValue(n) ((n)->value) 63 | 64 | #define listSetDupMethod(l,m) ((l)->dup = (m)) 65 | #define listSetFreeMethod(l,m) ((l)->free = (m)) 66 | #define listSetMatchMethod(l,m) ((l)->match = (m)) 67 | 68 | #define listGetDupMethod(l) ((l)->dup) 69 | #define listGetFree(l) ((l)->free) 70 | #define listGetMatchMethod(l) ((l)->match) 71 | 72 | /* Prototypes */ 73 | list *m_listCreate(void); 74 | void m_listRelease(list *list); 75 | void m_listEmpty(list *list); 76 | list *m_listAddNodeHead(list *list, void *value); 77 | list *m_listAddNodeTail(list *list, void *value); 78 | list *m_listInsertNode(list *list, listNode *old_node, void *value, int after); 79 | void m_listDelNode(list *list, listNode *node); 80 | listIter *m_listGetIterator(list *list, int direction); 81 | listNode *m_listNext(listIter *iter); 82 | void m_listReleaseIterator(listIter *iter); 83 | list *m_listDup(list *orig); 84 | listNode *m_listSearchKey(list *list, void *key); 85 | listNode *m_listIndex(list *list, long index); 86 | void m_listRewind(list *list, listIter *li); 87 | void m_listRewindTail(list *list, listIter *li); 88 | void m_listRotateTailToHead(list *list); 89 | void m_listRotateHeadToTail(list *list); 90 | void m_listJoin(list *l, list *o); 91 | 92 | /* Directions for iterators */ 93 | #define AL_START_HEAD 0 94 | #define AL_START_TAIL 1 95 | 96 | #endif /* __ADLIST_H__ */ 97 | -------------------------------------------------------------------------------- /dep/dict.h: -------------------------------------------------------------------------------- 1 | /* Hash Tables Implementation. 2 | * 3 | * This file implements in-memory hash tables with insert/del/replace/find/ 4 | * get-random-element operations. Hash tables will auto-resize if needed 5 | * tables of power of two in size are used, collisions are handled by 6 | * chaining. See the source code for more information... :) 7 | * 8 | * Copyright (c) 2006-2012, Salvatore Sanfilippo 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * * Neither the name of Redis nor the names of its contributors may be used 20 | * to endorse or promote products derived from this software without 21 | * specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 24 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 27 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 28 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 29 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 30 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 31 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | * POSSIBILITY OF SUCH DAMAGE. 34 | */ 35 | 36 | #include 37 | #include 38 | 39 | #ifndef __DICT_H 40 | #define __DICT_H 41 | 42 | #define DICT_OK 0 43 | #define DICT_ERR 1 44 | 45 | /* Unused arguments generate annoying warnings... */ 46 | #define DICT_NOTUSED(V) ((void)V) 47 | 48 | typedef struct m_dictEntry { 49 | void *key; 50 | union { 51 | void *val; 52 | uint64_t u64; 53 | int64_t s64; 54 | double d; 55 | } v; 56 | struct m_dictEntry *next; 57 | } m_dictEntry; 58 | 59 | typedef struct m_dictType { 60 | uint64_t (*hashFunction)(const void *key); 61 | void *(*keyDup)(void *privdata, const void *key); 62 | void *(*valDup)(void *privdata, const void *obj); 63 | int (*keyCompare)(void *privdata, const void *key1, const void *key2); 64 | void (*keyDestructor)(void *privdata, void *key); 65 | void (*valDestructor)(void *privdata, void *obj); 66 | } m_dictType; 67 | 68 | /* This is our hash table structure. Every dictionary has two of this as we 69 | * implement incremental rehashing, for the old to the new table. */ 70 | typedef struct dictht { 71 | m_dictEntry **table; 72 | unsigned long size; 73 | unsigned long sizemask; 74 | unsigned long used; 75 | } dictht; 76 | 77 | typedef struct dict { 78 | m_dictType *type; 79 | void *privdata; 80 | dictht ht[2]; 81 | long rehashidx; /* rehashing not in progress if rehashidx == -1 */ 82 | unsigned long iterators; /* number of iterators currently running */ 83 | } dict; 84 | 85 | /* If safe is set to 1 this is a safe iterator, that means, you can call 86 | * m_dictAdd, m_dictFind, and other functions against the dictionary even while 87 | * iterating. Otherwise it is a non safe iterator, and only m_dictNext() 88 | * should be called while iterating. */ 89 | typedef struct m_dictIterator { 90 | dict *d; 91 | long index; 92 | int table, safe; 93 | m_dictEntry *entry, *nextEntry; 94 | /* unsafe iterator fingerprint for misuse detection. */ 95 | long long fingerprint; 96 | } m_dictIterator; 97 | 98 | typedef void(dictScanFunction)(void *privdata, const m_dictEntry *de); 99 | typedef void(dictScanBucketFunction)(void *privdata, m_dictEntry **bucketref); 100 | 101 | /* This is the initial size of every hash table */ 102 | #define DICT_HT_INITIAL_SIZE 4 103 | #define HASHTABLE_MIN_FILL 10 /* Minimal hash table fill 10% */ 104 | 105 | /* ------------------------------- Macros ------------------------------------*/ 106 | #define dictFreeVal(d, entry) \ 107 | if ((d)->type->valDestructor) \ 108 | (d)->type->valDestructor((d)->privdata, (entry)->v.val) 109 | 110 | #define dictSetVal(d, entry, _val_) \ 111 | do { \ 112 | if ((d)->type->valDup) \ 113 | (entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \ 114 | else \ 115 | (entry)->v.val = (_val_); \ 116 | } while (0) 117 | 118 | #define dictSetSignedIntegerVal(entry, _val_) \ 119 | do { \ 120 | (entry)->v.s64 = _val_; \ 121 | } while (0) 122 | 123 | #define dictSetUnsignedIntegerVal(entry, _val_) \ 124 | do { \ 125 | (entry)->v.u64 = _val_; \ 126 | } while (0) 127 | 128 | #define dictSetDoubleVal(entry, _val_) \ 129 | do { \ 130 | (entry)->v.d = _val_; \ 131 | } while (0) 132 | 133 | #define dictFreeKey(d, entry) \ 134 | if ((d)->type->keyDestructor) \ 135 | (d)->type->keyDestructor((d)->privdata, (entry)->key) 136 | 137 | #define dictSetKey(d, entry, _key_) \ 138 | do { \ 139 | if ((d)->type->keyDup) \ 140 | (entry)->key = (d)->type->keyDup((d)->privdata, _key_); \ 141 | else \ 142 | (entry)->key = (_key_); \ 143 | } while (0) 144 | 145 | #define dictCompareKeys(d, key1, key2) \ 146 | (((d)->type->keyCompare) ? (d)->type->keyCompare((d)->privdata, key1, key2) : (key1) == (key2)) 147 | 148 | #define dictHashKey(d, key) (d)->type->hashFunction(key) 149 | #define dictGetKey(he) ((he)->key) 150 | #define dictGetVal(he) ((he)->v.val) 151 | #define dictGetSignedIntegerVal(he) ((he)->v.s64) 152 | #define dictGetUnsignedIntegerVal(he) ((he)->v.u64) 153 | #define dictGetDoubleVal(he) ((he)->v.d) 154 | #define dictSlots(d) ((d)->ht[0].size + (d)->ht[1].size) 155 | #define dictSize(d) ((d)->ht[0].used + (d)->ht[1].used) 156 | #define dictIsRehashing(d) ((d)->rehashidx != -1) 157 | 158 | /* API */ 159 | dict *m_dictCreate(m_dictType *type, void *privDataPtr); 160 | int m_dictExpand(dict *d, unsigned long size); 161 | int m_dictAdd(dict *d, void *key, void *val); 162 | m_dictEntry *m_dictAddRaw(dict *d, void *key, m_dictEntry **existing); 163 | m_dictEntry *m_dictAddOrFind(dict *d, void *key); 164 | int m_dictReplace(dict *d, void *key, void *val); 165 | int m_dictDelete(dict *d, const void *key); 166 | m_dictEntry *m_dictUnlink(dict *ht, const void *key); 167 | void m_dictFreeUnlinkedEntry(dict *d, m_dictEntry *he); 168 | void m_dictRelease(dict *d); 169 | m_dictEntry *m_dictFind(dict *d, const void *key); 170 | void *m_dictFetchValue(dict *d, const void *key); 171 | int m_dictResize(dict *d); 172 | m_dictIterator *m_dictGetIterator(dict *d); 173 | m_dictIterator *m_dictGetSafeIterator(dict *d); 174 | m_dictEntry *m_dictNext(m_dictIterator *iter); 175 | void m_dictReleaseIterator(m_dictIterator *iter); 176 | m_dictEntry *m_dictGetRandomKey(dict *d); 177 | m_dictEntry *m_dictGetFairRandomKey(dict *d); 178 | unsigned int m_dictGetSomeKeys(dict *d, m_dictEntry **des, unsigned int count); 179 | void m_dictGetStats(char *buf, size_t bufsize, dict *d); 180 | uint64_t m_dictGenHashFunction(const void *key, int len); 181 | uint64_t m_dictGenCaseHashFunction(const unsigned char *buf, int len); 182 | void m_dictEmpty(dict *d, void(callback)(void *)); 183 | void m_dictEnableResize(void); 184 | void m_dictDisableResize(void); 185 | int m_dictRehash(dict *d, int n); 186 | int m_dictRehashMilliseconds(dict *d, int ms); 187 | void m_dictSetHashFunctionSeed(uint8_t *seed); 188 | uint8_t *m_dictGetHashFunctionSeed(void); 189 | unsigned long m_dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata); 190 | uint64_t m_dictGetHash(dict *d, const void *key); 191 | m_dictEntry **m_dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash); 192 | int m_htNeedsResize(dict *dict); 193 | 194 | /* Hash table types */ 195 | extern m_dictType dictTypeHeapStringCopyKey; 196 | extern m_dictType dictTypeHeapStrings; 197 | extern m_dictType dictTypeHeapStringCopyKeyValue; 198 | 199 | #endif /* __DICT_H */ 200 | -------------------------------------------------------------------------------- /dep/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 | extern const char *SDS_NOINIT; 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | typedef char *sds; 44 | 45 | /* Note: sdshdr5 is never used, we just access the flags byte directly. 46 | * However is here to document the layout of type 5 SDS strings. */ 47 | struct __attribute__((__packed__)) sdshdr5 { 48 | unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ 49 | char buf[]; 50 | }; 51 | struct __attribute__((__packed__)) sdshdr8 { 52 | uint8_t len; /* used */ 53 | uint8_t alloc; /* excluding the header and null terminator */ 54 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 55 | char buf[]; 56 | }; 57 | struct __attribute__((__packed__)) sdshdr16 { 58 | uint16_t len; /* used */ 59 | uint16_t alloc; /* excluding the header and null terminator */ 60 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 61 | char buf[]; 62 | }; 63 | struct __attribute__((__packed__)) sdshdr32 { 64 | uint32_t len; /* used */ 65 | uint32_t alloc; /* excluding the header and null terminator */ 66 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 67 | char buf[]; 68 | }; 69 | struct __attribute__((__packed__)) sdshdr64 { 70 | uint64_t len; /* used */ 71 | uint64_t alloc; /* excluding the header and null terminator */ 72 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 73 | char buf[]; 74 | }; 75 | 76 | #define SDS_TYPE_5 0 77 | #define SDS_TYPE_8 1 78 | #define SDS_TYPE_16 2 79 | #define SDS_TYPE_32 3 80 | #define SDS_TYPE_64 4 81 | #define SDS_TYPE_MASK 7 82 | #define SDS_TYPE_BITS 3 83 | #define SDS_HDR_VAR(T, s) struct sdshdr##T *sh = (void *)((s) - (sizeof(struct sdshdr##T))); 84 | #define SDS_HDR(T, s) ((struct sdshdr##T *)((s) - (sizeof(struct sdshdr##T)))) 85 | #define SDS_TYPE_5_LEN(f) ((f) >> SDS_TYPE_BITS) 86 | 87 | static inline size_t sdslen(const sds s) { 88 | unsigned char flags = s[-1]; 89 | switch (flags & SDS_TYPE_MASK) { 90 | case SDS_TYPE_5: 91 | return SDS_TYPE_5_LEN(flags); 92 | case SDS_TYPE_8: 93 | return SDS_HDR(8, s)->len; 94 | case SDS_TYPE_16: 95 | return SDS_HDR(16, s)->len; 96 | case SDS_TYPE_32: 97 | return SDS_HDR(32, s)->len; 98 | case SDS_TYPE_64: 99 | return SDS_HDR(64, s)->len; 100 | } 101 | return 0; 102 | } 103 | 104 | static inline size_t sdsavail(const sds s) { 105 | unsigned char flags = s[-1]; 106 | switch (flags & SDS_TYPE_MASK) { 107 | case SDS_TYPE_5: { 108 | return 0; 109 | } 110 | case SDS_TYPE_8: { 111 | SDS_HDR_VAR(8, s); 112 | return sh->alloc - sh->len; 113 | } 114 | case SDS_TYPE_16: { 115 | SDS_HDR_VAR(16, s); 116 | return sh->alloc - sh->len; 117 | } 118 | case SDS_TYPE_32: { 119 | SDS_HDR_VAR(32, s); 120 | return sh->alloc - sh->len; 121 | } 122 | case SDS_TYPE_64: { 123 | SDS_HDR_VAR(64, s); 124 | return sh->alloc - sh->len; 125 | } 126 | } 127 | return 0; 128 | } 129 | 130 | static inline void sdssetlen(sds s, size_t newlen) { 131 | unsigned char flags = s[-1]; 132 | switch (flags & SDS_TYPE_MASK) { 133 | case SDS_TYPE_5: { 134 | unsigned char *fp = ((unsigned char *)s) - 1; 135 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 136 | } break; 137 | case SDS_TYPE_8: 138 | SDS_HDR(8, s)->len = newlen; 139 | break; 140 | case SDS_TYPE_16: 141 | SDS_HDR(16, s)->len = newlen; 142 | break; 143 | case SDS_TYPE_32: 144 | SDS_HDR(32, s)->len = newlen; 145 | break; 146 | case SDS_TYPE_64: 147 | SDS_HDR(64, s)->len = newlen; 148 | break; 149 | } 150 | } 151 | 152 | static inline void sdsinclen(sds s, size_t inc) { 153 | unsigned char flags = s[-1]; 154 | switch (flags & SDS_TYPE_MASK) { 155 | case SDS_TYPE_5: { 156 | unsigned char *fp = ((unsigned char *)s) - 1; 157 | unsigned char newlen = SDS_TYPE_5_LEN(flags) + inc; 158 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 159 | } break; 160 | case SDS_TYPE_8: 161 | SDS_HDR(8, s)->len += inc; 162 | break; 163 | case SDS_TYPE_16: 164 | SDS_HDR(16, s)->len += inc; 165 | break; 166 | case SDS_TYPE_32: 167 | SDS_HDR(32, s)->len += inc; 168 | break; 169 | case SDS_TYPE_64: 170 | SDS_HDR(64, s)->len += inc; 171 | break; 172 | } 173 | } 174 | 175 | /* sdsalloc() = sdsavail() + sdslen() */ 176 | static inline size_t sdsalloc(const sds s) { 177 | unsigned char flags = s[-1]; 178 | switch (flags & SDS_TYPE_MASK) { 179 | case SDS_TYPE_5: 180 | return SDS_TYPE_5_LEN(flags); 181 | case SDS_TYPE_8: 182 | return SDS_HDR(8, s)->alloc; 183 | case SDS_TYPE_16: 184 | return SDS_HDR(16, s)->alloc; 185 | case SDS_TYPE_32: 186 | return SDS_HDR(32, s)->alloc; 187 | case SDS_TYPE_64: 188 | return SDS_HDR(64, s)->alloc; 189 | } 190 | return 0; 191 | } 192 | 193 | static inline void sdssetalloc(sds s, size_t newlen) { 194 | unsigned char flags = s[-1]; 195 | switch (flags & SDS_TYPE_MASK) { 196 | case SDS_TYPE_5: 197 | /* Nothing to do, this type has no total allocation info. */ 198 | break; 199 | case SDS_TYPE_8: 200 | SDS_HDR(8, s)->alloc = newlen; 201 | break; 202 | case SDS_TYPE_16: 203 | SDS_HDR(16, s)->alloc = newlen; 204 | break; 205 | case SDS_TYPE_32: 206 | SDS_HDR(32, s)->alloc = newlen; 207 | break; 208 | case SDS_TYPE_64: 209 | SDS_HDR(64, s)->alloc = newlen; 210 | break; 211 | } 212 | } 213 | 214 | sds m_sdsnewlen(const void *init, size_t initlen); 215 | sds m_sdsnew(const char *init); 216 | sds m_sdsempty(void); 217 | sds m_sdsdup(const sds s); 218 | void m_sdsfree(sds s); 219 | sds m_sdsgrowzero(sds s, size_t len); 220 | sds m_sdscatlen(sds s, const void *t, size_t len); 221 | sds m_sdscat(sds s, const char *t); 222 | sds m_sdscatsds(sds s, const sds t); 223 | sds m_sdscpylen(sds s, const char *t, size_t len); 224 | sds m_sdscpy(sds s, const char *t); 225 | 226 | sds m_sdscatvprintf(sds s, const char *fmt, va_list ap); 227 | #ifdef __GNUC__ 228 | sds m_sdscatprintf(sds s, const char *fmt, ...) 229 | __attribute__((format(printf, 2, 3))); 230 | #else 231 | sds m_sdscatprintf(sds s, const char *fmt, ...); 232 | #endif 233 | 234 | sds m_sdscatfmt(sds s, char const *fmt, ...); 235 | sds m_sdstrim(sds s, const char *cset); 236 | void m_sdsrange(sds s, ssize_t start, ssize_t end); 237 | void m_sdsupdatelen(sds s); 238 | void m_sdsclear(sds s); 239 | int m_sdscmp(const sds s1, const sds s2); 240 | sds *m_sdssplitlen(const char *s, ssize_t len, const char *sep, int seplen, int *count); 241 | void m_sdsfreesplitres(sds *tokens, int count); 242 | void m_sdstolower(sds s); 243 | void m_sdstoupper(sds s); 244 | sds m_sdsfromlonglong(long long value); 245 | sds m_sdscatrepr(sds s, const char *p, size_t len); 246 | sds *m_sdssplitargs(const char *line, int *argc); 247 | sds m_sdsmapchars(sds s, const char *from, const char *to, size_t setlen); 248 | sds m_sdsjoin(char **argv, int argc, char *sep); 249 | sds m_sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); 250 | 251 | /* Low level functions exposed to the user API */ 252 | sds m_sdsMakeRoomFor(sds s, size_t addlen); 253 | void m_sdsIncrLen(sds s, ssize_t incr); 254 | sds m_sdsRemoveFreeSpace(sds s); 255 | size_t m_sdsAllocSize(sds s); 256 | void *m_sdsAllocPtr(sds s); 257 | 258 | /* Export the allocator used by SDS to the program using SDS. 259 | * Sometimes the program SDS is linked to, may use a different set of 260 | * allocators, but may want to allocate or free things that SDS will 261 | * respectively free or allocate. */ 262 | void *m_sds_malloc(size_t size); 263 | void *m_sds_realloc(void *ptr, size_t size); 264 | void m_sds_free(void *ptr); 265 | 266 | #ifdef REDIS_TEST 267 | int sdsTest(int argc, char *argv[]); 268 | #endif 269 | 270 | #endif 271 | -------------------------------------------------------------------------------- /dep/sdsalloc.h: -------------------------------------------------------------------------------- 1 | #define s_malloc RedisModule_Alloc 2 | #define s_realloc RedisModule_Realloc 3 | #define s_free RedisModule_Free 4 | -------------------------------------------------------------------------------- /dep/siphash.c: -------------------------------------------------------------------------------- 1 | /* 2 | SipHash reference C implementation 3 | 4 | Copyright (c) 2012-2016 Jean-Philippe Aumasson 5 | 6 | Copyright (c) 2012-2014 Daniel J. Bernstein 7 | Copyright (c) 2017 Salvatore Sanfilippo 8 | 9 | To the extent possible under law, the author(s) have dedicated all copyright 10 | and related and neighboring rights to this software to the public domain 11 | worldwide. This software is distributed without any warranty. 12 | 13 | You should have received a copy of the CC0 Public Domain Dedication along 14 | with this software. If not, see 15 | . 16 | 17 | ---------------------------------------------------------------------------- 18 | 19 | This version was modified by Salvatore Sanfilippo 20 | in the following ways: 21 | 22 | 1. We use SipHash 1-2. This is not believed to be as strong as the 23 | suggested 2-4 variant, but AFAIK there are not trivial attacks 24 | against this reduced-rounds version, and it runs at the same speed 25 | as Murmurhash2 that we used previously, why the 2-4 variant slowed 26 | down Redis by a 4% figure more or less. 27 | 2. Hard-code rounds in the hope the compiler can optimize it more 28 | in this raw from. Anyway we always want the standard 2-4 variant. 29 | 3. Modify the prototype and implementation so that the function directly 30 | returns an uint64_t value, the hash itself, instead of receiving an 31 | output buffer. This also means that the output size is set to 8 bytes 32 | and the 16 bytes output code handling was removed. 33 | 4. Provide a case insensitive variant to be used when hashing strings that 34 | must be considered identical by the hash table regardless of the case. 35 | If we don't have directly a case insensitive hash function, we need to 36 | perform a text transformation in some temporary buffer, which is costly. 37 | 5. Remove debugging code. 38 | 6. Modified the original test.c file to be a stand-alone function testing 39 | the function in the new form (returing an uint64_t) using just the 40 | relevant test vector. 41 | */ 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | /* Fast tolower() alike function that does not care about locale 49 | * but just returns a-z insetad of A-Z. */ 50 | int siptlw(int c) { 51 | if (c >= 'A' && c <= 'Z') { 52 | return c + ('a' - 'A'); 53 | } else { 54 | return c; 55 | } 56 | } 57 | 58 | /* Test of the CPU is Little Endian and supports not aligned accesses. 59 | * Two interesting conditions to speedup the function that happen to be 60 | * in most of x86 servers. */ 61 | #if defined(__X86_64__) || defined(__x86_64__) || defined(__i386__) 62 | #define UNALIGNED_LE_CPU 63 | #endif 64 | 65 | #define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) 66 | 67 | #define U32TO8_LE(p, v) \ 68 | (p)[0] = (uint8_t)((v)); \ 69 | (p)[1] = (uint8_t)((v) >> 8); \ 70 | (p)[2] = (uint8_t)((v) >> 16); \ 71 | (p)[3] = (uint8_t)((v) >> 24); 72 | 73 | #define U64TO8_LE(p, v) \ 74 | U32TO8_LE((p), (uint32_t)((v))); \ 75 | U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); 76 | 77 | #ifdef UNALIGNED_LE_CPU 78 | #define U8TO64_LE(p) (*((uint64_t *)(p))) 79 | #else 80 | #define U8TO64_LE(p) \ 81 | (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) 82 | #endif 83 | 84 | #define U8TO64_LE_NOCASE(p) \ 85 | (((uint64_t)(siptlw((p)[0]))) | ((uint64_t)(siptlw((p)[1])) << 8) | ((uint64_t)(siptlw((p)[2])) << 16) | ((uint64_t)(siptlw((p)[3])) << 24) | ((uint64_t)(siptlw((p)[4])) << 32) | ((uint64_t)(siptlw((p)[5])) << 40) | ((uint64_t)(siptlw((p)[6])) << 48) | ((uint64_t)(siptlw((p)[7])) << 56)) 86 | 87 | #define SIPROUND \ 88 | do { \ 89 | v0 += v1; \ 90 | v1 = ROTL(v1, 13); \ 91 | v1 ^= v0; \ 92 | v0 = ROTL(v0, 32); \ 93 | v2 += v3; \ 94 | v3 = ROTL(v3, 16); \ 95 | v3 ^= v2; \ 96 | v0 += v3; \ 97 | v3 = ROTL(v3, 21); \ 98 | v3 ^= v0; \ 99 | v2 += v1; \ 100 | v1 = ROTL(v1, 17); \ 101 | v1 ^= v2; \ 102 | v2 = ROTL(v2, 32); \ 103 | } while (0) 104 | 105 | uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k) { 106 | #ifndef UNALIGNED_LE_CPU 107 | uint64_t hash; 108 | uint8_t *out = (uint8_t *)&hash; 109 | #endif 110 | uint64_t v0 = 0x736f6d6570736575ULL; 111 | uint64_t v1 = 0x646f72616e646f6dULL; 112 | uint64_t v2 = 0x6c7967656e657261ULL; 113 | uint64_t v3 = 0x7465646279746573ULL; 114 | uint64_t k0 = U8TO64_LE(k); 115 | uint64_t k1 = U8TO64_LE(k + 8); 116 | uint64_t m; 117 | const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); 118 | const int left = inlen & 7; 119 | uint64_t b = ((uint64_t)inlen) << 56; 120 | v3 ^= k1; 121 | v2 ^= k0; 122 | v1 ^= k1; 123 | v0 ^= k0; 124 | 125 | for (; in != end; in += 8) { 126 | m = U8TO64_LE(in); 127 | v3 ^= m; 128 | 129 | SIPROUND; 130 | 131 | v0 ^= m; 132 | } 133 | 134 | switch (left) { 135 | case 7: 136 | b |= ((uint64_t)in[6]) << 48; /* fall-thru */ 137 | case 6: 138 | b |= ((uint64_t)in[5]) << 40; /* fall-thru */ 139 | case 5: 140 | b |= ((uint64_t)in[4]) << 32; /* fall-thru */ 141 | case 4: 142 | b |= ((uint64_t)in[3]) << 24; /* fall-thru */ 143 | case 3: 144 | b |= ((uint64_t)in[2]) << 16; /* fall-thru */ 145 | case 2: 146 | b |= ((uint64_t)in[1]) << 8; /* fall-thru */ 147 | case 1: 148 | b |= ((uint64_t)in[0]); 149 | break; 150 | case 0: 151 | break; 152 | } 153 | 154 | v3 ^= b; 155 | 156 | SIPROUND; 157 | 158 | v0 ^= b; 159 | v2 ^= 0xff; 160 | 161 | SIPROUND; 162 | SIPROUND; 163 | 164 | b = v0 ^ v1 ^ v2 ^ v3; 165 | #ifndef UNALIGNED_LE_CPU 166 | U64TO8_LE(out, b); 167 | return hash; 168 | #else 169 | return b; 170 | #endif 171 | } 172 | 173 | uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k) { 174 | #ifndef UNALIGNED_LE_CPU 175 | uint64_t hash; 176 | uint8_t *out = (uint8_t *)&hash; 177 | #endif 178 | uint64_t v0 = 0x736f6d6570736575ULL; 179 | uint64_t v1 = 0x646f72616e646f6dULL; 180 | uint64_t v2 = 0x6c7967656e657261ULL; 181 | uint64_t v3 = 0x7465646279746573ULL; 182 | uint64_t k0 = U8TO64_LE(k); 183 | uint64_t k1 = U8TO64_LE(k + 8); 184 | uint64_t m; 185 | const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); 186 | const int left = inlen & 7; 187 | uint64_t b = ((uint64_t)inlen) << 56; 188 | v3 ^= k1; 189 | v2 ^= k0; 190 | v1 ^= k1; 191 | v0 ^= k0; 192 | 193 | for (; in != end; in += 8) { 194 | m = U8TO64_LE_NOCASE(in); 195 | v3 ^= m; 196 | 197 | SIPROUND; 198 | 199 | v0 ^= m; 200 | } 201 | 202 | switch (left) { 203 | case 7: 204 | b |= ((uint64_t)siptlw(in[6])) << 48; /* fall-thru */ 205 | case 6: 206 | b |= ((uint64_t)siptlw(in[5])) << 40; /* fall-thru */ 207 | case 5: 208 | b |= ((uint64_t)siptlw(in[4])) << 32; /* fall-thru */ 209 | case 4: 210 | b |= ((uint64_t)siptlw(in[3])) << 24; /* fall-thru */ 211 | case 3: 212 | b |= ((uint64_t)siptlw(in[2])) << 16; /* fall-thru */ 213 | case 2: 214 | b |= ((uint64_t)siptlw(in[1])) << 8; /* fall-thru */ 215 | case 1: 216 | b |= ((uint64_t)siptlw(in[0])); 217 | break; 218 | case 0: 219 | break; 220 | } 221 | 222 | v3 ^= b; 223 | 224 | SIPROUND; 225 | 226 | v0 ^= b; 227 | v2 ^= 0xff; 228 | 229 | SIPROUND; 230 | SIPROUND; 231 | 232 | b = v0 ^ v1 ^ v2 ^ v3; 233 | #ifndef UNALIGNED_LE_CPU 234 | U64TO8_LE(out, b); 235 | return hash; 236 | #else 237 | return b; 238 | #endif 239 | } 240 | 241 | /* --------------------------------- TEST ------------------------------------ */ 242 | 243 | #ifdef SIPHASH_TEST 244 | 245 | const uint8_t vectors_sip64[64][8] = { 246 | { 247 | 0x31, 248 | 0x0e, 249 | 0x0e, 250 | 0xdd, 251 | 0x47, 252 | 0xdb, 253 | 0x6f, 254 | 0x72, 255 | }, 256 | { 257 | 0xfd, 258 | 0x67, 259 | 0xdc, 260 | 0x93, 261 | 0xc5, 262 | 0x39, 263 | 0xf8, 264 | 0x74, 265 | }, 266 | { 267 | 0x5a, 268 | 0x4f, 269 | 0xa9, 270 | 0xd9, 271 | 0x09, 272 | 0x80, 273 | 0x6c, 274 | 0x0d, 275 | }, 276 | { 277 | 0x2d, 278 | 0x7e, 279 | 0xfb, 280 | 0xd7, 281 | 0x96, 282 | 0x66, 283 | 0x67, 284 | 0x85, 285 | }, 286 | { 287 | 0xb7, 288 | 0x87, 289 | 0x71, 290 | 0x27, 291 | 0xe0, 292 | 0x94, 293 | 0x27, 294 | 0xcf, 295 | }, 296 | { 297 | 0x8d, 298 | 0xa6, 299 | 0x99, 300 | 0xcd, 301 | 0x64, 302 | 0x55, 303 | 0x76, 304 | 0x18, 305 | }, 306 | { 307 | 0xce, 308 | 0xe3, 309 | 0xfe, 310 | 0x58, 311 | 0x6e, 312 | 0x46, 313 | 0xc9, 314 | 0xcb, 315 | }, 316 | { 317 | 0x37, 318 | 0xd1, 319 | 0x01, 320 | 0x8b, 321 | 0xf5, 322 | 0x00, 323 | 0x02, 324 | 0xab, 325 | }, 326 | { 327 | 0x62, 328 | 0x24, 329 | 0x93, 330 | 0x9a, 331 | 0x79, 332 | 0xf5, 333 | 0xf5, 334 | 0x93, 335 | }, 336 | { 337 | 0xb0, 338 | 0xe4, 339 | 0xa9, 340 | 0x0b, 341 | 0xdf, 342 | 0x82, 343 | 0x00, 344 | 0x9e, 345 | }, 346 | { 347 | 0xf3, 348 | 0xb9, 349 | 0xdd, 350 | 0x94, 351 | 0xc5, 352 | 0xbb, 353 | 0x5d, 354 | 0x7a, 355 | }, 356 | { 357 | 0xa7, 358 | 0xad, 359 | 0x6b, 360 | 0x22, 361 | 0x46, 362 | 0x2f, 363 | 0xb3, 364 | 0xf4, 365 | }, 366 | { 367 | 0xfb, 368 | 0xe5, 369 | 0x0e, 370 | 0x86, 371 | 0xbc, 372 | 0x8f, 373 | 0x1e, 374 | 0x75, 375 | }, 376 | { 377 | 0x90, 378 | 0x3d, 379 | 0x84, 380 | 0xc0, 381 | 0x27, 382 | 0x56, 383 | 0xea, 384 | 0x14, 385 | }, 386 | { 387 | 0xee, 388 | 0xf2, 389 | 0x7a, 390 | 0x8e, 391 | 0x90, 392 | 0xca, 393 | 0x23, 394 | 0xf7, 395 | }, 396 | { 397 | 0xe5, 398 | 0x45, 399 | 0xbe, 400 | 0x49, 401 | 0x61, 402 | 0xca, 403 | 0x29, 404 | 0xa1, 405 | }, 406 | { 407 | 0xdb, 408 | 0x9b, 409 | 0xc2, 410 | 0x57, 411 | 0x7f, 412 | 0xcc, 413 | 0x2a, 414 | 0x3f, 415 | }, 416 | { 417 | 0x94, 418 | 0x47, 419 | 0xbe, 420 | 0x2c, 421 | 0xf5, 422 | 0xe9, 423 | 0x9a, 424 | 0x69, 425 | }, 426 | { 427 | 0x9c, 428 | 0xd3, 429 | 0x8d, 430 | 0x96, 431 | 0xf0, 432 | 0xb3, 433 | 0xc1, 434 | 0x4b, 435 | }, 436 | { 437 | 0xbd, 438 | 0x61, 439 | 0x79, 440 | 0xa7, 441 | 0x1d, 442 | 0xc9, 443 | 0x6d, 444 | 0xbb, 445 | }, 446 | { 447 | 0x98, 448 | 0xee, 449 | 0xa2, 450 | 0x1a, 451 | 0xf2, 452 | 0x5c, 453 | 0xd6, 454 | 0xbe, 455 | }, 456 | { 457 | 0xc7, 458 | 0x67, 459 | 0x3b, 460 | 0x2e, 461 | 0xb0, 462 | 0xcb, 463 | 0xf2, 464 | 0xd0, 465 | }, 466 | { 467 | 0x88, 468 | 0x3e, 469 | 0xa3, 470 | 0xe3, 471 | 0x95, 472 | 0x67, 473 | 0x53, 474 | 0x93, 475 | }, 476 | { 477 | 0xc8, 478 | 0xce, 479 | 0x5c, 480 | 0xcd, 481 | 0x8c, 482 | 0x03, 483 | 0x0c, 484 | 0xa8, 485 | }, 486 | { 487 | 0x94, 488 | 0xaf, 489 | 0x49, 490 | 0xf6, 491 | 0xc6, 492 | 0x50, 493 | 0xad, 494 | 0xb8, 495 | }, 496 | { 497 | 0xea, 498 | 0xb8, 499 | 0x85, 500 | 0x8a, 501 | 0xde, 502 | 0x92, 503 | 0xe1, 504 | 0xbc, 505 | }, 506 | { 507 | 0xf3, 508 | 0x15, 509 | 0xbb, 510 | 0x5b, 511 | 0xb8, 512 | 0x35, 513 | 0xd8, 514 | 0x17, 515 | }, 516 | { 517 | 0xad, 518 | 0xcf, 519 | 0x6b, 520 | 0x07, 521 | 0x63, 522 | 0x61, 523 | 0x2e, 524 | 0x2f, 525 | }, 526 | { 527 | 0xa5, 528 | 0xc9, 529 | 0x1d, 530 | 0xa7, 531 | 0xac, 532 | 0xaa, 533 | 0x4d, 534 | 0xde, 535 | }, 536 | { 537 | 0x71, 538 | 0x65, 539 | 0x95, 540 | 0x87, 541 | 0x66, 542 | 0x50, 543 | 0xa2, 544 | 0xa6, 545 | }, 546 | { 547 | 0x28, 548 | 0xef, 549 | 0x49, 550 | 0x5c, 551 | 0x53, 552 | 0xa3, 553 | 0x87, 554 | 0xad, 555 | }, 556 | { 557 | 0x42, 558 | 0xc3, 559 | 0x41, 560 | 0xd8, 561 | 0xfa, 562 | 0x92, 563 | 0xd8, 564 | 0x32, 565 | }, 566 | { 567 | 0xce, 568 | 0x7c, 569 | 0xf2, 570 | 0x72, 571 | 0x2f, 572 | 0x51, 573 | 0x27, 574 | 0x71, 575 | }, 576 | { 577 | 0xe3, 578 | 0x78, 579 | 0x59, 580 | 0xf9, 581 | 0x46, 582 | 0x23, 583 | 0xf3, 584 | 0xa7, 585 | }, 586 | { 587 | 0x38, 588 | 0x12, 589 | 0x05, 590 | 0xbb, 591 | 0x1a, 592 | 0xb0, 593 | 0xe0, 594 | 0x12, 595 | }, 596 | { 597 | 0xae, 598 | 0x97, 599 | 0xa1, 600 | 0x0f, 601 | 0xd4, 602 | 0x34, 603 | 0xe0, 604 | 0x15, 605 | }, 606 | { 607 | 0xb4, 608 | 0xa3, 609 | 0x15, 610 | 0x08, 611 | 0xbe, 612 | 0xff, 613 | 0x4d, 614 | 0x31, 615 | }, 616 | { 617 | 0x81, 618 | 0x39, 619 | 0x62, 620 | 0x29, 621 | 0xf0, 622 | 0x90, 623 | 0x79, 624 | 0x02, 625 | }, 626 | { 627 | 0x4d, 628 | 0x0c, 629 | 0xf4, 630 | 0x9e, 631 | 0xe5, 632 | 0xd4, 633 | 0xdc, 634 | 0xca, 635 | }, 636 | { 637 | 0x5c, 638 | 0x73, 639 | 0x33, 640 | 0x6a, 641 | 0x76, 642 | 0xd8, 643 | 0xbf, 644 | 0x9a, 645 | }, 646 | { 647 | 0xd0, 648 | 0xa7, 649 | 0x04, 650 | 0x53, 651 | 0x6b, 652 | 0xa9, 653 | 0x3e, 654 | 0x0e, 655 | }, 656 | { 657 | 0x92, 658 | 0x59, 659 | 0x58, 660 | 0xfc, 661 | 0xd6, 662 | 0x42, 663 | 0x0c, 664 | 0xad, 665 | }, 666 | { 667 | 0xa9, 668 | 0x15, 669 | 0xc2, 670 | 0x9b, 671 | 0xc8, 672 | 0x06, 673 | 0x73, 674 | 0x18, 675 | }, 676 | { 677 | 0x95, 678 | 0x2b, 679 | 0x79, 680 | 0xf3, 681 | 0xbc, 682 | 0x0a, 683 | 0xa6, 684 | 0xd4, 685 | }, 686 | { 687 | 0xf2, 688 | 0x1d, 689 | 0xf2, 690 | 0xe4, 691 | 0x1d, 692 | 0x45, 693 | 0x35, 694 | 0xf9, 695 | }, 696 | { 697 | 0x87, 698 | 0x57, 699 | 0x75, 700 | 0x19, 701 | 0x04, 702 | 0x8f, 703 | 0x53, 704 | 0xa9, 705 | }, 706 | { 707 | 0x10, 708 | 0xa5, 709 | 0x6c, 710 | 0xf5, 711 | 0xdf, 712 | 0xcd, 713 | 0x9a, 714 | 0xdb, 715 | }, 716 | { 717 | 0xeb, 718 | 0x75, 719 | 0x09, 720 | 0x5c, 721 | 0xcd, 722 | 0x98, 723 | 0x6c, 724 | 0xd0, 725 | }, 726 | { 727 | 0x51, 728 | 0xa9, 729 | 0xcb, 730 | 0x9e, 731 | 0xcb, 732 | 0xa3, 733 | 0x12, 734 | 0xe6, 735 | }, 736 | { 737 | 0x96, 738 | 0xaf, 739 | 0xad, 740 | 0xfc, 741 | 0x2c, 742 | 0xe6, 743 | 0x66, 744 | 0xc7, 745 | }, 746 | { 747 | 0x72, 748 | 0xfe, 749 | 0x52, 750 | 0x97, 751 | 0x5a, 752 | 0x43, 753 | 0x64, 754 | 0xee, 755 | }, 756 | { 757 | 0x5a, 758 | 0x16, 759 | 0x45, 760 | 0xb2, 761 | 0x76, 762 | 0xd5, 763 | 0x92, 764 | 0xa1, 765 | }, 766 | { 767 | 0xb2, 768 | 0x74, 769 | 0xcb, 770 | 0x8e, 771 | 0xbf, 772 | 0x87, 773 | 0x87, 774 | 0x0a, 775 | }, 776 | { 777 | 0x6f, 778 | 0x9b, 779 | 0xb4, 780 | 0x20, 781 | 0x3d, 782 | 0xe7, 783 | 0xb3, 784 | 0x81, 785 | }, 786 | { 787 | 0xea, 788 | 0xec, 789 | 0xb2, 790 | 0xa3, 791 | 0x0b, 792 | 0x22, 793 | 0xa8, 794 | 0x7f, 795 | }, 796 | { 797 | 0x99, 798 | 0x24, 799 | 0xa4, 800 | 0x3c, 801 | 0xc1, 802 | 0x31, 803 | 0x57, 804 | 0x24, 805 | }, 806 | { 807 | 0xbd, 808 | 0x83, 809 | 0x8d, 810 | 0x3a, 811 | 0xaf, 812 | 0xbf, 813 | 0x8d, 814 | 0xb7, 815 | }, 816 | { 817 | 0x0b, 818 | 0x1a, 819 | 0x2a, 820 | 0x32, 821 | 0x65, 822 | 0xd5, 823 | 0x1a, 824 | 0xea, 825 | }, 826 | { 827 | 0x13, 828 | 0x50, 829 | 0x79, 830 | 0xa3, 831 | 0x23, 832 | 0x1c, 833 | 0xe6, 834 | 0x60, 835 | }, 836 | { 837 | 0x93, 838 | 0x2b, 839 | 0x28, 840 | 0x46, 841 | 0xe4, 842 | 0xd7, 843 | 0x06, 844 | 0x66, 845 | }, 846 | { 847 | 0xe1, 848 | 0x91, 849 | 0x5f, 850 | 0x5c, 851 | 0xb1, 852 | 0xec, 853 | 0xa4, 854 | 0x6c, 855 | }, 856 | { 857 | 0xf3, 858 | 0x25, 859 | 0x96, 860 | 0x5c, 861 | 0xa1, 862 | 0x6d, 863 | 0x62, 864 | 0x9f, 865 | }, 866 | { 867 | 0x57, 868 | 0x5f, 869 | 0xf2, 870 | 0x8e, 871 | 0x60, 872 | 0x38, 873 | 0x1b, 874 | 0xe5, 875 | }, 876 | { 877 | 0x72, 878 | 0x45, 879 | 0x06, 880 | 0xeb, 881 | 0x4c, 882 | 0x32, 883 | 0x8a, 884 | 0x95, 885 | }, 886 | }; 887 | 888 | /* Test siphash using a test vector. Returns 0 if the function passed 889 | * all the tests, otherwise 1 is returned. 890 | * 891 | * IMPORTANT: The test vector is for SipHash 2-4. Before running 892 | * the test revert back the siphash() function to 2-4 rounds since 893 | * now it uses 1-2 rounds. */ 894 | int siphash_test(void) { 895 | uint8_t in[64], k[16]; 896 | int i; 897 | int fails = 0; 898 | 899 | for (i = 0; i < 16; ++i) 900 | k[i] = i; 901 | 902 | for (i = 0; i < 64; ++i) { 903 | in[i] = i; 904 | uint64_t hash = siphash(in, i, k); 905 | const uint8_t *v = NULL; 906 | v = (uint8_t *)vectors_sip64; 907 | if (memcmp(&hash, v + (i * 8), 8)) { 908 | /* printf("fail for %d bytes\n", i); */ 909 | fails++; 910 | } 911 | } 912 | 913 | /* Run a few basic tests with the case insensitive version. */ 914 | uint64_t h1, h2; 915 | h1 = siphash((uint8_t *)"hello world", 11, (uint8_t *)"1234567812345678"); 916 | h2 = siphash_nocase((uint8_t *)"hello world", 11, (uint8_t *)"1234567812345678"); 917 | if (h1 != h2) fails++; 918 | 919 | h1 = siphash((uint8_t *)"hello world", 11, (uint8_t *)"1234567812345678"); 920 | h2 = siphash_nocase((uint8_t *)"HELLO world", 11, (uint8_t *)"1234567812345678"); 921 | if (h1 != h2) fails++; 922 | 923 | h1 = siphash((uint8_t *)"HELLO world", 11, (uint8_t *)"1234567812345678"); 924 | h2 = siphash_nocase((uint8_t *)"HELLO world", 11, (uint8_t *)"1234567812345678"); 925 | if (h1 == h2) fails++; 926 | 927 | if (!fails) return 0; 928 | return 1; 929 | } 930 | 931 | int main(void) { 932 | if (siphash_test() == 0) { 933 | printf("SipHash test: OK\n"); 934 | return 0; 935 | } else { 936 | printf("SipHash test: FAILED\n"); 937 | return 1; 938 | } 939 | } 940 | 941 | #endif 942 | -------------------------------------------------------------------------------- /dep/skiplist.c: -------------------------------------------------------------------------------- 1 | #include "skiplist.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.h" 8 | 9 | static inline void *rm_malloc(size_t n) { 10 | return RedisModule_Alloc(n); 11 | } 12 | 13 | static inline void *rm_calloc(size_t nelem, size_t elemsz) { 14 | return RedisModule_Calloc(nelem, elemsz); 15 | } 16 | 17 | static inline void rm_free(void *p) { 18 | RedisModule_Free(p); 19 | } 20 | 21 | RedisModuleString *shared_minstring = NULL; 22 | RedisModuleString *shared_maxstring = NULL; 23 | 24 | /* Create a skiplist node with the specified number of levels. 25 | * The SDS string 'ele' is referenced by the node after the call. */ 26 | m_zskiplistNode *m_zslCreateNode(int level, scoretype *score, RedisModuleString *ele) { 27 | m_zskiplistNode *zn = rm_malloc(sizeof(*zn) + level * sizeof(struct zskiplistLevel)); 28 | zn->score = score; 29 | zn->ele = ele; 30 | return zn; 31 | } 32 | 33 | /* Create a new skiplist. */ 34 | m_zskiplist *m_zslCreate(unsigned char score_num) { 35 | int j; 36 | m_zskiplist *zsl; 37 | 38 | zsl = rm_malloc(sizeof(*zsl)); 39 | zsl->level = 1; 40 | zsl->length = 0; 41 | scoretype *score = rm_calloc(1, sizeof(scoretype) + score_num * sizeof(double)); 42 | score->score_num = score_num; 43 | zsl->header = m_zslCreateNode(ZSKIPLIST_MAXLEVEL, score, NULL); 44 | for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) { 45 | zsl->header->level[j].forward = NULL; 46 | zsl->header->level[j].span = 0; 47 | } 48 | zsl->header->backward = NULL; 49 | zsl->tail = NULL; 50 | zsl->score_num = score_num; 51 | return zsl; 52 | } 53 | 54 | /* Free the specified skiplist node. The referenced SDS string representation 55 | * of the element is freed too, unless node->ele is set to NULL before calling 56 | * this function. */ 57 | void m_zslFreeNode(m_zskiplistNode *node) { 58 | if (node->ele) { 59 | RedisModule_FreeString(NULL, node->ele); 60 | } 61 | rm_free(node->score); 62 | rm_free(node); 63 | } 64 | 65 | /* Free a whole skiplist. */ 66 | void m_zslFree(m_zskiplist *zsl) { 67 | m_zskiplistNode *node = zsl->header->level[0].forward, *next; 68 | 69 | rm_free(zsl->header->ele); 70 | rm_free(zsl->header->score); 71 | rm_free(zsl->header); 72 | while (node) { 73 | next = node->level[0].forward; 74 | m_zslFreeNode(node); 75 | node = next; 76 | } 77 | rm_free(zsl); 78 | } 79 | 80 | /* Returns a random level for the new skiplist node we are going to create. 81 | * The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL 82 | * (both inclusive), with a powerlaw-alike distribution where higher 83 | * levels are less likely to be returned. */ 84 | int m_zslRandomLevel(void) { 85 | int level = 1; 86 | while ((random() & 0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) 87 | level += 1; 88 | return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL; 89 | } 90 | 91 | /* Insert a new node in the skiplist. Assumes the element does not already 92 | * exist (up to the caller to enforce that). The skiplist takes ownership 93 | * of the passed SDS string 'ele'. */ 94 | m_zskiplistNode *m_zslInsert(m_zskiplist *zsl, scoretype *score, RedisModuleString *ele) { 95 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 96 | unsigned int rank[ZSKIPLIST_MAXLEVEL]; 97 | int i, level; 98 | 99 | x = zsl->header; 100 | for (i = zsl->level - 1; i >= 0; i--) { 101 | /* store rank that is crossed to reach the insert position */ 102 | rank[i] = i == (zsl->level - 1) ? 0 : rank[i + 1]; 103 | while (x->level[i].forward && 104 | (mscoreCmp(x->level[i].forward->score, score) < 0 || 105 | (mscoreCmp(x->level[i].forward->score, score) == 0 && 106 | RedisModule_StringCompare(x->level[i].forward->ele, ele) < 0))) { 107 | rank[i] += x->level[i].span; 108 | x = x->level[i].forward; 109 | } 110 | update[i] = x; 111 | } 112 | /* we assume the element is not already inside, since we allow duplicated 113 | * scores, reinserting the same element should never happen since the 114 | * caller of m_zslInsert() should test in the hash table if the element is 115 | * already inside or not. */ 116 | level = m_zslRandomLevel(); 117 | if (level > zsl->level) { 118 | for (i = zsl->level; i < level; i++) { 119 | rank[i] = 0; 120 | update[i] = zsl->header; 121 | update[i]->level[i].span = zsl->length; 122 | } 123 | zsl->level = level; 124 | } 125 | x = m_zslCreateNode(level, score, ele); 126 | for (i = 0; i < level; i++) { 127 | x->level[i].forward = update[i]->level[i].forward; 128 | update[i]->level[i].forward = x; 129 | 130 | /* update span covered by update[i] as x is inserted here */ 131 | x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]); 132 | update[i]->level[i].span = (rank[0] - rank[i]) + 1; 133 | } 134 | 135 | /* increment span for untouched levels */ 136 | for (i = level; i < zsl->level; i++) { 137 | update[i]->level[i].span++; 138 | } 139 | 140 | x->backward = (update[0] == zsl->header) ? NULL : update[0]; 141 | if (x->level[0].forward) 142 | x->level[0].forward->backward = x; 143 | else 144 | zsl->tail = x; 145 | zsl->length++; 146 | return x; 147 | } 148 | 149 | /* Internal function used by m_zslDelete, zslDeleteByScore and zslDeleteByRank */ 150 | void m_zslDeleteNode(m_zskiplist *zsl, m_zskiplistNode *x, m_zskiplistNode **update) { 151 | int i; 152 | for (i = 0; i < zsl->level; i++) { 153 | if (update[i]->level[i].forward == x) { 154 | update[i]->level[i].span += x->level[i].span - 1; 155 | update[i]->level[i].forward = x->level[i].forward; 156 | } else { 157 | update[i]->level[i].span -= 1; 158 | } 159 | } 160 | if (x->level[0].forward) { 161 | x->level[0].forward->backward = x->backward; 162 | } else { 163 | zsl->tail = x->backward; 164 | } 165 | while (zsl->level > 1 && zsl->header->level[zsl->level - 1].forward == NULL) 166 | zsl->level--; 167 | zsl->length--; 168 | } 169 | 170 | /* Delete an element with matching score/element from the skiplist. 171 | * The function returns 1 if the node was found and deleted, otherwise 172 | * 0 is returned. 173 | * 174 | * If 'node' is NULL the deleted node is freed by m_zslFreeNode(), otherwise 175 | * it is not freed (but just unlinked) and *node is set to the node pointer, 176 | * so that it is possible for the caller to reuse the node (including the 177 | * referenced SDS string at node->ele). */ 178 | int m_zslDelete(m_zskiplist *zsl, scoretype *score, RedisModuleString *ele, m_zskiplistNode **node) { 179 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 180 | int i; 181 | 182 | x = zsl->header; 183 | for (i = zsl->level - 1; i >= 0; i--) { 184 | while (x->level[i].forward && 185 | (mscoreCmp(x->level[i].forward->score, score) < 0 || 186 | (mscoreCmp(x->level[i].forward->score, score) == 0 && 187 | RedisModule_StringCompare(x->level[i].forward->ele, ele) < 0))) { 188 | x = x->level[i].forward; 189 | } 190 | update[i] = x; 191 | } 192 | /* We may have multiple elements with the same score, what we need 193 | * is to find the element with both the right score and object. */ 194 | x = x->level[0].forward; 195 | if (x && mscoreCmp(score, x->score) == 0 && RedisModule_StringCompare(x->ele, ele) == 0) { 196 | m_zslDeleteNode(zsl, x, update); 197 | if (!node) 198 | m_zslFreeNode(x); 199 | else 200 | *node = x; 201 | return 1; 202 | } 203 | return 0; /* not found */ 204 | } 205 | 206 | /* Update the score of an elmenent inside the sorted set skiplist. 207 | * Note that the element must exist and must match 'score'. 208 | * This function does not update the score in the hash table side, the 209 | * caller should take care of it. 210 | * 211 | * Note that this function attempts to just update the node, in case after 212 | * the score update, the node would be exactly at the same position. 213 | * Otherwise the skiplist is modified by removing and re-adding a new 214 | * element, which is more costly. 215 | * 216 | * The function returns the updated element skiplist node pointer. */ 217 | m_zskiplistNode *m_zslUpdateScore(m_zskiplist *zsl, scoretype *curscore, RedisModuleString *ele, scoretype *newscore) { 218 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 219 | int i; 220 | 221 | /* We need to seek to element to update to start: this is useful anyway, 222 | * we'll have to update or remove it. */ 223 | x = zsl->header; 224 | for (i = zsl->level - 1; i >= 0; i--) { 225 | while (x->level[i].forward && 226 | (mscoreCmp(x->level[i].forward->score, curscore) < 0 || 227 | (mscoreCmp(x->level[i].forward->score, curscore) == 0 && RedisModule_StringCompare(x->level[i].forward->ele, ele) < 0))) { 228 | x = x->level[i].forward; 229 | } 230 | update[i] = x; 231 | } 232 | 233 | /* Jump to our element: note that this function assumes that the 234 | * element with the matching score exists. */ 235 | x = x->level[0].forward; 236 | assert(x && mscoreCmp(curscore, x->score) == 0 && RedisModule_StringCompare(x->ele, ele) == 0); 237 | /* If the node, after the score update, would be still exactly 238 | * at the same position, we can just update the score without 239 | * actually removing and re-inserting the element in the skiplist. */ 240 | if ((x->backward == NULL || mscoreCmp(x->backward->score, newscore) < 0) && (x->level[0].forward == NULL || mscoreCmp(x->level[0].forward->score, newscore) > 0)) { 241 | rm_free(x->score); 242 | x->score = newscore; 243 | return x; 244 | } 245 | 246 | /* No way to reuse the old node: we need to remove and insert a new 247 | * one at a different place. */ 248 | m_zslDeleteNode(zsl, x, update); 249 | m_zskiplistNode *newnode = m_zslInsert(zsl, newscore, x->ele); 250 | /* We reused the old node x->ele SDS string, free the node now 251 | * since m_zslInsert created a new one. */ 252 | x->ele = NULL; 253 | m_zslFreeNode(x); 254 | return newnode; 255 | } 256 | 257 | /* Delete all the elements with rank between start and end from the skiplist. 258 | * Start and end are inclusive. Note that start and end need to be 1-based */ 259 | unsigned long m_zslDeleteRangeByRank(m_zskiplist *zsl, unsigned int start, unsigned int end, dict *dict) { 260 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 261 | unsigned long traversed = 0, removed = 0; 262 | int i; 263 | 264 | x = zsl->header; 265 | for (i = zsl->level - 1; i >= 0; i--) { 266 | while (x->level[i].forward && (traversed + x->level[i].span) < start) { 267 | traversed += x->level[i].span; 268 | x = x->level[i].forward; 269 | } 270 | update[i] = x; 271 | } 272 | 273 | traversed++; 274 | x = x->level[0].forward; 275 | while (x && traversed <= end) { 276 | m_zskiplistNode *next = x->level[0].forward; 277 | m_zslDeleteNode(zsl, x, update); 278 | m_dictDelete(dict, x->ele); 279 | m_zslFreeNode(x); 280 | removed++; 281 | traversed++; 282 | x = next; 283 | } 284 | return removed; 285 | } 286 | 287 | /* Find the rank by score. 288 | * Returns 0 when the element cannot be found, rank otherwise. 289 | * Note that the rank is 0-based due to the span of zsl->header to the first element. */ 290 | unsigned long m_zslGetRankByScore(m_zskiplist *zsl, scoretype *score) { 291 | m_zskiplistNode *x; 292 | unsigned long rank = 0; 293 | int i; 294 | 295 | x = zsl->header; 296 | for (i = zsl->level - 1; i >= 0; i--) { 297 | while (x->level[i].forward && 298 | (mscoreCmp(x->level[i].forward->score, score) < 0)) { 299 | rank += x->level[i].span; 300 | x = x->level[i].forward; 301 | } 302 | } 303 | return rank; 304 | } 305 | 306 | /* Find the rank for an element by both score and key. 307 | * Returns 0 when the element cannot be found, rank otherwise. 308 | * Note that the rank is 1-based due to the span of zsl->header to the 309 | * first element. */ 310 | unsigned long m_zslGetRank(m_zskiplist *zsl, scoretype *score, RedisModuleString *ele) { 311 | m_zskiplistNode *x; 312 | unsigned long rank = 0; 313 | int i; 314 | 315 | x = zsl->header; 316 | for (i = zsl->level - 1; i >= 0; i--) { 317 | while (x->level[i].forward && 318 | (mscoreCmp(x->level[i].forward->score, score) < 0 || 319 | (mscoreCmp(x->level[i].forward->score, score) == 0 && 320 | RedisModule_StringCompare(x->level[i].forward->ele, ele) <= 0))) { 321 | rank += x->level[i].span; 322 | x = x->level[i].forward; 323 | } 324 | 325 | /* x might be equal to zsl->header, so test if obj is non-NULL */ 326 | if (x->ele && RedisModule_StringCompare(x->ele, ele) == 0) { 327 | return rank; 328 | } 329 | } 330 | return 0; 331 | } 332 | 333 | /* Finds an element by its rank. The rank argument needs to be 1-based. */ 334 | m_zskiplistNode *m_zslGetElementByRank(m_zskiplist *zsl, unsigned long rank) { 335 | m_zskiplistNode *x; 336 | unsigned long traversed = 0; 337 | int i; 338 | 339 | x = zsl->header; 340 | for (i = zsl->level - 1; i >= 0; i--) { 341 | while (x->level[i].forward && (traversed + x->level[i].span) <= rank) { 342 | traversed += x->level[i].span; 343 | x = x->level[i].forward; 344 | } 345 | if (traversed == rank) { 346 | return x; 347 | } 348 | } 349 | return NULL; 350 | } 351 | 352 | /* Populate the rangespec according to the objects min and max. */ 353 | int m_zslParseRange(RedisModuleString *min, RedisModuleString *max, m_zrangespec *spec) { 354 | spec->minex = spec->maxex = 0; 355 | 356 | size_t max_len, min_len; 357 | const char *min_ptr = RedisModule_StringPtrLen(min, &min_len); 358 | const char *max_ptr = RedisModule_StringPtrLen(max, &max_len); 359 | int ret; 360 | /* Parse the min-max interval. If one of the values is prefixed 361 | * by the "(" character, it's considered "open". For instance 362 | * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max 363 | * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */ 364 | 365 | if (min_ptr[0] == '(') { 366 | ret = mscoreParse(min_ptr + 1, min_len - 1, &spec->min); 367 | if (ret <= 0) return C_ERR; 368 | spec->minex = 1; 369 | } else { 370 | ret = mscoreParse(min_ptr, min_len, &spec->min); 371 | if (ret <= 0) return C_ERR; 372 | } 373 | 374 | if (max_ptr[0] == '(') { 375 | ret = mscoreParse(max_ptr + 1, max_len - 1, &spec->max); 376 | if (ret <= 0) return C_ERR; 377 | spec->maxex = 1; 378 | } else { 379 | ret = mscoreParse(max_ptr, max_len, &spec->max); 380 | if (ret <= 0) return C_ERR; 381 | } 382 | return C_OK; 383 | } 384 | 385 | /* ------------------------ Lexicographic ranges ---------------------------- */ 386 | 387 | /* Parse max or min argument of ZRANGEBYLEX. 388 | * (foo means foo (open interval) 389 | * [foo means foo (closed interval) 390 | * - means the min string possible 391 | * + means the max string possible 392 | * 393 | * If the string is valid the *dest pointer is set to the redis object 394 | * that will be used for the comparison, and ex will be set to 0 or 1 395 | * respectively if the item is exclusive or inclusive. C_OK will be 396 | * returned. 397 | * 398 | * If the string is not a valid range C_ERR is returned, and the value 399 | * of *dest and *ex is undefined. */ 400 | int m_zslParseLexRangeItem(RedisModuleString *item, RedisModuleString **dest, int *ex) { 401 | size_t len; 402 | const char *c = RedisModule_StringPtrLen(item, &len); 403 | 404 | switch (c[0]) { 405 | case '+': 406 | if (c[1] != '\0') return C_ERR; 407 | *ex = 1; 408 | *dest = shared_maxstring; 409 | return C_OK; 410 | case '-': 411 | if (c[1] != '\0') return C_ERR; 412 | *ex = 1; 413 | *dest = shared_minstring; 414 | return C_OK; 415 | case '(': 416 | *ex = 1; 417 | *dest = RedisModule_CreateString(NULL, c + 1, len - 1); 418 | return C_OK; 419 | case '[': 420 | *ex = 0; 421 | *dest = RedisModule_CreateString(NULL, c + 1, len - 1); 422 | return C_OK; 423 | default: 424 | return C_ERR; 425 | } 426 | } 427 | 428 | /* Free a lex range structure, must be called only after zelParseLexRange() 429 | * populated the structure with success (C_OK returned). */ 430 | void m_zslFreeLexRange(m_zlexrangespec *spec) { 431 | if (spec->min && spec->min != shared_minstring && spec->min != shared_maxstring) { 432 | RedisModule_FreeString(NULL, spec->min); 433 | } 434 | if (spec->max && spec->max != shared_minstring && spec->max != shared_maxstring) { 435 | RedisModule_FreeString(NULL, spec->max); 436 | } 437 | } 438 | 439 | /* Populate the lex rangespec according to the objects min and max. 440 | * 441 | * Return C_OK on success. On error C_ERR is returned. 442 | * When OK is returned the structure must be freed with m_zslFreeLexRange(), 443 | * otherwise no release is needed. */ 444 | int m_zslParseLexRange(RedisModuleString *min, RedisModuleString *max, m_zlexrangespec *spec) { 445 | spec->min = spec->max = NULL; 446 | if (m_zslParseLexRangeItem(min, &spec->min, &spec->minex) == C_ERR || m_zslParseLexRangeItem(max, &spec->max, &spec->maxex) == C_ERR) { 447 | m_zslFreeLexRange(spec); 448 | return C_ERR; 449 | } else { 450 | return C_OK; 451 | } 452 | } 453 | 454 | /* This is just a wrapper to m_sdscmp() that is able to 455 | * handle shared.minstring and shared.maxstring as the equivalent of 456 | * -inf and +inf for strings */ 457 | int m_mscmplex(RedisModuleString *a, RedisModuleString *b) { 458 | if (a == b) return 0; 459 | if (a == shared_minstring || b == shared_maxstring) return -1; 460 | if (a == shared_maxstring || b == shared_minstring) return 1; 461 | return RedisModule_StringCompare(a, b); 462 | } 463 | 464 | int m_zslLexValueGteMin(RedisModuleString *value, m_zlexrangespec *spec) { 465 | return spec->minex ? (m_mscmplex(value, spec->min) > 0) : (m_mscmplex(value, spec->min) >= 0); 466 | } 467 | 468 | int m_zslLexValueLteMax(RedisModuleString *value, m_zlexrangespec *spec) { 469 | return spec->maxex ? (m_mscmplex(value, spec->max) < 0) : (m_mscmplex(value, spec->max) <= 0); 470 | } 471 | 472 | int m_zslValueGteMin(scoretype *value, m_zrangespec *spec) { 473 | return spec->minex ? (mscoreCmp(value, spec->min) > 0) : (mscoreCmp(value, spec->min) >= 0); 474 | } 475 | 476 | int m_zslValueLteMax(scoretype *value, m_zrangespec *spec) { 477 | return spec->maxex ? (mscoreCmp(value, spec->max) < 0) : (mscoreCmp(value, spec->max) <= 0); 478 | } 479 | 480 | /* Returns if there is a part of the zset is in range. */ 481 | int m_zslIsInRange(m_zskiplist *zsl, m_zrangespec *range) { 482 | m_zskiplistNode *x; 483 | 484 | /* Test for ranges that will always be empty. */ 485 | if (mscoreCmp(range->min, range->max) > 0 || (mscoreCmp(range->min, range->max) == 0 && (range->minex || range->maxex))) 486 | return 0; 487 | x = zsl->tail; 488 | if (x == NULL || !m_zslValueGteMin(x->score, range)) 489 | return 0; 490 | x = zsl->header->level[0].forward; 491 | if (x == NULL || !m_zslValueLteMax(x->score, range)) 492 | return 0; 493 | return 1; 494 | } 495 | 496 | /* Find the first node that is contained in the specified range. 497 | * Returns NULL when no element is contained in the range. */ 498 | m_zskiplistNode *m_zslFirstInRange(m_zskiplist *zsl, m_zrangespec *range) { 499 | m_zskiplistNode *x; 500 | int i; 501 | 502 | /* If everything is out of range, return early. */ 503 | if (!m_zslIsInRange(zsl, range)) return NULL; 504 | 505 | x = zsl->header; 506 | for (i = zsl->level - 1; i >= 0; i--) { 507 | /* Go forward while *OUT* of range. */ 508 | while (x->level[i].forward && !m_zslValueGteMin(x->level[i].forward->score, range)) 509 | x = x->level[i].forward; 510 | } 511 | 512 | /* This is an inner range, so the next node cannot be NULL. */ 513 | x = x->level[0].forward; 514 | assert(x != NULL); 515 | 516 | /* Check if score <= max. */ 517 | if (!m_zslValueLteMax(x->score, range)) return NULL; 518 | return x; 519 | } 520 | 521 | /* Find the last node that is contained in the specified range. 522 | * Returns NULL when no element is contained in the range. */ 523 | m_zskiplistNode *m_zslLastInRange(m_zskiplist *zsl, m_zrangespec *range) { 524 | m_zskiplistNode *x; 525 | int i; 526 | 527 | /* If everything is out of range, return early. */ 528 | if (!m_zslIsInRange(zsl, range)) return NULL; 529 | 530 | x = zsl->header; 531 | for (i = zsl->level - 1; i >= 0; i--) { 532 | /* Go forward while *IN* range. */ 533 | while (x->level[i].forward && m_zslValueLteMax(x->level[i].forward->score, range)) 534 | x = x->level[i].forward; 535 | } 536 | 537 | /* This is an inner range, so this node cannot be NULL. */ 538 | assert(x != NULL); 539 | 540 | /* Check if score >= min. */ 541 | if (!m_zslValueGteMin(x->score, range)) return NULL; 542 | return x; 543 | } 544 | 545 | unsigned long m_zslDeleteRangeByScore(m_zskiplist *zsl, m_zrangespec *range, dict *dict) { 546 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 547 | unsigned long removed = 0; 548 | int i; 549 | 550 | x = zsl->header; 551 | for (i = zsl->level - 1; i >= 0; i--) { 552 | while (x->level[i].forward && (range->minex ? mscoreCmp(x->level[i].forward->score, range->min) <= 0 : mscoreCmp(x->level[i].forward->score, range->min) < 0)) 553 | x = x->level[i].forward; 554 | update[i] = x; 555 | } 556 | 557 | /* Current node is the last with score < or <= min. */ 558 | x = x->level[0].forward; 559 | 560 | /* Delete nodes while in range. */ 561 | while (x && (range->maxex ? mscoreCmp(x->score, range->max) < 0 : mscoreCmp(x->score, range->max) <= 0)) { 562 | m_zskiplistNode *next = x->level[0].forward; 563 | m_zslDeleteNode(zsl, x, update); 564 | m_dictDelete(dict, x->ele); 565 | m_zslFreeNode(x); /* Here is where x->ele is actually released. */ 566 | removed++; 567 | x = next; 568 | } 569 | return removed; 570 | } 571 | 572 | unsigned long m_zslDeleteRangeByLex(m_zskiplist *zsl, m_zlexrangespec *range, dict *dict) { 573 | m_zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; 574 | unsigned long removed = 0; 575 | int i; 576 | 577 | x = zsl->header; 578 | for (i = zsl->level - 1; i >= 0; i--) { 579 | while (x->level[i].forward && !m_zslLexValueGteMin(x->level[i].forward->ele, range)) 580 | x = x->level[i].forward; 581 | update[i] = x; 582 | } 583 | 584 | /* Current node is the last with score < or <= min. */ 585 | x = x->level[0].forward; 586 | 587 | /* Delete nodes while in range. */ 588 | while (x && m_zslLexValueLteMax(x->ele, range)) { 589 | m_zskiplistNode *next = x->level[0].forward; 590 | m_zslDeleteNode(zsl, x, update); 591 | m_dictDelete(dict, x->ele); 592 | m_zslFreeNode(x); /* Here is where x->ele is actually released. */ 593 | removed++; 594 | x = next; 595 | } 596 | return removed; 597 | } 598 | 599 | /* Returns if there is a part of the zset is in the lex range. */ 600 | int m_zslIsInLexRange(m_zskiplist *zsl, m_zlexrangespec *range) { 601 | m_zskiplistNode *x; 602 | 603 | /* Test for ranges that will always be empty. */ 604 | int cmp = m_mscmplex(range->min, range->max); 605 | if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex))) 606 | return 0; 607 | x = zsl->tail; 608 | if (x == NULL || !m_zslLexValueGteMin(x->ele, range)) 609 | return 0; 610 | x = zsl->header->level[0].forward; 611 | if (x == NULL || !m_zslLexValueLteMax(x->ele, range)) 612 | return 0; 613 | return 1; 614 | } 615 | 616 | /* Find the first node that is contained in the specified lex range. 617 | * Returns NULL when no element is contained in the range. */ 618 | m_zskiplistNode *m_zslFirstInLexRange(m_zskiplist *zsl, m_zlexrangespec *range) { 619 | m_zskiplistNode *x; 620 | int i; 621 | 622 | /* If everything is out of range, return early. */ 623 | if (!m_zslIsInLexRange(zsl, range)) { 624 | return NULL; 625 | } 626 | 627 | x = zsl->header; 628 | for (i = zsl->level - 1; i >= 0; i--) { 629 | /* Go forward while *OUT* of range. */ 630 | while (x->level[i].forward && !m_zslLexValueGteMin(x->level[i].forward->ele, range)) 631 | x = x->level[i].forward; 632 | } 633 | 634 | /* This is an inner range, so the next node cannot be NULL. */ 635 | x = x->level[0].forward; 636 | assert(x != NULL); 637 | 638 | /* Check if score <= max. */ 639 | if (!m_zslLexValueLteMax(x->ele, range)) { 640 | return NULL; 641 | } 642 | return x; 643 | } 644 | 645 | /* Find the last node that is contained in the specified range. 646 | * Returns NULL when no element is contained in the range. */ 647 | m_zskiplistNode *m_zslLastInLexRange(m_zskiplist *zsl, m_zlexrangespec *range) { 648 | m_zskiplistNode *x; 649 | int i; 650 | 651 | /* If everything is out of range, return early. */ 652 | if (!m_zslIsInLexRange(zsl, range)) { 653 | return NULL; 654 | } 655 | 656 | x = zsl->header; 657 | for (i = zsl->level - 1; i >= 0; i--) { 658 | /* Go forward while *IN* range. */ 659 | while (x->level[i].forward && m_zslLexValueLteMax(x->level[i].forward->ele, range)) 660 | x = x->level[i].forward; 661 | } 662 | 663 | /* This is an inner range, so this node cannot be NULL. */ 664 | assert(x != NULL); 665 | 666 | /* Check if score >= min. */ 667 | if (!m_zslLexValueGteMin(x->ele, range)) { 668 | return NULL; 669 | } 670 | return x; 671 | } 672 | 673 | /* ------------------------ multi scores api ---------------------------- */ 674 | 675 | int mscoreGetNum(const char *s, size_t slen) { 676 | int score_num = 0; 677 | 678 | if (slen == 0) { 679 | return -1; 680 | } 681 | 682 | if (s[0] == SCORE_DELIMITER || s[slen - 1] == SCORE_DELIMITER) { 683 | return -1; 684 | } 685 | 686 | const char *iter = s; 687 | while (iter <= s + slen) { 688 | if (*iter == SCORE_DELIMITER || iter == s + slen) { 689 | score_num++; 690 | iter++; 691 | continue; 692 | } 693 | iter++; 694 | } 695 | 696 | return score_num; 697 | } 698 | 699 | int mscoreParse(const char *s, size_t slen, scoretype **score) { 700 | const char *start = s; 701 | int len = 0, i = 0; 702 | double tmp_score; 703 | int score_num = mscoreGetNum(s, slen); 704 | if (score_num <= 0 || score_num > MAX_SCORE_NUM) { 705 | return -1; 706 | } 707 | 708 | scoretype *multi_score = (scoretype *)RedisModule_Calloc(1, sizeof(scoretype) + score_num * sizeof(double)); 709 | multi_score->score_num = score_num; 710 | const char *iter = s; 711 | while (iter <= s + slen) { 712 | if (*iter == SCORE_DELIMITER || iter == s + slen) { 713 | if (m_string2d(start, len, &tmp_score) != 1) { 714 | goto fail; 715 | } 716 | 717 | if (isnan(tmp_score)) { 718 | goto fail; 719 | } 720 | 721 | multi_score->scores[i++] = tmp_score; 722 | start = iter + 1; 723 | len = 0; 724 | iter++; 725 | continue; 726 | } 727 | len++; 728 | iter++; 729 | } 730 | 731 | *score = multi_score; 732 | return score_num; 733 | fail: 734 | RedisModule_Free(multi_score); 735 | return -1; 736 | } 737 | 738 | inline int mscoreCmp(scoretype *s1, scoretype *s2) { 739 | assert(s1 != NULL); 740 | assert(s2 != NULL); 741 | assert(s1->score_num == s2->score_num); 742 | 743 | int num = s1->score_num, i; 744 | 745 | double *s1_p = (double *)s1->scores; 746 | double *s2_p = (double *)s2->scores; 747 | 748 | for (i = 0; i < num; i++) { 749 | if (s1_p[i] != s2_p[i]) { 750 | return s1_p[i] < s2_p[i] ? -1 : 1; 751 | } 752 | } 753 | 754 | return 0; 755 | } 756 | 757 | sds mscore2String(scoretype *score) { 758 | assert(score != NULL); 759 | char scorebuf[128]; 760 | int scorelen; 761 | 762 | sds score_str = m_sdsempty(); 763 | 764 | int i = 0; 765 | for (; i < score->score_num; i++) { 766 | scorelen = m_d2string(scorebuf, sizeof(scorebuf), score->scores[i]); 767 | score_str = m_sdscatlen(score_str, scorebuf, scorelen); 768 | if (i < score->score_num - 1) { 769 | score_str = m_sdscatlen(score_str, "#", 1); 770 | } 771 | } 772 | 773 | return score_str; 774 | } 775 | 776 | int mscoreAdd(scoretype *s1, scoretype *s2) { 777 | assert(s1 != NULL); 778 | assert(s2 != NULL); 779 | assert(s1->score_num == s2->score_num); 780 | 781 | int i = 0; 782 | for (; i < s1->score_num; i++) { 783 | s1->scores[i] += s2->scores[i]; 784 | if (isnan(s1->scores[i])) { 785 | return -1; 786 | } 787 | } 788 | 789 | return 0; 790 | } 791 | 792 | void mscoreAddIgnoreNan(scoretype *s1, scoretype *s2) { 793 | assert(s1 != NULL); 794 | assert(s2 != NULL); 795 | assert(s1->score_num == s2->score_num); 796 | 797 | for (int i = 0; i < s1->score_num; i++) { 798 | s1->scores[i] += s2->scores[i]; 799 | if (isnan(s1->scores[i])) { 800 | /* If one dimension of the score become NaN, 801 | * set it to 0. */ 802 | s1->scores[i] = 0; 803 | } 804 | } 805 | } 806 | 807 | scoretype *mnewScore(int score_num) { 808 | scoretype *score = (scoretype *)rm_calloc(1, sizeof(scoretype) + score_num * sizeof(double)); 809 | score->score_num = score_num; 810 | return score; 811 | } 812 | 813 | void mscoreMulWithWeight(scoretype *dst, scoretype *base, double weight) { 814 | assert(dst != NULL); 815 | assert(base != NULL); 816 | assert(dst->score_num == base->score_num); 817 | 818 | for (int i = 0; i < dst->score_num; i++) { 819 | dst->scores[i] = base->scores[i] * weight; 820 | /* If one dimension of the score become NaN, 821 | * set it to 0. */ 822 | if (isnan(dst->scores[i])) { 823 | dst->scores[i] = 0; 824 | } 825 | } 826 | } 827 | 828 | void mscoreAssign(scoretype *target, scoretype *src) { 829 | assert(target != NULL); 830 | assert(src != NULL); 831 | assert(target->score_num == src->score_num); 832 | 833 | for (int i = 0; i < target->score_num; i++) { 834 | target->scores[i] = src->scores[i]; 835 | } 836 | } -------------------------------------------------------------------------------- /dep/skiplist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../src/redismodule.h" 4 | #include "sds.h" 5 | #include "dict.h" 6 | 7 | #define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */ 8 | #define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ 9 | 10 | /* Input flags. */ 11 | #define ZADD_NONE 0 12 | #define ZADD_INCR (1 << 0) /* Increment the score instead of setting it. */ 13 | #define ZADD_NX (1 << 1) /* Don't touch elements not already existing. */ 14 | #define ZADD_XX (1 << 2) /* Only touch elements already existing. */ 15 | 16 | /* Output flags. */ 17 | #define ZADD_NOP (1 << 3) /* Operation not performed because of conditionals.*/ 18 | #define ZADD_NAN (1 << 4) /* Only touch elements already existing. */ 19 | #define ZADD_ADDED (1 << 5) /* The element was new and was added. */ 20 | #define ZADD_UPDATED (1 << 6) /* The element already existed, score updated. */ 21 | 22 | /* Flags only used by the ZADD command but not by zsetAdd() API: */ 23 | #define ZADD_CH (1 << 16) /* Return num of elements added or updated. */ 24 | 25 | #define SCORE_DELIMITER '#' 26 | #define MAX_SCORE_NUM 255 27 | typedef struct scoretype { 28 | unsigned char score_num; 29 | double scores[0]; 30 | } scoretype; 31 | typedef struct m_zskiplistNode { 32 | RedisModuleString *ele; 33 | scoretype *score; 34 | struct m_zskiplistNode *backward; 35 | struct zskiplistLevel { 36 | struct m_zskiplistNode *forward; 37 | unsigned long span; 38 | } level[]; 39 | } m_zskiplistNode; 40 | 41 | typedef struct m_zskiplist { 42 | struct m_zskiplistNode *header, *tail; 43 | unsigned long length; 44 | int level; 45 | size_t score_num; // schema 46 | } m_zskiplist; 47 | 48 | typedef struct { 49 | scoretype *min, *max; 50 | int minex, maxex; /* are min or max exclusive? */ 51 | } m_zrangespec; 52 | 53 | typedef struct { 54 | RedisModuleString *min, *max; /* May be set to shared.(minstring|maxstring) */ 55 | int minex, maxex; /* are min or max exclusive? */ 56 | } m_zlexrangespec; 57 | 58 | extern RedisModuleString *shared_minstring; 59 | extern RedisModuleString *shared_maxstring; 60 | 61 | m_zskiplist *m_zslCreate(unsigned char score_num); 62 | void m_zslFree(m_zskiplist *zsl); 63 | m_zskiplistNode *m_zslInsert(m_zskiplist *zsl, scoretype *score, RedisModuleString *ele); 64 | unsigned char *m_zzlInsert(unsigned char *zl, RedisModuleString *ele, scoretype *score); 65 | int m_zslDelete(m_zskiplist *zsl, scoretype *score, RedisModuleString *ele, m_zskiplistNode **node); 66 | unsigned long m_zslGetRank(m_zskiplist *zsl, scoretype *score, RedisModuleString *ele); 67 | unsigned long m_zslGetRankByScore(m_zskiplist *zsl, scoretype *score); 68 | m_zskiplistNode *m_zslUpdateScore(m_zskiplist *zsl, scoretype *curscore, RedisModuleString *ele, scoretype *newscore); 69 | m_zskiplistNode *m_zslGetElementByRank(m_zskiplist *zsl, unsigned long rank); 70 | int m_zslParseRange(RedisModuleString *min, RedisModuleString *max, m_zrangespec *spec); 71 | void m_zslFreeLexRange(m_zlexrangespec *spec); 72 | int m_zslParseLexRange(RedisModuleString *min, RedisModuleString *max, m_zlexrangespec *spec); 73 | int m_zslValueGteMin(scoretype *value, m_zrangespec *spec); 74 | int m_zslValueLteMax(scoretype *value, m_zrangespec *spec); 75 | int m_zslIsInRange(m_zskiplist *zsl, m_zrangespec *range); 76 | m_zskiplistNode *m_zslFirstInRange(m_zskiplist *zsl, m_zrangespec *range); 77 | m_zskiplistNode *m_zslLastInRange(m_zskiplist *zsl, m_zrangespec *range); 78 | int m_zslLexValueLteMax(RedisModuleString *value, m_zlexrangespec *spec); 79 | int m_zslLexValueGteMin(RedisModuleString *value, m_zlexrangespec *spec); 80 | m_zskiplistNode *m_zslLastInLexRange(m_zskiplist *zsl, m_zlexrangespec *range); 81 | m_zskiplistNode *m_zslFirstInLexRange(m_zskiplist *zsl, m_zlexrangespec *range); 82 | unsigned long m_zslDeleteRangeByScore(m_zskiplist *zsl, m_zrangespec *range, dict *dict); 83 | unsigned long m_zslDeleteRangeByRank(m_zskiplist *zsl, unsigned int start, unsigned int end, dict *dict); 84 | unsigned long m_zslDeleteRangeByLex(m_zskiplist *zsl, m_zlexrangespec *range, dict *dict); 85 | 86 | int mscoreGetNum(const char *s, size_t slen); 87 | int mscoreParse(const char *s, size_t slen, scoretype **score); 88 | int mscoreCmp(scoretype *s1, scoretype *s2); 89 | sds mscore2String(scoretype *score); 90 | int mscoreAdd(scoretype *s1, scoretype *s2); 91 | void mscoreAddIgnoreNan(scoretype *s1, scoretype *s2); 92 | scoretype *mnewScore(int score_num); 93 | void mscoreMulWithWeight(scoretype *dst, scoretype *base, double weight); 94 | void mscoreAssign(scoretype *target, scoretype *src); -------------------------------------------------------------------------------- /dep/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | /* Glob-style pattern matching. */ 20 | int m_stringmatchlen(const char *pattern, int patternLen, const char *string, int stringLen, int nocase) { 21 | while (patternLen && stringLen) { 22 | switch (pattern[0]) { 23 | case '*': 24 | while (pattern[1] == '*') { 25 | pattern++; 26 | patternLen--; 27 | } 28 | if (patternLen == 1) return 1; /* match */ 29 | while (stringLen) { 30 | if (m_stringmatchlen(pattern + 1, patternLen - 1, string, stringLen, nocase)) return 1; /* match */ 31 | string++; 32 | stringLen--; 33 | } 34 | return 0; /* no match */ 35 | break; 36 | case '?': 37 | if (stringLen == 0) return 0; /* no match */ 38 | string++; 39 | stringLen--; 40 | break; 41 | case '[': { 42 | int not, match; 43 | 44 | pattern++; 45 | patternLen--; 46 | not = pattern[0] == '^'; 47 | if (not ) { 48 | pattern++; 49 | patternLen--; 50 | } 51 | match = 0; 52 | while (1) { 53 | if (pattern[0] == '\\' && patternLen >= 2) { 54 | pattern++; 55 | patternLen--; 56 | if (pattern[0] == string[0]) match = 1; 57 | } else if (pattern[0] == ']') { 58 | break; 59 | } else if (patternLen == 0) { 60 | pattern--; 61 | patternLen++; 62 | break; 63 | } else if (pattern[1] == '-' && patternLen >= 3) { 64 | int start = pattern[0]; 65 | int end = pattern[2]; 66 | int c = string[0]; 67 | if (start > end) { 68 | int t = start; 69 | start = end; 70 | end = t; 71 | } 72 | if (nocase) { 73 | start = tolower(start); 74 | end = tolower(end); 75 | c = tolower(c); 76 | } 77 | pattern += 2; 78 | patternLen -= 2; 79 | if (c >= start && c <= end) match = 1; 80 | } else { 81 | if (!nocase) { 82 | if (pattern[0] == string[0]) match = 1; 83 | } else { 84 | if (tolower((int)pattern[0]) == tolower((int)string[0])) match = 1; 85 | } 86 | } 87 | pattern++; 88 | patternLen--; 89 | } 90 | if (not ) match = !match; 91 | if (!match) return 0; /* no match */ 92 | string++; 93 | stringLen--; 94 | break; 95 | } 96 | case '\\': 97 | if (patternLen >= 2) { 98 | pattern++; 99 | patternLen--; 100 | } 101 | /* fall through */ 102 | default: 103 | if (!nocase) { 104 | if (pattern[0] != string[0]) return 0; /* no match */ 105 | } else { 106 | if (tolower((int)pattern[0]) != tolower((int)string[0])) return 0; /* no match */ 107 | } 108 | string++; 109 | stringLen--; 110 | break; 111 | } 112 | pattern++; 113 | patternLen--; 114 | if (stringLen == 0) { 115 | while (*pattern == '*') { 116 | pattern++; 117 | patternLen--; 118 | } 119 | break; 120 | } 121 | } 122 | if (patternLen == 0 && stringLen == 0) return 1; 123 | return 0; 124 | } 125 | 126 | int m_stringmatch(const char *pattern, const char *string, int nocase) { 127 | return m_stringmatchlen(pattern, strlen(pattern), string, strlen(string), nocase); 128 | } 129 | 130 | /* Convert a string representing an amount of memory into the number of 131 | * bytes, so for instance memtoll("1Gb") will return 1073741824 that is 132 | * (1024*1024*1024). 133 | * 134 | * On parsing error, if *err is not NULL, it's set to 1, otherwise it's 135 | * set to 0. On error the function return value is 0, regardless of the 136 | * fact 'err' is NULL or not. */ 137 | long long m_memtoll(const char *p, int *err) { 138 | const char *u; 139 | char buf[128]; 140 | long mul; /* unit multiplier */ 141 | long long val; 142 | unsigned int digits; 143 | 144 | if (err) *err = 0; 145 | 146 | /* Search the first non digit character. */ 147 | u = p; 148 | if (*u == '-') u++; 149 | while (*u && isdigit(*u)) u++; 150 | if (*u == '\0' || !strcasecmp(u, "b")) { 151 | mul = 1; 152 | } else if (!strcasecmp(u, "k")) { 153 | mul = 1000; 154 | } else if (!strcasecmp(u, "kb")) { 155 | mul = 1024; 156 | } else if (!strcasecmp(u, "m")) { 157 | mul = 1000 * 1000; 158 | } else if (!strcasecmp(u, "mb")) { 159 | mul = 1024 * 1024; 160 | } else if (!strcasecmp(u, "g")) { 161 | mul = 1000L * 1000 * 1000; 162 | } else if (!strcasecmp(u, "gb")) { 163 | mul = 1024L * 1024 * 1024; 164 | } else { 165 | if (err) *err = 1; 166 | return 0; 167 | } 168 | 169 | /* Copy the digits into a buffer, we'll use strtoll() to convert 170 | * the digit (without the unit) into a number. */ 171 | digits = u - p; 172 | if (digits >= sizeof(buf)) { 173 | if (err) *err = 1; 174 | return 0; 175 | } 176 | memcpy(buf, p, digits); 177 | buf[digits] = '\0'; 178 | 179 | char *endptr; 180 | errno = 0; 181 | val = strtoll(buf, &endptr, 10); 182 | if ((val == 0 && errno == EINVAL) || *endptr != '\0') { 183 | if (err) *err = 1; 184 | return 0; 185 | } 186 | return val * mul; 187 | } 188 | 189 | /* Return the number of digits of 'v' when converted to string in radix 10. 190 | * See m_ll2string() for more information. */ 191 | uint32_t m_digits10(uint64_t v) { 192 | if (v < 10) return 1; 193 | if (v < 100) return 2; 194 | if (v < 1000) return 3; 195 | if (v < 1000000000000UL) { 196 | if (v < 100000000UL) { 197 | if (v < 1000000) { 198 | if (v < 10000) return 4; 199 | return 5 + (v >= 100000); 200 | } 201 | return 7 + (v >= 10000000UL); 202 | } 203 | if (v < 10000000000UL) { 204 | return 9 + (v >= 1000000000UL); 205 | } 206 | return 11 + (v >= 100000000000UL); 207 | } 208 | return 12 + m_digits10(v / 1000000000000UL); 209 | } 210 | 211 | /* Like m_digits10() but for signed values. */ 212 | uint32_t m_sdigits10(int64_t v) { 213 | if (v < 0) { 214 | /* Abs value of LLONG_MIN requires special handling. */ 215 | uint64_t uv = (v != LLONG_MIN) ? (uint64_t)-v : ((uint64_t)LLONG_MAX) + 1; 216 | return m_digits10(uv) + 1; /* +1 for the minus. */ 217 | } else { 218 | return m_digits10(v); 219 | } 220 | } 221 | 222 | /* Convert a long long into a string. Returns the number of 223 | * characters needed to represent the number. 224 | * If the buffer is not big enough to store the string, 0 is returned. 225 | * 226 | * Based on the following article (that apparently does not provide a 227 | * novel approach but only publicizes an already used technique): 228 | * 229 | * https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920 230 | * 231 | * Modified in order to handle signed integers since the original code was 232 | * designed for unsigned integers. */ 233 | int m_ll2string(char *dst, size_t dstlen, long long svalue) { 234 | static const char digits[201] 235 | = "0001020304050607080910111213141516171819" 236 | "2021222324252627282930313233343536373839" 237 | "4041424344454647484950515253545556575859" 238 | "6061626364656667686970717273747576777879" 239 | "8081828384858687888990919293949596979899"; 240 | int negative; 241 | unsigned long long value; 242 | 243 | /* The main loop works with 64bit unsigned integers for simplicity, so 244 | * we convert the number here and remember if it is negative. */ 245 | if (svalue < 0) { 246 | if (svalue != LLONG_MIN) { 247 | value = -svalue; 248 | } else { 249 | value = ((unsigned long long)LLONG_MAX) + 1; 250 | } 251 | negative = 1; 252 | } else { 253 | value = svalue; 254 | negative = 0; 255 | } 256 | 257 | /* Check length. */ 258 | uint32_t const length = m_digits10(value) + negative; 259 | if (length >= dstlen) return 0; 260 | 261 | /* Null term. */ 262 | uint32_t next = length; 263 | dst[next] = '\0'; 264 | next--; 265 | while (value >= 100) { 266 | int const i = (value % 100) * 2; 267 | value /= 100; 268 | dst[next] = digits[i + 1]; 269 | dst[next - 1] = digits[i]; 270 | next -= 2; 271 | } 272 | 273 | /* Handle last 1-2 digits. */ 274 | if (value < 10) { 275 | dst[next] = '0' + (uint32_t)value; 276 | } else { 277 | int i = (uint32_t)value * 2; 278 | dst[next] = digits[i + 1]; 279 | dst[next - 1] = digits[i]; 280 | } 281 | 282 | /* Add sign. */ 283 | if (negative) dst[0] = '-'; 284 | return length; 285 | } 286 | 287 | /* Convert a string into a long long. Returns 1 if the string could be parsed 288 | * into a (non-overflowing) long long, 0 otherwise. The value will be set to 289 | * the parsed value when appropriate. 290 | * 291 | * Note that this function demands that the string strictly represents 292 | * a long long: no spaces or other characters before or after the string 293 | * representing the number are accepted, nor zeroes at the start if not 294 | * for the string "0" representing the zero number. 295 | * 296 | * Because of its strictness, it is safe to use this function to check if 297 | * you can convert a string into a long long, and obtain back the string 298 | * from the number without any loss in the string representation. */ 299 | int m_string2ll(const char *s, size_t slen, long long *value) { 300 | const char *p = s; 301 | size_t plen = 0; 302 | int negative = 0; 303 | unsigned long long v; 304 | 305 | /* A zero length string is not a valid number. */ 306 | if (plen == slen) return 0; 307 | 308 | /* Special case: first and only digit is 0. */ 309 | if (slen == 1 && p[0] == '0') { 310 | if (value != NULL) *value = 0; 311 | return 1; 312 | } 313 | 314 | /* Handle negative numbers: just set a flag and continue like if it 315 | * was a positive number. Later convert into negative. */ 316 | if (p[0] == '-') { 317 | negative = 1; 318 | p++; 319 | plen++; 320 | 321 | /* Abort on only a negative sign. */ 322 | if (plen == slen) return 0; 323 | } 324 | 325 | /* First digit should be 1-9, otherwise the string should just be 0. */ 326 | if (p[0] >= '1' && p[0] <= '9') { 327 | v = p[0] - '0'; 328 | p++; 329 | plen++; 330 | } else { 331 | return 0; 332 | } 333 | 334 | /* Parse all the other digits, checking for overflow at every step. */ 335 | while (plen < slen && p[0] >= '0' && p[0] <= '9') { 336 | if (v > (ULLONG_MAX / 10)) /* Overflow. */ 337 | return 0; 338 | v *= 10; 339 | 340 | if (v > (ULLONG_MAX - (p[0] - '0'))) /* Overflow. */ 341 | return 0; 342 | v += p[0] - '0'; 343 | 344 | p++; 345 | plen++; 346 | } 347 | 348 | /* Return if not all bytes were used. */ 349 | if (plen < slen) return 0; 350 | 351 | /* Convert to negative if needed, and do the final overflow check when 352 | * converting from unsigned long long to long long. */ 353 | if (negative) { 354 | if (v > ((unsigned long long)(-(LLONG_MIN + 1)) + 1)) /* Overflow. */ 355 | return 0; 356 | if (value != NULL) *value = -v; 357 | } else { 358 | if (v > LLONG_MAX) /* Overflow. */ 359 | return 0; 360 | if (value != NULL) *value = v; 361 | } 362 | return 1; 363 | } 364 | 365 | /* Convert a string into a long. Returns 1 if the string could be parsed into a 366 | * (non-overflowing) long, 0 otherwise. The value will be set to the parsed 367 | * value when appropriate. */ 368 | int m_string2l(const char *s, size_t slen, long *lval) { 369 | long long llval; 370 | 371 | if (!m_string2ll(s, slen, &llval)) return 0; 372 | 373 | if (llval < LONG_MIN || llval > LONG_MAX) return 0; 374 | 375 | *lval = (long)llval; 376 | return 1; 377 | } 378 | 379 | /* Convert a string into a double. Returns 1 if the string could be parsed 380 | * into a (non-overflowing) double, 0 otherwise. The value will be set to 381 | * the parsed value when appropriate. 382 | * 383 | * Note that this function demands that the string strictly represents 384 | * a double: no spaces or other characters before or after the string 385 | * representing the number are accepted. */ 386 | int m_string2ld(const char *s, size_t slen, long double *dp) { 387 | char buf[MAX_LONG_DOUBLE_CHARS]; 388 | long double value; 389 | char *eptr; 390 | 391 | if (slen >= sizeof(buf)) return 0; 392 | memcpy(buf, s, slen); 393 | buf[slen] = '\0'; 394 | 395 | errno = 0; 396 | value = strtold(buf, &eptr); 397 | if (isspace(buf[0]) || eptr[0] != '\0' 398 | || (errno == ERANGE && (value == HUGE_VAL || value == -HUGE_VAL || value == 0)) || errno == EINVAL 399 | || isnan(value)) 400 | return 0; 401 | 402 | if (dp) *dp = value; 403 | return 1; 404 | } 405 | 406 | /* Convert a string into a double. Returns 1 if the string could be parsed 407 | * into a (non-overflowing) double, 0 otherwise. The value will be set to 408 | * the parsed value when appropriate. 409 | * 410 | * Note that this function demands that the string strictly represents 411 | * a double: no spaces or other characters before or after the string 412 | * representing the number are accepted. */ 413 | int m_string2d(const char *s, size_t slen, double *dp) { 414 | errno = 0; 415 | char *eptr; 416 | *dp = strtod(s, &eptr); 417 | if (slen == 0 || 418 | isspace(((const char*)s)[0]) || 419 | (size_t)(eptr-(char*)s) != slen || 420 | (errno == ERANGE && 421 | (*dp == HUGE_VAL || *dp == -HUGE_VAL || *dp == 0)) || 422 | isnan(*dp)) 423 | return 0; 424 | return 1; 425 | } 426 | 427 | /* Convert a double to a string representation. Returns the number of bytes 428 | * required. The representation should always be parsable by strtod(3). 429 | * This function does not support human-friendly formatting like m_ld2string 430 | * does. It is intended mainly to be used inside t_zset.c when writing scores 431 | * into a ziplist representing a sorted set. */ 432 | int m_d2string(char *buf, size_t len, double value) { 433 | if (isnan(value)) { 434 | len = snprintf(buf, len, "nan"); 435 | } else if (isinf(value)) { 436 | if (value < 0) 437 | len = snprintf(buf, len, "-inf"); 438 | else 439 | len = snprintf(buf, len, "inf"); 440 | } else if (value == 0) { 441 | /* See: http://en.wikipedia.org/wiki/Signed_zero, "Comparisons". */ 442 | if (1.0 / value < 0) 443 | len = snprintf(buf, len, "-0"); 444 | else 445 | len = snprintf(buf, len, "0"); 446 | } else { 447 | #if (DBL_MANT_DIG >= 52) && (LLONG_MAX == 0x7fffffffffffffffLL) 448 | /* Check if the float is in a safe range to be casted into a 449 | * long long. We are assuming that long long is 64 bit here. 450 | * Also we are assuming that there are no implementations around where 451 | * double has precision < 52 bit. 452 | * 453 | * Under this assumptions we test if a double is inside an interval 454 | * where casting to long long is safe. Then using two castings we 455 | * make sure the decimal part is zero. If all this is true we use 456 | * integer printing function that is much faster. */ 457 | double min = -4503599627370495; /* (2^52)-1 */ 458 | double max = 4503599627370496; /* -(2^52) */ 459 | if (value > min && value < max && value == ((double)((long long)value))) 460 | len = m_ll2string(buf, len, (long long)value); 461 | else 462 | #endif 463 | len = snprintf(buf, len, "%.17g", value); 464 | } 465 | 466 | return len; 467 | } 468 | 469 | /* Convert a long double into a string. If humanfriendly is non-zero 470 | * it does not use exponential format and trims trailing zeroes at the end, 471 | * however this results in loss of precision. Otherwise exp format is used 472 | * and the output of snprintf() is not modified. 473 | * 474 | * The function returns the length of the string or zero if there was not 475 | * enough buffer room to store it. */ 476 | int m_ld2string(char *buf, size_t len, long double value, int humanfriendly) { 477 | size_t l; 478 | 479 | if (isinf(value)) { 480 | /* Libc in odd systems (Hi Solaris!) will format infinite in a 481 | * different way, so better to handle it in an explicit way. */ 482 | if (len < 5) return 0; /* No room. 5 is "-inf\0" */ 483 | if (value > 0) { 484 | memcpy(buf, "inf", 3); 485 | l = 3; 486 | } else { 487 | memcpy(buf, "-inf", 4); 488 | l = 4; 489 | } 490 | } else if (humanfriendly) { 491 | /* We use 17 digits precision since with 128 bit floats that precision 492 | * after rounding is able to represent most small decimal numbers in a 493 | * way that is "non surprising" for the user (that is, most small 494 | * decimal numbers will be represented in a way that when converted 495 | * back into a string are exactly the same as what the user typed.) */ 496 | l = snprintf(buf, len, "%.17Lf", value); 497 | if (l + 1 > len) return 0; /* No room. */ 498 | /* Now remove trailing zeroes after the '.' */ 499 | if (strchr(buf, '.') != NULL) { 500 | char *p = buf + l - 1; 501 | while (*p == '0') { 502 | p--; 503 | l--; 504 | } 505 | if (*p == '.') l--; 506 | } 507 | } else { 508 | l = snprintf(buf, len, "%.17Lg", value); 509 | if (l + 1 > len) return 0; /* No room. */ 510 | } 511 | buf[l] = '\0'; 512 | return l; 513 | } 514 | 515 | 516 | -------------------------------------------------------------------------------- /dep/util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012, Salvatore Sanfilippo 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef __UTIL_H_ 31 | #define __UTIL_H_ 32 | 33 | #include 34 | #include 35 | 36 | /* The maximum number of characters needed to represent a long double 37 | * as a string (long double has a huge range). 38 | * This should be the size of the buffer given to ld2string */ 39 | #define MAX_LONG_DOUBLE_CHARS 5*1024 40 | #define LONG_STR_SIZE 21 /* Bytes needed for long -> str + '\0' */ 41 | 42 | #if __GNUC__ >= 4 43 | #define redis_unreachable __builtin_unreachable 44 | #else 45 | #define redis_unreachable abort 46 | #endif 47 | 48 | #define C_OK 0 49 | #define C_ERR -1 50 | 51 | int m_stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase); 52 | int m_stringmatch(const char *p, const char *s, int nocase); 53 | int m_stringmatchlen_fuzz_test(void); 54 | long long m_memtoll(const char *p, int *err); 55 | uint32_t m_digits10(uint64_t v); 56 | uint32_t m_sdigits10(int64_t v); 57 | int m_ll2string(char *s, size_t len, long long value); 58 | int m_string2ll(const char *s, size_t slen, long long *value); 59 | int m_string2l(const char *s, size_t slen, long *value); 60 | int m_string2ld(const char *s, size_t slen, long double *dp); 61 | int m_string2d(const char *s, size_t slen, double *dp); 62 | int m_d2string(char *buf, size_t len, double value); 63 | int m_ld2string(char *buf, size_t len, long double value, int humanfriendly); 64 | 65 | #endif -------------------------------------------------------------------------------- /imgs/tairzset_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tair-opensource/TairZset/4dc3a6559da1636c37df04f51fbef52b2ebca2f5/imgs/tairzset_logo.jpg -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET tairzset_module) 2 | 3 | set(SRCS 4 | tairzset.h 5 | tairzset.c 6 | redismodule.h ) 7 | 8 | add_library(${TARGET} SHARED ${SRCS} ${USRC}) 9 | set_target_properties(${TARGET} PROPERTIES SUFFIX ".so") 10 | set_target_properties(${TARGET} PROPERTIES PREFIX "") -------------------------------------------------------------------------------- /src/tairzset.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Alibaba Tair Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "redismodule.h" 20 | #include "skiplist.h" 21 | #include "dict.h" 22 | #include "util.h" 23 | 24 | #include 25 | typedef struct TairZsetObj { 26 | dict *dict; 27 | m_zskiplist *zsl; 28 | } TairZsetObj; 29 | 30 | uint64_t dictModuleStrHash(const void *key) { 31 | size_t len; 32 | const char *buf = RedisModule_StringPtrLen(key, &len); 33 | return m_dictGenHashFunction(buf, (int)len); 34 | } 35 | 36 | int dictModuleStrKeyCompare(void *privdata, const void *key1, 37 | const void *key2) { 38 | size_t l1, l2; 39 | DICT_NOTUSED(privdata); 40 | 41 | const char *buf1 = RedisModule_StringPtrLen(key1, &l1); 42 | const char *buf2 = RedisModule_StringPtrLen(key2, &l2); 43 | if (l1 != l2) return 0; 44 | return memcmp(buf1, buf2, l1) == 0; 45 | } 46 | 47 | m_dictType tairZsetDictType = { 48 | dictModuleStrHash, /* hash function */ 49 | NULL, /* key dup */ 50 | NULL, /* val dup */ 51 | dictModuleStrKeyCompare, /* key compare */ 52 | NULL, /* Note: RedisModleString shared & freed by skiplist */ 53 | NULL /* val destructor */ 54 | }; 55 | 56 | int parse_score(const char * buf, size_t len, scoretype *score); 57 | int score_cmp(double *s1, double *s2, unsigned char num); -------------------------------------------------------------------------------- /tests/tairzset-6.0.0.tcl: -------------------------------------------------------------------------------- 1 | set testmodule [file normalize your_path/tairzset_module.so] 2 | 3 | proc redis_deferring_client {args} { 4 | set level 0 5 | if {[llength $args] > 0 && [string is integer [lindex $args 0]]} { 6 | set level [lindex $args 0] 7 | set args [lrange $args 1 end] 8 | } 9 | 10 | # create client that defers reading reply 11 | set client [redis [srv $level "host"] [srv $level "port"] 1 $::tls] 12 | 13 | # select the right db and read the response (OK) 14 | $client select 9 15 | $client read 16 | return $client 17 | } 18 | 19 | start_server {tags {"tairzset"} overrides {bind 0.0.0.0}} { 20 | r module load $testmodule 21 | 22 | proc create_tairzset {key items} { 23 | r del $key 24 | foreach {score entry} $items { 25 | r exzadd $key $score $entry 26 | } 27 | } 28 | 29 | test "EXBZPOP with a single existing sorted set" { 30 | set rd [redis_deferring_client] 31 | create_tairzset zset {0 a 1 b 2 c} 32 | 33 | $rd exbzpopmin zset 5 34 | assert_equal {zset a 0} [$rd read] 35 | $rd exbzpopmin zset 5 36 | assert_equal {zset b 1} [$rd read] 37 | $rd exbzpopmax zset 5 38 | assert_equal {zset c 2} [$rd read] 39 | assert_equal 0 [r exists zset] 40 | } 41 | 42 | test "EXBZPOP with multiple existing sorted sets" { 43 | set rd [redis_deferring_client] 44 | create_tairzset z1 {0 a 1 b 2 c} 45 | create_tairzset z2 {3 d 4 e 5 f} 46 | 47 | $rd exbzpopmin z1 z2 5 48 | assert_equal {z1 a 0} [$rd read] 49 | $rd exbzpopmax z1 z2 5 50 | assert_equal {z1 c 2} [$rd read] 51 | assert_equal 1 [r exzcard z1] 52 | assert_equal 3 [r exzcard z2] 53 | 54 | $rd exbzpopmax z2 z1 5 55 | assert_equal {z2 f 5} [$rd read] 56 | $rd exbzpopmin z2 z1 5 57 | assert_equal {z2 d 3} [$rd read] 58 | assert_equal 1 [r exzcard z1] 59 | assert_equal 1 [r exzcard z2] 60 | } 61 | 62 | test "EXBZPOP second sorted set has members" { 63 | set rd [redis_deferring_client] 64 | r del z1 65 | create_tairzset z2 {3 d 4 e 5 f} 66 | $rd exbzpopmax z1 z2 5 67 | assert_equal {z2 f 5} [$rd read] 68 | $rd exbzpopmin z2 z1 5 69 | assert_equal {z2 d 3} [$rd read] 70 | assert_equal 0 [r exzcard z1] 71 | assert_equal 1 [r exzcard z2] 72 | } 73 | 74 | test "EXBZPOPMIN, ZADD + DEL should not awake blocked client" { 75 | set rd [redis_deferring_client] 76 | r del zset 77 | 78 | $rd exbzpopmin zset 0 79 | r multi 80 | r exzadd zset 0 foo 81 | r del zset 82 | r exec 83 | r del zset 84 | r exzadd zset 1 bar 85 | $rd read 86 | } {zset bar 1} 87 | 88 | test "EXBZPOPMIN, ZADD + DEL + SET should not awake blocked client" { 89 | set rd [redis_deferring_client] 90 | r del zset 91 | 92 | $rd exbzpopmin zset 0 93 | r multi 94 | r exzadd zset 0 foo 95 | r del zset 96 | r set zset foo 97 | r exec 98 | r del zset 99 | r exzadd zset 1 bar 100 | $rd read 101 | } {zset bar 1} 102 | 103 | test "EXBZPOPMIN with same key multiple times should work" { 104 | set rd [redis_deferring_client] 105 | r del z1 z2 106 | 107 | # Data arriving after the BZPOPMIN. 108 | $rd exbzpopmin z1 z2 z2 z1 0 109 | r exzadd z1 0 a 110 | assert_equal [$rd read] {z1 a 0} 111 | $rd exbzpopmin z1 z2 z2 z1 0 112 | r exzadd z2 1 b 113 | assert_equal [$rd read] {z2 b 1} 114 | 115 | # Data already there. 116 | r exzadd z1 0 a 117 | r exzadd z2 1 b 118 | $rd exbzpopmin z1 z2 z2 z1 0 119 | assert_equal [$rd read] {z1 a 0} 120 | $rd exbzpopmin z1 z2 z2 z1 0 121 | assert_equal [$rd read] {z2 b 1} 122 | } 123 | 124 | test "MULTI/EXEC is isolated from the point of view of EXBZPOPMIN" { 125 | set rd [redis_deferring_client] 126 | r del zset 127 | $rd exbzpopmin zset 0 128 | r multi 129 | r exzadd zset 0 a 130 | r exzadd zset 1 b 131 | r exzadd zset 2 c 132 | r exec 133 | $rd read 134 | } {zset a 0} 135 | 136 | test "EXBZPOPMIN with variadic EXZADD" { 137 | set rd [redis_deferring_client] 138 | r del zset 139 | if {$::valgrind} {after 100} 140 | $rd exbzpopmin zset 0 141 | if {$::valgrind} {after 100} 142 | assert_equal 2 [r exzadd zset -1 foo 1 bar] 143 | if {$::valgrind} {after 100} 144 | assert_equal {zset foo -1} [$rd read] 145 | assert_equal {bar} [r exzrange zset 0 -1] 146 | } 147 | 148 | test "EXBZPOPMIN with zero timeout should block indefinitely" { 149 | set rd [redis_deferring_client] 150 | r del zset 151 | $rd exbzpopmin zset 0 152 | after 1000 153 | r exzadd zset 0 foo 154 | assert_equal {zset foo 0} [$rd read] 155 | } 156 | } --------------------------------------------------------------------------------