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 + +#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 = ""; + 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 */ +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 + + 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`. + +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 +#include +#include + +/* 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 + * Shows how to generate arrays as commands replies. + * It just outputs 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 */ +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 + +/* ---------------- 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"