summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--redis.conf8
-rw-r--r--src/Makefile2
-rw-r--r--src/config.c2
-rw-r--r--src/module.c1384
-rw-r--r--src/modules/.gitignore2
-rw-r--r--src/modules/API.md695
-rw-r--r--src/modules/Makefile17
-rw-r--r--src/modules/helloworld.c334
-rw-r--r--src/networking.c2
-rw-r--r--src/redismodule.h152
-rw-r--r--src/server.c17
-rw-r--r--src/server.h12
-rw-r--r--src/util.c2
13 files changed, 2625 insertions, 4 deletions
diff --git a/redis.conf b/redis.conf
index c68fe694f..a39a643ab 100644
--- a/redis.conf
+++ b/redis.conf
@@ -35,6 +35,14 @@
# include /path/to/local.conf
# include /path/to/other.conf
+################################## MODULES #####################################
+
+# Load modules at startup. If the server is not able to load modules
+# it will abort. It is possible to use multiple loadmodule directives.
+#
+# loadmodule /path/to/my_module.so
+# loadmodule /path/to/other_module.so
+
################################## NETWORK #####################################
# By default, if no "bind" configuration directive is specified, Redis listens
diff --git a/src/Makefile b/src/Makefile
index a958ad2d4..c390d3f2e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -117,7 +117,7 @@ endif
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
-REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o geo.o lazyfree.o
+REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o geo.o lazyfree.o module.o
REDIS_GEOHASH_OBJ=../deps/geohash-int/geohash.o ../deps/geohash-int/geohash_helper.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
diff --git a/src/config.c b/src/config.c
index 1fc24a56c..c72f0aeb2 100644
--- a/src/config.c
+++ b/src/config.c
@@ -632,6 +632,8 @@ void loadServerConfigFromString(char *config) {
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"loadmodule") && argc == 2) {
+ listAddNodeTail(server.loadmodule_queue,sdsnew(argv[1]));
} else if (!strcasecmp(argv[0],"sentinel")) {
/* argc == 1 is handled by main() as we need to enter the sentinel
* mode ASAP. */
diff --git a/src/module.c b/src/module.c
new file mode 100644
index 000000000..45cd079b2
--- /dev/null
+++ b/src/module.c
@@ -0,0 +1,1384 @@
+#include "server.h"
+#include "cluster.h"
+#include <dlfcn.h>
+
+#define REDISMODULE_CORE 1
+#include "redismodule.h"
+
+/* --------------------------------------------------------------------------
+ * Private data structures used by the modules system. Those are data
+ * structures that are never exposed to Redis Modules, if not as void
+ * pointers that have an API the module can call with them)
+ * -------------------------------------------------------------------------- */
+
+/* This structure represents a module inside the system. */
+struct RedisModule {
+ void *handle; /* Module dlopen() handle. */
+ char *name; /* Module name. */
+ int ver; /* Module version. We use just progressive integers. */
+ int apiver; /* Module API version as requested during initialization.*/
+};
+typedef struct RedisModule RedisModule;
+
+static dict *modules; /* Hash table of modules. SDS -> RedisModule ptr.*/
+
+/* Entries in the context->amqueue array, representing objects to free
+ * when the callback returns. */
+struct AutoMemEntry {
+ void *ptr;
+ int type;
+};
+
+/* AutMemEntry type field values. */
+#define REDISMODULE_AM_KEY 0
+#define REDISMODULE_AM_STRING 1
+#define REDISMODULE_AM_REPLY 2
+#define REDISMODULE_AM_FREED 3 /* Explicitly freed by user already. */
+
+/* This structure represents the context in which Redis modules operate.
+ * Most APIs module can access, get a pointer to the context, so that the API
+ * implementation can hold state across calls, or remember what to free after
+ * the call and so forth.
+ *
+ * Note that not all the context structure is always filled with actual values
+ * but only the fields needed in a given context. */
+struct RedisModuleCtx {
+ void *getapifuncptr; /* NOTE: Must be the first field. */
+ struct RedisModule *module; /* Module reference. */
+ client *client; /* Client calling a command. */
+ struct AutoMemEntry *amqueue; /* Auto memory queue of objects to free. */
+ int amqueue_len; /* Number of slots in amqueue. */
+ int amqueue_used; /* Number of used slots in amqueue. */
+ int flags; /* REDISMODULE_CTX_... flags. */
+};
+typedef struct RedisModuleCtx RedisModuleCtx;
+
+#define REDISMODULE_CTX_INIT {(void*)&RedisModule_GetApi, NULL, NULL, NULL, 0, 0, 0}
+#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
+#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
+
+/* This represents a Redis key opened with RedisModule_OpenKey(). */
+struct RedisModuleKey {
+ RedisModuleCtx *ctx;
+ redisDb *db;
+ robj *key; /* Key name object. */
+ robj *value; /* Value object, or NULL if the key was not found. */
+ void *iter; /* Iterator. */
+ int mode; /* Opening mode. */
+};
+typedef struct RedisModuleKey RedisModuleKey;
+
+/* Function pointer type of a function representing a command inside
+ * a Redis module. */
+typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, void **argv, int argc);
+
+/* This struct holds the information about a command registered by a module.*/
+struct RedisModuleCommandProxy {
+ struct RedisModule *module;
+ RedisModuleCmdFunc func;
+ struct redisCommand *rediscmd;
+};
+typedef struct RedisModuleCommandProxy RedisModuleCommandProxy;
+
+#define REDISMODULE_REPLYFLAG_NONE 0
+#define REDISMODULE_REPLYFLAG_TOPARSE (1<<0) /* Protocol must be parsed. */
+#define REDISMODULE_REPLYFLAG_NESTED (1<<1) /* Nested reply object. No proto
+ or struct free. */
+
+/* Reply of RedisModule_Call() function. The function is filled in a lazy
+ * way depending on the function called on the reply structure. By default
+ * only the type and proto are filled. */
+struct RedisModuleCallReply {
+ RedisModuleCtx *ctx;
+ int type; /* REDISMODULE_REPLY_... */
+ int flags; /* REDISMODULE_REPLYFLAG_... */
+ size_t len; /* Len of strings or num of elements of arrays. */
+ char *proto; /* Raw reply protocol. An SDS string at top-level object. */
+ size_t protolen;/* Length of protocol. */
+ union {
+ const char *str; /* String pointer for string and error replies. This
+ does not need to be freed, always points inside
+ a reply->proto buffer of the reply object or, in
+ case of array elements, of parent reply objects. */
+ long long ll; /* Reply value for integer reply. */
+ struct RedisModuleCallReply *array; /* Array of sub-reply elements. */
+ } val;
+};
+typedef struct RedisModuleCallReply RedisModuleCallReply;
+
+/* --------------------------------------------------------------------------
+ * Prototypes
+ * -------------------------------------------------------------------------- */
+
+void RedisModule_FreeCallReply(RedisModuleCallReply *reply);
+void RedisModule_CloseKey(RedisModuleKey *key);
+void RedisModule_AutoMemoryCollect(RedisModuleCtx *ctx);
+robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap);
+void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx);
+
+/* --------------------------------------------------------------------------
+ * Helpers for modules API implementation
+ * -------------------------------------------------------------------------- */
+
+/* Create an empty key of the specified type. 'kp' must point to a key object
+ * opened for writing where the .value member is set to NULL because the
+ * key was found to be non existing.
+ *
+ * On success REDISMODULE_OK is returned and the key is populated with
+ * the value of the specified type. The function fails and returns
+ * REDISMODULE_ERR if:
+ *
+ * 1) The key is not open for writing.
+ * 2) The key is not empty.
+ * 3) The specified type is unknown.
+ */
+int moduleCreateEmtpyKey(RedisModuleKey *key, int type) {
+ robj *obj;
+
+ /* The key must be open for writing and non existing to proceed. */
+ if (!(key->mode & REDISMODULE_WRITE) || key->value)
+ return REDISMODULE_ERR;
+
+ switch(type) {
+ case REDISMODULE_KEYTYPE_LIST:
+ obj = createQuicklistObject();
+ quicklistSetOptions(obj->ptr, server.list_max_ziplist_size,
+ server.list_compress_depth);
+ break;
+ default: return REDISMODULE_ERR;
+ }
+ dbAdd(key->db,key->key,obj);
+ key->value = obj;
+ return REDISMODULE_OK;
+}
+
+/* This function is called in low-level API implementation functions in order
+ * to check if the value associated with the key remained empty after an
+ * operation that removed elements from an aggregate data type.
+ *
+ * If this happens, the key is deleted from the DB and the key object state
+ * is set to the right one in order to be targeted again by write operations
+ * possibly recreating the key if needed.
+ *
+ * The function returns 1 if the key value object is found empty and is
+ * deleted, otherwise 0 is returned. */
+int moduleDelKeyIfEmpty(RedisModuleKey *key) {
+ if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL) return 0;
+ int isempty;
+ robj *o = key->value;
+
+ switch(o->type) {
+ case OBJ_LIST: isempty = listTypeLength(o) == 0; break;
+ case OBJ_SET: isempty = setTypeSize(o) == 0; break;
+ case OBJ_ZSET: isempty = zsetLength(o) == 0; break;
+ case OBJ_HASH : isempty = hashTypeLength(o) == 0; break;
+ default: isempty = 0;
+ }
+
+ if (isempty) {
+ dbDelete(key->db,key->key);
+ key->value = NULL;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------
+ * Service API exported to modules
+ * -------------------------------------------------------------------------- */
+
+/* Lookup the requested module API and store the function pointer into the
+ * target pointer. The function returns REDISMODULE_ERR if there is no such
+ * named API, otherwise REDISMODULE_OK. */
+int RedisModule_GetApi(const char *funcname, void **targetPtrPtr) {
+ dictEntry *he = dictFind(server.moduleapi, funcname);
+ if (!he) return REDISMODULE_ERR;
+ *targetPtrPtr = dictGetVal(he);
+ return REDISMODULE_OK;
+}
+
+/* This Redis command binds the normal Redis command invocation with commands
+ * exported by modules. */
+void RedisModuleCommandDispatcher(client *c) {
+ RedisModuleCommandProxy *cp = (void*)c->cmd->getkeys_proc;
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+
+ ctx.module = cp->module;
+ ctx.client = c;
+ cp->func(&ctx,(void**)c->argv,c->argc);
+ RedisModule_AutoMemoryCollect(&ctx);
+ preventCommandPropagation(c);
+
+ /* Handle the replication of the final EXEC, since whatever a command
+ * emits is always wrappered around MULTI/EXEC. */
+ if (ctx.flags & REDISMODULE_CTX_MULTI_EMITTED) {
+ robj *propargv[1];
+ propargv[0] = createStringObject("EXEC",4);
+ alsoPropagate(server.execCommand,c->db->id,propargv,1,
+ PROPAGATE_AOF|PROPAGATE_REPL);
+ decrRefCount(propargv[0]);
+ }
+}
+
+/* Register a new command in the Redis server, that will be handled by
+ * calling the function pointer 'func' using the RedisModule calling
+ * convention. The function returns REDISMODULE_ERR if the specified command
+ * name is already busy, otherwise REDISMODULE_OK. */
+int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc) {
+ struct redisCommand *rediscmd;
+ RedisModuleCommandProxy *cp;
+ sds cmdname = sdsnew(name);
+
+ /* Check if the command name is busy. */
+ if (lookupCommand((char*)name) != NULL) {
+ sdsfree(cmdname);
+ return REDISMODULE_ERR;
+ }
+
+ /* Create a command "proxy", which is a structure that is referenced
+ * in the command table, so that the generic command that works as
+ * binidng between modules and Redis, can know what function to call
+ * and what the module is.
+ *
+ * Note that we use the Redis command table 'getkeys_proc' in order to
+ * pass a reference to the command proxy structure. */
+ cp = zmalloc(sizeof(*cp));
+ cp->module = ctx->module;
+ cp->func = cmdfunc;
+ cp->rediscmd = zmalloc(sizeof(*rediscmd));
+ cp->rediscmd->name = cmdname;
+ cp->rediscmd->proc = RedisModuleCommandDispatcher;
+ cp->rediscmd->arity = -1;
+ cp->rediscmd->flags = 0;
+ cp->rediscmd->getkeys_proc = (redisGetKeysProc*)cp;
+ cp->rediscmd->firstkey = 1;
+ cp->rediscmd->lastkey = 1;
+ cp->rediscmd->keystep = 1;
+ cp->rediscmd->microseconds = 0;
+ cp->rediscmd->calls = 0;
+ dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
+ dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
+ return REDISMODULE_OK;
+}
+
+/* Called by RedisModule_Init() to setup the ctx->module structure. */
+void RedisModule_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver){
+ RedisModule *module;
+
+ if (ctx->module != NULL) return;
+ module = zmalloc(sizeof(*module));
+ module->name = sdsnew((char*)name);
+ module->ver = ver;
+ module->apiver = apiver;
+ ctx->module = module;
+}
+
+/* --------------------------------------------------------------------------
+ * Automatic memory management for modules
+ * -------------------------------------------------------------------------- */
+
+/* Enable auto memory. */
+void RedisModule_AutoMemory(RedisModuleCtx *ctx) {
+ ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY;
+}
+
+/* Add a new object to release automatically when the callback returns. */
+void RedisModule_AutoMemoryAdd(RedisModuleCtx *ctx, int type, void *ptr) {
+ if (!(ctx->flags & REDISMODULE_CTX_AUTO_MEMORY)) return;
+ if (ctx->amqueue_used == ctx->amqueue_len) {
+ ctx->amqueue_len *= 2;
+ if (ctx->amqueue_len < 16) ctx->amqueue_len = 16;
+ ctx->amqueue = zrealloc(ctx->amqueue,sizeof(struct AutoMemEntry)*ctx->amqueue_len);
+ }
+ ctx->amqueue[ctx->amqueue_used].type = type;
+ ctx->amqueue[ctx->amqueue_used].ptr = ptr;
+ ctx->amqueue_used++;
+}
+
+/* Mark an object as freed in the auto release queue, so that users can still
+ * free things manually if they want. */
+void RedisModule_AutoMemoryFreed(RedisModuleCtx *ctx, int type, void *ptr) {
+ if (!(ctx->flags & REDISMODULE_CTX_AUTO_MEMORY)) return;
+
+ int j;
+ for (j = 0; j < ctx->amqueue_used; j++) {
+ if (ctx->amqueue[j].type == type &&
+ ctx->amqueue[j].ptr == ptr)
+ {
+ ctx->amqueue[j].type = REDISMODULE_AM_FREED;
+ /* Optimization: if this is the last element, we can
+ * reuse it. */
+ if (j == ctx->amqueue_used-1) ctx->amqueue_used--;
+ }
+ }
+}
+
+/* Release all the objects in queue. */
+void RedisModule_AutoMemoryCollect(RedisModuleCtx *ctx) {
+ if (!(ctx->flags & REDISMODULE_CTX_AUTO_MEMORY)) return;
+ /* Clear the AUTO_MEMORY flag from the context, otherwise the functions
+ * we call to free the resources, will try to scan the auto release
+ * queue to mark the entries as freed. */
+ ctx->flags &= ~REDISMODULE_CTX_AUTO_MEMORY;
+ int j;
+ for (j = 0; j < ctx->amqueue_used; j++) {
+ void *ptr = ctx->amqueue[j].ptr;
+ switch(ctx->amqueue[j].type) {
+ case REDISMODULE_AM_STRING: decrRefCount(ptr); break;
+ case REDISMODULE_AM_REPLY: RedisModule_FreeCallReply(ptr); break;
+ case REDISMODULE_AM_KEY: RedisModule_CloseKey(ptr); break;
+ }
+ }
+ ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY;
+ zfree(ctx->amqueue);
+ ctx->amqueue = NULL;
+ ctx->amqueue_len = 0;
+ ctx->amqueue_used = 0;
+}
+
+/* --------------------------------------------------------------------------
+ * String objects APIs
+ * -------------------------------------------------------------------------- */
+
+/* Create a new module string object. Must be freed with
+ * RedisModule_FreeString(), unless automatic memory is enabled. */
+RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len)
+{
+ RedisModuleString *o = createStringObject(ptr,len);
+ RedisModule_AutoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
+ return o;
+}
+
+/* Like RedisModule_CreatString, but creates a string starting from a long long
+ * integer instea of taking a buffer and length. */
+RedisModuleString *RedisModule_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll) {
+ char buf[LONG_STR_SIZE];
+ size_t len = ll2string(buf,sizeof(buf),ll);
+ return RedisModule_CreateString(ctx,buf,len);
+}
+
+/* Free a module string object obtained with one of the Redis API calls
+ * that return new string objects. */
+void RedisModule_FreeString(RedisModuleCtx *ctx, RedisModuleString *str) {
+ decrRefCount(str);
+ RedisModule_AutoMemoryFreed(ctx,REDISMODULE_AM_STRING,str);
+}
+
+/* Return the string pointer and length. */
+const char *RedisModule_StringPtrLen(RedisModuleString *str, size_t *len) {
+ if (len) *len = sdslen(str->ptr);
+ return str->ptr;
+}
+
+/* Turn the string into a long long, storing it at *ll if not NULL.
+ * Returns REDISMODULE_OK on success. If the string can't be parsed
+ * as a valid, strict long long (no spaces before/after), REDISMODULE_ERR
+ * is returned. */
+int RedisModule_StringToLongLong(RedisModuleString *str, long long *ll) {
+ return string2ll(str->ptr,sdslen(str->ptr),ll) ? REDISMODULE_OK :
+ REDISMODULE_ERR;
+}
+
+/* --------------------------------------------------------------------------
+ * Reply APIs
+ *
+ * Most functions always return REDISMODULE_OK so you can use it with
+ * 'return' in order to return from the command implementation with:
+ *
+ * if (... some condition ...)
+ * return RedisModule_ReplyWithLongLong(ctx,mycount);
+ * -------------------------------------------------------------------------- */
+
+/* Send an error about the number of arguments given to the command. */
+int RedisModule_WrongArity(RedisModuleCtx *ctx) {
+ addReplyErrorFormat(ctx->client,
+ "wrong number of arguments for '%s' command",
+ (char*)ctx->client->argv[0]->ptr);
+ return REDISMODULE_OK;
+}
+
+/* Send an integer reply with the specified long long value.
+ * The function always returns REDISMODULE_OK. */
+int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
+ addReplyLongLong(ctx->client,ll);
+ return REDISMODULE_OK;
+}
+
+/* Reply with an error or simple string (status message). Used to implement
+ * ReplyWithSimpleString() and ReplyWithError(). */
+int RedisModule_ReplyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
+ sds strmsg = sdsnewlen(prefix,1);
+ strmsg = sdscat(strmsg,msg);
+ strmsg = sdscatlen(strmsg,"\r\n",2);
+ addReplySds(ctx->client,strmsg);
+ return REDISMODULE_OK;
+}
+
+/* Reply with the error 'err'.
+ *
+ * Note that 'err' must contain all the error, including
+ * the initial error code. The function only provides the initial "-", so
+ * the usage is, for example:
+ *
+ * RedisModule_ReplyWithError(ctx,"ERR Wrong Type");
+ *
+ * and not just:
+ *
+ * RedisModule_ReplyWithError(ctx,"Wrong Type");
+ */
+int RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err) {
+ return RedisModule_ReplyWithStatus(ctx,err,"-");
+}
+
+/* Reply with a simple string (+... \r\n in RESP protocol). This replies
+ * are suitalbe only when sending a small non-binary string wiht small
+ * overhead, like "OK" or similar replies. */
+int RedisModule_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) {
+ return RedisModule_ReplyWithStatus(ctx,msg,"+");
+}
+
+/* Reply with an array type of 'len' elements. However 'len' other calls
+ * to ReplyWith* style functions must follow in order to emit the elements
+ * of the array. */
+int RedisModule_ReplyWithArray(RedisModuleCtx *ctx, int len) {
+ addReplyMultiBulkLen(ctx->client,len);
+ return REDISMODULE_OK;
+}
+
+/* Reply with a bulk string, taking in input a C buffer pointer and length. */
+int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
+ addReplyBulkCBuffer(ctx->client,(char*)buf,len);
+ return REDISMODULE_OK;
+}
+
+/* Reply with a bulk string, taking in input a RedisModuleString object. */
+int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) {
+ addReplyBulk(ctx->client,str);
+ return REDISMODULE_OK;
+}
+
+/* --------------------------------------------------------------------------
+ * Commands replication API
+ * -------------------------------------------------------------------------- */
+
+/* Helper function to replicate MULTI the first time we replicate something
+ * in the context of a command execution. EXEC will be handled by the
+ * RedisModuleCommandDispatcher() function. */
+void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
+ if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
+ execCommandPropagateMulti(ctx->client);
+ ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
+}
+
+/* Replicate the specified command and arguments to slaves and AOF, as effect
+ * of execution of the calling command implementation.
+ *
+ * The replicated commands are always wrapepd into the MULTI/EXEC that
+ * contains all the commands replicated in a given module command
+ * execution. However the commands replicated with RedisModule_Call()
+ * are the first items, the ones replicated with RedisModule_Replicate()
+ * will all follow before the EXEC.
+ *
+ * Modules should try to use one interface or the other. */
+int RedisModule_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
+ struct redisCommand *cmd;
+ robj **argv = NULL;
+ int argc = 0, flags = 0, j;
+ va_list ap;
+
+ cmd = lookupCommandByCString((char*)cmdname);
+ if (!cmd) return REDISMODULE_ERR;
+
+ /* Create the client and dispatch the command. */
+ va_start(ap, fmt);
+ argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap);
+ va_end(ap);
+ if (argv == NULL) return REDISMODULE_ERR;
+
+ /* Replicate! */
+ moduleReplicateMultiIfNeeded(ctx);
+ alsoPropagate(cmd,ctx->client->db->id,argv,argc,
+ PROPAGATE_AOF|PROPAGATE_REPL);
+
+ /* Release the argv. */
+ for (j = 0; j < argc; j++) decrRefCount(argv[j]);
+ zfree(argv);
+ return REDISMODULE_OK;
+}
+
+/* This function will replicate the command exactly as it was invoked
+ * by the client. This function will not wrap the command into
+ * a MULTI/EXEC stanza, so it should not be mixed with other replication
+ * commands. */
+int RedisModule_ReplicateVerbatim(RedisModuleCtx *ctx) {
+ alsoPropagate(ctx->client->cmd,ctx->client->db->id,
+ ctx->client->argv,ctx->client->argc,
+ PROPAGATE_AOF|PROPAGATE_REPL);
+ return REDISMODULE_OK;
+}
+
+/* --------------------------------------------------------------------------
+ * DB and Key APIs -- Generic API
+ * -------------------------------------------------------------------------- */
+
+/* Return the currently selected DB. */
+int RedisModule_GetSelectedDb(RedisModuleCtx *ctx) {
+ return ctx->client->db->id;
+}
+
+/* Change the currently selected DB. Returns an error if the id
+ * is out of range. */
+int RedisModule_SelectDb(RedisModuleCtx *ctx, int newid) {
+ int retval = selectDb(ctx->client,newid);
+ return (retval == C_OK) ? REDISMODULE_OK : REDISMODULE_ERR;
+}
+
+/* Return an handle representing a Redis key, so that it is possible
+ * to call other APIs with the key handle as argument to perform
+ * operations on the key.
+ *
+ * The return value is the handle repesenting the key, that must be
+ * closed with RedisModule_CloseKey().
+ *
+ * If the key does not exist and WRITE mode is requested, the handle
+ * is still returned, since it is possible to perform operations on
+ * a yet not existing key (that will be created, for example, after
+ * a list push operation). If the mode is just READ instead, and the
+ * key does not exist, NULL is returned. However it is still safe to
+ * call RedisModule_CloseKey() and RedisModule_KeyType() on a NULL
+ * value. */
+void *RedisModule_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) {
+ RedisModuleKey *kp;
+ robj *value;
+
+ if (mode & REDISMODULE_WRITE) {
+ value = lookupKeyWrite(ctx->client->db,keyname);
+ } else {
+ value = lookupKeyRead(ctx->client->db,keyname);
+ if (value == NULL) {
+ decrRefCount(keyname);
+ return NULL;
+ }
+ }
+
+ /* Setup the key handle. */
+ kp = zmalloc(sizeof(*kp));
+ kp->ctx = ctx;
+ kp->db = ctx->client->db;
+ kp->key = keyname;
+ incrRefCount(keyname);
+ kp->value = value;
+ kp->iter = NULL;
+ kp->mode = mode;
+ RedisModule_AutoMemoryAdd(ctx,REDISMODULE_AM_KEY,kp);
+ return (void*)kp;
+}
+
+/* Close a key handle. */
+void RedisModule_CloseKey(RedisModuleKey *key) {
+ if (key == NULL) return;
+ if (key->mode & REDISMODULE_WRITE) signalModifiedKey(key->db,key->key);
+ /* TODO: if (key->iter) RedisModule_KeyIteratorStop(kp); */
+ decrRefCount(key->key);
+ RedisModule_AutoMemoryFreed(key->ctx,REDISMODULE_AM_KEY,key);
+ zfree(key);
+}
+
+/* Return the type of the key. If the key pointer is NULL then
+ * REDISMODULE_KEYTYPE_EMPTY is returned. */
+int RedisModule_KeyType(RedisModuleKey *key) {
+ if (key == NULL || key->value == NULL) return REDISMODULE_KEYTYPE_EMPTY;
+ /* We map between defines so that we are free to change the internal
+ * defines as desired. */
+ switch(key->value->type) {
+ case OBJ_STRING: return REDISMODULE_KEYTYPE_STRING;
+ case OBJ_LIST: return REDISMODULE_KEYTYPE_LIST;
+ case OBJ_SET: return REDISMODULE_KEYTYPE_SET;
+ case OBJ_ZSET: return REDISMODULE_KEYTYPE_ZSET;
+ case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH;
+ default: return 0;
+ }
+}
+
+/* Return the length of the value associated with the key.
+ * For strings this is the length of the string. For all the other types
+ * is the number of elements (just counting keys for hashes).
+ *
+ * If the key pointer is NULL or the key is empty, zero is returned. */
+size_t RedisModule_ValueLength(RedisModuleKey *key) {
+ if (key == NULL || key->value == NULL) return 0;
+ switch(key->value->type) {
+ case OBJ_STRING: return stringObjectLen(key->value);
+ case OBJ_LIST: return listTypeLength(key->value);
+ case OBJ_SET: return setTypeSize(key->value);
+ case OBJ_ZSET: return zsetLength(key->value);
+ case OBJ_HASH: return hashTypeLength(key->value);
+ default: return 0;
+ }
+}
+
+/* If the key is open for writing, remove it, and setup the key to
+ * accept new writes as an empty key (that will be created on demand).
+ * On success REDISMODULE_OK is returned. If the key is not open for
+ * writing REDISMODULE_ERR is returned. */
+int RedisModule_DeleteKey(RedisModuleKey *key) {
+ if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
+ if (key->value) {
+ dbDelete(key->db,key->key);
+ key->value = NULL;
+ }
+ return REDISMODULE_OK;
+}
+
+/* --------------------------------------------------------------------------
+ * Key API for String type
+ * -------------------------------------------------------------------------- */
+
+/* If the key is open for writing, set the specified string 'str' as the
+ * value of the key, deleting the old value if any.
+ * On success REDISMODULE_OK is returned. If the key is not open for
+ * writing or there is an active iterator, REDISMODULE_ERR is returned. */
+int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str) {
+ if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
+ RedisModule_DeleteKey(key);
+ setKey(key->db,key->key,str);
+ return REDISMODULE_OK;
+}
+
+/* Prepare the key associated string value for DMA access, and returns
+ * a pointer and size (by reference), that the user can use to read or
+ * modify the string in-place accessing it directly via pointer.
+ *
+ * The 'mode' is composed by bitwise OR-ing the following flags:
+ *
+ * REDISMODULE_READ -- Read access
+ * REDISMODULE_WRITE -- WRite access
+ *
+ * If the DMA is not requested for writing, the pointer returned should
+ * only be accessed in a read-only fashion.
+ *
+ * On error (wrong type) NULL is returned.
+ *
+ * DMA access rules:
+ *
+ * 1. No other key writing function should be called since the moment
+ * the pointer is obtained, for all the time we want to use DMA access
+ * to read or modify the string.
+ *
+ * 2. Each time RedisModule_StringTruncate() is called, to continue with the DMA
+ * access, RedisModule_StringDMA() should be called again to re-obtain
+ * a new pointer and length.
+ *
+ * 3. If the returned pointer is not NULL, but the length is zero, no
+ * byte can be touched (the string is empty, or the key itself is empty)
+ * so a RedisModule_StringTruncate() call should be used if there is to enlarge
+ * the string, and later call StringDMA() again to get the pointer.
+ */
+char *RedisModule_StringDMA(RedisModuleKey *key, size_t *len, int mode) {
+ /* We need to return *some* pointer for empty keys, we just return
+ * a string literal pointer, that is the advantage to be mapped into
+ * a read only memory page, so the module will segfault if a write
+ * attempt is performed. */
+ char *emptystring = "<dma-empty-string>";
+ if (key->value == NULL) {
+ *len = 0;
+ return emptystring;
+ }
+
+ if (key->value->type != OBJ_STRING) return NULL;
+
+ /* For write access, and even for read access if the object is encoded,
+ * we unshare the string (that has the side effect of decoding it). */
+ if ((mode & REDISMODULE_WRITE) || key->value->encoding != OBJ_ENCODING_RAW)
+ key->value = dbUnshareStringValue(key->db, key->key, key->value);
+
+ *len = sdslen(key->value->ptr);
+ return key->value->ptr;
+}
+
+/* If the string is open for writing and is of string type, resize it, padding
+ * with zero bytes if the new length is greater than the old one.
+ *
+ * After this call, RedisModule_StringDMA() must be called again to continue
+ * DMA access with the new pointer.
+ *
+ * The function returns REDISMODULE_OK on success, and REDISMODULE_ERR on
+ * error, that is, the key is not open for writing, is not a string
+ * or resizing for more than 512 MB is requested.
+ *
+ * If the key is empty, a string key is created with the new string value
+ * unless the new length value requested is zero. */
+int RedisModule_StringTruncate(RedisModuleKey *key, size_t newlen) {
+ if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
+ if (key->value && key->value->type != OBJ_STRING) return REDISMODULE_ERR;
+ if (newlen > 512*1024*1024) return REDISMODULE_ERR;
+
+ /* Empty key and new len set to 0. Just return REDISMODULE_OK without
+ * doing anything. */
+ if (key->value == NULL && newlen == 0) return REDISMODULE_OK;
+
+ /* Empty key: fill it with a zero-length key so that we can handle the
+ * resize with a common code path. */
+ if (key->value == NULL) {
+ robj *emptyobj = createStringObject("",0);
+ setKey(key->db,key->key,emptyobj);
+ key->value = emptyobj;
+ decrRefCount(emptyobj);
+ }
+
+ /* Unshare and resize. */
+ key->value = dbUnshareStringValue(key->db, key->key, key->value);
+ size_t curlen = sdslen(key->value->ptr);
+ if (newlen > curlen) {
+ key->value->ptr = sdsgrowzero(key->value->ptr,newlen);
+ } else if (newlen < curlen) {
+ sdsrange(key->value->ptr,0,newlen-1);
+ /* If the string is too wasteful, reallocate it. */
+ if (sdslen(key->value->ptr) > sdsavail(key->value->ptr))
+ key->value->ptr = sdsRemoveFreeSpace(key->value->ptr);
+ }
+ return REDISMODULE_OK;
+}
+
+/* --------------------------------------------------------------------------
+ * Key API for List type
+ * -------------------------------------------------------------------------- */
+
+/* Push an element into a list, on head or tail depending on 'where' argumnet.
+ * If the key pointer is about an empty key opened for writing, the key
+ * is created. On error (key opened for read-only operations or of the wrong
+ * type) REDISMODULE_ERR is returned, otherwise REDISMODULE_OK is returned. */
+int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele) {
+ if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR;
+ if (key->value == NULL) moduleCreateEmtpyKey(key,REDISMODULE_KEYTYPE_LIST);
+ if (key->value->type != OBJ_LIST) return REDISMODULE_ERR;
+ listTypePush(key->value, ele,
+ (where == REDISMODULE_LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL);
+ signalModifiedKey(key->db,key->key);
+ return REDISMODULE_OK;
+}
+
+/* Pop an element from the list, and returns it as a module string object
+ * that the user should be free with RedisModule_FreeString() or by enabling
+ * automatic memory. 'where' specifies if the element should be popped from
+ * head or tail. The command returns NULL if:
+ * 1) The list is empty.
+ * 2) The key was not open for writing.
+ * 3) The key is not a list. */
+RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where) {
+ if (!(key->mode & REDISMODULE_WRITE) ||
+ key->value == NULL ||
+ key->value->type != OBJ_LIST) return NULL;
+ robj *ele = listTypePop(key->value,
+ (where == REDISMODULE_LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL);
+ robj *decoded = getDecodedObject(ele);
+ decrRefCount(ele);
+ moduleDelKeyIfEmpty(key);
+ RedisModule_AutoMemoryAdd(key->ctx,REDISMODULE_AM_STRING,decoded);
+ return decoded;
+}
+
+/* --------------------------------------------------------------------------
+ * Redis <-> Modules generic Call() API
+ * -------------------------------------------------------------------------- */
+
+/* Create a new RedisModuleCallReply object. The processing of the reply
+ * is lazy, the object is just populated with the raw protocol and later
+ * is processed as needed. Initially we just make sure to set the right
+ * reply type, which is extremely cheap to do. */
+RedisModuleCallReply *moduleCreateCallReplyFromProto(RedisModuleCtx *ctx, sds proto) {
+ RedisModuleCallReply *reply = zmalloc(sizeof(*reply));
+ reply->ctx = ctx;
+ reply->proto = proto;
+ reply->protolen = sdslen(proto);
+ reply->flags = REDISMODULE_REPLYFLAG_TOPARSE; /* Lazy parsing. */
+ switch(proto[0]) {
+ case '$':
+ case '+': reply->type = REDISMODULE_REPLY_STRING;
+ case '-': reply->type = REDISMODULE_REPLY_ERROR;
+ case ':': reply->type = REDISMODULE_REPLY_INTEGER;
+ case '*': reply->type = REDISMODULE_REPLY_ARRAY;
+ default: reply->type = REDISMODULE_REPLY_UNKNOWN;
+ }
+ if ((proto[0] == '*' || proto[0] == '$') && proto[1] == '-')
+ reply->type = REDISMODULE_REPLY_NULL;
+ return reply;
+}
+
+void moduleParseCallReply_Int(RedisModuleCallReply *reply);
+void moduleParseCallReply_BulkString(RedisModuleCallReply *reply);
+void moduleParseCallReply_SimpleString(RedisModuleCallReply *reply);
+void moduleParseCallReply_Array(RedisModuleCallReply *reply);
+
+/* Do nothing if REDISMODULE_REPLYFLAG_TOPARSE is false, otherwise
+ * use the protcol of the reply in reply->proto in order to fill the
+ * reply with parsed data according to the reply type. */
+void moduleParseCallReply(RedisModuleCallReply *reply) {
+ if (!(reply->flags & REDISMODULE_REPLYFLAG_TOPARSE)) return;
+ reply->flags &= ~REDISMODULE_REPLYFLAG_TOPARSE;
+
+ switch(reply->proto[0]) {
+ case ':': moduleParseCallReply_Int(reply); break;
+ case '$': moduleParseCallReply_BulkString(reply); break;
+ case '-': /* handled by next item. */
+ case '+': moduleParseCallReply_SimpleString(reply); break;
+ case '*': moduleParseCallReply_Array(reply); break;
+ }
+}
+
+void moduleParseCallReply_Int(RedisModuleCallReply *reply) {
+ char *proto = reply->proto;
+ char *p = strchr(proto+1,'\r');
+
+ string2ll(proto+1,p-proto-1,&reply->val.ll);
+ reply->protolen = p-proto+2;
+ reply->type = REDISMODULE_REPLY_INTEGER;
+}
+
+void moduleParseCallReply_BulkString(RedisModuleCallReply *reply) {
+ char *proto = reply->proto;
+ char *p = strchr(proto+1,'\r');
+ long long bulklen;
+
+ string2ll(proto+1,p-proto-1,&bulklen);
+ if (bulklen == -1) {
+ reply->protolen = proto-p+2;
+ reply->type = REDISMODULE_REPLY_NULL;
+ } else {
+ reply->val.str = p+2;
+ reply->len = bulklen;
+ reply->protolen = p-proto+2+bulklen+2;
+ reply->type = REDISMODULE_REPLY_STRING;
+ }
+}
+
+void moduleParseCallReply_SimpleString(RedisModuleCallReply *reply) {
+ char *proto = reply->proto;
+ char *p = strchr(proto+1,'\r');
+
+ reply->val.str = proto+1;
+ reply->len = p-proto-1;
+ reply->protolen = proto-p+2;
+ reply->type = proto[0] == '+' ? REDISMODULE_REPLY_STRING :
+ REDISMODULE_REPLY_ERROR;
+}
+
+void moduleParseCallReply_Array(RedisModuleCallReply *reply) {
+ char *proto = reply->proto;
+ char *p = strchr(proto+1,'\r');
+ long long arraylen, j;
+
+ string2ll(proto+1,p-proto-1,&arraylen);
+ p += 2;
+
+ if (arraylen == -1) {
+ reply->protolen = proto-p;
+ reply->type = REDISMODULE_REPLY_NULL;
+ return;
+ }
+
+ reply->val.array = zmalloc(sizeof(RedisModuleCallReply)*arraylen);
+ reply->len = arraylen;
+ for (j = 0; j < arraylen; j++) {
+ RedisModuleCallReply *ele = reply->val.array+j;
+ ele->flags = REDISMODULE_REPLYFLAG_NESTED |
+ REDISMODULE_REPLYFLAG_TOPARSE;
+ ele->proto = p;
+ moduleParseCallReply(ele);
+ p += ele->protolen;
+ }
+ reply->protolen = proto-p;
+ reply->type = REDISMODULE_REPLY_ARRAY;
+}
+
+/* Free a Call reply and all the nested replies it contains if it's an
+ * array. */
+void RedisModule_FreeCallReply_Rec(RedisModuleCallReply *reply, int freenested){
+ /* Don't free nested replies by default: the user must always free the
+ * toplevel reply. However be gentle and don't crash if the module
+ * misuses the API. */
+ if (!freenested && reply->flags & REDISMODULE_REPLYFLAG_NESTED) return;
+
+ if (!(reply->flags & REDISMODULE_REPLYFLAG_TOPARSE)) {
+ if (reply->type == REDISMODULE_REPLY_ARRAY) {
+ size_t j;
+ for (j = 0; j < reply->len; j++)
+ RedisModule_FreeCallReply_Rec(reply->val.array+j,1);
+ zfree(reply->val.array);
+ }
+ }
+
+ /* For nested replies, we don't free reply->proto (which if not NULL
+ * references the parent reply->proto buffer), nor the structure
+ * itself which is allocated as an array of structures, and is freed
+ * when the array value is released. */
+ if (!(reply->flags & REDISMODULE_REPLYFLAG_NESTED)) {
+ if (reply->proto) sdsfree(reply->proto);
+ zfree(reply);
+ }
+}
+
+/* Wrapper for the recursive free reply function. This is needed in order
+ * to have the first level function to return on nested replies, but only
+ * if called by the module API. */
+void RedisModule_FreeCallReply(RedisModuleCallReply *reply) {
+ RedisModule_FreeCallReply_Rec(reply,0);
+ RedisModule_AutoMemoryFreed(reply->ctx,REDISMODULE_AM_REPLY,reply);
+}
+
+/* Return the reply type. */
+int RedisModule_CallReplyType(RedisModuleCallReply *reply) {
+ return reply->type;
+}
+
+/* Return the reply type length, where applicable. */
+size_t RedisModule_CallReplyLength(RedisModuleCallReply *reply) {
+ moduleParseCallReply(reply);
+ switch(reply->type) {
+ case REDISMODULE_REPLY_STRING:
+ case REDISMODULE_REPLY_ERROR:
+ case REDISMODULE_REPLY_ARRAY:
+ return reply->len;
+ default:
+ return 0;
+ }
+}
+
+/* Return the 'idx'-th nested call reply element of an array reply, or NULL
+ * if the reply type is wrong or the index is out of range. */
+RedisModuleCallReply *RedisModule_CallReplyArrayElement(RedisModuleCallReply *reply, size_t idx) {
+ moduleParseCallReply(reply);
+ if (reply->type != REDISMODULE_REPLY_ARRAY) return NULL;
+ if (idx >= reply->len) return NULL;
+ return reply->val.array+idx;
+}
+
+/* Return the long long of an integer reply. */
+long long RedisModule_CallReplyInteger(RedisModuleCallReply *reply) {
+ moduleParseCallReply(reply);
+ if (reply->type != REDISMODULE_REPLY_INTEGER) return LLONG_MIN;
+ return reply->val.ll;
+}
+
+/* Return the pointer and length of a string or error reply. */
+const char *RedisModule_CallReplyStringPtr(RedisModuleCallReply *reply, size_t *len) {
+ moduleParseCallReply(reply);
+ if (reply->type != REDISMODULE_REPLY_STRING &&
+ reply->type != REDISMODULE_REPLY_ERROR) return NULL;
+ if (len) *len = reply->len;
+ return reply->val.str;
+}
+
+/* Return a new string object from a call reply of type string, error or
+ * integer. Otherwise (wrong reply type) return NULL. */
+RedisModuleString *RedisModule_CreateStringFromCallReply(RedisModuleCallReply *reply) {
+ moduleParseCallReply(reply);
+ switch(reply->type) {
+ case REDISMODULE_REPLY_STRING:
+ case REDISMODULE_REPLY_ERROR:
+ return RedisModule_CreateString(reply->ctx,reply->val.str,reply->len);
+ case REDISMODULE_REPLY_INTEGER: {
+ char buf[64];
+ int len = ll2string(buf,sizeof(buf),reply->val.ll);
+ return RedisModule_CreateString(reply->ctx,buf,len);
+ }
+ default: return NULL;
+ }
+}
+
+/* Returns an array of robj pointers, and populates *argc with the number
+ * of items, by parsing the format specifier "fmt" as described for
+ * the RedisModule_Call(), RedisModule_Replicate() and other module APIs.
+ *
+ * The integer pointed by 'flags' is populated with flags according
+ * to special modifiers in "fmt". For now only one exists:
+ *
+ * "!" -> REDISMODULE_ARGV_REPLICATE
+ *
+ * On error (format specifier error) NULL is returned and nothing is
+ * allocated. On success the argument vector is returned. */
+
+#define REDISMODULE_ARGV_REPLICATE (1<<0)
+
+robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
+ int argc = 0, argv_size, j;
+ robj **argv = NULL;
+
+ /* As a first guess to avoid useless reallocations, size argv to
+ * hold one argument for each char specifier in 'fmt'. */
+ argv_size = strlen(fmt)+1; /* +1 because of the command name. */
+ argv = zrealloc(argv,sizeof(robj*)*argv_size);
+
+ /* Build the arguments vector based on the format specifier. */
+ argv[0] = createStringObject(cmdname,strlen(cmdname));
+ argc++;
+
+ /* Create the client and dispatch the command. */
+ const char *p = fmt;
+ while(*p) {
+ if (*p == 'c') {
+ char *cstr = va_arg(ap,char*);
+ argv[argc++] = createStringObject(cstr,strlen(cstr));
+ } else if (*p == 's') {
+ robj *obj = va_arg(ap,void*);
+ argv[argc++] = obj;
+ incrRefCount(obj);
+ } else if (*p == 'b') {
+ char *buf = va_arg(ap,char*);
+ size_t len = va_arg(ap,size_t);
+ argv[argc++] = createStringObject(buf,len);
+ } else if (*p == 'l') {
+ long ll = va_arg(ap,long long);
+ argv[argc++] = createStringObjectFromLongLong(ll);
+ } else if (*p == 'v') {
+ /* TODO: work in progress. */
+ } else if (*p == '!') {
+ if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
+ } else {
+ goto fmterr;
+ }
+ p++;
+ }
+ *argcp = argc;
+ return argv;
+
+fmterr:
+ for (j = 0; j < argc; j++)
+ decrRefCount(argv[j]);
+ zfree(argv);
+ return NULL;
+}
+
+/* Exported API to call any Redis command from modules.
+ * On success a RedisModuleCallReply object is returned, otherwise
+ * NULL is returned and errno is set to the following values:
+ *
+ * EINVAL: command non existing, wrong arity, wrong format specifier.
+ * EPERM: operation in Cluster instance with key in non local slot. */
+RedisModuleCallReply *RedisModule_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
+ struct redisCommand *cmd;
+ client *c = NULL;
+ robj **argv = NULL;
+ int argc = 0, flags = 0;
+ va_list ap;
+ RedisModuleCallReply *reply = NULL;
+ int replicate = 0; /* Replicate this command? */
+
+ cmd = lookupCommandByCString((char*)cmdname);
+ if (!cmd) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Create the client and dispatch the command. */
+ va_start(ap, fmt);
+ c = createClient(-1);
+ argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap);
+ replicate = flags & REDISMODULE_ARGV_REPLICATE;
+ va_end(ap);
+
+ /* Setup our fake client for command execution. */
+ c->flags |= CLIENT_MODULE;
+ c->argv = argv;
+ c->argc = argc;
+ c->cmd = c->lastcmd = cmd;
+ /* We handle the above format error only when the client is setup so that
+ * we can free it normally. */
+ if (argv == NULL) goto cleanup;
+
+ /* Basic arity checks. */
+ if ((cmd->arity > 0 && cmd->arity != argc) || (argc < -cmd->arity)) {
+ errno = EINVAL;
+ goto cleanup;
+ }
+
+ /* If this is a Redis Cluster node, we need to make sure the module is not
+ * trying to access non-local keys, with the exception of commands
+ * received from our master. */
+ if (server.cluster_enabled && !(ctx->client->flags & CLIENT_MASTER)) {
+ /* Duplicate relevant flags in the module client. */
+ c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING);
+ c->flags |= ctx->client->flags & (CLIENT_READONLY|CLIENT_ASKING);
+ if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,NULL) !=
+ server.cluster->myself)
+ {
+ errno = EPERM;
+ goto cleanup;
+ }
+ }
+
+ /* If we are using single commands replication, we need to wrap what
+ * we propagate into a MULTI/EXEC block, so that it will be atomic like
+ * a Lua script in the context of AOF and slaves. */
+ if (replicate) moduleReplicateMultiIfNeeded(ctx);
+
+ /* Run the command */
+ int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
+ if (replicate) {
+ call_flags |= CMD_CALL_PROPAGATE_AOF;
+ call_flags |= CMD_CALL_PROPAGATE_REPL;
+ }
+ call(c,call_flags);
+
+ /* Convert the result of the Redis command into a suitable Lua type.
+ * The first thing we need is to create a single string from the client
+ * output buffers. */
+ sds proto = sdsnewlen(c->buf,c->bufpos);
+ c->bufpos = 0;
+ while(listLength(c->reply)) {
+ sds o = listNodeValue(listFirst(c->reply));
+
+ proto = sdscatsds(proto,o);
+ listDelNode(c->reply,listFirst(c->reply));
+ }
+ reply = moduleCreateCallReplyFromProto(ctx,proto);
+ RedisModule_AutoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply);
+
+cleanup:
+ freeClient(c);
+ return reply;
+}
+
+/* Return a pointer, and a length, to the protocol returned by the command
+ * that returned the reply object. */
+const char *RedisModule_CallReplyProto(RedisModuleCallReply *reply, size_t *len) {
+ if (reply->proto) *len = sdslen(reply->proto);
+ return reply->proto;
+}
+
+/* --------------------------------------------------------------------------
+ * Modules API internals
+ * -------------------------------------------------------------------------- */
+
+/* server.moduleapi dictionary type. Only uses plain C strings since
+ * this gets queries from modules. */
+
+unsigned int dictCStringKeyHash(const void *key) {
+ return dictGenHashFunction((unsigned char*)key, strlen((char*)key));
+}
+
+int dictCStringKeyCompare(void *privdata, const void *key1, const void *key2) {
+ DICT_NOTUSED(privdata);
+ return strcmp(key1,key2) == 0;
+}
+
+dictType moduleAPIDictType = {
+ dictCStringKeyHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictCStringKeyCompare, /* key compare */
+ NULL, /* key destructor */
+ NULL /* val destructor */
+};
+
+int moduleRegisterApi(const char *funcname, void *funcptr) {
+ return dictAdd(server.moduleapi, (char*)funcname, funcptr);
+}
+
+#define REGISTER_API(name) \
+ moduleRegisterApi("RedisModule_" #name, (void *)RedisModule_ ## name)
+
+/* Register all the APIs we export. */
+void moduleRegisterCoreAPI(void) {
+ server.moduleapi = dictCreate(&moduleAPIDictType,NULL);
+ REGISTER_API(CreateCommand);
+ REGISTER_API(SetModuleAttribs);
+ REGISTER_API(WrongArity);
+ REGISTER_API(ReplyWithLongLong);
+ REGISTER_API(ReplyWithError);
+ REGISTER_API(ReplyWithSimpleString);
+ REGISTER_API(ReplyWithArray);
+ REGISTER_API(ReplyWithString);
+ REGISTER_API(ReplyWithStringBuffer);
+ REGISTER_API(GetSelectedDb);
+ REGISTER_API(SelectDb);
+ REGISTER_API(OpenKey);
+ REGISTER_API(CloseKey);
+ REGISTER_API(KeyType);
+ REGISTER_API(ValueLength);
+ REGISTER_API(ListPush);
+ REGISTER_API(ListPop);
+ REGISTER_API(StringToLongLong);
+ REGISTER_API(Call);
+ REGISTER_API(CallReplyProto);
+ REGISTER_API(FreeCallReply);
+ REGISTER_API(CallReplyInteger);
+ REGISTER_API(CallReplyType);
+ REGISTER_API(CallReplyLength);
+ REGISTER_API(CallReplyArrayElement);
+ REGISTER_API(CallReplyStringPtr);
+ REGISTER_API(CreateStringFromCallReply);
+ REGISTER_API(CreateString);
+ REGISTER_API(CreateStringFromLongLong);
+ REGISTER_API(FreeString);
+ REGISTER_API(StringPtrLen);
+ REGISTER_API(AutoMemory);
+ REGISTER_API(Replicate);
+ REGISTER_API(ReplicateVerbatim);
+ REGISTER_API(DeleteKey);
+ REGISTER_API(StringSet);
+ REGISTER_API(StringDMA);
+ REGISTER_API(StringTruncate);
+}
+
+/* Global initialization at Redis startup. */
+void moduleInitModulesSystem(void) {
+ server.loadmodule_queue = listCreate();
+ modules = dictCreate(&modulesDictType,NULL);
+ moduleRegisterCoreAPI();
+}
+
+/* Load all the modules in the server.loadmodule_queue list, which is
+ * populated by `loadmodule` directives in the configuration file.
+ * We can't load modules directly when processing the configuration file
+ * because the server must be fully initialized before loading modules.
+ *
+ * The function aborts the server on errors, since to start with missing
+ * modules is not considered sane: clients may rely on the existance of
+ * given commands, loading AOF also may need some modules to exist, and
+ * if this instance is a slave, it must understand commands from master. */
+void moduleLoadFromQueue(void) {
+ listIter li;
+ listNode *ln;
+
+ listRewind(server.loadmodule_queue,&li);
+ while((ln = listNext(&li))) {
+ sds modulepath = ln->value;
+ if (moduleLoad(modulepath) == C_ERR) {
+ serverLog(LL_WARNING,
+ "Can't load module from %s: server aborting",
+ modulepath);
+ exit(1);
+ }
+ }
+}
+
+void moduleFreeModuleStructure(struct RedisModule *module) {
+ sdsfree(module->name);
+ zfree(module);
+}
+
+/* Load a module and initialize it. On success C_OK is returned, otherwise
+ * C_ERR is returned. */
+int moduleLoad(const char *path) {
+ int (*onload)(void *);
+ void *handle;
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+
+ handle = dlopen(path,RTLD_LAZY);
+ if (handle == NULL) return C_ERR;
+ onload = (int (*)(void *)) dlsym(handle,"RedisModule_OnLoad");
+ if (onload == NULL) {
+ serverLog(LL_WARNING,
+ "Module %s does not export RedisModule_OnLoad() "
+ "symbol. Module not loaded.",path);
+ return C_ERR;
+ }
+ if (onload((void*)&ctx) == REDISMODULE_ERR) {
+ if (ctx.module) moduleFreeModuleStructure(ctx.module);
+ dlclose(handle);
+ serverLog(LL_WARNING,
+ "Module %s initialization failed. Module not loaded",path);
+ return C_ERR;
+ }
+
+ /* Redis module loaded! Register it. */
+ dictAdd(modules,ctx.module->name,ctx.module);
+ ctx.module->handle = handle;
+ serverLog(LL_NOTICE,"Module '%s' loaded from %s",ctx.module->name,path);
+ return C_OK;
+}
+
+/* Unload the module registered with the specified name. On success
+ * C_OK is returned, otherwise C_ERR is returned and errno is set
+ * to the following values depending on the type of error:
+ *
+ * ENONET: No such module having the specified name. */
+int moduleUnload(sds name) {
+ struct RedisModule *module = dictFetchValue(modules,name);
+ if (module == NULL) {
+ errno = ENOENT;
+ return REDISMODULE_ERR;
+ }
+
+ /* Unregister all the commands registered by this module. */
+ dictIterator *di = dictGetSafeIterator(server.commands);
+ dictEntry *de;
+ while ((de = dictNext(di)) != NULL) {
+ struct redisCommand *cmd = dictGetVal(de);
+ if (cmd->proc == RedisModuleCommandDispatcher) {
+ RedisModuleCommandProxy *cp = (void*)cmd->getkeys_proc;
+ sds cmdname = cp->rediscmd->name;
+ if (cp->module == module) {
+ dictDelete(server.commands,cmdname);
+ dictDelete(server.orig_commands,cmdname);
+ sdsfree(cmdname);
+ zfree(cp->rediscmd);
+ zfree(cp);
+ }
+ }
+ }
+ dictReleaseIterator(di);
+
+ /* Unregister all the hooks. TODO: Yet no hooks support here. */
+
+ /* Unload the dynamic library. */
+ if (dlclose(module->handle) == -1) {
+ char *error = dlerror();
+ if (error == NULL) error = "Unknown error";
+ serverLog(LL_WARNING,"Error when trying to close the %s module: %s",
+ module->name, error);
+ }
+
+ /* Remove from list of modules. */
+ serverLog(LL_NOTICE,"Module %s unloaded",module->name);
+ dictDelete(modules,module->name);
+
+ /* Free the module structure. */
+ zfree(module);
+
+ return REDISMODULE_OK;
+}
+
+/* Redis MODULE command.
+ *
+ * MODULE LOAD <path> */
+void moduleCommand(client *c) {
+ char *subcmd = c->argv[1]->ptr;
+
+ if (!strcasecmp(subcmd,"load") && c->argc == 3) {
+ if (moduleLoad(c->argv[2]->ptr) == C_OK)
+ addReply(c,shared.ok);
+ else
+ addReplyError(c,
+ "Error loading the extension. Please check the server logs.");
+ } else if (!strcasecmp(subcmd,"unload") && c->argc == 3) {
+ if (moduleUnload(c->argv[2]->ptr) == C_OK)
+ addReply(c,shared.ok);
+ else {
+ char *errmsg = "operation not possible.";
+ switch(errno) {
+ case ENOENT: errmsg = "no such module with that name";
+ }
+ addReplyErrorFormat(c,"Error unloading module: %s",errmsg);
+ }
+ } else if (!strcasecmp(subcmd,"list") && c->argc == 2) {
+ dictIterator *di = dictGetIterator(modules);
+ dictEntry *de;
+
+ addReplyMultiBulkLen(c,dictSize(modules));
+ while ((de = dictNext(di)) != NULL) {
+ sds name = dictGetKey(de);
+ struct RedisModule *module = dictGetVal(de);
+ addReplyMultiBulkLen(c,4);
+ addReplyBulkCString(c,"name");
+ addReplyBulkCBuffer(c,name,sdslen(name));
+ addReplyBulkCString(c,"ver");
+ addReplyLongLong(c,module->ver);
+ }
+ dictReleaseIterator(di);
+ } else {
+ addReply(c,shared.syntaxerr);
+ }
+}
diff --git a/src/modules/.gitignore b/src/modules/.gitignore
new file mode 100644
index 000000000..4de1735ec
--- /dev/null
+++ b/src/modules/.gitignore
@@ -0,0 +1,2 @@
+*.so
+*.xo
diff --git a/src/modules/API.md b/src/modules/API.md
new file mode 100644
index 000000000..bc183251d
--- /dev/null
+++ b/src/modules/API.md
@@ -0,0 +1,695 @@
+Redis Modules API reference manual
+===
+
+Redis modules make possible to extend Redis functionality using external
+modules, implementing new Redis commands at a speed and with features
+similar to what can be done inside the core itself.
+
+Redis modules are dynamic libraries, that can be loaded into Redis at
+startup or using the `MODULE LOAD` command. Redis exports a C API, in the
+form of a single C header file called `redismodule.h`. Modules are meant
+to be written in C, however it will be possible to use C++ or other languages
+that have C binding functionalities.
+
+Modules are designed in order to be loaded into different versions of Redis,
+so a given module does not need to be designed, or recompiled, in order to
+run with a specific version of Redis. For this reason, the module will
+register to the Redis core using a specific API version. The current API
+version is "1".
+
+This document is about an alpha version of Redis modules. API, functionalities
+and other details may change in the future.
+
+# Loading modules
+
+In order to test the module you are developing, you can load the module
+using the following `redis.conf` configuration directive:
+
+ loadmodule /path/to/mymodule.so
+
+It is also possible to load a module at runtime using the following command:
+
+ MODULE LOAD /path/to/mymodule.so
+
+In order to list all loaded modules, use:
+
+ MODULE LIST
+
+Finally, you can unload (and later reload if you wish) a module using the
+following command:
+
+ MODULE UNLOAD mymodule
+
+Note that `mymodule` above is not the filename without the `.so` suffix, but
+instead, the name the module used to register itself into the Redis core.
+The name can be obtained using `MODULE LIST`. However it is good practice
+that the filename of the dynamic library is the same as the name the module
+uses to register itself into the Redis core.
+
+# The simplest module you can write
+
+In order to show the different parts of a module, here we'll show a very
+simple module that implements a command that outputs a random number.
+
+ #include "redismodule.h"
+ #include <stdlib.h>
+
+ int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ RedisModule_ReplyWithLongLong(ctx,rand());
+ return REDISMODULE_OK;
+ }
+
+ int RedisModule_OnLoad(RedisModuleCtx *ctx) {
+ if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"helloworld.rand",
+ HelloworldRand_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+ }
+
+The example module has two functions. One implements a command called
+HELLOWORLD.RAND. This function is specific of that module. However the
+other function called `RedisModule_OnLoad()` must be present in each
+Redis module. It is the entry point for the module to be initialized,
+register its commands, and potentially other private data structures
+it uses.
+
+Note that it is a good idea for modules to call commands with the
+name of the module followed by a dot, and finally the command name,
+like in the case of `HELLOWORLD.RAND`. This way it is less likely to
+have collisions.
+
+Note that if different modules have colliding commands, they'll not be
+able to work in Redis at the same time, since the function
+`RedisModule_CreateCommand` will fail in one of the modules, so the module
+loading will abort returning an error condition.
+
+# Module initialization
+
+The above example shows the usage of the function `RedisModule_Init()`.
+It should be the first function called by the module `OnLoad` function.
+The following is the function prototype:
+
+ int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename,
+ int module_version, int api_version);
+
+The `Init` function announces the Redis core that the module has a given
+name, its version (that is reported by `MODULE LIST`), and that is willing
+to use a specific version of the API.
+
+If the API version is wrong, the name is already taken, or there are other
+similar errors, the function will return `REDISMODULE_ERR`, and the module
+`OnLoad` function should return ASAP with an error.
+
+Before the `Init` function is called, no other API function can be called,
+otherwise the module will segfault and the Redis instance will crash.
+
+The second function called, `RedisModule_CreateCommand`, is used in order
+to register commands into the Redis core. The following is the prototype:
+
+ int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *cmdname,
+ RedisModuleCmdFunc cmdfunc);
+
+As you can see, most Redis modules API calls all take as first argument
+the `context` of the module, so that they have a reference to the module
+calling it, to the command and client executing a given command, and so forth.
+
+To create a new command, the above function needs the context, the command
+name, and the function pointer of the function implementing the command,
+which must have the following prototype:
+
+
+ int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
+
+The command function arguments are just the context, that will be passed
+to all the other API calls, the command argument vector, and total number
+of arguments, as passed by the user.
+
+As you can see, the arguments are provided as pointers to a specific data
+type, the `RedisModuleString`. This is an opaque data type you have API
+functions to access and use, direct access to its fields is never needed.
+
+Zooming into the example command implementation, we can find another call:
+
+ int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long integer);
+
+This function returns an integer to the client that invoked the command,
+exactly like other Redis commands do, like for example `INCR` or `SCARD`.
+
+# Setup and dependencies of a Redis module
+
+Redis modules don't depend on Redis or some other library, nor they
+need to be compiled with a specific `redismodule.h` file. In order
+to create a new module, just copy a recent version of `redismodule.h`
+in your source tree, link all the libraries you want, and create
+a dynamic library having the `RedisModule_OnLoad()` function symbol
+exported.
+
+The module will be able to load into different versions of Redis.
+
+# Working with RedisModuleString objects
+
+The command argument vector `argv` passed to module commands, and the
+return value of other module APIs functions, are of type `RedisModuleString`.
+
+Usually you directly pass module strings to other API calls, however sometimes
+you may need to directly access the string object.
+
+There are a few functions in order to work with string objects:
+
+ const char *RedisModule_StringPtr(RedisModuleString *string, size_t *len);
+
+The above function accesses a string by returning its pointer and length.
+You should never write to a string object pointer, as you can see from the
+`const` pointer qualifier.
+
+However, if you want, you can create new string objects using the following
+API:
+
+ RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len);
+
+The string returned by the above command must be freed using a corresponding
+call to `RedisModule_FreeString()`:
+
+ void RedisModule_FreeString(RedisModuleString *str);
+
+However if you want to avoid having to free strings, the automatic memory
+management, covered later in this document, can be a good alternative, by
+doing it for you.
+
+Note that the strings provided via the argument vector `argv` never need
+to be freed. You only need to free new strings you create, or new strings
+returned by other APIs, where it is specified that the returned string must
+be freed.
+
+## Creating strings from numbers or parsing strings as numbers
+
+Creating a new string from an integer is a very common operation, so there
+is a function to do this:
+
+ RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx,10);
+
+Similarly in order to parse a string as a number:
+
+ long long myval;
+ if (RedisModule_StringToLongLong(ctx,argv[1],&myval) == REDISMODULE_OK) {
+ /* Do something with 'myval' */
+ }
+
+## Accessing Redis keys from modules
+
+Most Redis modules, in order to be useful, have to interact with the Redis
+data space (this is not always true, for example an ID generator may
+never touch Redis keys). Redis modules have two different APIs in order to
+access the Redis data space, one is a low level API that provides very
+fast access and a set of functions to manipulate Redis data structures.
+The other API is more high level, and allows to call Redis commands and
+fetch the result, similarly to how Lua scripts access Redis.
+
+The high level API is also useful in order to access Redis functionalities
+that are not available as APIs.
+
+In general modules developers should prefer the low level API, because commands
+implemented using the low level API run at a speed comparable to the speed
+of native Redis commands. However there are definitely use cases for the
+higher level API. For example often the bottleneck could be processing the
+data and not accessing it.
+
+Also note that sometimes using the low level API is not harder compared to
+the higher level one.
+
+# Calling Redis commands
+
+The high level API to access Redis is the sum of the `RedisModule_Call()`
+function, together with the functions needed in order to access the
+reply object returned by `Call()`.
+
+`RedisModule_Call` uses a special calling convention, with a format specifier
+that is used to specify what kind of objects you are passing as arguments
+to the function.
+
+Redis commands are invoked just using a command name and a list of arguments.
+However when calling commands, the arguments may originate from different
+kind of strings: null-terminated C strings, RedisModuleString objects as
+received from the `argv` parameter in the command implementation, binary
+safe C buffers with a pointer and a length, and so forth.
+
+For example if I want to call `INCRBY` using a first argument (the key)
+a string received in the argument vector `argv`, which is an array
+of RedisModuleString object pointers, and a C string representing the
+number "10" as second argument (the increment), I'll use the following
+function call:
+
+ RedisModuleCallReply *reply;
+ reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
+
+The first argument is the context, and the second is always a null terminated
+C string with the command name. The third argument is the format specifier
+where each character corresponds to the type of the arguments that will follow.
+In the above case `"sc"` means a RedisModuleString object, and a null
+terminated C string. The other arguments are just the two arguments as
+specified. In fact `argv[1]` is a RedisModuleString and `"10"` is a null
+terminated C string.
+
+This is the full list of format specifiers:
+
+* **c** -- Null terminated C string pointer.
+* **b** -- C buffer, two arguments needed: C string pointer and `size_t` length.
+* **s** -- RedisModuleString as received in `argv` or by other Redis module APIs returning a RedisModuleString object.
+* **l** -- Long long integer.
+* **v** -- NOT YET IMPLEMENTED: Array of RedisModuleString objects.
+* **!** -- This modifier just tells the function to replicate the command to slaves and AOF. It is ignored from the point of view of arguments parsing.
+
+The function returns a `RedisModuleCallReply` object on success, on
+error NULL is returned.
+
+NULL is returned when the command name is invalid, the format specifier uses
+characters that are not recognized, or when the command is called with the
+wrong number of arguments. In the above cases the `errno` var is set to `EINVAL`. NULL is also returned when, in an instance with Cluster enabled, the target
+keys are about non local hash slots. In this case `errno` is set to `EPERM`.
+
+## Working with RedisModuleCallReply objects.
+
+`RedisModuleCall` returns reply objects that can be accessed using the
+`RedisModule_CallReply*` family of functions.
+
+In order to obtain the type or reply (corresponding to one of the data types
+supported by the Redis protocol), the function `RedisModule_CallReplyType()`
+is used:
+
+ reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
+ if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
+ long long myval = RedisModule_CallReplyInteger(reply);
+ /* Do something with myval. */
+ }
+
+Valid reply types are:
+
+* `REDISMODULE_REPLY_STRING` Bulk string or status replies.
+* `REDISMODULE_REPLY_ERROR` Errors.
+* `REDISMODULE_REPLY_INTEGER` Signed 64 bit integers.
+* `REDISMODULE_REPLY_ARRAY` Array of replies.
+* `REDISMODULE_REPLY_NULL` NULL reply.
+
+Strings, errors and arrays have an associated length. For strings and errors
+the length corresponds to the length of the string. For arrays the length
+is the number of elements. To obtain the reply length the following function
+is used:
+
+ size_t reply_len = RedisModule_CallReplyLength(reply);
+
+In order to obtain the value of an integer reply, the following function is used, as already shown in the example above:
+
+ long long reply_integer_val = RedisModule_CallReplyInteger(reply);
+
+Called with a reply object of the wrong type, the above function always
+returns `LLONG_MIN`.
+
+Sub elements of array replies are accessed this way:
+
+ RedisModuleCallReply *subreply;
+ subreply = RedisModule_CallReplyArrayElement(reply,idx);
+
+The above function returns NULL if you try to access out of range elements.
+
+Strings and errors (which are like strings but with a different type) can
+be accessed using in the following way, making sure to never write to
+the resulting pointer (that is returned as as `const` pointer so that
+misusing must be pretty explicit):
+
+ size_t len;
+ char *ptr = RedisModule_CallReplyStringPtr(reply,&len);
+
+If the reply type is not a string or an error, NULL is returned.
+
+RedisCallReply objects are not the same as module string objects
+(RedisModuleString types). However sometimes you may need to pass replies
+of type string or integer, to API functions expecting a module string.
+
+When this is the case, you may want to evaluate if using the low level
+API could be a simpler way to implement your command, or you can use
+the following function in order to create a new string object from a
+call reply of type string, error or integer:
+
+ RedisModuleString *mystr = RedisModule_CreateStringFromCallReply(myreply);
+
+If the reply is not of the right type, NULL is returned.
+The returned string object should be released with `RedisModule_FreeString()`
+as usually, or by enabling automatic memory management (see corresponding
+section).
+
+# Releasing call reply objects
+
+Reply objects must be freed using `RedisModule_FreeCallRelpy`. For arrays,
+you need to free only the top level reply, not the nested replies.
+Currently the module implementation provides a protection in order to avoid
+crashing if you free a nested reply object for error, however this feature
+is not guaranteed to be here forever, so should not be considered part
+of the API.
+
+If you use automatic memory management (explained later in this document)
+you don't need to free replies (but you still could if you wish to release
+memory ASAP).
+
+## Returning values from Redis commands
+
+Like normal Redis commands, new commands implemented via modules must be
+able to return values to the caller. The API exports a set of functions for
+this goal, in order to return the usual types of the Redis protocol, and
+arrays of such types as elemented. Also errors can be returned with any
+error string and code (the error code is the initial uppercase letters in
+the error message, like the "BUSY" string in the "BUSY the sever is busy" error
+message).
+
+All the functions to send a reply to the client are called
+`RedisModule_ReplyWith<something>`.
+
+To return an error, use:
+
+ RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err);
+
+There is a predefined error string for key of wrong type errors:
+
+ REDISMODULE_ERRORMSG_WRONGTYPE
+
+Example usage:
+
+ RedisModule_ReplyWithError(ctx,"ERR invalid arguments");
+
+We already saw how to reply with a long long in the examples above:
+
+ RedisModule_ReplyWithLongLong(ctx,12345);
+
+To reply with a simple string, that can't contain binary values or newlines,
+(so it's suitable to send small words, like "OK") we use:
+
+ RedisModule_ReplyWithSimpleString(ctx,"OK");
+
+It's possible to reply with "bulk strings" that are binary safe, using
+two different functions:
+
+ int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len);
+
+ int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str);
+
+The first function gets a C pointer and length. The second a RedisMoudleString
+object. Use one or the other depending on the source type you have at hand.
+
+In order to reply with an array, you just need to use a function to emit the
+array length, followed by as many calls to the above functions as the number
+of elements of the array are:
+
+ RedisModule_ReplyWithArray(ctx,2);
+ RedisModule_ReplyWithStringBuffer(ctx,"age",3);
+ RedisModule_ReplyWithLongLong(ctx,22);
+
+To return nested arrays is easy, your nested array element just uses another
+call to `RedisModule_ReplyWithArray()` followed by the calls to emit the
+sub array elements.
+
+# Arity and type checks
+
+Often commands need to check that the number of arguments and type of the key
+is correct. In order to report a wrong arity, there is a specific function
+called `RedisModule_WrongArity()`. The usage is trivial:
+
+ if (argc != 2) return RedisModule_WrongArity(ctx);
+
+Checking for the wrong type involves opening the key and checking the type:
+
+ 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);
+ }
+
+Note that you often want to proceed with a command both if the key
+is of the expected type, or if it's empty.
+
+## Low level access to keys
+
+Low level access to keys allow to perform operations on value objects associated
+to keys directly, with a speed similar to what Redis uses internally to
+implement the built-in commands.
+
+Once a key is opened, a key pointer is returned that will be used with all the
+other low level API calls in order to perform operations on the key or its
+associated value.
+
+Because the API is meant to be very fast, it cannot do too many run-time
+checks, so the user must be aware of certain rules to follow:
+
+* Opening the same key multiple times where at least one instance is opened for writing, is undefined and may lead to crashes.
+* While a key is open, it should only be accessed via the low level key API. For example opening a key, then calling DEL on the same key using the `RedisModule_Call()` API will result into a crash. However it is safe to open a key, perform some operation with the low level API, closing it, then using other APIs to manage the same key, and later opening it again to do some more work.
+
+In order to open a key the `RedisModule_OpenKey` function is used. It returns
+a key pointer, that we'll use with all the next calls to access and modify
+the value:
+
+ RedisModuleKey *key;
+ key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);
+
+The second argument is the key name, that must be a `RedisModuleString` object.
+The third argument is the mode: `REDISMODULE_READ` or `REDISMODULE_WRITE`.
+It is possible to use `|` to bitwise OR the two modes to open the key in
+both modes. Currently a key opened for writing can also be accessed for reading
+but this is to be considered an implementation detail. The right mode should
+be used in sane modules.
+
+You can open non exisitng keys for writing, since the keys will be created
+when an attempt to write to the key is performed. However when opening keys
+just for reading, `RedisModule_OpenKey` will return NULL if the key does not
+exist.
+
+Once you are done using a key, you can close it with:
+
+ RedisModule_CloseKey(key);
+
+Note that if automatic memory management is enabled, you are not forced to
+close keys. When the module function returns, Redis will take care to close
+all the keys which are still open.
+
+## Getting the key type
+
+In order to obtain the value of a key, use the `RedisModule_KeyType()` function:
+
+ int keytype = RedisModule_KeyType(key);
+
+It returns one of the following values:
+
+ REDISMODULE_KEYTYPE_EMPTY
+ REDISMODULE_KEYTYPE_STRING
+ REDISMODULE_KEYTYPE_LIST
+ REDISMODULE_KEYTYPE_HASH
+ REDISMODULE_KEYTYPE_SET
+ REDISMODULE_KEYTYPE_ZSET
+
+The above are just the usual Redis key types, with the addition of an empty
+type, that signals the key pointer is associated with an empty key that
+does not yet exists.
+
+## Creating new keys
+
+To create a new key, open it for writing and then write to it using one
+of the key writing functions. Example:
+
+ RedisModuleKey *key;
+ key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);
+ if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
+ RedisModule_StringSet(key,argv[2]);
+ }
+
+## Deleting keys
+
+Just use:
+
+ RedisModule_DeleteKey(key);
+
+The function returns `REDISMODULE_ERR` if the key is not open for writing.
+Note that after a key gets deleted, it is setup in order to be targeted
+by new key commands. For example `RedisModule_KeyType()` will return it is
+an empty key, and writing to it will create a new key, possibly of another
+type (depending on the API used).
+
+## Obtaining the length of values
+
+There is a single function in order to retrieve the length of the value
+associated to an open key. The returned length is value-specific, and is
+the string length for strings, and the number of elements for the aggregated
+data types (how many elements there is in a list, set, sorted set, hash).
+
+ size_t len = RedisModule_ValueLength(key);
+
+If the key does not exist, 0 is returned by the function:
+
+## String type API
+
+Setting a new string value, like the Redis `SET` command does, is performed
+using:
+
+ int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str);
+
+The function works exactly like the Redis `SET` command itself, that is, if
+there is a prior value (of any type) it will be deleted.
+
+Accessing existing string values is performed using DMA (direct memory
+access) for speed. The API will return a pointer and a length, so that's
+possible to access and, if needed, modify the string directly.
+
+ size_t len, j;
+ char *myptr = RedisModule_StringDMA(key,REDISMODULE_WRITE,&len);
+ for (j = 0; j < len; j++) myptr[j] = 'A';
+
+In the above example we write directly on the string. Note that if you want
+to write, you must be sure to ask for `WRITE` mode.
+
+DMA pointers are only valid if no other operations are performed with the key
+before using the pointer, after the DMA call.
+
+Sometimes when we want to manipulate strings directly, we need to change
+their size as well. For this scope, the `RedisModule_StringTruncate` function
+is used. Example:
+
+ RedisModule_StringTruncate(mykey,1024);
+
+The function truncates, or enlarges the string as needed, padding it with
+zero bytes if the previos length is smaller than the new length we request.
+If the string does not exist since `key` is associated to an open empty key,
+a string value is created and associated to the key.
+
+Note that every time `StringTruncate()` is called, we need to re-obtain
+the DMA pointer again, since the old may be invalid.
+
+## List type API
+
+It's possible to push and pop values from list values:
+
+ int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele);
+ RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where);
+
+In both the APIs the `where` argument specifies if to push or pop from tail
+or head, using the following macros:
+
+ REDISMODULE_LIST_HEAD
+ REDISMODULE_LIST_TAIL
+
+Elements returned by `RedisModule_ListPop()` are like strings craeted with
+`RedisModule_CreateString()`, they must be released with
+`RedisModule_FreeString()` or by enabling automatic memory management.
+
+## Set type API
+
+Work in progress.
+
+## Sorted set type API
+
+Work in progress.
+
+## Hash type API
+
+Work in progress.
+
+## Iterating aggregated values
+
+Work in progress.
+
+## Accessing keys TTL and setting expires
+
+Work in progress.
+
+# Replicating commands
+
+If you want to use module commands exactly like normal Redis commands, in the
+context of replicated Redis instances, or using the AOF file for persistence,
+it is important for module commands to handle their replication in a consistent
+way.
+
+When using the higher level APIs to invoke commands, replication happens
+automatically if you use the "!" modifier in the format string of
+`RedisModule_Call()` as in the following example:
+
+ reply = RedisModule_Call(ctx,"INCR","!sc",argv[1],"10");
+
+As you can see the format specifier is `"!sc"`. The bang is not parsed as a
+format specifier, but it internally flags the command as "must replicate".
+
+If you use the above programming style, there are no problems.
+However sometimes things are more complex than that, and you use the low level
+API. In this case, if there are no side effects in the command execution, and
+it consistently always performs the same work, what is possible to do is to
+replicate the command verbatim as the user executed it. To do that, you just
+need to call the following function:
+
+ RedisModule_ReplicateVerbatim(ctx);
+
+When you use the above API, you should not use any other replication function
+since they are not guaranteed to mix well.
+
+However this is not the only option. It's also possible to exactly tell
+Redis what commands to replicate as the effect of the command execution, using
+an API similar to `RedisModule_Call()` but that instead of calling the command
+sends it to the AOF / slaves stream. Example:
+
+ RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment);
+
+It's possible to call `RedisModule_Replicate` multiple times, and each
+will emit a command. All the sequence emitted is wrapped between a
+`MULTI/EXEC` transaction, so that the AOF and replication effects are the
+same as executing a single command.
+
+Note that `Call()` replication and `Replicate()` replication have a rule,
+in case you want to mix both forms of replication (not necessarily a good
+idea if there are simpler approaches). Commands replicated with `Call()`
+are always the first emitted in the final `MULTI/EXEC` block, while all
+the commands emitted with `Replicate()` will follow.
+
+# Automatic memory management
+
+Normally when writing programs in the C language, programmers need to manage
+memory manually. This is why the Redis modules API has functions to release
+strings, close open keys, free replies, and so forth.
+
+However given that commands are executed in a contained environment and
+with a set of strict APIs, Redis is able to provide automatic memory management
+to modules, at the cost of some performance (most of the time, a very low
+cost).
+
+When automatic memory management is enabled:
+
+1. You don't need to close open keys.
+2. You don't need to free replies.
+3. You don't need to free RedisModuleString objects.
+
+However you can still do it, if you want. For example, automatic memory
+management may be active, but inside a loop allocating a lot of strings,
+you may still want to free strings no longer used.
+
+In order to enable automatic memory management, just call the following
+function at the start of the command implementation:
+
+ RedisModule_AutoMemory(ctx);
+
+Automatic memory management is usually the way to go, however experienced
+C programmers may not use it in order to gain some speed and memory usage
+benefit.
+
+# Writing commands compatible with Redis Cluster
+
+Work in progress. Implement and document the following API:
+
+ RedisModule_IsKeysPositionRequest(ctx);
+ RedisModule_KeyAtPos(ctx,pos);
+ RedisModule_KeyAtRange(ctx,start,stop,step);
+
+Command implementations, on keys position request, must reply with
+`REDISMODULE_KEYPOS_OK` to signal the request was processed, otherwise
+Cluster returns an error for those module commands that are not able to
+describe the position of keys.
diff --git a/src/modules/Makefile b/src/modules/Makefile
new file mode 100644
index 000000000..0149c4849
--- /dev/null
+++ b/src/modules/Makefile
@@ -0,0 +1,17 @@
+SHOBJ_CFLAGS ?= -dynamic -fno-common -g -ggdb
+SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
+
+.SUFFIXES: .c .so .xo .o
+
+all: helloworld.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
+
+clean:
+ rm -rf *.xo *.so
diff --git a/src/modules/helloworld.c b/src/modules/helloworld.c
new file mode 100644
index 000000000..8598de895
--- /dev/null
+++ b/src/modules/helloworld.c
@@ -0,0 +1,334 @@
+#include "../redismodule.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.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_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.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) {
+ 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)
+ 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)
+{
+ RedisModuleCallReply *reply;
+ 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. */
+ reply = RedisModule_Call(ctx,"INCR","c!","foo");
+ reply = 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;
+}
+
+/* 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) {
+ if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.simple",
+ HelloSimple_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.push.native",
+ HelloPushNative_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.push.call",
+ HelloPushCall_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.list.sum.len",
+ HelloListSumLen_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.list.splice",
+ HelloListSplice_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.list.splice.auto",
+ HelloListSpliceAuto_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.rand.array",
+ HelloRandArray_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.repl1",
+ HelloRepl1_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.repl2",
+ HelloRepl2_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hello.toggle.case",
+ HelloToggleCase_RedisCommand) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}
diff --git a/src/networking.c b/src/networking.c
index 1e2d75eac..d50d2c852 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -158,7 +158,7 @@ client *createClient(int fd) {
int prepareClientToWrite(client *c) {
/* If it's the Lua client we always return ok without installing any
* handler since there is no socket at all. */
- if (c->flags & CLIENT_LUA) return C_OK;
+ if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
/* CLIENT REPLY OFF / SKIP handling: don't send replies. */
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
diff --git a/src/redismodule.h b/src/redismodule.h
new file mode 100644
index 000000000..93b03e7d3
--- /dev/null
+++ b/src/redismodule.h
@@ -0,0 +1,152 @@
+#ifndef REDISMODULE_H
+#define REDISMODULE_H
+
+#include <sys/types.h>
+
+/* ---------------- Defines common between core and modules --------------- */
+
+/* Error status return values. */
+#define REDISMODULE_OK 0
+#define REDISMODULE_ERR 1
+
+/* API versions. */
+#define REDISMODULE_APIVER_1 1
+
+/* API flags and constants */
+#define REDISMODULE_READ (1<<0)
+#define REDISMODULE_WRITE (1<<1)
+
+#define REDISMODULE_LIST_HEAD 0
+#define REDISMODULE_LIST_TAIL 1
+
+/* Key types. */
+#define REDISMODULE_KEYTYPE_EMPTY 0
+#define REDISMODULE_KEYTYPE_STRING 1
+#define REDISMODULE_KEYTYPE_LIST 2
+#define REDISMODULE_KEYTYPE_HASH 3
+#define REDISMODULE_KEYTYPE_SET 4
+#define REDISMODULE_KEYTYPE_ZSET 5
+
+/* Reply types. */
+#define REDISMODULE_REPLY_UNKNOWN -1
+#define REDISMODULE_REPLY_STRING 0
+#define REDISMODULE_REPLY_ERROR 1
+#define REDISMODULE_REPLY_INTEGER 2
+#define REDISMODULE_REPLY_ARRAY 3
+#define REDISMODULE_REPLY_NULL 4
+
+/* Error messages. */
+#define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value"
+
+/* ------------------------- End of common defines ------------------------ */
+
+#ifndef REDISMODULE_CORE
+
+/* Incomplete structures for compiler checks but opaque access. */
+typedef struct RedisModuleCtx RedisModuleCtx;
+typedef struct RedisModuleKey RedisModuleKey;
+typedef struct RedisModuleString RedisModuleString;
+typedef struct RedisModuleCallReply RedisModuleCallReply;
+
+typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
+
+#define REDISMODULE_GET_API(name) \
+ RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name))
+
+#define REDISMODULE_API_FUNC(x) (*x)
+
+int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
+int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc);
+int REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
+int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
+int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);
+int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
+int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid);
+void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode);
+void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp);
+int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp);
+size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp);
+int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele);
+RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where);
+RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
+const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len);
+void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply);
+int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply);
+long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply);
+size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply);
+RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
+RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
+RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
+void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
+const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(RedisModuleString *str, size_t *len);
+int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
+int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);
+int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, int len);
+int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
+int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
+int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(RedisModuleString *str, long long *ll);
+void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
+int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
+int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
+const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
+RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
+int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
+int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
+char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
+int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
+
+/* This is included inline inside each Redis module. */
+static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
+ void *getapifuncptr = ((void**)ctx)[0];
+ RedisModule_GetApi = (int (*)(const char *, void *)) getapifuncptr;
+ REDISMODULE_GET_API(CreateCommand);
+ REDISMODULE_GET_API(SetModuleAttribs);
+ REDISMODULE_GET_API(WrongArity);
+ REDISMODULE_GET_API(ReplyWithLongLong);
+ REDISMODULE_GET_API(ReplyWithError);
+ REDISMODULE_GET_API(ReplyWithSimpleString);
+ REDISMODULE_GET_API(ReplyWithArray);
+ REDISMODULE_GET_API(ReplyWithStringBuffer);
+ REDISMODULE_GET_API(ReplyWithString);
+ REDISMODULE_GET_API(GetSelectedDb);
+ REDISMODULE_GET_API(SelectDb);
+ REDISMODULE_GET_API(OpenKey);
+ REDISMODULE_GET_API(CloseKey);
+ REDISMODULE_GET_API(KeyType);
+ REDISMODULE_GET_API(ValueLength);
+ REDISMODULE_GET_API(ListPush);
+ REDISMODULE_GET_API(ListPop);
+ REDISMODULE_GET_API(StringToLongLong);
+ REDISMODULE_GET_API(Call);
+ REDISMODULE_GET_API(CallReplyProto);
+ REDISMODULE_GET_API(FreeCallReply);
+ REDISMODULE_GET_API(CallReplyInteger);
+ REDISMODULE_GET_API(CallReplyType);
+ REDISMODULE_GET_API(CallReplyLength);
+ REDISMODULE_GET_API(CallReplyArrayElement);
+ REDISMODULE_GET_API(CallReplyStringPtr);
+ REDISMODULE_GET_API(CreateStringFromCallReply);
+ REDISMODULE_GET_API(CreateString);
+ REDISMODULE_GET_API(CreateStringFromLongLong);
+ REDISMODULE_GET_API(FreeString);
+ REDISMODULE_GET_API(StringPtrLen);
+ REDISMODULE_GET_API(AutoMemory);
+ REDISMODULE_GET_API(Replicate);
+ REDISMODULE_GET_API(ReplicateVerbatim);
+ REDISMODULE_GET_API(DeleteKey);
+ REDISMODULE_GET_API(StringSet);
+ REDISMODULE_GET_API(StringDMA);
+ REDISMODULE_GET_API(StringTruncate);
+
+ RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
+ return REDISMODULE_OK;
+}
+
+#else
+
+/* Things only defined for the modules core, not exported to modules
+ * including this file. */
+#define RedisModuleString robj
+
+#endif /* REDISMODULE_CORE */
+#endif /* REDISMOUDLE_H */
diff --git a/src/server.c b/src/server.c
index 3e0ed8dfa..22062d532 100644
--- a/src/server.c
+++ b/src/server.c
@@ -123,6 +123,7 @@ struct redisServer server; /* server global state */
* are not fast commands.
*/
struct redisCommand redisCommandTable[] = {
+ {"module",moduleCommand,-2,"as",0,NULL,1,1,1,0,0},
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
{"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
@@ -648,6 +649,18 @@ dictType clusterNodesBlackListDictType = {
NULL /* val destructor */
};
+/* Cluster re-addition blacklist. This maps node IDs to the time
+ * we can re-add this node. The goal is to avoid readding a removed
+ * node for some time. */
+dictType modulesDictType = {
+ dictSdsCaseHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCaseCompare, /* key compare */
+ dictSdsDestructor, /* key destructor */
+ NULL /* val destructor */
+};
+
/* Migrate cache dict type. */
dictType migrateCacheDictType = {
dictSdsHash, /* hash function */
@@ -2238,6 +2251,7 @@ void call(client *c, int flags) {
/* Initialization: clear the flags that must be set by the command on
* demand, and initialize the array for additional commands propagation. */
c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
+ redisOpArray prev_also_propagate = server.also_propagate;
redisOpArrayInit(&server.also_propagate);
/* Call the command. */
@@ -2333,6 +2347,7 @@ void call(client *c, int flags) {
}
redisOpArrayFree(&server.also_propagate);
}
+ server.also_propagate = prev_also_propagate;
server.stat_numcommands++;
}
@@ -3993,6 +4008,7 @@ int main(int argc, char **argv) {
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig();
+ moduleInitModulesSystem();
/* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */
@@ -4099,6 +4115,7 @@ int main(int argc, char **argv) {
#ifdef __linux__
linuxMemoryWarnings();
#endif
+ moduleLoadFromQueue();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
diff --git a/src/server.h b/src/server.h
index c840cf1f3..aa42b1002 100644
--- a/src/server.h
+++ b/src/server.h
@@ -256,6 +256,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CLIENT_REPLY_SKIP (1<<24) /* Don't send just this reply. */
#define CLIENT_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
#define CLIENT_LUA_DEBUG_SYNC (1<<26) /* EVAL debugging without fork() */
+#define CLIENT_MODULE (1<<27) /* Non connected client used by some module. */
/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */
@@ -570,7 +571,6 @@ typedef struct client {
uint64_t id; /* Client incremental unique ID. */
int fd; /* Client socket. */
redisDb *db; /* Pointer to currently SELECTed DB. */
- int dictid; /* ID of the currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */
sds querybuf; /* Buffer we use to accumulate client queries. */
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
@@ -725,6 +725,9 @@ struct redisServer {
int cronloops; /* Number of times the cron function run */
char runid[CONFIG_RUN_ID_SIZE+1]; /* ID always different at every exec. */
int sentinel_mode; /* True if this instance is a Sentinel. */
+ /* Modules */
+ dict *moduleapi; /* Exported APIs dictionary for modules. */
+ list *loadmodule_queue; /* List of modules to load at startup. */
/* Networking */
int port; /* TCP listening port */
int tcp_backlog; /* TCP listen() backlog */
@@ -1085,11 +1088,17 @@ extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
extern dictType hashDictType;
extern dictType replScriptCacheDictType;
extern dictType keyptrDictType;
+extern dictType modulesDictType;
/*-----------------------------------------------------------------------------
* Functions prototypes
*----------------------------------------------------------------------------*/
+/* Modules */
+void moduleInitModulesSystem(void);
+int moduleLoad(const char *path);
+void moduleLoadFromQueue(void);
+
/* Utils */
long long ustime(void);
long long mstime(void);
@@ -1686,6 +1695,7 @@ void pfcountCommand(client *c);
void pfmergeCommand(client *c);
void pfdebugCommand(client *c);
void latencyCommand(client *c);
+void moduleCommand(client *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
diff --git a/src/util.c b/src/util.c
index c1494e021..8d68f0bb1 100644
--- a/src/util.c
+++ b/src/util.c
@@ -274,7 +274,7 @@ uint32_t sdigits10(int64_t v) {
*
* Modified in order to handle signed integers since the original code was
* designed for unsigned integers. */
-int ll2string(char* dst, size_t dstlen, long long svalue) {
+int ll2string(char *dst, size_t dstlen, long long svalue) {
static const char digits[201] =
"0001020304050607080910111213141516171819"
"2021222324252627282930313233343536373839"