├── .gitignore ├── LICENSE ├── README.md ├── redismodule.h ├── rmutil ├── Makefile ├── alloc.c ├── alloc.h ├── heap.c ├── heap.h ├── logging.h ├── periodic.c ├── periodic.h ├── priority_queue.c ├── priority_queue.h ├── sds.c ├── sds.h ├── sdsalloc.h ├── strings.c ├── strings.h ├── test.h ├── test_heap.c ├── test_periodic.c ├── test_priority_queue.c ├── test_util.h ├── test_vector.c ├── util.c ├── util.h ├── vector.c └── vector.h └── src ├── Makefile └── dbx.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | .vscode 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Thiania 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis Module for maintaining hash by simple SQL (Support csv import/export) 2 | 3 | This module aims to provide simple DML to manipulate the hashes in REDIS for SQL users. It works as simple as you expected. It translates the input statement to a set of pure REDIS commands. It does not need nor generate any intermediate stuffs which occupied your storages. The target data is your hashes only. It also provides the CSV import and export function. 4 | 5 | ## Usage 6 | ```sql 7 | $ redis-cli 8 | 127.0.0.1:6379> hmset phonebook:0001 name "Peter Nelson" tel "1-456-1246-3421" birth "2019-10-01" pos 3 gender "M" 9 | 127.0.0.1:6379> hmset phonebook:0002 name "Betty Joan" tel "1-444-9999-1112" birth "2019-12-01" pos 1 gender "F" 10 | 127.0.0.1:6379> hmset phonebook:0003 name "Bloody Mary" tel "1-666-1234-9812" birth "2018-01-31" pos 2 gender "F" 11 | 127.0.0.1:6379> hmset phonebook:0004 name "Mattias Swensson" tel "1-888-3333-1412" birth "2017-06-30" pos 4 gender "M" 12 | 127.0.0.1:6379> dbx select name,tel from phonebook where gender = "F" order by pos desc 13 | 1) 1) name 14 | 2) "Bloody Mary" 15 | 3) tel 16 | 4) "1-666-1234-9812" 17 | 2) 1) name 18 | 2) "Betty Joan" 19 | 3) tel 20 | 4) "1-444-9999-1112" 21 | ``` 22 | 23 | ## Getting started 24 | 25 | ### Get the package and build the binary: 26 | ```sql 27 | $ git clone https://github.com/cscan/dbx.git 28 | $ cd dbx/src && make 29 | ``` 30 | 31 | This plugin library is written in pure C. A file dbx.so is built after successfully compiled. 32 | 33 | ### Load the module in redis (3 ways) 34 | 35 | 1. Load the module in CLI 36 | ```sql 37 | 127.0.0.1:6379> module load /path/to/dbx.so 38 | ``` 39 | 40 | 2. Start the server with loadmodule argument 41 | ```sql 42 | $ redis-server --loadmodule /path/to/dbx.so 43 | ``` 44 | 45 | 3. Adding the following line in the file redis.conf and then restart the server 46 | ```sql 47 | loadmodule /path/to/dbx.so 48 | ``` 49 | 50 | If you still have problem in loading the module, please visit: https://redis.io/topics/modules-intro 51 | 52 | ## More Examples 53 | 54 | ### Select statement 55 | You may specify multiple fields separated by comma 56 | ```sql 57 | 127.0.0.1:6379> dbx select name, gender, birth from phonebook 58 | 1) 1) name 59 | 2) "Betty Joan" 60 | 3) gender 61 | 4) "F" 62 | 5) birth 63 | 6) "2019-12-01" 64 | 2) 1) name 65 | 2) "Mattias Swensson" 66 | 3) gender 67 | 4) "M" 68 | 5) birth 69 | 6) "2017-06-30" 70 | 3) 1) name 71 | 2) "Peter Nelson" 72 | 3) gender 73 | 4) "M" 74 | 5) birth 75 | 6) "2019-10-01" 76 | 4) 1) name 77 | 2) "Bloody Mary" 78 | 3) gender 79 | 4) "F" 80 | 5) birth 81 | 6) "2018-01-31" 82 | ``` 83 | 84 | "*" is support 85 | ```sql 86 | 127.0.0.1:6379> dbx select * from phonebook where birth > '2019-11-11' 87 | 1) 1) "name" 88 | 2) "Betty Joan" 89 | 3) "tel" 90 | 4) "1-444-9999-1112" 91 | 5) "birth" 92 | 6) "2019-12-01" 93 | 7) "pos" 94 | 8) "1" 95 | 9) "gender" 96 | 10) "F" 97 | ``` 98 | 99 | If you want to show the exact keys, you may try rowid() 100 | ```sql 101 | 127.0.0.1:6379> dbx select rowid() from phonebook 102 | 1) 1) rowid() 103 | 2) "phonebook:1588299191-764848276" 104 | 2) 1) rowid() 105 | 2) "phonebook:1588299202-1052597574" 106 | 3) 1) rowid() 107 | 2) "phonebook:1588298418-551514504" 108 | 4) 1) rowid() 109 | 2) "phonebook:1588299196-2115347437" 110 | ``` 111 | 112 | The above is nearly like REDIS keys command 113 | ```sql 114 | 127.0.0.1:6379> keys phonebook* 115 | 1) "phonebook:1588298418-551514504" 116 | 2) "phonebook:1588299196-2115347437" 117 | 3) "phonebook:1588299202-1052597574" 118 | 4) "phonebook:1588299191-764848276" 119 | ``` 120 | 121 | Each record is exactly a hash, you could use raw REDIS commands ``hget, hmget or hgetall`` to retrieve the same content 122 | 123 | #### Where clause 124 | Your could specify =, >, <, >=, <=, <>, != or like conditions in where clause. Now the module only support "and" to join multiple conditions. 125 | ```sql 126 | 127.0.0.1:6379> dbx select tel from phonebook where name like Son 127 | 1) 1) tel 128 | 2) "1-888-3333-1412" 129 | 2) 1) tel 130 | 2) "1-456-1246-3421" 131 | 127.0.0.1:6379> dbx select tel from phonebook where name like Son and pos = 4 132 | 1) 1) tel 133 | 2) "1-888-3333-1412" 134 | ``` 135 | 136 | #### Order clause 137 | Ordering can be ascending or descending. All sortings are alpha-sort. 138 | ```sql 139 | 127.0.0.1:6379> dbx select name, pos from phonebook order by pos asc 140 | 1) 1) name 141 | 2) "Betty Joan" 142 | 3) pos 143 | 4) "1" 144 | 2) 1) name 145 | 2) "Bloody Mary" 146 | 3) pos 147 | 4) "2" 148 | 3) 1) name 149 | 2) "Peter Nelson" 150 | 3) pos 151 | 4) "3" 152 | 4) 1) name 153 | 2) "Mattias Swensson" 154 | 3) pos 155 | 4) "4" 156 | 127.0.0.1:6379> dbx select name from phonebook order by pos desc 157 | 1) 1) name 158 | 2) "Mattias Swensson" 159 | 2) 1) name 160 | 2) "Peter Nelson" 161 | 3) 1) name 162 | 2) "Bloody Mary" 163 | 4) 1) name 164 | 2) "Betty Joan" 165 | ``` 166 | 167 | #### Top clause 168 | ```sql 169 | 127.0.0.1:6379> dbx select top 3 name, tel from phonebook order by pos desc 170 | 1) 1) name 171 | 2) "Mattias Swensson" 172 | 3) tel 173 | 4) "1-888-3333-1412" 174 | 2) 1) name 175 | 2) "Peter Nelson" 176 | 3) tel 177 | 4) "1-456-1246-3421" 178 | 3) 1) name 179 | 2) "Bloody Mary" 180 | 3) tel 181 | 4) "1-666-1234-9812" 182 | 127.0.0.1:6379> dbx select top 0 * from phonebook 183 | (empty list or set) 184 | ``` 185 | 186 | #### Into clause for copy hash table 187 | You could create another hash table by into clause. 188 | ```sql 189 | 127.0.0.1:6379> dbx select * into testbook from phonebook 190 | 1) testbook:1588325407-1751904058 191 | 2) testbook:1588325407-1751904059 192 | 3) testbook:1588325407-1751904060 193 | 4) testbook:1588325407-1751904061 194 | 127.0.0.1:6379> keys testbook* 195 | 1) "testbook:1588325407-1751904061" 196 | 2) "testbook:1588325407-1751904059" 197 | 3) "testbook:1588325407-1751904058" 198 | 4) "testbook:1588325407-1751904060" 199 | 127.0.0.1:6379> dbx select * from testbook 200 | 1) 1) "name" 201 | 2) "Mattias Swensson" 202 | 3) "tel" 203 | 4) "1-888-3333-1412" 204 | 5) "birth" 205 | 6) "2017-06-30" 206 | 7) "pos" 207 | 8) "4" 208 | 9) "gender" 209 | 10) "M" 210 | 2) 1) "name" 211 | 2) "Peter Nelson" 212 | 3) "tel" 213 | 4) "1-456-1246-3421" 214 | 5) "birth" 215 | 6) "2019-10-01" 216 | 7) "pos" 217 | 8) "3" 218 | 9) "gender" 219 | 10) "M" 220 | 3) 1) "name" 221 | 2) "Bloody Mary" 222 | 3) "tel" 223 | 4) "1-666-1234-9812" 224 | 5) "birth" 225 | 6) "2018-01-31" 226 | 7) "pos" 227 | 8) "2" 228 | 9) "gender" 229 | 10) "F" 230 | 4) 1) "name" 231 | 2) "Betty Joan" 232 | 3) "tel" 233 | 4) "1-444-9999-1112" 234 | 5) "birth" 235 | 6) "2019-12-01" 236 | 7) "pos" 237 | 8) "1" 238 | 9) "gender" 239 | 10) "F" 240 | ``` 241 | 242 | #### Into csv clause for exporting records in csv format 243 | ```sql 244 | 127.0.0.1:6379> dbx select * into csv "/tmp/testbook.csv" from phonebook where pos > 2 245 | 1) Kevin Louis,111-2123-1233,2009-12-31,6,F 246 | 2) Kenneth Cheng,123-12134-123,2000-12-31,5,M 247 | 127.0.0.1:6379> quit 248 | $ cat /tmp/testbook.csv 249 | Kevin Louis,111-2123-1233,2009-12-31,6,F 250 | Kenneth Cheng,123-12134-123,2000-12-31,5,M 251 | $ 252 | ``` 253 | 254 | ### Delete statement 255 | You may also use Insert and Delete statement to operate the hash. If you does not provide the where clause, it will delete all the records of the specified key prefix. (i.e. phonebook) 256 | ```sql 257 | 127.0.0.1:6379> dbx delete from phonebook where gender = F 258 | (integer) 2 259 | 127.0.0.1:6379> dbx delete from phonebook 260 | (integer) 2 261 | ``` 262 | 263 | ### Insert statement 264 | The module provides simple Insert statement which same as the function of the REDIS command hmset. It will append a random string to your provided key (i.e. phonebook). If operation is successful, it will return the key name. 265 | ```sql 266 | 127.0.0.1:6379> dbx insert into phonebook (name,tel,birth,pos,gender) values ('Peter Nelson' ,1-456-1246-3421, 2019-10-01, 3, M) 267 | "phonebook:1588298418-551514504" 268 | 127.0.0.1:6379> dbx insert into phonebook (name,tel,birth,pos,gender) values ('Betty Joan' ,1-444-9999-1112, 2019-12-01, 1, F) 269 | "phonebook:1588299191-764848276" 270 | 127.0.0.1:6379> dbx insert into phonebook (name,tel,birth,pos,gender) values ('Bloody Mary' ,1-666-1234-9812, 2018-01-31, 2, F) 271 | "phonebook:1588299196-2115347437" 272 | 127.0.0.1:6379> dbx insert into phonebook (name,tel,birth,pos,gender) values ('Mattias Swensson' ,1-888-3333-1412, 2017-06-30, 4, M) 273 | "phonebook:1588299202-1052597574" 274 | 127.0.0.1:6379> hgetall phonebook:1588298418-551514504 275 | 1) "name" 276 | 2) "Peter Nelson" 277 | 3) "tel" 278 | 4) "1-456-1246-3421" 279 | 5) "birth" 280 | 6) "2019-10-01" 281 | 7) "pos" 282 | 8) "3" 283 | 9) "gender" 284 | 10) "M" 285 | 127.0.0.1:6379> 286 | ``` 287 | Note that Redis requires at least one space after the single and double quoted arguments, otherwise you will get ``Invalid argument(s)`` error. If you don't want to take care of this, you could quote the whole SQL statement by double quote as below: 288 | ```sql 289 | 127.0.0.1:6379> dbx "insert into phonebook (name,tel,birth,pos,gender) values ('Peter Nelson','1-456-1246-3421','2019-10-01',3, 'M')" 290 | ``` 291 | 292 | #### From clause for importing CSV file 293 | The module provides simple import function by specifying from clause in Insert statement. It only support comma deliminated. Please make sure that the specified import file can be accessed by Redis server. 294 | ```bash 295 | $ cat > /tmp/test.csv << EOF 296 | "Kenneth Cheng","123-12134-123","2000-12-31","5","M" 297 | "Kevin Louis","111-2123-1233","2009-12-31","6","F" 298 | EOF 299 | $ redis-cli 300 | 127.0.0.1:6379> dbx insert into phonebook (name, tel, birth, pos, gender) from "/tmp/test.csv" 301 | 1) "phonebook:1588509697-1579004777" 302 | 2) "phonebook:1588509697-1579004778" 303 | 127.0.0.1:6379> dbx select name from phonebook 304 | 1) 1) name 305 | 2) "Kenneth Cheng" 306 | 2) 1) name 307 | 2) "Kevin Louis" 308 | 127.0.0.1:6379> dbx delete from phonebook 309 | (integer) 2 310 | 127.0.0.1:6379> quit 311 | $ cat > /tmp/testheader.csv << EOF 312 | name,tel,birth,pos,gender 313 | "Kenneth Cheng","123-12134-123","2000-12-31","5","M" 314 | "Kevin Louis","111-2123-1233","2009-12-31","6","F" 315 | EOF 316 | $ redis-cli 317 | 127.0.0.1:6379> dbx insert into phonebook from "/tmp/testheader.csv" 318 | 1) "phonebook:1588509697-1579004779" 319 | 2) "phonebook:1588509697-1579004780" 320 | 127.0.0.1:6379> dbx select name from phonebook 321 | 1) 1) name 322 | 2) "Kenneth Cheng" 323 | 2) 1) name 324 | 2) "Kevin Louis" 325 | ``` 326 | 327 | ### Issue command from BASH shell 328 | ```sql 329 | $ redis-cli dbx select "*" from phonebook where gender = M order by pos desc 330 | 1) 1) "name" 331 | 2) "Mattias Swensson" 332 | 3) "tel" 333 | 4) "1-888-3333-1412" 334 | 5) "birth" 335 | 6) "2017-06-30" 336 | 7) "pos" 337 | 8) "4" 338 | 9) "gender" 339 | 10) "M" 340 | 2) 1) "name" 341 | 2) "Peter Nelson" 342 | 3) "tel" 343 | 4) "1-456-1246-3421" 344 | 5) "birth" 345 | 6) "2019-10-01" 346 | 7) "pos" 347 | 8) "3" 348 | 9) "gender" 349 | 10) "M" 350 | $ redis-cli dbx select name from phonebook where tel like 9812 351 | 1) 1) name 352 | 2) "Bloody Mary" 353 | ``` 354 | Note that "*" requires double quoted otherwise it will pass all the filename in current directory. Of course you could quote the whole SQL statement. 355 | ```sql 356 | $ redis-cli dbx "select * from phonebook where gender = M order by pos desc" 357 | ``` 358 | 359 | ## Compatibility 360 | REDIS v4.0 361 | 362 | ## License 363 | MIT 364 | 365 | ## Status 366 | This project is in an early stage of development. Any contribution is welcome :D 367 | -------------------------------------------------------------------------------- /redismodule.h: -------------------------------------------------------------------------------- 1 | #ifndef REDISMODULE_H 2 | #define REDISMODULE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* ---------------- Defines common between core and modules --------------- */ 9 | 10 | /* Error status return values. */ 11 | #define REDISMODULE_OK 0 12 | #define REDISMODULE_ERR 1 13 | 14 | /* API versions. */ 15 | #define REDISMODULE_APIVER_1 1 16 | 17 | /* API flags and constants */ 18 | #define REDISMODULE_READ (1<<0) 19 | #define REDISMODULE_WRITE (1<<1) 20 | 21 | #define REDISMODULE_LIST_HEAD 0 22 | #define REDISMODULE_LIST_TAIL 1 23 | 24 | /* Key types. */ 25 | #define REDISMODULE_KEYTYPE_EMPTY 0 26 | #define REDISMODULE_KEYTYPE_STRING 1 27 | #define REDISMODULE_KEYTYPE_LIST 2 28 | #define REDISMODULE_KEYTYPE_HASH 3 29 | #define REDISMODULE_KEYTYPE_SET 4 30 | #define REDISMODULE_KEYTYPE_ZSET 5 31 | #define REDISMODULE_KEYTYPE_MODULE 6 32 | 33 | /* Reply types. */ 34 | #define REDISMODULE_REPLY_UNKNOWN -1 35 | #define REDISMODULE_REPLY_STRING 0 36 | #define REDISMODULE_REPLY_ERROR 1 37 | #define REDISMODULE_REPLY_INTEGER 2 38 | #define REDISMODULE_REPLY_ARRAY 3 39 | #define REDISMODULE_REPLY_NULL 4 40 | 41 | /* Postponed array length. */ 42 | #define REDISMODULE_POSTPONED_ARRAY_LEN -1 43 | 44 | /* Expire */ 45 | #define REDISMODULE_NO_EXPIRE -1 46 | 47 | /* Sorted set API flags. */ 48 | #define REDISMODULE_ZADD_XX (1<<0) 49 | #define REDISMODULE_ZADD_NX (1<<1) 50 | #define REDISMODULE_ZADD_ADDED (1<<2) 51 | #define REDISMODULE_ZADD_UPDATED (1<<3) 52 | #define REDISMODULE_ZADD_NOP (1<<4) 53 | 54 | /* Hash API flags. */ 55 | #define REDISMODULE_HASH_NONE 0 56 | #define REDISMODULE_HASH_NX (1<<0) 57 | #define REDISMODULE_HASH_XX (1<<1) 58 | #define REDISMODULE_HASH_CFIELDS (1<<2) 59 | #define REDISMODULE_HASH_EXISTS (1<<3) 60 | 61 | /* Context Flags: Info about the current context returned by 62 | * RM_GetContextFlags(). */ 63 | 64 | /* The command is running in the context of a Lua script */ 65 | #define REDISMODULE_CTX_FLAGS_LUA (1<<0) 66 | /* The command is running inside a Redis transaction */ 67 | #define REDISMODULE_CTX_FLAGS_MULTI (1<<1) 68 | /* The instance is a master */ 69 | #define REDISMODULE_CTX_FLAGS_MASTER (1<<2) 70 | /* The instance is a slave */ 71 | #define REDISMODULE_CTX_FLAGS_SLAVE (1<<3) 72 | /* The instance is read-only (usually meaning it's a slave as well) */ 73 | #define REDISMODULE_CTX_FLAGS_READONLY (1<<4) 74 | /* The instance is running in cluster mode */ 75 | #define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5) 76 | /* The instance has AOF enabled */ 77 | #define REDISMODULE_CTX_FLAGS_AOF (1<<6) 78 | /* The instance has RDB enabled */ 79 | #define REDISMODULE_CTX_FLAGS_RDB (1<<7) 80 | /* The instance has Maxmemory set */ 81 | #define REDISMODULE_CTX_FLAGS_MAXMEMORY (1<<8) 82 | /* Maxmemory is set and has an eviction policy that may delete keys */ 83 | #define REDISMODULE_CTX_FLAGS_EVICT (1<<9) 84 | /* Redis is out of memory according to the maxmemory flag. */ 85 | #define REDISMODULE_CTX_FLAGS_OOM (1<<10) 86 | /* Less than 25% of memory available according to maxmemory. */ 87 | #define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11) 88 | 89 | #define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */ 90 | #define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */ 91 | #define REDISMODULE_NOTIFY_LIST (1<<4) /* l */ 92 | #define REDISMODULE_NOTIFY_SET (1<<5) /* s */ 93 | #define REDISMODULE_NOTIFY_HASH (1<<6) /* h */ 94 | #define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */ 95 | #define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */ 96 | #define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */ 97 | #define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */ 98 | #define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */ 99 | 100 | 101 | /* A special pointer that we can use between the core and the module to signal 102 | * field deletion, and that is impossible to be a valid pointer. */ 103 | #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) 104 | 105 | /* Error messages. */ 106 | #define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value" 107 | 108 | #define REDISMODULE_POSITIVE_INFINITE (1.0/0.0) 109 | #define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0) 110 | 111 | /* Cluster API defines. */ 112 | #define REDISMODULE_NODE_ID_LEN 40 113 | #define REDISMODULE_NODE_MYSELF (1<<0) 114 | #define REDISMODULE_NODE_MASTER (1<<1) 115 | #define REDISMODULE_NODE_SLAVE (1<<2) 116 | #define REDISMODULE_NODE_PFAIL (1<<3) 117 | #define REDISMODULE_NODE_FAIL (1<<4) 118 | #define REDISMODULE_NODE_NOFAILOVER (1<<5) 119 | 120 | #define REDISMODULE_CLUSTER_FLAG_NONE 0 121 | #define REDISMODULE_CLUSTER_FLAG_NO_FAILOVER (1<<1) 122 | #define REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION (1<<2) 123 | 124 | #define REDISMODULE_NOT_USED(V) ((void) V) 125 | 126 | /* This type represents a timer handle, and is returned when a timer is 127 | * registered and used in order to invalidate a timer. It's just a 64 bit 128 | * number, because this is how each timer is represented inside the radix tree 129 | * of timers that are going to expire, sorted by expire time. */ 130 | typedef uint64_t RedisModuleTimerID; 131 | 132 | /* ------------------------- End of common defines ------------------------ */ 133 | 134 | #ifndef REDISMODULE_CORE 135 | 136 | typedef long long mstime_t; 137 | 138 | /* Incomplete structures for compiler checks but opaque access. */ 139 | typedef struct RedisModuleCtx RedisModuleCtx; 140 | typedef struct RedisModuleKey RedisModuleKey; 141 | typedef struct RedisModuleString RedisModuleString; 142 | typedef struct RedisModuleCallReply RedisModuleCallReply; 143 | typedef struct RedisModuleIO RedisModuleIO; 144 | typedef struct RedisModuleType RedisModuleType; 145 | typedef struct RedisModuleDigest RedisModuleDigest; 146 | typedef struct RedisModuleBlockedClient RedisModuleBlockedClient; 147 | typedef struct RedisModuleClusterInfo RedisModuleClusterInfo; 148 | typedef struct RedisModuleDict RedisModuleDict; 149 | typedef struct RedisModuleDictIter RedisModuleDictIter; 150 | 151 | typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); 152 | typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc); 153 | typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); 154 | typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); 155 | typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); 156 | typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); 157 | typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value); 158 | typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); 159 | typedef void (*RedisModuleTypeFreeFunc)(void *value); 160 | typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len); 161 | typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data); 162 | 163 | #define REDISMODULE_TYPE_METHOD_VERSION 1 164 | typedef struct RedisModuleTypeMethods { 165 | uint64_t version; 166 | RedisModuleTypeLoadFunc rdb_load; 167 | RedisModuleTypeSaveFunc rdb_save; 168 | RedisModuleTypeRewriteFunc aof_rewrite; 169 | RedisModuleTypeMemUsageFunc mem_usage; 170 | RedisModuleTypeDigestFunc digest; 171 | RedisModuleTypeFreeFunc free; 172 | } RedisModuleTypeMethods; 173 | 174 | #define REDISMODULE_GET_API(name) \ 175 | RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name)) 176 | 177 | #define REDISMODULE_API_FUNC(x) (*x) 178 | 179 | 180 | void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes); 181 | void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes); 182 | void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr); 183 | void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size); 184 | char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str); 185 | int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *); 186 | int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); 187 | void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver); 188 | int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name); 189 | int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx); 190 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll); 191 | int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx); 192 | int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid); 193 | void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode); 194 | void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp); 195 | int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp); 196 | size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp); 197 | int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele); 198 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where); 199 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 200 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len); 201 | void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply); 202 | int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply); 203 | long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply); 204 | size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply); 205 | RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx); 206 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len); 207 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll); 208 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str); 209 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...); 210 | void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str); 211 | const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len); 212 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err); 213 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg); 214 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len); 215 | void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len); 216 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len); 217 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str); 218 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx); 219 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d); 220 | int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply); 221 | int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll); 222 | int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d); 223 | void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx); 224 | int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...); 225 | int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx); 226 | const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len); 227 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply); 228 | int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key); 229 | int REDISMODULE_API_FUNC(RedisModule_UnlinkKey)(RedisModuleKey *key); 230 | int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str); 231 | char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode); 232 | int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen); 233 | mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key); 234 | int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire); 235 | int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr); 236 | int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore); 237 | int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score); 238 | int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted); 239 | void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key); 240 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 241 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex); 242 | int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 243 | int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max); 244 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score); 245 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key); 246 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); 247 | int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); 248 | int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); 249 | int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...); 250 | int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx); 251 | void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos); 252 | unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx); 253 | int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx); 254 | void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes); 255 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods); 256 | int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value); 257 | RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key); 258 | void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); 259 | void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); 260 | uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); 261 | void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); 262 | int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io); 263 | void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...); 264 | void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s); 265 | void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len); 266 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io); 267 | char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr); 268 | void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value); 269 | double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io); 270 | void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value); 271 | float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); 272 | void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); 273 | void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); 274 | int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len); 275 | void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); 276 | int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); 277 | RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); 278 | long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void); 279 | void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len); 280 | void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele); 281 | void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md); 282 | RedisModuleDict *REDISMODULE_API_FUNC(RedisModule_CreateDict)(RedisModuleCtx *ctx); 283 | void REDISMODULE_API_FUNC(RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d); 284 | uint64_t REDISMODULE_API_FUNC(RedisModule_DictSize)(RedisModuleDict *d); 285 | int REDISMODULE_API_FUNC(RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr); 286 | int REDISMODULE_API_FUNC(RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr); 287 | int REDISMODULE_API_FUNC(RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr); 288 | int REDISMODULE_API_FUNC(RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr); 289 | void *REDISMODULE_API_FUNC(RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey); 290 | void *REDISMODULE_API_FUNC(RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey); 291 | int REDISMODULE_API_FUNC(RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval); 292 | int REDISMODULE_API_FUNC(RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval); 293 | RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen); 294 | RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key); 295 | void REDISMODULE_API_FUNC(RedisModule_DictIteratorStop)(RedisModuleDictIter *di); 296 | int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen); 297 | int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key); 298 | void *REDISMODULE_API_FUNC(RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr); 299 | void *REDISMODULE_API_FUNC(RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr); 300 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr); 301 | RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr); 302 | int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen); 303 | int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key); 304 | 305 | /* Experimental APIs */ 306 | #ifdef REDISMODULE_EXPERIMENTAL_API 307 | #define REDISMODULE_EXPERIMENTAL_API_VERSION 3 308 | RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms); 309 | int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata); 310 | int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx); 311 | int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx); 312 | void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx); 313 | RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx); 314 | int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc); 315 | RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc); 316 | void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx); 317 | void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx); 318 | void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx); 319 | int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb); 320 | int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx); 321 | void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback); 322 | int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len); 323 | int REDISMODULE_API_FUNC(RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags); 324 | char **REDISMODULE_API_FUNC(RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes); 325 | void REDISMODULE_API_FUNC(RedisModule_FreeClusterNodesList)(char **ids); 326 | RedisModuleTimerID REDISMODULE_API_FUNC(RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data); 327 | int REDISMODULE_API_FUNC(RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data); 328 | int REDISMODULE_API_FUNC(RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data); 329 | const char *REDISMODULE_API_FUNC(RedisModule_GetMyClusterID)(void); 330 | size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void); 331 | void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len); 332 | void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len); 333 | void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback); 334 | void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags); 335 | #endif 336 | 337 | /* This is included inline inside each Redis module. */ 338 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused)); 339 | static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { 340 | void *getapifuncptr = ((void**)ctx)[0]; 341 | RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; 342 | REDISMODULE_GET_API(Alloc); 343 | REDISMODULE_GET_API(Calloc); 344 | REDISMODULE_GET_API(Free); 345 | REDISMODULE_GET_API(Realloc); 346 | REDISMODULE_GET_API(Strdup); 347 | REDISMODULE_GET_API(CreateCommand); 348 | REDISMODULE_GET_API(SetModuleAttribs); 349 | REDISMODULE_GET_API(IsModuleNameBusy); 350 | REDISMODULE_GET_API(WrongArity); 351 | REDISMODULE_GET_API(ReplyWithLongLong); 352 | REDISMODULE_GET_API(ReplyWithError); 353 | REDISMODULE_GET_API(ReplyWithSimpleString); 354 | REDISMODULE_GET_API(ReplyWithArray); 355 | REDISMODULE_GET_API(ReplySetArrayLength); 356 | REDISMODULE_GET_API(ReplyWithStringBuffer); 357 | REDISMODULE_GET_API(ReplyWithString); 358 | REDISMODULE_GET_API(ReplyWithNull); 359 | REDISMODULE_GET_API(ReplyWithCallReply); 360 | REDISMODULE_GET_API(ReplyWithDouble); 361 | REDISMODULE_GET_API(ReplySetArrayLength); 362 | REDISMODULE_GET_API(GetSelectedDb); 363 | REDISMODULE_GET_API(SelectDb); 364 | REDISMODULE_GET_API(OpenKey); 365 | REDISMODULE_GET_API(CloseKey); 366 | REDISMODULE_GET_API(KeyType); 367 | REDISMODULE_GET_API(ValueLength); 368 | REDISMODULE_GET_API(ListPush); 369 | REDISMODULE_GET_API(ListPop); 370 | REDISMODULE_GET_API(StringToLongLong); 371 | REDISMODULE_GET_API(StringToDouble); 372 | REDISMODULE_GET_API(Call); 373 | REDISMODULE_GET_API(CallReplyProto); 374 | REDISMODULE_GET_API(FreeCallReply); 375 | REDISMODULE_GET_API(CallReplyInteger); 376 | REDISMODULE_GET_API(CallReplyType); 377 | REDISMODULE_GET_API(CallReplyLength); 378 | REDISMODULE_GET_API(CallReplyArrayElement); 379 | REDISMODULE_GET_API(CallReplyStringPtr); 380 | REDISMODULE_GET_API(CreateStringFromCallReply); 381 | REDISMODULE_GET_API(CreateString); 382 | REDISMODULE_GET_API(CreateStringFromLongLong); 383 | REDISMODULE_GET_API(CreateStringFromString); 384 | REDISMODULE_GET_API(CreateStringPrintf); 385 | REDISMODULE_GET_API(FreeString); 386 | REDISMODULE_GET_API(StringPtrLen); 387 | REDISMODULE_GET_API(AutoMemory); 388 | REDISMODULE_GET_API(Replicate); 389 | REDISMODULE_GET_API(ReplicateVerbatim); 390 | REDISMODULE_GET_API(DeleteKey); 391 | REDISMODULE_GET_API(UnlinkKey); 392 | REDISMODULE_GET_API(StringSet); 393 | REDISMODULE_GET_API(StringDMA); 394 | REDISMODULE_GET_API(StringTruncate); 395 | REDISMODULE_GET_API(GetExpire); 396 | REDISMODULE_GET_API(SetExpire); 397 | REDISMODULE_GET_API(ZsetAdd); 398 | REDISMODULE_GET_API(ZsetIncrby); 399 | REDISMODULE_GET_API(ZsetScore); 400 | REDISMODULE_GET_API(ZsetRem); 401 | REDISMODULE_GET_API(ZsetRangeStop); 402 | REDISMODULE_GET_API(ZsetFirstInScoreRange); 403 | REDISMODULE_GET_API(ZsetLastInScoreRange); 404 | REDISMODULE_GET_API(ZsetFirstInLexRange); 405 | REDISMODULE_GET_API(ZsetLastInLexRange); 406 | REDISMODULE_GET_API(ZsetRangeCurrentElement); 407 | REDISMODULE_GET_API(ZsetRangeNext); 408 | REDISMODULE_GET_API(ZsetRangePrev); 409 | REDISMODULE_GET_API(ZsetRangeEndReached); 410 | REDISMODULE_GET_API(HashSet); 411 | REDISMODULE_GET_API(HashGet); 412 | REDISMODULE_GET_API(IsKeysPositionRequest); 413 | REDISMODULE_GET_API(KeyAtPos); 414 | REDISMODULE_GET_API(GetClientId); 415 | REDISMODULE_GET_API(GetContextFlags); 416 | REDISMODULE_GET_API(PoolAlloc); 417 | REDISMODULE_GET_API(CreateDataType); 418 | REDISMODULE_GET_API(ModuleTypeSetValue); 419 | REDISMODULE_GET_API(ModuleTypeGetType); 420 | REDISMODULE_GET_API(ModuleTypeGetValue); 421 | REDISMODULE_GET_API(SaveUnsigned); 422 | REDISMODULE_GET_API(LoadUnsigned); 423 | REDISMODULE_GET_API(SaveSigned); 424 | REDISMODULE_GET_API(LoadSigned); 425 | REDISMODULE_GET_API(SaveString); 426 | REDISMODULE_GET_API(SaveStringBuffer); 427 | REDISMODULE_GET_API(LoadString); 428 | REDISMODULE_GET_API(LoadStringBuffer); 429 | REDISMODULE_GET_API(SaveDouble); 430 | REDISMODULE_GET_API(LoadDouble); 431 | REDISMODULE_GET_API(SaveFloat); 432 | REDISMODULE_GET_API(LoadFloat); 433 | REDISMODULE_GET_API(EmitAOF); 434 | REDISMODULE_GET_API(Log); 435 | REDISMODULE_GET_API(LogIOError); 436 | REDISMODULE_GET_API(StringAppendBuffer); 437 | REDISMODULE_GET_API(RetainString); 438 | REDISMODULE_GET_API(StringCompare); 439 | REDISMODULE_GET_API(GetContextFromIO); 440 | REDISMODULE_GET_API(Milliseconds); 441 | REDISMODULE_GET_API(DigestAddStringBuffer); 442 | REDISMODULE_GET_API(DigestAddLongLong); 443 | REDISMODULE_GET_API(DigestEndSequence); 444 | REDISMODULE_GET_API(CreateDict); 445 | REDISMODULE_GET_API(FreeDict); 446 | REDISMODULE_GET_API(DictSize); 447 | REDISMODULE_GET_API(DictSetC); 448 | REDISMODULE_GET_API(DictReplaceC); 449 | REDISMODULE_GET_API(DictSet); 450 | REDISMODULE_GET_API(DictReplace); 451 | REDISMODULE_GET_API(DictGetC); 452 | REDISMODULE_GET_API(DictGet); 453 | REDISMODULE_GET_API(DictDelC); 454 | REDISMODULE_GET_API(DictDel); 455 | REDISMODULE_GET_API(DictIteratorStartC); 456 | REDISMODULE_GET_API(DictIteratorStart); 457 | REDISMODULE_GET_API(DictIteratorStop); 458 | REDISMODULE_GET_API(DictIteratorReseekC); 459 | REDISMODULE_GET_API(DictIteratorReseek); 460 | REDISMODULE_GET_API(DictNextC); 461 | REDISMODULE_GET_API(DictPrevC); 462 | REDISMODULE_GET_API(DictNext); 463 | REDISMODULE_GET_API(DictPrev); 464 | REDISMODULE_GET_API(DictCompare); 465 | REDISMODULE_GET_API(DictCompareC); 466 | 467 | #ifdef REDISMODULE_EXPERIMENTAL_API 468 | REDISMODULE_GET_API(GetThreadSafeContext); 469 | REDISMODULE_GET_API(FreeThreadSafeContext); 470 | REDISMODULE_GET_API(ThreadSafeContextLock); 471 | REDISMODULE_GET_API(ThreadSafeContextUnlock); 472 | REDISMODULE_GET_API(BlockClient); 473 | REDISMODULE_GET_API(UnblockClient); 474 | REDISMODULE_GET_API(IsBlockedReplyRequest); 475 | REDISMODULE_GET_API(IsBlockedTimeoutRequest); 476 | REDISMODULE_GET_API(GetBlockedClientPrivateData); 477 | REDISMODULE_GET_API(GetBlockedClientHandle); 478 | REDISMODULE_GET_API(AbortBlock); 479 | REDISMODULE_GET_API(SetDisconnectCallback); 480 | REDISMODULE_GET_API(SubscribeToKeyspaceEvents); 481 | REDISMODULE_GET_API(BlockedClientDisconnected); 482 | REDISMODULE_GET_API(RegisterClusterMessageReceiver); 483 | REDISMODULE_GET_API(SendClusterMessage); 484 | REDISMODULE_GET_API(GetClusterNodeInfo); 485 | REDISMODULE_GET_API(GetClusterNodesList); 486 | REDISMODULE_GET_API(FreeClusterNodesList); 487 | REDISMODULE_GET_API(CreateTimer); 488 | REDISMODULE_GET_API(StopTimer); 489 | REDISMODULE_GET_API(GetTimerInfo); 490 | REDISMODULE_GET_API(GetMyClusterID); 491 | REDISMODULE_GET_API(GetClusterSize); 492 | REDISMODULE_GET_API(GetRandomBytes); 493 | REDISMODULE_GET_API(GetRandomHexChars); 494 | REDISMODULE_GET_API(SetClusterFlags); 495 | #endif 496 | 497 | if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; 498 | RedisModule_SetModuleAttribs(ctx,name,ver,apiver); 499 | return REDISMODULE_OK; 500 | } 501 | 502 | #else 503 | 504 | /* Things only defined for the modules core, not exported to modules 505 | * including this file. */ 506 | #define RedisModuleString robj 507 | 508 | #endif /* REDISMODULE_CORE */ 509 | #endif /* REDISMOUDLE_H */ -------------------------------------------------------------------------------- /rmutil/Makefile: -------------------------------------------------------------------------------- 1 | # set environment variable RM_INCLUDE_DIR to the location of redismodule.h 2 | ifndef RM_INCLUDE_DIR 3 | RM_INCLUDE_DIR=../ 4 | endif 5 | 6 | CFLAGS ?= -g -fPIC -O3 -std=gnu99 -Wall -Wno-unused-function 7 | CFLAGS += -I$(RM_INCLUDE_DIR) 8 | CC=gcc 9 | 10 | OBJS=util.o strings.o sds.o vector.o alloc.o periodic.o 11 | 12 | all: librmutil.a 13 | 14 | clean: 15 | rm -rf *.o *.a 16 | 17 | librmutil.a: $(OBJS) 18 | ar rcs $@ $^ 19 | 20 | test_vector: test_vector.o vector.o 21 | $(CC) -Wall -o $@ $^ -lc -lpthread -O0 22 | @(sh -c ./$@) 23 | .PHONY: test_vector 24 | 25 | test_periodic: test_periodic.o periodic.o 26 | $(CC) -Wall -o $@ $^ -lc -lpthread -O0 27 | @(sh -c ./$@) 28 | .PHONY: test_periodic 29 | 30 | test: test_periodic test_vector 31 | .PHONY: test 32 | -------------------------------------------------------------------------------- /rmutil/alloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "alloc.h" 5 | 6 | /* A patched implementation of strdup that will use our patched calloc */ 7 | char *rmalloc_strndup(const char *s, size_t n) { 8 | char *ret = calloc(n + 1, sizeof(char)); 9 | if (ret) 10 | memcpy(ret, s, n); 11 | return ret; 12 | } 13 | 14 | /* 15 | * Re-patching RedisModule_Alloc and friends to the original malloc functions 16 | * 17 | * This function should be called if you are working with malloc-patched code 18 | * outside of redis, usually for unit tests. Call it once when entering your unit 19 | * tests' main(). 20 | * 21 | * Since including "alloc.h" while defining REDIS_MODULE_TARGET 22 | * replaces all malloc functions in redis with the RM_Alloc family of functions, 23 | * when running that code outside of redis, your app will crash. This function 24 | * patches the RM_Alloc functions back to the original mallocs. */ 25 | void RMUTil_InitAlloc() { 26 | 27 | RedisModule_Alloc = malloc; 28 | RedisModule_Realloc = realloc; 29 | RedisModule_Calloc = calloc; 30 | RedisModule_Free = free; 31 | RedisModule_Strdup = strdup; 32 | } 33 | -------------------------------------------------------------------------------- /rmutil/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_ALLOC__ 2 | #define __RMUTIL_ALLOC__ 3 | 4 | /* Automatic Redis Module Allocation functions monkey-patching. 5 | * 6 | * Including this file while REDIS_MODULE_TARGET is defined, will explicitly 7 | * override malloc, calloc, realloc & free with RedisModule_Alloc, 8 | * RedisModule_Callc, etc implementations, that allow Redis better control and 9 | * reporting over allocations per module. 10 | * 11 | * You should include this file in all c files AS THE LAST INCLUDED FILE 12 | * 13 | * This only has effect when when compiling with the macro REDIS_MODULE_TARGET 14 | * defined. The idea is that for unit tests it will not be defined, but for the 15 | * module build target it will be. 16 | * 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | char *rmalloc_strndup(const char *s, size_t n); 23 | 24 | #ifdef REDIS_MODULE_TARGET /* Set this when compiling your code as a module */ 25 | 26 | #define malloc(size) RedisModule_Alloc(size) 27 | #define calloc(count, size) RedisModule_Calloc(count, size) 28 | #define realloc(ptr, size) RedisModule_Realloc(ptr, size) 29 | #define free(ptr) RedisModule_Free(ptr) 30 | 31 | #ifdef strdup 32 | #undef strdup 33 | #endif 34 | #define strdup(ptr) RedisModule_Strdup(ptr) 35 | 36 | /* More overriding */ 37 | // needed to avoid calling strndup->malloc 38 | #ifdef strndup 39 | #undef strndup 40 | #endif 41 | #define strndup(s, n) rmalloc_strndup(s, n) 42 | 43 | #else 44 | 45 | #endif /* REDIS_MODULE_TARGET */ 46 | /* This function should be called if you are working with malloc-patched code 47 | * outside of redis, usually for unit tests. Call it once when entering your unit 48 | * tests' main() */ 49 | void RMUTil_InitAlloc(); 50 | 51 | #endif /* __RMUTIL_ALLOC__ */ 52 | -------------------------------------------------------------------------------- /rmutil/heap.c: -------------------------------------------------------------------------------- 1 | #include "heap.h" 2 | 3 | /* Byte-wise swap two items of size SIZE. */ 4 | #define SWAP(a, b, size) \ 5 | do \ 6 | { \ 7 | register size_t __size = (size); \ 8 | register char *__a = (a), *__b = (b); \ 9 | do \ 10 | { \ 11 | char __tmp = *__a; \ 12 | *__a++ = *__b; \ 13 | *__b++ = __tmp; \ 14 | } while (--__size > 0); \ 15 | } while (0) 16 | 17 | inline char *__vector_GetPtr(Vector *v, size_t pos) { 18 | return v->data + (pos * v->elemSize); 19 | } 20 | 21 | void __sift_up(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 22 | size_t len = last - first; 23 | if (len > 1) { 24 | len = (len - 2) / 2; 25 | size_t ptr = first + len; 26 | if (cmp(__vector_GetPtr(v, ptr), __vector_GetPtr(v, --last)) < 0) { 27 | char t[v->elemSize]; 28 | memcpy(t, __vector_GetPtr(v, last), v->elemSize); 29 | do { 30 | memcpy(__vector_GetPtr(v, last), __vector_GetPtr(v, ptr), v->elemSize); 31 | last = ptr; 32 | if (len == 0) 33 | break; 34 | len = (len - 1) / 2; 35 | ptr = first + len; 36 | } while (cmp(__vector_GetPtr(v, ptr), t) < 0); 37 | memcpy(__vector_GetPtr(v, last), t, v->elemSize); 38 | } 39 | } 40 | } 41 | 42 | void __sift_down(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *), size_t start) { 43 | // left-child of __start is at 2 * __start + 1 44 | // right-child of __start is at 2 * __start + 2 45 | size_t len = last - first; 46 | size_t child = start - first; 47 | 48 | if (len < 2 || (len - 2) / 2 < child) 49 | return; 50 | 51 | child = 2 * child + 1; 52 | 53 | if ((child + 1) < len && cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, first + child + 1)) < 0) { 54 | // right-child exists and is greater than left-child 55 | ++child; 56 | } 57 | 58 | // check if we are in heap-order 59 | if (cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, start)) < 0) 60 | // we are, __start is larger than it's largest child 61 | return; 62 | 63 | char top[v->elemSize]; 64 | memcpy(top, __vector_GetPtr(v, start), v->elemSize); 65 | do { 66 | // we are not in heap-order, swap the parent with it's largest child 67 | memcpy(__vector_GetPtr(v, start), __vector_GetPtr(v, first + child), v->elemSize); 68 | start = first + child; 69 | 70 | if ((len - 2) / 2 < child) 71 | break; 72 | 73 | // recompute the child based off of the updated parent 74 | child = 2 * child + 1; 75 | 76 | if ((child + 1) < len && cmp(__vector_GetPtr(v, first + child), __vector_GetPtr(v, first + child + 1)) < 0) { 77 | // right-child exists and is greater than left-child 78 | ++child; 79 | } 80 | 81 | // check if we are in heap-order 82 | } while (cmp(__vector_GetPtr(v, first + child), top) >= 0); 83 | memcpy(__vector_GetPtr(v, start), top, v->elemSize); 84 | } 85 | 86 | 87 | void Make_Heap(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 88 | if (last - first > 1) { 89 | // start from the first parent, there is no need to consider children 90 | for (int start = (last - first - 2) / 2; start >= 0; --start) { 91 | __sift_down(v, first, last, cmp, first + start); 92 | } 93 | } 94 | } 95 | 96 | 97 | inline void Heap_Push(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 98 | __sift_up(v, first, last, cmp); 99 | } 100 | 101 | 102 | inline void Heap_Pop(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)) { 103 | if (last - first > 1) { 104 | SWAP(__vector_GetPtr(v, first), __vector_GetPtr(v, --last), v->elemSize); 105 | __sift_down(v, first, last, cmp, first); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rmutil/heap.h: -------------------------------------------------------------------------------- 1 | #ifndef __HEAP_H__ 2 | #define __HEAP_H__ 3 | 4 | #include "vector.h" 5 | 6 | 7 | /* Make heap from range 8 | * Rearranges the elements in the range [first,last) in such a way that they form a heap. 9 | * A heap is a way to organize the elements of a range that allows for fast retrieval of the element with the highest 10 | * value at any moment (with pop_heap), even repeatedly, while allowing for fast insertion of new elements (with 11 | * push_heap). 12 | * The element with the highest value is always pointed by first. The order of the other elements depends on the 13 | * particular implementation, but it is consistent throughout all heap-related functions of this header. 14 | * The elements are compared using cmp. 15 | */ 16 | void Make_Heap(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 17 | 18 | 19 | /* Push element into heap range 20 | * Given a heap in the range [first,last-1), this function extends the range considered a heap to [first,last) by 21 | * placing the value in (last-1) into its corresponding location within it. 22 | * A range can be organized into a heap by calling make_heap. After that, its heap properties are preserved if elements 23 | * are added and removed from it using push_heap and pop_heap, respectively. 24 | */ 25 | void Heap_Push(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 26 | 27 | 28 | /* Pop element from heap range 29 | * Rearranges the elements in the heap range [first,last) in such a way that the part considered a heap is shortened 30 | * by one: The element with the highest value is moved to (last-1). 31 | * While the element with the highest value is moved from first to (last-1) (which now is out of the heap), the other 32 | * elements are reorganized in such a way that the range [first,last-1) preserves the properties of a heap. 33 | * A range can be organized into a heap by calling make_heap. After that, its heap properties are preserved if elements 34 | * are added and removed from it using push_heap and pop_heap, respectively. 35 | */ 36 | void Heap_Pop(Vector *v, size_t first, size_t last, int (*cmp)(void *, void *)); 37 | 38 | #endif //__HEAP_H__ 39 | -------------------------------------------------------------------------------- /rmutil/logging.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_LOGGING_H__ 2 | #define __RMUTIL_LOGGING_H__ 3 | 4 | /* Convenience macros for redis logging */ 5 | 6 | #define RM_LOG_DEBUG(ctx, ...) RedisModule_Log(ctx, "debug", __VA_ARGS__) 7 | #define RM_LOG_VERBOSE(ctx, ...) RedisModule_Log(ctx, "verbose", __VA_ARGS__) 8 | #define RM_LOG_NOTICE(ctx, ...) RedisModule_Log(ctx, "notice", __VA_ARGS__) 9 | #define RM_LOG_WARNING(ctx, ...) RedisModule_Log(ctx, "warning", __VA_ARGS__) 10 | 11 | #endif -------------------------------------------------------------------------------- /rmutil/periodic.c: -------------------------------------------------------------------------------- 1 | #define REDISMODULE_EXPERIMENTAL_API 2 | #include "periodic.h" 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct RMUtilTimer { 8 | RMutilTimerFunc cb; 9 | RMUtilTimerTerminationFunc onTerm; 10 | void *privdata; 11 | struct timespec interval; 12 | pthread_t thread; 13 | pthread_mutex_t lock; 14 | pthread_cond_t cond; 15 | } RMUtilTimer; 16 | 17 | static struct timespec timespecAdd(struct timespec *a, struct timespec *b) { 18 | struct timespec ret; 19 | ret.tv_sec = a->tv_sec + b->tv_sec; 20 | 21 | long long ns = a->tv_nsec + b->tv_nsec; 22 | ret.tv_sec += ns / 1000000000; 23 | ret.tv_nsec = ns % 1000000000; 24 | return ret; 25 | } 26 | 27 | static void *rmutilTimer_Loop(void *ctx) { 28 | RMUtilTimer *tm = ctx; 29 | 30 | int rc = ETIMEDOUT; 31 | struct timespec ts; 32 | 33 | pthread_mutex_lock(&tm->lock); 34 | while (rc != 0) { 35 | clock_gettime(CLOCK_REALTIME, &ts); 36 | struct timespec timeout = timespecAdd(&ts, &tm->interval); 37 | if ((rc = pthread_cond_timedwait(&tm->cond, &tm->lock, &timeout)) == ETIMEDOUT) { 38 | 39 | // Create a thread safe context if we're running inside redis 40 | RedisModuleCtx *rctx = NULL; 41 | if (RedisModule_GetThreadSafeContext) rctx = RedisModule_GetThreadSafeContext(NULL); 42 | 43 | // call our callback... 44 | tm->cb(rctx, tm->privdata); 45 | 46 | // If needed - free the thread safe context. 47 | // It's up to the user to decide whether automemory is active there 48 | if (rctx) RedisModule_FreeThreadSafeContext(rctx); 49 | } 50 | if (rc == EINVAL) { 51 | perror("Error waiting for condition"); 52 | break; 53 | } 54 | } 55 | 56 | // call the termination callback if needed 57 | if (tm->onTerm != NULL) { 58 | tm->onTerm(tm->privdata); 59 | } 60 | 61 | // free resources associated with the timer 62 | pthread_cond_destroy(&tm->cond); 63 | free(tm); 64 | 65 | return NULL; 66 | } 67 | 68 | /* set a new frequency for the timer. This will take effect AFTER the next trigger */ 69 | void RMUtilTimer_SetInterval(struct RMUtilTimer *t, struct timespec newInterval) { 70 | t->interval = newInterval; 71 | } 72 | 73 | RMUtilTimer *RMUtil_NewPeriodicTimer(RMutilTimerFunc cb, RMUtilTimerTerminationFunc onTerm, 74 | void *privdata, struct timespec interval) { 75 | RMUtilTimer *ret = malloc(sizeof(*ret)); 76 | *ret = (RMUtilTimer){ 77 | .privdata = privdata, .interval = interval, .cb = cb, .onTerm = onTerm, 78 | }; 79 | pthread_cond_init(&ret->cond, NULL); 80 | pthread_mutex_init(&ret->lock, NULL); 81 | 82 | pthread_create(&ret->thread, NULL, rmutilTimer_Loop, ret); 83 | return ret; 84 | } 85 | 86 | int RMUtilTimer_Terminate(struct RMUtilTimer *t) { 87 | return pthread_cond_signal(&t->cond); 88 | } 89 | -------------------------------------------------------------------------------- /rmutil/periodic.h: -------------------------------------------------------------------------------- 1 | #ifndef RMUTIL_PERIODIC_H_ 2 | #define RMUTIL_PERIODIC_H_ 3 | #include 4 | #include 5 | 6 | /** periodic.h - Utility periodic timer running a task repeatedly every given time interval */ 7 | 8 | /* RMUtilTimer - opaque context for the timer */ 9 | struct RMUtilTimer; 10 | 11 | /* RMutilTimerFunc - callback type for timer tasks. The ctx is a thread-safe redis module context 12 | * that should be locked/unlocked by the callback when running stuff against redis. privdata is 13 | * pre-existing private data */ 14 | typedef void (*RMutilTimerFunc)(RedisModuleCtx *ctx, void *privdata); 15 | 16 | typedef void (*RMUtilTimerTerminationFunc)(void *privdata); 17 | 18 | /* Create and start a new periodic timer. Each timer has its own thread and can only be run and 19 | * stopped once. The timer runs `cb` every `interval` with `privdata` passed to the callback. */ 20 | struct RMUtilTimer *RMUtil_NewPeriodicTimer(RMutilTimerFunc cb, RMUtilTimerTerminationFunc onTerm, 21 | void *privdata, struct timespec interval); 22 | 23 | /* set a new frequency for the timer. This will take effect AFTER the next trigger */ 24 | void RMUtilTimer_SetInterval(struct RMUtilTimer *t, struct timespec newInterval); 25 | 26 | /* Stop the timer loop, call the termination callbck to free up any resources linked to the timer, 27 | * and free the timer after stopping. 28 | * 29 | * This function doesn't wait for the thread to terminate, as it may cause a race condition if the 30 | * timer's callback is waiting for the redis global lock. 31 | * Instead you should make sure any resources are freed by the callback after the thread loop is 32 | * finished. 33 | * 34 | * The timer is freed automatically, so the callback doesn't need to do anything about it. 35 | * The callback gets the timer's associated privdata as its argument. 36 | * 37 | * If no callback is specified we do not free up privdata. If privdata is NULL we still call the 38 | * callback, as it may log stuff or free global resources. 39 | */ 40 | int RMUtilTimer_Terminate(struct RMUtilTimer *t); 41 | 42 | /* DEPRECATED - do not use this function (well now you can't), use terminate instead 43 | Free the timer context. The caller should be responsible for freeing the private data at this 44 | * point */ 45 | // void RMUtilTimer_Free(struct RMUtilTimer *t); 46 | #endif -------------------------------------------------------------------------------- /rmutil/priority_queue.c: -------------------------------------------------------------------------------- 1 | #include "priority_queue.h" 2 | #include "heap.h" 3 | 4 | PriorityQueue *__newPriorityQueueSize(size_t elemSize, size_t cap, int (*cmp)(void *, void *)) { 5 | PriorityQueue *pq = malloc(sizeof(PriorityQueue)); 6 | pq->v = __newVectorSize(elemSize, cap); 7 | pq->cmp = cmp; 8 | return pq; 9 | } 10 | 11 | inline size_t Priority_Queue_Size(PriorityQueue *pq) { 12 | return Vector_Size(pq->v); 13 | } 14 | 15 | inline int Priority_Queue_Top(PriorityQueue *pq, void *ptr) { 16 | return Vector_Get(pq->v, 0, ptr); 17 | } 18 | 19 | inline size_t __priority_Queue_PushPtr(PriorityQueue *pq, void *elem) { 20 | size_t top = __vector_PushPtr(pq->v, elem); 21 | Heap_Push(pq->v, 0, top, pq->cmp); 22 | return top; 23 | } 24 | 25 | inline void Priority_Queue_Pop(PriorityQueue *pq) { 26 | if (pq->v->top == 0) { 27 | return; 28 | } 29 | Heap_Pop(pq->v, 0, pq->v->top, pq->cmp); 30 | pq->v->top--; 31 | } 32 | 33 | void Priority_Queue_Free(PriorityQueue *pq) { 34 | Vector_Free(pq->v); 35 | free(pq); 36 | } 37 | -------------------------------------------------------------------------------- /rmutil/priority_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef __PRIORITY_QUEUE_H__ 2 | #define __PRIORITY_QUEUE_H__ 3 | 4 | #include "vector.h" 5 | 6 | /* Priority queue 7 | * Priority queues are designed such that its first element is always the greatest of the elements it contains. 8 | * This context is similar to a heap, where elements can be inserted at any moment, and only the max heap element can be 9 | * retrieved (the one at the top in the priority queue). 10 | * Priority queues are implemented as Vectors. Elements are popped from the "back" of Vector, which is known as the top 11 | * of the priority queue. 12 | */ 13 | typedef struct { 14 | Vector *v; 15 | 16 | int (*cmp)(void *, void *); 17 | } PriorityQueue; 18 | 19 | /* Construct priority queue 20 | * Constructs a priority_queue container adaptor object. 21 | */ 22 | PriorityQueue *__newPriorityQueueSize(size_t elemSize, size_t cap, int (*cmp)(void *, void *)); 23 | 24 | #define NewPriorityQueue(type, cap, cmp) __newPriorityQueueSize(sizeof(type), cap, cmp) 25 | 26 | /* Return size 27 | * Returns the number of elements in the priority_queue. 28 | */ 29 | size_t Priority_Queue_Size(PriorityQueue *pq); 30 | 31 | /* Access top element 32 | * Copy the top element in the priority_queue to ptr. 33 | * The top element is the element that compares higher in the priority_queue. 34 | */ 35 | int Priority_Queue_Top(PriorityQueue *pq, void *ptr); 36 | 37 | /* Insert element 38 | * Inserts a new element in the priority_queue. 39 | */ 40 | size_t __priority_Queue_PushPtr(PriorityQueue *pq, void *elem); 41 | 42 | #define Priority_Queue_Push(pq, elem) __priority_Queue_PushPtr(pq, &(typeof(elem)){elem}) 43 | 44 | /* Remove top element 45 | * Removes the element on top of the priority_queue, effectively reducing its size by one. The element removed is the 46 | * one with the highest value. 47 | * The value of this element can be retrieved before being popped by calling Priority_Queue_Top. 48 | */ 49 | void Priority_Queue_Pop(PriorityQueue *pq); 50 | 51 | /* free the priority queue and the underlying data. Does not release its elements if 52 | * they are pointers */ 53 | void Priority_Queue_Free(PriorityQueue *pq); 54 | 55 | #endif //__PRIORITY_QUEUE_H__ 56 | -------------------------------------------------------------------------------- /rmutil/sds.c: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "sds.h" 39 | #include "sdsalloc.h" 40 | 41 | static inline int sdsHdrSize(char type) { 42 | switch(type&SDS_TYPE_MASK) { 43 | case SDS_TYPE_5: 44 | return sizeof(struct sdshdr5); 45 | case SDS_TYPE_8: 46 | return sizeof(struct sdshdr8); 47 | case SDS_TYPE_16: 48 | return sizeof(struct sdshdr16); 49 | case SDS_TYPE_32: 50 | return sizeof(struct sdshdr32); 51 | case SDS_TYPE_64: 52 | return sizeof(struct sdshdr64); 53 | } 54 | return 0; 55 | } 56 | 57 | static inline char sdsReqType(size_t string_size) { 58 | if (string_size < 32) 59 | return SDS_TYPE_5; 60 | if (string_size < 0xff) 61 | return SDS_TYPE_8; 62 | if (string_size < 0xffff) 63 | return SDS_TYPE_16; 64 | if (string_size < 0xffffffff) 65 | return SDS_TYPE_32; 66 | return SDS_TYPE_64; 67 | } 68 | 69 | /* Create a new sds string with the content specified by the 'init' pointer 70 | * and 'initlen'. 71 | * If NULL is used for 'init' the string is initialized with zero bytes. 72 | * 73 | * The string is always null-termined (all the sds strings are, always) so 74 | * even if you create an sds string with: 75 | * 76 | * mystring = sdsnewlen("abc",3); 77 | * 78 | * You can print the string with printf() as there is an implicit \0 at the 79 | * end of the string. However the string is binary safe and can contain 80 | * \0 characters in the middle, as the length is stored in the sds header. */ 81 | sds sdsnewlen(const void *init, size_t initlen) { 82 | void *sh; 83 | sds s; 84 | char type = sdsReqType(initlen); 85 | /* Empty strings are usually created in order to append. Use type 8 86 | * since type 5 is not good at this. */ 87 | if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; 88 | int hdrlen = sdsHdrSize(type); 89 | unsigned char *fp; /* flags pointer. */ 90 | 91 | sh = s_malloc(hdrlen+initlen+1); 92 | if (!init) 93 | memset(sh, 0, hdrlen+initlen+1); 94 | if (sh == NULL) return NULL; 95 | s = (char*)sh+hdrlen; 96 | fp = ((unsigned char*)s)-1; 97 | switch(type) { 98 | case SDS_TYPE_5: { 99 | *fp = type | (initlen << SDS_TYPE_BITS); 100 | break; 101 | } 102 | case SDS_TYPE_8: { 103 | SDS_HDR_VAR(8,s); 104 | sh->len = initlen; 105 | sh->alloc = initlen; 106 | *fp = type; 107 | break; 108 | } 109 | case SDS_TYPE_16: { 110 | SDS_HDR_VAR(16,s); 111 | sh->len = initlen; 112 | sh->alloc = initlen; 113 | *fp = type; 114 | break; 115 | } 116 | case SDS_TYPE_32: { 117 | SDS_HDR_VAR(32,s); 118 | sh->len = initlen; 119 | sh->alloc = initlen; 120 | *fp = type; 121 | break; 122 | } 123 | case SDS_TYPE_64: { 124 | SDS_HDR_VAR(64,s); 125 | sh->len = initlen; 126 | sh->alloc = initlen; 127 | *fp = type; 128 | break; 129 | } 130 | } 131 | if (initlen && init) 132 | memcpy(s, init, initlen); 133 | s[initlen] = '\0'; 134 | return s; 135 | } 136 | 137 | /* Create an empty (zero length) sds string. Even in this case the string 138 | * always has an implicit null term. */ 139 | sds sdsempty(void) { 140 | return sdsnewlen("",0); 141 | } 142 | 143 | /* Create a new sds string starting from a null terminated C string. */ 144 | sds sdsnew(const char *init) { 145 | size_t initlen = (init == NULL) ? 0 : strlen(init); 146 | return sdsnewlen(init, initlen); 147 | } 148 | 149 | /* Duplicate an sds string. */ 150 | sds sdsdup(const sds s) { 151 | return sdsnewlen(s, sdslen(s)); 152 | } 153 | 154 | /* Free an sds string. No operation is performed if 's' is NULL. */ 155 | void sdsfree(sds s) { 156 | if (s == NULL) return; 157 | s_free((char*)s-sdsHdrSize(s[-1])); 158 | } 159 | 160 | /* Set the sds string length to the length as obtained with strlen(), so 161 | * considering as content only up to the first null term character. 162 | * 163 | * This function is useful when the sds string is hacked manually in some 164 | * way, like in the following example: 165 | * 166 | * s = sdsnew("foobar"); 167 | * s[2] = '\0'; 168 | * sdsupdatelen(s); 169 | * printf("%d\n", sdslen(s)); 170 | * 171 | * The output will be "2", but if we comment out the call to sdsupdatelen() 172 | * the output will be "6" as the string was modified but the logical length 173 | * remains 6 bytes. */ 174 | void sdsupdatelen(sds s) { 175 | int reallen = strlen(s); 176 | sdssetlen(s, reallen); 177 | } 178 | 179 | /* Modify an sds string in-place to make it empty (zero length). 180 | * However all the existing buffer is not discarded but set as free space 181 | * so that next append operations will not require allocations up to the 182 | * number of bytes previously available. */ 183 | void sdsclear(sds s) { 184 | sdssetlen(s, 0); 185 | s[0] = '\0'; 186 | } 187 | 188 | /* Enlarge the free space at the end of the sds string so that the caller 189 | * is sure that after calling this function can overwrite up to addlen 190 | * bytes after the end of the string, plus one more byte for nul term. 191 | * 192 | * Note: this does not change the *length* of the sds string as returned 193 | * by sdslen(), but only the free buffer space we have. */ 194 | sds sdsMakeRoomFor(sds s, size_t addlen) { 195 | void *sh, *newsh; 196 | size_t avail = sdsavail(s); 197 | size_t len, newlen; 198 | char type, oldtype = s[-1] & SDS_TYPE_MASK; 199 | int hdrlen; 200 | 201 | /* Return ASAP if there is enough space left. */ 202 | if (avail >= addlen) return s; 203 | 204 | len = sdslen(s); 205 | sh = (char*)s-sdsHdrSize(oldtype); 206 | newlen = (len+addlen); 207 | if (newlen < SDS_MAX_PREALLOC) 208 | newlen *= 2; 209 | else 210 | newlen += SDS_MAX_PREALLOC; 211 | 212 | type = sdsReqType(newlen); 213 | 214 | /* Don't use type 5: the user is appending to the string and type 5 is 215 | * not able to remember empty space, so sdsMakeRoomFor() must be called 216 | * at every appending operation. */ 217 | if (type == SDS_TYPE_5) type = SDS_TYPE_8; 218 | 219 | hdrlen = sdsHdrSize(type); 220 | if (oldtype==type) { 221 | newsh = s_realloc(sh, hdrlen+newlen+1); 222 | if (newsh == NULL) return NULL; 223 | s = (char*)newsh+hdrlen; 224 | } else { 225 | /* Since the header size changes, need to move the string forward, 226 | * and can't use realloc */ 227 | newsh = s_malloc(hdrlen+newlen+1); 228 | if (newsh == NULL) return NULL; 229 | memcpy((char*)newsh+hdrlen, s, len+1); 230 | s_free(sh); 231 | s = (char*)newsh+hdrlen; 232 | s[-1] = type; 233 | sdssetlen(s, len); 234 | } 235 | sdssetalloc(s, newlen); 236 | return s; 237 | } 238 | 239 | /* Reallocate the sds string so that it has no free space at the end. The 240 | * contained string remains not altered, but next concatenation operations 241 | * will require a reallocation. 242 | * 243 | * After the call, the passed sds string is no longer valid and all the 244 | * references must be substituted with the new pointer returned by the call. */ 245 | sds sdsRemoveFreeSpace(sds s) { 246 | void *sh, *newsh; 247 | char type, oldtype = s[-1] & SDS_TYPE_MASK; 248 | int hdrlen; 249 | size_t len = sdslen(s); 250 | sh = (char*)s-sdsHdrSize(oldtype); 251 | 252 | type = sdsReqType(len); 253 | hdrlen = sdsHdrSize(type); 254 | if (oldtype==type) { 255 | newsh = s_realloc(sh, hdrlen+len+1); 256 | if (newsh == NULL) return NULL; 257 | s = (char*)newsh+hdrlen; 258 | } else { 259 | newsh = s_malloc(hdrlen+len+1); 260 | if (newsh == NULL) return NULL; 261 | memcpy((char*)newsh+hdrlen, s, len+1); 262 | s_free(sh); 263 | s = (char*)newsh+hdrlen; 264 | s[-1] = type; 265 | sdssetlen(s, len); 266 | } 267 | sdssetalloc(s, len); 268 | return s; 269 | } 270 | 271 | /* Return the total size of the allocation of the specifed sds string, 272 | * including: 273 | * 1) The sds header before the pointer. 274 | * 2) The string. 275 | * 3) The free buffer at the end if any. 276 | * 4) The implicit null term. 277 | */ 278 | size_t sdsAllocSize(sds s) { 279 | size_t alloc = sdsalloc(s); 280 | return sdsHdrSize(s[-1])+alloc+1; 281 | } 282 | 283 | /* Return the pointer of the actual SDS allocation (normally SDS strings 284 | * are referenced by the start of the string buffer). */ 285 | void *sdsAllocPtr(sds s) { 286 | return (void*) (s-sdsHdrSize(s[-1])); 287 | } 288 | 289 | /* Increment the sds length and decrements the left free space at the 290 | * end of the string according to 'incr'. Also set the null term 291 | * in the new end of the string. 292 | * 293 | * This function is used in order to fix the string length after the 294 | * user calls sdsMakeRoomFor(), writes something after the end of 295 | * the current string, and finally needs to set the new length. 296 | * 297 | * Note: it is possible to use a negative increment in order to 298 | * right-trim the string. 299 | * 300 | * Usage example: 301 | * 302 | * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the 303 | * following schema, to cat bytes coming from the kernel to the end of an 304 | * sds string without copying into an intermediate buffer: 305 | * 306 | * oldlen = sdslen(s); 307 | * s = sdsMakeRoomFor(s, BUFFER_SIZE); 308 | * nread = read(fd, s+oldlen, BUFFER_SIZE); 309 | * ... check for nread <= 0 and handle it ... 310 | * sdsIncrLen(s, nread); 311 | */ 312 | void sdsIncrLen(sds s, int incr) { 313 | unsigned char flags = s[-1]; 314 | size_t len; 315 | switch(flags&SDS_TYPE_MASK) { 316 | case SDS_TYPE_5: { 317 | unsigned char *fp = ((unsigned char*)s)-1; 318 | unsigned char oldlen = SDS_TYPE_5_LEN(flags); 319 | assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); 320 | *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); 321 | len = oldlen+incr; 322 | break; 323 | } 324 | case SDS_TYPE_8: { 325 | SDS_HDR_VAR(8,s); 326 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); 327 | len = (sh->len += incr); 328 | break; 329 | } 330 | case SDS_TYPE_16: { 331 | SDS_HDR_VAR(16,s); 332 | assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); 333 | len = (sh->len += incr); 334 | break; 335 | } 336 | case SDS_TYPE_32: { 337 | SDS_HDR_VAR(32,s); 338 | assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); 339 | len = (sh->len += incr); 340 | break; 341 | } 342 | case SDS_TYPE_64: { 343 | SDS_HDR_VAR(64,s); 344 | assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); 345 | len = (sh->len += incr); 346 | break; 347 | } 348 | default: len = 0; /* Just to avoid compilation warnings. */ 349 | } 350 | s[len] = '\0'; 351 | } 352 | 353 | /* Grow the sds to have the specified length. Bytes that were not part of 354 | * the original length of the sds will be set to zero. 355 | * 356 | * if the specified length is smaller than the current length, no operation 357 | * is performed. */ 358 | sds sdsgrowzero(sds s, size_t len) { 359 | size_t curlen = sdslen(s); 360 | 361 | if (len <= curlen) return s; 362 | s = sdsMakeRoomFor(s,len-curlen); 363 | if (s == NULL) return NULL; 364 | 365 | /* Make sure added region doesn't contain garbage */ 366 | memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ 367 | sdssetlen(s, len); 368 | return s; 369 | } 370 | 371 | /* Append the specified binary-safe string pointed by 't' of 'len' bytes to the 372 | * end of the specified sds string 's'. 373 | * 374 | * After the call, the passed sds string is no longer valid and all the 375 | * references must be substituted with the new pointer returned by the call. */ 376 | sds sdscatlen(sds s, const void *t, size_t len) { 377 | size_t curlen = sdslen(s); 378 | 379 | s = sdsMakeRoomFor(s,len); 380 | if (s == NULL) return NULL; 381 | memcpy(s+curlen, t, len); 382 | sdssetlen(s, curlen+len); 383 | s[curlen+len] = '\0'; 384 | return s; 385 | } 386 | 387 | /* Append the specified null termianted C string to the sds string 's'. 388 | * 389 | * After the call, the passed sds string is no longer valid and all the 390 | * references must be substituted with the new pointer returned by the call. */ 391 | sds sdscat(sds s, const char *t) { 392 | return sdscatlen(s, t, strlen(t)); 393 | } 394 | 395 | /* Append the specified sds 't' to the existing sds 's'. 396 | * 397 | * After the call, the modified sds string is no longer valid and all the 398 | * references must be substituted with the new pointer returned by the call. */ 399 | sds sdscatsds(sds s, const sds t) { 400 | return sdscatlen(s, t, sdslen(t)); 401 | } 402 | 403 | /* Destructively modify the sds string 's' to hold the specified binary 404 | * safe string pointed by 't' of length 'len' bytes. */ 405 | sds sdscpylen(sds s, const char *t, size_t len) { 406 | if (sdsalloc(s) < len) { 407 | s = sdsMakeRoomFor(s,len-sdslen(s)); 408 | if (s == NULL) return NULL; 409 | } 410 | memcpy(s, t, len); 411 | s[len] = '\0'; 412 | sdssetlen(s, len); 413 | return s; 414 | } 415 | 416 | /* Like sdscpylen() but 't' must be a null-termined string so that the length 417 | * of the string is obtained with strlen(). */ 418 | sds sdscpy(sds s, const char *t) { 419 | return sdscpylen(s, t, strlen(t)); 420 | } 421 | 422 | /* Helper for sdscatlonglong() doing the actual number -> string 423 | * conversion. 's' must point to a string with room for at least 424 | * SDS_LLSTR_SIZE bytes. 425 | * 426 | * The function returns the length of the null-terminated string 427 | * representation stored at 's'. */ 428 | #define SDS_LLSTR_SIZE 21 429 | int sdsll2str(char *s, long long value) { 430 | char *p, aux; 431 | unsigned long long v; 432 | size_t l; 433 | 434 | /* Generate the string representation, this method produces 435 | * an reversed string. */ 436 | v = (value < 0) ? -value : value; 437 | p = s; 438 | do { 439 | *p++ = '0'+(v%10); 440 | v /= 10; 441 | } while(v); 442 | if (value < 0) *p++ = '-'; 443 | 444 | /* Compute length and add null term. */ 445 | l = p-s; 446 | *p = '\0'; 447 | 448 | /* Reverse the string. */ 449 | p--; 450 | while(s < p) { 451 | aux = *s; 452 | *s = *p; 453 | *p = aux; 454 | s++; 455 | p--; 456 | } 457 | return l; 458 | } 459 | 460 | /* Identical sdsll2str(), but for unsigned long long type. */ 461 | int sdsull2str(char *s, unsigned long long v) { 462 | char *p, aux; 463 | size_t l; 464 | 465 | /* Generate the string representation, this method produces 466 | * an reversed string. */ 467 | p = s; 468 | do { 469 | *p++ = '0'+(v%10); 470 | v /= 10; 471 | } while(v); 472 | 473 | /* Compute length and add null term. */ 474 | l = p-s; 475 | *p = '\0'; 476 | 477 | /* Reverse the string. */ 478 | p--; 479 | while(s < p) { 480 | aux = *s; 481 | *s = *p; 482 | *p = aux; 483 | s++; 484 | p--; 485 | } 486 | return l; 487 | } 488 | 489 | /* Create an sds string from a long long value. It is much faster than: 490 | * 491 | * sdscatprintf(sdsempty(),"%lld\n", value); 492 | */ 493 | sds sdsfromlonglong(long long value) { 494 | char buf[SDS_LLSTR_SIZE]; 495 | int len = sdsll2str(buf,value); 496 | 497 | return sdsnewlen(buf,len); 498 | } 499 | 500 | /* Like sdscatprintf() but gets va_list instead of being variadic. */ 501 | sds sdscatvprintf(sds s, const char *fmt, va_list ap) { 502 | va_list cpy; 503 | char staticbuf[1024], *buf = staticbuf, *t; 504 | size_t buflen = strlen(fmt)*2; 505 | 506 | /* We try to start using a static buffer for speed. 507 | * If not possible we revert to heap allocation. */ 508 | if (buflen > sizeof(staticbuf)) { 509 | buf = s_malloc(buflen); 510 | if (buf == NULL) return NULL; 511 | } else { 512 | buflen = sizeof(staticbuf); 513 | } 514 | 515 | /* Try with buffers two times bigger every time we fail to 516 | * fit the string in the current buffer size. */ 517 | while(1) { 518 | buf[buflen-2] = '\0'; 519 | va_copy(cpy,ap); 520 | vsnprintf(buf, buflen, fmt, cpy); 521 | va_end(cpy); 522 | if (buf[buflen-2] != '\0') { 523 | if (buf != staticbuf) s_free(buf); 524 | buflen *= 2; 525 | buf = s_malloc(buflen); 526 | if (buf == NULL) return NULL; 527 | continue; 528 | } 529 | break; 530 | } 531 | 532 | /* Finally concat the obtained string to the SDS string and return it. */ 533 | t = sdscat(s, buf); 534 | if (buf != staticbuf) s_free(buf); 535 | return t; 536 | } 537 | 538 | /* Append to the sds string 's' a string obtained using printf-alike format 539 | * specifier. 540 | * 541 | * After the call, the modified sds string is no longer valid and all the 542 | * references must be substituted with the new pointer returned by the call. 543 | * 544 | * Example: 545 | * 546 | * s = sdsnew("Sum is: "); 547 | * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b). 548 | * 549 | * Often you need to create a string from scratch with the printf-alike 550 | * format. When this is the need, just use sdsempty() as the target string: 551 | * 552 | * s = sdscatprintf(sdsempty(), "... your format ...", args); 553 | */ 554 | sds sdscatprintf(sds s, const char *fmt, ...) { 555 | va_list ap; 556 | char *t; 557 | va_start(ap, fmt); 558 | t = sdscatvprintf(s,fmt,ap); 559 | va_end(ap); 560 | return t; 561 | } 562 | 563 | /* This function is similar to sdscatprintf, but much faster as it does 564 | * not rely on sprintf() family functions implemented by the libc that 565 | * are often very slow. Moreover directly handling the sds string as 566 | * new data is concatenated provides a performance improvement. 567 | * 568 | * However this function only handles an incompatible subset of printf-alike 569 | * format specifiers: 570 | * 571 | * %s - C String 572 | * %S - SDS string 573 | * %i - signed int 574 | * %I - 64 bit signed integer (long long, int64_t) 575 | * %u - unsigned int 576 | * %U - 64 bit unsigned integer (unsigned long long, uint64_t) 577 | * %% - Verbatim "%" character. 578 | */ 579 | sds sdscatfmt(sds s, char const *fmt, ...) { 580 | size_t initlen = sdslen(s); 581 | const char *f = fmt; 582 | int i; 583 | va_list ap; 584 | 585 | va_start(ap,fmt); 586 | f = fmt; /* Next format specifier byte to process. */ 587 | i = initlen; /* Position of the next byte to write to dest str. */ 588 | while(*f) { 589 | char next, *str; 590 | size_t l; 591 | long long num; 592 | unsigned long long unum; 593 | 594 | /* Make sure there is always space for at least 1 char. */ 595 | if (sdsavail(s)==0) { 596 | s = sdsMakeRoomFor(s,1); 597 | } 598 | 599 | switch(*f) { 600 | case '%': 601 | next = *(f+1); 602 | f++; 603 | switch(next) { 604 | case 's': 605 | case 'S': 606 | str = va_arg(ap,char*); 607 | l = (next == 's') ? strlen(str) : sdslen(str); 608 | if (sdsavail(s) < l) { 609 | s = sdsMakeRoomFor(s,l); 610 | } 611 | memcpy(s+i,str,l); 612 | sdsinclen(s,l); 613 | i += l; 614 | break; 615 | case 'i': 616 | case 'I': 617 | if (next == 'i') 618 | num = va_arg(ap,int); 619 | else 620 | num = va_arg(ap,long long); 621 | { 622 | char buf[SDS_LLSTR_SIZE]; 623 | l = sdsll2str(buf,num); 624 | if (sdsavail(s) < l) { 625 | s = sdsMakeRoomFor(s,l); 626 | } 627 | memcpy(s+i,buf,l); 628 | sdsinclen(s,l); 629 | i += l; 630 | } 631 | break; 632 | case 'u': 633 | case 'U': 634 | if (next == 'u') 635 | unum = va_arg(ap,unsigned int); 636 | else 637 | unum = va_arg(ap,unsigned long long); 638 | { 639 | char buf[SDS_LLSTR_SIZE]; 640 | l = sdsull2str(buf,unum); 641 | if (sdsavail(s) < l) { 642 | s = sdsMakeRoomFor(s,l); 643 | } 644 | memcpy(s+i,buf,l); 645 | sdsinclen(s,l); 646 | i += l; 647 | } 648 | break; 649 | default: /* Handle %% and generally %. */ 650 | s[i++] = next; 651 | sdsinclen(s,1); 652 | break; 653 | } 654 | break; 655 | default: 656 | s[i++] = *f; 657 | sdsinclen(s,1); 658 | break; 659 | } 660 | f++; 661 | } 662 | va_end(ap); 663 | 664 | /* Add null-term */ 665 | s[i] = '\0'; 666 | return s; 667 | } 668 | 669 | /* Remove the part of the string from left and from right composed just of 670 | * contiguous characters found in 'cset', that is a null terminted C string. 671 | * 672 | * After the call, the modified sds string is no longer valid and all the 673 | * references must be substituted with the new pointer returned by the call. 674 | * 675 | * Example: 676 | * 677 | * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); 678 | * s = sdstrim(s,"Aa. :"); 679 | * printf("%s\n", s); 680 | * 681 | * Output will be just "Hello World". 682 | */ 683 | sds sdstrim(sds s, const char *cset) { 684 | char *start, *end, *sp, *ep; 685 | size_t len; 686 | 687 | sp = start = s; 688 | ep = end = s+sdslen(s)-1; 689 | while(sp <= end && strchr(cset, *sp)) sp++; 690 | while(ep > sp && strchr(cset, *ep)) ep--; 691 | len = (sp > ep) ? 0 : ((ep-sp)+1); 692 | if (s != sp) memmove(s, sp, len); 693 | s[len] = '\0'; 694 | sdssetlen(s,len); 695 | return s; 696 | } 697 | 698 | /* Turn the string into a smaller (or equal) string containing only the 699 | * substring specified by the 'start' and 'end' indexes. 700 | * 701 | * start and end can be negative, where -1 means the last character of the 702 | * string, -2 the penultimate character, and so forth. 703 | * 704 | * The interval is inclusive, so the start and end characters will be part 705 | * of the resulting string. 706 | * 707 | * The string is modified in-place. 708 | * 709 | * Example: 710 | * 711 | * s = sdsnew("Hello World"); 712 | * sdsrange(s,1,-1); => "ello World" 713 | */ 714 | void sdsrange(sds s, int start, int end) { 715 | size_t newlen, len = sdslen(s); 716 | 717 | if (len == 0) return; 718 | if (start < 0) { 719 | start = len+start; 720 | if (start < 0) start = 0; 721 | } 722 | if (end < 0) { 723 | end = len+end; 724 | if (end < 0) end = 0; 725 | } 726 | newlen = (start > end) ? 0 : (end-start)+1; 727 | if (newlen != 0) { 728 | if (start >= (signed)len) { 729 | newlen = 0; 730 | } else if (end >= (signed)len) { 731 | end = len-1; 732 | newlen = (start > end) ? 0 : (end-start)+1; 733 | } 734 | } else { 735 | start = 0; 736 | } 737 | if (start && newlen) memmove(s, s+start, newlen); 738 | s[newlen] = 0; 739 | sdssetlen(s,newlen); 740 | } 741 | 742 | /* Apply tolower() to every character of the sds string 's'. */ 743 | void sdstolower(sds s) { 744 | int len = sdslen(s), j; 745 | 746 | for (j = 0; j < len; j++) s[j] = tolower(s[j]); 747 | } 748 | 749 | /* Apply toupper() to every character of the sds string 's'. */ 750 | void sdstoupper(sds s) { 751 | int len = sdslen(s), j; 752 | 753 | for (j = 0; j < len; j++) s[j] = toupper(s[j]); 754 | } 755 | 756 | /* Compare two sds strings s1 and s2 with memcmp(). 757 | * 758 | * Return value: 759 | * 760 | * positive if s1 > s2. 761 | * negative if s1 < s2. 762 | * 0 if s1 and s2 are exactly the same binary string. 763 | * 764 | * If two strings share exactly the same prefix, but one of the two has 765 | * additional characters, the longer string is considered to be greater than 766 | * the smaller one. */ 767 | int sdscmp(const sds s1, const sds s2) { 768 | size_t l1, l2, minlen; 769 | int cmp; 770 | 771 | l1 = sdslen(s1); 772 | l2 = sdslen(s2); 773 | minlen = (l1 < l2) ? l1 : l2; 774 | cmp = memcmp(s1,s2,minlen); 775 | if (cmp == 0) return l1-l2; 776 | return cmp; 777 | } 778 | 779 | /* Split 's' with separator in 'sep'. An array 780 | * of sds strings is returned. *count will be set 781 | * by reference to the number of tokens returned. 782 | * 783 | * On out of memory, zero length string, zero length 784 | * separator, NULL is returned. 785 | * 786 | * Note that 'sep' is able to split a string using 787 | * a multi-character separator. For example 788 | * sdssplit("foo_-_bar","_-_"); will return two 789 | * elements "foo" and "bar". 790 | * 791 | * This version of the function is binary-safe but 792 | * requires length arguments. sdssplit() is just the 793 | * same function but for zero-terminated strings. 794 | */ 795 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { 796 | int elements = 0, slots = 5, start = 0, j; 797 | sds *tokens; 798 | 799 | if (seplen < 1 || len < 0) return NULL; 800 | 801 | tokens = s_malloc(sizeof(sds)*slots); 802 | if (tokens == NULL) return NULL; 803 | 804 | if (len == 0) { 805 | *count = 0; 806 | return tokens; 807 | } 808 | for (j = 0; j < (len-(seplen-1)); j++) { 809 | /* make sure there is room for the next element and the final one */ 810 | if (slots < elements+2) { 811 | sds *newtokens; 812 | 813 | slots *= 2; 814 | newtokens = s_realloc(tokens,sizeof(sds)*slots); 815 | if (newtokens == NULL) goto cleanup; 816 | tokens = newtokens; 817 | } 818 | /* search the separator */ 819 | if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { 820 | tokens[elements] = sdsnewlen(s+start,j-start); 821 | if (tokens[elements] == NULL) goto cleanup; 822 | elements++; 823 | start = j+seplen; 824 | j = j+seplen-1; /* skip the separator */ 825 | } 826 | } 827 | /* Add the final element. We are sure there is room in the tokens array. */ 828 | tokens[elements] = sdsnewlen(s+start,len-start); 829 | if (tokens[elements] == NULL) goto cleanup; 830 | elements++; 831 | *count = elements; 832 | return tokens; 833 | 834 | cleanup: 835 | { 836 | int i; 837 | for (i = 0; i < elements; i++) sdsfree(tokens[i]); 838 | s_free(tokens); 839 | *count = 0; 840 | return NULL; 841 | } 842 | } 843 | 844 | /* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ 845 | void sdsfreesplitres(sds *tokens, int count) { 846 | if (!tokens) return; 847 | while(count--) 848 | sdsfree(tokens[count]); 849 | s_free(tokens); 850 | } 851 | 852 | /* Append to the sds string "s" an escaped string representation where 853 | * all the non-printable characters (tested with isprint()) are turned into 854 | * escapes in the form "\n\r\a...." or "\x". 855 | * 856 | * After the call, the modified sds string is no longer valid and all the 857 | * references must be substituted with the new pointer returned by the call. */ 858 | sds sdscatrepr(sds s, const char *p, size_t len) { 859 | s = sdscatlen(s,"\"",1); 860 | while(len--) { 861 | switch(*p) { 862 | case '\\': 863 | case '"': 864 | s = sdscatprintf(s,"\\%c",*p); 865 | break; 866 | case '\n': s = sdscatlen(s,"\\n",2); break; 867 | case '\r': s = sdscatlen(s,"\\r",2); break; 868 | case '\t': s = sdscatlen(s,"\\t",2); break; 869 | case '\a': s = sdscatlen(s,"\\a",2); break; 870 | case '\b': s = sdscatlen(s,"\\b",2); break; 871 | default: 872 | if (isprint(*p)) 873 | s = sdscatprintf(s,"%c",*p); 874 | else 875 | s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); 876 | break; 877 | } 878 | p++; 879 | } 880 | return sdscatlen(s,"\"",1); 881 | } 882 | 883 | /* Helper function for sdssplitargs() that returns non zero if 'c' 884 | * is a valid hex digit. */ 885 | int is_hex_digit(char c) { 886 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || 887 | (c >= 'A' && c <= 'F'); 888 | } 889 | 890 | /* Helper function for sdssplitargs() that converts a hex digit into an 891 | * integer from 0 to 15 */ 892 | int hex_digit_to_int(char c) { 893 | switch(c) { 894 | case '0': return 0; 895 | case '1': return 1; 896 | case '2': return 2; 897 | case '3': return 3; 898 | case '4': return 4; 899 | case '5': return 5; 900 | case '6': return 6; 901 | case '7': return 7; 902 | case '8': return 8; 903 | case '9': return 9; 904 | case 'a': case 'A': return 10; 905 | case 'b': case 'B': return 11; 906 | case 'c': case 'C': return 12; 907 | case 'd': case 'D': return 13; 908 | case 'e': case 'E': return 14; 909 | case 'f': case 'F': return 15; 910 | default: return 0; 911 | } 912 | } 913 | 914 | /* Split a line into arguments, where every argument can be in the 915 | * following programming-language REPL-alike form: 916 | * 917 | * foo bar "newline are supported\n" and "\xff\x00otherstuff" 918 | * 919 | * The number of arguments is stored into *argc, and an array 920 | * of sds is returned. 921 | * 922 | * The caller should free the resulting array of sds strings with 923 | * sdsfreesplitres(). 924 | * 925 | * Note that sdscatrepr() is able to convert back a string into 926 | * a quoted string in the same format sdssplitargs() is able to parse. 927 | * 928 | * The function returns the allocated tokens on success, even when the 929 | * input string is empty, or NULL if the input contains unbalanced 930 | * quotes or closed quotes followed by non space characters 931 | * as in: "foo"bar or "foo' 932 | */ 933 | sds *sdssplitargs(const char *line, int *argc) { 934 | const char *p = line; 935 | char *current = NULL; 936 | char **vector = NULL; 937 | 938 | *argc = 0; 939 | while(1) { 940 | /* skip blanks */ 941 | while(*p && isspace(*p)) p++; 942 | if (*p) { 943 | /* get a token */ 944 | int inq=0; /* set to 1 if we are in "quotes" */ 945 | int insq=0; /* set to 1 if we are in 'single quotes' */ 946 | int done=0; 947 | 948 | if (current == NULL) current = sdsempty(); 949 | while(!done) { 950 | if (inq) { 951 | if (*p == '\\' && *(p+1) == 'x' && 952 | is_hex_digit(*(p+2)) && 953 | is_hex_digit(*(p+3))) 954 | { 955 | unsigned char byte; 956 | 957 | byte = (hex_digit_to_int(*(p+2))*16)+ 958 | hex_digit_to_int(*(p+3)); 959 | current = sdscatlen(current,(char*)&byte,1); 960 | p += 3; 961 | } else if (*p == '\\' && *(p+1)) { 962 | char c; 963 | 964 | p++; 965 | switch(*p) { 966 | case 'n': c = '\n'; break; 967 | case 'r': c = '\r'; break; 968 | case 't': c = '\t'; break; 969 | case 'b': c = '\b'; break; 970 | case 'a': c = '\a'; break; 971 | default: c = *p; break; 972 | } 973 | current = sdscatlen(current,&c,1); 974 | } else if (*p == '"') { 975 | /* closing quote must be followed by a space or 976 | * nothing at all. */ 977 | if (*(p+1) && !isspace(*(p+1))) goto err; 978 | done=1; 979 | } else if (!*p) { 980 | /* unterminated quotes */ 981 | goto err; 982 | } else { 983 | current = sdscatlen(current,p,1); 984 | } 985 | } else if (insq) { 986 | if (*p == '\\' && *(p+1) == '\'') { 987 | p++; 988 | current = sdscatlen(current,"'",1); 989 | } else if (*p == '\'') { 990 | /* closing quote must be followed by a space or 991 | * nothing at all. */ 992 | if (*(p+1) && !isspace(*(p+1))) goto err; 993 | done=1; 994 | } else if (!*p) { 995 | /* unterminated quotes */ 996 | goto err; 997 | } else { 998 | current = sdscatlen(current,p,1); 999 | } 1000 | } else { 1001 | switch(*p) { 1002 | case ' ': 1003 | case '\n': 1004 | case '\r': 1005 | case '\t': 1006 | case '\0': 1007 | done=1; 1008 | break; 1009 | case '"': 1010 | inq=1; 1011 | break; 1012 | case '\'': 1013 | insq=1; 1014 | break; 1015 | default: 1016 | current = sdscatlen(current,p,1); 1017 | break; 1018 | } 1019 | } 1020 | if (*p) p++; 1021 | } 1022 | /* add the token to the vector */ 1023 | vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); 1024 | vector[*argc] = current; 1025 | (*argc)++; 1026 | current = NULL; 1027 | } else { 1028 | /* Even on empty input string return something not NULL. */ 1029 | if (vector == NULL) vector = s_malloc(sizeof(void*)); 1030 | return vector; 1031 | } 1032 | } 1033 | 1034 | err: 1035 | while((*argc)--) 1036 | sdsfree(vector[*argc]); 1037 | s_free(vector); 1038 | if (current) sdsfree(current); 1039 | *argc = 0; 1040 | return NULL; 1041 | } 1042 | 1043 | /* Modify the string substituting all the occurrences of the set of 1044 | * characters specified in the 'from' string to the corresponding character 1045 | * in the 'to' array. 1046 | * 1047 | * For instance: sdsmapchars(mystring, "ho", "01", 2) 1048 | * will have the effect of turning the string "hello" into "0ell1". 1049 | * 1050 | * The function returns the sds string pointer, that is always the same 1051 | * as the input pointer since no resize is needed. */ 1052 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { 1053 | size_t j, i, l = sdslen(s); 1054 | 1055 | for (j = 0; j < l; j++) { 1056 | for (i = 0; i < setlen; i++) { 1057 | if (s[j] == from[i]) { 1058 | s[j] = to[i]; 1059 | break; 1060 | } 1061 | } 1062 | } 1063 | return s; 1064 | } 1065 | 1066 | /* Join an array of C strings using the specified separator (also a C string). 1067 | * Returns the result as an sds string. */ 1068 | sds sdsjoin(char **argv, int argc, char *sep) { 1069 | sds join = sdsempty(); 1070 | int j; 1071 | 1072 | for (j = 0; j < argc; j++) { 1073 | join = sdscat(join, argv[j]); 1074 | if (j != argc-1) join = sdscat(join,sep); 1075 | } 1076 | return join; 1077 | } 1078 | 1079 | /* Like sdsjoin, but joins an array of SDS strings. */ 1080 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { 1081 | sds join = sdsempty(); 1082 | int j; 1083 | 1084 | for (j = 0; j < argc; j++) { 1085 | join = sdscatsds(join, argv[j]); 1086 | if (j != argc-1) join = sdscatlen(join,sep,seplen); 1087 | } 1088 | return join; 1089 | } 1090 | 1091 | /* Wrappers to the allocators used by SDS. Note that SDS will actually 1092 | * just use the macros defined into sdsalloc.h in order to avoid to pay 1093 | * the overhead of function calls. Here we define these wrappers only for 1094 | * the programs SDS is linked to, if they want to touch the SDS internals 1095 | * even if they use a different allocator. */ 1096 | void *sds_malloc(size_t size) { return s_malloc(size); } 1097 | void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } 1098 | void sds_free(void *ptr) { s_free(ptr); } 1099 | 1100 | #if defined(SDS_TEST_MAIN) 1101 | #include 1102 | #include "testhelp.h" 1103 | #include "limits.h" 1104 | 1105 | #define UNUSED(x) (void)(x) 1106 | int sdsTest(void) { 1107 | { 1108 | sds x = sdsnew("foo"), y; 1109 | 1110 | test_cond("Create a string and obtain the length", 1111 | sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) 1112 | 1113 | sdsfree(x); 1114 | x = sdsnewlen("foo",2); 1115 | test_cond("Create a string with specified length", 1116 | sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) 1117 | 1118 | x = sdscat(x,"bar"); 1119 | test_cond("Strings concatenation", 1120 | sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); 1121 | 1122 | x = sdscpy(x,"a"); 1123 | test_cond("sdscpy() against an originally longer string", 1124 | sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) 1125 | 1126 | x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); 1127 | test_cond("sdscpy() against an originally shorter string", 1128 | sdslen(x) == 33 && 1129 | memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) 1130 | 1131 | sdsfree(x); 1132 | x = sdscatprintf(sdsempty(),"%d",123); 1133 | test_cond("sdscatprintf() seems working in the base case", 1134 | sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) 1135 | 1136 | sdsfree(x); 1137 | x = sdsnew("--"); 1138 | x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); 1139 | test_cond("sdscatfmt() seems working in the base case", 1140 | sdslen(x) == 60 && 1141 | memcmp(x,"--Hello Hi! World -9223372036854775808," 1142 | "9223372036854775807--",60) == 0) 1143 | printf("[%s]\n",x); 1144 | 1145 | sdsfree(x); 1146 | x = sdsnew("--"); 1147 | x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); 1148 | test_cond("sdscatfmt() seems working with unsigned numbers", 1149 | sdslen(x) == 35 && 1150 | memcmp(x,"--4294967295,18446744073709551615--",35) == 0) 1151 | 1152 | sdsfree(x); 1153 | x = sdsnew(" x "); 1154 | sdstrim(x," x"); 1155 | test_cond("sdstrim() works when all chars match", 1156 | sdslen(x) == 0) 1157 | 1158 | sdsfree(x); 1159 | x = sdsnew(" x "); 1160 | sdstrim(x," "); 1161 | test_cond("sdstrim() works when a single char remains", 1162 | sdslen(x) == 1 && x[0] == 'x') 1163 | 1164 | sdsfree(x); 1165 | x = sdsnew("xxciaoyyy"); 1166 | sdstrim(x,"xy"); 1167 | test_cond("sdstrim() correctly trims characters", 1168 | sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) 1169 | 1170 | y = sdsdup(x); 1171 | sdsrange(y,1,1); 1172 | test_cond("sdsrange(...,1,1)", 1173 | sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) 1174 | 1175 | sdsfree(y); 1176 | y = sdsdup(x); 1177 | sdsrange(y,1,-1); 1178 | test_cond("sdsrange(...,1,-1)", 1179 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) 1180 | 1181 | sdsfree(y); 1182 | y = sdsdup(x); 1183 | sdsrange(y,-2,-1); 1184 | test_cond("sdsrange(...,-2,-1)", 1185 | sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) 1186 | 1187 | sdsfree(y); 1188 | y = sdsdup(x); 1189 | sdsrange(y,2,1); 1190 | test_cond("sdsrange(...,2,1)", 1191 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0) 1192 | 1193 | sdsfree(y); 1194 | y = sdsdup(x); 1195 | sdsrange(y,1,100); 1196 | test_cond("sdsrange(...,1,100)", 1197 | sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) 1198 | 1199 | sdsfree(y); 1200 | y = sdsdup(x); 1201 | sdsrange(y,100,100); 1202 | test_cond("sdsrange(...,100,100)", 1203 | sdslen(y) == 0 && memcmp(y,"\0",1) == 0) 1204 | 1205 | sdsfree(y); 1206 | sdsfree(x); 1207 | x = sdsnew("foo"); 1208 | y = sdsnew("foa"); 1209 | test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) 1210 | 1211 | sdsfree(y); 1212 | sdsfree(x); 1213 | x = sdsnew("bar"); 1214 | y = sdsnew("bar"); 1215 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) 1216 | 1217 | sdsfree(y); 1218 | sdsfree(x); 1219 | x = sdsnew("aar"); 1220 | y = sdsnew("bar"); 1221 | test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) 1222 | 1223 | sdsfree(y); 1224 | sdsfree(x); 1225 | x = sdsnewlen("\a\n\0foo\r",7); 1226 | y = sdscatrepr(sdsempty(),x,sdslen(x)); 1227 | test_cond("sdscatrepr(...data...)", 1228 | memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) 1229 | 1230 | { 1231 | unsigned int oldfree; 1232 | char *p; 1233 | int step = 10, j, i; 1234 | 1235 | sdsfree(x); 1236 | sdsfree(y); 1237 | x = sdsnew("0"); 1238 | test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); 1239 | 1240 | /* Run the test a few times in order to hit the first two 1241 | * SDS header types. */ 1242 | for (i = 0; i < 10; i++) { 1243 | int oldlen = sdslen(x); 1244 | x = sdsMakeRoomFor(x,step); 1245 | int type = x[-1]&SDS_TYPE_MASK; 1246 | 1247 | test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); 1248 | if (type != SDS_TYPE_5) { 1249 | test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); 1250 | oldfree = sdsavail(x); 1251 | } 1252 | p = x+oldlen; 1253 | for (j = 0; j < step; j++) { 1254 | p[j] = 'A'+j; 1255 | } 1256 | sdsIncrLen(x,step); 1257 | } 1258 | test_cond("sdsMakeRoomFor() content", 1259 | memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); 1260 | test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); 1261 | 1262 | sdsfree(x); 1263 | } 1264 | } 1265 | test_report() 1266 | return 0; 1267 | } 1268 | #endif 1269 | 1270 | #ifdef SDS_TEST_MAIN 1271 | int main(void) { 1272 | return sdsTest(); 1273 | } 1274 | #endif 1275 | -------------------------------------------------------------------------------- /rmutil/sds.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __SDS_H 34 | #define __SDS_H 35 | 36 | #define SDS_MAX_PREALLOC (1024*1024) 37 | 38 | #include 39 | #include 40 | #include 41 | 42 | typedef char *sds; 43 | 44 | /* Note: sdshdr5 is never used, we just access the flags byte directly. 45 | * However is here to document the layout of type 5 SDS strings. */ 46 | struct __attribute__ ((__packed__)) sdshdr5 { 47 | unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ 48 | char buf[]; 49 | }; 50 | struct __attribute__ ((__packed__)) sdshdr8 { 51 | uint8_t len; /* used */ 52 | uint8_t alloc; /* excluding the header and null terminator */ 53 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 54 | char buf[]; 55 | }; 56 | struct __attribute__ ((__packed__)) sdshdr16 { 57 | uint16_t len; /* used */ 58 | uint16_t alloc; /* excluding the header and null terminator */ 59 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 60 | char buf[]; 61 | }; 62 | struct __attribute__ ((__packed__)) sdshdr32 { 63 | uint32_t len; /* used */ 64 | uint32_t alloc; /* excluding the header and null terminator */ 65 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 66 | char buf[]; 67 | }; 68 | struct __attribute__ ((__packed__)) sdshdr64 { 69 | uint64_t len; /* used */ 70 | uint64_t alloc; /* excluding the header and null terminator */ 71 | unsigned char flags; /* 3 lsb of type, 5 unused bits */ 72 | char buf[]; 73 | }; 74 | 75 | #define SDS_TYPE_5 0 76 | #define SDS_TYPE_8 1 77 | #define SDS_TYPE_16 2 78 | #define SDS_TYPE_32 3 79 | #define SDS_TYPE_64 4 80 | #define SDS_TYPE_MASK 7 81 | #define SDS_TYPE_BITS 3 82 | #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T))); 83 | #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) 84 | #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) 85 | 86 | static inline size_t sdslen(const sds s) { 87 | unsigned char flags = s[-1]; 88 | switch(flags&SDS_TYPE_MASK) { 89 | case SDS_TYPE_5: 90 | return SDS_TYPE_5_LEN(flags); 91 | case SDS_TYPE_8: 92 | return SDS_HDR(8,s)->len; 93 | case SDS_TYPE_16: 94 | return SDS_HDR(16,s)->len; 95 | case SDS_TYPE_32: 96 | return SDS_HDR(32,s)->len; 97 | case SDS_TYPE_64: 98 | return SDS_HDR(64,s)->len; 99 | } 100 | return 0; 101 | } 102 | 103 | static inline size_t sdsavail(const sds s) { 104 | unsigned char flags = s[-1]; 105 | switch(flags&SDS_TYPE_MASK) { 106 | case SDS_TYPE_5: { 107 | return 0; 108 | } 109 | case SDS_TYPE_8: { 110 | SDS_HDR_VAR(8,s); 111 | return sh->alloc - sh->len; 112 | } 113 | case SDS_TYPE_16: { 114 | SDS_HDR_VAR(16,s); 115 | return sh->alloc - sh->len; 116 | } 117 | case SDS_TYPE_32: { 118 | SDS_HDR_VAR(32,s); 119 | return sh->alloc - sh->len; 120 | } 121 | case SDS_TYPE_64: { 122 | SDS_HDR_VAR(64,s); 123 | return sh->alloc - sh->len; 124 | } 125 | } 126 | return 0; 127 | } 128 | 129 | static inline void sdssetlen(sds s, size_t newlen) { 130 | unsigned char flags = s[-1]; 131 | switch(flags&SDS_TYPE_MASK) { 132 | case SDS_TYPE_5: 133 | { 134 | unsigned char *fp = ((unsigned char*)s)-1; 135 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 136 | } 137 | break; 138 | case SDS_TYPE_8: 139 | SDS_HDR(8,s)->len = newlen; 140 | break; 141 | case SDS_TYPE_16: 142 | SDS_HDR(16,s)->len = newlen; 143 | break; 144 | case SDS_TYPE_32: 145 | SDS_HDR(32,s)->len = newlen; 146 | break; 147 | case SDS_TYPE_64: 148 | SDS_HDR(64,s)->len = newlen; 149 | break; 150 | } 151 | } 152 | 153 | static inline void sdsinclen(sds s, size_t inc) { 154 | unsigned char flags = s[-1]; 155 | switch(flags&SDS_TYPE_MASK) { 156 | case SDS_TYPE_5: 157 | { 158 | unsigned char *fp = ((unsigned char*)s)-1; 159 | unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc; 160 | *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); 161 | } 162 | break; 163 | case SDS_TYPE_8: 164 | SDS_HDR(8,s)->len += inc; 165 | break; 166 | case SDS_TYPE_16: 167 | SDS_HDR(16,s)->len += inc; 168 | break; 169 | case SDS_TYPE_32: 170 | SDS_HDR(32,s)->len += inc; 171 | break; 172 | case SDS_TYPE_64: 173 | SDS_HDR(64,s)->len += inc; 174 | break; 175 | } 176 | } 177 | 178 | /* sdsalloc() = sdsavail() + sdslen() */ 179 | static inline size_t sdsalloc(const sds s) { 180 | unsigned char flags = s[-1]; 181 | switch(flags&SDS_TYPE_MASK) { 182 | case SDS_TYPE_5: 183 | return SDS_TYPE_5_LEN(flags); 184 | case SDS_TYPE_8: 185 | return SDS_HDR(8,s)->alloc; 186 | case SDS_TYPE_16: 187 | return SDS_HDR(16,s)->alloc; 188 | case SDS_TYPE_32: 189 | return SDS_HDR(32,s)->alloc; 190 | case SDS_TYPE_64: 191 | return SDS_HDR(64,s)->alloc; 192 | } 193 | return 0; 194 | } 195 | 196 | static inline void sdssetalloc(sds s, size_t newlen) { 197 | unsigned char flags = s[-1]; 198 | switch(flags&SDS_TYPE_MASK) { 199 | case SDS_TYPE_5: 200 | /* Nothing to do, this type has no total allocation info. */ 201 | break; 202 | case SDS_TYPE_8: 203 | SDS_HDR(8,s)->alloc = newlen; 204 | break; 205 | case SDS_TYPE_16: 206 | SDS_HDR(16,s)->alloc = newlen; 207 | break; 208 | case SDS_TYPE_32: 209 | SDS_HDR(32,s)->alloc = newlen; 210 | break; 211 | case SDS_TYPE_64: 212 | SDS_HDR(64,s)->alloc = newlen; 213 | break; 214 | } 215 | } 216 | 217 | sds sdsnewlen(const void *init, size_t initlen); 218 | sds sdsnew(const char *init); 219 | sds sdsempty(void); 220 | sds sdsdup(const sds s); 221 | void sdsfree(sds s); 222 | sds sdsgrowzero(sds s, size_t len); 223 | sds sdscatlen(sds s, const void *t, size_t len); 224 | sds sdscat(sds s, const char *t); 225 | sds sdscatsds(sds s, const sds t); 226 | sds sdscpylen(sds s, const char *t, size_t len); 227 | sds sdscpy(sds s, const char *t); 228 | 229 | sds sdscatvprintf(sds s, const char *fmt, va_list ap); 230 | #ifdef __GNUC__ 231 | sds sdscatprintf(sds s, const char *fmt, ...) 232 | __attribute__((format(printf, 2, 3))); 233 | #else 234 | sds sdscatprintf(sds s, const char *fmt, ...); 235 | #endif 236 | 237 | sds sdscatfmt(sds s, char const *fmt, ...); 238 | sds sdstrim(sds s, const char *cset); 239 | void sdsrange(sds s, int start, int end); 240 | void sdsupdatelen(sds s); 241 | void sdsclear(sds s); 242 | int sdscmp(const sds s1, const sds s2); 243 | sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); 244 | void sdsfreesplitres(sds *tokens, int count); 245 | void sdstolower(sds s); 246 | void sdstoupper(sds s); 247 | sds sdsfromlonglong(long long value); 248 | sds sdscatrepr(sds s, const char *p, size_t len); 249 | sds *sdssplitargs(const char *line, int *argc); 250 | sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); 251 | sds sdsjoin(char **argv, int argc, char *sep); 252 | sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); 253 | 254 | /* Low level functions exposed to the user API */ 255 | sds sdsMakeRoomFor(sds s, size_t addlen); 256 | void sdsIncrLen(sds s, int incr); 257 | sds sdsRemoveFreeSpace(sds s); 258 | size_t sdsAllocSize(sds s); 259 | void *sdsAllocPtr(sds s); 260 | 261 | /* Export the allocator used by SDS to the program using SDS. 262 | * Sometimes the program SDS is linked to, may use a different set of 263 | * allocators, but may want to allocate or free things that SDS will 264 | * respectively free or allocate. */ 265 | void *sds_malloc(size_t size); 266 | void *sds_realloc(void *ptr, size_t size); 267 | void sds_free(void *ptr); 268 | 269 | #ifdef REDIS_TEST 270 | int sdsTest(int argc, char *argv[]); 271 | #endif 272 | 273 | #endif 274 | -------------------------------------------------------------------------------- /rmutil/sdsalloc.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Redis Labs, Inc 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * * Redistributions of source code must retain the above copyright notice, 11 | * this list of conditions and the following disclaimer. 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * * Neither the name of Redis nor the names of its contributors may be used 16 | * to endorse or promote products derived from this software without 17 | * specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | /* SDS allocator selection. 33 | * 34 | * This file is used in order to change the SDS allocator at compile time. 35 | * Just define the following defines to what you want to use. Also add 36 | * the include of your alternate allocator if needed (not needed in order 37 | * to use the default libc allocator). */ 38 | 39 | #if defined(__MACH__) 40 | #include 41 | #else 42 | #include 43 | #endif 44 | //#include "zmalloc.h" 45 | #define s_malloc malloc 46 | #define s_realloc realloc 47 | #define s_free free 48 | -------------------------------------------------------------------------------- /rmutil/strings.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "strings.h" 5 | #include "alloc.h" 6 | 7 | #include "sds.h" 8 | 9 | // RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...) { 10 | // sds s = sdsempty(); 11 | 12 | // va_list ap; 13 | // va_start(ap, fmt); 14 | // s = sdscatvprintf(s, fmt, ap); 15 | // va_end(ap); 16 | 17 | // RedisModuleString *ret = RedisModule_CreateString(ctx, (const char *)s, sdslen(s)); 18 | // sdsfree(s); 19 | // return ret; 20 | // } 21 | 22 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2) { 23 | 24 | const char *c1, *c2; 25 | size_t l1, l2; 26 | c1 = RedisModule_StringPtrLen(s1, &l1); 27 | c2 = RedisModule_StringPtrLen(s2, &l2); 28 | if (l1 != l2) return 0; 29 | 30 | return strncmp(c1, c2, l1) == 0; 31 | } 32 | 33 | int RMUtil_StringEqualsC(RedisModuleString *s1, const char *s2) { 34 | 35 | const char *c1; 36 | size_t l1, l2 = strlen(s2); 37 | c1 = RedisModule_StringPtrLen(s1, &l1); 38 | if (l1 != l2) return 0; 39 | 40 | return strncmp(c1, s2, l1) == 0; 41 | } 42 | int RMUtil_StringEqualsCaseC(RedisModuleString *s1, const char *s2) { 43 | 44 | const char *c1; 45 | size_t l1, l2 = strlen(s2); 46 | c1 = RedisModule_StringPtrLen(s1, &l1); 47 | if (l1 != l2) return 0; 48 | 49 | return strncasecmp(c1, s2, l1) == 0; 50 | } 51 | 52 | void RMUtil_StringToLower(RedisModuleString *s) { 53 | 54 | size_t l; 55 | char *c = (char *)RedisModule_StringPtrLen(s, &l); 56 | size_t i; 57 | for (i = 0; i < l; i++) { 58 | *c = tolower(*c); 59 | ++c; 60 | } 61 | } 62 | 63 | void RMUtil_StringToUpper(RedisModuleString *s) { 64 | size_t l; 65 | char *c = (char *)RedisModule_StringPtrLen(s, &l); 66 | size_t i; 67 | for (i = 0; i < l; i++) { 68 | *c = toupper(*c); 69 | ++c; 70 | } 71 | } 72 | 73 | void RMUtil_StringConvert(RedisModuleString **rs, const char **ss, size_t n, int options) { 74 | for (size_t ii = 0; ii < n; ++ii) { 75 | const char *p = RedisModule_StringPtrLen(rs[ii], NULL); 76 | if (options & RMUTIL_STRINGCONVERT_COPY) { 77 | p = strdup(p); 78 | } 79 | ss[ii] = p; 80 | } 81 | } -------------------------------------------------------------------------------- /rmutil/strings.h: -------------------------------------------------------------------------------- 1 | #ifndef __RMUTIL_STRINGS_H__ 2 | #define __RMUTIL_STRINGS_H__ 3 | 4 | #include 5 | 6 | /* 7 | * Create a new RedisModuleString object from a printf-style format and arguments. 8 | * Note that RedisModuleString objects CANNOT be used as formatting arguments. 9 | */ 10 | // DEPRECATED since it was added to the RedisModule API. Replaced with a macro below 11 | // RedisModuleString *RMUtil_CreateFormattedString(RedisModuleCtx *ctx, const char *fmt, ...); 12 | #define RMUtil_CreateFormattedString RedisModule_CreateStringPrintf 13 | 14 | /* Return 1 if the two strings are equal. Case *sensitive* */ 15 | int RMUtil_StringEquals(RedisModuleString *s1, RedisModuleString *s2); 16 | 17 | /* Return 1 if the string is equal to a C NULL terminated string. Case *sensitive* */ 18 | int RMUtil_StringEqualsC(RedisModuleString *s1, const char *s2); 19 | 20 | /* Return 1 if the string is equal to a C NULL terminated string. Case *insensitive* */ 21 | int RMUtil_StringEqualsCaseC(RedisModuleString *s1, const char *s2); 22 | 23 | /* Converts a redis string to lowercase in place without reallocating anything */ 24 | void RMUtil_StringToLower(RedisModuleString *s); 25 | 26 | /* Converts a redis string to uppercase in place without reallocating anything */ 27 | void RMUtil_StringToUpper(RedisModuleString *s); 28 | 29 | // If set, copy the strings using strdup rather than simply storing pointers. 30 | #define RMUTIL_STRINGCONVERT_COPY 1 31 | 32 | /** 33 | * Convert one or more RedisModuleString objects into `const char*`. 34 | * Both rs and ss are arrays, and should be of length. 35 | * Options may be 0 or `RMUTIL_STRINGCONVERT_COPY` 36 | */ 37 | void RMUtil_StringConvert(RedisModuleString **rs, const char **ss, size_t n, int options); 38 | #endif 39 | -------------------------------------------------------------------------------- /rmutil/test.h: -------------------------------------------------------------------------------- 1 | #ifndef __TESTUTIL_H__ 2 | #define __TESTUTIL_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | static int numTests = 0; 9 | static int numAsserts = 0; 10 | 11 | #define TESTFUNC(f) \ 12 | printf(" Testing %s\t\t", __STRING(f)); \ 13 | numTests++; \ 14 | fflush(stdout); \ 15 | if (f()) { \ 16 | printf(" %s FAILED!\n", __STRING(f)); \ 17 | exit(1); \ 18 | } else \ 19 | printf("[PASS]\n"); 20 | 21 | #define ASSERTM(expr, ...) \ 22 | if (!(expr)) { \ 23 | fprintf(stderr, "%s:%d: Assertion '%s' Failed: " __VA_ARGS__ "\n", __FILE__, __LINE__, \ 24 | __STRING(expr)); \ 25 | return -1; \ 26 | } \ 27 | numAsserts++; 28 | 29 | #define ASSERT(expr) \ 30 | if (!(expr)) { \ 31 | fprintf(stderr, "%s:%d Assertion '%s' Failed\n", __FILE__, __LINE__, __STRING(expr)); \ 32 | return -1; \ 33 | } \ 34 | numAsserts++; 35 | 36 | #define ASSERT_STRING_EQ(s1, s2) ASSERT(!strcmp(s1, s2)); 37 | 38 | #define ASSERT_EQUAL(x, y, ...) \ 39 | if (x != y) { \ 40 | fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \ 41 | fprintf(stderr, "%g != %g: " __VA_ARGS__ "\n", (double)x, (double)y); \ 42 | return -1; \ 43 | } \ 44 | numAsserts++; 45 | 46 | #define FAIL(fmt, ...) \ 47 | { \ 48 | fprintf(stderr, "%s:%d: FAIL: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ 49 | return -1; \ 50 | } 51 | 52 | #define RETURN_TEST_SUCCESS return 0; 53 | #define TEST_CASE(x, block) \ 54 | int x { \ 55 | block; \ 56 | return 0 \ 57 | } 58 | 59 | #define PRINT_TEST_SUMMARY printf("\nTotal: %d tests and %d assertions OK\n", numTests, numAsserts); 60 | 61 | #define TEST_MAIN(body) \ 62 | int main(int argc, char **argv) { \ 63 | printf("Starting Test '%s'...\n", argv[0]); \ 64 | body; \ 65 | PRINT_TEST_SUMMARY; \ 66 | printf("\n--------------------\n\n"); \ 67 | return 0; \ 68 | } 69 | #endif -------------------------------------------------------------------------------- /rmutil/test_heap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "heap.h" 3 | #include "assert.h" 4 | 5 | int cmp(void *a, void *b) { 6 | int *__a = (int *) a; 7 | int *__b = (int *) b; 8 | return *__a - *__b; 9 | } 10 | 11 | int main(int argc, char **argv) { 12 | int myints[] = {10, 20, 30, 5, 15}; 13 | Vector *v = NewVector(int, 5); 14 | for (int i = 0; i < 5; i++) { 15 | Vector_Push(v, myints[i]); 16 | } 17 | 18 | Make_Heap(v, 0, v->top, cmp); 19 | 20 | int n; 21 | Vector_Get(v, 0, &n); 22 | assert(30 == n); 23 | 24 | Heap_Pop(v, 0, v->top, cmp); 25 | v->top = 4; 26 | Vector_Get(v, 0, &n); 27 | assert(20 == n); 28 | 29 | Vector_Push(v, 99); 30 | Heap_Push(v, 0, v->top, cmp); 31 | Vector_Get(v, 0, &n); 32 | assert(99 == n); 33 | 34 | Vector_Free(v); 35 | printf("PASS!\n"); 36 | return 0; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /rmutil/test_periodic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "periodic.h" 5 | #include "assert.h" 6 | #include "test.h" 7 | 8 | void timerCb(RedisModuleCtx *ctx, void *p) { 9 | int *x = p; 10 | (*x)++; 11 | } 12 | 13 | int testPeriodic() { 14 | int x = 0; 15 | struct RMUtilTimer *tm = RMUtil_NewPeriodicTimer( 16 | timerCb, NULL, &x, (struct timespec){.tv_sec = 0, .tv_nsec = 10000000}); 17 | 18 | sleep(1); 19 | 20 | ASSERT_EQUAL(0, RMUtilTimer_Terminate(tm)); 21 | ASSERT(x > 0); 22 | ASSERT(x <= 100); 23 | return 0; 24 | } 25 | 26 | TEST_MAIN({ TESTFUNC(testPeriodic); }); 27 | -------------------------------------------------------------------------------- /rmutil/test_priority_queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "assert.h" 3 | #include "priority_queue.h" 4 | 5 | int cmp(void* i1, void* i2) { 6 | int *__i1 = (int*) i1; 7 | int *__i2 = (int*) i2; 8 | return *__i1 - *__i2; 9 | } 10 | 11 | int main(int argc, char **argv) { 12 | PriorityQueue *pq = NewPriorityQueue(int, 10, cmp); 13 | assert(0 == Priority_Queue_Size(pq)); 14 | 15 | for (int i = 0; i < 5; i++) { 16 | Priority_Queue_Push(pq, i); 17 | } 18 | assert(5 == Priority_Queue_Size(pq)); 19 | 20 | Priority_Queue_Pop(pq); 21 | assert(4 == Priority_Queue_Size(pq)); 22 | 23 | Priority_Queue_Push(pq, 10); 24 | Priority_Queue_Push(pq, 20); 25 | Priority_Queue_Push(pq, 15); 26 | int n; 27 | Priority_Queue_Top(pq, &n); 28 | assert(20 == n); 29 | 30 | Priority_Queue_Pop(pq); 31 | Priority_Queue_Top(pq, &n); 32 | assert(15 == n); 33 | 34 | Priority_Queue_Free(pq); 35 | printf("PASS!\n"); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /rmutil/test_util.h: -------------------------------------------------------------------------------- 1 | #ifndef __TEST_UTIL_H__ 2 | #define __TEST_UTIL_H__ 3 | 4 | #include "util.h" 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | #define RMUtil_Test(f) \ 11 | if (argc < 2 || RMUtil_ArgExists(__STRING(f), argv, argc, 1)) { \ 12 | int rc = f(ctx); \ 13 | if (rc != REDISMODULE_OK) { \ 14 | RedisModule_ReplyWithError(ctx, "Test " __STRING(f) " FAILED"); \ 15 | return REDISMODULE_ERR;\ 16 | }\ 17 | } 18 | 19 | 20 | #define RMUtil_Assert(expr) if (!(expr)) { fprintf (stderr, "Assertion '%s' Failed\n", __STRING(expr)); return REDISMODULE_ERR; } 21 | 22 | #define RMUtil_AssertNullReply(rep) RMUtil_Assert( \ 23 | RedisModule_CallReplyType(rep) == REDISMODULE_REPLY_NULL || RedisModule_CreateStringFromCallReply(rep) == NULL) 24 | 25 | #define RMUtil_AssertReplyEquals(rep, cstr) RMUtil_Assert( \ 26 | RMUtil_StringEquals(RedisModule_CreateStringFromCallReply(rep), RedisModule_CreateString(ctx, cstr, strlen(cstr))) \ 27 | ) 28 | # 29 | 30 | /** 31 | * Create an arg list to pass to a redis command handler manually, based on the format in fmt. 32 | * The accepted format specifiers are: 33 | * c - for null terminated c strings 34 | * s - for RedisModuleString* objects 35 | * l - for longs 36 | * 37 | * Example: RMUtil_MakeArgs(ctx, &argc, "clc", "hello", 1337, "world"); 38 | * 39 | * Returns an array of RedisModuleString pointers. The size of the array is store in argcp 40 | */ 41 | RedisModuleString **RMUtil_MakeArgs(RedisModuleCtx *ctx, int *argcp, const char *fmt, ...) { 42 | 43 | va_list ap; 44 | va_start(ap, fmt); 45 | RedisModuleString **argv = calloc(strlen(fmt), sizeof(RedisModuleString*)); 46 | int argc = 0; 47 | const char *p = fmt; 48 | while(*p) { 49 | if (*p == 'c') { 50 | char *cstr = va_arg(ap,char*); 51 | argv[argc++] = RedisModule_CreateString(ctx, cstr, strlen(cstr)); 52 | } else if (*p == 's') { 53 | argv[argc++] = va_arg(ap,void*);; 54 | } else if (*p == 'l') { 55 | long ll = va_arg(ap,long long); 56 | argv[argc++] = RedisModule_CreateStringFromLongLong(ctx, ll); 57 | } else { 58 | goto fmterr; 59 | } 60 | p++; 61 | } 62 | *argcp = argc; 63 | 64 | return argv; 65 | fmterr: 66 | free(argv); 67 | return NULL; 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /rmutil/test_vector.c: -------------------------------------------------------------------------------- 1 | #include "vector.h" 2 | #include 3 | #include "test.h" 4 | 5 | int testVector() { 6 | 7 | Vector *v = NewVector(int, 1); 8 | ASSERT(v != NULL); 9 | // Vector_Put(v, 0, 1); 10 | // Vector_Put(v, 1, 3); 11 | for (int i = 0; i < 10; i++) { 12 | Vector_Push(v, i); 13 | } 14 | ASSERT_EQUAL(10, Vector_Size(v)); 15 | ASSERT_EQUAL(16, Vector_Cap(v)); 16 | 17 | for (int i = 0; i < Vector_Size(v); i++) { 18 | int n; 19 | int rc = Vector_Get(v, i, &n); 20 | ASSERT_EQUAL(1, rc); 21 | // printf("%d %d\n", rc, n); 22 | 23 | ASSERT_EQUAL(n, i); 24 | } 25 | 26 | Vector_Free(v); 27 | 28 | v = NewVector(char *, 0); 29 | int N = 4; 30 | char *strings[4] = {"hello", "world", "foo", "bar"}; 31 | 32 | for (int i = 0; i < N; i++) { 33 | Vector_Push(v, strings[i]); 34 | } 35 | ASSERT_EQUAL(N, Vector_Size(v)); 36 | ASSERT(Vector_Cap(v) >= N); 37 | 38 | for (int i = 0; i < Vector_Size(v); i++) { 39 | char *x; 40 | int rc = Vector_Get(v, i, &x); 41 | ASSERT_EQUAL(1, rc); 42 | ASSERT_STRING_EQ(x, strings[i]); 43 | } 44 | 45 | int rc = Vector_Get(v, 100, NULL); 46 | ASSERT_EQUAL(0, rc); 47 | 48 | Vector_Free(v); 49 | 50 | return 0; 51 | // Vector_Push(v, "hello"); 52 | // Vector_Push(v, "world"); 53 | // char *x = NULL; 54 | // int rc = Vector_Getx(v, 0, &x); 55 | // printf("rc: %d got %s\n", rc, x); 56 | } 57 | 58 | TEST_MAIN({ TESTFUNC(testVector); }); 59 | -------------------------------------------------------------------------------- /rmutil/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #define REDISMODULE_EXPERIMENTAL_API 10 | #include 11 | #include "util.h" 12 | 13 | /** 14 | Check if an argument exists in an argument list (argv,argc), starting at offset. 15 | @return 0 if it doesn't exist, otherwise the offset it exists in 16 | */ 17 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset) { 18 | 19 | size_t larg = strlen(arg); 20 | for (; offset < argc; offset++) { 21 | size_t l; 22 | const char *carg = RedisModule_StringPtrLen(argv[offset], &l); 23 | if (l != larg) continue; 24 | if (carg != NULL && strncasecmp(carg, arg, larg) == 0) { 25 | return offset; 26 | } 27 | } 28 | return 0; 29 | } 30 | 31 | /** 32 | Check if an argument exists in an argument list (argv,argc) 33 | @return -1 if it doesn't exist, otherwise the offset it exists in 34 | */ 35 | int RMUtil_ArgIndex(const char *arg, RedisModuleString **argv, int argc) { 36 | 37 | size_t larg = strlen(arg); 38 | for (int offset = 0; offset < argc; offset++) { 39 | size_t l; 40 | const char *carg = RedisModule_StringPtrLen(argv[offset], &l); 41 | if (l != larg) continue; 42 | if (carg != NULL && strncasecmp(carg, arg, larg) == 0) { 43 | return offset; 44 | } 45 | } 46 | return -1; 47 | } 48 | 49 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx) { 50 | 51 | RedisModuleCallReply *r = RedisModule_Call(ctx, "INFO", "c", "all"); 52 | if (r == NULL || RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { 53 | return NULL; 54 | } 55 | 56 | int cap = 100; // rough estimate of info lines 57 | RMUtilInfo *info = malloc(sizeof(RMUtilInfo)); 58 | info->entries = calloc(cap, sizeof(RMUtilInfoEntry)); 59 | 60 | int i = 0; 61 | size_t sz; 62 | char *text = (char *)RedisModule_CallReplyStringPtr(r, &sz); 63 | 64 | char *line = text; 65 | while (line && line < text + sz) { 66 | char *line = strsep(&text, "\r\n"); 67 | if (line == NULL) break; 68 | 69 | if (!(*line >= 'a' && *line <= 'z')) { // skip non entry lines 70 | continue; 71 | } 72 | 73 | char *key = strsep(&line, ":"); 74 | info->entries[i].key = strdup(key); 75 | info->entries[i].val = strdup(line); 76 | i++; 77 | if (i >= cap) { 78 | cap *= 2; 79 | info->entries = realloc(info->entries, cap * sizeof(RMUtilInfoEntry)); 80 | } 81 | } 82 | info->numEntries = i; 83 | RedisModule_FreeCallReply(r); 84 | return info; 85 | } 86 | void RMUtilRedisInfo_Free(RMUtilInfo *info) { 87 | for (int i = 0; i < info->numEntries; i++) { 88 | free(info->entries[i].key); 89 | free(info->entries[i].val); 90 | } 91 | free(info->entries); 92 | free(info); 93 | } 94 | 95 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val) { 96 | 97 | const char *p = NULL; 98 | if (!RMUtilInfo_GetString(info, key, &p)) { 99 | return 0; 100 | } 101 | 102 | *val = strtoll(p, NULL, 10); 103 | if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN)) || (errno != 0 && *val == 0)) { 104 | *val = -1; 105 | return 0; 106 | } 107 | 108 | return 1; 109 | } 110 | 111 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str) { 112 | int i; 113 | for (i = 0; i < info->numEntries; i++) { 114 | if (!strcmp(key, info->entries[i].key)) { 115 | *str = info->entries[i].val; 116 | return 1; 117 | } 118 | } 119 | return 0; 120 | } 121 | 122 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d) { 123 | const char *p = NULL; 124 | if (!RMUtilInfo_GetString(info, key, &p)) { 125 | printf("not found %s\n", key); 126 | return 0; 127 | } 128 | 129 | *d = strtod(p, NULL); 130 | if ((errno == ERANGE && (*d == HUGE_VAL || *d == -HUGE_VAL)) || (errno != 0 && *d == 0)) { 131 | return 0; 132 | } 133 | 134 | return 1; 135 | } 136 | 137 | /* 138 | c -- pointer to a Null terminated C string pointer. 139 | b -- pointer to a C buffer, followed by pointer to a size_t for its length 140 | s -- pointer to a RedisModuleString 141 | l -- pointer to Long long integer. 142 | d -- pointer to a Double 143 | * -- do not parse this argument at all 144 | */ 145 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...) { 146 | va_list ap; 147 | va_start(ap, fmt); 148 | int rc = rmutil_vparseArgs(argv, argc, offset, fmt, ap); 149 | va_end(ap); 150 | return rc; 151 | } 152 | 153 | // Internal function that parses arguments based on the format described above 154 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap) { 155 | 156 | int i = offset; 157 | char *c = (char *)fmt; 158 | while (*c && i < argc) { 159 | 160 | // read c string 161 | if (*c == 'c') { 162 | char **p = va_arg(ap, char **); 163 | *p = (char *)RedisModule_StringPtrLen(argv[i], NULL); 164 | } else if (*c == 'b') { 165 | char **p = va_arg(ap, char **); 166 | size_t *len = va_arg(ap, size_t *); 167 | *p = (char *)RedisModule_StringPtrLen(argv[i], len); 168 | } else if (*c == 's') { // read redis string 169 | 170 | RedisModuleString **s = va_arg(ap, void *); 171 | *s = argv[i]; 172 | 173 | } else if (*c == 'l') { // read long 174 | long long *l = va_arg(ap, long long *); 175 | 176 | if (RedisModule_StringToLongLong(argv[i], l) != REDISMODULE_OK) { 177 | return REDISMODULE_ERR; 178 | } 179 | } else if (*c == 'd') { // read double 180 | double *d = va_arg(ap, double *); 181 | if (RedisModule_StringToDouble(argv[i], d) != REDISMODULE_OK) { 182 | return REDISMODULE_ERR; 183 | } 184 | } else if (*c == '*') { // skip current arg 185 | // do nothing 186 | } else { 187 | return REDISMODULE_ERR; // WAT? 188 | } 189 | c++; 190 | i++; 191 | } 192 | // if the format is longer than argc, retun an error 193 | if (*c != 0) { 194 | return REDISMODULE_ERR; 195 | } 196 | return REDISMODULE_OK; 197 | } 198 | 199 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, 200 | ...) { 201 | 202 | int pos = RMUtil_ArgIndex(token, argv, argc); 203 | if (pos < 0) { 204 | return REDISMODULE_ERR; 205 | } 206 | 207 | va_list ap; 208 | va_start(ap, fmt); 209 | int rc = rmutil_vparseArgs(argv, argc, pos + 1, fmt, ap); 210 | va_end(ap); 211 | return rc; 212 | } 213 | 214 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath(RedisModuleCallReply *rep, 215 | const char *path) { 216 | if (rep == NULL) return NULL; 217 | 218 | RedisModuleCallReply *ele = rep; 219 | const char *s = path; 220 | char *e; 221 | long idx; 222 | do { 223 | errno = 0; 224 | idx = strtol(s, &e, 10); 225 | 226 | if ((errno == ERANGE && (idx == LONG_MAX || idx == LONG_MIN)) || (errno != 0 && idx == 0) || 227 | (REDISMODULE_REPLY_ARRAY != RedisModule_CallReplyType(ele)) || (s == e)) { 228 | ele = NULL; 229 | break; 230 | } 231 | s = e; 232 | ele = RedisModule_CallReplyArrayElement(ele, idx - 1); 233 | 234 | } while ((ele != NULL) && (*e != '\0')); 235 | 236 | return ele; 237 | } 238 | 239 | int RedisModule_TryGetValue(RedisModuleKey *key, const RedisModuleType *type, void **out) { 240 | if (key == NULL) { 241 | return RMUTIL_VALUE_MISSING; 242 | } 243 | int keytype = RedisModule_KeyType(key); 244 | if (keytype == REDISMODULE_KEYTYPE_EMPTY) { 245 | return RMUTIL_VALUE_EMPTY; 246 | } else if (keytype == REDISMODULE_KEYTYPE_MODULE && RedisModule_ModuleTypeGetType(key) == type) { 247 | *out = RedisModule_ModuleTypeGetValue(key); 248 | return RMUTIL_VALUE_OK; 249 | } else { 250 | return RMUTIL_VALUE_MISMATCH; 251 | } 252 | } 253 | 254 | RedisModuleString **RMUtil_ParseVarArgs(RedisModuleString **argv, int argc, int offset, 255 | const char *keyword, size_t *nargs) { 256 | if (offset > argc) { 257 | return NULL; 258 | } 259 | 260 | argv += offset; 261 | argc -= offset; 262 | 263 | int ix = RMUtil_ArgIndex(keyword, argv, argc); 264 | if (ix < 0) { 265 | return NULL; 266 | } else if (ix >= argc - 1) { 267 | *nargs = RMUTIL_VARARGS_BADARG; 268 | return argv; 269 | } 270 | 271 | argv += (ix + 1); 272 | argc -= (ix + 1); 273 | 274 | long long n = 0; 275 | RMUtil_ParseArgs(argv, argc, 0, "l", &n); 276 | if (n > argc - 1 || n < 0) { 277 | *nargs = RMUTIL_VARARGS_BADARG; 278 | return argv; 279 | } 280 | 281 | *nargs = n; 282 | return argv + 1; 283 | } 284 | 285 | void RMUtil_DefaultAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) { 286 | RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL); 287 | RedisModuleCallReply *rep = RedisModule_Call(ctx, "DUMP", "s", key); 288 | if (rep != NULL && RedisModule_CallReplyType(rep) == REDISMODULE_REPLY_STRING) { 289 | size_t n; 290 | const char *s = RedisModule_CallReplyStringPtr(rep, &n); 291 | RedisModule_EmitAOF(aof, "RESTORE", "slb", key, 0, s, n); 292 | } else { 293 | RedisModule_Log(RedisModule_GetContextFromIO(aof), "warning", "Failed to emit AOF"); 294 | } 295 | if (rep != NULL) { 296 | RedisModule_FreeCallReply(rep); 297 | } 298 | RedisModule_FreeThreadSafeContext(ctx); 299 | } -------------------------------------------------------------------------------- /rmutil/util.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTIL_H__ 2 | #define __UTIL_H__ 3 | 4 | #include 5 | #include 6 | 7 | /// make sure the response is not NULL or an error, and if it is sends the error to the client and 8 | /// exit the current function 9 | #define RMUTIL_ASSERT_NOERROR(ctx, r) \ 10 | if (r == NULL) { \ 11 | return RedisModule_ReplyWithError(ctx, "ERR reply is NULL"); \ 12 | } else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { \ 13 | RedisModule_ReplyWithCallReply(ctx, r); \ 14 | return REDISMODULE_ERR; \ 15 | } 16 | 17 | #define __rmutil_register_cmd(ctx, cmd, f, mode) \ 18 | if (RedisModule_CreateCommand(ctx, cmd, f, mode, 1, 1, 1) == REDISMODULE_ERR) \ 19 | return REDISMODULE_ERR; 20 | 21 | #define RMUtil_RegisterReadCmd(ctx, cmd, f) __rmutil_register_cmd(ctx, cmd, f, "readonly") 22 | 23 | #define RMUtil_RegisterWriteCmd(ctx, cmd, f) __rmutil_register_cmd(ctx, cmd, f, "write") 24 | 25 | #define RMUtil_RegisterWriteDenyOOMCmd(ctx, cmd, f) __rmutil_register_cmd(ctx, cmd, f, "write deny-oom") 26 | 27 | /* RedisModule utilities. */ 28 | 29 | /** DEPRECATED: Return the offset of an arg if it exists in the arg list, or 0 if it's not there */ 30 | int RMUtil_ArgExists(const char *arg, RedisModuleString **argv, int argc, int offset); 31 | 32 | /* Same as argExists but returns -1 if not found. Use this, RMUtil_ArgExists is kept for backwards 33 | compatibility. */ 34 | int RMUtil_ArgIndex(const char *arg, RedisModuleString **argv, int argc); 35 | 36 | /** 37 | Automatically conver the arg list to corresponding variable pointers according to a given format. 38 | You pass it the command arg list and count, the starting offset, a parsing format, and pointers to 39 | the variables. 40 | The format is a string consisting of the following identifiers: 41 | 42 | c -- pointer to a Null terminated C string pointer. 43 | s -- pointer to a RedisModuleString 44 | l -- pointer to Long long integer. 45 | d -- pointer to a Double 46 | * -- do not parse this argument at all 47 | 48 | Example: If I want to parse args[1], args[2] as a long long and double, I do: 49 | double d; 50 | long long l; 51 | RMUtil_ParseArgs(argv, argc, 1, "ld", &l, &d); 52 | */ 53 | int RMUtil_ParseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, ...); 54 | 55 | /** 56 | Same as RMUtil_ParseArgs, but only parses the arguments after `token`, if it was found. 57 | This is useful for optional stuff like [LIMIT [offset] [limit]] 58 | */ 59 | int RMUtil_ParseArgsAfter(const char *token, RedisModuleString **argv, int argc, const char *fmt, 60 | ...); 61 | 62 | int rmutil_vparseArgs(RedisModuleString **argv, int argc, int offset, const char *fmt, va_list ap); 63 | 64 | #define RMUTIL_VARARGS_BADARG ((size_t)-1) 65 | /** 66 | * Parse arguments in the form of KEYWORD {len} {arg} .. {arg}_len. 67 | * If keyword is present, returns the position within `argv` containing the arguments. 68 | * Returns NULL if the keyword is not found. 69 | * If a parse error has occurred, `nargs` is set to RMUTIL_VARARGS_BADARG, but 70 | * the return value is not NULL. 71 | */ 72 | RedisModuleString **RMUtil_ParseVarArgs(RedisModuleString **argv, int argc, int offset, 73 | const char *keyword, size_t *nargs); 74 | 75 | /** 76 | * Default implementation of an AoF rewrite function that simply calls DUMP/RESTORE 77 | * internally. To use this function, pass it as the .aof_rewrite value in 78 | * RedisModuleTypeMethods 79 | */ 80 | void RMUtil_DefaultAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value); 81 | 82 | // A single key/value entry in a redis info map 83 | typedef struct { 84 | char *key; 85 | char *val; 86 | } RMUtilInfoEntry; 87 | 88 | // Representation of INFO command response, as a list of k/v pairs 89 | typedef struct { 90 | RMUtilInfoEntry *entries; 91 | int numEntries; 92 | } RMUtilInfo; 93 | 94 | /** 95 | * Get redis INFO result and parse it as RMUtilInfo. 96 | * Returns NULL if something goes wrong. 97 | * The resulting object needs to be freed with RMUtilRedisInfo_Free 98 | */ 99 | RMUtilInfo *RMUtil_GetRedisInfo(RedisModuleCtx *ctx); 100 | 101 | /** 102 | * Free an RMUtilInfo object and its entries 103 | */ 104 | void RMUtilRedisInfo_Free(RMUtilInfo *info); 105 | 106 | /** 107 | * Get an integer value from an info object. Returns 1 if the value was found and 108 | * is an integer, 0 otherwise. the value is placed in 'val' 109 | */ 110 | int RMUtilInfo_GetInt(RMUtilInfo *info, const char *key, long long *val); 111 | 112 | /** 113 | * Get a string value from an info object. The value is placed in str. 114 | * Returns 1 if the key was found, 0 if not 115 | */ 116 | int RMUtilInfo_GetString(RMUtilInfo *info, const char *key, const char **str); 117 | 118 | /** 119 | * Get a double value from an info object. Returns 1 if the value was found and is 120 | * a correctly formatted double, 0 otherwise. the value is placed in 'd' 121 | */ 122 | int RMUtilInfo_GetDouble(RMUtilInfo *info, const char *key, double *d); 123 | 124 | /* 125 | * Returns a call reply array's element given by a space-delimited path. E.g., 126 | * the path "1 2 3" will return the 3rd element from the 2 element of the 1st 127 | * element from an array (or NULL if not found) 128 | */ 129 | RedisModuleCallReply *RedisModule_CallReplyArrayElementByPath(RedisModuleCallReply *rep, 130 | const char *path); 131 | 132 | /** 133 | * Extract the module type from an opened key. 134 | */ 135 | typedef enum { 136 | RMUTIL_VALUE_OK = 0, 137 | RMUTIL_VALUE_MISSING, 138 | RMUTIL_VALUE_EMPTY, 139 | RMUTIL_VALUE_MISMATCH 140 | } RMUtil_TryGetValueStatus; 141 | 142 | /** 143 | * Tries to extract the module-specific type from the value. 144 | * @param key an opened key (may be null) 145 | * @param type the pointer to the type to match to 146 | * @param[out] out if the value is present, will be set to it. 147 | * @return a value in the @ref RMUtil_TryGetValueStatus enum. 148 | */ 149 | int RedisModule_TryGetValue(RedisModuleKey *key, const RedisModuleType *type, void **out); 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /rmutil/vector.c: -------------------------------------------------------------------------------- 1 | #include "vector.h" 2 | #include 3 | 4 | inline int __vector_PushPtr(Vector *v, void *elem) { 5 | if (v->top == v->cap) { 6 | Vector_Resize(v, v->cap ? v->cap * 2 : 1); 7 | } 8 | 9 | __vector_PutPtr(v, v->top, elem); 10 | return v->top; 11 | } 12 | 13 | inline int Vector_Get(Vector *v, size_t pos, void *ptr) { 14 | // return 0 if pos is out of bounds 15 | if (pos >= v->top) { 16 | return 0; 17 | } 18 | 19 | memcpy(ptr, v->data + (pos * v->elemSize), v->elemSize); 20 | return 1; 21 | } 22 | 23 | /* Get the element at the end of the vector, decreasing the size by one */ 24 | inline int Vector_Pop(Vector *v, void *ptr) { 25 | if (v->top > 0) { 26 | if (ptr != NULL) { 27 | Vector_Get(v, v->top - 1, ptr); 28 | } 29 | v->top--; 30 | return 1; 31 | } 32 | return 0; 33 | } 34 | 35 | inline int __vector_PutPtr(Vector *v, size_t pos, void *elem) { 36 | // resize if pos is out of bounds 37 | if (pos >= v->cap) { 38 | Vector_Resize(v, pos + 1); 39 | } 40 | 41 | if (elem) { 42 | memcpy(v->data + pos * v->elemSize, elem, v->elemSize); 43 | } else { 44 | memset(v->data + pos * v->elemSize, 0, v->elemSize); 45 | } 46 | // move the end offset to pos if we grew 47 | if (pos >= v->top) { 48 | v->top = pos + 1; 49 | } 50 | return 1; 51 | } 52 | 53 | int Vector_Resize(Vector *v, size_t newcap) { 54 | int oldcap = v->cap; 55 | v->cap = newcap; 56 | 57 | v->data = realloc(v->data, v->cap * v->elemSize); 58 | 59 | // If we grew: 60 | // put all zeros at the newly realloc'd part of the vector 61 | if (newcap > oldcap) { 62 | int offset = oldcap * v->elemSize; 63 | memset(v->data + offset, 0, v->cap * v->elemSize - offset); 64 | } 65 | return v->cap; 66 | } 67 | 68 | Vector *__newVectorSize(size_t elemSize, size_t cap) { 69 | Vector *vec = malloc(sizeof(Vector)); 70 | vec->data = calloc(cap, elemSize); 71 | vec->top = 0; 72 | vec->elemSize = elemSize; 73 | vec->cap = cap; 74 | 75 | return vec; 76 | } 77 | 78 | void Vector_Free(Vector *v) { 79 | free(v->data); 80 | free(v); 81 | } 82 | 83 | 84 | /* return the used size of the vector, regardless of capacity */ 85 | inline int Vector_Size(Vector *v) { return v->top; } 86 | 87 | /* return the actual capacity */ 88 | inline int Vector_Cap(Vector *v) { return v->cap; } 89 | -------------------------------------------------------------------------------- /rmutil/vector.h: -------------------------------------------------------------------------------- 1 | #ifndef __VECTOR_H__ 2 | #define __VECTOR_H__ 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * Generic resizable vector that can be used if you just want to store stuff 9 | * temporarily. 10 | * Works like C++ std::vector with an underlying resizable buffer 11 | */ 12 | typedef struct { 13 | char *data; 14 | size_t elemSize; 15 | size_t cap; 16 | size_t top; 17 | 18 | } Vector; 19 | 20 | /* Create a new vector with element size. This should generally be used 21 | * internall by the NewVector macro */ 22 | Vector *__newVectorSize(size_t elemSize, size_t cap); 23 | 24 | // Put a pointer in the vector. To be used internall by the library 25 | int __vector_PutPtr(Vector *v, size_t pos, void *elem); 26 | 27 | /* 28 | * Create a new vector for a given type and a given capacity. 29 | * e.g. NewVector(int, 0) - empty vector of ints 30 | */ 31 | #define NewVector(type, cap) __newVectorSize(sizeof(type), cap) 32 | 33 | /* 34 | * get the element at index pos. The value is copied in to ptr. If pos is outside 35 | * the vector capacity, we return 0 36 | * otherwise 1 37 | */ 38 | int Vector_Get(Vector *v, size_t pos, void *ptr); 39 | 40 | /* Get the element at the end of the vector, decreasing the size by one */ 41 | int Vector_Pop(Vector *v, void *ptr); 42 | 43 | //#define Vector_Getx(v, pos, ptr) pos < v->cap ? 1 : 0; *ptr = 44 | //*(typeof(ptr))(v->data + v->elemSize*pos) 45 | 46 | /* 47 | * Put an element at pos. 48 | * Note: If pos is outside the vector capacity, we resize it accordingly 49 | */ 50 | #define Vector_Put(v, pos, elem) __vector_PutPtr(v, pos, elem ? &(typeof(elem)){elem} : NULL) 51 | 52 | /* Push an element at the end of v, resizing it if needed. This macro wraps 53 | * __vector_PushPtr */ 54 | #define Vector_Push(v, elem) __vector_PushPtr(v, elem ? &(typeof(elem)){elem} : NULL) 55 | 56 | int __vector_PushPtr(Vector *v, void *elem); 57 | 58 | /* resize capacity of v */ 59 | int Vector_Resize(Vector *v, size_t newcap); 60 | 61 | /* return the used size of the vector, regardless of capacity */ 62 | int Vector_Size(Vector *v); 63 | 64 | /* return the actual capacity */ 65 | int Vector_Cap(Vector *v); 66 | 67 | /* free the vector and the underlying data. Does not release its elements if 68 | * they are pointers*/ 69 | void Vector_Free(Vector *v); 70 | 71 | int __vecotr_PutPtr(Vector *v, size_t pos, void *elem); 72 | 73 | #endif -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | #set environment variable RM_INCLUDE_DIR to the location of redismodule.h 2 | ifndef RM_INCLUDE_DIR 3 | RM_INCLUDE_DIR=../ 4 | endif 5 | 6 | ifndef RMUTIL_LIBDIR 7 | RMUTIL_LIBDIR=../rmutil 8 | endif 9 | 10 | # find the OS 11 | uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 12 | 13 | # Compile flags for linux / osx 14 | ifeq ($(uname_S),Linux) 15 | SHOBJ_CFLAGS ?= -fno-common -g -ggdb -lc -lm 16 | SHOBJ_LDFLAGS ?= -shared -Bsymbolic 17 | else ifeq ($(uname_S),Darwin) 18 | SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb 19 | SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup -macosx_version_min 10.13 20 | else 21 | SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb -lc -lm 22 | SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup 23 | endif 24 | CFLAGS = -I$(RM_INCLUDE_DIR) -Wall -g -fPIC -std=gnu99 25 | CC=gcc 26 | 27 | all: rmutil dbx.so 28 | 29 | rmutil: FORCE 30 | $(MAKE) -C $(RMUTIL_LIBDIR) 31 | 32 | dbx.so: dbx.o 33 | $(LD) -o $@ dbx.o $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc 34 | 35 | clean: 36 | rm -rf *.xo *.so *.o 37 | 38 | FORCE: 39 | -------------------------------------------------------------------------------- /src/dbx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../redismodule.h" 6 | #include "../rmutil/util.h" 7 | #include "../rmutil/strings.h" 8 | #include "../rmutil/vector.h" 9 | #include "../rmutil/test_util.h" 10 | 11 | static int rn; 12 | 13 | /* Helper function: compiles a regex, or dies complaining. */ 14 | int regexCompile(RedisModuleCtx *ctx, regex_t *r, const char *t) { 15 | int status = regcomp(r, t, REG_EXTENDED | REG_NOSUB | REG_NEWLINE); 16 | 17 | if (status) { 18 | char rerr[128]; 19 | char err[256]; 20 | regerror(status, r, rerr, 128); 21 | sprintf(err, "ERR regex compilation failed: %s", rerr); 22 | RedisModule_ReplyWithError(ctx, err); 23 | return status; 24 | } 25 | 26 | return 0; 27 | } 28 | 29 | char* trim(char* s, char t) { 30 | char* p = s; 31 | if (p[strlen(s)-1] == t) p[strlen(s)-1] = 0; 32 | if (p[0] == t) p++; 33 | return p; 34 | } 35 | char* toLower(char* s) { 36 | for(char *p = s; *p; p++) 37 | *p = tolower(*p); 38 | return s; 39 | } 40 | char* toUpper(char* s) { 41 | for(char *p = s; *p; p++) 42 | *p = toupper(*p); 43 | return s; 44 | } 45 | 46 | const char* RedisModule_StringToChar(RedisModuleString *s) { 47 | size_t l; 48 | return RedisModule_StringPtrLen(s, &l); 49 | } 50 | 51 | char* VectorGetString(Vector *v, size_t i) { 52 | char *value; 53 | Vector_Get(v, i, &value); 54 | return value; 55 | } 56 | 57 | int whereRecord(RedisModuleCtx *ctx, RedisModuleString *key, Vector *vWhere) { 58 | //char *field; 59 | char *w; 60 | int condition; 61 | int match = 1; 62 | 63 | // If where statement is defined, get the specified hash content and do comparison 64 | size_t n = Vector_Size(vWhere); 65 | if (n == 0) return 1; 66 | if (n % 3 != 0) return 0; 67 | for (size_t i = 0; i < n; i += 3) { 68 | // Vector_Get(vWhere, i, &field); 69 | Vector_Get(vWhere, i+1, &condition); 70 | Vector_Get(vWhere, i+2, &w); 71 | if (condition == 7) 72 | toLower(w); 73 | if (strlen(w) == 0) return 0; 74 | 75 | RedisModuleCallReply *tags = RedisModule_Call(ctx, "HGET", "sc", key, VectorGetString(vWhere, i)); 76 | if (RedisModule_CallReplyLength(tags) == 0) { 77 | RedisModule_FreeCallReply(tags); 78 | return 0; 79 | } 80 | RedisModuleString *rms = RedisModule_CreateStringFromCallReply(tags); 81 | const char *s = RedisModule_StringToChar(rms); 82 | switch(condition) { 83 | case 0: 84 | match = strcmp(s, w) >= 0? 1: 0; 85 | break; 86 | case 1: 87 | match = strcmp(s, w) <= 0? 1: 0; 88 | break; 89 | case 2: 90 | case 3: 91 | match = strcmp(s, w) != 0? 1: 0; 92 | break; 93 | case 4: 94 | match = strcmp(s, w) > 0? 1: 0; 95 | break; 96 | case 5: 97 | match = strcmp(s, w) < 0? 1: 0; 98 | break; 99 | case 6: 100 | match = strcmp(s, w) == 0? 1: 0; 101 | break; 102 | case 7: 103 | match = strstr(toLower((char*)s), w)? 1: 0; 104 | break; 105 | } 106 | RedisModule_FreeString(ctx, rms); 107 | RedisModule_FreeCallReply(tags); 108 | if (match == 0) return 0; 109 | } 110 | return match; 111 | } 112 | 113 | void showRecord(RedisModuleCtx *ctx, RedisModuleString *key, Vector *vSelect) { 114 | RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); 115 | 116 | char* field; 117 | size_t nSelected = Vector_Size(vSelect); 118 | size_t n = 0; 119 | for(size_t i = 0; i < nSelected; i++) { 120 | Vector_Get(vSelect, i, &field); 121 | 122 | // If '*' is specified in selected hash list, display all hashes then 123 | if (strcmp(field, "*") == 0) { 124 | RedisModuleCallReply *tags = RedisModule_Call(ctx, "HGETALL", "s", key); 125 | size_t tf = RedisModule_CallReplyLength(tags); 126 | if (tf > 0) { 127 | for(size_t j=0; j 0) { 146 | RedisModuleString *rms = RedisModule_CreateStringFromCallReply(tags); 147 | RedisModule_ReplyWithString(ctx, rms); 148 | RedisModule_FreeString(ctx, rms); 149 | } 150 | else 151 | RedisModule_ReplyWithNull(ctx); // If hash is undefined 152 | n += 2; 153 | RedisModule_FreeCallReply(tags); 154 | } 155 | } 156 | RedisModule_ReplySetArrayLength(ctx, n); 157 | } 158 | 159 | void intoRecord(RedisModuleCtx *ctx, RedisModuleString *key, Vector *vSelect, char *intoKey) { 160 | char* field; 161 | size_t nSelected = Vector_Size(vSelect); 162 | char newkey[64]; 163 | 164 | sprintf(newkey, "%s:%u-%i", intoKey, (unsigned)time(NULL), rn++); 165 | for(size_t i = 0; i < nSelected; i++) { 166 | Vector_Get(vSelect, i, &field); 167 | 168 | // If '*' is specified in selected hash list, display all hashes then 169 | if (strcmp(field, "*") == 0) { 170 | RedisModuleCallReply *tags = RedisModule_Call(ctx, "HGETALL", "s", key); 171 | size_t tf = RedisModule_CallReplyLength(tags); 172 | if (tf > 0) { 173 | for(size_t j=0; j 0) { 187 | RedisModuleString *rms = RedisModule_CreateStringFromCallReply(tags); 188 | RedisModule_Call(ctx, "HSET", "ccs", newkey, field, rms); 189 | RedisModule_FreeString(ctx, rms); 190 | } 191 | else 192 | RedisModule_Call(ctx, "HSET", "ccc", newkey, field, ""); 193 | RedisModule_FreeCallReply(tags); 194 | } 195 | } 196 | RedisModule_ReplyWithSimpleString(ctx, newkey); 197 | } 198 | 199 | void intoCSV(RedisModuleCtx *ctx, RedisModuleString *key, Vector *vSelect, char *filename) { 200 | char* field; 201 | size_t nSelected = Vector_Size(vSelect); 202 | char line[1024]; 203 | 204 | FILE *fp = fopen(filename, "a"); 205 | strcpy(line, ""); 206 | for(size_t i = 0; i < nSelected; i++) { 207 | Vector_Get(vSelect, i, &field); 208 | // If '*' is specified in selected hash list, display all hashes then 209 | if (strcmp(field, "*") == 0) { 210 | RedisModuleCallReply *tags = RedisModule_Call(ctx, "HGETALL", "s", key); 211 | size_t tf = RedisModule_CallReplyLength(tags); 212 | if (tf > 0) { 213 | for(size_t j=0; j 0) strcat(line, ","); 217 | strcat(line, RedisModule_StringToChar(rms2)); 218 | // RedisModule_FreeString(ctx, rms1); 219 | RedisModule_FreeString(ctx, rms2); 220 | } 221 | } 222 | RedisModule_FreeCallReply(tags); 223 | } 224 | else { 225 | if (strlen(line) > 0) strcat(line, ","); 226 | RedisModuleCallReply *tags = RedisModule_Call(ctx, "HGET", "sc", key, field); 227 | if (RedisModule_CallReplyLength(tags) > 0) { 228 | RedisModuleString *rms = RedisModule_CreateStringFromCallReply(tags); 229 | strcat(line, RedisModule_StringToChar(rms)); 230 | RedisModule_FreeString(ctx, rms); 231 | } 232 | RedisModule_FreeCallReply(tags); 233 | } 234 | } 235 | RedisModule_ReplyWithSimpleString(ctx, line); 236 | fprintf(fp, "%s\n", line); 237 | fclose(fp); 238 | } 239 | 240 | /* Split the string by specified delimilator */ 241 | Vector* splitStringByChar(char *s, char* d) { 242 | size_t cap; 243 | char *p = s; 244 | for (cap=1; p[cap]; p[cap]==d[0] ? cap++ : *p++); 245 | 246 | Vector *v = NewVector(void *, cap); 247 | if (strlen(s) > 0) { 248 | char *token = strtok(s, d); 249 | for(int i=0; i=", "<=", "!=", "<>", ">", "<", "=", "~"}; 262 | char *p = token; 263 | while(*p++) { 264 | for(int i=0; i<8; i++) { 265 | char *c = chk[i]; 266 | if (strncmp(c, p, strlen(c)) == 0) { 267 | *p = 0; 268 | p += strlen(c); 269 | Vector_Push(v, token); 270 | Vector_Push(v, i); 271 | Vector_Push(v, p); 272 | break; 273 | } 274 | } 275 | } 276 | token = strtok(NULL, "&&"); 277 | } 278 | return v; 279 | } 280 | 281 | size_t processRecords(RedisModuleCtx *ctx, RedisModuleCallReply *keys, regex_t *r, Vector *vSelect, Vector *vWhere, long *top, char *intoKey, char *csvFile) { 282 | size_t nKeys = RedisModule_CallReplyLength(keys); 283 | size_t affected = 0; 284 | for (size_t i = 0; i < nKeys; i++) { 285 | RedisModuleString *key = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(keys, i)); 286 | const char *s = RedisModule_StringToChar(key); 287 | if (!regexec(r, s, 1, NULL, 0)) { 288 | if (vWhere == NULL || whereRecord(ctx, key, vWhere)) { 289 | if (strlen(csvFile) > 0) 290 | intoCSV(ctx, key, vSelect, csvFile); 291 | else if (strlen(intoKey) > 0) 292 | intoRecord(ctx, key, vSelect, intoKey); 293 | else 294 | showRecord(ctx, key, vSelect); 295 | affected++; 296 | (*top)--; 297 | } 298 | } 299 | RedisModule_FreeString(ctx, key); 300 | if (*top == 0) return affected; 301 | } 302 | return affected; 303 | } 304 | 305 | /* Create temporary set for sorting */ 306 | size_t buildSetByPattern(RedisModuleCtx *ctx, regex_t *r, char *setName, Vector *vWhere) { 307 | RedisModule_Call(ctx, "DEL", "c", setName); 308 | RedisModuleString *scursor = RedisModule_CreateStringFromLongLong(ctx, 0); 309 | long long lcursor; 310 | size_t affected = 0; 311 | do { 312 | RedisModuleCallReply *rep = RedisModule_Call(ctx, "SCAN", "s", scursor); 313 | 314 | /* Get the current cursor. */ 315 | scursor = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(rep, 0)); 316 | RedisModule_StringToLongLong(scursor, &lcursor); 317 | 318 | /* Filter by pattern matching. */ 319 | RedisModuleCallReply *keys = RedisModule_CallReplyArrayElement(rep, 1); 320 | size_t nKeys = RedisModule_CallReplyLength(keys); 321 | for (size_t i = 0; i < nKeys; i++) { 322 | RedisModuleString *key = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(keys, i)); 323 | const char *s = RedisModule_StringToChar(key); 324 | if (!regexec(r, s, 1, NULL, 0)) { 325 | if (vWhere == NULL || whereRecord(ctx, key, vWhere)) { 326 | RedisModule_Call(ctx, "SADD", "cs", setName, key); 327 | affected++; 328 | } 329 | } 330 | RedisModule_FreeString(ctx, key); 331 | } 332 | 333 | RedisModule_FreeCallReply(keys); 334 | RedisModule_FreeCallReply(rep); 335 | } while (lcursor); 336 | 337 | return affected; 338 | } 339 | 340 | int SelectCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 341 | RedisModule_AutoMemory(ctx); 342 | 343 | if (argc < 2) 344 | return RedisModule_WrongArity(ctx); 345 | 346 | // Table 347 | long top = -1; 348 | RedisModuleString *fromKeys; 349 | char intoKey[32] = ""; 350 | char csvFile[128] = ""; 351 | 352 | // Process the arguments 353 | size_t plen; 354 | char s[1024] = ""; 355 | for (int i=1; i 0) strcat(s, " "); 357 | const char *temp = RedisModule_StringPtrLen(argv[i], &plen); 358 | if (strlen(s) + plen > 1024) { 359 | RedisModule_ReplyWithError(ctx, "arguments are too long"); 360 | return REDISMODULE_ERR; 361 | } 362 | 363 | if (argc > 2) { // argc > 2 means the arguments are not in quoted. i.e. "..." 364 | char *p = (char*)temp; 365 | while (*p++) *p = *p == 32? 7: *p; // Convert all spaces in tabs, then convert back during parsing 366 | } 367 | 368 | if (strcmp("like", temp) == 0) 369 | strcat(s, "~"); 370 | else 371 | strcat(s, temp); 372 | } 373 | 374 | char *sp = s; 375 | if (strncmp("select", sp, 6) == 0) sp += 6; 376 | 377 | int step = 0; 378 | char temp[1024] = ""; 379 | char stmSelect[1024] = ""; 380 | char stmWhere[1024] = ""; 381 | char stmOrder[1024] = ""; 382 | 383 | char *token = strtok(sp, " "); 384 | while (token != NULL) { 385 | // If it is beginning in single quote, find the end quote in the following tokens 386 | if (token[0] == 39) { 387 | strcpy(temp, &token[1]); 388 | strcat(temp, " "); 389 | strcat(temp, strtok(NULL, "'")); 390 | strcpy(token, temp); 391 | } 392 | switch(step) { 393 | case 0: 394 | if (strcmp("top", token) == 0) { 395 | step = -2; 396 | break; 397 | } 398 | step = -1; 399 | case -1: 400 | if (strlen(token) > 512) { 401 | RedisModule_ReplyWithError(ctx, "select arguments are too long"); 402 | return REDISMODULE_ERR; 403 | } 404 | strcat(stmSelect, token); 405 | step = -3; 406 | break; 407 | case -2: 408 | top = atol(token); 409 | step = -1; 410 | break; 411 | case -3: 412 | if (strcmp("into", token) == 0) 413 | step = -4; 414 | else if (strcmp("from", token) == 0) 415 | step = -6; 416 | else { 417 | if (strlen(stmSelect) + strlen(token) > 512) { 418 | RedisModule_ReplyWithError(ctx, "select arguments are too long"); 419 | return REDISMODULE_ERR; 420 | } 421 | strcat(stmSelect, token); 422 | } 423 | break; 424 | case -4: 425 | // parse into clause, assume time+rand always is new key 426 | if (strcmp("csv", token) == 0) 427 | step = -45; 428 | else { 429 | strcpy(intoKey, token); 430 | step = -5; 431 | } 432 | break; 433 | case -45: 434 | strcpy(csvFile, token); 435 | step = -5; 436 | break; 437 | case -5: 438 | if (strcmp("from", token) == 0) 439 | step = -6; 440 | else { 441 | RedisModule_ReplyWithError(ctx, "from keyword is expected"); 442 | return REDISMODULE_ERR; 443 | } 444 | break; 445 | case -6: 446 | // parse from clause 447 | fromKeys = RMUtil_CreateFormattedString(ctx, token); 448 | step = 7; 449 | break; 450 | case 7: 451 | if (strcmp("where", token) == 0) 452 | step = -8; 453 | else if (strcmp("order", token) == 0) 454 | step = -10; 455 | break; 456 | case -8: 457 | case 9: 458 | // parse where clause 459 | if (strcmp("order", token) == 0) 460 | step = -10; 461 | else if (strcmp("and", token) == 0) 462 | strcat(stmWhere, "&&"); 463 | else { 464 | if (strlen(stmWhere) + strlen(token) > 512) { 465 | RedisModule_ReplyWithError(ctx, "where arguments are too long"); 466 | return REDISMODULE_ERR; 467 | } 468 | char *p = token; 469 | while (*p++) *p = *p == 7? 32: *p; 470 | strcat(stmWhere, token); 471 | step = 9; 472 | } 473 | break; 474 | case -10: 475 | if (strcmp("by", token) == 0) 476 | step = -11; 477 | else { 478 | RedisModule_ReplyWithError(ctx, "missing 'by' after order"); 479 | return REDISMODULE_ERR; 480 | } 481 | break; 482 | case -11: 483 | case 12: 484 | // parse order clause 485 | if (strlen(stmOrder) + strlen(token) > 512) { 486 | RedisModule_ReplyWithError(ctx, "order arguments are too long"); 487 | return REDISMODULE_ERR; 488 | } 489 | if (strcmp("desc", token) == 0) 490 | strcat(stmOrder, "-"); 491 | else 492 | if (strcmp("asc", token) != 0) 493 | strcat(stmOrder, token); 494 | step = 12; 495 | break; 496 | } 497 | token = strtok(NULL, " "); 498 | } 499 | 500 | if (step <= 0) { 501 | RedisModule_ReplyWithError(ctx, "parse error"); 502 | return REDISMODULE_ERR; 503 | } 504 | 505 | Vector *vSelect = splitStringByChar(stmSelect, ","); 506 | Vector *vWhere = splitWhereString(stmWhere); 507 | Vector *vOrder = splitStringByChar(stmOrder, ","); 508 | 509 | /* Convert key to regex */ 510 | const char *pat = RedisModule_StringToChar(fromKeys); 511 | regex_t regex; 512 | if (regexCompile(ctx, ®ex, pat)) return REDISMODULE_ERR; 513 | 514 | /* Print result in array format */ 515 | RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); 516 | 517 | if (Vector_Size(vOrder) > 0) { 518 | // temporary set name 519 | char setName[32]; 520 | sprintf(setName, "__db_tempset_%i", rn++); 521 | 522 | RedisModuleCallReply *rep; 523 | 524 | if (buildSetByPattern(ctx, ®ex, setName, vWhere) > 0) { 525 | char *field; 526 | int nSortField = Vector_Size(vOrder); 527 | int cap = 3 * nSortField + 2; 528 | 529 | RedisModuleString *param[cap]; 530 | param[0] = RedisModule_CreateString(ctx, setName, strlen(setName)); 531 | 532 | for(int sf = 0; sf < nSortField; sf++) { 533 | Vector_Get(vOrder, sf, &field); 534 | if (field[strlen(field)-1] == '-') { 535 | field[strlen(field)-1] = 0; 536 | param[3+sf*3] = RedisModule_CreateString(ctx, "desc", 4); 537 | } 538 | else 539 | param[3+sf*3] = RedisModule_CreateString(ctx, "asc", 3); 540 | param[1+sf*3] = RedisModule_CreateString(ctx, "by", 2); 541 | param[2+sf*3] = RedisModule_CreateStringPrintf(ctx, "*->%s", field, 3 + strlen(field)); 542 | } 543 | param[cap-1] = RedisModule_CreateString(ctx, "alpha", 5); 544 | rep = RedisModule_Call(ctx, "SORT", "v", ¶m, cap); 545 | 546 | for(int i = 0; i < cap; i++) 547 | RedisModule_FreeString(ctx, param[i]); 548 | 549 | size_t n = processRecords(ctx, rep, ®ex, vSelect, NULL, &top, intoKey, csvFile); 550 | 551 | RedisModule_FreeCallReply(rep); 552 | RedisModule_ReplySetArrayLength(ctx, n); 553 | } 554 | else 555 | RedisModule_ReplySetArrayLength(ctx, 0); 556 | 557 | // Remove the temporary set before leave 558 | RedisModule_Call(ctx, "DEL", "c", setName); 559 | } 560 | else { 561 | RedisModuleString *scursor = RedisModule_CreateStringFromLongLong(ctx, 0); 562 | long long lcursor; 563 | size_t n = 0; 564 | do { 565 | RedisModuleCallReply *rep = RedisModule_Call(ctx, "SCAN", "s", scursor); 566 | 567 | /* Get the current cursor. */ 568 | scursor = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(rep, 0)); 569 | RedisModule_StringToLongLong(scursor, &lcursor); 570 | 571 | /* Filter by pattern matching. */ 572 | RedisModuleCallReply *rkeys = RedisModule_CallReplyArrayElement(rep, 1); 573 | n += processRecords(ctx, rkeys, ®ex, vSelect, vWhere, &top, intoKey, csvFile); 574 | 575 | RedisModule_FreeCallReply(rkeys); 576 | RedisModule_FreeCallReply(rep); 577 | if (top == 0) break; 578 | } while (lcursor); 579 | 580 | RedisModule_ReplySetArrayLength(ctx, n); 581 | RedisModule_FreeString(ctx, scursor); 582 | } 583 | 584 | RedisModule_FreeString(ctx, fromKeys); 585 | Vector_Free(vSelect); 586 | Vector_Free(vWhere); 587 | Vector_Free(vOrder); 588 | 589 | return REDISMODULE_OK; 590 | } 591 | 592 | int InsertCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 593 | RedisModule_AutoMemory(ctx); 594 | 595 | if (argc < 2) 596 | return RedisModule_WrongArity(ctx); 597 | 598 | // Table 599 | char intoKey[128] = ""; 600 | RedisModuleString *fromCSV = NULL; 601 | 602 | // Process the arguments 603 | size_t plen; 604 | char s[1024] = ""; 605 | for (int i=1; i 0) strcat(s, " "); 607 | const char *temp = RedisModule_StringPtrLen(argv[i], &plen); 608 | if (strlen(s) + plen > 1024) { 609 | RedisModule_ReplyWithError(ctx, "arguments are too long"); 610 | return REDISMODULE_ERR; 611 | } 612 | 613 | if (argc > 2) { // argc > 2 means the arguments are not in quoted. i.e. "..." 614 | char *p = (char*)temp; 615 | while (*p++) *p = *p == 32? 7: *p; // Convert all spaces in tabs, then convert back during parsing 616 | } 617 | strcat(s, temp); 618 | } 619 | 620 | char *sp = s; 621 | if (strncmp("insert", sp, 6) == 0) sp += 6; 622 | 623 | int step = 0; 624 | char temp[1024] = ""; 625 | char stmField[1024] = ""; 626 | char stmValue[1024] = ""; 627 | 628 | char *p; 629 | char *token = strtok(sp, " "); 630 | while (token != NULL) { 631 | if (token[0] == 39) { 632 | strcpy(temp, &token[1]); 633 | strcat(temp, " "); 634 | strcat(temp, strtok(NULL, "'")); 635 | strcpy(token, temp); 636 | } 637 | switch(step) { 638 | case 0: 639 | if (strcmp("into", token) == 0) 640 | step = -1; 641 | else { 642 | RedisModule_ReplyWithError(ctx, "into keyword is expected"); 643 | return REDISMODULE_ERR; 644 | } 645 | break; 646 | case -1: 647 | // parse into clause, assume time+rand always is new key 648 | strcpy(intoKey, token); 649 | step = -2; 650 | break; 651 | case -2: 652 | if (token[0] == '(') { 653 | strcpy(stmField, &token[1]); 654 | if (token[strlen(token) - 1] == ')') { 655 | stmField[strlen(stmField) - 1] = 0; 656 | step = -4; 657 | } 658 | else 659 | step = -3; 660 | } 661 | else if (strcmp("values", token) == 0) 662 | step = -5; 663 | else if (strcmp("from", token) == 0) 664 | step = -7; 665 | else { 666 | RedisModule_ReplyWithError(ctx, "values keyword is expected"); 667 | return REDISMODULE_ERR; 668 | } 669 | break; 670 | case -3: 671 | if (token[strlen(token) - 1] == ')') { 672 | token[strlen(token) - 1] = 0; 673 | strcat(stmField, token); 674 | step = -4; 675 | } 676 | break; 677 | case -4: 678 | if (strcmp("values", token) == 0) 679 | step = -5; 680 | else if (strcmp("from", token) == 0) 681 | step = -7; 682 | else { 683 | RedisModule_ReplyWithError(ctx, "values or from keyword is expected"); 684 | return REDISMODULE_ERR; 685 | } 686 | break; 687 | case -5: 688 | case -6: 689 | p = token; 690 | while (*p++) *p = *p == 7? 32: *p; 691 | if (token[0] == '(') { 692 | strcpy(stmValue, &token[1]); 693 | if (token[strlen(token) - 1] == ')') { 694 | stmValue[strlen(stmValue) - 1] = 0; 695 | step = 8; 696 | } 697 | else 698 | step = -6; 699 | } 700 | else if (token[strlen(token) - 1] == ')') { 701 | token[strlen(token) - 1] = 0; 702 | strcat(stmValue, token); 703 | step = 8; 704 | } 705 | else 706 | strcat(stmValue, token); 707 | break; 708 | case -7: 709 | fromCSV = RedisModule_CreateString(ctx, token, strlen(token)); 710 | step = 8; 711 | break; 712 | case 8: 713 | RedisModule_ReplyWithError(ctx, "The end of statement is expected"); 714 | return REDISMODULE_ERR; 715 | break; 716 | } 717 | token = strtok(NULL, " "); 718 | } 719 | 720 | if (step < 7) { 721 | RedisModule_ReplyWithError(ctx, "parse error"); 722 | return REDISMODULE_ERR; 723 | } 724 | 725 | Vector *vField = splitStringByChar(stmField, ","); 726 | Vector *vValue = splitStringByChar(stmValue, ","); 727 | 728 | if (fromCSV != NULL) { 729 | const char *filename = RedisModule_StringToChar(fromCSV); 730 | FILE *fp = fopen(filename, "r"); 731 | if (fp == NULL) { 732 | RedisModule_ReplyWithError(ctx, "File does not exist"); 733 | return REDISMODULE_ERR; 734 | } 735 | char line[1024]; 736 | char *value, *field; 737 | size_t n = 0; 738 | RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); 739 | 740 | while(fgets(line, 1024, fp) != NULL) { 741 | if (line[strlen(line)-1] == 10) line[strlen(line)-1] = 0; 742 | if (line[strlen(line)-1] == 13) line[strlen(line)-1] = 0; 743 | 744 | value = strtok(line, ","); 745 | if (Vector_Size(vField) == 0) { 746 | while (value != NULL) { 747 | if (strlen(stmField) > 0) strcat(stmField, ","); 748 | strcat(stmField, trim(value, '"')); 749 | value = strtok(NULL, ","); 750 | } 751 | if (vField) Vector_Free(vField); 752 | vField = splitStringByChar(stmField, ","); 753 | continue; 754 | } 755 | 756 | RedisModuleString *key = RedisModule_CreateStringPrintf(ctx, "%s:%u-%i", intoKey, (unsigned)time(NULL), rn++); 757 | for (size_t i=0; i 0) strcat(s, " "); 816 | const char *temp = RedisModule_StringPtrLen(argv[i], &plen); 817 | if (strlen(s) + plen > 1024) { 818 | RedisModule_ReplyWithError(ctx, "arguments are too long"); 819 | return REDISMODULE_ERR; 820 | } 821 | 822 | if (argc > 2) { // argc > 2 means the arguments are not in quoted. i.e. "..." 823 | char *p = (char*)temp; 824 | while (*p++) *p = *p == 32? 7: *p; // Convert all spaces in tabs, then convert back during parsing 825 | } 826 | strcat(s, temp); 827 | } 828 | 829 | char *sp = s; 830 | if (strncmp("delete", sp, 6) == 0) sp += 6; 831 | 832 | int step = 0; 833 | char temp[1024] = ""; 834 | char stmWhere[1024] = ""; 835 | 836 | char *token = strtok(sp, " "); 837 | while (token != NULL) { 838 | // If it is beginning in single quote, find the end quote in the following tokens 839 | if (token[0] == 39) { 840 | strcpy(temp, &token[1]); 841 | strcat(temp, " "); 842 | strcat(temp, strtok(NULL, "'")); 843 | strcpy(token, temp); 844 | } 845 | switch(step) { 846 | case 0: 847 | if (strcmp("from", token) == 0) 848 | step = -1; 849 | else { 850 | RedisModule_ReplyWithError(ctx, "from keyword is expected"); 851 | return REDISMODULE_ERR; 852 | } 853 | break; 854 | case -1: 855 | // parse from clause 856 | fromKeys = RMUtil_CreateFormattedString(ctx, token); 857 | step = 2; 858 | break; 859 | case 2: 860 | if (strcmp("where", token) == 0) 861 | step = -3; 862 | else { 863 | RedisModule_ReplyWithError(ctx, "where statement is expected"); 864 | return REDISMODULE_ERR; 865 | } 866 | break; 867 | case -3: 868 | case 4: 869 | // parse where clause 870 | if (strlen(stmWhere) + strlen(token) > 512) { 871 | RedisModule_ReplyWithError(ctx, "where arguments are too long"); 872 | return REDISMODULE_ERR; 873 | } 874 | if (strcmp("and", token) == 0) 875 | strcat(stmWhere, "&&"); 876 | else { 877 | char *p = token; 878 | while (*p++) *p = *p == 7? 32: *p; 879 | strcat(stmWhere, token); 880 | step = 4; 881 | } 882 | break; 883 | } 884 | token = strtok(NULL, " "); 885 | } 886 | 887 | if (step <= 0) { 888 | RedisModule_ReplyWithError(ctx, "parse error"); 889 | return REDISMODULE_ERR; 890 | } 891 | 892 | Vector *vWhere = splitWhereString(stmWhere); 893 | 894 | /* Convert key to regex */ 895 | const char *pat = RedisModule_StringToChar(fromKeys); 896 | regex_t regex; 897 | if (regexCompile(ctx, ®ex, pat)) return REDISMODULE_ERR; 898 | 899 | RedisModuleString *scursor = RedisModule_CreateStringFromLongLong(ctx, 0); 900 | long long lcursor; 901 | size_t affected = 0; 902 | do { 903 | RedisModuleCallReply *rep = RedisModule_Call(ctx, "SCAN", "s", scursor); 904 | 905 | /* Get the current cursor. */ 906 | scursor = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(rep, 0)); 907 | RedisModule_StringToLongLong(scursor, &lcursor); 908 | 909 | /* Filter by pattern matching. */ 910 | RedisModuleCallReply *keys = RedisModule_CallReplyArrayElement(rep, 1); 911 | size_t nKeys = RedisModule_CallReplyLength(keys); 912 | for (size_t i = 0; i < nKeys; i++) { 913 | RedisModuleString *key = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(keys, i)); 914 | const char *s = RedisModule_StringToChar(key); 915 | if (!regexec(®ex, s, 1, NULL, 0)) { 916 | if (vWhere == NULL || whereRecord(ctx, key, vWhere)) { 917 | RedisModule_Call(ctx, "DEL", "s", key); 918 | affected++; 919 | } 920 | } 921 | RedisModule_FreeString(ctx, key); 922 | } 923 | 924 | RedisModule_FreeCallReply(rep); 925 | } while (lcursor); 926 | 927 | RedisModule_FreeString(ctx, scursor); 928 | 929 | RedisModule_ReplyWithLongLong(ctx, affected); 930 | RedisModule_FreeString(ctx, fromKeys); 931 | Vector_Free(vWhere); 932 | 933 | return REDISMODULE_OK; 934 | } 935 | 936 | int ExecCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { 937 | if (argc < 2) 938 | return RedisModule_WrongArity(ctx); 939 | 940 | const char *arg = RedisModule_StringToChar(argv[1]); 941 | 942 | if (strncmp(arg, "select", 6) == 0) 943 | return SelectCommand(ctx, argv, argc); 944 | else if (strncmp(arg, "insert", 6) == 0) 945 | return InsertCommand(ctx, argv, argc); 946 | else if (strncmp(arg, "delete", 6) == 0) 947 | return DeleteCommand(ctx, argv, argc); 948 | else { 949 | RedisModule_ReplyWithError(ctx, "parse error"); 950 | return REDISMODULE_ERR; 951 | } 952 | } 953 | 954 | int RedisModule_OnLoad(RedisModuleCtx *ctx) { 955 | 956 | rn = rand(); 957 | 958 | // Register the module 959 | if (RedisModule_Init(ctx, "dbx", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) 960 | return REDISMODULE_ERR; 961 | 962 | // Register the command 963 | if (RedisModule_CreateCommand(ctx, "dbx.select", SelectCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR) 964 | return REDISMODULE_ERR; 965 | 966 | if (RedisModule_CreateCommand(ctx, "dbx.insert", InsertCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) 967 | return REDISMODULE_ERR; 968 | 969 | if (RedisModule_CreateCommand(ctx, "dbx.delete", DeleteCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) 970 | return REDISMODULE_ERR; 971 | 972 | // Register the command 973 | if (RedisModule_CreateCommand(ctx, "dbx", ExecCommand, "write deny-oom", 1, 1, 1) == REDISMODULE_ERR) 974 | return REDISMODULE_ERR; 975 | 976 | return REDISMODULE_OK; 977 | } 978 | --------------------------------------------------------------------------------