1 /* Helloworld module -- A few examples of the Redis Modules API in the form
2  * of commands showing how to accomplish common tasks.
3  *
4  * This module does not do anything useful, if not for a few commands. The
5  * examples are designed in order to show the API.
6  *
7  * -----------------------------------------------------------------------------
8  *
9  * Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions are met:
14  *
15  *   * Redistributions of source code must retain the above copyright notice,
16  *     this list of conditions and the following disclaimer.
17  *   * Redistributions in binary form must reproduce the above copyright
18  *     notice, this list of conditions and the following disclaimer in the
19  *     documentation and/or other materials provided with the distribution.
20  *   * Neither the name of Redis nor the names of its contributors may be used
21  *     to endorse or promote products derived from this software without
22  *     specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 #include "../redismodule.h"
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <ctype.h>
41 #include <string.h>
42 
43 /* HELLO.SIMPLE is among the simplest commands you can implement.
44  * It just returns the currently selected DB id, a functionality which is
45  * missing in Redis. The command uses two important API calls: one to
46  * fetch the currently selected DB, the other in order to send the client
47  * an integer reply as response. */
HelloSimple_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)48 int HelloSimple_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
49     REDISMODULE_NOT_USED(argv);
50     REDISMODULE_NOT_USED(argc);
51     RedisModule_ReplyWithLongLong(ctx,RedisModule_GetSelectedDb(ctx));
52     return REDISMODULE_OK;
53 }
54 
55 /* HELLO.PUSH.NATIVE re-implements RPUSH, and shows the low level modules API
56  * where you can "open" keys, make low level operations, create new keys by
57  * pushing elements into non-existing keys, and so forth.
58  *
59  * You'll find this command to be roughly as fast as the actual RPUSH
60  * command. */
HelloPushNative_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)61 int HelloPushNative_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
62 {
63     if (argc != 3) return RedisModule_WrongArity(ctx);
64 
65     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
66         REDISMODULE_READ|REDISMODULE_WRITE);
67 
68     RedisModule_ListPush(key,REDISMODULE_LIST_TAIL,argv[2]);
69     size_t newlen = RedisModule_ValueLength(key);
70     RedisModule_CloseKey(key);
71     RedisModule_ReplyWithLongLong(ctx,newlen);
72     return REDISMODULE_OK;
73 }
74 
75 /* HELLO.PUSH.CALL implements RPUSH using an higher level approach, calling
76  * a Redis command instead of working with the key in a low level way. This
77  * approach is useful when you need to call Redis commands that are not
78  * available as low level APIs, or when you don't need the maximum speed
79  * possible but instead prefer implementation simplicity. */
HelloPushCall_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)80 int HelloPushCall_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
81 {
82     if (argc != 3) return RedisModule_WrongArity(ctx);
83 
84     RedisModuleCallReply *reply;
85 
86     reply = RedisModule_Call(ctx,"RPUSH","ss",argv[1],argv[2]);
87     long long len = RedisModule_CallReplyInteger(reply);
88     RedisModule_FreeCallReply(reply);
89     RedisModule_ReplyWithLongLong(ctx,len);
90     return REDISMODULE_OK;
91 }
92 
93 /* HELLO.PUSH.CALL2
94  * This is exactly as HELLO.PUSH.CALL, but shows how we can reply to the
95  * client using directly a reply object that Call() returned. */
HelloPushCall2_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)96 int HelloPushCall2_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
97 {
98     if (argc != 3) return RedisModule_WrongArity(ctx);
99 
100     RedisModuleCallReply *reply;
101 
102     reply = RedisModule_Call(ctx,"RPUSH","ss",argv[1],argv[2]);
103     RedisModule_ReplyWithCallReply(ctx,reply);
104     RedisModule_FreeCallReply(reply);
105     return REDISMODULE_OK;
106 }
107 
108 /* HELLO.LIST.SUM.LEN returns the total length of all the items inside
109  * a Redis list, by using the high level Call() API.
110  * This command is an example of the array reply access. */
HelloListSumLen_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)111 int HelloListSumLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
112 {
113     if (argc != 2) return RedisModule_WrongArity(ctx);
114 
115     RedisModuleCallReply *reply;
116 
117     reply = RedisModule_Call(ctx,"LRANGE","sll",argv[1],(long long)0,(long long)-1);
118     size_t strlen = 0;
119     size_t items = RedisModule_CallReplyLength(reply);
120     size_t j;
121     for (j = 0; j < items; j++) {
122         RedisModuleCallReply *ele = RedisModule_CallReplyArrayElement(reply,j);
123         strlen += RedisModule_CallReplyLength(ele);
124     }
125     RedisModule_FreeCallReply(reply);
126     RedisModule_ReplyWithLongLong(ctx,strlen);
127     return REDISMODULE_OK;
128 }
129 
130 /* HELLO.LIST.SPLICE srclist dstlist count
131  * Moves 'count' elements from the tail of 'srclist' to the head of
132  * 'dstlist'. If less than count elements are available, it moves as much
133  * elements as possible. */
HelloListSplice_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)134 int HelloListSplice_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
135     if (argc != 4) return RedisModule_WrongArity(ctx);
136 
137     RedisModuleKey *srckey = RedisModule_OpenKey(ctx,argv[1],
138         REDISMODULE_READ|REDISMODULE_WRITE);
139     RedisModuleKey *dstkey = RedisModule_OpenKey(ctx,argv[2],
140         REDISMODULE_READ|REDISMODULE_WRITE);
141 
142     /* Src and dst key must be empty or lists. */
143     if ((RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_LIST &&
144          RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_EMPTY) ||
145         (RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_LIST &&
146          RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_EMPTY))
147     {
148         RedisModule_CloseKey(srckey);
149         RedisModule_CloseKey(dstkey);
150         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
151     }
152 
153     long long count;
154     if ((RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) ||
155         (count < 0)) {
156         RedisModule_CloseKey(srckey);
157         RedisModule_CloseKey(dstkey);
158         return RedisModule_ReplyWithError(ctx,"ERR invalid count");
159     }
160 
161     while(count-- > 0) {
162         RedisModuleString *ele;
163 
164         ele = RedisModule_ListPop(srckey,REDISMODULE_LIST_TAIL);
165         if (ele == NULL) break;
166         RedisModule_ListPush(dstkey,REDISMODULE_LIST_HEAD,ele);
167         RedisModule_FreeString(ctx,ele);
168     }
169 
170     size_t len = RedisModule_ValueLength(srckey);
171     RedisModule_CloseKey(srckey);
172     RedisModule_CloseKey(dstkey);
173     RedisModule_ReplyWithLongLong(ctx,len);
174     return REDISMODULE_OK;
175 }
176 
177 /* Like the HELLO.LIST.SPLICE above, but uses automatic memory management
178  * in order to avoid freeing stuff. */
HelloListSpliceAuto_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)179 int HelloListSpliceAuto_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
180     if (argc != 4) return RedisModule_WrongArity(ctx);
181 
182     RedisModule_AutoMemory(ctx);
183 
184     RedisModuleKey *srckey = RedisModule_OpenKey(ctx,argv[1],
185         REDISMODULE_READ|REDISMODULE_WRITE);
186     RedisModuleKey *dstkey = RedisModule_OpenKey(ctx,argv[2],
187         REDISMODULE_READ|REDISMODULE_WRITE);
188 
189     /* Src and dst key must be empty or lists. */
190     if ((RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_LIST &&
191          RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_EMPTY) ||
192         (RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_LIST &&
193          RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_EMPTY))
194     {
195         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
196     }
197 
198     long long count;
199     if ((RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) ||
200         (count < 0))
201     {
202         return RedisModule_ReplyWithError(ctx,"ERR invalid count");
203     }
204 
205     while(count-- > 0) {
206         RedisModuleString *ele;
207 
208         ele = RedisModule_ListPop(srckey,REDISMODULE_LIST_TAIL);
209         if (ele == NULL) break;
210         RedisModule_ListPush(dstkey,REDISMODULE_LIST_HEAD,ele);
211     }
212 
213     size_t len = RedisModule_ValueLength(srckey);
214     RedisModule_ReplyWithLongLong(ctx,len);
215     return REDISMODULE_OK;
216 }
217 
218 /* HELLO.RAND.ARRAY <count>
219  * Shows how to generate arrays as commands replies.
220  * It just outputs <count> random numbers. */
HelloRandArray_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)221 int HelloRandArray_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
222     if (argc != 2) return RedisModule_WrongArity(ctx);
223     long long count;
224     if (RedisModule_StringToLongLong(argv[1],&count) != REDISMODULE_OK ||
225         count < 0)
226         return RedisModule_ReplyWithError(ctx,"ERR invalid count");
227 
228     /* To reply with an array, we call RedisModule_ReplyWithArray() followed
229      * by other "count" calls to other reply functions in order to generate
230      * the elements of the array. */
231     RedisModule_ReplyWithArray(ctx,count);
232     while(count--) RedisModule_ReplyWithLongLong(ctx,rand());
233     return REDISMODULE_OK;
234 }
235 
236 /* This is a simple command to test replication. Because of the "!" modified
237  * in the RedisModule_Call() call, the two INCRs get replicated.
238  * Also note how the ECHO is replicated in an unexpected position (check
239  * comments the function implementation). */
HelloRepl1_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)240 int HelloRepl1_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
241 {
242     REDISMODULE_NOT_USED(argv);
243     REDISMODULE_NOT_USED(argc);
244     RedisModule_AutoMemory(ctx);
245 
246     /* This will be replicated *after* the two INCR statements, since
247      * the Call() replication has precedence, so the actual replication
248      * stream will be:
249      *
250      * MULTI
251      * INCR foo
252      * INCR bar
253      * ECHO c foo
254      * EXEC
255      */
256     RedisModule_Replicate(ctx,"ECHO","c","foo");
257 
258     /* Using the "!" modifier we replicate the command if it
259      * modified the dataset in some way. */
260     RedisModule_Call(ctx,"INCR","c!","foo");
261     RedisModule_Call(ctx,"INCR","c!","bar");
262 
263     RedisModule_ReplyWithLongLong(ctx,0);
264 
265     return REDISMODULE_OK;
266 }
267 
268 /* Another command to show replication. In this case, we call
269  * RedisModule_ReplicateVerbatim() to mean we want just the command to be
270  * propagated to slaves / AOF exactly as it was called by the user.
271  *
272  * This command also shows how to work with string objects.
273  * It takes a list, and increments all the elements (that must have
274  * a numerical value) by 1, returning the sum of all the elements
275  * as reply.
276  *
277  * Usage: HELLO.REPL2 <list-key> */
HelloRepl2_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)278 int HelloRepl2_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
279     if (argc != 2) return RedisModule_WrongArity(ctx);
280 
281     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
282     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
283         REDISMODULE_READ|REDISMODULE_WRITE);
284 
285     if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST)
286         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
287 
288     size_t listlen = RedisModule_ValueLength(key);
289     long long sum = 0;
290 
291     /* Rotate and increment. */
292     while(listlen--) {
293         RedisModuleString *ele = RedisModule_ListPop(key,REDISMODULE_LIST_TAIL);
294         long long val;
295         if (RedisModule_StringToLongLong(ele,&val) != REDISMODULE_OK) val = 0;
296         val++;
297         sum += val;
298         RedisModuleString *newele = RedisModule_CreateStringFromLongLong(ctx,val);
299         RedisModule_ListPush(key,REDISMODULE_LIST_HEAD,newele);
300     }
301     RedisModule_ReplyWithLongLong(ctx,sum);
302     RedisModule_ReplicateVerbatim(ctx);
303     return REDISMODULE_OK;
304 }
305 
306 /* This is an example of strings DMA access. Given a key containing a string
307  * it toggles the case of each character from lower to upper case or the
308  * other way around.
309  *
310  * No automatic memory management is used in this example (for the sake
311  * of variety).
312  *
313  * HELLO.TOGGLE.CASE key */
HelloToggleCase_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)314 int HelloToggleCase_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
315     if (argc != 2) return RedisModule_WrongArity(ctx);
316 
317     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
318         REDISMODULE_READ|REDISMODULE_WRITE);
319 
320     int keytype = RedisModule_KeyType(key);
321     if (keytype != REDISMODULE_KEYTYPE_STRING &&
322         keytype != REDISMODULE_KEYTYPE_EMPTY)
323     {
324         RedisModule_CloseKey(key);
325         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
326     }
327 
328     if (keytype == REDISMODULE_KEYTYPE_STRING) {
329         size_t len, j;
330         char *s = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
331         for (j = 0; j < len; j++) {
332             if (isupper(s[j])) {
333                 s[j] = tolower(s[j]);
334             } else {
335                 s[j] = toupper(s[j]);
336             }
337         }
338     }
339 
340     RedisModule_CloseKey(key);
341     RedisModule_ReplyWithSimpleString(ctx,"OK");
342     RedisModule_ReplicateVerbatim(ctx);
343     return REDISMODULE_OK;
344 }
345 
346 /* HELLO.MORE.EXPIRE key milliseconds.
347  *
348  * If the key has already an associated TTL, extends it by "milliseconds"
349  * milliseconds. Otherwise no operation is performed. */
HelloMoreExpire_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)350 int HelloMoreExpire_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
351     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
352     if (argc != 3) return RedisModule_WrongArity(ctx);
353 
354     mstime_t addms, expire;
355 
356     if (RedisModule_StringToLongLong(argv[2],&addms) != REDISMODULE_OK)
357         return RedisModule_ReplyWithError(ctx,"ERR invalid expire time");
358 
359     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
360         REDISMODULE_READ|REDISMODULE_WRITE);
361     expire = RedisModule_GetExpire(key);
362     if (expire != REDISMODULE_NO_EXPIRE) {
363         expire += addms;
364         RedisModule_SetExpire(key,expire);
365     }
366     return RedisModule_ReplyWithSimpleString(ctx,"OK");
367 }
368 
369 /* HELLO.ZSUMRANGE key startscore endscore
370  * Return the sum of all the scores elements between startscore and endscore.
371  *
372  * The computation is performed two times, one time from start to end and
373  * another time backward. The two scores, returned as a two element array,
374  * should match.*/
HelloZsumRange_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)375 int HelloZsumRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
376     double score_start, score_end;
377     if (argc != 4) return RedisModule_WrongArity(ctx);
378 
379     if (RedisModule_StringToDouble(argv[2],&score_start) != REDISMODULE_OK ||
380         RedisModule_StringToDouble(argv[3],&score_end) != REDISMODULE_OK)
381     {
382         return RedisModule_ReplyWithError(ctx,"ERR invalid range");
383     }
384 
385     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
386         REDISMODULE_READ|REDISMODULE_WRITE);
387     if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET) {
388         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
389     }
390 
391     double scoresum_a = 0;
392     double scoresum_b = 0;
393 
394     RedisModule_ZsetFirstInScoreRange(key,score_start,score_end,0,0);
395     while(!RedisModule_ZsetRangeEndReached(key)) {
396         double score;
397         RedisModuleString *ele = RedisModule_ZsetRangeCurrentElement(key,&score);
398         RedisModule_FreeString(ctx,ele);
399         scoresum_a += score;
400         RedisModule_ZsetRangeNext(key);
401     }
402     RedisModule_ZsetRangeStop(key);
403 
404     RedisModule_ZsetLastInScoreRange(key,score_start,score_end,0,0);
405     while(!RedisModule_ZsetRangeEndReached(key)) {
406         double score;
407         RedisModuleString *ele = RedisModule_ZsetRangeCurrentElement(key,&score);
408         RedisModule_FreeString(ctx,ele);
409         scoresum_b += score;
410         RedisModule_ZsetRangePrev(key);
411     }
412 
413     RedisModule_ZsetRangeStop(key);
414 
415     RedisModule_CloseKey(key);
416 
417     RedisModule_ReplyWithArray(ctx,2);
418     RedisModule_ReplyWithDouble(ctx,scoresum_a);
419     RedisModule_ReplyWithDouble(ctx,scoresum_b);
420     return REDISMODULE_OK;
421 }
422 
423 /* HELLO.LEXRANGE key min_lex max_lex min_age max_age
424  * This command expects a sorted set stored at key in the following form:
425  * - All the elements have score 0.
426  * - Elements are pairs of "<name>:<age>", for example "Anna:52".
427  * The command will return all the sorted set items that are lexicographically
428  * between the specified range (using the same format as ZRANGEBYLEX)
429  * and having an age between min_age and max_age. */
HelloLexRange_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)430 int HelloLexRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
431     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
432 
433     if (argc != 6) return RedisModule_WrongArity(ctx);
434 
435     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
436         REDISMODULE_READ|REDISMODULE_WRITE);
437     if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET) {
438         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
439     }
440 
441     if (RedisModule_ZsetFirstInLexRange(key,argv[2],argv[3]) != REDISMODULE_OK) {
442         return RedisModule_ReplyWithError(ctx,"invalid range");
443     }
444 
445     int arraylen = 0;
446     RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN);
447     while(!RedisModule_ZsetRangeEndReached(key)) {
448         double score;
449         RedisModuleString *ele = RedisModule_ZsetRangeCurrentElement(key,&score);
450         RedisModule_ReplyWithString(ctx,ele);
451         RedisModule_FreeString(ctx,ele);
452         RedisModule_ZsetRangeNext(key);
453         arraylen++;
454     }
455     RedisModule_ZsetRangeStop(key);
456     RedisModule_ReplySetArrayLength(ctx,arraylen);
457     RedisModule_CloseKey(key);
458     return REDISMODULE_OK;
459 }
460 
461 /* HELLO.HCOPY key srcfield dstfield
462  * This is just an example command that sets the hash field dstfield to the
463  * same value of srcfield. If srcfield does not exist no operation is
464  * performed.
465  *
466  * The command returns 1 if the copy is performed (srcfield exists) otherwise
467  * 0 is returned. */
HelloHCopy_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)468 int HelloHCopy_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
469     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
470 
471     if (argc != 4) return RedisModule_WrongArity(ctx);
472     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
473         REDISMODULE_READ|REDISMODULE_WRITE);
474     int type = RedisModule_KeyType(key);
475     if (type != REDISMODULE_KEYTYPE_HASH &&
476         type != REDISMODULE_KEYTYPE_EMPTY)
477     {
478         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
479     }
480 
481     /* Get the old field value. */
482     RedisModuleString *oldval;
483     RedisModule_HashGet(key,REDISMODULE_HASH_NONE,argv[2],&oldval,NULL);
484     if (oldval) {
485         RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[3],oldval,NULL);
486     }
487     RedisModule_ReplyWithLongLong(ctx,oldval != NULL);
488     return REDISMODULE_OK;
489 }
490 
491 /* HELLO.LEFTPAD str len ch
492  * This is an implementation of the infamous LEFTPAD function, that
493  * was at the center of an issue with the npm modules system in March 2016.
494  *
495  * LEFTPAD is a good example of using a Redis Modules API called
496  * "pool allocator", that was a famous way to allocate memory in yet another
497  * open source project, the Apache web server.
498  *
499  * The concept is very simple: there is memory that is useful to allocate
500  * only in the context of serving a request, and must be freed anyway when
501  * the callback implementing the command returns. So in that case the module
502  * does not need to retain a reference to these allocations, it is just
503  * required to free the memory before returning. When this is the case the
504  * module can call RedisModule_PoolAlloc() instead, that works like malloc()
505  * but will automatically free the memory when the module callback returns.
506  *
507  * Note that PoolAlloc() does not necessarily require AutoMemory to be
508  * active. */
HelloLeftPad_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)509 int HelloLeftPad_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
510     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
511     long long padlen;
512 
513     if (argc != 4) return RedisModule_WrongArity(ctx);
514 
515     if ((RedisModule_StringToLongLong(argv[2],&padlen) != REDISMODULE_OK) ||
516         (padlen< 0)) {
517         return RedisModule_ReplyWithError(ctx,"ERR invalid padding length");
518     }
519     size_t strlen, chlen;
520     const char *str = RedisModule_StringPtrLen(argv[1], &strlen);
521     const char *ch = RedisModule_StringPtrLen(argv[3], &chlen);
522 
523     /* If the string is already larger than the target len, just return
524      * the string itself. */
525     if (strlen >= (size_t)padlen)
526         return RedisModule_ReplyWithString(ctx,argv[1]);
527 
528     /* Padding must be a single character in this simple implementation. */
529     if (chlen != 1)
530         return RedisModule_ReplyWithError(ctx,
531             "ERR padding must be a single char");
532 
533     /* Here we use our pool allocator, for our throw-away allocation. */
534     padlen -= strlen;
535     char *buf = RedisModule_PoolAlloc(ctx,padlen+strlen);
536     for (long long j = 0; j < padlen; j++) buf[j] = *ch;
537     memcpy(buf+padlen,str,strlen);
538 
539     RedisModule_ReplyWithStringBuffer(ctx,buf,padlen+strlen);
540     return REDISMODULE_OK;
541 }
542 
543 /* This function must be present on each Redis module. It is used in order to
544  * register the commands into the Redis server. */
RedisModule_OnLoad(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)545 int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
546     if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
547         == REDISMODULE_ERR) return REDISMODULE_ERR;
548 
549     /* Log the list of parameters passing loading the module. */
550     for (int j = 0; j < argc; j++) {
551         const char *s = RedisModule_StringPtrLen(argv[j],NULL);
552         printf("Module loaded with ARGV[%d] = %s\n", j, s);
553     }
554 
555     if (RedisModule_CreateCommand(ctx,"hello.simple",
556         HelloSimple_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
557         return REDISMODULE_ERR;
558 
559     if (RedisModule_CreateCommand(ctx,"hello.push.native",
560         HelloPushNative_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
561         return REDISMODULE_ERR;
562 
563     if (RedisModule_CreateCommand(ctx,"hello.push.call",
564         HelloPushCall_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
565         return REDISMODULE_ERR;
566 
567     if (RedisModule_CreateCommand(ctx,"hello.push.call2",
568         HelloPushCall2_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
569         return REDISMODULE_ERR;
570 
571     if (RedisModule_CreateCommand(ctx,"hello.list.sum.len",
572         HelloListSumLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
573         return REDISMODULE_ERR;
574 
575     if (RedisModule_CreateCommand(ctx,"hello.list.splice",
576         HelloListSplice_RedisCommand,"write deny-oom",1,2,1) == REDISMODULE_ERR)
577         return REDISMODULE_ERR;
578 
579     if (RedisModule_CreateCommand(ctx,"hello.list.splice.auto",
580         HelloListSpliceAuto_RedisCommand,
581         "write deny-oom",1,2,1) == REDISMODULE_ERR)
582         return REDISMODULE_ERR;
583 
584     if (RedisModule_CreateCommand(ctx,"hello.rand.array",
585         HelloRandArray_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
586         return REDISMODULE_ERR;
587 
588     if (RedisModule_CreateCommand(ctx,"hello.repl1",
589         HelloRepl1_RedisCommand,"write",0,0,0) == REDISMODULE_ERR)
590         return REDISMODULE_ERR;
591 
592     if (RedisModule_CreateCommand(ctx,"hello.repl2",
593         HelloRepl2_RedisCommand,"write",1,1,1) == REDISMODULE_ERR)
594         return REDISMODULE_ERR;
595 
596     if (RedisModule_CreateCommand(ctx,"hello.toggle.case",
597         HelloToggleCase_RedisCommand,"write",1,1,1) == REDISMODULE_ERR)
598         return REDISMODULE_ERR;
599 
600     if (RedisModule_CreateCommand(ctx,"hello.more.expire",
601         HelloMoreExpire_RedisCommand,"write",1,1,1) == REDISMODULE_ERR)
602         return REDISMODULE_ERR;
603 
604     if (RedisModule_CreateCommand(ctx,"hello.zsumrange",
605         HelloZsumRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
606         return REDISMODULE_ERR;
607 
608     if (RedisModule_CreateCommand(ctx,"hello.lexrange",
609         HelloLexRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
610         return REDISMODULE_ERR;
611 
612     if (RedisModule_CreateCommand(ctx,"hello.hcopy",
613         HelloHCopy_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
614         return REDISMODULE_ERR;
615 
616     if (RedisModule_CreateCommand(ctx,"hello.leftpad",
617         HelloLeftPad_RedisCommand,"",1,1,1) == REDISMODULE_ERR)
618         return REDISMODULE_ERR;
619 
620     return REDISMODULE_OK;
621 }
622