Unverified Commit 86781600 authored by guybe7's avatar guybe7 Committed by GitHub
Browse files

Auto-generate the command table from JSON files (#9656)

Delete the hardcoded command table and replace it with an auto-generated table, based
on a JSON file that describes the commands (each command must have a JSON file).

These JSON files are the SSOT of everything there is to know about Redis commands,
and it is reflected fully in COMMAND INFO.

These JSON files are used to generate commands.c (using a python script), which is then
committed to the repo and compiled.

The purpose is:
* Clients and proxies will be able to get much more info from redis, instead of relying on hard coded logic.
* drop the dependency between Redis-user and the commands.json in redis-doc.
* delete help.h and have redis-cli learn everything it needs to know just by issuing COMMAND (will be
  done in a separate PR)
* redis.io should stop using commands.json and learn everything from Redis (ultimately one of the release
  artifacts should be a large JSON, containing all the information about all of the commands, which will be
  generated from COMMAND's reply)
* the byproduct of this is:
  * module commands will be able to provide that info and possibly be more of a first-class citizens
  * in theory, one may be able to generate a redis client library for a strictly typed language, by using this info.

### Interface changes

#### COMMAND INFO's reply change (and arg-less COMMAND)

Before this commit the reply at index 7 contained the key-specs list
and reply at index 8 contained the sub-commands list (Both unreleased).
Now, reply at index 7 is a map of:
- summary - short command description
- since - debut version
- group - command group
- complexity - complexity string
- doc-flags - flags used for documentation (e.g. "deprecated")
- deprecated-since - if deprecated, from which version?
- replaced-by - if deprecated, which command replaced it?
- history - a list of (version, what-changed) tuples
- hints - a list of strings, meant to provide hints for clients/proxies. see https://github.com/redis/redis/issues/9876
- arguments - an array of arguments. each element is a map, with the possibility of nesting (sub-arguments)
- key-specs - an array of keys specs (already in unstable, just changed location)
- subcommands - a list of sub-commands (already in unstable, just changed location)
- reply-schema - will be added in the future (see https://github.com/redis/redis/issues/9845)

more details on these can be found in https://github.com/redis/redis-doc/pull/1697

only the first three fields are mandatory 

#### API changes (unreleased API obviously)

now they take RedisModuleCommand opaque pointer instead of looking up the command by name

- RM_CreateSubcommand
- RM_AddCommandKeySpec
- RM_SetCommandKeySpecBeginSearchIndex
- RM_SetCommandKeySpecBeginSearchKeyword
- RM_SetCommandKeySpecFindKeysRange
- RM_SetCommandKeySpecFindKeysKeynum

Currently, we did not add module API to provide additional information about their commands because
we couldn't agree on how the API should look like, see https://github.com/redis/redis/issues/9944

.

### Somehow related changes
1. Literals should be in uppercase while placeholder in lowercase. Now all the GEO* command
   will be documented with M|KM|FT|MI and can take both lowercase and uppercase

### Unrelated changes
1. Bugfix: no_madaory_keys was absent in COMMAND's reply
2. expose CMD_MODULE as "module" via COMMAND
3. have a dedicated uint64 for ACL categories (instead of having them in the same uint64 as command flags)
Co-authored-by: default avatarItamar Haber <itamar@garantiadata.com>
parent fbfdf513
......@@ -470,7 +470,7 @@ void afterErrorReply(client *c, const char *s, size_t len) {
}
if (len > 4096) len = 4096;
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
const char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
"to its %s: '%.*s' after processing the command "
"'%s'", from, to, (int)len, s, cmdname);
......@@ -2616,7 +2616,7 @@ void clientCommand(client *c) {
" Kill connections made from the specified address",
" * LADDR (<ip:port>|<unixsocket>:0)",
" Kill connections made to specified local address",
" * TYPE (normal|master|replica|pubsub)",
" * TYPE (NORMAL|MASTER|REPLICA|PUBSUB)",
" Kill connections by type.",
" * USER <username>",
" Kill connections authenticated by <username>.",
......
......@@ -240,6 +240,23 @@ typedef uint64_t RedisModuleTimerID;
* are modified from the user's sperspective, to invalidate WATCH. */
#define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1)
typedef enum {
REDISMODULE_ARG_TYPE_STRING,
REDISMODULE_ARG_TYPE_INTEGER,
REDISMODULE_ARG_TYPE_DOUBLE,
REDISMODULE_ARG_TYPE_KEY, /* A string, but represents a keyname */
REDISMODULE_ARG_TYPE_PATTERN,
REDISMODULE_ARG_TYPE_UNIX_TIME,
REDISMODULE_ARG_TYPE_PURE_TOKEN,
REDISMODULE_ARG_TYPE_ONEOF, /* Must have sub-arguments */
REDISMODULE_ARG_TYPE_BLOCK /* Must have sub-arguments */
} RedisModuleCommandArgType;
#define REDISMODULE_CMD_ARG_NONE (0)
#define REDISMODULE_CMD_ARG_OPTIONAL (1<<0) /* The argument is optional (like GET in SET command) */
#define REDISMODULE_CMD_ARG_MULTIPLE (1<<1) /* The argument may repeat itself (like key in DEL) */
#define REDISMODULE_CMD_ARG_MULTIPLE_TOKEN (1<<2) /* The argument may repeat itself, and so does its token (like `GET pattern` in SORT) */
/* Server events definitions.
* Those flags should not be used directly by the module, instead
* the module should use RedisModuleEvent_* variables */
......@@ -534,6 +551,7 @@ typedef long long mstime_t;
/* Incomplete structures for compiler checks but opaque access. */
typedef struct RedisModuleCtx RedisModuleCtx;
typedef struct RedisModuleCommand RedisModuleCommand;
typedef struct RedisModuleKey RedisModuleKey;
typedef struct RedisModuleString RedisModuleString;
typedef struct RedisModuleCallReply RedisModuleCallReply;
......@@ -552,6 +570,7 @@ typedef struct RedisModuleScanCursor RedisModuleScanCursor;
typedef struct RedisModuleDefragCtx RedisModuleDefragCtx;
typedef struct RedisModuleUser RedisModuleUser;
typedef struct RedisModuleKeyOptCtx RedisModuleKeyOptCtx;
typedef struct RedisModuleCommandArg RedisModuleCommandArg;
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
......@@ -623,7 +642,8 @@ REDISMODULE_API void * (*RedisModule_Calloc)(size_t nmemb, size_t size) REDISMOD
REDISMODULE_API char * (*RedisModule_Strdup)(const char *str) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_CreateSubcommand)(RedisModuleCtx *ctx, const char *parent_name, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleCommand *(*RedisModule_GetCommand)(RedisModuleCtx *ctx, const char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_CreateSubcommand)(RedisModuleCommand *parent, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
......@@ -850,11 +870,11 @@ REDISMODULE_API int (*RedisModule_GetKeyspaceNotificationFlagsAll)() REDISMODULE
REDISMODULE_API int (*RedisModule_IsSubEventSupported)(RedisModuleEvent event, uint64_t subevent) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetServerVersion)() REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetTypeMethodVersion)() REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_AddCommandKeySpec)(RedisModuleCtx *ctx, const char *name, const char *specflags, int *spec_id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecBeginSearchIndex)(RedisModuleCtx *ctx, const char *name, int spec_id, int index) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecBeginSearchKeyword)(RedisModuleCtx *ctx, const char *name, int spec_id, const char *keyword, int startfrom) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecFindKeysRange)(RedisModuleCtx *ctx, const char *name, int spec_id, int lastkey, int keystep, int limit) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecFindKeysKeynum)(RedisModuleCtx *ctx, const char *name, int spec_id, int keynumidx, int firstkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_AddCommandKeySpec)(RedisModuleCommand *command, const char *specflags, int *spec_id) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecBeginSearchIndex)(RedisModuleCommand *command, int spec_id, int index) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecBeginSearchKeyword)(RedisModuleCommand *command, int spec_id, const char *keyword, int startfrom) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecFindKeysRange)(RedisModuleCommand *command, int spec_id, int lastkey, int keystep, int limit) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandKeySpecFindKeysKeynum)(RedisModuleCommand *command, int spec_id, int keynumidx, int firstkey, int keystep) REDISMODULE_ATTR;
/* Experimental APIs */
#ifdef REDISMODULE_EXPERIMENTAL_API
......@@ -945,6 +965,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(Realloc);
REDISMODULE_GET_API(Strdup);
REDISMODULE_GET_API(CreateCommand);
REDISMODULE_GET_API(GetCommand);
REDISMODULE_GET_API(CreateSubcommand);
REDISMODULE_GET_API(SetModuleAttribs);
REDISMODULE_GET_API(IsModuleNameBusy);
......@@ -1266,6 +1287,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
/* Things only defined for the modules core, not exported to modules
* including this file. */
#define RedisModuleString robj
#define RedisModuleCommandArg redisCommandArg
#endif /* REDISMODULE_CORE */
#endif /* REDISMODULE_H */
......@@ -79,2006 +79,6 @@ double R_Zero, R_PosInf, R_NegInf, R_Nan;
/* Global vars */
struct redisServer server; /* Server global state */
/* Our command table.
*
* Every entry is composed of the following fields:
*
* name: A string representing the command name.
*
* function: Pointer to the C function implementing the command.
*
* arity: Number of arguments, it is possible to use -N to say >= N
*
* sflags: Command flags as string. See below for a table of flags.
*
* flags: Flags as bitmask. Computed by Redis using the 'sflags' field.
*
* get_keys_proc: An optional function to get key arguments from a command.
* This is only used when the following three fields are not
* enough to specify what arguments are keys.
*
* first_key_index: First argument that is a key
*
* last_key_index: Last argument that is a key
*
* key_step: Step to get all the keys from first to last argument.
* For instance in MSET the step is two since arguments
* are key,val,key,val,...
*
* microseconds: Microseconds of total execution time for this command.
*
* calls: Total number of calls of this command.
*
* id: Command bit identifier for ACLs or other goals.
*
* The flags, microseconds and calls fields are computed by Redis and should
* always be set to zero.
*
* Command flags are expressed using space separated strings, that are turned
* into actual flags by the populateCommandTable() function.
*
* This is the meaning of the flags:
*
* write: Write command (may modify the key space).
*
* read-only: Commands just reading from keys without changing the content.
* Note that commands that don't read from the keyspace such as
* TIME, SELECT, INFO, administrative commands, and connection
* or transaction related commands (multi, exec, discard, ...)
* are not flagged as read-only commands, since they affect the
* server or the connection in other ways.
*
* use-memory: May increase memory usage once called. Don't allow if out
* of memory.
*
* admin: Administrative command, like SAVE or SHUTDOWN.
*
* pub-sub: Pub/Sub related command.
*
* no-script: Command not allowed in scripts.
*
* random: Random command. Command is not deterministic, that is, the same
* command with the same arguments, with the same key space, may
* have different results. For instance SPOP and RANDOMKEY are
* two random commands.
*
* to-sort: Sort command output array if called from script, so that the
* output is deterministic. When this flag is used (not always
* possible), then the "random" flag is not needed.
*
* ok-loading: Allow the command while loading the database.
*
* ok-stale: Allow the command while a slave has stale data but is not
* allowed to serve this data. Normally no command is accepted
* in this condition but just a few.
*
* no-monitor: Do not automatically propagate the command on MONITOR.
*
* no-slowlog: Do not automatically propagate the command to the slowlog.
*
* cluster-asking: Perform an implicit ASKING for this command, so the
* command will be accepted in cluster mode if the slot is marked
* as 'importing'.
*
* fast: Fast command: O(1) or O(log(N)) command that should never
* delay its execution as long as the kernel scheduler is giving
* us time. Note that commands that may trigger a DEL as a side
* effect (like SET) are not fast commands.
*
* may-replicate: Command may produce replication traffic, but should be
* allowed under circumstances where write commands are disallowed.
* Examples include PUBLISH, which replicates pubsub messages,and
* EVAL, which may execute write commands, which are replicated,
* or may just execute read commands. A command can not be marked
* both "write" and "may-replicate"
*
* sentinel: This command is present in sentinel mode too.
*
* sentinel-only: This command is present only when in sentinel mode.
*
* no-mandatory-keys: This key arguments for this command are optional.
*
* The following additional flags are only used in order to put commands
* in a specific ACL category. Commands can have multiple ACL categories.
* See redis.conf for the exact meaning of each.
*
* @keyspace, @read, @write, @set, @sortedset, @list, @hash, @string, @bitmap,
* @hyperloglog, @stream, @admin, @fast, @slow, @pubsub, @blocking, @dangerous,
* @connection, @transaction, @scripting, @geo.
*
* Note that:
*
* 1) The read-only flag implies the @read ACL category.
* 2) The write flag implies the @write ACL category.
* 3) The fast flag implies the @fast ACL category.
* 4) The admin flag implies the @admin and @dangerous ACL category.
* 5) The pub-sub flag implies the @pubsub ACL category.
* 6) The lack of fast flag implies the @slow ACL category.
* 7) The non obvious "keyspace" category includes the commands
* that interact with keys without having anything to do with
* specific data structures, such as: DEL, RENAME, MOVE, SELECT,
* TYPE, EXPIRE*, PEXPIRE*, TTL, PTTL, ...
*/
struct redisCommand configSubcommands[] = {
{"set",configSetCommand,-4,
"admin ok-stale no-script"},
{"get",configGetCommand,3,
"admin ok-loading ok-stale no-script"},
{"resetstat",configResetStatCommand,2,
"admin ok-stale no-script"},
{"rewrite",configRewriteCommand,2,
"admin ok-stale no-script"},
{"help",configHelpCommand,2,
"ok-stale ok-loading"},
{NULL},
};
struct redisCommand xinfoSubcommands[] = {
{"consumers",xinfoCommand,4,
"read-only random @stream",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"groups",xinfoCommand,3,
"read-only @stream",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"stream",xinfoCommand,-3,
"read-only @stream",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"help",xinfoCommand,2,
"ok-stale ok-loading @stream"},
{NULL},
};
struct redisCommand xgroupSubcommands[] = {
{"create",xgroupCommand,-5,
"write use-memory @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"setid",xgroupCommand,5,
"write @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"destroy",xgroupCommand,4,
"write @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"createconsumer",xgroupCommand,5,
"write use-memory @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"delconsumer",xgroupCommand,5,
"write @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"help",xgroupCommand,2,
"ok-stale ok-loading @stream"},
{NULL},
};
struct redisCommand commandSubcommands[] = {
{"count",commandCountCommand,2,
"ok-loading ok-stale @connection"},
{"list",commandListCommand,-2,
"ok-loading ok-stale @connection"},
{"info",commandInfoCommand,-3,
"ok-loading ok-stale @connection"},
{"getkeys",commandGetKeysCommand,-4,
"ok-loading ok-stale @connection"},
{"help",commandHelpCommand,2,
"ok-loading ok-stale @connection"},
{NULL},
};
struct redisCommand memorySubcommands[] = {
{"doctor",memoryCommand,2,
"random"},
{"stats",memoryCommand,2,
"random"},
{"malloc-stats",memoryCommand,2,
"random"},
{"purge",memoryCommand,2,
""},
{"usage",memoryCommand,-3,
"read-only",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"help",memoryCommand,2,
"ok-stale ok-loading"},
{NULL},
};
struct redisCommand aclSubcommands[] = {
{"cat",aclCommand,-2,
"no-script ok-loading ok-stale sentinel"},
{"deluser",aclCommand,-3,
"admin no-script ok-loading ok-stale sentinel"},
{"genpass",aclCommand,-2,
"no-script ok-loading ok-stale sentinel"},
{"getuser",aclCommand,3,
"admin no-script ok-loading ok-stale sentinel"},
{"list",aclCommand,2,
"admin no-script ok-loading ok-stale sentinel"},
{"load",aclCommand,2,
"admin no-script ok-loading ok-stale sentinel"},
{"log",aclCommand,-2,
"admin no-script ok-loading ok-stale sentinel"},
{"save",aclCommand,2,
"admin no-script ok-loading ok-stale sentinel"},
{"setuser",aclCommand,-3,
"admin no-script ok-loading ok-stale sentinel"},
{"users",aclCommand,2,
"admin no-script ok-loading ok-stale sentinel"},
{"whoami",aclCommand,2,
"no-script ok-loading ok-stale sentinel"},
{"help",aclCommand,2,
"ok-stale ok-loading sentinel"},
{NULL},
};
struct redisCommand latencySubcommands[] = {
{"doctor",latencyCommand,2,
"admin no-script ok-loading ok-stale"},
{"graph",latencyCommand,3,
"admin no-script ok-loading ok-stale"},
{"history",latencyCommand,3,
"admin no-script ok-loading ok-stale"},
{"latest",latencyCommand,2,
"admin no-script ok-loading ok-stale"},
{"reset",latencyCommand,-2,
"admin no-script ok-loading ok-stale"},
{"help",latencyCommand,2,
"ok-stale ok-loading"},
{NULL},
};
struct redisCommand moduleSubcommands[] = {
{"list",moduleCommand,2,
"admin no-script"},
{"load",moduleCommand,-3,
"admin no-script"},
{"unload",moduleCommand,3,
"admin no-script"},
{"help",moduleCommand,2,
"ok-stale ok-loading"},
{NULL},
};
struct redisCommand slowlogSubcommands[] = {
{"get",slowlogCommand,-2,
"admin random ok-loading ok-stale"},
{"len",slowlogCommand,2,
"admin random ok-loading ok-stale"},
{"reset",slowlogCommand,2,
"admin ok-loading ok-stale"},
{"help",slowlogCommand,2,
"ok-stale ok-loading"},
{NULL},
};
struct redisCommand objectSubcommands[] = {
{"encoding",objectCommand,3,
"read-only @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"freq",objectCommand,3,
"read-only random @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"idletime",objectCommand,3,
"read-only random @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"refcount",objectCommand,3,
"read-only @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"help",objectCommand,2,
"ok-stale ok-loading @keyspace"},
{NULL},
};
struct redisCommand scriptSubcommands[] = {
{"debug",scriptCommand,3,
"no-script @scripting"},
{"exists",scriptCommand,-3,
"no-script @scripting"},
{"flush",scriptCommand,-2,
"may-replicate no-script @scripting"},
{"kill",scriptCommand,2,
"no-script @scripting"},
{"load",scriptCommand,3,
"may-replicate no-script @scripting"},
{"help",scriptCommand,2,
"ok-loading ok-stale @scripting"},
{NULL},
};
struct redisCommand functionSubcommands[] = {
{"create",functionsCreateCommand,-5,
"may-replicate no-script @scripting"},
{"delete",functionsDeleteCommand,3,
"may-replicate no-script @scripting"},
{"kill",functionsKillCommand,2,
"no-script @scripting"},
{"info",functionsInfoCommand,-3,
"no-script @scripting"},
{"list",functionsListCommand,2,
"no-script @scripting"},
{"stats",functionsStatsCommand,2,
"no-script @scripting"},
{"help",functionsHelpCommand,2,
"ok-loading ok-stale @scripting"},
{NULL},
};
struct redisCommand clientSubcommands[] = {
{"caching",clientCommand,3,
"no-script ok-loading ok-stale @connection"},
{"getredir",clientCommand,2,
"no-script ok-loading ok-stale @connection"},
{"id",clientCommand,2,
"no-script ok-loading ok-stale @connection"},
{"info",clientCommand,2,
"no-script random ok-loading ok-stale @connection"},
{"kill",clientCommand,-3,
"admin no-script ok-loading ok-stale @connection"},
{"list",clientCommand,-2,
"admin no-script random ok-loading ok-stale @connection"},
{"unpause",clientCommand,2,
"admin no-script ok-loading ok-stale @connection"},
{"pause",clientCommand,-3,
"admin no-script ok-loading ok-stale @connection"},
{"reply",clientCommand,3,
"no-script ok-loading ok-stale @connection"},
{"setname",clientCommand,3,
"no-script ok-loading ok-stale @connection"},
{"getname",clientCommand,2,
"no-script ok-loading ok-stale @connection"},
{"unblock",clientCommand,-3,
"admin no-script ok-loading ok-stale @connection"},
{"tracking",clientCommand,-3,
"no-script ok-loading ok-stale @connection"},
{"trackinginfo",clientCommand,2,
"no-script ok-loading ok-stale @connection"},
{"no-evict",clientCommand,3,
"admin no-script ok-loading ok-stale @connection"},
{"help",clientCommand,2,
"ok-loading ok-stale @connection"},
{NULL},
};
struct redisCommand pubsubSubcommands[] = {
{"channels",pubsubCommand,-2,
"pub-sub ok-loading ok-stale"},
{"numpat",pubsubCommand,2,
"pub-sub ok-loading ok-stale"},
{"numsub",pubsubCommand,-2,
"pub-sub ok-loading ok-stale"},
{"help",pubsubCommand,2,
"ok-loading ok-stale"},
{NULL},
};
struct redisCommand clusterSubcommands[] = {
{"addslots",clusterCommand,-3,
"admin ok-stale random"},
{"addslotsrange",clusterCommand,-4,
"admin ok-stale random"},
{"bumpepoch",clusterCommand,2,
"admin ok-stale random"},
{"count-failure-reports",clusterCommand,3,
"admin ok-stale random"},
{"countkeysinslot",clusterCommand,3,
"ok-stale random"},
{"delslots",clusterCommand,-3,
"admin ok-stale random"},
{"delslotsrange",clusterCommand,-4,
"admin ok-stale random"},
{"failover",clusterCommand,-2,
"admin ok-stale random"},
{"forget",clusterCommand,3,
"admin ok-stale random"},
{"getkeysinslot",clusterCommand,4,
"ok-stale random"},
{"flushslots",clusterCommand,2,
"admin ok-stale random"},
{"info",clusterCommand,2,
"ok-stale random"},
{"keyslot",clusterCommand,3,
"ok-stale random"},
{"meet",clusterCommand,-4,
"admin ok-stale random"},
{"myid",clusterCommand,2,
"ok-stale random"},
{"nodes",clusterCommand,2,
"ok-stale random"},
{"replicate",clusterCommand,3,
"admin ok-stale random"},
{"reset",clusterCommand,3,
"admin ok-stale random"},
{"set-config-epoch",clusterCommand,3,
"admin ok-stale random"},
{"setslot",clusterCommand,-4,
"admin ok-stale random"},
{"replicas",clusterCommand,3,
"admin ok-stale random"},
{"saveconfig",clusterCommand,2,
"admin ok-stale random"},
{"slots",clusterCommand,2,
"ok-stale random"},
{"help",clusterCommand,2,
"ok-loading ok-stale"},
{NULL},
};
struct redisCommand sentinelSubcommands[] = {
{"ckquorum",sentinelCommand,3,
"admin only-sentinel"},
{"config",sentinelCommand,-3,
"admin only-sentinel"},
{"debug",sentinelCommand,-2,
"admin only-sentinel"},
{"get-master-addr-by-name",sentinelCommand,3,
"admin only-sentinel"},
{"failover",sentinelCommand,3,
"admin only-sentinel"},
{"flushconfig",sentinelCommand,2,
"admin only-sentinel"},
{"info-cache",sentinelCommand,3,
"admin only-sentinel"},
{"is-master-down-by-addr",sentinelCommand,6,
"admin only-sentinel"},
{"master",sentinelCommand,3,
"admin only-sentinel"},
{"masters",sentinelCommand,2,
"admin only-sentinel"},
{"monitor",sentinelCommand,6,
"admin only-sentinel"},
{"myid",sentinelCommand,2,
"admin only-sentinel"},
{"pending-scripts",sentinelCommand,2,
"admin only-sentinel"},
{"remove",sentinelCommand,3,
"admin only-sentinel"},
{"replicas",sentinelCommand,3,
"admin only-sentinel"},
{"reset",sentinelCommand,3,
"admin only-sentinel"},
{"sentinels",sentinelCommand,3,
"admin only-sentinel"},
{"set",sentinelCommand,-5,
"admin only-sentinel"},
{"simulate-failure",sentinelCommand,-3,
"admin only-sentinel"},
{"help",sentinelCommand,2,
"ok-loading ok-stale only-sentinel"},
{NULL},
};
struct redisCommand redisCommandTable[] = {
{"module",NULL,-2,
"",
.subcommands=moduleSubcommands},
{"get",getCommand,2,
"read-only fast @string",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"getex",getexCommand,-2,
"write fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"getdel",getdelCommand,2,
"write fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
/* Note that we can't flag set as fast, since it may perform an
* implicit DEL of a large key. */
{"set",setCommand,-3,
"write use-memory @string",
{{"read write", /* "read" because of the GET token */
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"setnx",setnxCommand,3,
"write use-memory fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"setex",setexCommand,4,
"write use-memory @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"psetex",psetexCommand,4,
"write use-memory @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"append",appendCommand,3,
"write use-memory fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"strlen",strlenCommand,2,
"read-only fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"del",delCommand,-2,
"write @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"unlink",unlinkCommand,-2,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"exists",existsCommand,-2,
"read-only fast @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"setbit",setbitCommand,4,
"write use-memory @bitmap",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"getbit",getbitCommand,3,
"read-only fast @bitmap",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"bitfield",bitfieldCommand,-2,
"write use-memory @bitmap",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"bitfield_ro",bitfieldroCommand,-2,
"read-only fast @bitmap",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"setrange",setrangeCommand,4,
"write use-memory @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"getrange",getrangeCommand,4,
"read-only @string",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"substr",getrangeCommand,4,
"read-only @string",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"incr",incrCommand,2,
"write use-memory fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"decr",decrCommand,2,
"write use-memory fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"mget",mgetCommand,-2,
"read-only fast @string",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"rpush",rpushCommand,-3,
"write use-memory fast @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lpush",lpushCommand,-3,
"write use-memory fast @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"rpushx",rpushxCommand,-3,
"write use-memory fast @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lpushx",lpushxCommand,-3,
"write use-memory fast @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"linsert",linsertCommand,5,
"write use-memory @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"rpop",rpopCommand,-2,
"write fast @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lpop",lpopCommand,-2,
"write fast @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lmpop",lmpopCommand,-4,
"write @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
lmpopGetKeys},
{"brpop",brpopCommand,-3,
"write no-script @list @blocking",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-2,1,0}}}},
{"brpoplpush",brpoplpushCommand,4,
"write use-memory no-script @list @blocking",
{{"read write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"blmove",blmoveCommand,6,
"write use-memory no-script @list @blocking",
{{"read write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"blpop",blpopCommand,-3,
"write no-script @list @blocking",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-2,1,0}}}},
{"blmpop",blmpopCommand,-5,
"write @list @blocking",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
blmpopGetKeys},
{"llen",llenCommand,2,
"read-only fast @list",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lindex",lindexCommand,3,
"read-only @list",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lset",lsetCommand,4,
"write use-memory @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lrange",lrangeCommand,4,
"read-only @list",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"ltrim",ltrimCommand,4,
"write @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lpos",lposCommand,-3,
"read-only @list",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lrem",lremCommand,4,
"write @list",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"rpoplpush",rpoplpushCommand,3,
"write use-memory @list",
{{"read write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"lmove",lmoveCommand,5,
"write use-memory @list",
{{"read write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"sadd",saddCommand,-3,
"write use-memory fast @set",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"srem",sremCommand,-3,
"write fast @set",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"smove",smoveCommand,4,
"write fast @set",
{{"read write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"sismember",sismemberCommand,3,
"read-only fast @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"smismember",smismemberCommand,-3,
"read-only fast @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"scard",scardCommand,2,
"read-only fast @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"spop",spopCommand,-2,
"write random fast @set",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"srandmember",srandmemberCommand,-2,
"read-only random @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"sinter",sinterCommand,-2,
"read-only to-sort @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"sintercard",sinterCardCommand,-3,
"read-only @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_KEYNUM,.fk.range={0,1,1}}},
sintercardGetKeys},
{"sinterstore",sinterstoreCommand,-3,
"write use-memory @set",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"sunion",sunionCommand,-2,
"read-only to-sort @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"sunionstore",sunionstoreCommand,-3,
"write use-memory @set",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"sdiff",sdiffCommand,-2,
"read-only to-sort @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"sdiffstore",sdiffstoreCommand,-3,
"write use-memory @set",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"smembers",sinterCommand,2,
"read-only to-sort @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"sscan",sscanCommand,-3,
"read-only random @set",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zadd",zaddCommand,-4,
"write use-memory fast @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zincrby",zincrbyCommand,4,
"write use-memory fast @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrem",zremCommand,-3,
"write fast @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zremrangebyscore",zremrangebyscoreCommand,4,
"write @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zremrangebyrank",zremrangebyrankCommand,4,
"write @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zremrangebylex",zremrangebylexCommand,4,
"write @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zunionstore",zunionstoreCommand,-4,
"write use-memory @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zunionInterDiffStoreGetKeys},
{"zinterstore",zinterstoreCommand,-4,
"write use-memory @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zunionInterDiffStoreGetKeys},
{"zdiffstore",zdiffstoreCommand,-4,
"write use-memory @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zunionInterDiffStoreGetKeys},
{"zunion",zunionCommand,-3,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zunionInterDiffGetKeys},
{"zinter",zinterCommand,-3,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zunionInterDiffGetKeys},
{"zintercard",zinterCardCommand,-3,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zunionInterDiffGetKeys},
{"zdiff",zdiffCommand,-3,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zunionInterDiffGetKeys},
{"zrange",zrangeCommand,-4,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrangestore",zrangestoreCommand,-5,
"write use-memory @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrangebyscore",zrangebyscoreCommand,-4,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrevrangebyscore",zrevrangebyscoreCommand,-4,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrangebylex",zrangebylexCommand,-4,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrevrangebylex",zrevrangebylexCommand,-4,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zcount",zcountCommand,4,
"read-only fast @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zlexcount",zlexcountCommand,4,
"read-only fast @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrevrange",zrevrangeCommand,-4,
"read-only @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zcard",zcardCommand,2,
"read-only fast @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zscore",zscoreCommand,3,
"read-only fast @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zmscore",zmscoreCommand,-3,
"read-only fast @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrank",zrankCommand,3,
"read-only fast @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zrevrank",zrevrankCommand,3,
"read-only fast @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zscan",zscanCommand,-3,
"read-only random @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zpopmin",zpopminCommand,-2,
"write fast @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zpopmax",zpopmaxCommand,-2,
"write fast @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"zmpop", zmpopCommand,-4,
"write @sortedset",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
zmpopGetKeys},
{"bzpopmin",bzpopminCommand,-3,
"write no-script fast @sortedset @blocking",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-2,1,0}}}},
{"bzpopmax",bzpopmaxCommand,-3,
"write no-script fast @sortedset @blocking",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-2,1,0}}}},
{"bzmpop",bzmpopCommand,-5,
"write @sortedset @blocking",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
blmpopGetKeys},
{"zrandmember",zrandmemberCommand,-2,
"read-only random @sortedset",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hset",hsetCommand,-4,
"write use-memory fast @hash",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hsetnx",hsetnxCommand,4,
"write use-memory fast @hash",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hget",hgetCommand,3,
"read-only fast @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hmset",hsetCommand,-4,
"write use-memory fast @hash",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hmget",hmgetCommand,-3,
"read-only fast @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hincrby",hincrbyCommand,4,
"write use-memory fast @hash",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hincrbyfloat",hincrbyfloatCommand,4,
"write use-memory fast @hash",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hdel",hdelCommand,-3,
"write fast @hash",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hlen",hlenCommand,2,
"read-only fast @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hstrlen",hstrlenCommand,3,
"read-only fast @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hkeys",hkeysCommand,2,
"read-only to-sort @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hvals",hvalsCommand,2,
"read-only to-sort @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hgetall",hgetallCommand,2,
"read-only random @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hexists",hexistsCommand,3,
"read-only fast @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hrandfield",hrandfieldCommand,-2,
"read-only random @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"hscan",hscanCommand,-3,
"read-only random @hash",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"incrby",incrbyCommand,3,
"write use-memory fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"decrby",decrbyCommand,3,
"write use-memory fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"incrbyfloat",incrbyfloatCommand,3,
"write use-memory fast @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"getset",getsetCommand,3,
"write use-memory fast @string",
{{"read write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"mset",msetCommand,-3,
"write use-memory @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,2,0}}}},
{"msetnx",msetnxCommand,-3,
"write use-memory @string",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,2,0}}}},
{"randomkey",randomkeyCommand,1,
"read-only random @keyspace"},
{"select",selectCommand,2,
"ok-loading fast ok-stale @connection"},
{"swapdb",swapdbCommand,3,
"write fast @keyspace @dangerous"},
{"move",moveCommand,3,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"copy",copyCommand,-3,
"write use-memory @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
/* Like for SET, we can't mark RENAME as a fast command because
* overwriting the target key may result in an implicit slow DEL. */
{"rename",renameCommand,3,
"write @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={1,1,0}}}},
{"renamenx",renamenxCommand,3,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={1,1,0}}}},
{"expire",expireCommand,-3,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"expireat",expireatCommand,-3,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"pexpire",pexpireCommand,-3,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"pexpireat",pexpireatCommand,-3,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"keys",keysCommand,2,
"read-only to-sort @keyspace @dangerous"},
{"scan",scanCommand,-2,
"read-only random @keyspace"},
{"dbsize",dbsizeCommand,1,
"read-only fast @keyspace"},
{"auth",authCommand,-2,
"no-auth no-script ok-loading ok-stale fast sentinel @connection"},
/* PING is used for Redis failure detection and availability check.
* So we return LOADING in case there's a synchronous replication in progress,
* MASTERDOWN when replica-serve-stale-data=no and link with MASTER is down,
* BUSY when blocked by a script, etc. */
{"ping",pingCommand,-1,
"fast sentinel @connection"},
{"sentinel",NULL,-2,
"admin only-sentinel",
.subcommands=sentinelSubcommands},
{"echo",echoCommand,2,
"fast @connection"},
{"save",saveCommand,1,
"admin no-script"},
{"bgsave",bgsaveCommand,-1,
"admin no-script"},
{"bgrewriteaof",bgrewriteaofCommand,1,
"admin no-script"},
{"shutdown",shutdownCommand,-1,
"admin no-script ok-loading ok-stale sentinel"},
{"lastsave",lastsaveCommand,1,
"random fast ok-loading ok-stale @admin @dangerous"},
{"type",typeCommand,2,
"read-only fast @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"multi",multiCommand,1,
"no-script fast ok-loading ok-stale @transaction"},
{"exec",execCommand,1,
"no-script no-slowlog ok-loading ok-stale @transaction"},
{"discard",discardCommand,1,
"no-script fast ok-loading ok-stale @transaction"},
{"sync",syncCommand,1,
"admin no-script"},
{"psync",syncCommand,-3,
"admin no-script"},
{"replconf",replconfCommand,-1,
"admin no-script ok-loading ok-stale"},
{"flushdb",flushdbCommand,-1,
"write @keyspace @dangerous"},
{"flushall",flushallCommand,-1,
"write @keyspace @dangerous"},
{"sort",sortCommand,-2,
"write use-memory @list @set @sortedset @dangerous",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write incomplete", /* We can't use "keyword" here because we may give false information. */
KSPEC_BS_UNKNOWN,{{0}},
KSPEC_FK_UNKNOWN,{{0}}}},
sortGetKeys},
{"sort_ro",sortroCommand,-2,
"read-only @list @set @sortedset @dangerous",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"info",infoCommand,-1,
"ok-loading ok-stale random sentinel @dangerous"},
{"monitor",monitorCommand,1,
"admin no-script ok-loading ok-stale"},
{"ttl",ttlCommand,2,
"read-only fast random @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"touch",touchCommand,-2,
"read-only fast @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"pttl",pttlCommand,2,
"read-only fast random @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"expiretime",expiretimeCommand,2,
"read-only fast random @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"pexpiretime",pexpiretimeCommand,2,
"read-only fast random @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"persist",persistCommand,2,
"write fast @keyspace",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"slaveof",replicaofCommand,3,
"admin no-script ok-stale"},
{"replicaof",replicaofCommand,3,
"admin no-script ok-stale"},
{"role",roleCommand,1,
"ok-loading ok-stale no-script fast sentinel @admin @dangerous"},
{"debug",debugCommand,-2,
"admin no-script ok-loading ok-stale"},
{"config",NULL,-2,
"",
.subcommands=configSubcommands},
{"subscribe",subscribeCommand,-2,
"pub-sub no-script ok-loading ok-stale sentinel"},
{"unsubscribe",unsubscribeCommand,-1,
"pub-sub no-script ok-loading ok-stale sentinel"},
{"psubscribe",psubscribeCommand,-2,
"pub-sub no-script ok-loading ok-stale sentinel"},
{"punsubscribe",punsubscribeCommand,-1,
"pub-sub no-script ok-loading ok-stale sentinel"},
{"publish",publishCommand,3,
"pub-sub ok-loading ok-stale fast may-replicate sentinel"},
{"pubsub",NULL,-2,
"",
.subcommands=pubsubSubcommands},
{"watch",watchCommand,-2,
"no-script fast ok-loading ok-stale @transaction",
{{"",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"unwatch",unwatchCommand,1,
"no-script fast ok-loading ok-stale @transaction"},
{"cluster",NULL,-2,
"",
.subcommands=clusterSubcommands},
{"restore",restoreCommand,-4,
"write use-memory @keyspace @dangerous",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"restore-asking",restoreCommand,-4,
"write use-memory cluster-asking @keyspace @dangerous",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"migrate",migrateCommand,-6,
"write random @keyspace @dangerous",
{{"write",
KSPEC_BS_INDEX,.bs.index={3},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write incomplete",
KSPEC_BS_KEYWORD,.bs.keyword={"KEYS",-2},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}},
migrateGetKeys},
{"asking",askingCommand,1,
"fast @connection"},
{"readonly",readonlyCommand,1,
"fast @connection"},
{"readwrite",readwriteCommand,1,
"fast @connection"},
{"dump",dumpCommand,2,
"read-only random @keyspace",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"object",NULL,-2,
"",
.subcommands=objectSubcommands},
{"memory",NULL,-2,
"",
.subcommands=memorySubcommands},
{"client",NULL,-2,
"sentinel",
.subcommands=clientSubcommands},
{"hello",helloCommand,-1,
"no-auth no-script fast ok-loading ok-stale sentinel @connection"},
/* EVAL can modify the dataset, however it is not flagged as a write
* command since we do the check while running commands from Lua.
*
* EVAL and EVALSHA also feed monitors before the commands are executed,
* as opposed to after.
*/
{"eval",evalCommand,-3,
"no-script no-monitor may-replicate no-mandatory-keys @scripting",
{{"read write", /* We pass both read and write because these flag are worst-case-scenario */
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
evalGetKeys},
{"eval_ro",evalRoCommand,-3,
"no-script no-monitor no-mandatory-keys @scripting",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
evalGetKeys},
{"evalsha",evalShaCommand,-3,
"no-script no-monitor may-replicate no-mandatory-keys @scripting",
{{"read write", /* We pass both read and write because these flag are worst-case-scenario */
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
evalGetKeys},
{"evalsha_ro",evalShaRoCommand,-3,
"no-script no-monitor no-mandatory-keys @scripting",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
evalGetKeys},
{"slowlog",NULL,-2,
"",
.subcommands=slowlogSubcommands},
{"script",NULL,-2,
"",
.subcommands=scriptSubcommands},
{"time",timeCommand,1,
"random fast ok-loading ok-stale"},
{"bitop",bitopCommand,-4,
"write use-memory @bitmap",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={3},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"bitcount",bitcountCommand,-2,
"read-only @bitmap",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"bitpos",bitposCommand,-3,
"read-only @bitmap",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"wait",waitCommand,3,
"no-script @connection"},
{"command",commandCommand,-1,
"ok-loading ok-stale random sentinel @connection",
.subcommands=commandSubcommands},
{"geoadd",geoaddCommand,-5,
"write use-memory @geo",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
/* GEORADIUS has store options that may write. */
{"georadius",georadiusCommand,-6,
"write use-memory @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_KEYWORD,.bs.keyword={"STORE",6},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_KEYWORD,.bs.keyword={"STOREDIST",6},
KSPEC_FK_RANGE,.fk.range={0,1,0}}},
georadiusGetKeys},
{"georadius_ro",georadiusroCommand,-6,
"read-only @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"georadiusbymember",georadiusbymemberCommand,-5,"write use-memory @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_KEYWORD,.bs.keyword={"STORE",5},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"write",
KSPEC_BS_KEYWORD,.bs.keyword={"STOREDIST",5},
KSPEC_FK_RANGE,.fk.range={0,1,0}}},
georadiusGetKeys},
{"georadiusbymember_ro",georadiusbymemberroCommand,-5,
"read-only @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"geohash",geohashCommand,-2,
"read-only @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"geopos",geoposCommand,-2,
"read-only @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"geodist",geodistCommand,-4,
"read-only @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"geosearch",geosearchCommand,-7,
"read-only @geo",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"geosearchstore",geosearchstoreCommand,-8,
"write use-memory @geo",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"pfselftest",pfselftestCommand,1,
"admin @hyperloglog"},
{"pfadd",pfaddCommand,-2,
"write use-memory fast @hyperloglog",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
/* Technically speaking PFCOUNT may change the key since it changes the
* final bytes in the HyperLogLog representation. However in this case
* we claim that the representation, even if accessible, is an internal
* affair, and the command is semantically read only. */
{"pfcount",pfcountCommand,-2,
"read-only may-replicate @hyperloglog",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
{"pfmerge",pfmergeCommand,-2,
"write use-memory @hyperloglog",
{{"read write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}},
{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={-1,1,0}}}},
/* Unlike PFCOUNT that is considered as a read-only command (although
* it changes a bit), PFDEBUG may change the entire key when converting
* from sparse to dense representation */
{"pfdebug",pfdebugCommand,-3,
"admin write use-memory @hyperloglog",
{{"write",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xadd",xaddCommand,-5,
"write use-memory fast random @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xrange",xrangeCommand,-4,
"read-only @stream",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xrevrange",xrevrangeCommand,-4,
"read-only @stream",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xlen",xlenCommand,2,
"read-only fast @stream",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xread",xreadCommand,-4,
"read-only @stream @blocking",
{{"read",
KSPEC_BS_KEYWORD,.bs.keyword={"STREAMS",1},
KSPEC_FK_RANGE,.fk.range={-1,1,2}}},
xreadGetKeys},
{"xreadgroup",xreadCommand,-7,
"write @stream @blocking",
{{"read",
KSPEC_BS_KEYWORD,.bs.keyword={"STREAMS",4},
KSPEC_FK_RANGE,.fk.range={-1,1,2}}},
xreadGetKeys},
{"xgroup",NULL,-2,
"",
.subcommands=xgroupSubcommands},
{"xsetid",xsetidCommand,3,
"write use-memory fast @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xack",xackCommand,-4,
"write fast random @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xpending",xpendingCommand,-3,
"read-only random @stream",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xclaim",xclaimCommand,-6,
"write random fast @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xautoclaim",xautoclaimCommand,-6,
"write random fast @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xinfo",NULL,-2,
"",
.subcommands=xinfoSubcommands},
{"xdel",xdelCommand,-3,
"write fast @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"xtrim",xtrimCommand,-4,
"write random @stream",
{{"write",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={0,1,0}}}},
{"latency",NULL,-2,
"",
.subcommands=latencySubcommands},
{"lolwut",lolwutCommand,-1,
"read-only fast"},
{"acl",NULL,-2,
"sentinel",
.subcommands=aclSubcommands},
{"lcs",lcsCommand,-3,
"read-only @string",
{{"read",
KSPEC_BS_INDEX,.bs.index={1},
KSPEC_FK_RANGE,.fk.range={1,1,0}}}},
{"quit",quitCommand,-1,
"no-auth no-script ok-stale ok-loading fast @connection"},
{"reset",resetCommand,1,
"no-auth no-script ok-stale ok-loading fast @connection"},
{"failover",failoverCommand,-1,
"admin no-script ok-stale"},
{"function",NULL,-2,
"",
.subcommands=functionSubcommands},
{"fcall",fcallCommand,-3,
"no-script no-monitor may-replicate no-mandatory-keys @scripting",
{{"read write", /* We pass both read and write because these flag are worst-case-scenario */
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
functionGetKeys},
{"fcall_ro",fcallCommandReadOnly,-3,
"no-script no-monitor no-mandatory-keys @scripting",
{{"read",
KSPEC_BS_INDEX,.bs.index={2},
KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},
functionGetKeys},
};
/*============================ Utility functions ============================ */
/* We use a private localtime implementation which is fork-safe. The logging
......@@ -4518,82 +2518,26 @@ void commandAddSubcommand(struct redisCommand *parent, struct redisCommand *subc
serverAssert(dictAdd(parent->subcommands_dict, sdsnew(subcommand->name), subcommand) == DICT_OK);
}
/* Set implicit ACl categories (see comment above the definition of
* struct redisCommand). */
void setImplictACLCategories(struct redisCommand *c) {
if (c->flags & CMD_WRITE)
c->acl_categories |= ACL_CATEGORY_WRITE;
if (c->flags & CMD_READONLY)
c->acl_categories |= ACL_CATEGORY_READ;
if (c->flags & CMD_ADMIN)
c->acl_categories |= ACL_CATEGORY_ADMIN|ACL_CATEGORY_DANGEROUS;
if (c->flags & CMD_PUBSUB)
c->acl_categories |= ACL_CATEGORY_PUBSUB;
if (c->flags & CMD_FAST)
c->acl_categories |= ACL_CATEGORY_FAST;
/* Parse the flags string description 'strflags' and set them to the
* command 'c'. Abort on error. */
void parseCommandFlags(struct redisCommand *c, char *strflags) {
int argc;
sds *argv;
/* Split the line into arguments for processing. */
argv = sdssplitargs(strflags,&argc);
if (argv == NULL)
serverPanic("Failed splitting strflags!");
for (int j = 0; j < argc; j++) {
char *flag = argv[j];
if (!strcasecmp(flag,"write")) {
c->flags |= CMD_WRITE|CMD_CATEGORY_WRITE;
} else if (!strcasecmp(flag,"read-only")) {
c->flags |= CMD_READONLY|CMD_CATEGORY_READ;
} else if (!strcasecmp(flag,"use-memory")) {
c->flags |= CMD_DENYOOM;
} else if (!strcasecmp(flag,"admin")) {
c->flags |= CMD_ADMIN|CMD_CATEGORY_ADMIN|CMD_CATEGORY_DANGEROUS;
} else if (!strcasecmp(flag,"pub-sub")) {
c->flags |= CMD_PUBSUB|CMD_CATEGORY_PUBSUB;
} else if (!strcasecmp(flag,"no-script")) {
c->flags |= CMD_NOSCRIPT;
} else if (!strcasecmp(flag,"random")) {
c->flags |= CMD_RANDOM;
} else if (!strcasecmp(flag,"to-sort")) {
c->flags |= CMD_SORT_FOR_SCRIPT;
} else if (!strcasecmp(flag,"ok-loading")) {
c->flags |= CMD_LOADING;
} else if (!strcasecmp(flag,"ok-stale")) {
c->flags |= CMD_STALE;
} else if (!strcasecmp(flag,"no-monitor")) {
c->flags |= CMD_SKIP_MONITOR;
} else if (!strcasecmp(flag,"no-slowlog")) {
c->flags |= CMD_SKIP_SLOWLOG;
} else if (!strcasecmp(flag,"cluster-asking")) {
c->flags |= CMD_ASKING;
} else if (!strcasecmp(flag,"fast")) {
c->flags |= CMD_FAST | CMD_CATEGORY_FAST;
} else if (!strcasecmp(flag,"no-auth")) {
c->flags |= CMD_NO_AUTH;
} else if (!strcasecmp(flag,"may-replicate")) {
c->flags |= CMD_MAY_REPLICATE;
} else if (!strcasecmp(flag,"sentinel")) {
c->flags |= CMD_SENTINEL;
} else if (!strcasecmp(flag,"only-sentinel")) {
c->flags |= CMD_SENTINEL; /* Obviously it's s sentinel command */
c->flags |= CMD_ONLY_SENTINEL;
} else if (!strcasecmp(flag,"no-mandatory-keys")) {
c->flags |= CMD_NO_MANDATORY_KEYS;
} else {
/* Parse ACL categories here if the flag name starts with @. */
uint64_t catflag;
if (flag[0] == '@' &&
(catflag = ACLGetCommandCategoryFlagByName(flag+1)) != 0)
{
c->flags |= catflag;
} else {
sdsfreesplitres(argv,argc);
serverPanic("Unsupported command flag %s", flag);
}
}
}
/* If it's not @fast is @slow in this binary world. */
if (!(c->flags & CMD_CATEGORY_FAST)) c->flags |= CMD_CATEGORY_SLOW;
sdsfreesplitres(argv,argc);
if (!(c->acl_categories & ACL_CATEGORY_FAST))
c->acl_categories |= ACL_CATEGORY_SLOW;
}
void populateCommandStructure(struct redisCommand *c) {
int argc;
sds *argv;
/* Redis commands don't need more args than STATIC_KEY_SPECS_NUM (Number of keys
* specs can be greater than STATIC_KEY_SPECS_NUM only for module commands) */
c->key_specs = c->key_specs_static;
......@@ -4601,28 +2545,8 @@ void populateCommandStructure(struct redisCommand *c) {
for (int i = 0; i < STATIC_KEY_SPECS_NUM; i++) {
if (c->key_specs[i].begin_search_type == KSPEC_BS_INVALID)
continue;
/* Split the line into arguments for processing. */
argv = sdssplitargs(c->key_specs[i].sflags,&argc);
if (argv == NULL)
serverPanic("Failed splitting key sflags!");
for (int j = 0; j < argc; j++) {
char *flag = argv[j];
if (!strcasecmp(flag,"write")) {
c->key_specs[i].flags |= CMD_KEY_WRITE;
} else if (!strcasecmp(flag,"read")) {
c->key_specs[i].flags |= CMD_KEY_READ;
} else if (!strcasecmp(flag,"incomplete")) {
c->key_specs[i].flags |= CMD_KEY_INCOMPLETE;
} else {
serverPanic("Unsupported key-arg flag %s", flag);
}
}
break;
c->key_specs_num++;
sdsfreesplitres(argv,argc);
}
populateCommandLegacyRangeSpec(c);
......@@ -4640,26 +2564,29 @@ void populateCommandStructure(struct redisCommand *c) {
/* Translate the command string flags description into an actual
* set of flags. */
parseCommandFlags(sub,sub->sflags);
setImplictACLCategories(sub);
populateCommandStructure(sub);
commandAddSubcommand(c,sub);
}
}
}
extern struct redisCommand redisCommandTable[];
/* Populates the Redis Command Table starting from the hard coded list
* we have on top of server.c file. */
void populateCommandTable(void) {
int j;
int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);
struct redisCommand *c;
for (j = 0;; j++) {
c = redisCommandTable + j;
if (c->name == NULL)
break;
for (j = 0; j < numcommands; j++) {
struct redisCommand *c = redisCommandTable+j;
int retval1, retval2;
/* Translate the command string flags description into an actual
* set of flags. */
parseCommandFlags(c,c->sflags);
setImplictACLCategories(c);
if (!(c->flags & CMD_SENTINEL) && server.sentinel_mode)
continue;
......@@ -5514,8 +3441,8 @@ int processCommand(client *c) {
!(c->cmd->proc == scriptCommand &&
c->argc == 2 &&
tolower(((char*)c->argv[1]->ptr)[0]) == 'k') &&
!(c->cmd->proc == functionsKillCommand) &&
!(c->cmd->proc == functionsStatsCommand))
!(c->cmd->proc == functionKillCommand) &&
!(c->cmd->proc == functionStatsCommand))
{
if (scriptIsEval()) {
rejectCommand(c, shared.slowevalerr);
......@@ -5790,6 +3717,7 @@ void addReplyFlagsForCommand(client *c, struct redisCommand *cmd) {
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_WRITE, "write");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_READONLY, "readonly");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_DENYOOM, "denyoom");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_MODULE, "module");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_ADMIN, "admin");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_PUBSUB, "pubsub");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_NOSCRIPT, "noscript");
......@@ -5803,6 +3731,7 @@ void addReplyFlagsForCommand(client *c, struct redisCommand *cmd) {
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_FAST, "fast");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_NO_AUTH, "no_auth");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_MAY_REPLICATE, "may_replicate");
flagcount += addReplyCommandFlag(c,cmd->flags,CMD_NO_MANDATORY_KEYS, "no_mandatory_keys");
/* "sentinel" and "only-sentinel" are hidden on purpose. */
if (cmd->movablekeys) {
addReplyStatus(c, "movablekeys");
......@@ -5811,6 +3740,14 @@ void addReplyFlagsForCommand(client *c, struct redisCommand *cmd) {
setDeferredSetLen(c, flaglen, flagcount);
}
void addReplyDocFlagsForCommand(client *c, struct redisCommand *cmd) {
int flagcount = 0;
void *flaglen = addReplyDeferredLen(c);
flagcount += addReplyCommandFlag(c,cmd->doc_flags,CMD_DOC_DEPRECATED, "deprecated");
flagcount += addReplyCommandFlag(c,cmd->doc_flags,CMD_DOC_SYSCMD, "syscmd");
setDeferredSetLen(c, flaglen, flagcount);
}
void addReplyFlagsForKeyArgs(client *c, uint64_t flags) {
int flagcount = 0;
void *flaglen = addReplyDeferredLen(c);
......@@ -5820,7 +3757,124 @@ void addReplyFlagsForKeyArgs(client *c, uint64_t flags) {
setDeferredSetLen(c, flaglen, flagcount);
}
void addReplyCommandKeyArgs(client *c, struct redisCommand *cmd) {
/* Must match redisCommandArgType */
const char *ARG_TYPE_STR[] = {
"string",
"integer",
"double",
"key",
"pattern",
"unix-time",
"pure-token",
"oneof",
"block",
};
void addReplyFlagsForArg(client *c, uint64_t flags) {
int flagcount = 0;
void *flaglen = addReplyDeferredLen(c);
flagcount += addReplyCommandFlag(c,flags,CMD_ARG_OPTIONAL, "optional");
flagcount += addReplyCommandFlag(c,flags,CMD_ARG_MULTIPLE, "multiple");
flagcount += addReplyCommandFlag(c,flags,CMD_ARG_MULTIPLE_TOKEN, "multiple-token");
setDeferredSetLen(c, flaglen, flagcount);
}
void addReplyCommandArgList(client *c, struct redisCommandArg *args) {
int j;
void *setreply = addReplyDeferredLen(c);
for (j = 0; args && args[j].name != NULL; j++) {
long maplen = 0;
void *mapreply = addReplyDeferredLen(c);
addReplyBulkCString(c, "name");
addReplyBulkCString(c, args[j].name);
maplen++;
addReplyBulkCString(c, "type");
addReplyBulkCString(c, ARG_TYPE_STR[args[j].type]);
maplen++;
if (args[j].type == ARG_TYPE_KEY) {
addReplyBulkCString(c, "key-spec-index");
addReplyLongLong(c, args[j].key_spec_index);
maplen++;
}
if (args[j].token) {
addReplyBulkCString(c, "token");
addReplyBulkCString(c, args[j].token);
maplen++;
}
if (args[j].summary) {
addReplyBulkCString(c, "summary");
addReplyBulkCString(c, args[j].summary);
maplen++;
}
if (args[j].since) {
addReplyBulkCString(c, "since");
addReplyBulkCString(c, args[j].since);
maplen++;
}
if (args[j].flags) {
addReplyBulkCString(c, "flags");
addReplyFlagsForArg(c, args[j].flags);
maplen++;
}
if (args[j].type == ARG_TYPE_ONEOF || args[j].type == ARG_TYPE_BLOCK) {
addReplyBulkCString(c, "arguments");
addReplyCommandArgList(c, args[j].subargs);
maplen++;
}
setDeferredMapLen(c, mapreply, maplen);
}
setDeferredSetLen(c, setreply, j);
}
/* Must match redisCommandRESP2Type */
const char *RESP2_TYPE_STR[] = {
"simple-string",
"error",
"integer",
"bulk-string",
"null-bulk-string",
"array",
"null-array",
};
/* Must match redisCommandRESP3Type */
const char *RESP3_TYPE_STR[] = {
"simple-string",
"error",
"integer",
"double",
"bulk-string",
"array",
"map",
"set",
"bool",
"null",
};
void addReplyCommandHistory(client *c, struct redisCommand *cmd) {
int j;
void *array = addReplyDeferredLen(c);
for (j = 0; cmd->history && cmd->history[j].since != NULL; j++) {
addReplyArrayLen(c, 2);
addReplyBulkCString(c, cmd->history[j].since);
addReplyBulkCString(c, cmd->history[j].changes);
}
setDeferredSetLen(c, array, j);
}
void addReplyCommandHints(client *c, struct redisCommand *cmd) {
int j;
void *array = addReplyDeferredLen(c);
for (j = 0; cmd->hints && cmd->hints[j] != NULL; j++) {
addReplyBulkCString(c, cmd->hints[j]);
}
setDeferredSetLen(c, array, j);
}
void addReplyCommandKeySpecs(client *c, struct redisCommand *cmd) {
addReplySetLen(c, cmd->key_specs_num);
for (int i = 0; i < cmd->key_specs_num; i++) {
addReplyMapLen(c, 3);
......@@ -5828,7 +3882,7 @@ void addReplyCommandKeyArgs(client *c, struct redisCommand *cmd) {
addReplyBulkCString(c, "flags");
addReplyFlagsForKeyArgs(c,cmd->key_specs[i].flags);
addReplyBulkCString(c, "begin_search");
addReplyBulkCString(c, "begin-search");
switch (cmd->key_specs[i].begin_search_type) {
case KSPEC_BS_UNKNOWN:
addReplyMapLen(c, 2);
......@@ -5861,10 +3915,10 @@ void addReplyCommandKeyArgs(client *c, struct redisCommand *cmd) {
addReplyLongLong(c, cmd->key_specs[i].bs.keyword.startfrom);
break;
default:
serverPanic("Invalid begin_search key spec type %d", cmd->key_specs[i].begin_search_type);
serverPanic("Invalid begin-search key spec type %d", cmd->key_specs[i].begin_search_type);
}
addReplyBulkCString(c, "find_keys");
addReplyBulkCString(c, "find-keys");
switch (cmd->key_specs[i].find_keys_type) {
case KSPEC_FK_UNKNOWN:
addReplyMapLen(c, 2);
......@@ -5903,7 +3957,7 @@ void addReplyCommandKeyArgs(client *c, struct redisCommand *cmd) {
addReplyLongLong(c, cmd->key_specs[i].fk.keynum.keystep);
break;
default:
serverPanic("Invalid begin_search key spec type %d", cmd->key_specs[i].begin_search_type);
serverPanic("Invalid find-keys key spec type %d", cmd->key_specs[i].begin_search_type);
}
}
}
......@@ -5926,6 +3980,28 @@ void addReplyCommandSubCommands(client *c, struct redisCommand *cmd) {
dictReleaseIterator(di);
}
/* Must match redisCommandGroup */
const char *COMMAND_GROUP_STR[] = {
"generic",
"string",
"list",
"set",
"sorted-set",
"hash",
"pubsub",
"transactions",
"connection",
"server",
"scripting",
"hyperloglog",
"cluster",
"sentinel",
"geo",
"stream",
"bitmap",
"module"
};
/* Output the representation of a Redis command. Used by the COMMAND command. */
void addReplyCommand(client *c, struct redisCommand *cmd) {
if (!cmd) {
......@@ -5939,17 +4015,73 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
lastkey += firstkey;
keystep = cmd->legacy_range_key_spec.fk.range.keystep;
}
/* We are adding: command name, arg count, flags, first, last, offset, categories, key args, subcommands */
addReplyArrayLen(c, 9);
/* We are adding: command name, arg count, flags, first, last, offset, categories, additional information (map) */
addReplyArrayLen(c, 8);
addReplyBulkCString(c, cmd->name);
addReplyLongLong(c, cmd->arity);
addReplyFlagsForCommand(c, cmd);
addReplyLongLong(c, firstkey);
addReplyLongLong(c, lastkey);
addReplyLongLong(c, keystep);
addReplyCommandCategories(c,cmd);
addReplyCommandKeyArgs(c,cmd);
addReplyCommandSubCommands(c,cmd);
addReplyCommandCategories(c, cmd);
long maplen = 0;
void *mapreply = addReplyDeferredLen(c);
addReplyBulkCString(c, "summary");
addReplyBulkCString(c, cmd->summary);
maplen++;
addReplyBulkCString(c, "since");
addReplyBulkCString(c, cmd->since);
maplen++;
addReplyBulkCString(c, "group");
addReplyBulkCString(c, COMMAND_GROUP_STR[cmd->group]);
maplen++;
if (cmd->complexity) {
addReplyBulkCString(c, "complexity");
addReplyBulkCString(c, cmd->complexity);
maplen++;
}
if (cmd->doc_flags) {
addReplyBulkCString(c, "doc-flags");
addReplyDocFlagsForCommand(c, cmd);
maplen++;
}
if (cmd->deprecated_since) {
addReplyBulkCString(c, "deprecated-since");
addReplyBulkCString(c, cmd->deprecated_since);
maplen++;
}
if (cmd->replaced_by) {
addReplyBulkCString(c, "replaced-by");
addReplyBulkCString(c, cmd->replaced_by);
maplen++;
}
if (cmd->history) {
addReplyBulkCString(c, "history");
addReplyCommandHistory(c, cmd);
maplen++;
}
if (cmd->hints) {
addReplyBulkCString(c, "hints");
addReplyCommandHints(c, cmd);
maplen++;
}
if (cmd->args) {
addReplyBulkCString(c, "arguments");
addReplyCommandArgList(c, cmd->args);
maplen++;
}
if (cmd->key_specs_num) {
addReplyBulkCString(c, "key-specs");
addReplyCommandKeySpecs(c, cmd);
maplen++;
}
if (cmd->subcommands_dict) {
addReplyBulkCString(c, "subcommands");
addReplyCommandSubCommands(c, cmd);
maplen++;
}
setDeferredMapLen(c, mapreply, maplen);
}
}
......@@ -6039,7 +4171,7 @@ int shouldFilterFromCommandList(struct redisCommand *cmd, commandListFilter *fil
uint64_t cat = filter->cache.u.aclcat;
if (cat == 0)
return 1; /* Invalid ACL category */
return (!(cmd->flags & cat));
return (!(cmd->acl_categories & cat));
break;
}
case (COMMAND_LIST_FILTER_PATTERN):
......
......@@ -181,62 +181,60 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define HASHTABLE_MIN_FILL 10 /* Minimal hash table fill 10% */
#define HASHTABLE_MAX_LOAD_FACTOR 1.618 /* Maximum hash table load factor. */
/* Command flags. Please check the command table defined in the server.c file
/* Command flags. Please check the definition of struct redisCommand in this file
* for more information about the meaning of every flag. */
#define CMD_WRITE (1ULL<<0) /* "write" flag */
#define CMD_READONLY (1ULL<<1) /* "read-only" flag */
#define CMD_DENYOOM (1ULL<<2) /* "use-memory" flag */
#define CMD_WRITE (1ULL<<0)
#define CMD_READONLY (1ULL<<1)
#define CMD_DENYOOM (1ULL<<2)
#define CMD_MODULE (1ULL<<3) /* Command exported by module. */
#define CMD_ADMIN (1ULL<<4) /* "admin" flag */
#define CMD_PUBSUB (1ULL<<5) /* "pub-sub" flag */
#define CMD_NOSCRIPT (1ULL<<6) /* "no-script" flag */
#define CMD_RANDOM (1ULL<<7) /* "random" flag */
#define CMD_SORT_FOR_SCRIPT (1ULL<<8) /* "to-sort" flag */
#define CMD_LOADING (1ULL<<9) /* "ok-loading" flag */
#define CMD_STALE (1ULL<<10) /* "ok-stale" flag */
#define CMD_SKIP_MONITOR (1ULL<<11) /* "no-monitor" flag */
#define CMD_SKIP_SLOWLOG (1ULL<<12) /* "no-slowlog" flag */
#define CMD_ASKING (1ULL<<13) /* "cluster-asking" flag */
#define CMD_FAST (1ULL<<14) /* "fast" flag */
#define CMD_NO_AUTH (1ULL<<15) /* "no-auth" flag */
#define CMD_MAY_REPLICATE (1ULL<<16) /* "may-replicate" flag */
/* Key argument flags. Please check the command table defined in the server.c file
* for more information about the meaning of every flag. */
#define CMD_KEY_WRITE (1ULL<<0) /* "write" flag */
#define CMD_KEY_READ (1ULL<<1) /* "read" flag */
#define CMD_KEY_INCOMPLETE (1ULL<<2) /* "incomplete" flag (meaning that the keyspec might not point out to all keys it should cover) */
#define CMD_ADMIN (1ULL<<4)
#define CMD_PUBSUB (1ULL<<5)
#define CMD_NOSCRIPT (1ULL<<6)
#define CMD_RANDOM (1ULL<<7)
#define CMD_SORT_FOR_SCRIPT (1ULL<<8)
#define CMD_LOADING (1ULL<<9)
#define CMD_STALE (1ULL<<10)
#define CMD_SKIP_MONITOR (1ULL<<11)
#define CMD_SKIP_SLOWLOG (1ULL<<12)
#define CMD_ASKING (1ULL<<13)
#define CMD_FAST (1ULL<<14)
#define CMD_NO_AUTH (1ULL<<15)
#define CMD_MAY_REPLICATE (1ULL<<16)
#define CMD_SENTINEL (1ULL<<17)
#define CMD_ONLY_SENTINEL (1ULL<<18)
#define CMD_NO_MANDATORY_KEYS (1ULL<<19)
/* Command flags used by the module system. */
#define CMD_MODULE_GETKEYS (1ULL<<17) /* Use the modules getkeys interface. */
#define CMD_MODULE_NO_CLUSTER (1ULL<<18) /* Deny on Redis Cluster. */
#define CMD_MODULE_GETKEYS (1ULL<<20) /* Use the modules getkeys interface. */
#define CMD_MODULE_NO_CLUSTER (1ULL<<21) /* Deny on Redis Cluster. */
/* Command flags that describe ACLs categories. */
#define CMD_CATEGORY_KEYSPACE (1ULL<<19)
#define CMD_CATEGORY_READ (1ULL<<20)
#define CMD_CATEGORY_WRITE (1ULL<<21)
#define CMD_CATEGORY_SET (1ULL<<22)
#define CMD_CATEGORY_SORTEDSET (1ULL<<23)
#define CMD_CATEGORY_LIST (1ULL<<24)
#define CMD_CATEGORY_HASH (1ULL<<25)
#define CMD_CATEGORY_STRING (1ULL<<26)
#define CMD_CATEGORY_BITMAP (1ULL<<27)
#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<28)
#define CMD_CATEGORY_GEO (1ULL<<29)
#define CMD_CATEGORY_STREAM (1ULL<<30)
#define CMD_CATEGORY_PUBSUB (1ULL<<31)
#define CMD_CATEGORY_ADMIN (1ULL<<32)
#define CMD_CATEGORY_FAST (1ULL<<33)
#define CMD_CATEGORY_SLOW (1ULL<<34)
#define CMD_CATEGORY_BLOCKING (1ULL<<35)
#define CMD_CATEGORY_DANGEROUS (1ULL<<36)
#define CMD_CATEGORY_CONNECTION (1ULL<<37)
#define CMD_CATEGORY_TRANSACTION (1ULL<<38)
#define CMD_CATEGORY_SCRIPTING (1ULL<<39)
#define CMD_SENTINEL (1ULL<<40) /* "sentinel" flag */
#define CMD_ONLY_SENTINEL (1ULL<<41) /* "only-sentinel" flag */
#define CMD_NO_MANDATORY_KEYS (1ULL<<42) /* "no-mandatory-keys" flag */
#define ACL_CATEGORY_KEYSPACE (1ULL<<0)
#define ACL_CATEGORY_READ (1ULL<<1)
#define ACL_CATEGORY_WRITE (1ULL<<2)
#define ACL_CATEGORY_SET (1ULL<<3)
#define ACL_CATEGORY_SORTEDSET (1ULL<<4)
#define ACL_CATEGORY_LIST (1ULL<<5)
#define ACL_CATEGORY_HASH (1ULL<<6)
#define ACL_CATEGORY_STRING (1ULL<<7)
#define ACL_CATEGORY_BITMAP (1ULL<<8)
#define ACL_CATEGORY_HYPERLOGLOG (1ULL<<9)
#define ACL_CATEGORY_GEO (1ULL<<10)
#define ACL_CATEGORY_STREAM (1ULL<<11)
#define ACL_CATEGORY_PUBSUB (1ULL<<12)
#define ACL_CATEGORY_ADMIN (1ULL<<13)
#define ACL_CATEGORY_FAST (1ULL<<14)
#define ACL_CATEGORY_SLOW (1ULL<<15)
#define ACL_CATEGORY_BLOCKING (1ULL<<16)
#define ACL_CATEGORY_DANGEROUS (1ULL<<17)
#define ACL_CATEGORY_CONNECTION (1ULL<<18)
#define ACL_CATEGORY_TRANSACTION (1ULL<<19)
#define ACL_CATEGORY_SCRIPTING (1ULL<<20)
/* Key argument flags. Please check the command table defined in the server.c file
* for more information about the meaning of every flag. */
#define CMD_KEY_WRITE (1ULL<<0)
#define CMD_KEY_READ (1ULL<<1)
#define CMD_KEY_INCOMPLETE (1ULL<<2) /* meaning that the keyspec might not point out to all keys it should cover */
/* AOF states */
#define AOF_OFF 0 /* AOF is off */
......@@ -250,6 +248,11 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
#define AOF_OPEN_ERR 3
#define AOF_FAILED 4
/* Command doc flags */
#define CMD_DOC_NONE 0
#define CMD_DOC_DEPRECATED (1<<0) /* Command is deprecated */
#define CMD_DOC_SYSCMD (1<<1) /* System (internal) command */
/* Client flags */
#define CLIENT_SLAVE (1<<0) /* This client is a replica */
#define CLIENT_MASTER (1<<1) /* This client is a master */
......@@ -1811,6 +1814,7 @@ typedef struct {
* 2. keynum: there's an arg that contains the number of key args somewhere before the keys themselves
*/
/* Must be synced with generate-command-code.py */
typedef enum {
KSPEC_BS_INVALID = 0, /* Must be 0 */
KSPEC_BS_UNKNOWN,
......@@ -1818,6 +1822,7 @@ typedef enum {
KSPEC_BS_KEYWORD
} kspec_bs_type;
/* Must be synced with generate-command-code.py */
typedef enum {
KSPEC_FK_INVALID = 0, /* Must be 0 */
KSPEC_FK_UNKNOWN,
......@@ -1827,7 +1832,7 @@ typedef enum {
typedef struct {
/* Declarative data */
const char *sflags;
uint64_t flags;
kspec_bs_type begin_search_type;
union {
struct {
......@@ -1868,31 +1873,209 @@ typedef struct {
int keystep;
} keynum;
} fk;
/* Runtime data */
uint64_t flags;
} keySpec;
/* Number of static key specs */
#define STATIC_KEY_SPECS_NUM 4
/* Must be synced with ARG_TYPE_STR and generate-command-code.py */
typedef enum {
ARG_TYPE_STRING,
ARG_TYPE_INTEGER,
ARG_TYPE_DOUBLE,
ARG_TYPE_KEY,
ARG_TYPE_PATTERN,
ARG_TYPE_UNIX_TIME,
ARG_TYPE_PURE_TOKEN,
ARG_TYPE_ONEOF, /* Has subargs */
ARG_TYPE_BLOCK /* Has subargs */
} redisCommandArgType;
#define CMD_ARG_NONE (0)
#define CMD_ARG_OPTIONAL (1<<0)
#define CMD_ARG_MULTIPLE (1<<1)
#define CMD_ARG_MULTIPLE_TOKEN (1<<2)
typedef struct redisCommandArg {
const char *name;
redisCommandArgType type;
int key_spec_index;
const char *token;
const char *summary;
const char *since;
int flags;
struct redisCommandArg *subargs;
} redisCommandArg;
/* Must be synced with RESP2_TYPE_STR and generate-command-code.py */
typedef enum {
RESP2_SIMPLE_STRING,
RESP2_ERROR,
RESP2_INTEGER,
RESP2_BULK_STRING,
RESP2_NULL_BULK_STRING,
RESP2_ARRAY,
RESP2_NULL_ARRAY,
} redisCommandRESP2Type;
/* Must be synced with RESP3_TYPE_STR and generate-command-code.py */
typedef enum {
RESP3_SIMPLE_STRING,
RESP3_ERROR,
RESP3_INTEGER,
RESP3_DOUBLE,
RESP3_BULK_STRING,
RESP3_ARRAY,
RESP3_MAP,
RESP3_SET,
RESP3_BOOL,
RESP3_NULL,
} redisCommandRESP3Type;
typedef struct {
const char *since;
const char *changes;
} commandHistory;
/* Must be synced with COMMAND_GROUP_STR and generate-command-code.py */
typedef enum {
COMMAND_GROUP_GENERIC,
COMMAND_GROUP_STRING,
COMMAND_GROUP_LIST,
COMMAND_GROUP_SET,
COMMAND_GROUP_SORTED_SET,
COMMAND_GROUP_HASH,
COMMAND_GROUP_PUBSUB,
COMMAND_GROUP_TRANSACTIONS,
COMMAND_GROUP_CONNECTION,
COMMAND_GROUP_SERVER,
COMMAND_GROUP_SCRIPTING,
COMMAND_GROUP_HYPERLOGLOG,
COMMAND_GROUP_CLUSTER,
COMMAND_GROUP_SENTINEL,
COMMAND_GROUP_GEO,
COMMAND_GROUP_STREAM,
COMMAND_GROUP_BITMAP,
COMMAND_GROUP_MODULE,
} redisCommandGroup;
typedef void redisCommandProc(client *c);
typedef int redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
/* Redis command structure.
*
* Note that the command table is in commands.c and it is auto-generated.
*
* This is the meaning of the flags:
*
* CMD_WRITE: Write command (may modify the key space).
*
* CMD_READONLY: Commands just reading from keys without changing the content.
* Note that commands that don't read from the keyspace such as
* TIME, SELECT, INFO, administrative commands, and connection
* or transaction related commands (multi, exec, discard, ...)
* are not flagged as read-only commands, since they affect the
* server or the connection in other ways.
*
* CMD_DENYOOM: May increase memory usage once called. Don't allow if out
* of memory.
*
* CMD_ADMIN: Administrative command, like SAVE or SHUTDOWN.
*
* CMD_PUBSUB: Pub/Sub related command.
*
* CMD_NOSCRIPT: Command not allowed in scripts.
*
* CMD_RANDOM: Random command. Command is not deterministic, that is, the same
* command with the same arguments, with the same key space, may
* have different results. For instance SPOP and RANDOMKEY are
* two random commands.
*
* CMD_SORT_FOR_SCRIPT: Sort command output array if called from script, so that the
* output is deterministic. When this flag is used (not always
* possible), then the "random" flag is not needed.
*
* CMD_LOADING: Allow the command while loading the database.
*
* CMD_STALE: Allow the command while a slave has stale data but is not
* allowed to serve this data. Normally no command is accepted
* in this condition but just a few.
*
* CMD_SKIP_MONITOR: Do not automatically propagate the command on MONITOR.
*
* CMD_SKIP_SLOWLOG: Do not automatically propagate the command to the slowlog.
*
* CMD_ASKING: Perform an implicit ASKING for this command, so the
* command will be accepted in cluster mode if the slot is marked
* as 'importing'.
*
* CMD_FAST: Fast command: O(1) or O(log(N)) command that should never
* delay its execution as long as the kernel scheduler is giving
* us time. Note that commands that may trigger a DEL as a side
* effect (like SET) are not fast commands.
*
* CMD_NO_AUTH: Command doesn't require authentication
*
* CMD_MAY_REPLICATE: Command may produce replication traffic, but should be
* allowed under circumstances where write commands are disallowed.
* Examples include PUBLISH, which replicates pubsub messages,and
* EVAL, which may execute write commands, which are replicated,
* or may just execute read commands. A command can not be marked
* both CMD_WRITE and CMD_MAY_REPLICATE
*
* CMD_SENTINEL: This command is present in sentinel mode too.
*
* CMD_SENTINEL_ONLY: This command is present only when in sentinel mode.
*
* CMD_NO_MANDATORY_KEYS: This key arguments for this command are optional.
*
* The following additional flags are only used in order to put commands
* in a specific ACL category. Commands can have multiple ACL categories.
* See redis.conf for the exact meaning of each.
*
* @keyspace, @read, @write, @set, @sortedset, @list, @hash, @string, @bitmap,
* @hyperloglog, @stream, @admin, @fast, @slow, @pubsub, @blocking, @dangerous,
* @connection, @transaction, @scripting, @geo.
*
* Note that:
*
* 1) The read-only flag implies the @read ACL category.
* 2) The write flag implies the @write ACL category.
* 3) The fast flag implies the @fast ACL category.
* 4) The admin flag implies the @admin and @dangerous ACL category.
* 5) The pub-sub flag implies the @pubsub ACL category.
* 6) The lack of fast flag implies the @slow ACL category.
* 7) The non obvious "keyspace" category includes the commands
* that interact with keys without having anything to do with
* specific data structures, such as: DEL, RENAME, MOVE, SELECT,
* TYPE, EXPIRE*, PEXPIRE*, TTL, PTTL, ...
*/
struct redisCommand {
/* Declarative data */
char *name;
redisCommandProc *proc;
int arity;
char *sflags; /* Flags as string representation, one char per flag. */
keySpec key_specs_static[STATIC_KEY_SPECS_NUM];
const char *name; /* A string representing the command name. */
const char *summary; /* Summary of the command (optional). */
const char *complexity; /* Complexity description (optional). */
const char *since; /* Debut version of the command (optional). */
int doc_flags; /* Flags for documentation (see CMD_DOC_*). */
const char *replaced_by; /* In case the command is deprecated, this is the successor command. */
const char *deprecated_since; /* In case the command is deprecated, when did it happen? */
redisCommandGroup group; /* Command group */
commandHistory *history; /* History of the command */
const char **hints; /* An array of strings that are meant o be hints for clients/proxies regarding this command */
redisCommandProc *proc; /* Command implementation */
int arity; /* Number of arguments, it is possible to use -N to say >= N */
uint64_t flags; /* Command flags, see CMD_*. */
uint64_t acl_categories; /* ACl categories, see ACL_CATEGORY_*. */
keySpec key_specs_static[STATIC_KEY_SPECS_NUM]; /* Key specs. See keySpec */
/* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect (may be NULL) */
redisGetKeysProc *getkeys_proc;
/* Array of subcommands (may be NULL) */
struct redisCommand *subcommands;
/* Array of arguments (may be NULL) */
struct redisCommandArg *args;
/* Runtime data */
uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
/* What keys should be loaded in background when calling this command? */
long long microseconds, calls, rejected_calls, failed_calls;
int id; /* Command ID. This is a progressive ID starting from 0 that
......@@ -2825,6 +3008,7 @@ void saveCommand(client *c);
void bgsaveCommand(client *c);
void bgrewriteaofCommand(client *c);
void shutdownCommand(client *c);
void slowlogCommand(client *c);
void moveCommand(client *c);
void copyCommand(client *c);
void renameCommand(client *c);
......@@ -2976,6 +3160,15 @@ void evalRoCommand(client *c);
void evalShaCommand(client *c);
void evalShaRoCommand(client *c);
void scriptCommand(client *c);
void fcallCommand(client *c);
void fcallroCommand(client *c);
void functionCreateCommand(client *c);
void functionDeleteCommand(client *c);
void functionKillCommand(client *c);
void functionStatsCommand(client *c);
void functionInfoCommand(client *c);
void functionListCommand(client *c);
void functionHelpCommand(client *c);
void timeCommand(client *c);
void bitopCommand(client *c);
void bitcountCommand(client *c);
......
......@@ -48,7 +48,4 @@ typedef struct slowlogEntry {
void slowlogInit(void);
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration);
/* Exported commands */
void slowlogCommand(client *c);
#endif /* __SLOWLOG_H__ */
......@@ -52,7 +52,6 @@ TEST_MODULES = \
subcommands.so \
reply.so
.PHONY: all
all: $(TEST_MODULES)
......
......@@ -35,76 +35,79 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
/* Test legacy range "gluing" */
if (RedisModule_CreateCommand(ctx,"kspec.legacy",kspec_legacy,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *legacy = RedisModule_GetCommand(ctx,"kspec.legacy");
if (RedisModule_AddCommandKeySpec(ctx,"kspec.legacy","read",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(legacy,"read",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"kspec.legacy",spec_id,1) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchIndex(legacy,spec_id,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.legacy",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(legacy,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"kspec.legacy","write",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(legacy,"write",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"kspec.legacy",spec_id,2) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchIndex(legacy,spec_id,2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.legacy",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(legacy,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* First is legacy, rest are new specs */
if (RedisModule_CreateCommand(ctx,"kspec.complex1",kspec_complex1,"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *complex1 = RedisModule_GetCommand(ctx,"kspec.complex1");
if (RedisModule_AddCommandKeySpec(ctx,"kspec.complex1","write",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(complex1,"write",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(ctx,"kspec.complex1",spec_id,"STORE",2) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex1,spec_id,"STORE",2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.complex1",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(complex1,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"kspec.complex1","read",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(complex1,"read",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(ctx,"kspec.complex1",spec_id,"KEYS",2) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex1,spec_id,"KEYS",2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysKeynum(ctx,"kspec.complex1",spec_id,0,1,1) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysKeynum(complex1,spec_id,0,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* First is not legacy, more than STATIC_KEYS_SPECS_NUM specs */
if (RedisModule_CreateCommand(ctx,"kspec.complex2",kspec_complex2,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *complex2 = RedisModule_GetCommand(ctx,"kspec.complex2");
if (RedisModule_AddCommandKeySpec(ctx,"kspec.complex2","write",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(complex2,"write",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(ctx,"kspec.complex2",spec_id,"STORE",5) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex2,spec_id,"STORE",5) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.complex2",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"kspec.complex2","read",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(complex2,"read",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"kspec.complex2",spec_id,1) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.complex2",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"kspec.complex2","read",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(complex2,"read",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"kspec.complex2",spec_id,2) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.complex2",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"kspec.complex2","write",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(complex2,"write",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"kspec.complex2",spec_id,3) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,3) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysKeynum(ctx,"kspec.complex2",spec_id,0,1,1) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysKeynum(complex2,spec_id,0,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"kspec.complex2","write",&spec_id) == REDISMODULE_ERR)
if (RedisModule_AddCommandKeySpec(complex2,"write",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(ctx,"kspec.complex2",spec_id,"MOREKEYS",5) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex2,spec_id,"MOREKEYS",5) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"kspec.complex2",spec_id,-1,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,-1,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
......
......@@ -27,29 +27,37 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateCommand(ctx,"subcommands.bitarray",NULL,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *parent = RedisModule_GetCommand(ctx,"subcommands.bitarray");
if (RedisModule_CreateSubcommand(ctx,"subcommands.bitarray","set",cmd_set,"",0,0,0) == REDISMODULE_ERR)
if (RedisModule_CreateSubcommand(parent,"set",cmd_set,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"subcommands.bitarray|set","write",&spec_id) == REDISMODULE_ERR)
RedisModuleCommand *subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|set");
if (RedisModule_AddCommandKeySpec(subcmd,"write",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"subcommands.bitarray|set",spec_id,1) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchIndex(subcmd,spec_id,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"subcommands.bitarray|set",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateSubcommand(ctx,"subcommands.bitarray","get",cmd_get,"",0,0,0) == REDISMODULE_ERR)
if (RedisModule_CreateSubcommand(parent,"get",cmd_get,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(ctx,"subcommands.bitarray|get","read",&spec_id) == REDISMODULE_ERR)
subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|get");
if (RedisModule_AddCommandKeySpec(subcmd,"read",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchIndex(ctx,"subcommands.bitarray|get",spec_id,1) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecBeginSearchIndex(subcmd,spec_id,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(ctx,"subcommands.bitarray|get",spec_id,0,1,0) == REDISMODULE_ERR)
if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* Sanity */
RedisModule_Assert(RedisModule_CreateSubcommand(ctx,"bitarray","get",NULL,"",0,0,0) == REDISMODULE_ERR);
RedisModule_Assert(RedisModule_CreateSubcommand(ctx,"subcommands.bitarray","get",NULL,"",0,0,0) == REDISMODULE_ERR);
RedisModule_Assert(RedisModule_CreateSubcommand(ctx,"subcommands.bitarray|get","get",NULL,"",0,0,0) == REDISMODULE_ERR);
/* Trying to create the same subcommand fails */
RedisModule_Assert(RedisModule_CreateSubcommand(parent,"get",NULL,"",0,0,0) == REDISMODULE_ERR);
/* Trying to create a sub-subcommand fails */
RedisModule_Assert(RedisModule_CreateSubcommand(subcmd,"get",NULL,"",0,0,0) == REDISMODULE_ERR);
return REDISMODULE_OK;
}
......@@ -6,7 +6,7 @@ start_server {tags {"modules"}} {
test {COMMAND INFO correctly reports a movable keys module command} {
set info [lindex [r command info getkeys.command] 0]
assert_equal {movablekeys} [lindex $info 2]
assert_equal {module movablekeys} [lindex $info 2]
assert_equal {0} [lindex $info 3]
assert_equal {0} [lindex $info 4]
assert_equal {0} [lindex $info 5]
......
......@@ -5,17 +5,57 @@ start_server {tags {"modules"}} {
test "Module key specs: Legacy" {
set reply [r command info kspec.legacy]
assert_equal $reply {{kspec.legacy -1 {} 1 2 1 {} {{flags read begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} {flags write begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}}
# Verify (first, last, step)
assert_equal [lindex [lindex $reply 0] 3] 1
assert_equal [lindex [lindex $reply 0] 4] 2
assert_equal [lindex [lindex $reply 0] 5] 1
# create a dict for easy lookup
unset -nocomplain mydict
foreach {k v} [lindex [lindex $reply 0] 7] {
dict append mydict $k $v
}
# Verify key-specs
set keyspecs [dict get $mydict key-specs]
assert_equal [lindex $keyspecs 0] {flags read begin-search {type index spec {index 1}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 1] {flags write begin-search {type index spec {index 2}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}
}
test "Module key specs: Complex specs, case 1" {
set reply [r command info kspec.complex1]
assert_equal $reply {{kspec.complex1 -1 movablekeys 1 1 1 {} {{flags {} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} {flags write begin_search {type keyword spec {keyword STORE startfrom 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} {flags read begin_search {type keyword spec {keyword KEYS startfrom 2}} find_keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}}} {}}}
# Verify (first, last, step)
assert_equal [lindex [lindex $reply 0] 3] 1
assert_equal [lindex [lindex $reply 0] 4] 1
assert_equal [lindex [lindex $reply 0] 5] 1
# create a dict for easy lookup
unset -nocomplain mydict
foreach {k v} [lindex [lindex $reply 0] 7] {
dict append mydict $k $v
}
# Verify key-specs
set keyspecs [dict get $mydict key-specs]
assert_equal [lindex $keyspecs 0] {flags {} begin-search {type index spec {index 1}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 1] {flags write begin-search {type keyword spec {keyword STORE startfrom 2}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 2] {flags read begin-search {type keyword spec {keyword KEYS startfrom 2}} find-keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}}
}
test "Module key specs: Complex specs, case 2" {
set reply [r command info kspec.complex2]
assert_equal $reply {{kspec.complex2 -1 movablekeys 1 2 1 {} {{flags write begin_search {type keyword spec {keyword STORE startfrom 5}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} {flags read begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} {flags read begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} {flags write begin_search {type index spec {index 3}} find_keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}} {flags write begin_search {type keyword spec {keyword MOREKEYS startfrom 5}} find_keys {type range spec {lastkey -1 keystep 1 limit 0}}}} {}}}
# Verify (first, last, step)
assert_equal [lindex [lindex $reply 0] 3] 1
assert_equal [lindex [lindex $reply 0] 4] 2
assert_equal [lindex [lindex $reply 0] 5] 1
# create a dict for easy lookup
unset -nocomplain mydict
foreach {k v} [lindex [lindex $reply 0] 7] {
dict append mydict $k $v
}
# Verify key-specs
set keyspecs [dict get $mydict key-specs]
assert_equal [lindex $keyspecs 0] {flags write begin-search {type keyword spec {keyword STORE startfrom 5}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 1] {flags read begin-search {type index spec {index 1}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 2] {flags read begin-search {type index spec {index 2}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 3] {flags write begin-search {type index spec {index 3}} find-keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}}
assert_equal [lindex $keyspecs 4] {flags write begin-search {type keyword spec {keyword MOREKEYS startfrom 5}} find-keys {type range spec {lastkey -1 keystep 1 limit 0}}}
}
test "Module command list filtering" {
......
......@@ -4,9 +4,16 @@ start_server {tags {"modules"}} {
r module load $testmodule
test "Module subcommands via COMMAND" {
# Verify that module subcommands are displayed correctly in COMMAND
set reply [r command info subcommands.bitarray]
set subcmds [lindex [lindex $reply 0] 8]
assert_equal [lsort $subcmds] {{get -2 {} 1 1 1 {} {{flags read begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}} {set -2 {} 1 1 1 {} {{flags write begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}}
# create a dict for easy lookup
unset -nocomplain mydict
foreach {k v} [lindex [lindex $reply 0] 7] {
dict append mydict $k $v
}
set subcmds [lsort [dict get $mydict subcommands]]
assert_equal [lindex $subcmds 0] {get -2 module 1 1 1 {} {summary {} since {} group module key-specs {{flags read begin-search {type index spec {index 1}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}}}}
assert_equal [lindex $subcmds 1] {set -2 module 1 1 1 {} {summary {} since {} group module key-specs {{flags write begin-search {type index spec {index 1}} find-keys {type range spec {lastkey 0 keystep 1 limit 0}}}}}}
}
test "Module pure-container command fails on arity error" {
......
#!/usr/bin/env python
import os
import glob
import json
# Note: This script should be run from the src/ dir: ../utils/generate-command-code.py
ARG_TYPES = {
"string": "ARG_TYPE_STRING",
"integer": "ARG_TYPE_INTEGER",
"double": "ARG_TYPE_DOUBLE",
"key": "ARG_TYPE_KEY",
"pattern": "ARG_TYPE_PATTERN",
"unix-time": "ARG_TYPE_UNIX_TIME",
"pure-token": "ARG_TYPE_PURE_TOKEN",
"oneof": "ARG_TYPE_ONEOF",
"block": "ARG_TYPE_BLOCK",
}
GROUPS = {
"generic": "COMMAND_GROUP_GENERIC",
"string": "COMMAND_GROUP_STRING",
"list": "COMMAND_GROUP_LIST",
"set": "COMMAND_GROUP_SET",
"sorted_set": "COMMAND_GROUP_SORTED_SET",
"hash": "COMMAND_GROUP_HASH",
"pubsub": "COMMAND_GROUP_PUBSUB",
"transactions": "COMMAND_GROUP_TRANSACTIONS",
"connection": "COMMAND_GROUP_CONNECTION",
"server": "COMMAND_GROUP_SERVER",
"scripting": "COMMAND_GROUP_SCRIPTING",
"hyperloglog": "COMMAND_GROUP_HYPERLOGLOG",
"cluster": "COMMAND_GROUP_CLUSTER",
"sentinel": "COMMAND_GROUP_SENTINEL",
"geo": "COMMAND_GROUP_GEO",
"stream": "COMMAND_GROUP_STREAM",
"bitmap": "COMMAND_GROUP_BITMAP",
}
RESP2_TYPES = {
"simple-string": "RESP2_SIMPLE_STRING",
"error": "RESP2_ERROR",
"integer": "RESP2_INTEGER",
"bulk-string": "RESP2_BULK_STRING",
"null-bulk-string": "RESP2_NULL_BULK_STRING",
"array": "RESP2_ARRAY",
"null-array": "RESP2_NULL_ARRAY",
}
RESP3_TYPES = {
"simple-string": "RESP3_SIMPLE_STRING",
"error": "RESP3_ERROR",
"integer": "RESP3_INTEGER",
"double": "RESP3_DOUBLE",
"bulk-string": "RESP3_BULK_STRING",
"array": "RESP3_ARRAY",
"map": "RESP3_MAP",
"set": "RESP3_SET",
"bool": "RESP3_BOOL",
"null": "RESP3_NULL",
}
def get_optional_desc_string(desc, field, force_uppercase=False):
v = desc.get(field, None)
if v and force_uppercase:
v = v.upper()
ret = "\"%s\"" % v if v else "NULL"
return ret.replace("\n", "\\n")
# Globals
subcommands = {} # container_name -> dict(subcommand_name -> Subcommand) - Only subcommands
commands = {} # command_name -> Command - Only commands
class KeySpec(object):
def __init__(self, spec):
self.spec = spec
def struct_code(self):
def _flags_code():
s = ""
for flag in self.spec.get("flags", []):
s += "CMD_KEY_%s|" % flag
return s[:-1] if s else 0
def _begin_search_code():
if self.spec["begin_search"].get("index"):
return "KSPEC_BS_INDEX,.bs.index={%d}" % (
self.spec["begin_search"]["index"]["pos"]
)
elif self.spec["begin_search"].get("keyword"):
return "KSPEC_BS_KEYWORD,.bs.keyword={\"%s\",%d}" % (
self.spec["begin_search"]["keyword"]["keyword"],
self.spec["begin_search"]["keyword"]["startfrom"],
)
elif "unknown" in self.spec["begin_search"]:
return "KSPEC_BS_UNKNOWN,{{0}}"
else:
print("Invalid begin_search! value=%s" % self.spec["begin_search"])
exit(1)
def _find_keys_code():
if self.spec["find_keys"].get("range"):
return "KSPEC_FK_RANGE,.fk.range={%d,%d,%d}" % (
self.spec["find_keys"]["range"]["lastkey"],
self.spec["find_keys"]["range"]["step"],
self.spec["find_keys"]["range"]["limit"]
)
elif self.spec["find_keys"].get("keynum"):
return "KSPEC_FK_KEYNUM,.fk.keynum={%d,%d,%d}" % (
self.spec["find_keys"]["keynum"]["keynumidx"],
self.spec["find_keys"]["keynum"]["firstkey"],
self.spec["find_keys"]["keynum"]["step"]
)
elif "unknown" in self.spec["find_keys"]:
return "KSPEC_FK_UNKNOWN,{{0}}"
else:
print("Invalid find_keys! value=%s" % self.spec["find_keys"])
exit(1)
return "%s,%s,%s" % (
_flags_code(),
_begin_search_code(),
_find_keys_code()
)
class Argument(object):
def __init__(self, parent_name, desc):
self.desc = desc
self.name = self.desc["name"].lower()
self.type = self.desc["type"]
self.parent_name = parent_name
self.subargs = []
self.subargs_name = None
if self.type in ["oneof", "block"]:
for subdesc in self.desc["arguments"]:
self.subargs.append(Argument(self.fullname(), subdesc))
def fullname(self):
return ("%s %s" % (self.parent_name, self.name)).replace("-", "_")
def struct_name(self):
return "%s_Arg" % (self.fullname().replace(" ", "_"))
def subarg_table_name(self):
assert self.subargs
return "%s_Subargs" % (self.fullname().replace(" ", "_"))
def struct_code(self):
"""
Output example:
"expiration",ARG_TYPE_ONEOF,NULL,NULL,NULL,CMD_ARG_OPTIONAL,.value.subargs=SET_expiration_Subargs
"""
def _flags_code():
s = ""
if self.desc.get("optional", False):
s += "CMD_ARG_OPTIONAL|"
if self.desc.get("multiple", False):
s += "CMD_ARG_MULTIPLE|"
if self.desc.get("multiple_token", False):
assert self.desc.get("multiple", False) # Sanity
s += "CMD_ARG_MULTIPLE_TOKEN|"
return s[:-1] if s else "CMD_ARG_NONE"
s = "\"%s\",%s,%d,%s,%s,%s,%s" % (
self.name,
ARG_TYPES[self.type],
self.desc.get("key_spec_index", -1),
get_optional_desc_string(self.desc, "token", force_uppercase=True),
get_optional_desc_string(self.desc, "summary"),
get_optional_desc_string(self.desc, "since"),
_flags_code(),
)
if self.subargs:
s += ",.subargs=%s" % self.subarg_table_name()
return s
def write_internal_structs(self, f):
if self.subargs:
for subarg in self.subargs:
subarg.write_internal_structs(f)
f.write("/* %s argument table */\n" % self.fullname())
f.write("struct redisCommandArg %s[] = {\n" % self.subarg_table_name())
for subarg in self.subargs:
f.write("{%s},\n" % subarg.struct_code())
f.write("{0}\n")
f.write("};\n\n")
class Command(object):
def __init__(self, name, desc):
self.name = name.upper()
self.desc = desc
self.group = self.desc["group"]
self.subcommands = []
self.args = []
for arg_desc in self.desc.get("arguments", []):
self.args.append(Argument(self.fullname(), arg_desc))
def fullname(self):
return self.name.replace("-", "_").replace(":", "")
def return_types_table_name(self):
return "%s_ReturnInfo" % self.fullname().replace(" ", "_")
def subcommand_table_name(self):
assert self.subcommands
return "%s_Subcommands" % self.name
def history_table_name(self):
return "%s_History" % (self.fullname().replace(" ", "_"))
def hints_table_name(self):
return "%s_Hints" % (self.fullname().replace(" ", "_"))
def arg_table_name(self):
return "%s_Args" % (self.fullname().replace(" ", "_"))
def struct_name(self):
return "%s_Command" % (self.fullname().replace(" ", "_"))
def history_code(self):
if not self.desc.get("history"):
return ""
s = ""
for tupl in self.desc["history"]:
s += "{\"%s\",\"%s\"},\n" % (tupl[0], tupl[1])
s += "{0}"
return s
def hints_code(self):
if not self.desc.get("hints"):
return ""
s = ""
for hint in self.desc["hints"].split(' '):
s += "\"%s\",\n" % hint
s += "NULL"
return s
def struct_code(self):
"""
Output example:
"set","Set the string value of a key","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_STRING,SET_History,SET_Hints,setCommand,-3,"write denyoom @string",{{"write read",KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=SET_Args
"""
def _flags_code():
s = ""
for flag in self.desc.get("command_flags", []):
s += "CMD_%s|" % flag
return s[:-1] if s else 0
def _acl_categories_code():
s = ""
for cat in self.desc.get("acl_categories", []):
s += "ACL_CATEGORY_%s|" % cat
return s[:-1] if s else 0
def _doc_flags_code():
s = ""
for flag in self.desc.get("doc_flags", []):
s += "CMD_DOC_%s|" % flag
return s[:-1] if s else "CMD_DOC_NONE"
def _key_specs_code():
s = ""
for spec in self.desc.get("key_specs", []):
s += "{%s}," % KeySpec(spec).struct_code()
return s[:-1]
s = "\"%s\",%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%s,%s," % (
self.name.lower(),
get_optional_desc_string(self.desc, "summary"),
get_optional_desc_string(self.desc, "complexity"),
get_optional_desc_string(self.desc, "since"),
_doc_flags_code(),
get_optional_desc_string(self.desc, "replaced_by"),
get_optional_desc_string(self.desc, "deprecated_since"),
GROUPS[self.group],
self.history_table_name(),
self.hints_table_name(),
self.desc.get("function", "NULL"),
self.desc["arity"],
_flags_code(),
_acl_categories_code()
)
specs = _key_specs_code()
if specs:
s += "{%s}," % specs
if self.desc.get("get_keys_function"):
s += "%s," % self.desc["get_keys_function"]
if self.subcommands:
s += ".subcommands=%s," % self.subcommand_table_name()
if self.args:
s += ".args=%s," % self.arg_table_name()
return s[:-1]
def write_internal_structs(self, f):
if self.subcommands:
for subcommand in sorted(self.subcommands, key=lambda cmd: cmd.name):
subcommand.write_internal_structs(f)
f.write("/* %s command table */\n" % self.fullname())
f.write("struct redisCommand %s[] = {\n" % self.subcommand_table_name())
for subcommand in self.subcommands:
f.write("{%s},\n" % subcommand.struct_code())
f.write("{0}\n")
f.write("};\n\n")
f.write("/********** %s ********************/\n\n" % self.fullname())
f.write("/* %s history */\n" % self.fullname())
code = self.history_code()
if code:
f.write("commandHistory %s[] = {\n" % self.history_table_name())
f.write("%s\n" % code)
f.write("};\n\n")
else:
f.write("#define %s NULL\n\n" % self.history_table_name())
f.write("/* %s hints */\n" % self.fullname())
code = self.hints_code()
if code:
f.write("const char *%s[] = {\n" % self.hints_table_name())
f.write("%s\n" % code)
f.write("};\n\n")
else:
f.write("#define %s NULL\n\n" % self.hints_table_name())
if self.args:
for arg in self.args:
arg.write_internal_structs(f)
f.write("/* %s argument table */\n" % self.fullname())
f.write("struct redisCommandArg %s[] = {\n" % self.arg_table_name())
for arg in self.args:
f.write("{%s},\n" % arg.struct_code())
f.write("{0}\n")
f.write("};\n\n")
class Subcommand(Command):
def __init__(self, name, desc):
self.container_name = desc["container"].upper()
super(Subcommand, self).__init__(name, desc)
def fullname(self):
return "%s %s" % (self.container_name, self.name.replace("-", "_").replace(":", ""))
def create_command(name, desc):
if desc.get("container"):
cmd = Subcommand(name.upper(), desc)
subcommands.setdefault(desc["container"].upper(), {})[name] = cmd
else:
cmd = Command(name.upper(), desc)
commands[name.upper()] = cmd
# MAIN
# Create all command objects
print("Processing json files...")
for filename in glob.glob('commands/*.json'):
with open(filename,"r") as f:
d = json.load(f)
for name, desc in d.items():
create_command(name, desc)
# Link subcommands to containers
print("Linking container command to subcommands...")
for command in commands.values():
assert command.group
if command.name not in subcommands:
continue
for subcommand in subcommands[command.name].values():
assert not subcommand.group or subcommand.group == command.group
subcommand.group = command.group
command.subcommands.append(subcommand)
print("Generating commands.c...")
with open("commands.c","w") as f:
f.write("/* Automatically generated by %s, do not edit. */\n\n" % os.path.basename(__file__))
f.write("#include \"server.h\"\n")
f.write(
"""
/* We have fabulous commands from
* the fantastic
* Redis Command Table! */\n
"""
)
command_list = sorted(commands.values(), key=lambda cmd: (cmd.group, cmd.name))
for command in command_list:
command.write_internal_structs(f)
f.write("/* Main command table */\n")
f.write("struct redisCommand redisCommandTable[] = {\n")
curr_group = None
for command in command_list:
if curr_group != command.group:
curr_group = command.group
f.write("/* %s */\n" % curr_group)
f.write("{%s},\n" % command.struct_code())
f.write("{0}\n")
f.write("};\n")
print("All done, exiting.")
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment