diff options
Diffstat (limited to 'src/modules')
-rw-r--r-- | src/modules/.gitignore | 2 | ||||
-rw-r--r-- | src/modules/Makefile | 69 | ||||
-rw-r--r-- | src/modules/helloacl.c | 190 | ||||
-rw-r--r-- | src/modules/helloblock.c | 218 | ||||
-rw-r--r-- | src/modules/hellocluster.c | 118 | ||||
-rw-r--r-- | src/modules/hellodict.c | 131 | ||||
-rw-r--r-- | src/modules/hellohook.c | 92 | ||||
-rw-r--r-- | src/modules/hellotimer.c | 75 | ||||
-rw-r--r-- | src/modules/hellotype.c | 362 | ||||
-rw-r--r-- | src/modules/helloworld.c | 621 |
10 files changed, 1878 insertions, 0 deletions
diff --git a/src/modules/.gitignore b/src/modules/.gitignore new file mode 100644 index 0000000..4de1735 --- /dev/null +++ b/src/modules/.gitignore @@ -0,0 +1,2 @@ +*.so +*.xo diff --git a/src/modules/Makefile b/src/modules/Makefile new file mode 100644 index 0000000..b9ef578 --- /dev/null +++ b/src/modules/Makefile @@ -0,0 +1,69 @@ + +# find the OS +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') + +# Compile flags for linux / osx +ifeq ($(uname_S),Linux) + SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2 + SHOBJ_LDFLAGS ?= -shared +else + SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2 + SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup +endif + +# OS X 11.x doesn't have /usr/lib/libSystem.dylib and needs an explicit setting. +ifeq ($(uname_S),Darwin) +ifeq ("$(wildcard /usr/lib/libSystem.dylib)","") +LIBS = -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -lsystem +endif +endif + +.SUFFIXES: .c .so .xo .o + +all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so + +.c.xo: + $(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@ + +helloworld.xo: ../redismodule.h + +helloworld.so: helloworld.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc + +hellotype.xo: ../redismodule.h + +hellotype.so: hellotype.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc + +helloblock.xo: ../redismodule.h + +helloblock.so: helloblock.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lpthread -lc + +hellocluster.xo: ../redismodule.h + +hellocluster.so: hellocluster.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc + +hellotimer.xo: ../redismodule.h + +hellotimer.so: hellotimer.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc + +hellodict.xo: ../redismodule.h + +hellodict.so: hellodict.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc + +hellohook.xo: ../redismodule.h + +hellohook.so: hellohook.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc + +helloacl.xo: ../redismodule.h + +helloacl.so: helloacl.xo + $(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc + +clean: + rm -rf *.xo *.so diff --git a/src/modules/helloacl.c b/src/modules/helloacl.c new file mode 100644 index 0000000..53f3a44 --- /dev/null +++ b/src/modules/helloacl.c @@ -0,0 +1,190 @@ +/* ACL API example - An example for performing custom synchronous and + * asynchronous password authentication. + * + * ----------------------------------------------------------------------------- + * + * Copyright 2019 Amazon.com, Inc. or its affiliates. + * 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. + */ + +#include "../redismodule.h" +#include <pthread.h> +#include <unistd.h> + +// A simple global user +static RedisModuleUser *global; +static uint64_t global_auth_client_id = 0; + +/* HELLOACL.REVOKE + * Synchronously revoke access from a user. */ +int RevokeCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (global_auth_client_id) { + RedisModule_DeauthenticateAndCloseClient(ctx, global_auth_client_id); + return RedisModule_ReplyWithSimpleString(ctx, "OK"); + } else { + return RedisModule_ReplyWithError(ctx, "Global user currently not used"); + } +} + +/* HELLOACL.RESET + * Synchronously delete and re-create a module user. */ +int ResetCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + RedisModule_FreeModuleUser(global); + global = RedisModule_CreateModuleUser("global"); + RedisModule_SetModuleUserACL(global, "allcommands"); + RedisModule_SetModuleUserACL(global, "allkeys"); + RedisModule_SetModuleUserACL(global, "on"); + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* Callback handler for user changes, use this to notify a module of + * changes to users authenticated by the module */ +void HelloACL_UserChanged(uint64_t client_id, void *privdata) { + REDISMODULE_NOT_USED(privdata); + REDISMODULE_NOT_USED(client_id); + global_auth_client_id = 0; +} + +/* HELLOACL.AUTHGLOBAL + * Synchronously assigns a module user to the current context. */ +int AuthGlobalCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (global_auth_client_id) { + return RedisModule_ReplyWithError(ctx, "Global user currently used"); + } + + RedisModule_AuthenticateClientWithUser(ctx, global, HelloACL_UserChanged, NULL, &global_auth_client_id); + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +#define TIMEOUT_TIME 1000 + +/* Reply callback for auth command HELLOACL.AUTHASYNC */ +int HelloACL_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + size_t length; + + RedisModuleString *user_string = RedisModule_GetBlockedClientPrivateData(ctx); + const char *name = RedisModule_StringPtrLen(user_string, &length); + + if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length, NULL, NULL, NULL) == + REDISMODULE_ERR) { + return RedisModule_ReplyWithError(ctx, "Invalid Username or password"); + } + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* Timeout callback for auth command HELLOACL.AUTHASYNC */ +int HelloACL_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + return RedisModule_ReplyWithSimpleString(ctx, "Request timedout"); +} + +/* Private data frees data for HELLOACL.AUTHASYNC command. */ +void HelloACL_FreeData(RedisModuleCtx *ctx, void *privdata) { + REDISMODULE_NOT_USED(ctx); + RedisModule_FreeString(NULL, privdata); +} + +/* Background authentication can happen here. */ +void *HelloACL_ThreadMain(void *args) { + void **targs = args; + RedisModuleBlockedClient *bc = targs[0]; + RedisModuleString *user = targs[1]; + RedisModule_Free(targs); + + RedisModule_UnblockClient(bc,user); + return NULL; +} + +/* HELLOACL.AUTHASYNC + * Asynchronously assigns an ACL user to the current context. */ +int AuthAsyncCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + pthread_t tid; + RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, HelloACL_Reply, HelloACL_Timeout, HelloACL_FreeData, TIMEOUT_TIME); + + + void **targs = RedisModule_Alloc(sizeof(void*)*2); + targs[0] = bc; + targs[1] = RedisModule_CreateStringFromString(NULL, argv[1]); + + if (pthread_create(&tid, NULL, HelloACL_ThreadMain, targs) != 0) { + RedisModule_AbortBlock(bc); + return RedisModule_ReplyWithError(ctx, "-ERR Can't start thread"); + } + + return REDISMODULE_OK; +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"helloacl",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"helloacl.reset", + ResetCommand_RedisCommand,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"helloacl.revoke", + RevokeCommand_RedisCommand,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"helloacl.authglobal", + AuthGlobalCommand_RedisCommand,"no-auth",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"helloacl.authasync", + AuthAsyncCommand_RedisCommand,"no-auth",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + global = RedisModule_CreateModuleUser("global"); + RedisModule_SetModuleUserACL(global, "allcommands"); + RedisModule_SetModuleUserACL(global, "allkeys"); + RedisModule_SetModuleUserACL(global, "on"); + + global_auth_client_id = 0; + + return REDISMODULE_OK; +} diff --git a/src/modules/helloblock.c b/src/modules/helloblock.c new file mode 100644 index 0000000..dc3d749 --- /dev/null +++ b/src/modules/helloblock.c @@ -0,0 +1,218 @@ +/* Helloblock module -- An example of blocking command implementation + * with threads. + * + * ----------------------------------------------------------------------------- + * + * 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. + */ + +#include "../redismodule.h" +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> + +/* Reply callback for blocking command HELLO.BLOCK */ +int HelloBlock_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + int *myint = RedisModule_GetBlockedClientPrivateData(ctx); + return RedisModule_ReplyWithLongLong(ctx,*myint); +} + +/* Timeout callback for blocking command HELLO.BLOCK */ +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 HELLO.BLOCK command. */ +void HelloBlock_FreeData(RedisModuleCtx *ctx, void *privdata) { + REDISMODULE_NOT_USED(ctx); + RedisModule_Free(privdata); +} + +/* The thread entry point that actually executes the blocking part + * of the command HELLO.BLOCK. */ +void *HelloBlock_ThreadMain(void *arg) { + void **targ = arg; + RedisModuleBlockedClient *bc = targ[0]; + long long delay = (unsigned long)targ[1]; + RedisModule_Free(targ); + + sleep(delay); + int *r = RedisModule_Alloc(sizeof(int)); + *r = rand(); + RedisModule_UnblockClient(bc,r); + return NULL; +} + +/* An example blocked client disconnection callback. + * + * Note that in the case of the HELLO.BLOCK command, the blocked client is now + * owned by the thread calling sleep(). In this specific case, there is not + * much we can do, however normally we could instead implement a way to + * signal the thread that the client disconnected, and sleep the specified + * amount of seconds with a while loop calling sleep(1), so that once we + * detect the client disconnection, we can terminate the thread ASAP. */ +void HelloBlock_Disconnected(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc) { + RedisModule_Log(ctx,"warning","Blocked client %p disconnected!", + (void*)bc); + + /* Here you should cleanup your state / threads, and if possible + * call RedisModule_UnblockClient(), or notify the thread that will + * call the function ASAP. */ +} + +/* HELLO.BLOCK <delay> <timeout> -- Block for <count> seconds, then reply with + * a random number. Timeout is the command timeout, so that you can test + * what happens when the delay is greater than the timeout. */ +int HelloBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 3) return RedisModule_WrongArity(ctx); + long long delay; + long long timeout; + + if (RedisModule_StringToLongLong(argv[1],&delay) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx,"ERR invalid count"); + } + + if (RedisModule_StringToLongLong(argv[2],&timeout) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx,"ERR invalid count"); + } + + pthread_t tid; + RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout); + + /* Here we set a disconnection handler, however since this module will + * block in sleep() in a thread, there is not much we can do in the + * callback, so this is just to show you the API. */ + RedisModule_SetDisconnectCallback(bc,HelloBlock_Disconnected); + + /* Now that we setup a blocking client, we need to pass the control + * to the thread. However we need to pass arguments to the thread: + * the delay and a reference to the blocked client handle. */ + void **targ = RedisModule_Alloc(sizeof(void*)*2); + targ[0] = bc; + targ[1] = (void*)(unsigned long) delay; + + if (pthread_create(&tid,NULL,HelloBlock_ThreadMain,targ) != 0) { + RedisModule_AbortBlock(bc); + return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread"); + } + return REDISMODULE_OK; +} + +/* The thread entry point that actually executes the blocking part + * of the command HELLO.KEYS. + * + * Note: this implementation is very simple on purpose, so no duplicated + * keys (returned by SCAN) are filtered. However adding such a functionality + * would be trivial just using any data structure implementing a dictionary + * in order to filter the duplicated items. */ +void *HelloKeys_ThreadMain(void *arg) { + RedisModuleBlockedClient *bc = arg; + RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bc); + long long cursor = 0; + size_t replylen = 0; + + RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN); + do { + RedisModule_ThreadSafeContextLock(ctx); + RedisModuleCallReply *reply = RedisModule_Call(ctx, + "SCAN","l",(long long)cursor); + RedisModule_ThreadSafeContextUnlock(ctx); + + RedisModuleCallReply *cr_cursor = + RedisModule_CallReplyArrayElement(reply,0); + RedisModuleCallReply *cr_keys = + RedisModule_CallReplyArrayElement(reply,1); + + RedisModuleString *s = RedisModule_CreateStringFromCallReply(cr_cursor); + RedisModule_StringToLongLong(s,&cursor); + RedisModule_FreeString(ctx,s); + + size_t items = RedisModule_CallReplyLength(cr_keys); + for (size_t j = 0; j < items; j++) { + RedisModuleCallReply *ele = + RedisModule_CallReplyArrayElement(cr_keys,j); + RedisModule_ReplyWithCallReply(ctx,ele); + replylen++; + } + RedisModule_FreeCallReply(reply); + } while (cursor != 0); + RedisModule_ReplySetArrayLength(ctx,replylen); + + RedisModule_FreeThreadSafeContext(ctx); + RedisModule_UnblockClient(bc,NULL); + return NULL; +} + +/* HELLO.KEYS -- Return all the keys in the current database without blocking + * the server. The keys do not represent a point-in-time state so only the keys + * that were in the database from the start to the end are guaranteed to be + * there. */ +int HelloKeys_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + if (argc != 1) return RedisModule_WrongArity(ctx); + + pthread_t tid; + + /* Note that when blocking the client we do not set any callback: no + * timeout is possible since we passed '0', nor we need a reply callback + * because we'll use the thread safe context to accumulate a reply. */ + RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,NULL,NULL,NULL,0); + + /* Now that we setup a blocking client, we need to pass the control + * to the thread. However we need to pass arguments to the thread: + * the reference to the blocked client handle. */ + if (pthread_create(&tid,NULL,HelloKeys_ThreadMain,bc) != 0) { + RedisModule_AbortBlock(bc); + return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread"); + } + return REDISMODULE_OK; +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"helloblock",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.block", + HelloBlock_RedisCommand,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"hello.keys", + HelloKeys_RedisCommand,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/src/modules/hellocluster.c b/src/modules/hellocluster.c new file mode 100644 index 0000000..bc145c2 --- /dev/null +++ b/src/modules/hellocluster.c @@ -0,0 +1,118 @@ +/* Helloworld cluster -- A ping/pong cluster API example. + * + * ----------------------------------------------------------------------------- + * + * Copyright (c) 2018, 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. + */ + +#include "../redismodule.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +#define MSGTYPE_PING 1 +#define MSGTYPE_PONG 2 + +/* HELLOCLUSTER.PINGALL */ +int PingallCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + RedisModule_SendClusterMessage(ctx,NULL,MSGTYPE_PING,"Hey",3); + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* HELLOCLUSTER.LIST */ +int ListCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + size_t numnodes; + char **ids = RedisModule_GetClusterNodesList(ctx,&numnodes); + if (ids == NULL) { + return RedisModule_ReplyWithError(ctx,"Cluster not enabled"); + } + + RedisModule_ReplyWithArray(ctx,numnodes); + for (size_t j = 0; j < numnodes; j++) { + int port; + RedisModule_GetClusterNodeInfo(ctx,ids[j],NULL,NULL,&port,NULL); + RedisModule_ReplyWithArray(ctx,2); + RedisModule_ReplyWithStringBuffer(ctx,ids[j],REDISMODULE_NODE_ID_LEN); + RedisModule_ReplyWithLongLong(ctx,port); + } + RedisModule_FreeClusterNodesList(ids); + return REDISMODULE_OK; +} + +/* Callback for message MSGTYPE_PING */ +void PingReceiver(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) { + RedisModule_Log(ctx,"notice","PING (type %d) RECEIVED from %.*s: '%.*s'", + type,REDISMODULE_NODE_ID_LEN,sender_id,(int)len, payload); + RedisModule_SendClusterMessage(ctx,NULL,MSGTYPE_PONG,"Ohi!",4); + RedisModuleCallReply *reply = RedisModule_Call(ctx, "INCR", "c", "pings_received"); + RedisModule_FreeCallReply(reply); +} + +/* Callback for message MSGTYPE_PONG. */ +void PongReceiver(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) { + RedisModule_Log(ctx,"notice","PONG (type %d) RECEIVED from %.*s: '%.*s'", + type,REDISMODULE_NODE_ID_LEN,sender_id,(int)len, payload); +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"hellocluster",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hellocluster.pingall", + PingallCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hellocluster.list", + ListCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + /* Disable Redis Cluster sharding and redirections. This way every node + * will be able to access every possible key, regardless of the hash slot. + * This way the PING message handler will be able to increment a specific + * variable. Normally you do that in order for the distributed system + * you create as a module to have total freedom in the keyspace + * manipulation. */ + RedisModule_SetClusterFlags(ctx,REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION); + + /* Register our handlers for different message types. */ + RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PING,PingReceiver); + RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PONG,PongReceiver); + return REDISMODULE_OK; +} diff --git a/src/modules/hellodict.c b/src/modules/hellodict.c new file mode 100644 index 0000000..12b6e91 --- /dev/null +++ b/src/modules/hellodict.c @@ -0,0 +1,131 @@ +/* Hellodict -- An example of modules dictionary API + * + * This module implements a volatile key-value store on top of the + * dictionary exported by the Redis modules API. + * + * ----------------------------------------------------------------------------- + * + * Copyright (c) 2018, 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. + */ + +#include "../redismodule.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +static RedisModuleDict *Keyspace; + +/* HELLODICT.SET <key> <value> + * + * Set the specified key to the specified value. */ +int cmd_SET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 3) return RedisModule_WrongArity(ctx); + RedisModule_DictSet(Keyspace,argv[1],argv[2]); + /* We need to keep a reference to the value stored at the key, otherwise + * it would be freed when this callback returns. */ + RedisModule_RetainString(NULL,argv[2]); + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* HELLODICT.GET <key> + * + * Return the value of the specified key, or a null reply if the key + * is not defined. */ +int cmd_GET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + RedisModuleString *val = RedisModule_DictGet(Keyspace,argv[1],NULL); + if (val == NULL) { + return RedisModule_ReplyWithNull(ctx); + } else { + return RedisModule_ReplyWithString(ctx, val); + } +} + +/* HELLODICT.KEYRANGE <startkey> <endkey> <count> + * + * Return a list of matching keys, lexicographically between startkey + * and endkey. No more than 'count' items are emitted. */ +int cmd_KEYRANGE(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 4) return RedisModule_WrongArity(ctx); + + /* Parse the count argument. */ + long long count; + if (RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx,"ERR invalid count"); + } + + /* Seek the iterator. */ + RedisModuleDictIter *iter = RedisModule_DictIteratorStart( + Keyspace, ">=", argv[1]); + + /* Reply with the matching items. */ + char *key; + size_t keylen; + long long replylen = 0; /* Keep track of the emitted array len. */ + RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN); + while((key = RedisModule_DictNextC(iter,&keylen,NULL)) != NULL) { + if (replylen >= count) break; + if (RedisModule_DictCompare(iter,"<=",argv[2]) == REDISMODULE_ERR) + break; + RedisModule_ReplyWithStringBuffer(ctx,key,keylen); + replylen++; + } + RedisModule_ReplySetArrayLength(ctx,replylen); + + /* Cleanup. */ + RedisModule_DictIteratorStop(iter); + return REDISMODULE_OK; +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"hellodict",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hellodict.set", + cmd_SET,"write deny-oom",1,1,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hellodict.get", + cmd_GET,"readonly",1,1,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hellodict.keyrange", + cmd_KEYRANGE,"readonly",1,1,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + /* Create our global dictionary. Here we'll set our keys and values. */ + Keyspace = RedisModule_CreateDict(NULL); + + return REDISMODULE_OK; +} diff --git a/src/modules/hellohook.c b/src/modules/hellohook.c new file mode 100644 index 0000000..2859a8b --- /dev/null +++ b/src/modules/hellohook.c @@ -0,0 +1,92 @@ +/* Server hooks API example + * + * ----------------------------------------------------------------------------- + * + * Copyright (c) 2019, 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. + */ + +#include "../redismodule.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +/* Client state change callback. */ +void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) +{ + REDISMODULE_NOT_USED(ctx); + REDISMODULE_NOT_USED(e); + + RedisModuleClientInfo *ci = data; + printf("Client %s event for client #%llu %s:%d\n", + (sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ? + "connection" : "disconnection", + (unsigned long long)ci->id,ci->addr,ci->port); +} + +void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) +{ + REDISMODULE_NOT_USED(ctx); + REDISMODULE_NOT_USED(e); + + RedisModuleFlushInfo *fi = data; + if (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) { + if (fi->dbnum != -1) { + RedisModuleCallReply *reply; + reply = RedisModule_Call(ctx,"DBSIZE",""); + long long numkeys = RedisModule_CallReplyInteger(reply); + printf("FLUSHDB event of database %d started (%lld keys in DB)\n", + fi->dbnum, numkeys); + RedisModule_FreeCallReply(reply); + } else { + printf("FLUSHALL event started\n"); + } + } else { + if (fi->dbnum != -1) { + printf("FLUSHDB event of database %d ended\n",fi->dbnum); + } else { + printf("FLUSHALL event ended\n"); + } + } +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"hellohook",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + RedisModule_SubscribeToServerEvent(ctx, + RedisModuleEvent_ClientChange, clientChangeCallback); + RedisModule_SubscribeToServerEvent(ctx, + RedisModuleEvent_FlushDB, flushdbCallback); + return REDISMODULE_OK; +} diff --git a/src/modules/hellotimer.c b/src/modules/hellotimer.c new file mode 100644 index 0000000..67e1e67 --- /dev/null +++ b/src/modules/hellotimer.c @@ -0,0 +1,75 @@ +/* Timer API example -- Register and handle timer events + * + * ----------------------------------------------------------------------------- + * + * Copyright (c) 2018, 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. + */ + +#include "../redismodule.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +/* Timer callback. */ +void timerHandler(RedisModuleCtx *ctx, void *data) { + REDISMODULE_NOT_USED(ctx); + printf("Fired %s!\n", (char *)data); + RedisModule_Free(data); +} + +/* HELLOTIMER.TIMER*/ +int TimerCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + for (int j = 0; j < 10; j++) { + int delay = rand() % 5000; + char *buf = RedisModule_Alloc(256); + snprintf(buf,256,"After %d", delay); + RedisModuleTimerID tid = RedisModule_CreateTimer(ctx,delay,timerHandler,buf); + REDISMODULE_NOT_USED(tid); + } + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"hellotimer",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hellotimer.timer", + TimerCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/src/modules/hellotype.c b/src/modules/hellotype.c new file mode 100644 index 0000000..1dc53d2 --- /dev/null +++ b/src/modules/hellotype.c @@ -0,0 +1,362 @@ +/* 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. + * + * ----------------------------------------------------------------------------- + * + * 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. + */ + +#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); + RedisModule_SignalKeyAsReady(ctx,argv[1]); + + 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_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; +} + +/* ====================== Example of a blocking command ==================== */ + +/* 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); + + RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx); + RedisModuleKey *key = RedisModule_OpenKey(ctx,keyname,REDISMODULE_READ); + int type = RedisModule_KeyType(key); + if (type != REDISMODULE_KEYTYPE_MODULE || + RedisModule_ModuleTypeGetType(key) != HelloType) + { + RedisModule_CloseKey(key); + return REDISMODULE_ERR; + } + + /* 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); + 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 version 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); + } + + /* Parse the timeout before even trying to serve the client synchronously, + * so that we always fail ASAP on syntax errors. */ + long long timeout; + if (RedisModule_StringToLongLong(argv[4],&timeout) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx, + "ERR invalid timeout parameter"); + } + + /* 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. */ + void *privdata = RedisModule_Alloc(100); + RedisModule_BlockClientOnKeys(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout,argv+1,1,privdata); + return REDISMODULE_OK; +} + +/* ========================== "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; + } +} + +/* The goal of this function is to return the amount of memory used by + * the HelloType value. */ +size_t HelloTypeMemUsage(const void *value) { + const struct HelloTypeObject *hto = value; + struct HelloTypeNode *node = hto->head; + return sizeof(*hto) + sizeof(*node)*hto->len; +} + +void HelloTypeFree(void *value) { + HelloTypeReleaseObject(value); +} + +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); +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + + if (RedisModule_Init(ctx,"hellotype",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + RedisModuleTypeMethods tm = { + .version = REDISMODULE_TYPE_METHOD_VERSION, + .rdb_load = HelloTypeRdbLoad, + .rdb_save = HelloTypeRdbSave, + .aof_rewrite = HelloTypeAofRewrite, + .mem_usage = HelloTypeMemUsage, + .free = HelloTypeFree, + .digest = HelloTypeDigest + }; + + HelloType = RedisModule_CreateDataType(ctx,"hellotype",0,&tm); + 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; + + if (RedisModule_CreateCommand(ctx,"hellotype.brange", + HelloTypeBRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/src/modules/helloworld.c b/src/modules/helloworld.c new file mode 100644 index 0000000..e517963 --- /dev/null +++ b/src/modules/helloworld.c @@ -0,0 +1,621 @@ +/* Helloworld module -- A few examples of the Redis Modules API in the form + * of commands showing how to accomplish common tasks. + * + * This module does not do anything useful, if not for a few commands. The + * examples are designed in order to show the API. + * + * ----------------------------------------------------------------------------- + * + * 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. + */ + +#include "../redismodule.h" +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +/* HELLO.SIMPLE is among the simplest commands you can implement. + * It just returns the currently selected DB id, a functionality which is + * missing in Redis. The command uses two important API calls: one to + * fetch the currently selected DB, the other in order to send the client + * an integer reply as response. */ +int HelloSimple_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + RedisModule_ReplyWithLongLong(ctx,RedisModule_GetSelectedDb(ctx)); + return REDISMODULE_OK; +} + +/* HELLO.PUSH.NATIVE re-implements RPUSH, and shows the low level modules API + * where you can "open" keys, make low level operations, create new keys by + * pushing elements into non-existing keys, and so forth. + * + * You'll find this command to be roughly as fast as the actual RPUSH + * command. */ +int HelloPushNative_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + if (argc != 3) return RedisModule_WrongArity(ctx); + + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + + RedisModule_ListPush(key,REDISMODULE_LIST_TAIL,argv[2]); + size_t newlen = RedisModule_ValueLength(key); + RedisModule_CloseKey(key); + RedisModule_ReplyWithLongLong(ctx,newlen); + return REDISMODULE_OK; +} + +/* HELLO.PUSH.CALL implements RPUSH using an higher level approach, calling + * a Redis command instead of working with the key in a low level way. This + * approach is useful when you need to call Redis commands that are not + * available as low level APIs, or when you don't need the maximum speed + * possible but instead prefer implementation simplicity. */ +int HelloPushCall_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + if (argc != 3) return RedisModule_WrongArity(ctx); + + RedisModuleCallReply *reply; + + reply = RedisModule_Call(ctx,"RPUSH","ss",argv[1],argv[2]); + long long len = RedisModule_CallReplyInteger(reply); + RedisModule_FreeCallReply(reply); + RedisModule_ReplyWithLongLong(ctx,len); + return REDISMODULE_OK; +} + +/* HELLO.PUSH.CALL2 + * This is exactly as HELLO.PUSH.CALL, but shows how we can reply to the + * client using directly a reply object that Call() returned. */ +int HelloPushCall2_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + if (argc != 3) return RedisModule_WrongArity(ctx); + + RedisModuleCallReply *reply; + + reply = RedisModule_Call(ctx,"RPUSH","ss",argv[1],argv[2]); + RedisModule_ReplyWithCallReply(ctx,reply); + RedisModule_FreeCallReply(reply); + return REDISMODULE_OK; +} + +/* HELLO.LIST.SUM.LEN returns the total length of all the items inside + * a Redis list, by using the high level Call() API. + * This command is an example of the array reply access. */ +int HelloListSumLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + if (argc != 2) return RedisModule_WrongArity(ctx); + + RedisModuleCallReply *reply; + + reply = RedisModule_Call(ctx,"LRANGE","sll",argv[1],(long long)0,(long long)-1); + size_t strlen = 0; + size_t items = RedisModule_CallReplyLength(reply); + size_t j; + for (j = 0; j < items; j++) { + RedisModuleCallReply *ele = RedisModule_CallReplyArrayElement(reply,j); + strlen += RedisModule_CallReplyLength(ele); + } + RedisModule_FreeCallReply(reply); + RedisModule_ReplyWithLongLong(ctx,strlen); + return REDISMODULE_OK; +} + +/* HELLO.LIST.SPLICE srclist dstlist count + * Moves 'count' elements from the tail of 'srclist' to the head of + * 'dstlist'. If less than count elements are available, it moves as much + * elements as possible. */ +int HelloListSplice_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 4) return RedisModule_WrongArity(ctx); + + RedisModuleKey *srckey = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + RedisModuleKey *dstkey = RedisModule_OpenKey(ctx,argv[2], + REDISMODULE_READ|REDISMODULE_WRITE); + + /* Src and dst key must be empty or lists. */ + if ((RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_LIST && + RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_EMPTY) || + (RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_LIST && + RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_EMPTY)) + { + RedisModule_CloseKey(srckey); + RedisModule_CloseKey(dstkey); + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + + long long count; + if ((RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) || + (count < 0)) { + RedisModule_CloseKey(srckey); + RedisModule_CloseKey(dstkey); + return RedisModule_ReplyWithError(ctx,"ERR invalid count"); + } + + while(count-- > 0) { + RedisModuleString *ele; + + ele = RedisModule_ListPop(srckey,REDISMODULE_LIST_TAIL); + if (ele == NULL) break; + RedisModule_ListPush(dstkey,REDISMODULE_LIST_HEAD,ele); + RedisModule_FreeString(ctx,ele); + } + + size_t len = RedisModule_ValueLength(srckey); + RedisModule_CloseKey(srckey); + RedisModule_CloseKey(dstkey); + RedisModule_ReplyWithLongLong(ctx,len); + return REDISMODULE_OK; +} + +/* Like the HELLO.LIST.SPLICE above, but uses automatic memory management + * in order to avoid freeing stuff. */ +int HelloListSpliceAuto_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 4) return RedisModule_WrongArity(ctx); + + RedisModule_AutoMemory(ctx); + + RedisModuleKey *srckey = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + RedisModuleKey *dstkey = RedisModule_OpenKey(ctx,argv[2], + REDISMODULE_READ|REDISMODULE_WRITE); + + /* Src and dst key must be empty or lists. */ + if ((RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_LIST && + RedisModule_KeyType(srckey) != REDISMODULE_KEYTYPE_EMPTY) || + (RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_LIST && + RedisModule_KeyType(dstkey) != REDISMODULE_KEYTYPE_EMPTY)) + { + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + + long long count; + if ((RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) || + (count < 0)) + { + return RedisModule_ReplyWithError(ctx,"ERR invalid count"); + } + + while(count-- > 0) { + RedisModuleString *ele; + + ele = RedisModule_ListPop(srckey,REDISMODULE_LIST_TAIL); + if (ele == NULL) break; + RedisModule_ListPush(dstkey,REDISMODULE_LIST_HEAD,ele); + } + + size_t len = RedisModule_ValueLength(srckey); + RedisModule_ReplyWithLongLong(ctx,len); + return REDISMODULE_OK; +} + +/* HELLO.RAND.ARRAY <count> + * Shows how to generate arrays as commands replies. + * It just outputs <count> random numbers. */ +int HelloRandArray_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + long long count; + if (RedisModule_StringToLongLong(argv[1],&count) != REDISMODULE_OK || + count < 0) + return RedisModule_ReplyWithError(ctx,"ERR invalid count"); + + /* To reply with an array, we call RedisModule_ReplyWithArray() followed + * by other "count" calls to other reply functions in order to generate + * the elements of the array. */ + RedisModule_ReplyWithArray(ctx,count); + while(count--) RedisModule_ReplyWithLongLong(ctx,rand()); + return REDISMODULE_OK; +} + +/* This is a simple command to test replication. Because of the "!" modified + * in the RedisModule_Call() call, the two INCRs get replicated. + * Also note how the ECHO is replicated in an unexpected position (check + * comments the function implementation). */ +int HelloRepl1_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + RedisModule_AutoMemory(ctx); + + /* This will be replicated *after* the two INCR statements, since + * the Call() replication has precedence, so the actual replication + * stream will be: + * + * MULTI + * INCR foo + * INCR bar + * ECHO c foo + * EXEC + */ + RedisModule_Replicate(ctx,"ECHO","c","foo"); + + /* Using the "!" modifier we replicate the command if it + * modified the dataset in some way. */ + RedisModule_Call(ctx,"INCR","c!","foo"); + RedisModule_Call(ctx,"INCR","c!","bar"); + + RedisModule_ReplyWithLongLong(ctx,0); + + return REDISMODULE_OK; +} + +/* Another command to show replication. In this case, we call + * RedisModule_ReplicateVerbatim() to mean we want just the command to be + * propagated to slaves / AOF exactly as it was called by the user. + * + * This command also shows how to work with string objects. + * It takes a list, and increments all the elements (that must have + * a numerical value) by 1, returning the sum of all the elements + * as reply. + * + * Usage: HELLO.REPL2 <list-key> */ +int HelloRepl2_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + + if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_LIST) + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + + size_t listlen = RedisModule_ValueLength(key); + long long sum = 0; + + /* Rotate and increment. */ + while(listlen--) { + RedisModuleString *ele = RedisModule_ListPop(key,REDISMODULE_LIST_TAIL); + long long val; + if (RedisModule_StringToLongLong(ele,&val) != REDISMODULE_OK) val = 0; + val++; + sum += val; + RedisModuleString *newele = RedisModule_CreateStringFromLongLong(ctx,val); + RedisModule_ListPush(key,REDISMODULE_LIST_HEAD,newele); + } + RedisModule_ReplyWithLongLong(ctx,sum); + RedisModule_ReplicateVerbatim(ctx); + return REDISMODULE_OK; +} + +/* This is an example of strings DMA access. Given a key containing a string + * it toggles the case of each character from lower to upper case or the + * other way around. + * + * No automatic memory management is used in this example (for the sake + * of variety). + * + * HELLO.TOGGLE.CASE key */ +int HelloToggleCase_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (argc != 2) return RedisModule_WrongArity(ctx); + + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + + int keytype = RedisModule_KeyType(key); + if (keytype != REDISMODULE_KEYTYPE_STRING && + keytype != REDISMODULE_KEYTYPE_EMPTY) + { + RedisModule_CloseKey(key); + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + + if (keytype == REDISMODULE_KEYTYPE_STRING) { + size_t len, j; + char *s = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE); + for (j = 0; j < len; j++) { + if (isupper(s[j])) { + s[j] = tolower(s[j]); + } else { + s[j] = toupper(s[j]); + } + } + } + + RedisModule_CloseKey(key); + RedisModule_ReplyWithSimpleString(ctx,"OK"); + RedisModule_ReplicateVerbatim(ctx); + return REDISMODULE_OK; +} + +/* HELLO.MORE.EXPIRE key milliseconds. + * + * If the key has already an associated TTL, extends it by "milliseconds" + * milliseconds. Otherwise no operation is performed. */ +int HelloMoreExpire_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ + if (argc != 3) return RedisModule_WrongArity(ctx); + + mstime_t addms, expire; + + if (RedisModule_StringToLongLong(argv[2],&addms) != REDISMODULE_OK) + return RedisModule_ReplyWithError(ctx,"ERR invalid expire time"); + + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + expire = RedisModule_GetExpire(key); + if (expire != REDISMODULE_NO_EXPIRE) { + expire += addms; + RedisModule_SetExpire(key,expire); + } + return RedisModule_ReplyWithSimpleString(ctx,"OK"); +} + +/* HELLO.ZSUMRANGE key startscore endscore + * Return the sum of all the scores elements between startscore and endscore. + * + * The computation is performed two times, one time from start to end and + * another time backward. The two scores, returned as a two element array, + * should match.*/ +int HelloZsumRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + double score_start, score_end; + if (argc != 4) return RedisModule_WrongArity(ctx); + + if (RedisModule_StringToDouble(argv[2],&score_start) != REDISMODULE_OK || + RedisModule_StringToDouble(argv[3],&score_end) != REDISMODULE_OK) + { + return RedisModule_ReplyWithError(ctx,"ERR invalid range"); + } + + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET) { + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + + double scoresum_a = 0; + double scoresum_b = 0; + + RedisModule_ZsetFirstInScoreRange(key,score_start,score_end,0,0); + while(!RedisModule_ZsetRangeEndReached(key)) { + double score; + RedisModuleString *ele = RedisModule_ZsetRangeCurrentElement(key,&score); + RedisModule_FreeString(ctx,ele); + scoresum_a += score; + RedisModule_ZsetRangeNext(key); + } + RedisModule_ZsetRangeStop(key); + + RedisModule_ZsetLastInScoreRange(key,score_start,score_end,0,0); + while(!RedisModule_ZsetRangeEndReached(key)) { + double score; + RedisModuleString *ele = RedisModule_ZsetRangeCurrentElement(key,&score); + RedisModule_FreeString(ctx,ele); + scoresum_b += score; + RedisModule_ZsetRangePrev(key); + } + + RedisModule_ZsetRangeStop(key); + + RedisModule_CloseKey(key); + + RedisModule_ReplyWithArray(ctx,2); + RedisModule_ReplyWithDouble(ctx,scoresum_a); + RedisModule_ReplyWithDouble(ctx,scoresum_b); + return REDISMODULE_OK; +} + +/* HELLO.LEXRANGE key min_lex max_lex min_age max_age + * This command expects a sorted set stored at key in the following form: + * - All the elements have score 0. + * - Elements are pairs of "<name>:<age>", for example "Anna:52". + * The command will return all the sorted set items that are lexicographically + * between the specified range (using the same format as ZRANGEBYLEX) + * and having an age between min_age and max_age. */ +int HelloLexRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ + + if (argc != 6) return RedisModule_WrongArity(ctx); + + RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1], + REDISMODULE_READ|REDISMODULE_WRITE); + if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_ZSET) { + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + + if (RedisModule_ZsetFirstInLexRange(key,argv[2],argv[3]) != REDISMODULE_OK) { + return RedisModule_ReplyWithError(ctx,"invalid range"); + } + + int arraylen = 0; + RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN); + while(!RedisModule_ZsetRangeEndReached(key)) { + double score; + RedisModuleString *ele = RedisModule_ZsetRangeCurrentElement(key,&score); + RedisModule_ReplyWithString(ctx,ele); + RedisModule_FreeString(ctx,ele); + RedisModule_ZsetRangeNext(key); + arraylen++; + } + RedisModule_ZsetRangeStop(key); + RedisModule_ReplySetArrayLength(ctx,arraylen); + RedisModule_CloseKey(key); + return REDISMODULE_OK; +} + +/* HELLO.HCOPY key srcfield dstfield + * This is just an example command that sets the hash field dstfield to the + * same value of srcfield. If srcfield does not exist no operation is + * performed. + * + * The command returns 1 if the copy is performed (srcfield exists) otherwise + * 0 is returned. */ +int HelloHCopy_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_HASH && + type != REDISMODULE_KEYTYPE_EMPTY) + { + return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE); + } + + /* Get the old field value. */ + RedisModuleString *oldval; + RedisModule_HashGet(key,REDISMODULE_HASH_NONE,argv[2],&oldval,NULL); + if (oldval) { + RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[3],oldval,NULL); + } + RedisModule_ReplyWithLongLong(ctx,oldval != NULL); + return REDISMODULE_OK; +} + +/* HELLO.LEFTPAD str len ch + * This is an implementation of the infamous LEFTPAD function, that + * was at the center of an issue with the npm modules system in March 2016. + * + * LEFTPAD is a good example of using a Redis Modules API called + * "pool allocator", that was a famous way to allocate memory in yet another + * open source project, the Apache web server. + * + * The concept is very simple: there is memory that is useful to allocate + * only in the context of serving a request, and must be freed anyway when + * the callback implementing the command returns. So in that case the module + * does not need to retain a reference to these allocations, it is just + * required to free the memory before returning. When this is the case the + * module can call RedisModule_PoolAlloc() instead, that works like malloc() + * but will automatically free the memory when the module callback returns. + * + * Note that PoolAlloc() does not necessarily require AutoMemory to be + * active. */ +int HelloLeftPad_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + RedisModule_AutoMemory(ctx); /* Use automatic memory management. */ + long long padlen; + + if (argc != 4) return RedisModule_WrongArity(ctx); + + if ((RedisModule_StringToLongLong(argv[2],&padlen) != REDISMODULE_OK) || + (padlen< 0)) { + return RedisModule_ReplyWithError(ctx,"ERR invalid padding length"); + } + size_t strlen, chlen; + const char *str = RedisModule_StringPtrLen(argv[1], &strlen); + const char *ch = RedisModule_StringPtrLen(argv[3], &chlen); + + /* If the string is already larger than the target len, just return + * the string itself. */ + if (strlen >= (size_t)padlen) + return RedisModule_ReplyWithString(ctx,argv[1]); + + /* Padding must be a single character in this simple implementation. */ + if (chlen != 1) + return RedisModule_ReplyWithError(ctx, + "ERR padding must be a single char"); + + /* Here we use our pool allocator, for our throw-away allocation. */ + padlen -= strlen; + char *buf = RedisModule_PoolAlloc(ctx,padlen+strlen); + for (long long j = 0; j < padlen; j++) buf[j] = *ch; + memcpy(buf+padlen,str,strlen); + + RedisModule_ReplyWithStringBuffer(ctx,buf,padlen+strlen); + return REDISMODULE_OK; +} + +/* This function must be present on each Redis module. It is used in order to + * register the commands into the Redis server. */ +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1) + == REDISMODULE_ERR) return REDISMODULE_ERR; + + /* Log the list of parameters passing loading the module. */ + for (int j = 0; j < argc; j++) { + const char *s = RedisModule_StringPtrLen(argv[j],NULL); + printf("Module loaded with ARGV[%d] = %s\n", j, s); + } + + if (RedisModule_CreateCommand(ctx,"hello.simple", + HelloSimple_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.push.native", + HelloPushNative_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.push.call", + HelloPushCall_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.push.call2", + HelloPushCall2_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.list.sum.len", + HelloListSumLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.list.splice", + HelloListSplice_RedisCommand,"write deny-oom",1,2,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.list.splice.auto", + HelloListSpliceAuto_RedisCommand, + "write deny-oom",1,2,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.rand.array", + HelloRandArray_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.repl1", + HelloRepl1_RedisCommand,"write",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.repl2", + HelloRepl2_RedisCommand,"write",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.toggle.case", + HelloToggleCase_RedisCommand,"write",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.more.expire", + HelloMoreExpire_RedisCommand,"write",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.zsumrange", + HelloZsumRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.lexrange", + HelloLexRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.hcopy", + HelloHCopy_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"hello.leftpad", + HelloLeftPad_RedisCommand,"",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} |