2016-06-03 18:19:25 +02:00
|
|
|
/* This file implements a new module native data type called "HELLOTYPE".
|
|
|
|
* The data structure implemented is a very simple ordered linked list of
|
|
|
|
* 64 bit integers, in order to have something that is real world enough, but
|
|
|
|
* at the same time, extremely simple to understand, to show how the API
|
|
|
|
* works, how a new data type is created, and how to write basic methods
|
|
|
|
* for RDB loading, saving and AOF rewriting.
|
|
|
|
*
|
2016-08-02 10:32:36 +02:00
|
|
|
* -----------------------------------------------------------------------------
|
2016-06-03 18:19:25 +02:00
|
|
|
*
|
|
|
|
* Copyright (c) 2016, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
|
|
* to endorse or promote products derived from this software without
|
|
|
|
* specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
2016-05-18 11:45:40 +02:00
|
|
|
#include "../redismodule.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
static RedisModuleType *HelloType;
|
|
|
|
|
|
|
|
/* ========================== Internal data structure =======================
|
|
|
|
* This is just a linked list of 64 bit integers where elements are inserted
|
|
|
|
* in-place, so it's ordered. There is no pop/push operation but just insert
|
|
|
|
* because it is enough to show the implementation of new data types without
|
|
|
|
* making things complex. */
|
|
|
|
|
|
|
|
struct HelloTypeNode {
|
|
|
|
int64_t value;
|
|
|
|
struct HelloTypeNode *next;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct HelloTypeObject {
|
|
|
|
struct HelloTypeNode *head;
|
|
|
|
size_t len; /* Number of elements added. */
|
|
|
|
};
|
|
|
|
|
|
|
|
struct HelloTypeObject *createHelloTypeObject(void) {
|
|
|
|
struct HelloTypeObject *o;
|
|
|
|
o = RedisModule_Alloc(sizeof(*o));
|
|
|
|
o->head = NULL;
|
|
|
|
o->len = 0;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HelloTypeInsert(struct HelloTypeObject *o, int64_t ele) {
|
|
|
|
struct HelloTypeNode *next = o->head, *newnode, *prev = NULL;
|
|
|
|
|
|
|
|
while(next && next->value < ele) {
|
|
|
|
prev = next;
|
|
|
|
next = next->next;
|
|
|
|
}
|
|
|
|
newnode = RedisModule_Alloc(sizeof(*newnode));
|
|
|
|
newnode->value = ele;
|
|
|
|
newnode->next = next;
|
|
|
|
if (prev) {
|
|
|
|
prev->next = newnode;
|
|
|
|
} else {
|
|
|
|
o->head = newnode;
|
|
|
|
}
|
|
|
|
o->len++;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HelloTypeReleaseObject(struct HelloTypeObject *o) {
|
|
|
|
struct HelloTypeNode *cur, *next;
|
|
|
|
cur = o->head;
|
|
|
|
while(cur) {
|
|
|
|
next = cur->next;
|
|
|
|
RedisModule_Free(cur);
|
|
|
|
cur = next;
|
|
|
|
}
|
|
|
|
RedisModule_Free(o);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ========================= "hellotype" type commands ======================= */
|
|
|
|
|
|
|
|
/* HELLOTYPE.INSERT key value */
|
|
|
|
int HelloTypeInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
|
|
|
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
|
|
|
|
|
|
|
if (argc != 3) return RedisModule_WrongArity(ctx);
|
|
|
|
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
|
|
|
REDISMODULE_READ|REDISMODULE_WRITE);
|
|
|
|
int type = RedisModule_KeyType(key);
|
|
|
|
if (type != REDISMODULE_KEYTYPE_EMPTY &&
|
|
|
|
RedisModule_ModuleTypeGetType(key) != HelloType)
|
|
|
|
{
|
|
|
|
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
|
|
|
}
|
|
|
|
|
|
|
|
long long value;
|
|
|
|
if ((RedisModule_StringToLongLong(argv[2],&value) != REDISMODULE_OK)) {
|
|
|
|
return RedisModule_ReplyWithError(ctx,"ERR invalid value: must be a signed 64 bit integer");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create an empty value object if the key is currently empty. */
|
|
|
|
struct HelloTypeObject *hto;
|
|
|
|
if (type == REDISMODULE_KEYTYPE_EMPTY) {
|
|
|
|
hto = createHelloTypeObject();
|
|
|
|
RedisModule_ModuleTypeSetValue(key,HelloType,hto);
|
|
|
|
} else {
|
|
|
|
hto = RedisModule_ModuleTypeGetValue(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Insert the new element. */
|
|
|
|
HelloTypeInsert(hto,value);
|
2019-10-31 10:30:54 +01:00
|
|
|
RedisModule_SignalKeyAsReady(ctx,argv[1]);
|
2016-05-18 11:45:40 +02:00
|
|
|
|
|
|
|
RedisModule_ReplyWithLongLong(ctx,hto->len);
|
|
|
|
RedisModule_ReplicateVerbatim(ctx);
|
|
|
|
return REDISMODULE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* HELLOTYPE.RANGE key first count */
|
|
|
|
int HelloTypeRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
|
|
|
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
|
|
|
|
|
|
|
if (argc != 4) return RedisModule_WrongArity(ctx);
|
|
|
|
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
|
|
|
REDISMODULE_READ|REDISMODULE_WRITE);
|
|
|
|
int type = RedisModule_KeyType(key);
|
|
|
|
if (type != REDISMODULE_KEYTYPE_EMPTY &&
|
|
|
|
RedisModule_ModuleTypeGetType(key) != HelloType)
|
|
|
|
{
|
|
|
|
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
|
|
|
}
|
|
|
|
|
|
|
|
long long first, count;
|
|
|
|
if (RedisModule_StringToLongLong(argv[2],&first) != REDISMODULE_OK ||
|
|
|
|
RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK ||
|
|
|
|
first < 0 || count < 0)
|
|
|
|
{
|
|
|
|
return RedisModule_ReplyWithError(ctx,
|
|
|
|
"ERR invalid first or count parameters");
|
|
|
|
}
|
|
|
|
|
|
|
|
struct HelloTypeObject *hto = RedisModule_ModuleTypeGetValue(key);
|
|
|
|
struct HelloTypeNode *node = hto ? hto->head : NULL;
|
|
|
|
RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
|
|
|
long long arraylen = 0;
|
|
|
|
while(node && count--) {
|
|
|
|
RedisModule_ReplyWithLongLong(ctx,node->value);
|
|
|
|
arraylen++;
|
|
|
|
node = node->next;
|
|
|
|
}
|
|
|
|
RedisModule_ReplySetArrayLength(ctx,arraylen);
|
|
|
|
return REDISMODULE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* HELLOTYPE.LEN key */
|
|
|
|
int HelloTypeLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
|
|
|
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
|
|
|
|
|
|
|
if (argc != 2) return RedisModule_WrongArity(ctx);
|
|
|
|
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
|
|
|
REDISMODULE_READ|REDISMODULE_WRITE);
|
|
|
|
int type = RedisModule_KeyType(key);
|
|
|
|
if (type != REDISMODULE_KEYTYPE_EMPTY &&
|
|
|
|
RedisModule_ModuleTypeGetType(key) != HelloType)
|
|
|
|
{
|
|
|
|
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct HelloTypeObject *hto = RedisModule_ModuleTypeGetValue(key);
|
|
|
|
RedisModule_ReplyWithLongLong(ctx,hto ? hto->len : 0);
|
|
|
|
return REDISMODULE_OK;
|
|
|
|
}
|
|
|
|
|
2019-10-31 10:30:54 +01:00
|
|
|
/* ====================== Example of a blocking command ==================== */
|
|
|
|
|
2019-10-31 11:35:05 +01:00
|
|
|
/* Reply callback for blocking command HELLOTYPE.BRANGE, this will get
|
|
|
|
* called when the key we blocked for is ready: we need to check if we
|
|
|
|
* can really serve the client, and reply OK or ERR accordingly. */
|
|
|
|
int HelloBlock_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
|
|
|
REDISMODULE_NOT_USED(argv);
|
|
|
|
REDISMODULE_NOT_USED(argc);
|
2019-10-31 10:30:54 +01:00
|
|
|
|
2019-10-31 11:35:05 +01:00
|
|
|
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
2019-10-31 10:30:54 +01:00
|
|
|
RedisModuleKey *key = RedisModule_OpenKey(ctx,keyname,REDISMODULE_READ);
|
|
|
|
int type = RedisModule_KeyType(key);
|
|
|
|
if (type != REDISMODULE_KEYTYPE_MODULE ||
|
|
|
|
RedisModule_ModuleTypeGetType(key) != HelloType)
|
|
|
|
{
|
2019-10-31 11:35:05 +01:00
|
|
|
RedisModule_CloseKey(key);
|
|
|
|
return REDISMODULE_ERR;
|
2019-10-31 10:30:54 +01:00
|
|
|
}
|
|
|
|
|
2019-10-31 11:35:05 +01:00
|
|
|
/* In case the key is able to serve our blocked client, let's directly
|
|
|
|
* use our original command implementation to make this example simpler. */
|
|
|
|
RedisModule_CloseKey(key);
|
2019-10-31 10:30:54 +01:00
|
|
|
return HelloTypeRange_RedisCommand(ctx,argv,argc-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Timeout callback for blocking command HELLOTYPE.BRANGE */
|
|
|
|
int HelloBlock_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
|
|
|
REDISMODULE_NOT_USED(argv);
|
|
|
|
REDISMODULE_NOT_USED(argc);
|
|
|
|
return RedisModule_ReplyWithSimpleString(ctx,"Request timedout");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Private data freeing callback for HELLOTYPE.BRANGE command. */
|
|
|
|
void HelloBlock_FreeData(RedisModuleCtx *ctx, void *privdata) {
|
|
|
|
REDISMODULE_NOT_USED(ctx);
|
|
|
|
RedisModule_Free(privdata);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* HELLOTYPE.BRANGE key first count timeout -- This is a blocking verison of
|
|
|
|
* the RANGE operation, in order to show how to use the API
|
|
|
|
* RedisModule_BlockClientOnKeys(). */
|
|
|
|
int HelloTypeBRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
|
|
|
if (argc != 5) return RedisModule_WrongArity(ctx);
|
|
|
|
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
|
|
|
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
|
|
|
REDISMODULE_READ|REDISMODULE_WRITE);
|
|
|
|
int type = RedisModule_KeyType(key);
|
|
|
|
if (type != REDISMODULE_KEYTYPE_EMPTY &&
|
|
|
|
RedisModule_ModuleTypeGetType(key) != HelloType)
|
|
|
|
{
|
|
|
|
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
|
|
|
}
|
|
|
|
|
2019-10-31 12:31:22 +01:00
|
|
|
/* Parse the timeout before even trying to serve the client synchronously,
|
|
|
|
* so that we always fail ASAP on syntax errors. */
|
2019-10-31 10:30:54 +01:00
|
|
|
long long timeout;
|
|
|
|
if (RedisModule_StringToLongLong(argv[4],&timeout) != REDISMODULE_OK) {
|
|
|
|
return RedisModule_ReplyWithError(ctx,
|
|
|
|
"ERR invalid timeout parameter");
|
|
|
|
}
|
|
|
|
|
2019-10-31 12:31:22 +01:00
|
|
|
/* Can we serve the reply synchronously? */
|
|
|
|
if (type != REDISMODULE_KEYTYPE_EMPTY) {
|
|
|
|
return HelloTypeRange_RedisCommand(ctx,argv,argc-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Otherwise let's block on the key. */
|
2019-10-31 10:30:54 +01:00
|
|
|
void *privdata = RedisModule_Alloc(100);
|
2019-10-31 11:35:05 +01:00
|
|
|
RedisModule_BlockClientOnKeys(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout,argv+1,1,privdata);
|
2019-10-31 10:30:54 +01:00
|
|
|
return REDISMODULE_OK;
|
|
|
|
}
|
2016-05-18 11:45:40 +02:00
|
|
|
|
|
|
|
/* ========================== "hellotype" type methods ======================= */
|
|
|
|
|
|
|
|
void *HelloTypeRdbLoad(RedisModuleIO *rdb, int encver) {
|
|
|
|
if (encver != 0) {
|
|
|
|
/* RedisModule_Log("warning","Can't load data with version %d", encver);*/
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
uint64_t elements = RedisModule_LoadUnsigned(rdb);
|
|
|
|
struct HelloTypeObject *hto = createHelloTypeObject();
|
|
|
|
while(elements--) {
|
|
|
|
int64_t ele = RedisModule_LoadSigned(rdb);
|
|
|
|
HelloTypeInsert(hto,ele);
|
|
|
|
}
|
|
|
|
return hto;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HelloTypeRdbSave(RedisModuleIO *rdb, void *value) {
|
|
|
|
struct HelloTypeObject *hto = value;
|
|
|
|
struct HelloTypeNode *node = hto->head;
|
|
|
|
RedisModule_SaveUnsigned(rdb,hto->len);
|
|
|
|
while(node) {
|
|
|
|
RedisModule_SaveSigned(rdb,node->value);
|
|
|
|
node = node->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void HelloTypeAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value) {
|
|
|
|
struct HelloTypeObject *hto = value;
|
|
|
|
struct HelloTypeNode *node = hto->head;
|
|
|
|
while(node) {
|
|
|
|
RedisModule_EmitAOF(aof,"HELLOTYPE.INSERT","sl",key,node->value);
|
|
|
|
node = node->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-12 09:08:43 +01:00
|
|
|
/* The goal of this function is to return the amount of memory used by
|
|
|
|
* the HelloType value. */
|
2017-01-12 12:47:46 +01:00
|
|
|
size_t HelloTypeMemUsage(const void *value) {
|
|
|
|
const struct HelloTypeObject *hto = value;
|
2017-01-12 09:08:43 +01:00
|
|
|
struct HelloTypeNode *node = hto->head;
|
|
|
|
return sizeof(*hto) + sizeof(*node)*hto->len;
|
2016-05-18 11:45:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void HelloTypeFree(void *value) {
|
|
|
|
HelloTypeReleaseObject(value);
|
|
|
|
}
|
|
|
|
|
2017-07-06 10:29:19 +02:00
|
|
|
void HelloTypeDigest(RedisModuleDigest *md, void *value) {
|
|
|
|
struct HelloTypeObject *hto = value;
|
|
|
|
struct HelloTypeNode *node = hto->head;
|
|
|
|
while(node) {
|
|
|
|
RedisModule_DigestAddLongLong(md,node->value);
|
|
|
|
node = node->next;
|
|
|
|
}
|
|
|
|
RedisModule_DigestEndSequence(md);
|
|
|
|
}
|
|
|
|
|
2016-05-18 11:45:40 +02:00
|
|
|
/* This function must be present on each Redis module. It is used in order to
|
|
|
|
* register the commands into the Redis server. */
|
2016-06-13 09:57:10 +02:00
|
|
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
2016-10-07 13:10:29 +02:00
|
|
|
REDISMODULE_NOT_USED(argv);
|
|
|
|
REDISMODULE_NOT_USED(argc);
|
|
|
|
|
2016-05-18 11:45:40 +02:00
|
|
|
if (RedisModule_Init(ctx,"hellotype",1,REDISMODULE_APIVER_1)
|
|
|
|
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
|
|
|
|
2016-11-30 11:13:55 +01:00
|
|
|
RedisModuleTypeMethods tm = {
|
|
|
|
.version = REDISMODULE_TYPE_METHOD_VERSION,
|
|
|
|
.rdb_load = HelloTypeRdbLoad,
|
|
|
|
.rdb_save = HelloTypeRdbSave,
|
|
|
|
.aof_rewrite = HelloTypeAofRewrite,
|
2017-01-12 09:08:43 +01:00
|
|
|
.mem_usage = HelloTypeMemUsage,
|
2017-07-06 10:29:19 +02:00
|
|
|
.free = HelloTypeFree,
|
|
|
|
.digest = HelloTypeDigest
|
2016-11-30 11:13:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
HelloType = RedisModule_CreateDataType(ctx,"hellotype",0,&tm);
|
2016-05-18 11:45:40 +02:00
|
|
|
if (HelloType == NULL) return REDISMODULE_ERR;
|
|
|
|
|
|
|
|
if (RedisModule_CreateCommand(ctx,"hellotype.insert",
|
|
|
|
HelloTypeInsert_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
|
|
|
return REDISMODULE_ERR;
|
|
|
|
|
|
|
|
if (RedisModule_CreateCommand(ctx,"hellotype.range",
|
|
|
|
HelloTypeRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
|
|
|
return REDISMODULE_ERR;
|
|
|
|
|
|
|
|
if (RedisModule_CreateCommand(ctx,"hellotype.len",
|
|
|
|
HelloTypeLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
|
|
|
return REDISMODULE_ERR;
|
|
|
|
|
2019-10-31 10:30:54 +01:00
|
|
|
if (RedisModule_CreateCommand(ctx,"hellotype.brange",
|
|
|
|
HelloTypeBRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
|
|
|
return REDISMODULE_ERR;
|
|
|
|
|
2016-05-18 11:45:40 +02:00
|
|
|
return REDISMODULE_OK;
|
|
|
|
}
|