1 /* This file implements a new module native data type called "HELLOTYPE".
2  * The data structure implemented is a very simple ordered linked list of
3  * 64 bit integers, in order to have something that is real world enough, but
4  * at the same time, extremely simple to understand, to show how the API
5  * works, how a new data type is created, and how to write basic methods
6  * for RDB loading, saving and AOF rewriting.
7  *
8  * -----------------------------------------------------------------------------
9  *
10  * Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
11  * All rights reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions are met:
15  *
16  *   * Redistributions of source code must retain the above copyright notice,
17  *     this list of conditions and the following disclaimer.
18  *   * Redistributions in binary form must reproduce the above copyright
19  *     notice, this list of conditions and the following disclaimer in the
20  *     documentation and/or other materials provided with the distribution.
21  *   * Neither the name of Redis nor the names of its contributors may be used
22  *     to endorse or promote products derived from this software without
23  *     specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
29  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  * POSSIBILITY OF SUCH DAMAGE.
36  */
37 
38 #include "../redismodule.h"
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <ctype.h>
42 #include <string.h>
43 #include <stdint.h>
44 
45 static RedisModuleType *HelloType;
46 
47 /* ========================== Internal data structure  =======================
48  * This is just a linked list of 64 bit integers where elements are inserted
49  * in-place, so it's ordered. There is no pop/push operation but just insert
50  * because it is enough to show the implementation of new data types without
51  * making things complex. */
52 
53 struct HelloTypeNode {
54     int64_t value;
55     struct HelloTypeNode *next;
56 };
57 
58 struct HelloTypeObject {
59     struct HelloTypeNode *head;
60     size_t len; /* Number of elements added. */
61 };
62 
createHelloTypeObject(void)63 struct HelloTypeObject *createHelloTypeObject(void) {
64     struct HelloTypeObject *o;
65     o = RedisModule_Alloc(sizeof(*o));
66     o->head = NULL;
67     o->len = 0;
68     return o;
69 }
70 
HelloTypeInsert(struct HelloTypeObject * o,int64_t ele)71 void HelloTypeInsert(struct HelloTypeObject *o, int64_t ele) {
72     struct HelloTypeNode *next = o->head, *newnode, *prev = NULL;
73 
74     while(next && next->value < ele) {
75         prev = next;
76         next = next->next;
77     }
78     newnode = RedisModule_Alloc(sizeof(*newnode));
79     newnode->value = ele;
80     newnode->next = next;
81     if (prev) {
82         prev->next = newnode;
83     } else {
84         o->head = newnode;
85     }
86     o->len++;
87 }
88 
HelloTypeReleaseObject(struct HelloTypeObject * o)89 void HelloTypeReleaseObject(struct HelloTypeObject *o) {
90     struct HelloTypeNode *cur, *next;
91     cur = o->head;
92     while(cur) {
93         next = cur->next;
94         RedisModule_Free(cur);
95         cur = next;
96     }
97     RedisModule_Free(o);
98 }
99 
100 /* ========================= "hellotype" type commands ======================= */
101 
102 /* HELLOTYPE.INSERT key value */
HelloTypeInsert_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)103 int HelloTypeInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
104     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
105 
106     if (argc != 3) return RedisModule_WrongArity(ctx);
107     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
108         REDISMODULE_READ|REDISMODULE_WRITE);
109     int type = RedisModule_KeyType(key);
110     if (type != REDISMODULE_KEYTYPE_EMPTY &&
111         RedisModule_ModuleTypeGetType(key) != HelloType)
112     {
113         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
114     }
115 
116     long long value;
117     if ((RedisModule_StringToLongLong(argv[2],&value) != REDISMODULE_OK)) {
118         return RedisModule_ReplyWithError(ctx,"ERR invalid value: must be a signed 64 bit integer");
119     }
120 
121     /* Create an empty value object if the key is currently empty. */
122     struct HelloTypeObject *hto;
123     if (type == REDISMODULE_KEYTYPE_EMPTY) {
124         hto = createHelloTypeObject();
125         RedisModule_ModuleTypeSetValue(key,HelloType,hto);
126     } else {
127         hto = RedisModule_ModuleTypeGetValue(key);
128     }
129 
130     /* Insert the new element. */
131     HelloTypeInsert(hto,value);
132 
133     RedisModule_ReplyWithLongLong(ctx,hto->len);
134     RedisModule_ReplicateVerbatim(ctx);
135     return REDISMODULE_OK;
136 }
137 
138 /* HELLOTYPE.RANGE key first count */
HelloTypeRange_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)139 int HelloTypeRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
140     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
141 
142     if (argc != 4) return RedisModule_WrongArity(ctx);
143     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
144         REDISMODULE_READ|REDISMODULE_WRITE);
145     int type = RedisModule_KeyType(key);
146     if (type != REDISMODULE_KEYTYPE_EMPTY &&
147         RedisModule_ModuleTypeGetType(key) != HelloType)
148     {
149         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
150     }
151 
152     long long first, count;
153     if (RedisModule_StringToLongLong(argv[2],&first) != REDISMODULE_OK ||
154         RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK ||
155         first < 0 || count < 0)
156     {
157         return RedisModule_ReplyWithError(ctx,
158             "ERR invalid first or count parameters");
159     }
160 
161     struct HelloTypeObject *hto = RedisModule_ModuleTypeGetValue(key);
162     struct HelloTypeNode *node = hto ? hto->head : NULL;
163     RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
164     long long arraylen = 0;
165     while(node && count--) {
166         RedisModule_ReplyWithLongLong(ctx,node->value);
167         arraylen++;
168         node = node->next;
169     }
170     RedisModule_ReplySetArrayLength(ctx,arraylen);
171     return REDISMODULE_OK;
172 }
173 
174 /* HELLOTYPE.LEN key */
HelloTypeLen_RedisCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)175 int HelloTypeLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
176     RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
177 
178     if (argc != 2) return RedisModule_WrongArity(ctx);
179     RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
180         REDISMODULE_READ|REDISMODULE_WRITE);
181     int type = RedisModule_KeyType(key);
182     if (type != REDISMODULE_KEYTYPE_EMPTY &&
183         RedisModule_ModuleTypeGetType(key) != HelloType)
184     {
185         return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
186     }
187 
188     struct HelloTypeObject *hto = RedisModule_ModuleTypeGetValue(key);
189     RedisModule_ReplyWithLongLong(ctx,hto ? hto->len : 0);
190     return REDISMODULE_OK;
191 }
192 
193 
194 /* ========================== "hellotype" type methods ======================= */
195 
HelloTypeRdbLoad(RedisModuleIO * rdb,int encver)196 void *HelloTypeRdbLoad(RedisModuleIO *rdb, int encver) {
197     if (encver != 0) {
198         /* RedisModule_Log("warning","Can't load data with version %d", encver);*/
199         return NULL;
200     }
201     uint64_t elements = RedisModule_LoadUnsigned(rdb);
202     struct HelloTypeObject *hto = createHelloTypeObject();
203     while(elements--) {
204         int64_t ele = RedisModule_LoadSigned(rdb);
205         HelloTypeInsert(hto,ele);
206     }
207     return hto;
208 }
209 
HelloTypeRdbSave(RedisModuleIO * rdb,void * value)210 void HelloTypeRdbSave(RedisModuleIO *rdb, void *value) {
211     struct HelloTypeObject *hto = value;
212     struct HelloTypeNode *node = hto->head;
213     RedisModule_SaveUnsigned(rdb,hto->len);
214     while(node) {
215         RedisModule_SaveSigned(rdb,node->value);
216         node = node->next;
217     }
218 }
219 
HelloTypeAofRewrite(RedisModuleIO * aof,RedisModuleString * key,void * value)220 void HelloTypeAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) {
221     struct HelloTypeObject *hto = value;
222     struct HelloTypeNode *node = hto->head;
223     while(node) {
224         RedisModule_EmitAOF(aof,"HELLOTYPE.INSERT","sl",key,node->value);
225         node = node->next;
226     }
227 }
228 
229 /* The goal of this function is to return the amount of memory used by
230  * the HelloType value. */
HelloTypeMemUsage(const void * value)231 size_t HelloTypeMemUsage(const void *value) {
232     const struct HelloTypeObject *hto = value;
233     struct HelloTypeNode *node = hto->head;
234     return sizeof(*hto) + sizeof(*node)*hto->len;
235 }
236 
HelloTypeFree(void * value)237 void HelloTypeFree(void *value) {
238     HelloTypeReleaseObject(value);
239 }
240 
HelloTypeDigest(RedisModuleDigest * md,void * value)241 void HelloTypeDigest(RedisModuleDigest *md, void *value) {
242     struct HelloTypeObject *hto = value;
243     struct HelloTypeNode *node = hto->head;
244     while(node) {
245         RedisModule_DigestAddLongLong(md,node->value);
246         node = node->next;
247     }
248     RedisModule_DigestEndSequence(md);
249 }
250 
251 /* This function must be present on each Redis module. It is used in order to
252  * register the commands into the Redis server. */
RedisModule_OnLoad(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)253 int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
254     REDISMODULE_NOT_USED(argv);
255     REDISMODULE_NOT_USED(argc);
256 
257     if (RedisModule_Init(ctx,"hellotype",1,REDISMODULE_APIVER_1)
258         == REDISMODULE_ERR) return REDISMODULE_ERR;
259 
260     RedisModuleTypeMethods tm = {
261         .version = REDISMODULE_TYPE_METHOD_VERSION,
262         .rdb_load = HelloTypeRdbLoad,
263         .rdb_save = HelloTypeRdbSave,
264         .aof_rewrite = HelloTypeAofRewrite,
265         .mem_usage = HelloTypeMemUsage,
266         .free = HelloTypeFree,
267         .digest = HelloTypeDigest
268     };
269 
270     HelloType = RedisModule_CreateDataType(ctx,"hellotype",0,&tm);
271     if (HelloType == NULL) return REDISMODULE_ERR;
272 
273     if (RedisModule_CreateCommand(ctx,"hellotype.insert",
274         HelloTypeInsert_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
275         return REDISMODULE_ERR;
276 
277     if (RedisModule_CreateCommand(ctx,"hellotype.range",
278         HelloTypeRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
279         return REDISMODULE_ERR;
280 
281     if (RedisModule_CreateCommand(ctx,"hellotype.len",
282         HelloTypeLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
283         return REDISMODULE_ERR;
284 
285     return REDISMODULE_OK;
286 }
287