Unverified Commit 0a82fe84 authored by Viktor Söderqvist's avatar Viktor Söderqvist Committed by GitHub
Browse files

Command info module API (#10108)

Adds RM_SetCommandInfo, allowing modules to provide the following command info:

* summary
* complexity
* since
* history
* hints
* arity
* key specs
* args

This information affects the output of `COMMAND`, `COMMAND INFO` and `COMMAND DOCS`,
Cluster, ACL and is used to filter commands with the wrong number of arguments before
the call reaches the module code.

The recently added API functions for key specs (never released) are removed.

A minimalist example would look like so:
```c
    RedisModuleCommand *mycmd = RedisModule_GetCommand(ctx,"mymodule.mycommand");
    RedisModuleCommandInfo mycmd_info = {
        .version = REDISMODULE_COMMAND_INFO_VERSION,
        .arity = -5,
        .summary = "some description",
    };
    if (RedisModule_SetCommandInfo(mycmd, &mycmd_info) == REDISMODULE_ERR)
        return REDISMODULE_ERR;
````

Notes:
* All the provided information (including strings) is copied, not keeping references to the API input data.
* The version field is actually a static struct that contains the sizes of the the structs used in arrays,
  so we can extend these in the future and old version will still be able to take the part they can support.
parent d7fcb3c5
...@@ -44,7 +44,7 @@ $TCLSH tests/test_helper.tcl \ ...@@ -44,7 +44,7 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/aclcheck \ --single unit/moduleapi/aclcheck \
--single unit/moduleapi/subcommands \ --single unit/moduleapi/subcommands \
--single unit/moduleapi/reply \ --single unit/moduleapi/reply \
--single unit/moduleapi/cmdintrospection \
--single unit/moduleapi/eventloop \ --single unit/moduleapi/eventloop \
--single unit/moduleapi/timer \ --single unit/moduleapi/timer \
"${@}" "${@}"
This diff is collapsed.
...@@ -244,6 +244,8 @@ typedef uint64_t RedisModuleTimerID; ...@@ -244,6 +244,8 @@ typedef uint64_t RedisModuleTimerID;
* are modified from the user's sperspective, to invalidate WATCH. */ * are modified from the user's sperspective, to invalidate WATCH. */
#define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1) #define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1)
/* Definitions for RedisModule_SetCommandInfo. */
typedef enum { typedef enum {
REDISMODULE_ARG_TYPE_STRING, REDISMODULE_ARG_TYPE_STRING,
REDISMODULE_ARG_TYPE_INTEGER, REDISMODULE_ARG_TYPE_INTEGER,
...@@ -260,6 +262,139 @@ typedef enum { ...@@ -260,6 +262,139 @@ typedef enum {
#define REDISMODULE_CMD_ARG_OPTIONAL (1<<0) /* The argument is optional (like GET in SET command) */ #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 (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) */ #define REDISMODULE_CMD_ARG_MULTIPLE_TOKEN (1<<2) /* The argument may repeat itself, and so does its token (like `GET pattern` in SORT) */
#define _REDISMODULE_CMD_ARG_NEXT (1<<3)
typedef enum {
REDISMODULE_KSPEC_BS_INVALID = 0, /* Must be zero. An implicitly value of
* zero is provided when the field is
* absent in a struct literal. */
REDISMODULE_KSPEC_BS_UNKNOWN,
REDISMODULE_KSPEC_BS_INDEX,
REDISMODULE_KSPEC_BS_KEYWORD
} RedisModuleKeySpecBeginSearchType;
typedef enum {
REDISMODULE_KSPEC_FK_OMITTED = 0, /* Used when the field is absent in a
* struct literal. Don't use this value
* explicitly. */
REDISMODULE_KSPEC_FK_UNKNOWN,
REDISMODULE_KSPEC_FK_RANGE,
REDISMODULE_KSPEC_FK_KEYNUM
} RedisModuleKeySpecFindKeysType;
/* Key-spec flags. For details, see the documentation of
* RedisModule_SetCommandInfo and the key-spec flags in server.h. */
#define REDISMODULE_CMD_KEY_RO (1ULL<<0)
#define REDISMODULE_CMD_KEY_RW (1ULL<<1)
#define REDISMODULE_CMD_KEY_OW (1ULL<<2)
#define REDISMODULE_CMD_KEY_RM (1ULL<<3)
#define REDISMODULE_CMD_KEY_ACCESS (1ULL<<4)
#define REDISMODULE_CMD_KEY_UPDATE (1ULL<<5)
#define REDISMODULE_CMD_KEY_INSERT (1ULL<<6)
#define REDISMODULE_CMD_KEY_DELETE (1ULL<<7)
#define REDISMODULE_CMD_KEY_CHANNEL (1ULL<<8)
#define REDISMODULE_CMD_KEY_INCOMPLETE (1ULL<<9)
#define REDISMODULE_CMD_KEY_VARIABLE_FLAGS (1ULL<<10)
typedef struct RedisModuleCommandArg {
const char *name;
RedisModuleCommandArgType type;
int key_spec_index; /* If type is KEY, this is a zero-based index of
* the key_spec in the command. For other types,
* you may specify -1. */
const char *token; /* If type is PURE_TOKEN, this is the token. */
const char *summary;
const char *since;
int flags; /* The REDISMODULE_CMD_ARG_* macros. */
struct RedisModuleCommandArg *subargs;
} RedisModuleCommandArg;
typedef struct {
const char *since;
const char *changes;
} RedisModuleCommandHistoryEntry;
typedef struct {
const char *notes;
uint64_t flags; /* REDISMODULE_CMD_KEY_* macros. */
RedisModuleKeySpecBeginSearchType begin_search_type;
union {
struct {
/* The index from which we start the search for keys */
int pos;
} index;
struct {
/* The keyword that indicates the beginning of key args */
const char *keyword;
/* An index in argv from which to start searching.
* Can be negative, which means start search from the end, in reverse
* (Example: -2 means to start in reverse from the panultimate arg) */
int startfrom;
} keyword;
} bs;
RedisModuleKeySpecFindKeysType find_keys_type;
union {
struct {
/* Index of the last key relative to the result of the begin search
* step. Can be negative, in which case it's not relative. -1
* indicating till the last argument, -2 one before the last and so
* on. */
int lastkey;
/* How many args should we skip after finding a key, in order to
* find the next one. */
int keystep;
/* If lastkey is -1, we use limit to stop the search by a factor. 0
* and 1 mean no limit. 2 means 1/2 of the remaining args, 3 means
* 1/3, and so on. */
int limit;
} range;
struct {
/* Index of the argument containing the number of keys to come
* relative to the result of the begin search step */
int keynumidx;
/* Index of the fist key. (Usually it's just after keynumidx, in
* which case it should be set to keynumidx + 1.) */
int firstkey;
/* How many args should we skip after finding a key, in order to
* find the next one, relative to the result of the begin search
* step. */
int keystep;
} keynum;
} fk;
} RedisModuleCommandKeySpec;
typedef struct {
int version;
size_t sizeof_historyentry;
size_t sizeof_keyspec;
size_t sizeof_arg;
} RedisModuleCommandInfoVersion;
static const RedisModuleCommandInfoVersion RedisModule_CurrentCommandInfoVersion = {
.version = 1,
.sizeof_historyentry = sizeof(RedisModuleCommandHistoryEntry),
.sizeof_keyspec = sizeof(RedisModuleCommandKeySpec),
.sizeof_arg = sizeof(RedisModuleCommandArg)
};
#define REDISMODULE_COMMAND_INFO_VERSION (&RedisModule_CurrentCommandInfoVersion)
typedef struct {
/* Always set version to REDISMODULE_COMMAND_INFO_VERSION */
const RedisModuleCommandInfoVersion *version;
/* Version 1 fields (added in Redis 7.0.0) */
const char *summary; /* Summary of the command */
const char *complexity; /* Complexity description */
const char *since; /* Debut module version of the command */
RedisModuleCommandHistoryEntry *history; /* History */
/* A string of space-separated tips meant for clients/proxies regarding this
* command */
const char *tips;
/* Number of arguments, it is possible to use -N to say >= N */
int arity;
RedisModuleCommandKeySpec *key_specs;
RedisModuleCommandArg *args;
} RedisModuleCommandInfo;
/* Redis ACL key permission flags, which specify which permissions a module /* Redis ACL key permission flags, which specify which permissions a module
* needs on a key. */ * needs on a key. */
...@@ -623,7 +758,6 @@ typedef struct RedisModuleScanCursor RedisModuleScanCursor; ...@@ -623,7 +758,6 @@ typedef struct RedisModuleScanCursor RedisModuleScanCursor;
typedef struct RedisModuleDefragCtx RedisModuleDefragCtx; typedef struct RedisModuleDefragCtx RedisModuleDefragCtx;
typedef struct RedisModuleUser RedisModuleUser; typedef struct RedisModuleUser RedisModuleUser;
typedef struct RedisModuleKeyOptCtx RedisModuleKeyOptCtx; typedef struct RedisModuleKeyOptCtx RedisModuleKeyOptCtx;
typedef struct RedisModuleCommandArg RedisModuleCommandArg;
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc); typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
...@@ -697,6 +831,7 @@ REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR ...@@ -697,6 +831,7 @@ 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_CreateCommand)(RedisModuleCtx *ctx, 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 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 int (*RedisModule_CreateSubcommand)(RedisModuleCommand *parent, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_SetCommandInfo)(RedisModuleCommand *command, const RedisModuleCommandInfo *info) REDISMODULE_ATTR;
REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) 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_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
...@@ -924,14 +1059,6 @@ REDISMODULE_API int (*RedisModule_GetKeyspaceNotificationFlagsAll)() REDISMODULE ...@@ -924,14 +1059,6 @@ REDISMODULE_API int (*RedisModule_GetKeyspaceNotificationFlagsAll)() REDISMODULE
REDISMODULE_API int (*RedisModule_IsSubEventSupported)(RedisModuleEvent event, uint64_t subevent) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_IsSubEventSupported)(RedisModuleEvent event, uint64_t subevent) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetServerVersion)() REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetServerVersion)() REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_GetTypeMethodVersion)() REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_GetTypeMethodVersion)() REDISMODULE_ATTR;
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API
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;
#endif
REDISMODULE_API void (*RedisModule_Yield)(RedisModuleCtx *ctx, int flags, const char *busy_reply) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_Yield)(RedisModuleCtx *ctx, int flags, const char *busy_reply) REDISMODULE_ATTR;
REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) REDISMODULE_ATTR; REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata) REDISMODULE_ATTR;
...@@ -1023,6 +1150,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -1023,6 +1150,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(CreateCommand); REDISMODULE_GET_API(CreateCommand);
REDISMODULE_GET_API(GetCommand); REDISMODULE_GET_API(GetCommand);
REDISMODULE_GET_API(CreateSubcommand); REDISMODULE_GET_API(CreateSubcommand);
REDISMODULE_GET_API(SetCommandInfo);
REDISMODULE_GET_API(SetModuleAttribs); REDISMODULE_GET_API(SetModuleAttribs);
REDISMODULE_GET_API(IsModuleNameBusy); REDISMODULE_GET_API(IsModuleNameBusy);
REDISMODULE_GET_API(WrongArity); REDISMODULE_GET_API(WrongArity);
...@@ -1250,13 +1378,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -1250,13 +1378,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(IsSubEventSupported); REDISMODULE_GET_API(IsSubEventSupported);
REDISMODULE_GET_API(GetServerVersion); REDISMODULE_GET_API(GetServerVersion);
REDISMODULE_GET_API(GetTypeMethodVersion); REDISMODULE_GET_API(GetTypeMethodVersion);
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API
REDISMODULE_GET_API(AddCommandKeySpec);
REDISMODULE_GET_API(SetCommandKeySpecBeginSearchIndex);
REDISMODULE_GET_API(SetCommandKeySpecBeginSearchKeyword);
REDISMODULE_GET_API(SetCommandKeySpecFindKeysRange);
REDISMODULE_GET_API(SetCommandKeySpecFindKeysKeynum);
#endif
REDISMODULE_GET_API(Yield); REDISMODULE_GET_API(Yield);
REDISMODULE_GET_API(GetThreadSafeContext); REDISMODULE_GET_API(GetThreadSafeContext);
REDISMODULE_GET_API(GetDetachedThreadSafeContext); REDISMODULE_GET_API(GetDetachedThreadSafeContext);
...@@ -1347,7 +1468,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int ...@@ -1347,7 +1468,6 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
/* Things only defined for the modules core, not exported to modules /* Things only defined for the modules core, not exported to modules
* including this file. */ * including this file. */
#define RedisModuleString robj #define RedisModuleString robj
#define RedisModuleCommandArg redisCommandArg
#endif /* REDISMODULE_CORE */ #endif /* REDISMODULE_CORE */
#endif /* REDISMODULE_H */ #endif /* REDISMODULE_H */
...@@ -2631,12 +2631,14 @@ void setImplicitACLCategories(struct redisCommand *c) { ...@@ -2631,12 +2631,14 @@ void setImplicitACLCategories(struct redisCommand *c) {
c->acl_categories |= ACL_CATEGORY_SLOW; c->acl_categories |= ACL_CATEGORY_SLOW;
} }
/* Recursively populate the args structure and return the number of args. */ /* Recursively populate the args structure (setting num_args to the number of
* subargs) and return the number of args. */
int populateArgsStructure(struct redisCommandArg *args) { int populateArgsStructure(struct redisCommandArg *args) {
if (!args) if (!args)
return 0; return 0;
int count = 0; int count = 0;
while (args->name) { while (args->name) {
serverAssert(count < INT_MAX);
args->num_args = populateArgsStructure(args->subargs); args->num_args = populateArgsStructure(args->subargs);
count++; count++;
args++; args++;
......
...@@ -2308,8 +2308,9 @@ extern dict *modules; ...@@ -2308,8 +2308,9 @@ extern dict *modules;
* Functions prototypes * Functions prototypes
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
/* Key arguments specs */ /* Command metadata */
void populateCommandLegacyRangeSpec(struct redisCommand *c); void populateCommandLegacyRangeSpec(struct redisCommand *c);
int populateArgsStructure(struct redisCommandArg *args);
/* Modules */ /* Modules */
void moduleInitModulesSystem(void); void moduleInitModulesSystem(void);
......
...@@ -2,11 +2,12 @@ ...@@ -2,11 +2,12 @@
# find the OS # find the OS
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
warning_cflags = -W -Wall -Wno-missing-field-initializers
ifeq ($(uname_S),Darwin) ifeq ($(uname_S),Darwin)
SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2 SHOBJ_CFLAGS ?= $(warning_cflags) -dynamic -fno-common -g -ggdb -std=c99 -O2
SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
else # Linux, others else # Linux, others
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2 SHOBJ_CFLAGS ?= $(warning_cflags) -fno-common -g -ggdb -std=c99 -O2
SHOBJ_LDFLAGS ?= -shared SHOBJ_LDFLAGS ?= -shared
endif endif
...@@ -51,6 +52,7 @@ TEST_MODULES = \ ...@@ -51,6 +52,7 @@ TEST_MODULES = \
list.so \ list.so \
subcommands.so \ subcommands.so \
reply.so \ reply.so \
cmdintrospection.so \
eventloop.so eventloop.so
.PHONY: all .PHONY: all
......
#include "redismodule.h"
#define UNUSED(V) ((void) V)
int cmd_xadd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv);
UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
if (RedisModule_Init(ctx, "cmdintrospection", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"cmdintrospection.xadd",cmd_xadd,"write deny-oom random fast",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *xadd = RedisModule_GetCommand(ctx,"cmdintrospection.xadd");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.arity = -5,
.summary = "Appends a new entry to a stream",
.since = "5.0.0",
.complexity = "O(1) when adding a new entry, O(N) when trimming where N being the number of entries evicted.",
.tips = "nondeterministic_output",
.history = (RedisModuleCommandHistoryEntry[]){
/* NOTE: All versions specified should be the module's versions, not
* Redis'! We use Redis versions in this example for the purpose of
* testing (comparing the output with the output of the vanilla
* XADD). */
{"6.2.0", "Added the `NOMKSTREAM` option, `MINID` trimming strategy and the `LIMIT` option."},
{"7.0.0", "Added support for the `<ms>-*` explicit ID form."},
{0}
},
.key_specs = (RedisModuleCommandKeySpec[]){
{
.notes = "UPDATE instead of INSERT because of the optional trimming feature",
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{0}
},
.args = (RedisModuleCommandArg[]){
{
.name = "key",
.type = REDISMODULE_ARG_TYPE_KEY,
.key_spec_index = 0
},
{
.name = "nomkstream",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "NOMKSTREAM",
.since = "6.2.0",
.flags = REDISMODULE_CMD_ARG_OPTIONAL
},
{
.name = "trim",
.type = REDISMODULE_ARG_TYPE_BLOCK,
.flags = REDISMODULE_CMD_ARG_OPTIONAL,
.subargs = (RedisModuleCommandArg[]){
{
.name = "strategy",
.type = REDISMODULE_ARG_TYPE_ONEOF,
.subargs = (RedisModuleCommandArg[]){
{
.name = "maxlen",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "MAXLEN",
},
{
.name = "minid",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "MINID",
.since = "6.2.0",
},
{0}
}
},
{
.name = "operator",
.type = REDISMODULE_ARG_TYPE_ONEOF,
.flags = REDISMODULE_CMD_ARG_OPTIONAL,
.subargs = (RedisModuleCommandArg[]){
{
.name = "equal",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "="
},
{
.name = "approximately",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "~"
},
{0}
}
},
{
.name = "threshold",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{
.name = "count",
.type = REDISMODULE_ARG_TYPE_INTEGER,
.token = "LIMIT",
.since = "6.2.0",
.flags = REDISMODULE_CMD_ARG_OPTIONAL
},
{0}
}
},
{
.name = "id_or_auto",
.type = REDISMODULE_ARG_TYPE_ONEOF,
.subargs = (RedisModuleCommandArg[]){
{
.name = "auto_id",
.type = REDISMODULE_ARG_TYPE_PURE_TOKEN,
.token = "*"
},
{
.name = "id",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{0}
}
},
{
.name = "field_value",
.type = REDISMODULE_ARG_TYPE_BLOCK,
.flags = REDISMODULE_CMD_ARG_MULTIPLE,
.subargs = (RedisModuleCommandArg[]){
{
.name = "field",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{
.name = "value",
.type = REDISMODULE_ARG_TYPE_STRING,
},
{0}
}
},
{0}
}
};
if (RedisModule_SetCommandInfo(xadd, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
#include "redismodule.h" #include "redismodule.h"
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API
#define UNUSED(V) ((void) V) #define UNUSED(V) ((void) V)
int kspec_legacy(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { /* This function implements all commands in this module. All we care about is
* the COMMAND metadata anyway. */
int kspec_impl(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv); UNUSED(argv);
UNUSED(argc); UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK"); RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int kspec_complex1(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int createKspecNone(RedisModuleCtx *ctx) {
UNUSED(argv); /* A command without keyspecs; only the legacy (first,last,step) triple. */
UNUSED(argc); if (RedisModule_CreateCommand(ctx,"kspec.none",kspec_impl,"",1,3,2) == REDISMODULE_ERR)
RedisModule_ReplyWithSimpleString(ctx, "OK"); return REDISMODULE_ERR;
return REDISMODULE_OK;
}
int kspec_complex2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
UNUSED(argv);
UNUSED(argc);
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK; return REDISMODULE_OK;
} }
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int createKspecTwoRanges(RedisModuleCtx *ctx) {
REDISMODULE_NOT_USED(argv); /* Test that two position/range-based key specs are combined to produce the
REDISMODULE_NOT_USED(argc); * legacy (first,last,step) values representing both keys. */
if (RedisModule_CreateCommand(ctx,"kspec.tworanges",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
int spec_id; return REDISMODULE_ERR;
if (RedisModule_Init(ctx, "keyspecs", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR) RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.tworanges");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.arity = -2,
.key_specs = (RedisModuleCommandKeySpec[]){
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 2,
/* Omitted find_keys_type is shorthand for RANGE {0,1,0} */
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
/* Test legacy range "gluing" */ return REDISMODULE_OK;
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(legacy,"RO ACCESS",&spec_id) == REDISMODULE_ERR) int createKspecKeyword(RedisModuleCtx *ctx) {
return REDISMODULE_ERR; /* Only keyword-based specs. The legacy triple is wiped and set to (0,0,0). */
if (RedisModule_SetCommandKeySpecBeginSearchIndex(legacy,spec_id,1) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"kspec.keyword",kspec_impl,"",3,-1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(legacy,spec_id,0,1,0) == REDISMODULE_ERR)
RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.keyword");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.key_specs = (RedisModuleCommandKeySpec[]){
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "KEYS",
.bs.keyword.startfrom = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {-1,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(legacy,"RW UPDATE",&spec_id) == REDISMODULE_ERR) return REDISMODULE_OK;
return REDISMODULE_ERR; }
if (RedisModule_SetCommandKeySpecBeginSearchIndex(legacy,spec_id,2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(legacy,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
/* First is legacy, rest are new specs */ int createKspecComplex1(RedisModuleCtx *ctx) {
if (RedisModule_CreateCommand(ctx,"kspec.complex1",kspec_complex1,"",1,1,1) == REDISMODULE_ERR) /* First is a range a single key. The rest are keyword-based specs. */
if (RedisModule_CreateCommand(ctx,"kspec.complex1",kspec_impl,"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.complex1");
RedisModuleCommandInfo info = {
.version = REDISMODULE_COMMAND_INFO_VERSION,
.key_specs = (RedisModuleCommandKeySpec[]){
{
.flags = REDISMODULE_CMD_KEY_RO,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "STORE",
.bs.keyword.startfrom = 2,
},
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "KEYS",
.bs.keyword.startfrom = 2,
.find_keys_type = REDISMODULE_KSPEC_FK_KEYNUM,
.fk.keynum = {0,1,1}
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModuleCommand *complex1 = RedisModule_GetCommand(ctx,"kspec.complex1");
if (RedisModule_AddCommandKeySpec(complex1,"RW UPDATE",&spec_id) == REDISMODULE_ERR) return REDISMODULE_OK;
return REDISMODULE_ERR; }
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex1,spec_id,"STORE",2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(complex1,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex1,"RO ACCESS",&spec_id) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex1,spec_id,"KEYS",2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysKeynum(complex1,spec_id,0,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
int createKspecComplex2(RedisModuleCtx *ctx) {
/* First is not legacy, more than STATIC_KEYS_SPECS_NUM specs */ /* First is not legacy, more than STATIC_KEYS_SPECS_NUM specs */
if (RedisModule_CreateCommand(ctx,"kspec.complex2",kspec_complex2,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"kspec.complex2",kspec_impl,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModuleCommand *complex2 = RedisModule_GetCommand(ctx,"kspec.complex2");
RedisModuleCommand *command = RedisModule_GetCommand(ctx,"kspec.complex2");
if (RedisModule_AddCommandKeySpec(complex2,"RW UPDATE",&spec_id) == REDISMODULE_ERR) RedisModuleCommandInfo info = {
return REDISMODULE_ERR; .version = REDISMODULE_COMMAND_INFO_VERSION,
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex2,spec_id,"STORE",5) == REDISMODULE_ERR) .key_specs = (RedisModuleCommandKeySpec[]){
return REDISMODULE_ERR; {
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR) .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
return REDISMODULE_ERR; .begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "STORE",
if (RedisModule_AddCommandKeySpec(complex2,"RO ACCESS",&spec_id) == REDISMODULE_ERR) .bs.keyword.startfrom = 5,
return REDISMODULE_ERR; .find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,1) == REDISMODULE_ERR) .fk.range = {0,1,0}
return REDISMODULE_ERR; },
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR) {
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{
.flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 2,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
.bs.index.pos = 3,
.find_keys_type = REDISMODULE_KSPEC_FK_KEYNUM,
.fk.keynum = {0,1,1}
},
{
.flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
.begin_search_type = REDISMODULE_KSPEC_BS_KEYWORD,
.bs.keyword.keyword = "MOREKEYS",
.bs.keyword.startfrom = 5,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {-1,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(command, &info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex2,"RO ACCESS",&spec_id) == REDISMODULE_ERR) return REDISMODULE_OK;
return REDISMODULE_ERR; }
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,2) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,0,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex2,"RW UPDATE",&spec_id) == REDISMODULE_ERR) int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return REDISMODULE_ERR; REDISMODULE_NOT_USED(argv);
if (RedisModule_SetCommandKeySpecBeginSearchIndex(complex2,spec_id,3) == REDISMODULE_ERR) REDISMODULE_NOT_USED(argc);
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysKeynum(complex2,spec_id,0,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_AddCommandKeySpec(complex2,"RW UPDATE",&spec_id) == REDISMODULE_ERR) if (RedisModule_Init(ctx, "keyspecs", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecBeginSearchKeyword(complex2,spec_id,"MOREKEYS",5) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_SetCommandKeySpecFindKeysRange(complex2,spec_id,-1,1,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (createKspecNone(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecTwoRanges(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecKeyword(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecComplex1(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
if (createKspecComplex2(ctx) == REDISMODULE_ERR) return REDISMODULE_ERR;
return REDISMODULE_OK; return REDISMODULE_OK;
} }
#endif
...@@ -29,11 +29,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -29,11 +29,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
REDISMODULE_NOT_USED(argv); REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argc);
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API if (RedisModule_Init(ctx, "subcommands", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
int spec_id;
#endif
if (RedisModule_Init(ctx, "subcommands", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"subcommands.bitarray",NULL,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"subcommands.bitarray",NULL,"",0,0,0) == REDISMODULE_ERR)
...@@ -43,28 +39,40 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) ...@@ -43,28 +39,40 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateSubcommand(parent,"set",cmd_set,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateSubcommand(parent,"set",cmd_set,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
RedisModuleCommand *subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|set"); RedisModuleCommand *subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|set");
RedisModuleCommandInfo cmd_set_info = {
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API .version = REDISMODULE_COMMAND_INFO_VERSION,
if (RedisModule_AddCommandKeySpec(subcmd,"RW UPDATE",&spec_id) == REDISMODULE_ERR) .key_specs = (RedisModuleCommandKeySpec[]){
return REDISMODULE_ERR; {
if (RedisModule_SetCommandKeySpecBeginSearchIndex(subcmd,spec_id,1) == REDISMODULE_ERR) .flags = REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE,
return REDISMODULE_ERR; .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR) .bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(subcmd, &cmd_set_info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
#endif
if (RedisModule_CreateSubcommand(parent,"get",cmd_get,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateSubcommand(parent,"get",cmd_get,"",0,0,0) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|get"); subcmd = RedisModule_GetCommand(ctx,"subcommands.bitarray|get");
RedisModuleCommandInfo cmd_get_info = {
#ifdef INCLUDE_UNRELEASED_KEYSPEC_API .version = REDISMODULE_COMMAND_INFO_VERSION,
if (RedisModule_AddCommandKeySpec(subcmd,"RO ACCESS",&spec_id) == REDISMODULE_ERR) .key_specs = (RedisModuleCommandKeySpec[]){
return REDISMODULE_ERR; {
if (RedisModule_SetCommandKeySpecBeginSearchIndex(subcmd,spec_id,1) == REDISMODULE_ERR) .flags = REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS,
return REDISMODULE_ERR; .begin_search_type = REDISMODULE_KSPEC_BS_INDEX,
if (RedisModule_SetCommandKeySpecFindKeysRange(subcmd,spec_id,0,1,0) == REDISMODULE_ERR) .bs.index.pos = 1,
.find_keys_type = REDISMODULE_KSPEC_FK_RANGE,
.fk.range = {0,1,0}
},
{0}
}
};
if (RedisModule_SetCommandInfo(subcmd, &cmd_get_info) == REDISMODULE_ERR)
return REDISMODULE_ERR; return REDISMODULE_ERR;
#endif
/* Get the name of the command currently running. */ /* Get the name of the command currently running. */
if (RedisModule_CreateCommand(ctx,"subcommands.parent_get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR) if (RedisModule_CreateCommand(ctx,"subcommands.parent_get_fullname",cmd_get_fullname,"",0,0,0) == REDISMODULE_ERR)
......
set testmodule [file normalize tests/modules/cmdintrospection.so]
start_server {tags {"modules"}} {
r module load $testmodule
# cmdintrospection.xadd mimics XADD with regards to how
# what COMMAND exposes. There are two differences:
#
# 1. cmdintrospection.xadd (and all module commands) do not have ACL categories
# 2. cmdintrospection.xadd's `group` is "module"
#
# This tests verify that, apart from the above differences, the output of
# COMMAND INFO and COMMAND DOCS are identical for the two commands.
test "Module command introspection via COMMAND INFO" {
set redis_reply [lindex [r command info xadd] 0]
set module_reply [lindex [r command info cmdintrospection.xadd] 0]
for {set i 1} {$i < [llength $redis_reply]} {incr i} {
if {$i == 2} {
# Remove the "module" flag
set mylist [lindex $module_reply $i]
set idx [lsearch $mylist "module"]
set mylist [lreplace $mylist $idx $idx]
lset module_reply $i $mylist
}
if {$i == 6} {
# Skip ACL categories
continue
}
assert_equal [lindex $redis_reply $i] [lindex $module_reply $i]
}
}
test "Module command introspection via COMMAND DOCS" {
set redis_reply [dict create {*}[lindex [r command docs xadd] 1]]
set module_reply [dict create {*}[lindex [r command docs cmdintrospection.xadd] 1]]
# Compare the maps. We need to pop "group" first.
dict unset redis_reply group
dict unset module_reply group
assert_equal $redis_reply $module_reply
}
}
set testmodule [file normalize tests/modules/keyspecs.so] set testmodule [file normalize tests/modules/keyspecs.so]
if 0 { ; # Test suite disabled due to planned API changes
start_server {tags {"modules"}} { start_server {tags {"modules"}} {
r module load $testmodule r module load $testmodule
test "Module key specs: Legacy" { test "Module key specs: No spec, only legacy triple" {
set reply [lindex [r command info kspec.legacy] 0] set reply [lindex [r command info kspec.none] 0]
# Verify (first, last, step) # Verify (first, last, step) and not movablekeys
assert_equal [lindex $reply 2] {module}
assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] 3
assert_equal [lindex $reply 5] 2
# Verify key-spec auto-generated from the legacy triple
set keyspecs [lindex $reply 8]
assert_equal [llength $keyspecs] 1
assert_equal [lindex $keyspecs 0] {flags {} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 2 keystep 2 limit 0}}}
}
test "Module key specs: Two ranges" {
set reply [lindex [r command info kspec.tworanges] 0]
# Verify (first, last, step) and not movablekeys
assert_equal [lindex $reply 2] {module}
assert_equal [lindex $reply 3] 1 assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] 2 assert_equal [lindex $reply 4] 2
assert_equal [lindex $reply 5] 1 assert_equal [lindex $reply 5] 1
...@@ -16,22 +29,36 @@ start_server {tags {"modules"}} { ...@@ -16,22 +29,36 @@ start_server {tags {"modules"}} {
assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type index spec {index 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
} }
test "Module key specs: Keyword-only spec clears the legacy triple" {
set reply [lindex [r command info kspec.keyword] 0]
# Verify (first, last, step) and movablekeys
assert_equal [lindex $reply 2] {module movablekeys}
assert_equal [lindex $reply 3] 0
assert_equal [lindex $reply 4] 0
assert_equal [lindex $reply 5] 0
# Verify key-specs
set keyspecs [lindex $reply 8]
assert_equal [lindex $keyspecs 0] {flags {RO access} begin_search {type keyword spec {keyword KEYS startfrom 1}} find_keys {type range spec {lastkey -1 keystep 1 limit 0}}}
}
test "Module key specs: Complex specs, case 1" { test "Module key specs: Complex specs, case 1" {
set reply [lindex [r command info kspec.complex1] 0] set reply [lindex [r command info kspec.complex1] 0]
# Verify (first, last, step) # Verify (first, last, step) and movablekeys
assert_equal [lindex $reply 2] {module movablekeys}
assert_equal [lindex $reply 3] 1 assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] 1 assert_equal [lindex $reply 4] 1
assert_equal [lindex $reply 5] 1 assert_equal [lindex $reply 5] 1
# Verify key-specs # Verify key-specs
set keyspecs [lindex $reply 8] set keyspecs [lindex $reply 8]
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 0] {flags RO begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}
assert_equal [lindex $keyspecs 1] {flags {RW update} begin_search {type keyword spec {keyword STORE startfrom 2}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}} assert_equal [lindex $keyspecs 1] {flags {RW update} 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 {RO access} begin_search {type keyword spec {keyword KEYS startfrom 2}} find_keys {type keynum spec {keynumidx 0 firstkey 1 keystep 1}}} assert_equal [lindex $keyspecs 2] {flags {RO access} 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" { test "Module key specs: Complex specs, case 2" {
set reply [lindex [r command info kspec.complex2] 0] set reply [lindex [r command info kspec.complex2] 0]
# Verify (first, last, step) # Verify (first, last, step) and movablekeys
assert_equal [lindex $reply 2] {module movablekeys}
assert_equal [lindex $reply 3] 1 assert_equal [lindex $reply 3] 1
assert_equal [lindex $reply 4] 2 assert_equal [lindex $reply 4] 2
assert_equal [lindex $reply 5] 1 assert_equal [lindex $reply 5] 1
...@@ -47,12 +74,10 @@ start_server {tags {"modules"}} { ...@@ -47,12 +74,10 @@ start_server {tags {"modules"}} {
test "Module command list filtering" { test "Module command list filtering" {
;# Note: we piggyback this tcl file to test the general functionality of command list filtering ;# Note: we piggyback this tcl file to test the general functionality of command list filtering
set reply [r command list filterby module keyspecs] set reply [r command list filterby module keyspecs]
assert_equal [lsort $reply] {kspec.complex1 kspec.complex2 kspec.legacy} assert_equal [lsort $reply] {kspec.complex1 kspec.complex2 kspec.keyword kspec.none kspec.tworanges}
} }
test "Unload the module - keyspecs" { test "Unload the module - keyspecs" {
assert_equal {OK} [r module unload keyspecs] assert_equal {OK} [r module unload keyspecs]
} }
} }
} ; # Test suite disabled
...@@ -8,13 +8,8 @@ start_server {tags {"modules"}} { ...@@ -8,13 +8,8 @@ start_server {tags {"modules"}} {
set command_reply [r command info subcommands.bitarray] set command_reply [r command info subcommands.bitarray]
set first_cmd [lindex $command_reply 0] set first_cmd [lindex $command_reply 0]
set subcmds_in_command [lsort [lindex $first_cmd 9]] set subcmds_in_command [lsort [lindex $first_cmd 9]]
if 0 { ; # Keyspecs disabled due to planned changes in keyspec API assert_equal [lindex $subcmds_in_command 0] {subcommands.bitarray|get -2 module 1 1 1 {} {} {{flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}
assert_equal [lindex $subcmds_in_command 0] {subcommands.bitarray|get -2 module 1 1 1 {} {} {{flags {RO access} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}} assert_equal [lindex $subcmds_in_command 1] {subcommands.bitarray|set -2 module 1 1 1 {} {} {{flags {RW update} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}
assert_equal [lindex $subcmds_in_command 1] {subcommands.bitarray|set -2 module 1 1 1 {} {} {{flags {RW update} begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}
} else { ; # The same asserts without the key specs
assert_equal [lindex $subcmds_in_command 0] {subcommands.bitarray|get -2 module 0 0 0 {} {} {} {}}
assert_equal [lindex $subcmds_in_command 1] {subcommands.bitarray|set -2 module 0 0 0 {} {} {} {}}
}
# Verify that module subcommands are displayed correctly in COMMAND DOCS # Verify that module subcommands are displayed correctly in COMMAND DOCS
set docs_reply [r command docs subcommands.bitarray] set docs_reply [r command docs subcommands.bitarray]
......
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