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