Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move doc metadata from COMMAND to COMMAND DOCS #10056

Merged
merged 13 commits into from
Jan 11, 2022
Prev Previous commit
Next Next commit
rename COMMAND DETAILS to COMMAND DOCS. always include full subcomman…
…d name
oranagra committed Jan 11, 2022
commit 6cc5200f41d371737a9e5dd2ab11d89ece93c8b8
16 changes: 8 additions & 8 deletions src/commands.c
Original file line number Diff line number Diff line change
@@ -3795,16 +3795,16 @@ struct redisCommandArg BGSAVE_Args[] = {
/* COMMAND COUNT hints */
#define COMMAND_COUNT_Hints NULL

/********** COMMAND DETAILS ********************/
/********** COMMAND DOCS ********************/

/* COMMAND DETAILS history */
#define COMMAND_DETAILS_History NULL
/* COMMAND DOCS history */
#define COMMAND_DOCS_History NULL

/* COMMAND DETAILS hints */
#define COMMAND_DETAILS_Hints NULL
/* COMMAND DOCS hints */
#define COMMAND_DOCS_Hints NULL

/* COMMAND DETAILS argument table */
struct redisCommandArg COMMAND_DETAILS_Args[] = {
/* COMMAND DOCS argument table */
struct redisCommandArg COMMAND_DOCS_Args[] = {
{"command-name",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
{0}
};
@@ -3864,7 +3864,7 @@ struct redisCommandArg COMMAND_LIST_Args[] = {
/* COMMAND command table */
struct redisCommand COMMAND_Subcommands[] = {
{"count","Get total number of Redis commands","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_COUNT_History,COMMAND_COUNT_Hints,commandCountCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
{"details","Get array of specific Redis command documentation","O(N) when N is number of commands to look up","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_DETAILS_History,COMMAND_DETAILS_Hints,commandDetailsCommand,-2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=COMMAND_DETAILS_Args},
{"docs","Get array of specific Redis command documentation","O(N) when N is number of commands to look up","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_DOCS_History,COMMAND_DOCS_Hints,commandDocsCommand,-2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=COMMAND_DOCS_Args},
{"getkeys","Extract keys given a full Redis command","O(N) where N is the number of arguments to the command","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_GETKEYS_History,COMMAND_GETKEYS_Hints,commandGetKeysCommand,-4,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
{"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_HELP_History,COMMAND_HELP_Hints,commandHelpCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
{"info","Get array of specific Redis command details","O(N) when N is number of commands to look up","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_INFO_History,COMMAND_INFO_Hints,commandInfoCommand,-3,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=COMMAND_INFO_Args},
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"DETAILS": {
"DOCS": {
"summary": "Get array of specific Redis command documentation",
"complexity": "O(N) when N is number of commands to look up",
"group": "server",
"since": "7.0.0",
"arity": -2,
"container": "COMMAND",
"function": "commandDetailsCommand",
"function": "commandDocsCommand",
"command_flags": [
"LOADING",
"STALE"
31 changes: 16 additions & 15 deletions src/server.c
Original file line number Diff line number Diff line change
@@ -4318,7 +4318,7 @@ void addReplyCommandKeySpecs(client *c, struct redisCommand *cmd) {
}

/* Reply with an array of sub-command using the provided reply callback. */
void addReplyCommandSubCommands(client *c, struct redisCommand *cmd, void (*reply_function)(client*, struct redisCommand*, int), int use_map) {
void addReplyCommandSubCommands(client *c, struct redisCommand *cmd, void (*reply_function)(client*, struct redisCommand*), int use_map) {
if (!cmd->subcommands_dict) {
addReplySetLen(c, 0);
return;
@@ -4333,8 +4333,8 @@ void addReplyCommandSubCommands(client *c, struct redisCommand *cmd, void (*repl
while((de = dictNext(di)) != NULL) {
struct redisCommand *sub = (struct redisCommand *)dictGetVal(de);
if (use_map)
addReplyBulkCString(c, sub->name);
reply_function(c, sub, 0);
addReplyBulkSds(c, getFullCommandName(sub));
reply_function(c, sub);
}
dictReleaseIterator(di);
}
@@ -4362,7 +4362,7 @@ const char *COMMAND_GROUP_STR[] = {
};

/* Output the representation of a Redis command. Used by the COMMAND command and COMMAND INFO. */
void addReplyCommandInfo(client *c, struct redisCommand *cmd, int show_full_name) {
void addReplyCommandInfo(client *c, struct redisCommand *cmd) {
if (!cmd) {
addReplyNull(c);
} else {
@@ -4376,7 +4376,7 @@ void addReplyCommandInfo(client *c, struct redisCommand *cmd, int show_full_name
}

addReplyArrayLen(c, 10);
if (show_full_name && cmd->parent)
if (cmd->parent)
addReplyBulkSds(c, getFullCommandName(cmd));
else
addReplyBulkCString(c, cmd->name);
@@ -4392,9 +4392,8 @@ void addReplyCommandInfo(client *c, struct redisCommand *cmd, int show_full_name
}
}

/* Output the representation of a Redis command. Used by the COMMAND DETAILS. */
void addReplyCommandDetails(client *c, struct redisCommand *cmd, int show_full_name) {
UNUSED(show_full_name);
/* Output the representation of a Redis command. Used by the COMMAND DOCS. */
void addReplyCommandDocs(client *c, struct redisCommand *cmd) {
/* Count our reply len so we don't have to use deferred reply. */
long maplen = 3;
if (cmd->complexity) maplen++;
@@ -4441,7 +4440,7 @@ void addReplyCommandDetails(client *c, struct redisCommand *cmd, int show_full_n
}
if (cmd->subcommands_dict) {
addReplyBulkCString(c, "subcommands");
addReplyCommandSubCommands(c, cmd, addReplyCommandDetails, 1);
addReplyCommandSubCommands(c, cmd, addReplyCommandDocs, 1);
}
}

@@ -4487,7 +4486,7 @@ void commandCommand(client *c) {
addReplyArrayLen(c, dictSize(server.commands));
di = dictGetIterator(server.commands);
while ((de = dictNext(di)) != NULL) {
addReplyCommandInfo(c, dictGetVal(de), 0);
addReplyCommandInfo(c, dictGetVal(de));
}
dictReleaseIterator(di);
}
@@ -4601,12 +4600,12 @@ void commandInfoCommand(client *c) {
int i;
addReplyArrayLen(c, c->argc-2);
for (i = 2; i < c->argc; i++) {
addReplyCommandInfo(c, lookupCommandBySds(c->argv[i]->ptr), 1);
addReplyCommandInfo(c, lookupCommandBySds(c->argv[i]->ptr));
}
}

/* COMMAND DETAILS [<command-name> ...] */
void commandDetailsCommand(client *c) {
/* COMMAND DOCS [<command-name> ...] */
void commandDocsCommand(client *c) {
int i;
if (c->argc == 2) {
/* Reply with an array of all commands */
@@ -4617,7 +4616,7 @@ void commandDetailsCommand(client *c) {
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
addReplyBulkCString(c, cmd->name);
addReplyCommandDetails(c, cmd, 0);
addReplyCommandDocs(c, cmd);
}
dictReleaseIterator(di);
} else {
@@ -4632,7 +4631,7 @@ void commandDetailsCommand(client *c) {
addReplyBulkSds(c, getFullCommandName(cmd));
else
addReplyBulkCString(c, cmd->name);
addReplyCommandDetails(c, cmd, 1);
addReplyCommandDocs(c, cmd);
numcmds++;
}
setDeferredMapLen(c,replylen,numcmds);
@@ -4655,6 +4654,8 @@ void commandHelpCommand(client *c) {
" Return a list of all commands in this Redis server.",
"INFO <command-name> [<command-name> ...]",
" Return details about multiple Redis commands.",
"DOCS [<command-name> ...]",
" Return documentation details about all / multiple Redis commands.",
"GETKEYS <full-command>",
" Return the keys from a full Redis command.",
NULL
2 changes: 1 addition & 1 deletion src/server.h
Original file line number Diff line number Diff line change
@@ -3095,7 +3095,7 @@ void commandListCommand(client *c);
void commandInfoCommand(client *c);
void commandGetKeysCommand(client *c);
void commandHelpCommand(client *c);
void commandDetailsCommand(client *c);
void commandDocsCommand(client *c);
void setCommand(client *c);
void setnxCommand(client *c);
void setexCommand(client *c);
16 changes: 8 additions & 8 deletions tests/unit/moduleapi/subcommands.tcl
Original file line number Diff line number Diff line change
@@ -8,15 +8,15 @@ start_server {tags {"modules"}} {
set command_reply [r command info subcommands.bitarray]
set first_cmd [lindex $command_reply 0]
set subcmds_in_command [lsort [lindex $first_cmd 9]]
assert_equal [lindex $subcmds_in_command 0] {get -2 module 1 1 1 {} {} {{flags read 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] {set -2 module 1 1 1 {} {} {{flags write 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 read 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 write begin_search {type index spec {index 1}} find_keys {type range spec {lastkey 0 keystep 1 limit 0}}}} {}}

# Verify that module subcommands are displayed correctly in COMMAND DETAILS
set details_reply [r command details subcommands.bitarray]
set details [dict create {*}[lindex $details_reply 1]]
set subcmds_in_cmd_detail [dict create {*}[dict get $details subcommands]]
assert_equal [dict get $subcmds_in_cmd_detail "get"] {summary {} since {} group module}
assert_equal [dict get $subcmds_in_cmd_detail "set"] {summary {} since {} group module}
# Verify that module subcommands are displayed correctly in COMMAND DOCS
set docs_reply [r command docs subcommands.bitarray]
set docs [dict create {*}[lindex $docs_reply 1]]
set subcmds_in_cmd_docs [dict create {*}[dict get $docs subcommands]]
assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|get"] {summary {} since {} group module}
assert_equal [dict get $subcmds_in_cmd_docs "subcommands.bitarray|set"] {summary {} since {} group module}
}

test "Module pure-container command fails on arity error" {
42 changes: 21 additions & 21 deletions utils/generate-commands-json.py
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ def convert_keyspec(spec):
return spec


def convert_entry_to_objects_array(container, cmd, details):
def convert_entry_to_objects_array(cmd, docs):
"""Transform the JSON output of `COMMAND` to a friendlier format.

cmd is the output of `COMMAND` as follows:
@@ -45,7 +45,7 @@ def convert_entry_to_objects_array(container, cmd, details):
9. key-specs (as of Redis 7.0)
10. subcommands (as of Redis 7.0)

details is the output of `COMMAND DETAILS`, which holds a map of additional metadata
docs is the output of `COMMAND DOCS`, which holds a map of additional metadata

This returns a list with a dict for the command and per each of its
subcommands. Each dict contains one key, the command's full name, with a
@@ -61,35 +61,35 @@ def convert_entry_to_objects_array(container, cmd, details):
hints = cmd[7]
keyspecs = cmd[8]
subcommands = cmd[9] if len(cmd) > 9 else []
key = f'{container} {name}' if container else name
key = name.replace('|', ' ')

subcommand_details = details.pop('subcommands', [])
rep.extend([convert_entry_to_objects_array(name, x, subcommand_details[x[0]])[0] for x in subcommands])
subcommand_docs = docs.pop('subcommands', [])
rep.extend([convert_entry_to_objects_array(x, subcommand_docs[x[0]])[0] for x in subcommands])

# The command's value is ordered so the interesting stuff that we care about
# is at the start. Optional `None` and empty list values are filtered out.
value = OrderedDict()
value['summary'] = details.pop('summary')
value['since'] = details.pop('since')
value['group'] = details.pop('group')
set_if_not_none_or_empty(value, 'complexity', details.pop('complexity', None))
set_if_not_none_or_empty(value, 'deprecated_since', details.pop('deprecated_since', None))
set_if_not_none_or_empty(value, 'replaced_by', details.pop('replaced_by', None))
set_if_not_none_or_empty(value, 'history', details.pop('history', []))
value['summary'] = docs.pop('summary')
value['since'] = docs.pop('since')
value['group'] = docs.pop('group')
set_if_not_none_or_empty(value, 'complexity', docs.pop('complexity', None))
set_if_not_none_or_empty(value, 'deprecated_since', docs.pop('deprecated_since', None))
set_if_not_none_or_empty(value, 'replaced_by', docs.pop('replaced_by', None))
set_if_not_none_or_empty(value, 'history', docs.pop('history', []))
set_if_not_none_or_empty(value, 'acl_categories', acl_categories)
value['arity'] = arity
set_if_not_none_or_empty(value, 'key_specs',
[convert_keyspec(x) for x in keyspecs])
set_if_not_none_or_empty(value, 'arguments',
[convert_argument(x) for x in details.pop('arguments', [])])
[convert_argument(x) for x in docs.pop('arguments', [])])
set_if_not_none_or_empty(value, 'command_flags', command_flags)
set_if_not_none_or_empty(value, 'doc_flags', details.pop('doc_flags', []))
set_if_not_none_or_empty(value, 'doc_flags', docs.pop('doc_flags', []))
set_if_not_none_or_empty(value, 'hints', hints)

# All remaining details key-value tuples, if any, are appended to the command
# All remaining docs key-value tuples, if any, are appended to the command
# to be future-proof.
while len(details) > 0:
(k, v) = details.popitem()
while len(docs) > 0:
(k, v) = docs.popitem()
value[k] = v

obj[key] = value
@@ -102,7 +102,7 @@ def convert_entry_to_objects_array(container, cmd, details):
# MAIN
if __name__ == '__main__':
opts = {
'description': 'Transform the output from `redis-cli --json` using COMMAND and COMMAND DETAILS to a single commands.json format.',
'description': 'Transform the output from `redis-cli --json` using COMMAND and COMMAND DOCS to a single commands.json format.',
'epilog': f'Usage example: {argv[0]} --cli src/redis-cli --port 6379 > commands.json'
}
parser = argparse.ArgumentParser(**opts)
@@ -118,12 +118,12 @@ def convert_entry_to_objects_array(container, cmd, details):
stdout, stderr = p.communicate()
commands = json.loads(stdout)

p = subprocess.Popen([args.cli, '-h', args.host, '-p', str(args.port), '--json', 'command', 'details'], stdout=subprocess.PIPE)
p = subprocess.Popen([args.cli, '-h', args.host, '-p', str(args.port), '--json', 'command', 'docs'], stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
details = json.loads(stdout)
docs = json.loads(stdout)

for entry in commands:
cmd = convert_entry_to_objects_array(None, entry, details[entry[0]])
cmd = convert_entry_to_objects_array(entry, docs[entry[0]])
cmds.extend(cmd)

# The final output is a dict of all commands, ordered by name.