2 * Copyright (c) 2018-2019 Nokia.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "redismodule.h"
23 #include "../../redismodule/include/redismodule.h"
26 #include "exstringsStub.h"
29 /* make sure the response is not NULL or an error.
30 sends the error to the client and exit the current function if its */
31 #define ASSERT_NOERROR(r) \
33 return RedisModule_ReplyWithError(ctx,"ERR reply is NULL"); \
34 } else if (RedisModule_CallReplyType(r) == REDISMODULE_REPLY_ERROR) { \
35 RedisModule_ReplyWithCallReply(ctx,r); \
36 RedisModule_FreeCallReply(r); \
37 return REDISMODULE_ERR; \
41 #define OBJ_OP_XX (1<<1) /* OP if key exist */
42 #define OBJ_OP_NX (1<<2) /* OP if key not exist */
43 #define OBJ_OP_IE (1<<4) /* OP if equal old value */
44 #define OBJ_OP_NE (1<<5) /* OP if not equal old value */
46 int getKeyType(RedisModuleCtx *ctx, RedisModuleString *key_str)
48 RedisModuleKey *key = RedisModule_OpenKey(ctx, key_str, REDISMODULE_READ);
49 int type = RedisModule_KeyType(key);
50 RedisModule_CloseKey(key);
54 bool replyContentsEqualString(RedisModuleCallReply *reply, RedisModuleString *expected_value)
56 size_t replylen = 0, expectedlen = 0;
57 const char *expectedval = RedisModule_StringPtrLen(expected_value, &expectedlen);
58 const char *replyval = RedisModule_CallReplyStringPtr(reply, &replylen);
60 expectedlen == replylen &&
61 !strncmp(expectedval, replyval, replylen);
64 typedef struct _SetParams {
65 RedisModuleString **key_val_pairs;
69 typedef struct _PubParams {
70 RedisModuleString **channel_msg_pairs;
74 void multiPubCommand(RedisModuleCtx *ctx, PubParams* pubParams)
76 RedisModuleCallReply *reply = NULL;
77 for (unsigned int i = 0 ; i < pubParams->length ; i += 2) {
78 reply = RedisModule_Call(ctx, "PUBLISH", "v", pubParams->channel_msg_pairs + i, 2);
79 RedisModule_FreeCallReply(reply);
83 int setStringGenericCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
84 int argc, const int flag)
86 RedisModuleString *oldvalstr = NULL;
87 RedisModuleCallReply *reply = NULL;
90 return RedisModule_WrongArity(ctx);
94 /*Check if key type is string*/
95 RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
97 int type = RedisModule_KeyType(key);
98 RedisModule_CloseKey(key);
100 if (type == REDISMODULE_KEYTYPE_EMPTY) {
101 if (flag == OBJ_OP_IE){
102 RedisModule_ReplyWithNull(ctx);
103 return REDISMODULE_OK;
105 } else if (type != REDISMODULE_KEYTYPE_STRING) {
106 return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
110 reply = RedisModule_Call(ctx, "GET", "s", argv[1]);
111 ASSERT_NOERROR(reply)
112 size_t curlen=0, oldvallen=0;
113 const char *oldval = RedisModule_StringPtrLen(oldvalstr, &oldvallen);
114 const char *curval = RedisModule_CallReplyStringPtr(reply, &curlen);
115 if (((flag == OBJ_OP_IE) &&
116 (!curval || (oldvallen != curlen) || strncmp(oldval, curval, curlen)))
118 ((flag == OBJ_OP_NE) && curval && (oldvallen == curlen) &&
119 !strncmp(oldval, curval, curlen))) {
120 RedisModule_FreeCallReply(reply);
121 return RedisModule_ReplyWithNull(ctx);
123 RedisModule_FreeCallReply(reply);
125 /* Prepare the arguments for the command. */
126 int i, j=0, cmdargc=argc-2;
127 RedisModuleString *cmdargv[cmdargc];
128 for (i = 1; i < argc; i++) {
131 cmdargv[j++] = argv[i];
134 /* Call the command and pass back the reply. */
135 reply = RedisModule_Call(ctx, "SET", "v!", cmdargv, cmdargc);
136 ASSERT_NOERROR(reply)
137 RedisModule_ReplyWithCallReply(ctx, reply);
139 RedisModule_FreeCallReply(reply);
140 return REDISMODULE_OK;
143 int SetIE_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
145 return setStringGenericCommand(ctx, argv, argc, OBJ_OP_IE);
148 int SetNE_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
150 return setStringGenericCommand(ctx, argv, argc, OBJ_OP_NE);
153 int delStringGenericCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
154 int argc, const int flag)
156 RedisModuleString *oldvalstr = NULL;
157 RedisModuleCallReply *reply = NULL;
162 return RedisModule_WrongArity(ctx);
164 /*Check if key type is string*/
165 RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
167 int type = RedisModule_KeyType(key);
168 RedisModule_CloseKey(key);
170 if (type == REDISMODULE_KEYTYPE_EMPTY) {
171 return RedisModule_ReplyWithLongLong(ctx, 0);
172 } else if (type != REDISMODULE_KEYTYPE_STRING) {
173 return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
177 reply = RedisModule_Call(ctx, "GET", "s", argv[1]);
178 ASSERT_NOERROR(reply)
179 size_t curlen = 0, oldvallen = 0;
180 const char *oldval = RedisModule_StringPtrLen(oldvalstr, &oldvallen);
181 const char *curval = RedisModule_CallReplyStringPtr(reply, &curlen);
182 if (((flag == OBJ_OP_IE) &&
183 (!curval || (oldvallen != curlen) || strncmp(oldval, curval, curlen)))
185 ((flag == OBJ_OP_NE) && curval && (oldvallen == curlen) &&
186 !strncmp(oldval, curval, curlen))) {
187 RedisModule_FreeCallReply(reply);
188 return RedisModule_ReplyWithLongLong(ctx, 0);
190 RedisModule_FreeCallReply(reply);
192 /* Prepare the arguments for the command. */
194 RedisModuleString *cmdargv[1];
195 cmdargv[0] = argv[1];
197 /* Call the command and pass back the reply. */
198 reply = RedisModule_Call(ctx, "UNLINK", "v!", cmdargv, cmdargc);
199 ASSERT_NOERROR(reply)
200 RedisModule_ReplyWithCallReply(ctx, reply);
202 RedisModule_FreeCallReply(reply);
203 return REDISMODULE_OK;
206 int DelIE_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
208 return delStringGenericCommand(ctx, argv, argc, OBJ_OP_IE);
211 int DelNE_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
213 return delStringGenericCommand(ctx, argv, argc, OBJ_OP_NE);
216 int NGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
218 RedisModuleCallReply *reply = NULL;
221 return RedisModule_WrongArity(ctx);
223 /* Call the command to get keys with pattern. */
224 reply = RedisModule_Call(ctx, "KEYS", "s", argv[1]);
225 ASSERT_NOERROR(reply)
227 /* Prepare the arguments for the command. */
228 size_t items = RedisModule_CallReplyLength(reply);
230 //RedisModule_ReplyWithArray(ctx, items);
231 RedisModule_ReplyWithCallReply(ctx, reply);
232 RedisModule_FreeCallReply(reply);
235 RedisModuleString *cmdargv[items];
237 for (j = 0; j < items; j++) {
238 RedisModuleString *rms = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(reply, j));
241 /*Assume all keys via SDL is string type for sake of saving time*/
243 /*Check if key type is string*/
244 RedisModuleKey *key = RedisModule_OpenKey(ctx, rms ,REDISMODULE_READ);
247 int type = RedisModule_KeyType(key);
248 RedisModule_CloseKey(key);
249 if (type == REDISMODULE_KEYTYPE_STRING) {
253 RedisModule_CloseKey(key);
257 RedisModule_FreeCallReply(reply);
259 reply = RedisModule_Call(ctx, "MGET", "v", cmdargv, i);
260 ASSERT_NOERROR(reply)
261 items = RedisModule_CallReplyLength(reply);
262 RedisModule_ReplyWithArray(ctx, i*2);
263 for (j = 0; (j<items && j<i); j++) {
264 RedisModule_ReplyWithString(ctx, cmdargv[j]);
265 RedisModule_ReplyWithString(ctx, RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(reply, j)));
268 RedisModule_FreeCallReply(reply);
271 return REDISMODULE_OK;
274 int NDel_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
276 RedisModuleCallReply *reply = NULL;
279 return RedisModule_WrongArity(ctx);
281 /* Call the command to get keys with pattern. */
282 reply = RedisModule_Call(ctx, "KEYS", "s", argv[1]);
283 ASSERT_NOERROR(reply)
285 /* Prepare the arguments for the command. */
286 size_t items = RedisModule_CallReplyLength(reply);
288 RedisModule_ReplyWithLongLong(ctx, 0);
289 RedisModule_FreeCallReply(reply);
292 RedisModuleString *cmdargv[items];
294 for (j = 0; j < items; j++) {
295 RedisModuleString *rms = RedisModule_CreateStringFromCallReply(RedisModule_CallReplyArrayElement(reply, j));
298 /*Assume all keys via SDL is string type for sake of saving time*/
300 //Check if key type is string
301 RedisModuleKey *key = RedisModule_OpenKey(ctx, rms ,REDISMODULE_READ);
304 int type = RedisModule_KeyType(key);
305 RedisModule_CloseKey(key);
306 if (type == REDISMODULE_KEYTYPE_STRING) {
310 RedisModule_CloseKey(key);
314 RedisModule_FreeCallReply(reply);
316 reply = RedisModule_Call(ctx, "UNLINK", "v!", cmdargv, i);
317 ASSERT_NOERROR(reply)
318 RedisModule_ReplyWithCallReply(ctx, reply);
319 RedisModule_FreeCallReply(reply);
323 return REDISMODULE_OK;
326 int setPubStringCommon(RedisModuleCtx *ctx, SetParams* setParamsPtr, PubParams* pubParamsPtr)
328 RedisModuleCallReply *setReply;
329 setReply = RedisModule_Call(ctx, "MSET", "v!", setParamsPtr->key_val_pairs, setParamsPtr->length);
330 ASSERT_NOERROR(setReply)
331 int replytype = RedisModule_CallReplyType(setReply);
332 if (replytype == REDISMODULE_REPLY_NULL) {
333 RedisModule_ReplyWithNull(ctx);
335 multiPubCommand(ctx, pubParamsPtr);
336 RedisModule_ReplyWithCallReply(ctx, setReply);
338 RedisModule_FreeCallReply(setReply);
339 return REDISMODULE_OK;
342 int SetPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
344 if (argc < 5 || (argc % 2) == 0)
345 return RedisModule_WrongArity(ctx);
347 SetParams setParams = {
348 .key_val_pairs = argv + 1,
351 PubParams pubParams = {
352 .channel_msg_pairs = argv + argc - 2,
356 return setPubStringCommon(ctx, &setParams, &pubParams);
359 int SetMPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
361 if (argc < 7 || (argc % 2) == 0)
362 return RedisModule_WrongArity(ctx);
364 long long setPairsCount, pubPairsCount;
365 RedisModule_StringToLongLong(argv[1], &setPairsCount);
366 RedisModule_StringToLongLong(argv[2], &pubPairsCount);
367 if (setPairsCount < 1 || pubPairsCount < 1)
368 return RedisModule_ReplyWithError(ctx, "ERR SET_PAIR_COUNT and PUB_PAIR_COUNT must be greater than zero");
370 long long setLen, pubLen;
371 setLen = 2*setPairsCount;
372 pubLen = 2*pubPairsCount;
373 if (setLen + pubLen + 3 != argc)
374 return RedisModule_ReplyWithError(ctx, "ERR SET_PAIR_COUNT or PUB_PAIR_COUNT do not match the total pair count");
376 SetParams setParams = {
377 .key_val_pairs = argv + 3,
380 PubParams pubParams = {
381 .channel_msg_pairs = argv + 3 + setParams.length,
385 return setPubStringCommon(ctx, &setParams, &pubParams);
388 int setIENEPubStringCommon(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int flag)
390 if (argc < 6 || (argc % 2) != 0)
391 return RedisModule_WrongArity(ctx);
393 SetParams setParams = {
394 .key_val_pairs = argv + 1,
397 PubParams pubParams = {
398 .channel_msg_pairs = argv + 4,
401 RedisModuleString *key = setParams.key_val_pairs[0];
402 RedisModuleString *oldvalstr = argv[3];
404 int type = getKeyType(ctx, key);
405 if (flag == OBJ_OP_IE && type == REDISMODULE_KEYTYPE_EMPTY) {
406 return RedisModule_ReplyWithNull(ctx);
407 } else if (type != REDISMODULE_KEYTYPE_STRING && type != REDISMODULE_KEYTYPE_EMPTY) {
408 return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
411 RedisModuleCallReply *reply = RedisModule_Call(ctx, "GET", "s", key);
412 ASSERT_NOERROR(reply)
413 bool is_equal = replyContentsEqualString(reply, oldvalstr);
414 RedisModule_FreeCallReply(reply);
415 if ((flag == OBJ_OP_IE && !is_equal) ||
416 (flag == OBJ_OP_NE && is_equal)) {
417 return RedisModule_ReplyWithNull(ctx);
420 return setPubStringCommon(ctx, &setParams, &pubParams);
423 int SetNEPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
425 return setIENEPubStringCommon(ctx, argv, argc, OBJ_OP_NE);
428 int SetIEPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
430 return setIENEPubStringCommon(ctx, argv, argc, OBJ_OP_IE);
433 int setXXNXPubStringCommon(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int flag)
435 if (argc < 5 || (argc % 2) == 0)
436 return RedisModule_WrongArity(ctx);
438 SetParams setParams = {
439 .key_val_pairs = argv + 1,
442 PubParams pubParams = {
443 .channel_msg_pairs = argv + 3,
446 RedisModuleString *key = setParams.key_val_pairs[0];
448 int type = getKeyType(ctx, key);
449 if ((flag == OBJ_OP_XX && type == REDISMODULE_KEYTYPE_EMPTY) ||
450 (flag == OBJ_OP_NX && type == REDISMODULE_KEYTYPE_STRING)) {
451 return RedisModule_ReplyWithNull(ctx);
452 } else if (type != REDISMODULE_KEYTYPE_STRING && type != REDISMODULE_KEYTYPE_EMPTY) {
453 RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
454 return REDISMODULE_OK;
457 return setPubStringCommon(ctx, &setParams, &pubParams);
460 int SetNXPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
462 return setXXNXPubStringCommon(ctx, argv, argc, OBJ_OP_NX);
465 int SetXXPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
467 return setXXNXPubStringCommon(ctx, argv, argc, OBJ_OP_XX);
470 int delPubStringGenericCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
471 int argc, const int flag)
473 RedisModuleString *oldvalstr = NULL, *channel = NULL, *message = NULL;
474 RedisModuleCallReply *reply = NULL;
476 if (flag == OBJ_OP_NO) {
478 return RedisModule_WrongArity(ctx);
480 channel = argv[argc-2];
481 message = argv[argc-1];
485 return RedisModule_WrongArity(ctx);
493 if (flag != OBJ_OP_NO) {
494 /*Check if key type is string*/
495 RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
497 int type = RedisModule_KeyType(key);
498 RedisModule_CloseKey(key);
500 if (type == REDISMODULE_KEYTYPE_EMPTY) {
501 return RedisModule_ReplyWithLongLong(ctx, 0);
502 } else if (type != REDISMODULE_KEYTYPE_STRING) {
503 return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
507 if (flag == OBJ_OP_IE || flag == OBJ_OP_NE) {
509 reply = RedisModule_Call(ctx, "GET", "s", argv[1]);
510 ASSERT_NOERROR(reply)
511 size_t curlen = 0, oldvallen = 0;
512 const char *oldval = RedisModule_StringPtrLen(oldvalstr, &oldvallen);
513 const char *curval = RedisModule_CallReplyStringPtr(reply, &curlen);
514 if (((flag == OBJ_OP_IE) &&
515 (!curval || (oldvallen != curlen) || strncmp(oldval, curval, curlen)))
517 ((flag == OBJ_OP_NE) && curval && (oldvallen == curlen) &&
518 !strncmp(oldval, curval, curlen))) {
519 RedisModule_FreeCallReply(reply);
520 return RedisModule_ReplyWithLongLong(ctx, 0);
522 RedisModule_FreeCallReply(reply);
526 /* Prepare the arguments for the command. */
527 int i, j=0, cmdargc=argc-3;
528 RedisModuleString *cmdargv[cmdargc];
529 for (i = 1; i < argc-2; i++) {
530 if ((flag == OBJ_OP_IE || flag == OBJ_OP_NE) && (i == 2))
532 cmdargv[j++] = argv[i];
535 /* Call the command and pass back the reply. */
536 reply = RedisModule_Call(ctx, "UNLINK", "v!", cmdargv, j);
537 ASSERT_NOERROR(reply)
538 int replytype = RedisModule_CallReplyType(reply);
539 if (replytype == REDISMODULE_REPLY_NULL) {
540 RedisModule_ReplyWithNull(ctx);
542 else if (RedisModule_CallReplyInteger(reply) == 0) {
543 RedisModule_ReplyWithCallReply(ctx, reply);
546 cmdargv[0] = channel;
547 cmdargv[1] = message;
548 RedisModuleCallReply *pubreply = RedisModule_Call(ctx, "PUBLISH", "v", cmdargv, cmdargc);
549 RedisModule_FreeCallReply(pubreply);
550 RedisModule_ReplyWithCallReply(ctx, reply);
553 RedisModule_FreeCallReply(reply);
554 return REDISMODULE_OK;
557 int DelPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
559 return delPubStringGenericCommand(ctx, argv, argc, OBJ_OP_NO);
562 int DelIEPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
564 return delPubStringGenericCommand(ctx, argv, argc, OBJ_OP_IE);
567 int DelNEPub_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
569 return delPubStringGenericCommand(ctx, argv, argc, OBJ_OP_NE);
572 /* This function must be present on each Redis module. It is used in order to
573 * register the commands into the Redis server. */
574 int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
575 REDISMODULE_NOT_USED(argv);
576 REDISMODULE_NOT_USED(argc);
578 if (RedisModule_Init(ctx,"exstrings",1,REDISMODULE_APIVER_1)
579 == REDISMODULE_ERR) return REDISMODULE_ERR;
581 if (RedisModule_CreateCommand(ctx,"setie",
582 SetIE_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
583 return REDISMODULE_ERR;
585 if (RedisModule_CreateCommand(ctx,"setne",
586 SetNE_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
587 return REDISMODULE_ERR;
589 if (RedisModule_CreateCommand(ctx,"delie",
590 DelIE_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
591 return REDISMODULE_ERR;
593 if (RedisModule_CreateCommand(ctx,"delne",
594 DelNE_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
595 return REDISMODULE_ERR;
597 if (RedisModule_CreateCommand(ctx,"nget",
598 NGet_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
599 return REDISMODULE_ERR;
601 if (RedisModule_CreateCommand(ctx,"ndel",
602 NDel_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
603 return REDISMODULE_ERR;
605 if (RedisModule_CreateCommand(ctx,"msetpub",
606 SetPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
607 return REDISMODULE_ERR;
609 if (RedisModule_CreateCommand(ctx,"msetmpub",
610 SetMPub_RedisCommand,"write deny-oom pubsub",1,1,1) == REDISMODULE_ERR)
611 return REDISMODULE_ERR;
613 if (RedisModule_CreateCommand(ctx,"setiepub",
614 SetIEPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
615 return REDISMODULE_ERR;
617 if (RedisModule_CreateCommand(ctx,"setnepub",
618 SetNEPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
619 return REDISMODULE_ERR;
621 if (RedisModule_CreateCommand(ctx,"setxxpub",
622 SetXXPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
623 return REDISMODULE_ERR;
625 if (RedisModule_CreateCommand(ctx,"setnxpub",
626 SetNXPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
627 return REDISMODULE_ERR;
629 if (RedisModule_CreateCommand(ctx,"delpub",
630 DelPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
631 return REDISMODULE_ERR;
633 if (RedisModule_CreateCommand(ctx,"deliepub",
634 DelIEPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
635 return REDISMODULE_ERR;
637 if (RedisModule_CreateCommand(ctx,"delnepub",
638 DelNEPub_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
639 return REDISMODULE_ERR;
641 return REDISMODULE_OK;