diff --git a/src/db.c b/src/db.c index 60acb43cc..6f70a5383 100644 --- a/src/db.c +++ b/src/db.c @@ -1124,7 +1124,9 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in * This function uses the command table if a command-specific helper function * is not required, otherwise it calls the command-specific function. */ int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) { - if (cmd->getkeys_proc) { + if (cmd->flags & CMD_MODULE_GETKEYS) { + return moduleGetCommandKeysViaAPI(cmd,argv,argc,numkeys); + } else if (!(cmd->flags & CMD_MODULE) && cmd->getkeys_proc) { return cmd->getkeys_proc(cmd,argv,argc,numkeys); } else { return getKeysUsingCommandTable(cmd,argv,argc,numkeys); diff --git a/src/module.c b/src/module.c index c80876d46..5b7dcf888 100644 --- a/src/module.c +++ b/src/module.c @@ -52,12 +52,17 @@ struct RedisModuleCtx { int flags; /* REDISMODULE_CTX_... flags. */ void **postponed_arrays; /* To set with RM_ReplySetArrayLength(). */ int postponed_arrays_count; /* Number of entries in postponed_arrays. */ + + /* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST flag set. */ + int *keys_pos; + int keys_count; }; typedef struct RedisModuleCtx RedisModuleCtx; -#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, 0, 0, 0, NULL, 0} +#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, 0} #define REDISMODULE_CTX_MULTI_EMITTED (1<<0) #define REDISMODULE_CTX_AUTO_MEMORY (1<<1) +#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2) /* This represents a Redis key opened with RM_OpenKey(). */ struct RedisModuleKey { @@ -270,10 +275,92 @@ void RedisModuleCommandDispatcher(client *c) { moduleFreeContext(&ctx); } +/* This function returns the list of keys, with the same interface as the + * 'getkeys' function of the native commands, for module commands that exported + * the "getkeys-api" flag during the registration. This is done when the + * list of keys are not at fixed positions, so that first/last/step cannot + * be used. + * + * In order to accomplish its work, the module command is called, flagging + * the context in a way that the command can recognize this is a special + * "get keys" call by calling RedisModule_IsKeysPositionRequest(ctx). */ +int *moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) { + RedisModuleCommandProxy *cp = (void*)(unsigned long)cmd->getkeys_proc; + RedisModuleCtx ctx = REDISMODULE_CTX_INIT; + + ctx.module = cp->module; + ctx.client = NULL; + ctx.flags |= REDISMODULE_CTX_KEYS_POS_REQUEST; + cp->func(&ctx,(void**)argv,argc); + int *res = ctx.keys_pos; + if (numkeys) *numkeys = ctx.keys_count; + moduleFreeContext(&ctx); + return res; +} + +/* Return non-zero if a module command, that was declared with the + * flag "getkeys-api", is called in a special way to get the keys positions + * and not to get executed. Otherwise zero is returned. */ +int RM_IsKeysPositionRequest(RedisModuleCtx *ctx) { + return (ctx->flags & REDISMODULE_CTX_KEYS_POS_REQUEST) != 0; +} + +/* When a module command is called in order to obtain the position of + * keys, since it was flagged as "getkeys-api" during the registration, + * the command implementation checks for this special call using the + * RedisModule_IsKeysPositionRequest() API and uses this function in + * order to report keys, like in the following example: + * + * if (RedisModule_IsKeysPositionRequest(ctx)) { + * RedisModule_KeyAtPos(ctx,1); + * RedisModule_KeyAtPos(ctx,2); + * } + * + * Note: in the example below the get keys API would not be needed since + * keys are at fixed positions. This interface is only used for commands + * with a more complex structure. */ +void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) { + if (!(ctx->flags & REDISMODULE_CTX_KEYS_POS_REQUEST)) return; + if (pos <= 0) return; + ctx->keys_pos = zrealloc(ctx->keys_pos,sizeof(int)*(ctx->keys_count+1)); + ctx->keys_pos[ctx->keys_count++] = pos; +} + +/* Helper for RM_CreateCommand(). Truns a string representing command + * flags into the command flags used by the Redis core. + * + * It returns the set of flags, or -1 if unknown flags are found. */ +int commandFlagsFromString(char *s) { + int count, j; + int flags = 0; + sds *tokens = sdssplitlen(s,strlen(s)," ",1,&count); + for (j = 0; j < count; j++) { + char *t = tokens[j]; + if (!strcasecmp(t,"write")) flags |= CMD_WRITE; + else if (!strcasecmp(t,"readonly")) flags |= CMD_READONLY; + else if (!strcasecmp(t,"admin")) flags |= CMD_ADMIN; + else if (!strcasecmp(t,"deny-oom")) flags |= CMD_DENYOOM; + else if (!strcasecmp(t,"deny-script")) flags |= CMD_NOSCRIPT; + else if (!strcasecmp(t,"allow-loading")) flags |= CMD_LOADING; + else if (!strcasecmp(t,"pubsub")) flags |= CMD_PUBSUB; + else if (!strcasecmp(t,"random")) flags |= CMD_RANDOM; + else if (!strcasecmp(t,"allow-stale")) flags |= CMD_STALE; + else if (!strcasecmp(t,"no-monitor")) flags |= CMD_SKIP_MONITOR; + else if (!strcasecmp(t,"fast")) flags |= CMD_FAST; + else if (!strcasecmp(t,"getkeys-api")) flags |= CMD_MODULE_GETKEYS; + else if (!strcasecmp(t,"no-cluster")) flags |= CMD_MODULE_NO_CLUSTER; + else break; + } + sdsfreesplitres(tokens,count); + if (j != count) return -1; /* Some token not processed correctly. */ + return flags; +} + /* 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 is returned. + * name is already busy or a set of invalid flags were passed, otherwise + * REDISMODULE_OK is returned and the new command is registered. * * This function must be called during the initialization of the module * inside the RedisModule_OnLoad() function. Calling this function outside @@ -284,8 +371,45 @@ void RedisModuleCommandDispatcher(client *c) { * int MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); * * And is supposed to always return REDISMODULE_OK. + * + * The set of flags 'strflags' specify the behavior of the command, and should + * be passed as a C string compoesd of space separated words, like for + * example "write deny-oom". The set of flags are: + * + * "write": The command may modify the data set (it may also read from it). + * "readonly": The command returns data from keys but never writes. + * "admin": The command is an administrative command (may change replication + * or perform similar tasks). + * "deny-oom": The command may use additional memory and should be denied during + * out of memory conditions. + * "deny-script": Don't allow this command in Lua scripts. + * "allow-loading": Allow this command while the server is loading data. Only + * commands not interacting with the data set should be allowed + * to run in this mode. If not sure don't use this flag. + * "pubsub": The command publishes things on Pub/Sub channels. + * "random": The command may have different outputs even starting from the + * same input arguments and key values. + * "allow-stale": The command is allowed to run on slaves that don't serve stale + * data. Don't use if you don't know what this means. + * "no-monitor": Don't propoagate the command on monitor. Use this if the command + * has sensible data among the arguments. + * "fast": The command time complexity is not greater than O(log(N)) where + * N is the size of the collection or anything else representing + * the normal scalability issue with the command. + * "getkeys-api": The command implements the interface to return the arguments + * that are keys. Used when start/stop/step is not enough because + * of the command syntax. + * "no-cluster": The command should not register in Redis Cluster since is not + * designed to work with it because, for example, is unable to + * report the position of the keys, programmatically creates key + * names, or any other reason. */ -int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc) { +int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) { + int flags = strflags ? commandFlagsFromString((char*)strflags) : 0; + if (flags == -1) return REDISMODULE_ERR; + if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled) + return REDISMODULE_ERR; + struct redisCommand *rediscmd; RedisModuleCommandProxy *cp; sds cmdname = sdsnew(name); @@ -310,11 +434,11 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c cp->rediscmd->name = cmdname; cp->rediscmd->proc = RedisModuleCommandDispatcher; cp->rediscmd->arity = -1; - cp->rediscmd->flags = 0; + cp->rediscmd->flags = flags | CMD_MODULE; cp->rediscmd->getkeys_proc = (redisGetKeysProc*)(unsigned long)cp; - cp->rediscmd->firstkey = 1; - cp->rediscmd->lastkey = 1; - cp->rediscmd->keystep = 1; + cp->rediscmd->firstkey = firstkey; + cp->rediscmd->lastkey = lastkey; + cp->rediscmd->keystep = keystep; cp->rediscmd->microseconds = 0; cp->rediscmd->calls = 0; dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd); @@ -2129,6 +2253,8 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(ZsetRangeEndReached); REGISTER_API(HashSet); REGISTER_API(HashGet); + REGISTER_API(IsKeysPositionRequest); + REGISTER_API(KeyAtPos); } /* Global initialization at Redis startup. */ diff --git a/src/modules/API.md b/src/modules/API.md index 9a336c111..cb2ca2279 100644 --- a/src/modules/API.md +++ b/src/modules/API.md @@ -761,7 +761,6 @@ 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 diff --git a/src/modules/helloworld.c b/src/modules/helloworld.c index 1ae90c4e1..3734503bb 100644 --- a/src/modules/helloworld.c +++ b/src/modules/helloworld.c @@ -455,63 +455,64 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx) { == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.simple", - HelloSimple_RedisCommand) == REDISMODULE_ERR) + HelloSimple_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.push.native", - HelloPushNative_RedisCommand) == REDISMODULE_ERR) + HelloPushNative_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.push.call", - HelloPushCall_RedisCommand) == REDISMODULE_ERR) + HelloPushCall_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.push.call2", - HelloPushCall2_RedisCommand) == REDISMODULE_ERR) + HelloPushCall2_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.list.sum.len", - HelloListSumLen_RedisCommand) == REDISMODULE_ERR) + HelloListSumLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.list.splice", - HelloListSplice_RedisCommand) == REDISMODULE_ERR) + HelloListSplice_RedisCommand,"write deny-oom",1,2,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.list.splice.auto", - HelloListSpliceAuto_RedisCommand) == REDISMODULE_ERR) + HelloListSpliceAuto_RedisCommand, + "write deny-oom",1,2,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.rand.array", - HelloRandArray_RedisCommand) == REDISMODULE_ERR) + HelloRandArray_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.repl1", - HelloRepl1_RedisCommand) == REDISMODULE_ERR) + HelloRepl1_RedisCommand,"write",0,0,0) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.repl2", - HelloRepl2_RedisCommand) == REDISMODULE_ERR) + HelloRepl2_RedisCommand,"write",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.toggle.case", - HelloToggleCase_RedisCommand) == REDISMODULE_ERR) + HelloToggleCase_RedisCommand,"write",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.more.expire", - HelloMoreExpire_RedisCommand) == REDISMODULE_ERR) + HelloMoreExpire_RedisCommand,"write",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.zsumrange", - HelloZsumRange_RedisCommand) == REDISMODULE_ERR) + HelloZsumRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.lexrange", - HelloLexRange_RedisCommand) == REDISMODULE_ERR) + HelloLexRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; if (RedisModule_CreateCommand(ctx,"hello.hcopy", - HelloHCopy_RedisCommand) == REDISMODULE_ERR) + HelloHCopy_RedisCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; return REDISMODULE_OK; diff --git a/src/redismodule.h b/src/redismodule.h index 6503af4e8..d23f8cbc4 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -87,7 +87,7 @@ typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv #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_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep); 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); @@ -147,6 +147,8 @@ int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...); int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...); +int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx); +void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos); /* This is included inline inside each Redis module. */ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { @@ -213,6 +215,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ZsetRangeEndReached); REDISMODULE_GET_API(HashSet); REDISMODULE_GET_API(HashGet); + REDISMODULE_GET_API(IsKeysPositionRequest); + REDISMODULE_GET_API(KeyAtPos); RedisModule_SetModuleAttribs(ctx,name,ver,apiver); return REDISMODULE_OK; diff --git a/src/server.c b/src/server.c index 22062d532..f070865cb 100644 --- a/src/server.c +++ b/src/server.c @@ -2761,7 +2761,9 @@ void addReplyCommand(client *c, struct redisCommand *cmd) { flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_MONITOR, "skip_monitor"); flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking"); flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast"); - if (cmd->getkeys_proc) { + if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) || + cmd->flags & CMD_MODULE_GETKEYS) + { addReplyStatus(c, "movablekeys"); flagcount += 1; } diff --git a/src/server.h b/src/server.h index 9277196a2..28a8bebf6 100644 --- a/src/server.h +++ b/src/server.h @@ -178,20 +178,22 @@ typedef long long mstime_t; /* millisecond time type. */ /* Command flags. Please check the command table defined in the redis.c file * for more information about the meaning of every flag. */ -#define CMD_WRITE 1 /* "w" flag */ -#define CMD_READONLY 2 /* "r" flag */ -#define CMD_DENYOOM 4 /* "m" flag */ -#define CMD_NOT_USED_1 8 /* no longer used flag */ -#define CMD_ADMIN 16 /* "a" flag */ -#define CMD_PUBSUB 32 /* "p" flag */ -#define CMD_NOSCRIPT 64 /* "s" flag */ -#define CMD_RANDOM 128 /* "R" flag */ -#define CMD_SORT_FOR_SCRIPT 256 /* "S" flag */ -#define CMD_LOADING 512 /* "l" flag */ -#define CMD_STALE 1024 /* "t" flag */ -#define CMD_SKIP_MONITOR 2048 /* "M" flag */ -#define CMD_ASKING 4096 /* "k" flag */ -#define CMD_FAST 8192 /* "F" flag */ +#define CMD_WRITE (1<<0) /* "w" flag */ +#define CMD_READONLY (1<<1) /* "r" flag */ +#define CMD_DENYOOM (1<<2) /* "m" flag */ +#define CMD_MODULE (1<<3) /* Command exported by module. */ +#define CMD_ADMIN (1<<4) /* "a" flag */ +#define CMD_PUBSUB (1<<5) /* "p" flag */ +#define CMD_NOSCRIPT (1<<6) /* "s" flag */ +#define CMD_RANDOM (1<<7) /* "R" flag */ +#define CMD_SORT_FOR_SCRIPT (1<<8) /* "S" flag */ +#define CMD_LOADING (1<<9) /* "l" flag */ +#define CMD_STALE (1<<10) /* "t" flag */ +#define CMD_SKIP_MONITOR (1<<11) /* "M" flag */ +#define CMD_ASKING (1<<12) /* "k" flag */ +#define CMD_FAST (1<<13) /* "F" flag */ +#define CMD_MODULE_GETKEYS (1<<14) /* Use the modules getkeys interface. */ +#define CMD_MODULE_NO_CLUSTER (1<<15) /* Deny on Redis Cluster. */ /* Defines related to the dump file format. To store 32 bits lengths for short * keys requires a lot of space, so we check the most significant 2 bits of @@ -1098,6 +1100,7 @@ extern dictType modulesDictType; void moduleInitModulesSystem(void); int moduleLoad(const char *path); void moduleLoadFromQueue(void); +int *moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, int *numkeys); /* Utils */ long long ustime(void);