From 183458f76a0ef579eec3bd8ec1652062fbb111d6 Mon Sep 17 00:00:00 2001 From: Matt Stancliff Date: Mon, 16 Jun 2014 23:35:45 -0400 Subject: [PATCH] Cluster: Add COMMANDS command COMMANDS returns a nested multibulk reply for each command in the command table. The reply for each command contains: - command name - arity - array of command flags - start key position - end key position - key offset step - optional: if the keys are not deterministic and Redis uses an internal key evaluation function, the 6th field appears and is defined as a status reply of: REQUIRES ARGUMENT PARSING Cluster clients need to know where the keys are in each command to implement proper routing to cluster nodes. Redis commands can have multiple keys, keys at offset steps, or other issues where you can't always assume the first element after the command name is the cluster routing key. Using the information exposed by COMMANDS, client implementations can have live, accurate key extraction details for all commands. Also implements COMMANDS INFO [commands...] to return only a specific set of commands instead of all 160+ commands live in Redis. --- src/redis.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/redis.h | 1 + 2 files changed, 69 insertions(+) diff --git a/src/redis.c b/src/redis.c index c91763a6..4b7c9cb5 100644 --- a/src/redis.c +++ b/src/redis.c @@ -273,6 +273,7 @@ struct redisCommand redisCommandTable[] = { {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0}, {"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0}, {"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0}, + {"commands",commandsCommand,0,"rlt",0,NULL,0,0,0,0,0}, {"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0}, {"pfadd",pfaddCommand,-2,"wm",0,NULL,1,1,1,0,0}, {"pfcount",pfcountCommand,-2,"w",0,NULL,1,1,1,0,0}, @@ -2401,6 +2402,73 @@ void timeCommand(redisClient *c) { addReplyBulkLongLong(c,tv.tv_usec); } + +static int replyCmdFlag(redisClient *c, + struct redisCommand *cmd, int f, char *reply) { + if (cmd->flags & f) { + addReplyStatus(c, reply); + return 1; + } + return 0; +} +static void replyCmd(redisClient *c, struct redisCommand *cmd) { + if (!cmd) { + addReply(c, shared.nullbulk); + } else { + /* We are adding: command name, arg count, flags, first, last, offset */ + addReplyMultiBulkLen(c, 6); + addReplyBulkCString(c, cmd->name); + addReplyLongLong(c, cmd->arity); + + int flagcount = 0; + void *flaglen = addDeferredMultiBulkLength(c); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_WRITE, "write"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_READONLY, "readonly"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_DENYOOM, "denyoom"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_ADMIN, "admin"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_PUBSUB, "pubsub"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_NOSCRIPT, "noscript"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_RANDOM, "random"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_SORT_FOR_SCRIPT,"scriptsort"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_LOADING, "loading"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_STALE, "stale"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_SKIP_MONITOR, "skipmonitor"); + flagcount += replyCmdFlag(c,cmd,REDIS_CMD_ASKING, "asking"); + if (cmd->getkeys_proc) { + addReplyStatus(c, "movablekeys"); + flagcount += 1; + } + setDeferredMultiBulkLength(c, flaglen, flagcount); + + addReplyLongLong(c, cmd->firstkey); + addReplyLongLong(c, cmd->lastkey); + addReplyLongLong(c, cmd->keystep); + } +} +void commandsCommand(redisClient *c) { + dictIterator *di; + dictEntry *de; + + if (c->argc > 2 && !strcasecmp(c->argv[1]->ptr, "info")) { + int i; + addReplyMultiBulkLen(c, c->argc-2); + for (i = 2; i < c->argc; i++) { + replyCmd(c, dictFetchValue(server.commands, c->argv[i]->ptr)); + } + } else if (c->argc > 2) { + addReplyError(c, "Unknown subcommand."); + return; + } else { + addReplyMultiBulkLen(c, dictSize(server.commands)); + di = dictGetIterator(server.commands); + while ((de = dictNext(di)) != NULL) { + replyCmd(c, dictGetVal(de)); + } + dictReleaseIterator(di); + } +} + + /* Convert an amount of bytes into a human readable string in the form * of 100B, 2G, 100M, 4K, and so forth. */ void bytesToHuman(char *s, unsigned long long n) { diff --git a/src/redis.h b/src/redis.h index 2622fb97..eb20afd0 100644 --- a/src/redis.h +++ b/src/redis.h @@ -1327,6 +1327,7 @@ uint64_t redisBuildId(void); void authCommand(redisClient *c); void pingCommand(redisClient *c); void echoCommand(redisClient *c); +void commandsCommand(redisClient *c); void setCommand(redisClient *c); void setnxCommand(redisClient *c); void setexCommand(redisClient *c);