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

Auto-generate the command table from JSON files #9656

Merged
merged 63 commits into from Dec 15, 2021

Conversation

guybe7
Copy link
Collaborator

@guybe7 guybe7 commented Oct 20, 2021

This is the final fix of #9359, except #9845, and #9876, and #9944 which will be handled separately.

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:
NOTE, many of these where later moved to COMMAND DOCS, see: #10056)

  • 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 Define and document command tips #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 Commands' reply schema #9845)

more details on these can be found in redis/redis-doc#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.

Note: these where later moved, see: #10108)

  • 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 #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)

TODO

  • Some information is missing from the JSON files (mostly summary and complexity info) - will go over them manually
  • Open a GH issue: define and document general hints for RM_SetCommandHints - Define and document command tips #9876
  • Create a PR for redis-doc, describing all the changes in this PR (namely the changes in COMMAND) - Refresh COMMAND, add topics: command-args, key-specs redis-doc#1697
  • Add test: per command, all key-specs are referred to, and every key arg reference appears at least once in the key-specs array - tested manually, I don't see a value in writing an automatic test for this.
  • Write the argument array where missing:
    'fcall',
    'fcall_ro',
    'georadius_ro',
    'georadiusbymember_ro',
    'substr',
    'xsetid'
    internal command that take arguments (but we will not bother to add them to the jsons)
    'pfdebug',
    'replconf',
    'restore-asking',
  • Generate the arg array of COMMAND LIST
  • Come up with a way to prevent people from using the jsons directly, and force them to use COMMAND: all flag and acl cats will be uppercase in the jsons, the script to generate commands.c will lower them
  • Create jsons for new function command(s) + the new form of CONFIG SET
  • fix moduleAPI for arguments ("value" is gone from c file but not from h. fix test module)
  • Yossi's comments
  • add CMD_MODULE to COMMAND INFO

Open issues:

Argument's "value" and "name"

how should we call what is now called “value” (of an argument)?
it is one of the following:

  • a string to display when rendering the syntax
  • an array of sub-arguments

also, do we want to get rid of “name” ?it is indeed redundant (args are ordered so we can identify one by its nested index)

update: this is what we decided:

  1. “name” is mandatory. if the arg doesn’t have subargs this is the string we use for rendering the syntax
  2. get rid of “value”. if the command has subargs they will appear under “arguments”

basically, the players in syntax rendering are “name” (if arg doesn’t have subargs) , “token” (if exists), and “arguments” (if exists)

Usage of versions in module API

When a module sets "since", should it be the debut Redis version or the module version?

update: for now, the code suggests it's the module version

Optional args interchangeability

the rule is that all optional args, at the same nesting level, may interchange as long as there isn’t a non-optional arg between them
e.g.

SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]

all optionals can interchange

XPENDING key group [[IDLE min-idle-time] start end count [consumer]]

IDLE min-idle-time and consumer can’t interchange because there’s a non-optional between them

the only exception is MIGRATE, which actually has two forms:
either

MIGRATE host port key destination-db timeout [COPY] [REPLACE] [AUTH password] [AUTH2 username password]

or

MIGRATE host port "" destination-db timeout [COPY] [REPLACE] [AUTH password] [AUTH2 username password] KEYS key [key ...]

we have two options:

  1. say f*ck it, it’s only one exception, the cli hints will be wrong
  2. rearrange the command json file so that it’ll look like MIGRATE host port (key destination-db timeout [COPY] [REPLACE] [AUTH password] [AUTH2 username password] | "" destination-db timeout [COPY] [REPLACE] [AUTH password] [AUTH2 username password] KEYS key [key ...])
  3. add an ad-hoc feature in the JSON that says "this arg is optional, but only if (another arg) is not (value)"

update: for now we've decided to go with (1)

@oranagra oranagra linked an issue Oct 20, 2021 that may be closed by this pull request
script generate help.h
@guybe7 guybe7 changed the title WIP gen command table + help.h WIP gen command table Oct 27, 2021
src/server.h Outdated Show resolved Hide resolved
@guybe7
Copy link
Collaborator Author

guybe7 commented Oct 27, 2021

comments from Oran:

  1. remove both help.h and commands.c from makefile. document somewhere how to re-gen commands.c
  2. in the command json files, i suggest to put the arity, acl, key_specs and all other short metadata before the arguments (put the short ones first and the long ones last) (@itamarhaber FYI - it's better than alphabetical ordering - basically have "arguments" as the last field, and sort alphabetically the N-1 first fields)
  3. i'd rather use "resp3" over just "3" in the json files.
  4. re-arrange return_types: it should be a list oj objects. each object will have "summary", "type" (which can be either a string or an object of RESP2 and RESP3) and "contant_value" (optional, only for simple string)
  5. delete return_summary from jsons
  6. split the doc in commands.c - the first half should be next to struct redisCommand.
  7. try to avoid using the preprocessor in commands.c
  8. turn all non-freetext fields into enum (command.group, arg.type and return_type.type) we will no longer need redisCommandValueArgType (see Auto-generate the command table from JSON files #9656 (comment))

@guybe7
Copy link
Collaborator Author

guybe7 commented Oct 29, 2021

(note to self: pushed fixes to all above, except (7))

utils/generate-command-code.py Outdated Show resolved Hide resolved
src/commands/set.json Outdated Show resolved Hide resolved
src/commands/command.json Outdated Show resolved Hide resolved
@yossigo
Copy link
Member

yossigo commented Nov 17, 2021

@guybe7 I'm addressing the open issues at the top comment:

another issue is whether we want to provide a full-blown description of the return-type or just the immediate one? e.g. ZPOP returns an array of (member, score) tuples in RESP3 and a flat-map array for RESP2. in the current design we will just state that it returns an "array" for both, is that enough?

Because the differences between RESP2 and RESP3 are only semantic, I tend to say it probably makes sense to have a single returns element (and description) that refers to both versions, and branch only the type part as you listed in the example. If this proves to be not good enough, I'd just consider the entire returns element version specific and include an optional versions field that lists applicable protocol versions (with default being versions: [2,3]). I think we need to choose one of these two approaches but not both.

we have a few open questions about pure tokens:
should they have a "type"? usually, type is used to validate some free-test user input
is the token itself be under "token" or under "value"? if it's under "value" we can declare it as a mandatory attribute

I think there can be a type: flag that indicates there are no user supplied values, and the specified value indicates that flag was set.

@jhelbaum
Copy link
Contributor

@guybe7

I've started to work on integrating these changes into redis-cli.c. So far I see at least two issues:

  1. static char *commandGroups[] is missing - this is used for outputting command help.
  2. struct redisCommand is defined in server.h. This requires redis-cli.c to include server.h for the struct definition. Aside from the massive header coupling this introduces (server.h desperately needs to be broken up regardless), it's incompatible with redis-cli.c, which uses the hiredis implementation of sds.h instead of the one included from server.h. I'm not familiar with the differences between these implementations so I don't know how important this is. But it seems to me a poor design choice to require a client library to include server.h. The command-related definitions should be broken out into a separate header file.

oranagra pushed a commit that referenced this pull request Dec 30, 2021
Add blank before lists, so that they will be rendered as lists.

Commands RM_GetCommand and RM_CreateSubcommand were added in #9656.
yossigo pushed a commit that referenced this pull request Jan 6, 2022
With this rule, the script to generate commands.c from JSON runs whenever commands.o is built if any of commands/*.json are modified. Without such rule, it's easy to forget to run the script when updating the JSON files.

It's a follow-up on #9656 and #9951.
"type": "integer",
"token": "PX"
},
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

                    {
                        "name": "unix-time",
                        "type": "integer",
                        "token": "EXAT"
                    },
                    {
                        "name": "unix-time",
                        "type": "integer",
                        "token": "PXAT"
                    },

should we change it to?

                    {
                        "name": "timestamp",
                        "type": "unix-time",
                        "token": "EXAT"
                    },
                    {
                        "name": "milliseconds-timestamp",
                        "type": "unix-time",
                        "token": "PXAT"
                    },

i see in set.json

                    {
                        "name": "unix-time-seconds",
                        "type": "unix-time",
                        "token": "EXAT",
                        "since": "6.2.0"
                    },
                    {
                        "name": "unix-time-milliseconds",
                        "type": "unix-time",
                        "token": "PXAT",
                        "since": "6.2.0"
                    },

in pexpireat.json or expireat.json

            {
                "name": "milliseconds-timestamp",
                "type": "unix-time"
            },

            {
                "name": "timestamp",
                "type": "unix-time"
            },

make a demo commit: 6d1b509

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enjoy-binbin if you find a problem or an inconsistency on an already merged PR, please just post a PR (rather than a comment in the closed one).

p.s. i'm not sure if milliseconds timestamp could be technically considered a "unix-time" but i see it already is in some fields, so let's at least be consistent.

panjf2000 pushed a commit to panjf2000/redis that referenced this pull request Feb 3, 2022
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 redis#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 redis#9845)

more details on these can be found in redis/redis-doc#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 redis#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: Itamar Haber <itamar@garantiadata.com>
panjf2000 pushed a commit to panjf2000/redis that referenced this pull request Feb 3, 2022
Following redis#9656, this script generates a "commands.json" file from the output
of the new COMMAND. The output of this script is used in redis/redis-doc#1714
and by redis/redis-io#259. This also converts a couple of rogue dashes (in 
'key-specs' and 'multiple-token' flags) to underscores (continues redis#9959).
panjf2000 pushed a commit to panjf2000/redis that referenced this pull request Feb 3, 2022
Add blank before lists, so that they will be rendered as lists.

Commands RM_GetCommand and RM_CreateSubcommand were added in redis#9656.
panjf2000 pushed a commit to panjf2000/redis that referenced this pull request Feb 3, 2022
With this rule, the script to generate commands.c from JSON runs whenever commands.o is built if any of commands/*.json are modified. Without such rule, it's easy to forget to run the script when updating the JSON files.

It's a follow-up on redis#9656 and redis#9951.
oranagra added a commit that referenced this pull request Feb 5, 2022
…10043)

This is a followup to #9656 and implements the following step mentioned in that PR:

* When possible, extract all the help and completion tips from COMMAND DOCS (Redis 7.0 and up)
* If COMMAND DOCS fails, use the static help.h compiled into redis-cli.
* Supplement additional command names from COMMAND (pre-Redis 7.0)

The last step is needed to add module command and other non-standard commands.

This PR does not change the interactive hinting mechanism, which still uses only the param
strings to provide somewhat unreliable and inconsistent command hints (see #8084).
That task is left for a future PR. 

Co-authored-by: Oran Agra <oran@redislabs.com>
@oranagra oranagra mentioned this pull request Mar 1, 2022
{
"name": "key2",
"type": "key",
"key_spec_index": 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"key_spec_index": 1
"key_spec_index": 0

@guybe7 Please confirm it should be 0 here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yangbodong22011 you're right. there's only one key-spec, so it can't be at index 1.
please make a PR.

p.s. how did you find that? maybe we can benefit from some mechanism that validates there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oranagra Just found out while looking at the code, I haven't checked the rest of the commands.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make a PR.

We might have to think of other ways to make sure other commands don't go wrong, rather than fixing one by one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, @yangbodong22011 will you create a PR, or should I?

and yes, we need to figure out a better way to spot these issues... got any ideas?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to check whether the index is valid in generate-command-code.py and whether the key_spec contains an unused index.

rename and renamenx also has problem. (have 2 index, but just use key_spec_index: 0)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to check whether the index is valid in generate-command-code.py and whether the key_spec contains an unused index.

i write a small ugly test script, and find out these, you guys can check on it, in case you guys forget this one

  • LCS: key2 key_spec_index error

  • RENAME: have unused key_spec

  • RENAMENX: have unused key_spec

  • WATCH: key_specs missing flags?

  • SUNSUBSCRIBE / SPUBLISH / SSUBSCRIBE are NOT_KEY, i suppose they are ok

  • RESTORE-ASKING: missing arguments, should it be like RESTORE?

  • PFDEBUG: missing arguments, this is a debug command, so i suppose it is ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enjoy-binbin How "ugly" is your script? I think we need it in a test case or a separate CI job.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@enjoy-binbin thank you for reminding us.. looks like we forgot it.
so first thing's first, let's fix them, please make a PR.

if your test script is tcl based, let's put it in the test suite.
alternatively, we could set some assertions in the C code.
or put another test in the utils folder and somehow run it in some CI or manually from time to time (not preferred)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just write some logic in generate-command-code and the check spec_key logic base on my understanding
i just wanted to see if we have any other commands that have the same problem first (we can fix it first and then try to find a way to check it)
ok, i can summery it and then let's discuss it in other thread.

can see it in #10779

oranagra pushed a commit that referenced this pull request Aug 21, 2022
sundb added a commit to sundb/redis that referenced this pull request Sep 7, 2022
commit bdf7696
Author: sundb <sundbcn@gmail.com>
Date:   Tue Sep 6 19:50:14 2022 +0800

    Fix test fail in eextern test mode

commit 6ada91d
Author: sundb <sundbcn@gmail.com>
Date:   Tue Sep 6 17:39:52 2022 +0800

    Optimize comment in test

commit b972e06
Author: sundb <sundbcn@gmail.com>
Date:   Tue Sep 6 17:30:29 2022 +0800

    Fix crash due to wrongly split quicklist node

commit 8e51c95
Author: sundb <sundbcn@gmail.com>
Date:   Tue Sep 6 16:01:08 2022 +0800

    Fix crash due to delete entry from  compress quicklist node

commit 9022375
Author: Ariel Shtul <ariel.shtul@redislabs.com>
Date:   Tue Aug 23 09:37:59 2022 +0300

    [PERF] use snprintf once in addReplyDouble (redis#11093)

    The previous implementation calls `snprintf` twice, the second time used to
    'memcpy' the output of the first, which could be a very large string.
    The new implementation reserves space for the protocol header ahead
    of the formatted double, and then prepends the string length ahead of it.

    Measured improvement of simple ZADD of some 25%.

commit 407b5c9
Author: Itamar Haber <itamar@redis.com>
Date:   Mon Aug 22 15:05:01 2022 +0300

    Replaces a made-up term with a real one (redis#11169)

commit a534983
Author: Itamar Haber <itamar@redis.com>
Date:   Sun Aug 21 18:15:53 2022 +0300

    Changes "lower" to "capital" in GEO units history notes (redis#11164)

    A overlooked mistake in the redis#11162

commit ca6aead
Author: yourtree <56780191+yourtree@users.noreply.github.com>
Date:   Sun Aug 21 22:55:45 2022 +0800

    Support setlocale via CONFIG operation. (redis#11059)

    Till now Redis officially supported tuning it via environment variable see redis#1074.
    But we had other requests to allow changing it at runtime, see redis#799, and redis#11041.

    Note that `strcoll()` is used as Lua comparison function and also for comparison of
    certain string objects in Redis, which leads to a problem that, in different regions,
    for some characters, the result may be different. Below is an example.
    ```
    127.0.0.1:6333> SORT test alpha
    1) "<"
    2) ">"
    3) ","
    4) "*"
    127.0.0.1:6333> CONFIG GET locale-collate
    1) "locale-collate"
    2) ""
    127.0.0.1:6333> CONFIG SET locale-collate 1
    (error) ERR CONFIG SET failed (possibly related to argument 'locale')
    127.0.0.1:6333> CONFIG SET locale-collate C
    OK
    127.0.0.1:6333> SORT test alpha
    1) "*"
    2) ","
    3) "<"
    4) ">"
    ```
    That will cause accidental code compatibility issues for Lua scripts and some
    Redis commands. This commit creates a new config parameter to control the
    local environment which only affects `Collate` category. Above shows how it
    affects `SORT` command, and below shows the influence on Lua scripts.
    ```
    127.0.0.1:6333> CONFIG GET locale-collate
    1) " locale-collate"
    2) "C"
    127.0.0.1:6333> EVAL "return ',' < '*'" 0
    (nil)
    127.0.0.1:6333> CONFIG SET locale-collate ""
    OK
    127.0.0.1:6333> EVAL "return ',' < '*'" 0
    (integer) 1
    ```

    Co-authored-by: calvincjli <calvincjli@tencent.com>
    Co-authored-by: Oran Agra <oran@redislabs.com>

commit 31ef410
Author: Itamar Haber <itamar@redis.com>
Date:   Sun Aug 21 17:01:17 2022 +0300

    Adds historical note about lower-case geo units support (redis#11162)

    This change was part of redis#9656 (Redis 7.0)

commit c3a0253
Author: Wen Hui <wen.hui.ware@gmail.com>
Date:   Sun Aug 21 00:52:57 2022 -0400

    Add 2 test cases for XDEL and XGROUP CREATE command (redis#11137)

    This PR includes 2 missed test cases of XDEL and XGROUP CREATE command

    1. one test case: XDEL delete multiply id once
    2. 3 test cases:  XGROUP CREATE has ENTRIESREAD parameter,
       which equal 0 (special positive number), 3 and negative value.

    Co-authored-by: Ubuntu <lucas.guang.yang1@huawei.com>
    Co-authored-by: Oran Agra <oran@redislabs.com>
    Co-authored-by: Binbin <binloveplay1314@qq.com>
oranagra pushed a commit to oranagra/redis that referenced this pull request Sep 21, 2022
This change was part of redis#9656 (Redis 7.0)

(cherry picked from commit 31ef410)
oranagra pushed a commit that referenced this pull request Sep 21, 2022
This change was part of #9656 (Redis 7.0)

(cherry picked from commit 31ef410)
oranagra added a commit that referenced this pull request Mar 11, 2023
Work in progress towards implementing a reply schema as part of COMMAND DOCS, see #9845
Since ironing the details of the reply schema of each and every command can take a long time, we
would like to merge this PR when the infrastructure is ready, and let this mature in the unstable branch.
Meanwhile the changes of this PR are internal, they are part of the repo, but do not affect the produced build.

### Background
In #9656 we add a lot of information about Redis commands, but we are missing information about the replies

### Motivation
1. Documentation. This is the primary goal.
2. It should be possible, based on the output of COMMAND, to be able to generate client code in typed
  languages. In order to do that, we need Redis to tell us, in detail, what each reply looks like.
3. We would like to build a fuzzer that verifies the reply structure (for now we use the existing
  testsuite, see the "Testing" section)

### Schema
The idea is to supply some sort of schema for the various replies of each command.
The schema will describe the conceptual structure of the reply (for generated clients), as defined in RESP3.
Note that the reply structure itself may change, depending on the arguments (e.g. `XINFO STREAM`, with
and without the `FULL` modifier)
We decided to use the standard json-schema (see https://json-schema.org/) as the reply-schema.

Example for `BZPOPMIN`:
```
"reply_schema": {
    "oneOf": [
        {
            "description": "Timeout reached and no elements were popped.",
            "type": "null"
        },
        {
            "description": "The keyname, popped member, and its score.",
            "type": "array",
            "minItems": 3,
            "maxItems": 3,
            "items": [
                {
                    "description": "Keyname",
                    "type": "string"
                },
                {
                    "description": "Member",
                    "type": "string"
                },
                {
                    "description": "Score",
                    "type": "number"
                }
            ]
        }
    ]
}
```

#### Notes
1.  It is ok that some commands' reply structure depends on the arguments and it's the caller's responsibility
  to know which is the relevant one. this comes after looking at other request-reply systems like OpenAPI,
  where the reply schema can also be oneOf and the caller is responsible to know which schema is the relevant one.
2. The reply schemas will describe RESP3 replies only. even though RESP3 is structured, we want to use reply
  schema for documentation (and possibly to create a fuzzer that validates the replies)
3. For documentation, the description field will include an explanation of the scenario in which the reply is sent,
  including any relation to arguments. for example, for `ZRANGE`'s two schemas we will need to state that one
  is with `WITHSCORES` and the other is without.
4. For documentation, there will be another optional field "notes" in which we will add a short description of
  the representation in RESP2, in case it's not trivial (RESP3's `ZRANGE`'s nested array vs. RESP2's flat
  array, for example)

Given the above:
1. We can generate the "return" section of all commands in [redis-doc](https://redis.io/commands/)
  (given that "description" and "notes" are comprehensive enough)
2. We can generate a client in a strongly typed language (but the return type could be a conceptual
  `union` and the caller needs to know which schema is relevant). see the section below for RESP2 support.
3. We can create a fuzzer for RESP3.

### Limitations (because we are using the standard json-schema)
The problem is that Redis' replies are more diverse than what the json format allows. This means that,
when we convert the reply to a json (in order to validate the schema against it), we lose information (see
the "Testing" section below).
The other option would have been to extend the standard json-schema (and json format) to include stuff
like sets, bulk-strings, error-string, etc. but that would mean also extending the schema-validator - and that
seemed like too much work, so we decided to compromise.

Examples:
1. We cannot tell the difference between an "array" and a "set"
2. We cannot tell the difference between simple-string and bulk-string
3. we cannot verify true uniqueness of items in commands like ZRANGE: json-schema doesn't cover the
  case of two identical members with different scores (e.g. `[["m1",6],["m1",7]]`) because `uniqueItems`
  compares (member,score) tuples and not just the member name. 

### Testing
This commit includes some changes inside Redis in order to verify the schemas (existing and future ones)
are indeed correct (i.e. describe the actual response of Redis).
To do that, we added a debugging feature to Redis that causes it to produce a log of all the commands
it executed and their replies.
For that, Redis needs to be compiled with `-DLOG_REQ_RES` and run with
`--reg-res-logfile <file> --client-default-resp 3` (the testsuite already does that if you run it with
`--log-req-res --force-resp3`)
You should run the testsuite with the above args (and `--dont-clean`) in order to make Redis generate
`.reqres` files (same dir as the `stdout` files) which contain request-response pairs.
These files are later on processed by `./utils/req-res-log-validator.py` which does:
1. Goes over req-res files, generated by redis-servers, spawned by the testsuite (see logreqres.c)
2. For each request-response pair, it validates the response against the request's reply_schema
  (obtained from the extended COMMAND DOCS)
5. In order to get good coverage of the Redis commands, and all their different replies, we chose to use
  the existing redis test suite, rather than attempt to write a fuzzer.

#### Notes about RESP2
1. We will not be able to use the testing tool to verify RESP2 replies (we are ok with that, it's time to
  accept RESP3 as the future RESP)
2. Since the majority of the test suite is using RESP2, and we want the server to reply with RESP3
  so that we can validate it, we will need to know how to convert the actual reply to the one expected.
   - number and boolean are always strings in RESP2 so the conversion is easy
   - objects (maps) are always a flat array in RESP2
   - others (nested array in RESP3's `ZRANGE` and others) will need some special per-command
     handling (so the client will not be totally auto-generated)

Example for ZRANGE:
```
"reply_schema": {
    "anyOf": [
        {
            "description": "A list of member elements",
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "string"
            }
        },
        {
            "description": "Members and their scores. Returned in case `WITHSCORES` was used.",
            "notes": "In RESP2 this is returned as a flat array",
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "array",
                "minItems": 2,
                "maxItems": 2,
                "items": [
                    {
                        "description": "Member",
                        "type": "string"
                    },
                    {
                        "description": "Score",
                        "type": "number"
                    }
                ]
            }
        }
    ]
}
```

### Other changes
1. Some tests that behave differently depending on the RESP are now being tested for both RESP,
  regardless of the special log-req-res mode ("Pub/Sub PING" for example)
2. Update the history field of CLIENT LIST
3. Added basic tests for commands that were not covered at all by the testsuite

### TODO

- [x] (maybe a different PR) add a "condition" field to anyOf/oneOf schemas that refers to args. e.g.
  when `SET` return NULL, the condition is `arguments.get||arguments.condition`, for `OK` the condition
  is `!arguments.get`, and for `string` the condition is `arguments.get` - #11896
- [x] (maybe a different PR) also run `runtest-cluster` in the req-res logging mode
- [x] add the new tests to GH actions (i.e. compile with `-DLOG_REQ_RES`, run the tests, and run the validator)
- [x] (maybe a different PR) figure out a way to warn about (sub)schemas that are uncovered by the output
  of the tests - #11897
- [x] (probably a separate PR) add all missing schemas
- [x] check why "SDOWN is triggered by misconfigured instance replying with errors" fails with --log-req-res
- [x] move the response transformers to their own file (run both regular, cluster, and sentinel tests - need to
  fight with the tcl including mechanism a bit)
- [x] issue: module API - #11898
- [x] (probably a separate PR): improve schemas: add `required` to `object`s - #11899

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
Co-authored-by: Hanna Fadida <hanna.fadida@redislabs.com>
Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: Shaya Potter <shaya@redislabs.com>
oranagra added a commit that referenced this pull request Mar 30, 2023
Now that the command argument specs are available at runtime (#9656), this PR addresses
#8084 by implementing a complete solution for command-line hinting in `redis-cli`.

It correctly handles nearly every case in Redis's complex command argument definitions, including
`BLOCK` and `ONEOF` arguments, reordering of optional arguments, and repeated arguments
(even when followed by mandatory arguments). It also validates numerically-typed arguments.
It may not correctly handle all possible combinations of those, but overall it is quite robust.

Arguments are only matched after the space bar is typed, so partial word matching is not
supported - that proved to be more confusing than helpful. When the user's current input
cannot be matched against the argument specs, hinting is disabled.

Partial support has been implemented for legacy (pre-7.0) servers that do not support
`COMMAND DOCS`, by falling back to a statically-compiled command argument table.
On startup, if the server does not support `COMMAND DOCS`, `redis-cli` will now issue
an `INFO SERVER` command to retrieve the server version (unless `HELLO` has already
been sent, in which case the server version will be extracted from the reply to `HELLO`).
The server version will be used to filter the commands and arguments in the command table,
removing those not supported by that version of the server. However, the static table only
includes core Redis commands, so with a legacy server hinting will not be supported for
module commands. The auto generated help.h and the scripts that generates it are gone.

Command and argument tables for the server and CLI use different structs, due primarily
to the need to support different runtime data. In order to generate code for both, macros
have been added to `commands.def` (previously `commands.c`) to make it possible to
configure the code generation differently for different use cases (one linked with redis-server,
and one with redis-cli).

Also adding a basic testing framework for the command hints based on new (undocumented)
command line options to `redis-cli`: `--test_hint 'INPUT'` prints out the command-line hint for
a given input string, and `--test_hint_file <filename>` runs a suite of test cases for the hinting
mechanism. The test suite is in `tests/assets/test_cli_hint_suite.txt`, and it is run from
`tests/integration/redis-cli.tcl`.

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Mixficsol pushed a commit to Mixficsol/redis that referenced this pull request Apr 12, 2023
Mixficsol pushed a commit to Mixficsol/redis that referenced this pull request Apr 12, 2023
Work in progress towards implementing a reply schema as part of COMMAND DOCS, see redis#9845
Since ironing the details of the reply schema of each and every command can take a long time, we
would like to merge this PR when the infrastructure is ready, and let this mature in the unstable branch.
Meanwhile the changes of this PR are internal, they are part of the repo, but do not affect the produced build.

### Background
In redis#9656 we add a lot of information about Redis commands, but we are missing information about the replies

### Motivation
1. Documentation. This is the primary goal.
2. It should be possible, based on the output of COMMAND, to be able to generate client code in typed
  languages. In order to do that, we need Redis to tell us, in detail, what each reply looks like.
3. We would like to build a fuzzer that verifies the reply structure (for now we use the existing
  testsuite, see the "Testing" section)

### Schema
The idea is to supply some sort of schema for the various replies of each command.
The schema will describe the conceptual structure of the reply (for generated clients), as defined in RESP3.
Note that the reply structure itself may change, depending on the arguments (e.g. `XINFO STREAM`, with
and without the `FULL` modifier)
We decided to use the standard json-schema (see https://json-schema.org/) as the reply-schema.

Example for `BZPOPMIN`:
```
"reply_schema": {
    "oneOf": [
        {
            "description": "Timeout reached and no elements were popped.",
            "type": "null"
        },
        {
            "description": "The keyname, popped member, and its score.",
            "type": "array",
            "minItems": 3,
            "maxItems": 3,
            "items": [
                {
                    "description": "Keyname",
                    "type": "string"
                },
                {
                    "description": "Member",
                    "type": "string"
                },
                {
                    "description": "Score",
                    "type": "number"
                }
            ]
        }
    ]
}
```

#### Notes
1.  It is ok that some commands' reply structure depends on the arguments and it's the caller's responsibility
  to know which is the relevant one. this comes after looking at other request-reply systems like OpenAPI,
  where the reply schema can also be oneOf and the caller is responsible to know which schema is the relevant one.
2. The reply schemas will describe RESP3 replies only. even though RESP3 is structured, we want to use reply
  schema for documentation (and possibly to create a fuzzer that validates the replies)
3. For documentation, the description field will include an explanation of the scenario in which the reply is sent,
  including any relation to arguments. for example, for `ZRANGE`'s two schemas we will need to state that one
  is with `WITHSCORES` and the other is without.
4. For documentation, there will be another optional field "notes" in which we will add a short description of
  the representation in RESP2, in case it's not trivial (RESP3's `ZRANGE`'s nested array vs. RESP2's flat
  array, for example)

Given the above:
1. We can generate the "return" section of all commands in [redis-doc](https://redis.io/commands/)
  (given that "description" and "notes" are comprehensive enough)
2. We can generate a client in a strongly typed language (but the return type could be a conceptual
  `union` and the caller needs to know which schema is relevant). see the section below for RESP2 support.
3. We can create a fuzzer for RESP3.

### Limitations (because we are using the standard json-schema)
The problem is that Redis' replies are more diverse than what the json format allows. This means that,
when we convert the reply to a json (in order to validate the schema against it), we lose information (see
the "Testing" section below).
The other option would have been to extend the standard json-schema (and json format) to include stuff
like sets, bulk-strings, error-string, etc. but that would mean also extending the schema-validator - and that
seemed like too much work, so we decided to compromise.

Examples:
1. We cannot tell the difference between an "array" and a "set"
2. We cannot tell the difference between simple-string and bulk-string
3. we cannot verify true uniqueness of items in commands like ZRANGE: json-schema doesn't cover the
  case of two identical members with different scores (e.g. `[["m1",6],["m1",7]]`) because `uniqueItems`
  compares (member,score) tuples and not just the member name. 

### Testing
This commit includes some changes inside Redis in order to verify the schemas (existing and future ones)
are indeed correct (i.e. describe the actual response of Redis).
To do that, we added a debugging feature to Redis that causes it to produce a log of all the commands
it executed and their replies.
For that, Redis needs to be compiled with `-DLOG_REQ_RES` and run with
`--reg-res-logfile <file> --client-default-resp 3` (the testsuite already does that if you run it with
`--log-req-res --force-resp3`)
You should run the testsuite with the above args (and `--dont-clean`) in order to make Redis generate
`.reqres` files (same dir as the `stdout` files) which contain request-response pairs.
These files are later on processed by `./utils/req-res-log-validator.py` which does:
1. Goes over req-res files, generated by redis-servers, spawned by the testsuite (see logreqres.c)
2. For each request-response pair, it validates the response against the request's reply_schema
  (obtained from the extended COMMAND DOCS)
5. In order to get good coverage of the Redis commands, and all their different replies, we chose to use
  the existing redis test suite, rather than attempt to write a fuzzer.

#### Notes about RESP2
1. We will not be able to use the testing tool to verify RESP2 replies (we are ok with that, it's time to
  accept RESP3 as the future RESP)
2. Since the majority of the test suite is using RESP2, and we want the server to reply with RESP3
  so that we can validate it, we will need to know how to convert the actual reply to the one expected.
   - number and boolean are always strings in RESP2 so the conversion is easy
   - objects (maps) are always a flat array in RESP2
   - others (nested array in RESP3's `ZRANGE` and others) will need some special per-command
     handling (so the client will not be totally auto-generated)

Example for ZRANGE:
```
"reply_schema": {
    "anyOf": [
        {
            "description": "A list of member elements",
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "string"
            }
        },
        {
            "description": "Members and their scores. Returned in case `WITHSCORES` was used.",
            "notes": "In RESP2 this is returned as a flat array",
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "array",
                "minItems": 2,
                "maxItems": 2,
                "items": [
                    {
                        "description": "Member",
                        "type": "string"
                    },
                    {
                        "description": "Score",
                        "type": "number"
                    }
                ]
            }
        }
    ]
}
```

### Other changes
1. Some tests that behave differently depending on the RESP are now being tested for both RESP,
  regardless of the special log-req-res mode ("Pub/Sub PING" for example)
2. Update the history field of CLIENT LIST
3. Added basic tests for commands that were not covered at all by the testsuite

### TODO

- [x] (maybe a different PR) add a "condition" field to anyOf/oneOf schemas that refers to args. e.g.
  when `SET` return NULL, the condition is `arguments.get||arguments.condition`, for `OK` the condition
  is `!arguments.get`, and for `string` the condition is `arguments.get` - redis#11896
- [x] (maybe a different PR) also run `runtest-cluster` in the req-res logging mode
- [x] add the new tests to GH actions (i.e. compile with `-DLOG_REQ_RES`, run the tests, and run the validator)
- [x] (maybe a different PR) figure out a way to warn about (sub)schemas that are uncovered by the output
  of the tests - redis#11897
- [x] (probably a separate PR) add all missing schemas
- [x] check why "SDOWN is triggered by misconfigured instance replying with errors" fails with --log-req-res
- [x] move the response transformers to their own file (run both regular, cluster, and sentinel tests - need to
  fight with the tcl including mechanism a bit)
- [x] issue: module API - redis#11898
- [x] (probably a separate PR): improve schemas: add `required` to `object`s - redis#11899

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
Co-authored-by: Hanna Fadida <hanna.fadida@redislabs.com>
Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: Shaya Potter <shaya@redislabs.com>
enjoy-binbin pushed a commit to enjoy-binbin/redis that referenced this pull request Jul 31, 2023
enjoy-binbin pushed a commit to enjoy-binbin/redis that referenced this pull request Jul 31, 2023
Work in progress towards implementing a reply schema as part of COMMAND DOCS, see redis#9845
Since ironing the details of the reply schema of each and every command can take a long time, we
would like to merge this PR when the infrastructure is ready, and let this mature in the unstable branch.
Meanwhile the changes of this PR are internal, they are part of the repo, but do not affect the produced build.

### Background
In redis#9656 we add a lot of information about Redis commands, but we are missing information about the replies

### Motivation
1. Documentation. This is the primary goal.
2. It should be possible, based on the output of COMMAND, to be able to generate client code in typed
  languages. In order to do that, we need Redis to tell us, in detail, what each reply looks like.
3. We would like to build a fuzzer that verifies the reply structure (for now we use the existing
  testsuite, see the "Testing" section)

### Schema
The idea is to supply some sort of schema for the various replies of each command.
The schema will describe the conceptual structure of the reply (for generated clients), as defined in RESP3.
Note that the reply structure itself may change, depending on the arguments (e.g. `XINFO STREAM`, with
and without the `FULL` modifier)
We decided to use the standard json-schema (see https://json-schema.org/) as the reply-schema.

Example for `BZPOPMIN`:
```
"reply_schema": {
    "oneOf": [
        {
            "description": "Timeout reached and no elements were popped.",
            "type": "null"
        },
        {
            "description": "The keyname, popped member, and its score.",
            "type": "array",
            "minItems": 3,
            "maxItems": 3,
            "items": [
                {
                    "description": "Keyname",
                    "type": "string"
                },
                {
                    "description": "Member",
                    "type": "string"
                },
                {
                    "description": "Score",
                    "type": "number"
                }
            ]
        }
    ]
}
```

#### Notes
1.  It is ok that some commands' reply structure depends on the arguments and it's the caller's responsibility
  to know which is the relevant one. this comes after looking at other request-reply systems like OpenAPI,
  where the reply schema can also be oneOf and the caller is responsible to know which schema is the relevant one.
2. The reply schemas will describe RESP3 replies only. even though RESP3 is structured, we want to use reply
  schema for documentation (and possibly to create a fuzzer that validates the replies)
3. For documentation, the description field will include an explanation of the scenario in which the reply is sent,
  including any relation to arguments. for example, for `ZRANGE`'s two schemas we will need to state that one
  is with `WITHSCORES` and the other is without.
4. For documentation, there will be another optional field "notes" in which we will add a short description of
  the representation in RESP2, in case it's not trivial (RESP3's `ZRANGE`'s nested array vs. RESP2's flat
  array, for example)

Given the above:
1. We can generate the "return" section of all commands in [redis-doc](https://redis.io/commands/)
  (given that "description" and "notes" are comprehensive enough)
2. We can generate a client in a strongly typed language (but the return type could be a conceptual
  `union` and the caller needs to know which schema is relevant). see the section below for RESP2 support.
3. We can create a fuzzer for RESP3.

### Limitations (because we are using the standard json-schema)
The problem is that Redis' replies are more diverse than what the json format allows. This means that,
when we convert the reply to a json (in order to validate the schema against it), we lose information (see
the "Testing" section below).
The other option would have been to extend the standard json-schema (and json format) to include stuff
like sets, bulk-strings, error-string, etc. but that would mean also extending the schema-validator - and that
seemed like too much work, so we decided to compromise.

Examples:
1. We cannot tell the difference between an "array" and a "set"
2. We cannot tell the difference between simple-string and bulk-string
3. we cannot verify true uniqueness of items in commands like ZRANGE: json-schema doesn't cover the
  case of two identical members with different scores (e.g. `[["m1",6],["m1",7]]`) because `uniqueItems`
  compares (member,score) tuples and not just the member name. 

### Testing
This commit includes some changes inside Redis in order to verify the schemas (existing and future ones)
are indeed correct (i.e. describe the actual response of Redis).
To do that, we added a debugging feature to Redis that causes it to produce a log of all the commands
it executed and their replies.
For that, Redis needs to be compiled with `-DLOG_REQ_RES` and run with
`--reg-res-logfile <file> --client-default-resp 3` (the testsuite already does that if you run it with
`--log-req-res --force-resp3`)
You should run the testsuite with the above args (and `--dont-clean`) in order to make Redis generate
`.reqres` files (same dir as the `stdout` files) which contain request-response pairs.
These files are later on processed by `./utils/req-res-log-validator.py` which does:
1. Goes over req-res files, generated by redis-servers, spawned by the testsuite (see logreqres.c)
2. For each request-response pair, it validates the response against the request's reply_schema
  (obtained from the extended COMMAND DOCS)
5. In order to get good coverage of the Redis commands, and all their different replies, we chose to use
  the existing redis test suite, rather than attempt to write a fuzzer.

#### Notes about RESP2
1. We will not be able to use the testing tool to verify RESP2 replies (we are ok with that, it's time to
  accept RESP3 as the future RESP)
2. Since the majority of the test suite is using RESP2, and we want the server to reply with RESP3
  so that we can validate it, we will need to know how to convert the actual reply to the one expected.
   - number and boolean are always strings in RESP2 so the conversion is easy
   - objects (maps) are always a flat array in RESP2
   - others (nested array in RESP3's `ZRANGE` and others) will need some special per-command
     handling (so the client will not be totally auto-generated)

Example for ZRANGE:
```
"reply_schema": {
    "anyOf": [
        {
            "description": "A list of member elements",
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "string"
            }
        },
        {
            "description": "Members and their scores. Returned in case `WITHSCORES` was used.",
            "notes": "In RESP2 this is returned as a flat array",
            "type": "array",
            "uniqueItems": true,
            "items": {
                "type": "array",
                "minItems": 2,
                "maxItems": 2,
                "items": [
                    {
                        "description": "Member",
                        "type": "string"
                    },
                    {
                        "description": "Score",
                        "type": "number"
                    }
                ]
            }
        }
    ]
}
```

### Other changes
1. Some tests that behave differently depending on the RESP are now being tested for both RESP,
  regardless of the special log-req-res mode ("Pub/Sub PING" for example)
2. Update the history field of CLIENT LIST
3. Added basic tests for commands that were not covered at all by the testsuite

### TODO

- [x] (maybe a different PR) add a "condition" field to anyOf/oneOf schemas that refers to args. e.g.
  when `SET` return NULL, the condition is `arguments.get||arguments.condition`, for `OK` the condition
  is `!arguments.get`, and for `string` the condition is `arguments.get` - redis#11896
- [x] (maybe a different PR) also run `runtest-cluster` in the req-res logging mode
- [x] add the new tests to GH actions (i.e. compile with `-DLOG_REQ_RES`, run the tests, and run the validator)
- [x] (maybe a different PR) figure out a way to warn about (sub)schemas that are uncovered by the output
  of the tests - redis#11897
- [x] (probably a separate PR) add all missing schemas
- [x] check why "SDOWN is triggered by misconfigured instance replying with errors" fails with --log-req-res
- [x] move the response transformers to their own file (run both regular, cluster, and sentinel tests - need to
  fight with the tcl including mechanism a bit)
- [x] issue: module API - redis#11898
- [x] (probably a separate PR): improve schemas: add `required` to `object`s - redis#11899

Co-authored-by: Ozan Tezcan <ozantezcan@gmail.com>
Co-authored-by: Hanna Fadida <hanna.fadida@redislabs.com>
Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: Shaya Potter <shaya@redislabs.com>
enjoy-binbin added a commit to enjoy-binbin/redis that referenced this pull request Jan 22, 2024
This looks like it was missing from 7.0 (redis#9656).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approval-needed Waiting for core team approval to be merged release-notes indication that this issue needs to be mentioned in the release notes state:major-decision Requires core team consensus
Projects
Archived in project
7.0
Done
Development

Successfully merging this pull request may close these issues.

Redis as the SSOT of its commands
10 participants