Skip to main content

yo_resp/dispatch/
table.rs

1//! The command table: what each command is called, how many arguments it
2//! takes, where its keys are, and what `COMMAND` reports about it.
3//!
4//! Every field here was read out of a running Redis 8.8 with `COMMAND INFO`
5//! rather than written from the documentation, because this is the table a
6//! client library builds its own routing from. A cluster aware client asks
7//! `COMMAND` where the keys are and then decides which node to send a command
8//! to, so an arity or a key position that is off by one does not produce a
9//! wrong error message, it produces a client that sends `MSET` to the wrong
10//! shard. The summaries are ours, since those are the one field nobody parses.
11//!
12//! `cargo xtask check` compares this table against `commands.toml` in both
13//! directions, so a command cannot be dispatched without a storage plan and
14//! cannot claim `wire = "verified"` without an entry here.
15
16/// Everything `COMMAND` has to be able to say about one command.
17#[derive(Debug, Clone, Copy)]
18pub struct Spec {
19    /// The name, lower case, which is how `COMMAND` reports it whatever case
20    /// the client used.
21    pub name: &'static str,
22    /// Redis's arity: a positive number is exact, a negative one is a minimum
23    /// of its magnitude, and both count the command name itself.
24    pub arity: i32,
25    /// The command flags, in the order `COMMAND INFO` lists them.
26    pub flags: &'static [&'static str],
27    /// The first argument that is a key, or zero when there are none.
28    pub first_key: i32,
29    /// The last argument that is a key, negative counting back from the end.
30    pub last_key: i32,
31    /// How far apart the keys are, for the commands that take pairs.
32    pub step: i32,
33    /// The ACL categories, which are what `COMMAND LIST FILTERBY ACLCAT` reads.
34    pub acl: &'static [&'static str],
35    /// The Redis this command first appeared in.
36    pub since: &'static str,
37    /// The cost, in the shape `COMMAND DOCS` uses.
38    pub complexity: &'static str,
39    /// One line about what it does, in our words.
40    pub summary: &'static str,
41    /// The group in `commands.toml`, which is how the two files are compared.
42    pub group: &'static str,
43}
44
45/// Read only, fast, one key at argument one, which is most of the getters.
46const READ_FAST: &[&str] = &["readonly", "fast"];
47/// A write that allocates, fast, one key at argument one.
48const WRITE_FAST_OOM: &[&str] = &["write", "denyoom", "fast"];
49/// A write that allocates and is not counted as fast.
50const WRITE_OOM: &[&str] = &["write", "denyoom"];
51/// A write that allocates and is not for ordinary clients, which is `PFDEBUG`.
52const WRITE_OOM_ADMIN: &[&str] = &["write", "denyoom", "admin"];
53/// The read side categories.
54const AC_READ_FAST: &[&str] = &["@read", "@string", "@fast"];
55/// The bitmap read side, for the two that answer without walking the value.
56const AC_BIT_READ_FAST: &[&str] = &["@read", "@bitmap", "@fast"];
57/// The bitmap read side for the ones that walk it.
58const AC_BIT_READ: &[&str] = &["@read", "@bitmap", "@slow"];
59/// The bitmap write side. Redis counts none of these as fast, `SETBIT` included.
60const AC_BIT_WRITE: &[&str] = &["@write", "@bitmap", "@slow"];
61
62/// The sketch write side, which Redis counts as fast for `PFADD` alone.
63const AC_HLL_WRITE_FAST: &[&str] = &["@write", "@hyperloglog", "@fast"];
64/// The sketch write side for the ones that walk every register.
65const AC_HLL_WRITE: &[&str] = &["@write", "@hyperloglog", "@slow"];
66/// The sketch read side, which is `PFCOUNT` and only `PFCOUNT`.
67const AC_HLL_READ: &[&str] = &["@read", "@hyperloglog", "@slow"];
68/// The two that are not for clients, and are tagged so an ACL can say so.
69const AC_HLL_ADMIN: &[&str] = &["@hyperloglog", "@admin", "@slow", "@dangerous"];
70/// The read side categories for the ones that walk the value.
71const AC_READ_SLOW: &[&str] = &["@read", "@string", "@slow"];
72/// The write side categories.
73const AC_WRITE_FAST: &[&str] = &["@write", "@string", "@fast"];
74/// The write side categories for the ones that are not counted as fast.
75const AC_WRITE_SLOW: &[&str] = &["@write", "@string", "@slow"];
76/// A write that frees rather than allocates, so Redis does not mark it denyoom.
77const WRITE_FAST: &[&str] = &["write", "fast"];
78/// The set read side, for the ones that answer without walking the members.
79const AC_SET_READ_FAST: &[&str] = &["@read", "@set", "@fast"];
80/// The set read side for the ones that walk the members.
81const AC_SET_READ_SLOW: &[&str] = &["@read", "@set", "@slow"];
82/// The set write side.
83const AC_SET_WRITE_FAST: &[&str] = &["@write", "@set", "@fast"];
84/// The set write side for the ones that walk whole sets to decide what to
85/// write, which is the whole `*STORE` family.
86const AC_SET_WRITE_SLOW: &[&str] = &["@write", "@set", "@slow"];
87/// The hash read side, for the ones that answer without walking the fields.
88const AC_HASH_READ_FAST: &[&str] = &["@read", "@hash", "@fast"];
89/// The hash read side for the ones that walk the fields.
90const AC_HASH_READ_SLOW: &[&str] = &["@read", "@hash", "@slow"];
91/// The hash write side.
92const AC_HASH_WRITE_FAST: &[&str] = &["@write", "@hash", "@fast"];
93/// `HIMPORT`, which is a container and so has no write category of its own. The
94/// write flags and the key live on its `SET` subcommand, which the table does
95/// not carry any more than it carries `OBJECT ENCODING`.
96const AC_HASH_SLOW: &[&str] = &["@hash", "@slow"];
97/// Read only and not counted as fast, which is every list read that walks.
98const READ_SLOW: &[&str] = &["readonly"];
99/// A write that is not counted as fast and does not allocate, which on the list
100/// side is `LREM` and `LTRIM` and nothing else.
101const WRITE_SLOW: &[&str] = &["write"];
102/// The list read side, for the two that answer without walking the elements.
103const AC_LIST_READ_FAST: &[&str] = &["@read", "@list", "@fast"];
104/// The list read side for the ones that walk.
105const AC_LIST_READ_SLOW: &[&str] = &["@read", "@list", "@slow"];
106/// The list write side, which is the pushes and the pops. Redis counts a push
107/// as fast even though it can split a chunk, because the split is amortised.
108const AC_LIST_WRITE_FAST: &[&str] = &["@write", "@list", "@fast"];
109/// The list write side for the ones whose cost is the length of the list.
110const AC_LIST_WRITE_SLOW: &[&str] = &["@write", "@list", "@slow"];
111/// The five that can wait, which carry a category of their own so that an ACL
112/// can say "this user may not park a connection" without naming five commands.
113const AC_LIST_WRITE_BLOCKING: &[&str] = &["@write", "@list", "@slow", "@blocking"];
114/// The sorted set read side, for the ones that answer without walking members.
115const AC_ZSET_READ_FAST: &[&str] = &["@read", "@sortedset", "@fast"];
116/// The sorted set read side for the ones that walk members.
117const AC_ZSET_READ_SLOW: &[&str] = &["@read", "@sortedset", "@slow"];
118/// The sorted set write side.
119const AC_ZSET_WRITE_FAST: &[&str] = &["@write", "@sortedset", "@fast"];
120/// The sorted set write side for the ones whose cost is the size of the window
121/// they touch, which is the removals and `ZRANGESTORE`.
122const AC_ZSET_WRITE_SLOW: &[&str] = &["@write", "@sortedset", "@slow"];
123/// The two sorted set pops that can wait, which Redis counts as fast because
124/// each of them takes one member.
125const AC_ZSET_BLOCKING_FAST: &[&str] = &["@write", "@sortedset", "@fast", "@blocking"];
126/// And `BZMPOP`, whose cost is the number of keys named and the count popped.
127const AC_ZSET_BLOCKING_SLOW: &[&str] = &["@write", "@sortedset", "@slow", "@blocking"];
128/// The array read side, for the ones whose cost is the number of indices named
129/// and not the size of the array.
130/// The geo read side. Redis counts none of these as fast, not even GEODIST,
131/// which is two probes and some arithmetic.
132const AC_GEO_READ: &[&str] = &["@read", "@geo", "@slow"];
133/// The geo write side, which is GEOADD and the four forms that can store.
134const AC_GEO_WRITE: &[&str] = &["@write", "@geo", "@slow"];
135/// No ACL categories at all, which is what the vector set module registers.
136///
137/// It is the only group here with an empty list and it is not an omission. The
138/// module never calls the categories in, so a real server has no `vectorset`
139/// category to name, `ACL CAT vectorset` is an unknown category there, and
140/// `COMMAND LIST FILTERBY ACLCAT read` does not answer `VSIM`. Inventing a
141/// category would make a rule written against this server mean something it
142/// does not mean against a real one, which is the one thing a compatible ACL
143/// must not do, and it would do it in the direction that grants access rather
144/// than the direction that refuses it.
145const AC_NONE: &[&str] = &[];
146/// A vector set read, with the `module` flag every vector set command carries
147/// for the same reason the JSON and Bloom ones do.
148const VECTOR_READ: &[&str] = &["readonly", "module"];
149/// A vector set read the module also marks fast, which is the ones that answer
150/// about one element without searching.
151const VECTOR_READ_FAST: &[&str] = &["readonly", "module", "fast"];
152/// `VADD`, which is the one command here that can grow the set.
153const VECTOR_WRITE_OOM: &[&str] = &["write", "denyoom", "module"];
154/// `VREM`, which only ever frees, so the module does not mark it denyoom.
155const VECTOR_WRITE: &[&str] = &["write", "module"];
156/// `VSETATTR`, which the module marks fast and, though it takes a string off
157/// the wire, does not mark denyoom.
158const VECTOR_WRITE_FAST: &[&str] = &["write", "module", "fast"];
159/// The one category nearly every search command is in. The module registers
160/// `@search` on its own for most of them rather than pairing it with `@read` or
161/// `@write` the way RedisJSON does, which is the module's own answer to
162/// `COMMAND INFO` and is copied rather than tidied up.
163const AC_SEARCH: &[&str] = &["@search"];
164/// `FT.DROPINDEX` and the two spellings of it, which the module puts in the
165/// dangerous category because dropping an index with `DD` deletes the documents
166/// it followed.
167const AC_SEARCH_DROP: &[&str] = &["@write", "@slow", "@dangerous", "@search"];
168/// `FT._DROPIFX`, which is the same command with a shorter list. The module
169/// leaves the slow and dangerous categories off this one and there is no
170/// reading of it that makes it less dangerous than the others.
171const AC_SEARCH_WRITE: &[&str] = &["@write", "@search"];
172/// `FT._LIST`, which the module puts in `@admin` because listing every index is
173/// a question about the server rather than about anything in it.
174const AC_SEARCH_LIST: &[&str] = &["@admin", "@slow", "@search"];
175/// A search read, with the `module` flag every search command carries for the
176/// same reason the JSON and vector set ones do.
177const SEARCH_READ: &[&str] = &["readonly", "module"];
178/// A search write that takes a schema or an alias off the wire.
179const SEARCH_WRITE_OOM: &[&str] = &["write", "denyoom", "module"];
180/// A search write that only ever frees, which is dropping an index or an alias.
181const SEARCH_WRITE: &[&str] = &["write", "module"];
182/// The JSON read side. Two categories and no speed one, which is RedisJSON's
183/// own answer to `COMMAND INFO` and not an omission: the module registers
184/// `@read @json` and leaves it there.
185const AC_JSON_READ: &[&str] = &["@read", "@json"];
186/// The JSON write side, the same way.
187const AC_JSON_WRITE: &[&str] = &["@write", "@json"];
188/// A JSON read, with the `module` flag every RedisJSON command carries. It is
189/// there because the command came from a module on a real server, and a client
190/// that reads the flags off `COMMAND INFO` should see the same list from both.
191const JSON_READ: &[&str] = &["readonly", "module"];
192/// A JSON write that does not grow the document.
193const JSON_WRITE: &[&str] = &["write", "module"];
194/// A JSON write that does, which is the four that take a value off the wire.
195const JSON_WRITE_OOM: &[&str] = &["write", "denyoom", "module"];
196/// A JSON read whose key is not where the arity says it is, which is
197/// `JSON.DEBUG` and its subcommand.
198const JSON_READ_MOVABLE: &[&str] = &["readonly", "module", "movablekeys"];
199/// The Bloom filter read side. RedisBloom marks all of these `@fast` on top of
200/// the two categories, including the ones that walk the whole filter, which is
201/// the module's own answer to `COMMAND INFO` and is copied rather than judged.
202const AC_BLOOM_READ: &[&str] = &["@read", "@bloom"];
203/// The two reads the module also puts in `@fast` as a category of its own,
204/// which is `BF.INFO` and `BF.CARD`. Neither reads the bits at all.
205const AC_BLOOM_READ_FAST: &[&str] = &["@read", "@fast", "@bloom"];
206/// The Bloom filter write side.
207const AC_BLOOM_WRITE: &[&str] = &["@write", "@bloom"];
208/// `BF.RESERVE`, which is the one write that does no hashing.
209const AC_BLOOM_WRITE_FAST: &[&str] = &["@write", "@fast", "@bloom"];
210/// A Bloom read, with the `module` flag every RedisBloom command carries for
211/// the same reason the JSON ones do.
212const BLOOM_READ: &[&str] = &["readonly", "module", "fast"];
213/// A Bloom write. All of them can grow the filter, `BF.LOADCHUNK` included, so
214/// all of them deny out of memory.
215const BLOOM_WRITE: &[&str] = &["write", "denyoom", "module"];
216/// The cuckoo filter read side, which is the same three flags under a category
217/// of its own. `CF.COMPACT` is in here too, because the module has it down as a
218/// read even though it moves fingerprints between filters.
219const AC_CUCKOO_READ: &[&str] = &["@read", "@cuckoo"];
220/// The one read the module also calls fast, which is `CF.INFO`.
221const AC_CUCKOO_READ_FAST: &[&str] = &["@read", "@fast", "@cuckoo"];
222/// The cuckoo filter write side.
223const AC_CUCKOO_WRITE: &[&str] = &["@write", "@cuckoo"];
224/// `CF.RESERVE`, which is the one write that does no hashing.
225const AC_CUCKOO_WRITE_FAST: &[&str] = &["@write", "@fast", "@cuckoo"];
226/// A cuckoo read, with the `module` flag the whole family carries.
227const CUCKOO_READ: &[&str] = &["readonly", "module", "fast"];
228/// A cuckoo write, all of which can grow the chain.
229const CUCKOO_WRITE: &[&str] = &["write", "denyoom", "module"];
230/// `CF.DEL`, the one write that only ever frees a slot and so does not deny out
231/// of memory.
232const CUCKOO_DELETE: &[&str] = &["write", "module", "fast"];
233/// The count min sketch read side. The module does not call either of these
234/// fast in the flags even though it puts `CMS.INFO` in the fast category, which
235/// is a disagreement in RedisBloom's own table and is copied as it stands.
236const AC_CMS_READ: &[&str] = &["@read", "@cms"];
237/// `CMS.INFO`, which is the one read in the fast category.
238const AC_CMS_READ_FAST: &[&str] = &["@read", "@fast", "@cms"];
239/// The count min sketch write side.
240const AC_CMS_WRITE: &[&str] = &["@write", "@cms"];
241/// The two constructors, which allocate and then do nothing.
242const AC_CMS_WRITE_FAST: &[&str] = &["@write", "@fast", "@cms"];
243/// A count min sketch read, which carries `module` and not `fast`.
244const CMS_READ: &[&str] = &["readonly", "module"];
245/// A count min sketch write. The table never grows after it is made, so the two
246/// that can allocate are the constructors, and all four deny out of memory
247/// because the module marks all four.
248const CMS_WRITE: &[&str] = &["write", "denyoom", "module"];
249/// The top k read side, which the module does not call fast except for the one
250/// that reads four numbers off the header.
251const AC_TOPK_READ: &[&str] = &["@read", "@topk"];
252/// `TOPK.INFO`.
253const AC_TOPK_READ_FAST: &[&str] = &["@read", "@fast", "@topk"];
254/// The top k write side, which is the two that count things.
255const AC_TOPK_WRITE: &[&str] = &["@write", "@topk"];
256/// `TOPK.RESERVE`, the one write that only allocates.
257const AC_TOPK_WRITE_FAST: &[&str] = &["@write", "@fast", "@topk"];
258/// A top k read, which carries `module` and not `fast`.
259const TOPK_READ: &[&str] = &["readonly", "module"];
260/// A top k write. The table never grows after it is made, so the only one that
261/// can allocate is the constructor, and all three deny out of memory because the
262/// module marks all three.
263const TOPK_WRITE: &[&str] = &["write", "denyoom", "module"];
264/// The t digest read side for the ones that answer off the header or off one
265/// sweep of the centroids, which the module calls fast and which is all of them
266/// bar the trimmed mean.
267const AC_TDIGEST_READ_FAST: &[&str] = &["@read", "@fast", "@tdigest"];
268/// `TDIGEST.TRIMMED_MEAN`, the one read the module does not call fast.
269const AC_TDIGEST_READ: &[&str] = &["@read", "@tdigest"];
270/// The t digest write side for the two that only shape a digest.
271const AC_TDIGEST_WRITE_FAST: &[&str] = &["@write", "@fast", "@tdigest"];
272/// The two that move weight around.
273const AC_TDIGEST_WRITE: &[&str] = &["@write", "@tdigest"];
274/// A t digest read, which carries `module` and not `fast`.
275const TDIGEST_READ: &[&str] = &["readonly", "module"];
276/// A t digest write. All four deny out of memory because all four can end up
277/// asking for a set of centroids.
278const TDIGEST_WRITE: &[&str] = &["write", "denyoom", "module"];
279/// `TDIGEST.MERGE`, whose keys are behind a count and so cannot be found by the
280/// first, last and step the rest of the table uses.
281const TDIGEST_MERGE: &[&str] = &["write", "denyoom", "module", "movablekeys"];
282/// The time series read side for the two that answer off the header or off the
283/// last sample, which the module calls fast.
284const AC_TS_READ_FAST: &[&str] = &["@read", "@fast", "@timeseries"];
285/// The time series write side, which is everything that puts a sample in or
286/// changes what a series does with one.
287const AC_TS_WRITE: &[&str] = &["@write", "@timeseries"];
288/// The time series read side for the two that walk a span, which the module
289/// does not call fast.
290const AC_TS_READ: &[&str] = &["@read", "@timeseries"];
291/// `TS.CREATE`, the one write the module calls fast, because making an empty
292/// series is an allocation and nothing else.
293const AC_TS_WRITE_FAST: &[&str] = &["@write", "@fast", "@timeseries"];
294/// A time series read, which carries `module` and not `fast` whatever the ACL
295/// category says. That disagreement is RedisTimeSeries's own and is copied as it
296/// stands, the same way the count min sketch one is.
297const TS_READ: &[&str] = &["readonly", "module"];
298/// The two joined reads, which carry their keys behind a count and so cannot be
299/// described by the first, last and step triple.
300const TS_READ_MOVABLE: &[&str] = &["readonly", "module", "movablekeys"];
301/// A time series write, all of which can ask for another chunk.
302const TS_WRITE: &[&str] = &["write", "denyoom", "module"];
303/// `TS.DEL`, the one write that only ever frees samples and so does not deny out
304/// of memory.
305const TS_DELETE: &[&str] = &["write", "module"];
306/// `TS.CREATERULE`, which is the one time series write that says `fast` in the
307/// flags rather than only in the ACL categories, and the one that never asks for
308/// room of its own.
309const TS_RULE: &[&str] = &["write", "module", "fast"];
310/// The graph read side, for the ones that answer without walking the plane.
311const AC_GRAPH_READ_FAST: &[&str] = &["@read", "@graph", "@fast"];
312/// The graph read side for the ones that walk it.
313const AC_GRAPH_READ_SLOW: &[&str] = &["@read", "@graph", "@slow"];
314/// The graph write side, all of which are a probe and a run.
315const AC_GRAPH_WRITE_FAST: &[&str] = &["@write", "@graph", "@fast"];
316const AC_ARRAY_READ_FAST: &[&str] = &["@read", "@array", "@fast"];
317/// The array read side for `ARGETRANGE`, which answers once per position in the
318/// range and so costs the range rather than the population.
319const AC_ARRAY_READ_SLOW: &[&str] = &["@read", "@array", "@slow"];
320/// The array write side.
321const AC_ARRAY_WRITE_FAST: &[&str] = &["@write", "@array", "@fast"];
322/// The array write side for `ARDELRANGE`, the one array command Redis does not
323/// mark fast.
324const AC_ARRAY_WRITE_SLOW: &[&str] = &["@write", "@array", "@slow"];
325/// The stream read side, for the ones that answer without walking entries.
326const AC_STREAM_READ_FAST: &[&str] = &["@read", "@stream", "@fast"];
327/// The stream read side for the ranges, whose cost is what they return.
328const AC_STREAM_READ_SLOW: &[&str] = &["@read", "@stream", "@slow"];
329/// The stream write side, which is everything that appends, deletes or moves an
330/// entry between pending lists.
331const AC_STREAM_WRITE_FAST: &[&str] = &["@write", "@stream", "@fast"];
332/// The stream write side for `XTRIM`, whose cost is what it removes.
333const AC_STREAM_WRITE_SLOW: &[&str] = &["@write", "@stream", "@slow"];
334/// `XREAD`, which waits and does not write.
335const AC_STREAM_BLOCKING_READ: &[&str] = &["@read", "@stream", "@slow", "@blocking"];
336/// `XREADGROUP`, which waits and does write, since handing an entry to a
337/// consumer puts it on that consumer's pending list.
338const AC_STREAM_BLOCKING_WRITE: &[&str] = &["@write", "@stream", "@slow", "@blocking"];
339/// `XGROUP` and `XINFO`, whose keys are on the subcommand and whose categories
340/// are therefore only the container's.
341const AC_STREAM_CONTAINER: &[&str] = &["@slow"];
342/// The two stream reads, whose keys come after `STREAMS` and are half of what
343/// follows it, so nothing positional can find them.
344const READ_BLOCKING_MOVABLE: &[&str] = &["readonly", "blocking", "movablekeys"];
345/// The same for `XREADGROUP`, which is a write.
346const WRITE_BLOCKING_MOVABLE: &[&str] = &["write", "blocking", "movablekeys"];
347/// Read only and not counted as fast, for a command whose keys are counted
348/// rather than positioned, so a client has to read the key specs to route it.
349const READ_MOVABLE: &[&str] = &["readonly", "movablekeys"];
350/// The same for a write, which is the three store forms.
351const WRITE_MOVABLE: &[&str] = &["write", "denyoom", "movablekeys"];
352/// `MIGRATE`, which is the one movable key write that is not `denyoom`.
353///
354/// It only ever frees here, since the local key goes away and nothing arrives,
355/// so a server with no room left can still migrate its way out of trouble. That
356/// is the same reasoning that leaves the flag off `DEL`.
357const MIGRATE_FLAGS: &[&str] = &["write", "movablekeys"];
358/// The connection commands' categories.
359const AC_CONN: &[&str] = &["@fast", "@connection"];
360/// The keyspace read side, which is `EXISTS` and `TYPE`.
361const AC_KEY_READ: &[&str] = &["@keyspace", "@read", "@fast"];
362/// The keyspace reads that walk something, which is `SCAN` and `RANDOMKEY`.
363const AC_KEY_READ_SLOW: &[&str] = &["@keyspace", "@read", "@slow"];
364/// And `KEYS`, which is the same walk without a bound on it and is the one read
365/// in this group Redis calls dangerous.
366const AC_KEY_READ_ALL: &[&str] = &["@keyspace", "@read", "@slow", "@dangerous"];
367/// The keyspace writes that are allowed to cost what the value costs. `DEL`
368/// frees on the spot and `COPY` clones a body, and `RENAME` is in here with
369/// them even though it moves thirteen bytes, because Redis says slow for it and
370/// this list is Redis's list rather than ours.
371const AC_KEY_WRITE_SLOW: &[&str] = &["@keyspace", "@write", "@slow"];
372/// `UNLINK`, which Redis does count as fast because it does not, and the
373/// expiry writers, which move a deadline and never touch a value.
374const AC_KEY_WRITE_FAST: &[&str] = &["@keyspace", "@write", "@fast"];
375/// The two that empty a database, which are in the dangerous category.
376const AC_KEY_FLUSH: &[&str] = &["@keyspace", "@write", "@slow", "@dangerous"];
377/// `SWAPDB`, which is fast and dangerous at the same time. It is two pointer
378/// writes and it changes what every connected client is looking at, so Redis
379/// puts it in `@fast` and in `@dangerous` and both are right.
380const AC_SWAPDB: &[&str] = &["@keyspace", "@write", "@fast", "@dangerous"];
381/// `RESTORE`, which is dangerous for a reason worth saying out loud: it is the
382/// one command that takes bytes from a client and turns them into a value
383/// without any command ever having built it. `DUMP` is only `@read`, because
384/// reading a value out is no more than reading it.
385const AC_RESTORE: &[&str] = &["@keyspace", "@write", "@slow", "@dangerous"];
386/// `WAIT` and `WAITAOF`, which are the two commands that block on something
387/// that is not a key. They are not in `@keyspace` at all, because they name no
388/// key and read nothing, and they carry `@blocking` for the same reason the
389/// five list commands do.
390const AC_WAIT: &[&str] = &["@slow", "@blocking", "@connection"];
391/// `SORT`, which names three type categories because it takes any of the three
392/// and a write because of `STORE`. Redis leaves `@keyspace` off both of these
393/// even though the command lives in that group, and this list is Redis's.
394const AC_SORT_WRITE: &[&str] = &[
395    "@write",
396    "@set",
397    "@sortedset",
398    "@list",
399    "@slow",
400    "@dangerous",
401];
402/// `SORT_RO`, which is the same list with the write turned into a read.
403const AC_SORT_READ: &[&str] = &[
404    "@read",
405    "@set",
406    "@sortedset",
407    "@list",
408    "@slow",
409    "@dangerous",
410];
411
412/// Every command this server answers, in the order the groups ship.
413pub static COMMANDS: &[Spec] = &[
414    // ------------------------------------------------------------- strings
415    Spec {
416        name: "set",
417        arity: -3,
418        flags: WRITE_OOM,
419        first_key: 1,
420        last_key: 1,
421        step: 1,
422        acl: AC_WRITE_SLOW,
423        since: "1.0.0",
424        complexity: "O(1)",
425        summary: "Set a key to a string value, whatever it held before.",
426        group: "string",
427    },
428    Spec {
429        name: "get",
430        arity: 2,
431        flags: READ_FAST,
432        first_key: 1,
433        last_key: 1,
434        step: 1,
435        acl: AC_READ_FAST,
436        since: "1.0.0",
437        complexity: "O(1)",
438        summary: "The string value of a key.",
439        group: "string",
440    },
441    Spec {
442        name: "getset",
443        arity: 3,
444        flags: WRITE_FAST_OOM,
445        first_key: 1,
446        last_key: 1,
447        step: 1,
448        acl: AC_WRITE_FAST,
449        since: "1.0.0",
450        complexity: "O(1)",
451        summary: "Set a key and hand back what it held.",
452        group: "string",
453    },
454    Spec {
455        name: "getdel",
456        arity: 2,
457        flags: &["write", "fast"],
458        first_key: 1,
459        last_key: 1,
460        step: 1,
461        acl: AC_WRITE_FAST,
462        since: "6.2.0",
463        complexity: "O(1)",
464        summary: "Read a key and delete it in the same step.",
465        group: "string",
466    },
467    Spec {
468        name: "getex",
469        arity: -2,
470        flags: &["write", "fast"],
471        first_key: 1,
472        last_key: 1,
473        step: 1,
474        acl: AC_WRITE_FAST,
475        since: "6.2.0",
476        complexity: "O(1)",
477        summary: "Read a key and change its deadline in the same step.",
478        group: "string",
479    },
480    Spec {
481        name: "setnx",
482        arity: 3,
483        flags: WRITE_FAST_OOM,
484        first_key: 1,
485        last_key: 1,
486        step: 1,
487        acl: AC_WRITE_FAST,
488        since: "1.0.0",
489        complexity: "O(1)",
490        summary: "Set a key only if it is not there.",
491        group: "string",
492    },
493    Spec {
494        name: "setex",
495        arity: 4,
496        flags: WRITE_OOM,
497        first_key: 1,
498        last_key: 1,
499        step: 1,
500        acl: AC_WRITE_SLOW,
501        since: "2.0.0",
502        complexity: "O(1)",
503        summary: "Set a key and give it a deadline in seconds.",
504        group: "string",
505    },
506    Spec {
507        name: "psetex",
508        arity: 4,
509        flags: WRITE_OOM,
510        first_key: 1,
511        last_key: 1,
512        step: 1,
513        acl: AC_WRITE_SLOW,
514        since: "2.6.0",
515        complexity: "O(1)",
516        summary: "Set a key and give it a deadline in milliseconds.",
517        group: "string",
518    },
519    Spec {
520        name: "mset",
521        arity: -3,
522        flags: WRITE_OOM,
523        first_key: 1,
524        last_key: -1,
525        step: 2,
526        acl: AC_WRITE_SLOW,
527        since: "1.0.1",
528        complexity: "O(N) with N the number of keys",
529        summary: "Set several keys, all of them or none.",
530        group: "string",
531    },
532    Spec {
533        name: "msetnx",
534        arity: -3,
535        flags: WRITE_OOM,
536        first_key: 1,
537        last_key: -1,
538        step: 2,
539        acl: AC_WRITE_SLOW,
540        since: "1.0.1",
541        complexity: "O(N) with N the number of keys",
542        summary: "Set several keys only if none of them are there.",
543        group: "string",
544    },
545    Spec {
546        name: "mget",
547        arity: -2,
548        flags: READ_FAST,
549        first_key: 1,
550        last_key: -1,
551        step: 1,
552        acl: AC_READ_FAST,
553        since: "1.0.0",
554        complexity: "O(N) with N the number of keys",
555        summary: "The values of several keys, in the order asked for.",
556        group: "string",
557    },
558    Spec {
559        name: "append",
560        arity: 3,
561        flags: WRITE_FAST_OOM,
562        first_key: 1,
563        last_key: 1,
564        step: 1,
565        acl: AC_WRITE_FAST,
566        since: "2.0.0",
567        complexity: "O(M) with M the length of the value being appended",
568        summary: "Add to the end of a string, creating it if it is not there.",
569        group: "string",
570    },
571    Spec {
572        name: "strlen",
573        arity: 2,
574        flags: READ_FAST,
575        first_key: 1,
576        last_key: 1,
577        step: 1,
578        acl: AC_READ_FAST,
579        since: "2.2.0",
580        complexity: "O(1)",
581        summary: "How long a string value is, without reading it.",
582        group: "string",
583    },
584    Spec {
585        name: "setrange",
586        arity: 4,
587        flags: WRITE_OOM,
588        first_key: 1,
589        last_key: 1,
590        step: 1,
591        acl: AC_WRITE_SLOW,
592        since: "2.2.0",
593        complexity: "O(M) with M the length of the replacement",
594        summary: "Overwrite part of a string at an offset, zero filling the gap.",
595        group: "string",
596    },
597    Spec {
598        name: "getrange",
599        arity: 4,
600        flags: &["readonly"],
601        first_key: 1,
602        last_key: 1,
603        step: 1,
604        acl: AC_READ_SLOW,
605        since: "2.4.0",
606        complexity: "O(N) with N the length of the answer",
607        summary: "Part of a string, by an inclusive range that may count backwards.",
608        group: "string",
609    },
610    Spec {
611        name: "substr",
612        arity: 4,
613        flags: &["readonly"],
614        first_key: 1,
615        last_key: 1,
616        step: 1,
617        acl: AC_READ_SLOW,
618        since: "1.0.0",
619        complexity: "O(N) with N the length of the answer",
620        summary: "GETRANGE under the name it had before 2.4.",
621        group: "string",
622    },
623    Spec {
624        name: "incr",
625        arity: 2,
626        flags: WRITE_FAST_OOM,
627        first_key: 1,
628        last_key: 1,
629        step: 1,
630        acl: AC_WRITE_FAST,
631        since: "1.0.0",
632        complexity: "O(1)",
633        summary: "Add one, starting from zero if the key is not there.",
634        group: "string",
635    },
636    Spec {
637        name: "decr",
638        arity: 2,
639        flags: WRITE_FAST_OOM,
640        first_key: 1,
641        last_key: 1,
642        step: 1,
643        acl: AC_WRITE_FAST,
644        since: "1.0.0",
645        complexity: "O(1)",
646        summary: "Take one away, starting from zero if the key is not there.",
647        group: "string",
648    },
649    Spec {
650        name: "incrby",
651        arity: 3,
652        flags: WRITE_FAST_OOM,
653        first_key: 1,
654        last_key: 1,
655        step: 1,
656        acl: AC_WRITE_FAST,
657        since: "1.0.0",
658        complexity: "O(1)",
659        summary: "Add a number, starting from zero if the key is not there.",
660        group: "string",
661    },
662    Spec {
663        name: "decrby",
664        arity: 3,
665        flags: WRITE_FAST_OOM,
666        first_key: 1,
667        last_key: 1,
668        step: 1,
669        acl: AC_WRITE_FAST,
670        since: "1.0.0",
671        complexity: "O(1)",
672        summary: "Take a number away, starting from zero if the key is not there.",
673        group: "string",
674    },
675    Spec {
676        name: "incrbyfloat",
677        arity: 3,
678        flags: WRITE_FAST_OOM,
679        first_key: 1,
680        last_key: 1,
681        step: 1,
682        acl: AC_WRITE_FAST,
683        since: "2.6.0",
684        complexity: "O(1)",
685        summary: "Add a float, starting from zero if the key is not there.",
686        group: "string",
687    },
688    Spec {
689        name: "lcs",
690        arity: -3,
691        flags: &["readonly"],
692        first_key: 1,
693        last_key: 2,
694        step: 1,
695        acl: AC_READ_SLOW,
696        since: "7.0.0",
697        complexity: "O(N*M) with N and M the lengths of the two values",
698        summary: "The longest subsequence two string values have in common.",
699        group: "string",
700    },
701    Spec {
702        name: "msetex",
703        arity: -4,
704        flags: &["write", "denyoom", "movablekeys"],
705        first_key: 0,
706        last_key: 0,
707        step: 0,
708        acl: AC_WRITE_SLOW,
709        since: "8.4.0",
710        complexity: "O(N) with N the number of keys",
711        summary: "Set several keys with one deadline and one condition over all of them.",
712        group: "string",
713    },
714    Spec {
715        name: "delex",
716        arity: -2,
717        flags: &["write", "fast"],
718        first_key: 1,
719        last_key: 1,
720        step: 1,
721        acl: AC_WRITE_FAST,
722        since: "8.4.0",
723        complexity: "O(1) by value, O(N) by digest",
724        summary: "Delete a key only if it still holds what the caller thinks.",
725        group: "string",
726    },
727    Spec {
728        name: "digest",
729        arity: 2,
730        flags: READ_FAST,
731        first_key: 1,
732        last_key: 1,
733        step: 1,
734        acl: AC_READ_FAST,
735        since: "8.4.0",
736        complexity: "O(N) with N the length of the value",
737        summary: "The XXH3 of a string value, as sixteen hex characters.",
738        group: "string",
739    },
740    Spec {
741        name: "increx",
742        arity: -2,
743        flags: WRITE_FAST_OOM,
744        first_key: 1,
745        last_key: 1,
746        step: 1,
747        acl: AC_WRITE_FAST,
748        since: "8.8.0",
749        complexity: "O(1)",
750        summary: "Count, with a bound, a saturation policy and a deadline.",
751        group: "string",
752    },
753    // -------------------------------------------------------------- bitmaps
754    Spec {
755        name: "setbit",
756        arity: 4,
757        flags: WRITE_OOM,
758        first_key: 1,
759        last_key: 1,
760        step: 1,
761        acl: AC_BIT_WRITE,
762        since: "2.2.0",
763        complexity: "O(1)",
764        summary: "Set one bit of a string, growing it to reach the offset.",
765        group: "bitmap",
766    },
767    Spec {
768        name: "getbit",
769        arity: 3,
770        flags: READ_FAST,
771        first_key: 1,
772        last_key: 1,
773        step: 1,
774        acl: AC_BIT_READ_FAST,
775        since: "2.2.0",
776        complexity: "O(1)",
777        summary: "Read one bit of a string, or nought past its end.",
778        group: "bitmap",
779    },
780    Spec {
781        name: "bitcount",
782        arity: -2,
783        flags: &["readonly"],
784        first_key: 1,
785        last_key: 1,
786        step: 1,
787        acl: AC_BIT_READ,
788        since: "2.6.0",
789        complexity: "O(N)",
790        summary: "Count the set bits of a string, or of a range of it.",
791        group: "bitmap",
792    },
793    Spec {
794        name: "bitpos",
795        arity: -3,
796        flags: &["readonly"],
797        first_key: 1,
798        last_key: 1,
799        step: 1,
800        acl: AC_BIT_READ,
801        since: "2.8.7",
802        complexity: "O(N)",
803        summary: "Find the first bit set to one or nought in a string.",
804        group: "bitmap",
805    },
806    Spec {
807        name: "bitop",
808        arity: -4,
809        flags: WRITE_OOM,
810        first_key: 2,
811        last_key: -1,
812        step: 1,
813        acl: AC_BIT_WRITE,
814        since: "2.6.0",
815        complexity: "O(N) with N the length of the longest source",
816        summary: "Combine strings bit by bit and store the result.",
817        group: "bitmap",
818    },
819    Spec {
820        name: "bitfield",
821        arity: -2,
822        flags: WRITE_OOM,
823        first_key: 1,
824        last_key: 1,
825        step: 1,
826        acl: AC_BIT_WRITE,
827        since: "3.2.0",
828        complexity: "O(1) per subcommand",
829        summary: "Read and write packed integer fields inside a string.",
830        group: "bitmap",
831    },
832    Spec {
833        name: "bitfield_ro",
834        arity: -2,
835        flags: READ_FAST,
836        first_key: 1,
837        last_key: 1,
838        step: 1,
839        acl: AC_BIT_READ_FAST,
840        since: "6.0.0",
841        complexity: "O(1) per subcommand",
842        summary: "The read only half of BITFIELD, for a replica to answer.",
843        group: "bitmap",
844    },
845    // --------------------------------------------------------- hyperloglogs
846    Spec {
847        name: "pfadd",
848        arity: -2,
849        flags: WRITE_OOM,
850        first_key: 1,
851        last_key: 1,
852        step: 1,
853        acl: AC_HLL_WRITE_FAST,
854        since: "2.8.9",
855        complexity: "O(1) an element",
856        summary: "Add elements to a sketch, answering whether it changed.",
857        group: "hyperloglog",
858    },
859    Spec {
860        name: "pfcount",
861        arity: -2,
862        flags: &["readonly"],
863        first_key: 1,
864        last_key: -1,
865        step: 1,
866        acl: AC_HLL_READ,
867        since: "2.8.9",
868        complexity: "O(1) for one key, O(N) for N of them",
869        summary: "Estimate how many distinct elements the sketches hold.",
870        group: "hyperloglog",
871    },
872    Spec {
873        name: "pfmerge",
874        arity: -2,
875        flags: WRITE_OOM,
876        first_key: 1,
877        last_key: -1,
878        step: 1,
879        acl: AC_HLL_WRITE,
880        since: "2.8.9",
881        complexity: "O(N) in the number of sketches",
882        summary: "Merge sketches into the first one, which is a union.",
883        group: "hyperloglog",
884    },
885    Spec {
886        name: "pfdebug",
887        arity: 3,
888        flags: WRITE_OOM_ADMIN,
889        first_key: 2,
890        last_key: 2,
891        step: 1,
892        acl: AC_HLL_ADMIN,
893        since: "2.8.9",
894        complexity: "O(N)",
895        summary: "Look inside a sketch, and in one case convert it.",
896        group: "hyperloglog",
897    },
898    Spec {
899        name: "pfselftest",
900        arity: 1,
901        flags: &["admin"],
902        first_key: 0,
903        last_key: 0,
904        step: 0,
905        acl: AC_HLL_ADMIN,
906        since: "2.8.9",
907        complexity: "O(1)",
908        summary: "Check the sketch code, which our tests do at build time.",
909        group: "hyperloglog",
910    },
911    // ----------------------------------------------------------------- sets
912    Spec {
913        name: "sadd",
914        arity: -3,
915        flags: WRITE_FAST_OOM,
916        first_key: 1,
917        last_key: 1,
918        step: 1,
919        acl: AC_SET_WRITE_FAST,
920        since: "1.0.0",
921        complexity: "O(N) with N the number of members being added",
922        summary: "Add members to a set, creating it if it is not there.",
923        group: "set",
924    },
925    Spec {
926        name: "srem",
927        arity: -3,
928        flags: WRITE_FAST,
929        first_key: 1,
930        last_key: 1,
931        step: 1,
932        acl: AC_SET_WRITE_FAST,
933        since: "1.0.0",
934        complexity: "O(N) with N the number of members being removed",
935        summary: "Take members out of a set, deleting the key if none are left.",
936        group: "set",
937    },
938    Spec {
939        name: "scard",
940        arity: 2,
941        flags: READ_FAST,
942        first_key: 1,
943        last_key: 1,
944        step: 1,
945        acl: AC_SET_READ_FAST,
946        since: "1.0.0",
947        complexity: "O(1)",
948        summary: "How many members a set has.",
949        group: "set",
950    },
951    Spec {
952        name: "sismember",
953        arity: 3,
954        flags: READ_FAST,
955        first_key: 1,
956        last_key: 1,
957        step: 1,
958        acl: AC_SET_READ_FAST,
959        since: "1.0.0",
960        complexity: "O(1)",
961        summary: "Whether a member is in a set.",
962        group: "set",
963    },
964    Spec {
965        name: "smismember",
966        arity: -3,
967        flags: READ_FAST,
968        first_key: 1,
969        last_key: 1,
970        step: 1,
971        acl: AC_SET_READ_FAST,
972        since: "6.2.0",
973        complexity: "O(N) with N the number of members being asked about",
974        summary: "Whether each of several members is in a set, in the order asked.",
975        group: "set",
976    },
977    Spec {
978        name: "smembers",
979        arity: 2,
980        flags: &["readonly"],
981        first_key: 1,
982        last_key: 1,
983        step: 1,
984        acl: AC_SET_READ_SLOW,
985        since: "1.0.0",
986        complexity: "O(N) with N the size of the set",
987        summary: "Every member of a set.",
988        group: "set",
989    },
990    Spec {
991        name: "spop",
992        arity: -2,
993        flags: WRITE_FAST,
994        first_key: 1,
995        last_key: 1,
996        step: 1,
997        acl: AC_SET_WRITE_FAST,
998        since: "1.0.0",
999        complexity: "O(1) without a count, O(N) with one",
1000        summary: "Take members out of a set at random and hand them back.",
1001        group: "set",
1002    },
1003    Spec {
1004        name: "srandmember",
1005        arity: -2,
1006        flags: &["readonly"],
1007        first_key: 1,
1008        last_key: 1,
1009        step: 1,
1010        acl: AC_SET_READ_SLOW,
1011        since: "1.0.0",
1012        complexity: "O(1) without a count, O(N) with one",
1013        summary: "Members of a set at random, leaving the set as it was.",
1014        group: "set",
1015    },
1016    Spec {
1017        name: "smove",
1018        arity: 4,
1019        flags: WRITE_FAST,
1020        first_key: 1,
1021        last_key: 2,
1022        step: 1,
1023        acl: AC_SET_WRITE_FAST,
1024        since: "1.0.0",
1025        complexity: "O(1)",
1026        summary: "Move one member from one set to another.",
1027        group: "set",
1028    },
1029    Spec {
1030        name: "sscan",
1031        arity: -3,
1032        flags: &["readonly"],
1033        first_key: 1,
1034        last_key: 1,
1035        step: 1,
1036        acl: AC_SET_READ_SLOW,
1037        since: "2.8.0",
1038        complexity: "O(1) a call, O(N) for a whole iteration",
1039        summary: "Walk part of a set and say where to carry on from.",
1040        group: "set",
1041    },
1042    Spec {
1043        name: "sinter",
1044        arity: -2,
1045        flags: &["readonly"],
1046        first_key: 1,
1047        last_key: -1,
1048        step: 1,
1049        acl: AC_SET_READ_SLOW,
1050        since: "1.0.0",
1051        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
1052        summary: "The members every one of these sets has.",
1053        group: "set",
1054    },
1055    Spec {
1056        name: "sintercard",
1057        arity: -3,
1058        // The only set command whose keys are counted rather than positioned,
1059        // so the legacy key range cannot describe it and Redis reports zeroes
1060        // in these three fields too. A client that wants the keys reads the key
1061        // specs, which is what the count is for, and movablekeys is how it is
1062        // told to go and read them.
1063        flags: READ_MOVABLE,
1064        first_key: 0,
1065        last_key: 0,
1066        step: 0,
1067        acl: AC_SET_READ_SLOW,
1068        since: "7.0.0",
1069        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
1070        summary: "How many members every one of these sets has, up to a limit.",
1071        group: "set",
1072    },
1073    Spec {
1074        name: "sinterstore",
1075        arity: -3,
1076        flags: WRITE_OOM,
1077        first_key: 1,
1078        last_key: -1,
1079        step: 1,
1080        acl: AC_SET_WRITE_SLOW,
1081        since: "1.0.0",
1082        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
1083        summary: "Store the members every one of these sets has.",
1084        group: "set",
1085    },
1086    Spec {
1087        name: "sunion",
1088        arity: -2,
1089        flags: &["readonly"],
1090        first_key: 1,
1091        last_key: -1,
1092        step: 1,
1093        acl: AC_SET_READ_SLOW,
1094        since: "1.0.0",
1095        complexity: "O(N) in the total number of members",
1096        summary: "The members any of these sets has, each once.",
1097        group: "set",
1098    },
1099    Spec {
1100        name: "sunionstore",
1101        arity: -3,
1102        flags: WRITE_OOM,
1103        first_key: 1,
1104        last_key: -1,
1105        step: 1,
1106        acl: AC_SET_WRITE_SLOW,
1107        since: "1.0.0",
1108        complexity: "O(N) in the total number of members",
1109        summary: "Store the members any of these sets has.",
1110        group: "set",
1111    },
1112    Spec {
1113        name: "sdiff",
1114        arity: -2,
1115        flags: &["readonly"],
1116        first_key: 1,
1117        last_key: -1,
1118        step: 1,
1119        acl: AC_SET_READ_SLOW,
1120        since: "1.0.0",
1121        complexity: "O(N) in the total number of members",
1122        summary: "The members of the first set that no later set has.",
1123        group: "set",
1124    },
1125    Spec {
1126        name: "sdiffstore",
1127        arity: -3,
1128        flags: WRITE_OOM,
1129        first_key: 1,
1130        last_key: -1,
1131        step: 1,
1132        acl: AC_SET_WRITE_SLOW,
1133        since: "1.0.0",
1134        complexity: "O(N) in the total number of members",
1135        summary: "Store the members of the first set that no later set has.",
1136        group: "set",
1137    },
1138    // The two 8.10 added, which are to SUNION and SDIFF what SINTERCARD is to
1139    // SINTER, and which describe their keys the same way it does and for the
1140    // same reason.
1141    Spec {
1142        name: "sunioncard",
1143        arity: -3,
1144        flags: READ_MOVABLE,
1145        first_key: 0,
1146        last_key: 0,
1147        step: 0,
1148        acl: AC_SET_READ_SLOW,
1149        since: "8.10.0",
1150        complexity: "O(N) in the total number of members",
1151        summary: "How many members any of these sets has, up to a limit.",
1152        group: "set",
1153    },
1154    Spec {
1155        name: "sdiffcard",
1156        arity: -3,
1157        flags: READ_MOVABLE,
1158        first_key: 0,
1159        last_key: 0,
1160        step: 0,
1161        acl: AC_SET_READ_SLOW,
1162        since: "8.10.0",
1163        complexity: "O(N) in the total number of members",
1164        summary: "How many members the first set has that no later set has, up to a limit.",
1165        group: "set",
1166    },
1167    // -------------------------------------------------------------- hashes
1168    Spec {
1169        name: "hset",
1170        arity: -4,
1171        flags: WRITE_FAST_OOM,
1172        first_key: 1,
1173        last_key: 1,
1174        step: 1,
1175        acl: AC_HASH_WRITE_FAST,
1176        since: "2.0.0",
1177        complexity: "O(N) with N the number of pairs being written",
1178        summary: "Write fields into a hash, creating it if it is not there.",
1179        group: "hash",
1180    },
1181    Spec {
1182        name: "hsetnx",
1183        arity: 4,
1184        flags: WRITE_FAST_OOM,
1185        first_key: 1,
1186        last_key: 1,
1187        step: 1,
1188        acl: AC_HASH_WRITE_FAST,
1189        since: "2.0.0",
1190        complexity: "O(1)",
1191        summary: "Write a field only if the hash does not have it already.",
1192        group: "hash",
1193    },
1194    // Deprecated since 4.0 and still sent by a great deal of code, so it is
1195    // here rather than left out. It is HSET with an OK instead of a count.
1196    Spec {
1197        name: "hmset",
1198        arity: -4,
1199        flags: WRITE_FAST_OOM,
1200        first_key: 1,
1201        last_key: 1,
1202        step: 1,
1203        acl: AC_HASH_WRITE_FAST,
1204        since: "2.0.0",
1205        complexity: "O(N) with N the number of pairs being written",
1206        summary: "Write fields into a hash and answer OK. Use HSET.",
1207        group: "hash",
1208    },
1209    Spec {
1210        name: "hget",
1211        arity: 3,
1212        flags: READ_FAST,
1213        first_key: 1,
1214        last_key: 1,
1215        step: 1,
1216        acl: AC_HASH_READ_FAST,
1217        since: "2.0.0",
1218        complexity: "O(1)",
1219        summary: "The value of one field of a hash.",
1220        group: "hash",
1221    },
1222    Spec {
1223        name: "hmget",
1224        arity: -3,
1225        flags: READ_FAST,
1226        first_key: 1,
1227        last_key: 1,
1228        step: 1,
1229        acl: AC_HASH_READ_FAST,
1230        since: "2.0.0",
1231        complexity: "O(N) with N the number of fields asked for",
1232        summary: "The values of several fields, one reply entry each.",
1233        group: "hash",
1234    },
1235    Spec {
1236        name: "hdel",
1237        arity: -3,
1238        flags: WRITE_FAST,
1239        first_key: 1,
1240        last_key: 1,
1241        step: 1,
1242        acl: AC_HASH_WRITE_FAST,
1243        since: "2.0.0",
1244        complexity: "O(N) with N the number of fields being removed",
1245        summary: "Take fields out of a hash, deleting the key if none are left.",
1246        group: "hash",
1247    },
1248    Spec {
1249        name: "hlen",
1250        arity: 2,
1251        flags: READ_FAST,
1252        first_key: 1,
1253        last_key: 1,
1254        step: 1,
1255        acl: AC_HASH_READ_FAST,
1256        since: "2.0.0",
1257        complexity: "O(1)",
1258        summary: "How many fields a hash has.",
1259        group: "hash",
1260    },
1261    Spec {
1262        name: "hexists",
1263        arity: 3,
1264        flags: READ_FAST,
1265        first_key: 1,
1266        last_key: 1,
1267        step: 1,
1268        acl: AC_HASH_READ_FAST,
1269        since: "2.0.0",
1270        complexity: "O(1)",
1271        summary: "Whether a hash has a field.",
1272        group: "hash",
1273    },
1274    Spec {
1275        name: "hstrlen",
1276        arity: 3,
1277        flags: READ_FAST,
1278        first_key: 1,
1279        last_key: 1,
1280        step: 1,
1281        acl: AC_HASH_READ_FAST,
1282        since: "3.2.0",
1283        complexity: "O(1)",
1284        summary: "How many bytes a field's value is, without sending it.",
1285        group: "hash",
1286    },
1287    Spec {
1288        name: "hgetall",
1289        arity: 2,
1290        flags: &["readonly"],
1291        first_key: 1,
1292        last_key: 1,
1293        step: 1,
1294        acl: AC_HASH_READ_SLOW,
1295        since: "2.0.0",
1296        complexity: "O(N) in the size of the hash",
1297        summary: "Every field and value, as a map on RESP3.",
1298        group: "hash",
1299    },
1300    Spec {
1301        name: "hkeys",
1302        arity: 2,
1303        flags: &["readonly"],
1304        first_key: 1,
1305        last_key: 1,
1306        step: 1,
1307        acl: AC_HASH_READ_SLOW,
1308        since: "2.0.0",
1309        complexity: "O(N) in the size of the hash",
1310        summary: "Every field of a hash.",
1311        group: "hash",
1312    },
1313    Spec {
1314        name: "hvals",
1315        arity: 2,
1316        flags: &["readonly"],
1317        first_key: 1,
1318        last_key: 1,
1319        step: 1,
1320        acl: AC_HASH_READ_SLOW,
1321        since: "2.0.0",
1322        complexity: "O(N) in the size of the hash",
1323        summary: "Every value of a hash.",
1324        group: "hash",
1325    },
1326    Spec {
1327        name: "hincrby",
1328        arity: 4,
1329        flags: WRITE_FAST_OOM,
1330        first_key: 1,
1331        last_key: 1,
1332        step: 1,
1333        acl: AC_HASH_WRITE_FAST,
1334        since: "2.0.0",
1335        complexity: "O(1)",
1336        summary: "Add an integer to a field, treating a missing one as zero.",
1337        group: "hash",
1338    },
1339    Spec {
1340        name: "hincrbyfloat",
1341        arity: 4,
1342        flags: WRITE_FAST_OOM,
1343        first_key: 1,
1344        last_key: 1,
1345        step: 1,
1346        acl: AC_HASH_WRITE_FAST,
1347        since: "2.6.0",
1348        complexity: "O(1)",
1349        summary: "Add a float to a field, treating a missing one as zero.",
1350        group: "hash",
1351    },
1352    Spec {
1353        name: "hrandfield",
1354        arity: -2,
1355        flags: &["readonly"],
1356        first_key: 1,
1357        last_key: 1,
1358        step: 1,
1359        acl: AC_HASH_READ_SLOW,
1360        since: "6.2.0",
1361        complexity: "O(1) without a count, O(N) with one",
1362        summary: "Fields of a hash at random, leaving the hash as it was.",
1363        group: "hash",
1364    },
1365    Spec {
1366        name: "hscan",
1367        arity: -3,
1368        flags: &["readonly"],
1369        first_key: 1,
1370        last_key: 1,
1371        step: 1,
1372        acl: AC_HASH_READ_SLOW,
1373        since: "2.8.0",
1374        complexity: "O(1) a call, O(N) for a whole iteration",
1375        summary: "Walk part of a hash and say where to carry on from.",
1376        group: "hash",
1377    },
1378    Spec {
1379        name: "hexpire",
1380        arity: -6,
1381        flags: WRITE_FAST,
1382        first_key: 1,
1383        last_key: 1,
1384        step: 1,
1385        acl: AC_HASH_WRITE_FAST,
1386        since: "7.4.0",
1387        complexity: "O(N) with N the number of fields named",
1388        summary: "Put a deadline in seconds on hash fields.",
1389        group: "hash",
1390    },
1391    Spec {
1392        name: "hpexpire",
1393        arity: -6,
1394        flags: WRITE_FAST,
1395        first_key: 1,
1396        last_key: 1,
1397        step: 1,
1398        acl: AC_HASH_WRITE_FAST,
1399        since: "7.4.0",
1400        complexity: "O(N) with N the number of fields named",
1401        summary: "Put a deadline in milliseconds on hash fields.",
1402        group: "hash",
1403    },
1404    Spec {
1405        name: "hexpireat",
1406        arity: -6,
1407        flags: WRITE_FAST,
1408        first_key: 1,
1409        last_key: 1,
1410        step: 1,
1411        acl: AC_HASH_WRITE_FAST,
1412        since: "7.4.0",
1413        complexity: "O(N) with N the number of fields named",
1414        summary: "Put an absolute deadline in unix seconds on hash fields.",
1415        group: "hash",
1416    },
1417    Spec {
1418        name: "hpexpireat",
1419        arity: -6,
1420        flags: WRITE_FAST,
1421        first_key: 1,
1422        last_key: 1,
1423        step: 1,
1424        acl: AC_HASH_WRITE_FAST,
1425        since: "7.4.0",
1426        complexity: "O(N) with N the number of fields named",
1427        summary: "Put an absolute deadline in unix milliseconds on hash fields.",
1428        group: "hash",
1429    },
1430    Spec {
1431        name: "httl",
1432        arity: -5,
1433        flags: READ_FAST,
1434        first_key: 1,
1435        last_key: 1,
1436        step: 1,
1437        acl: AC_HASH_READ_FAST,
1438        since: "7.4.0",
1439        complexity: "O(N) with N the number of fields named",
1440        summary: "How long hash fields have left, in seconds.",
1441        group: "hash",
1442    },
1443    Spec {
1444        name: "hpttl",
1445        arity: -5,
1446        flags: READ_FAST,
1447        first_key: 1,
1448        last_key: 1,
1449        step: 1,
1450        acl: AC_HASH_READ_FAST,
1451        since: "7.4.0",
1452        complexity: "O(N) with N the number of fields named",
1453        summary: "How long hash fields have left, in milliseconds.",
1454        group: "hash",
1455    },
1456    Spec {
1457        name: "hexpiretime",
1458        arity: -5,
1459        flags: READ_FAST,
1460        first_key: 1,
1461        last_key: 1,
1462        step: 1,
1463        acl: AC_HASH_READ_FAST,
1464        since: "7.4.0",
1465        complexity: "O(N) with N the number of fields named",
1466        summary: "When hash fields fall due, in unix seconds.",
1467        group: "hash",
1468    },
1469    Spec {
1470        name: "hpexpiretime",
1471        arity: -5,
1472        flags: READ_FAST,
1473        first_key: 1,
1474        last_key: 1,
1475        step: 1,
1476        acl: AC_HASH_READ_FAST,
1477        since: "7.4.0",
1478        complexity: "O(N) with N the number of fields named",
1479        summary: "When hash fields fall due, in unix milliseconds.",
1480        group: "hash",
1481    },
1482    Spec {
1483        name: "hpersist",
1484        arity: -5,
1485        flags: WRITE_FAST,
1486        first_key: 1,
1487        last_key: 1,
1488        step: 1,
1489        acl: AC_HASH_WRITE_FAST,
1490        since: "7.4.0",
1491        complexity: "O(N) with N the number of fields named",
1492        summary: "Take the deadlines off hash fields.",
1493        group: "hash",
1494    },
1495    Spec {
1496        name: "hgetdel",
1497        arity: -5,
1498        flags: WRITE_FAST,
1499        first_key: 1,
1500        last_key: 1,
1501        step: 1,
1502        acl: AC_HASH_WRITE_FAST,
1503        since: "8.0.0",
1504        complexity: "O(N) with N the number of fields named",
1505        summary: "Read hash fields and delete them.",
1506        group: "hash",
1507    },
1508    Spec {
1509        name: "hgetex",
1510        arity: -5,
1511        flags: WRITE_FAST,
1512        first_key: 1,
1513        last_key: 1,
1514        step: 1,
1515        acl: AC_HASH_WRITE_FAST,
1516        since: "8.0.0",
1517        complexity: "O(N) with N the number of fields named",
1518        summary: "Read hash fields and set their deadlines.",
1519        group: "hash",
1520    },
1521    Spec {
1522        name: "hsetex",
1523        arity: -6,
1524        flags: WRITE_FAST_OOM,
1525        first_key: 1,
1526        last_key: 1,
1527        step: 1,
1528        acl: AC_HASH_WRITE_FAST,
1529        since: "8.0.0",
1530        complexity: "O(N) with N the number of fields being set",
1531        summary: "Set hash fields and their deadlines together.",
1532        group: "hash",
1533    },
1534    // A container with no flags and no keys of its own, which is what a real
1535    // 8.10.1 reports: the write flags and the key index live on `HIMPORT SET`
1536    // and this row is only the name and the categories.
1537    Spec {
1538        name: "himport",
1539        arity: -2,
1540        flags: &[],
1541        first_key: 0,
1542        last_key: 0,
1543        step: 0,
1544        acl: AC_HASH_SLOW,
1545        since: "8.10.0",
1546        complexity: "Depends on subcommand.",
1547        summary: "A container for session-based hash import commands using fieldsets.",
1548        group: "hash",
1549    },
1550    // ---------------------------------------------------------------- lists
1551    Spec {
1552        name: "lpush",
1553        arity: -3,
1554        flags: WRITE_FAST_OOM,
1555        first_key: 1,
1556        last_key: 1,
1557        step: 1,
1558        acl: AC_LIST_WRITE_FAST,
1559        since: "1.0.0",
1560        complexity: "O(N) with N the number of elements pushed",
1561        summary: "Push elements onto the head of a list.",
1562        group: "list",
1563    },
1564    Spec {
1565        name: "rpush",
1566        arity: -3,
1567        flags: WRITE_FAST_OOM,
1568        first_key: 1,
1569        last_key: 1,
1570        step: 1,
1571        acl: AC_LIST_WRITE_FAST,
1572        since: "1.0.0",
1573        complexity: "O(N) with N the number of elements pushed",
1574        summary: "Push elements onto the tail of a list.",
1575        group: "list",
1576    },
1577    Spec {
1578        name: "lpushx",
1579        arity: -3,
1580        flags: WRITE_FAST_OOM,
1581        first_key: 1,
1582        last_key: 1,
1583        step: 1,
1584        acl: AC_LIST_WRITE_FAST,
1585        since: "2.2.0",
1586        complexity: "O(N) with N the number of elements pushed",
1587        summary: "Push elements onto the head of a list that already exists.",
1588        group: "list",
1589    },
1590    Spec {
1591        name: "rpushx",
1592        arity: -3,
1593        flags: WRITE_FAST_OOM,
1594        first_key: 1,
1595        last_key: 1,
1596        step: 1,
1597        acl: AC_LIST_WRITE_FAST,
1598        since: "2.2.0",
1599        complexity: "O(N) with N the number of elements pushed",
1600        summary: "Push elements onto the tail of a list that already exists.",
1601        group: "list",
1602    },
1603    Spec {
1604        name: "lpop",
1605        arity: -2,
1606        flags: WRITE_FAST,
1607        first_key: 1,
1608        last_key: 1,
1609        step: 1,
1610        acl: AC_LIST_WRITE_FAST,
1611        since: "1.0.0",
1612        complexity: "O(N) with N the count asked for",
1613        summary: "Take elements off the head of a list.",
1614        group: "list",
1615    },
1616    Spec {
1617        name: "rpop",
1618        arity: -2,
1619        flags: WRITE_FAST,
1620        first_key: 1,
1621        last_key: 1,
1622        step: 1,
1623        acl: AC_LIST_WRITE_FAST,
1624        since: "1.0.0",
1625        complexity: "O(N) with N the count asked for",
1626        summary: "Take elements off the tail of a list.",
1627        group: "list",
1628    },
1629    Spec {
1630        name: "llen",
1631        arity: 2,
1632        flags: READ_FAST,
1633        first_key: 1,
1634        last_key: 1,
1635        step: 1,
1636        acl: AC_LIST_READ_FAST,
1637        since: "1.0.0",
1638        complexity: "O(1)",
1639        summary: "How many elements a list holds.",
1640        group: "list",
1641    },
1642    Spec {
1643        name: "lrange",
1644        arity: 4,
1645        flags: READ_SLOW,
1646        first_key: 1,
1647        last_key: 1,
1648        step: 1,
1649        acl: AC_LIST_READ_SLOW,
1650        since: "1.0.0",
1651        complexity: "O(S+N) with S the offset of the first element and N the range",
1652        summary: "Read a range of a list, both ends included.",
1653        group: "list",
1654    },
1655    Spec {
1656        name: "lindex",
1657        arity: 3,
1658        flags: READ_SLOW,
1659        first_key: 1,
1660        last_key: 1,
1661        step: 1,
1662        acl: AC_LIST_READ_SLOW,
1663        since: "1.0.0",
1664        complexity: "O(N) with N the distance to the index from the nearer end",
1665        summary: "Read one element of a list by index.",
1666        group: "list",
1667    },
1668    Spec {
1669        name: "lset",
1670        arity: 4,
1671        flags: WRITE_OOM,
1672        first_key: 1,
1673        last_key: 1,
1674        step: 1,
1675        acl: AC_LIST_WRITE_SLOW,
1676        since: "1.0.0",
1677        complexity: "O(N) with N the distance to the index from the nearer end",
1678        summary: "Replace one element of a list by index.",
1679        group: "list",
1680    },
1681    Spec {
1682        name: "linsert",
1683        arity: 5,
1684        flags: WRITE_OOM,
1685        first_key: 1,
1686        last_key: 1,
1687        step: 1,
1688        acl: AC_LIST_WRITE_SLOW,
1689        since: "2.2.0",
1690        complexity: "O(N) with N the distance to the pivot from the head",
1691        summary: "Insert an element before or after another one.",
1692        group: "list",
1693    },
1694    Spec {
1695        name: "lrem",
1696        arity: 4,
1697        flags: WRITE_SLOW,
1698        first_key: 1,
1699        last_key: 1,
1700        step: 1,
1701        acl: AC_LIST_WRITE_SLOW,
1702        since: "1.0.0",
1703        complexity: "O(N) with N the length of the list",
1704        summary: "Remove elements equal to a value from a list.",
1705        group: "list",
1706    },
1707    Spec {
1708        name: "ltrim",
1709        arity: 4,
1710        flags: WRITE_SLOW,
1711        first_key: 1,
1712        last_key: 1,
1713        step: 1,
1714        acl: AC_LIST_WRITE_SLOW,
1715        since: "1.0.0",
1716        complexity: "O(N) with N the number of elements thrown away",
1717        summary: "Keep a range of a list and throw the rest away.",
1718        group: "list",
1719    },
1720    Spec {
1721        name: "lpos",
1722        arity: -3,
1723        flags: READ_SLOW,
1724        first_key: 1,
1725        last_key: 1,
1726        step: 1,
1727        acl: AC_LIST_READ_SLOW,
1728        since: "6.0.6",
1729        complexity: "O(N) with N the length of the list",
1730        summary: "Find where a value sits in a list.",
1731        group: "list",
1732    },
1733    Spec {
1734        name: "rpoplpush",
1735        arity: 3,
1736        flags: WRITE_OOM,
1737        first_key: 1,
1738        last_key: 2,
1739        step: 1,
1740        acl: AC_LIST_WRITE_SLOW,
1741        since: "1.2.0",
1742        complexity: "O(1)",
1743        summary: "Move an element from the tail of one list to the head of another.",
1744        group: "list",
1745    },
1746    Spec {
1747        name: "lmove",
1748        arity: 5,
1749        flags: WRITE_OOM,
1750        first_key: 1,
1751        last_key: 2,
1752        step: 1,
1753        acl: AC_LIST_WRITE_SLOW,
1754        since: "6.2.0",
1755        complexity: "O(1)",
1756        summary: "Move an element from either end of one list to either end of another.",
1757        group: "list",
1758    },
1759    Spec {
1760        name: "lmovem",
1761        arity: -5,
1762        flags: WRITE_OOM,
1763        first_key: 1,
1764        last_key: 2,
1765        step: 1,
1766        acl: AC_LIST_WRITE_SLOW,
1767        since: "8.10.0",
1768        complexity: "O(N) in the number of elements moved",
1769        summary: "Move several elements from either end of one list to either end of another.",
1770        group: "list",
1771    },
1772    // The keys are behind a count, so `first_key` is zero and a cluster client
1773    // has to ask `COMMAND GETKEYS` rather than read a position out of this row.
1774    // That is what `movablekeys` means and it is why the three key fields are
1775    // all zero rather than pointing at argument two.
1776    Spec {
1777        name: "lmpop",
1778        arity: -4,
1779        flags: &["write", "movablekeys"],
1780        first_key: 0,
1781        last_key: 0,
1782        step: 0,
1783        acl: AC_LIST_WRITE_SLOW,
1784        since: "7.0.0",
1785        complexity: "O(N+M) with N the number of keys and M the count popped",
1786        summary: "Pop from the first of several lists that has anything in it.",
1787        group: "list",
1788    },
1789    // The five that wait. `blocking` is what the dispatcher branches on to send
1790    // them somewhere that can park a client, so it is load bearing here rather
1791    // than only being reported.
1792    //
1793    // `BLPOP` and `BRPOP` take their keys up to the timeout, which is the one
1794    // shape in the list group where `last_key` is negative: everything from
1795    // argument one to the second from last.
1796    Spec {
1797        name: "blpop",
1798        arity: -3,
1799        flags: &["write", "blocking"],
1800        first_key: 1,
1801        last_key: -2,
1802        step: 1,
1803        acl: AC_LIST_WRITE_BLOCKING,
1804        since: "2.0.0",
1805        complexity: "O(N) with N the number of keys named",
1806        summary: "Pop the head of the first list that has anything, waiting if none does.",
1807        group: "list",
1808    },
1809    Spec {
1810        name: "brpop",
1811        arity: -3,
1812        flags: &["write", "blocking"],
1813        first_key: 1,
1814        last_key: -2,
1815        step: 1,
1816        acl: AC_LIST_WRITE_BLOCKING,
1817        since: "2.0.0",
1818        complexity: "O(N) with N the number of keys named",
1819        summary: "Pop the tail of the first list that has anything, waiting if none does.",
1820        group: "list",
1821    },
1822    // Redis marks the two that push somewhere `denyoom` and does not mark the
1823    // pops, because these are the blocking commands that can grow the keyspace.
1824    Spec {
1825        name: "blmove",
1826        arity: 6,
1827        flags: &["write", "denyoom", "blocking"],
1828        first_key: 1,
1829        last_key: 2,
1830        step: 1,
1831        acl: AC_LIST_WRITE_BLOCKING,
1832        since: "6.2.0",
1833        complexity: "O(1)",
1834        summary: "Move an element between two lists, waiting for one to arrive.",
1835        group: "list",
1836    },
1837    Spec {
1838        name: "blmovem",
1839        arity: -6,
1840        flags: &["write", "denyoom", "blocking"],
1841        first_key: 1,
1842        last_key: 2,
1843        step: 1,
1844        acl: AC_LIST_WRITE_BLOCKING,
1845        since: "8.10.0",
1846        complexity: "O(N) in the number of elements moved",
1847        summary: "Move several elements between two lists, waiting for them to arrive.",
1848        group: "list",
1849    },
1850    Spec {
1851        name: "brpoplpush",
1852        arity: 4,
1853        flags: &["write", "denyoom", "blocking"],
1854        first_key: 1,
1855        last_key: 2,
1856        step: 1,
1857        acl: AC_LIST_WRITE_BLOCKING,
1858        since: "2.2.0",
1859        complexity: "O(1)",
1860        summary: "Move a tail element to another list's head, waiting for one to arrive.",
1861        group: "list",
1862    },
1863    // Keys behind a count again, so the same three zeroes `LMPOP` has.
1864    Spec {
1865        name: "blmpop",
1866        arity: -5,
1867        flags: &["write", "blocking", "movablekeys"],
1868        first_key: 0,
1869        last_key: 0,
1870        step: 0,
1871        acl: AC_LIST_WRITE_BLOCKING,
1872        since: "7.0.0",
1873        complexity: "O(N+M) with N the number of keys and M the count popped",
1874        summary: "Pop from the first of several lists that has anything, waiting if none does.",
1875        group: "list",
1876    },
1877    // ------------------------------------------------------------ sorted set
1878    Spec {
1879        name: "zadd",
1880        arity: -4,
1881        flags: WRITE_FAST_OOM,
1882        first_key: 1,
1883        last_key: 1,
1884        step: 1,
1885        acl: AC_ZSET_WRITE_FAST,
1886        since: "1.2.0",
1887        complexity: "O(log(N)) for each member added",
1888        summary: "Add members with scores, or move the scores of members already there.",
1889        group: "zset",
1890    },
1891    Spec {
1892        name: "zincrby",
1893        arity: 4,
1894        flags: WRITE_FAST_OOM,
1895        first_key: 1,
1896        last_key: 1,
1897        step: 1,
1898        acl: AC_ZSET_WRITE_FAST,
1899        since: "1.2.0",
1900        complexity: "O(log(N))",
1901        summary: "Add to a member's score, creating the member at zero if it is not there.",
1902        group: "zset",
1903    },
1904    Spec {
1905        name: "zcard",
1906        arity: 2,
1907        flags: READ_FAST,
1908        first_key: 1,
1909        last_key: 1,
1910        step: 1,
1911        acl: AC_ZSET_READ_FAST,
1912        since: "1.2.0",
1913        complexity: "O(1)",
1914        summary: "How many members a sorted set has.",
1915        group: "zset",
1916    },
1917    Spec {
1918        name: "zscore",
1919        arity: 3,
1920        flags: READ_FAST,
1921        first_key: 1,
1922        last_key: 1,
1923        step: 1,
1924        acl: AC_ZSET_READ_FAST,
1925        since: "1.2.0",
1926        complexity: "O(1)",
1927        summary: "A member's score, or nothing if it is not there.",
1928        group: "zset",
1929    },
1930    Spec {
1931        name: "zmscore",
1932        arity: -3,
1933        flags: READ_FAST,
1934        first_key: 1,
1935        last_key: 1,
1936        step: 1,
1937        acl: AC_ZSET_READ_FAST,
1938        since: "6.2.0",
1939        complexity: "O(N) with N the number of members asked about",
1940        summary: "The scores of several members in one round trip.",
1941        group: "zset",
1942    },
1943    Spec {
1944        name: "zrem",
1945        arity: -3,
1946        flags: WRITE_FAST,
1947        first_key: 1,
1948        last_key: 1,
1949        step: 1,
1950        acl: AC_ZSET_WRITE_FAST,
1951        since: "1.2.0",
1952        complexity: "O(M*log(N)) with M the number of members removed",
1953        summary: "Remove members, deleting the key if the last one goes.",
1954        group: "zset",
1955    },
1956    Spec {
1957        name: "zrank",
1958        arity: -3,
1959        flags: READ_FAST,
1960        first_key: 1,
1961        last_key: 1,
1962        step: 1,
1963        acl: AC_ZSET_READ_FAST,
1964        since: "2.0.0",
1965        complexity: "O(log(N))",
1966        summary: "Where a member sits counting up from the lowest score.",
1967        group: "zset",
1968    },
1969    Spec {
1970        name: "zrevrank",
1971        arity: -3,
1972        flags: READ_FAST,
1973        first_key: 1,
1974        last_key: 1,
1975        step: 1,
1976        acl: AC_ZSET_READ_FAST,
1977        since: "2.0.0",
1978        complexity: "O(log(N))",
1979        summary: "Where a member sits counting down from the highest score.",
1980        group: "zset",
1981    },
1982    Spec {
1983        name: "zcount",
1984        arity: 4,
1985        flags: READ_FAST,
1986        first_key: 1,
1987        last_key: 1,
1988        step: 1,
1989        acl: AC_ZSET_READ_FAST,
1990        since: "2.0.0",
1991        complexity: "O(log(N))",
1992        summary: "How many members have scores between two bounds.",
1993        group: "zset",
1994    },
1995    Spec {
1996        name: "zlexcount",
1997        arity: 4,
1998        flags: READ_FAST,
1999        first_key: 1,
2000        last_key: 1,
2001        step: 1,
2002        acl: AC_ZSET_READ_FAST,
2003        since: "2.8.9",
2004        complexity: "O(log(N))",
2005        summary: "How many members fall between two members, by name.",
2006        group: "zset",
2007    },
2008    Spec {
2009        name: "zrange",
2010        arity: -4,
2011        flags: READ_SLOW,
2012        first_key: 1,
2013        last_key: 1,
2014        step: 1,
2015        acl: AC_ZSET_READ_SLOW,
2016        since: "1.2.0",
2017        complexity: "O(log(N)+M) with M the number of members answered",
2018        summary: "A window of members, by rank or by score or by name, either way round.",
2019        group: "zset",
2020    },
2021    Spec {
2022        name: "zrevrange",
2023        arity: -4,
2024        flags: READ_SLOW,
2025        first_key: 1,
2026        last_key: 1,
2027        step: 1,
2028        acl: AC_ZSET_READ_SLOW,
2029        since: "1.2.0",
2030        complexity: "O(log(N)+M) with M the number of members answered",
2031        summary: "A window by rank, counting down from the highest score.",
2032        group: "zset",
2033    },
2034    Spec {
2035        name: "zrangebyscore",
2036        arity: -4,
2037        flags: READ_SLOW,
2038        first_key: 1,
2039        last_key: 1,
2040        step: 1,
2041        acl: AC_ZSET_READ_SLOW,
2042        since: "1.0.5",
2043        complexity: "O(log(N)+M) with M the number of members answered",
2044        summary: "The members whose scores fall between two bounds.",
2045        group: "zset",
2046    },
2047    Spec {
2048        name: "zrevrangebyscore",
2049        arity: -4,
2050        flags: READ_SLOW,
2051        first_key: 1,
2052        last_key: 1,
2053        step: 1,
2054        acl: AC_ZSET_READ_SLOW,
2055        since: "2.2.0",
2056        complexity: "O(log(N)+M) with M the number of members answered",
2057        summary: "The same window as ZRANGEBYSCORE, highest score first and named high end first.",
2058        group: "zset",
2059    },
2060    Spec {
2061        name: "zrangebylex",
2062        arity: -4,
2063        flags: READ_SLOW,
2064        first_key: 1,
2065        last_key: 1,
2066        step: 1,
2067        acl: AC_ZSET_READ_SLOW,
2068        since: "2.8.9",
2069        complexity: "O(log(N)+M) with M the number of members answered",
2070        summary: "The members that fall between two names, for a set where every score is the same.",
2071        group: "zset",
2072    },
2073    Spec {
2074        name: "zrevrangebylex",
2075        arity: -4,
2076        flags: READ_SLOW,
2077        first_key: 1,
2078        last_key: 1,
2079        step: 1,
2080        acl: AC_ZSET_READ_SLOW,
2081        since: "2.8.9",
2082        complexity: "O(log(N)+M) with M the number of members answered",
2083        summary: "The same window as ZRANGEBYLEX, backwards and named high end first.",
2084        group: "zset",
2085    },
2086    Spec {
2087        name: "zrangestore",
2088        arity: -5,
2089        flags: WRITE_OOM,
2090        first_key: 1,
2091        last_key: 2,
2092        step: 1,
2093        acl: AC_ZSET_WRITE_SLOW,
2094        since: "6.2.0",
2095        complexity: "O(log(N)+M) with M the number of members stored",
2096        summary: "Write a window of one sorted set into another key.",
2097        group: "zset",
2098    },
2099    Spec {
2100        name: "zremrangebyrank",
2101        arity: 4,
2102        flags: WRITE_SLOW,
2103        first_key: 1,
2104        last_key: 1,
2105        step: 1,
2106        acl: AC_ZSET_WRITE_SLOW,
2107        since: "2.0.0",
2108        complexity: "O(log(N)+M) with M the number of members removed",
2109        summary: "Remove the members in a range of ranks.",
2110        group: "zset",
2111    },
2112    Spec {
2113        name: "zremrangebyscore",
2114        arity: 4,
2115        flags: WRITE_SLOW,
2116        first_key: 1,
2117        last_key: 1,
2118        step: 1,
2119        acl: AC_ZSET_WRITE_SLOW,
2120        since: "1.2.0",
2121        complexity: "O(log(N)+M) with M the number of members removed",
2122        summary: "Remove the members whose scores fall between two bounds.",
2123        group: "zset",
2124    },
2125    Spec {
2126        name: "zremrangebylex",
2127        arity: 4,
2128        flags: WRITE_SLOW,
2129        first_key: 1,
2130        last_key: 1,
2131        step: 1,
2132        acl: AC_ZSET_WRITE_SLOW,
2133        since: "2.8.9",
2134        complexity: "O(log(N)+M) with M the number of members removed",
2135        summary: "Remove the members that fall between two names.",
2136        group: "zset",
2137    },
2138    Spec {
2139        name: "zunion",
2140        arity: -3,
2141        flags: READ_MOVABLE,
2142        first_key: 0,
2143        last_key: 0,
2144        step: 0,
2145        acl: AC_ZSET_READ_SLOW,
2146        since: "6.2.0",
2147        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2148        summary: "Every member of these sorted sets, with the scores combined.",
2149        group: "zset",
2150    },
2151    Spec {
2152        name: "zinter",
2153        arity: -3,
2154        flags: READ_MOVABLE,
2155        first_key: 0,
2156        last_key: 0,
2157        step: 0,
2158        acl: AC_ZSET_READ_SLOW,
2159        since: "6.2.0",
2160        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2161        summary: "Only the members all of these sorted sets have, with the scores combined.",
2162        group: "zset",
2163    },
2164    Spec {
2165        name: "zdiff",
2166        arity: -3,
2167        flags: READ_MOVABLE,
2168        first_key: 0,
2169        last_key: 0,
2170        step: 0,
2171        acl: AC_ZSET_READ_SLOW,
2172        since: "6.2.0",
2173        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2174        summary: "The members of the first that none of the rest have.",
2175        group: "zset",
2176    },
2177    Spec {
2178        name: "zunionstore",
2179        arity: -4,
2180        flags: WRITE_MOVABLE,
2181        first_key: 1,
2182        last_key: 1,
2183        step: 1,
2184        acl: AC_ZSET_WRITE_SLOW,
2185        since: "2.0.0",
2186        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2187        summary: "Store the union in another key and say how big it is.",
2188        group: "zset",
2189    },
2190    Spec {
2191        name: "zinterstore",
2192        arity: -4,
2193        flags: WRITE_MOVABLE,
2194        first_key: 1,
2195        last_key: 1,
2196        step: 1,
2197        acl: AC_ZSET_WRITE_SLOW,
2198        since: "2.0.0",
2199        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2200        summary: "Store the intersection in another key and say how big it is.",
2201        group: "zset",
2202    },
2203    Spec {
2204        name: "zdiffstore",
2205        arity: -4,
2206        flags: WRITE_MOVABLE,
2207        first_key: 1,
2208        last_key: 1,
2209        step: 1,
2210        acl: AC_ZSET_WRITE_SLOW,
2211        since: "6.2.0",
2212        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2213        summary: "Store the difference in another key and say how big it is.",
2214        group: "zset",
2215    },
2216    Spec {
2217        name: "zintercard",
2218        arity: -3,
2219        flags: READ_MOVABLE,
2220        first_key: 0,
2221        last_key: 0,
2222        step: 0,
2223        acl: AC_ZSET_READ_SLOW,
2224        since: "7.0.0",
2225        complexity: "O(N*M) worst case, N the smallest input and M the number of inputs",
2226        summary: "How many members the intersection would have, without building it.",
2227        group: "zset",
2228    },
2229    Spec {
2230        name: "zrandmember",
2231        arity: -2,
2232        flags: READ_SLOW,
2233        first_key: 1,
2234        last_key: 1,
2235        step: 1,
2236        acl: AC_ZSET_READ_SLOW,
2237        since: "6.2.0",
2238        complexity: "O(N) with N the number of members drawn",
2239        summary: "Draw members at random, with or without replacement.",
2240        group: "zset",
2241    },
2242    Spec {
2243        name: "zscan",
2244        arity: -3,
2245        flags: READ_SLOW,
2246        first_key: 1,
2247        last_key: 1,
2248        step: 1,
2249        acl: AC_ZSET_READ_SLOW,
2250        since: "2.8.0",
2251        complexity: "O(1) per call, O(N) over a full walk",
2252        summary: "Walk the members and their scores a batch at a time.",
2253        group: "zset",
2254    },
2255    // The pops. Redis calls the two single key ones fast even though they cost a
2256    // logarithm, on the grounds that the logarithm is of a size a client chose.
2257    Spec {
2258        name: "zpopmin",
2259        arity: -2,
2260        flags: WRITE_FAST,
2261        first_key: 1,
2262        last_key: 1,
2263        step: 1,
2264        acl: AC_ZSET_WRITE_FAST,
2265        since: "5.0.0",
2266        complexity: "O(log(N)*M) with M the number of members popped",
2267        summary: "Take the lowest scoring members off and answer them.",
2268        group: "zset",
2269    },
2270    Spec {
2271        name: "zpopmax",
2272        arity: -2,
2273        flags: WRITE_FAST,
2274        first_key: 1,
2275        last_key: 1,
2276        step: 1,
2277        acl: AC_ZSET_WRITE_FAST,
2278        since: "5.0.0",
2279        complexity: "O(log(N)*M) with M the number of members popped",
2280        summary: "Take the highest scoring members off and answer them.",
2281        group: "zset",
2282    },
2283    // Keys behind a count, so the same three zeroes `LMPOP` has, and `write`
2284    // without `denyoom` because a pop cannot grow the keyspace.
2285    Spec {
2286        name: "zmpop",
2287        arity: -4,
2288        flags: &["write", "movablekeys"],
2289        first_key: 0,
2290        last_key: 0,
2291        step: 0,
2292        acl: AC_ZSET_WRITE_SLOW,
2293        since: "7.0.0",
2294        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
2295        summary: "Pop from the first of several sorted sets that has anything in it.",
2296        group: "zset",
2297    },
2298    // The three that wait. `blocking` is what the dispatcher branches on, the
2299    // same as it is for the five list ones.
2300    Spec {
2301        name: "bzpopmin",
2302        arity: -3,
2303        flags: &["write", "blocking", "fast"],
2304        first_key: 1,
2305        last_key: -2,
2306        step: 1,
2307        acl: AC_ZSET_BLOCKING_FAST,
2308        since: "5.0.0",
2309        complexity: "O(log(N)) with N the size of the sorted set that answers",
2310        summary: "Take the lowest scoring member off the first sorted set that has one, waiting if none does.",
2311        group: "zset",
2312    },
2313    Spec {
2314        name: "bzpopmax",
2315        arity: -3,
2316        flags: &["write", "blocking", "fast"],
2317        first_key: 1,
2318        last_key: -2,
2319        step: 1,
2320        acl: AC_ZSET_BLOCKING_FAST,
2321        since: "5.0.0",
2322        complexity: "O(log(N)) with N the size of the sorted set that answers",
2323        summary: "Take the highest scoring member off the first sorted set that has one, waiting if none does.",
2324        group: "zset",
2325    },
2326    Spec {
2327        name: "bzmpop",
2328        arity: -5,
2329        flags: &["write", "blocking", "movablekeys"],
2330        first_key: 0,
2331        last_key: 0,
2332        step: 0,
2333        acl: AC_ZSET_BLOCKING_SLOW,
2334        since: "7.0.0",
2335        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
2336        summary: "Pop from the first of several sorted sets that has anything, waiting if none does.",
2337        group: "zset",
2338    },
2339    // ----------------------------------------------------------------- geo
2340    Spec {
2341        name: "geoadd",
2342        arity: -5,
2343        flags: WRITE_OOM,
2344        first_key: 1,
2345        last_key: 1,
2346        step: 1,
2347        acl: AC_GEO_WRITE,
2348        since: "3.2.0",
2349        complexity: "O(log(N)) per point added",
2350        summary: "Add places to a geo key, which is a sorted set of position hashes.",
2351        group: "geo",
2352    },
2353    Spec {
2354        name: "geopos",
2355        arity: -2,
2356        flags: READ_SLOW,
2357        first_key: 1,
2358        last_key: 1,
2359        step: 1,
2360        acl: AC_GEO_READ,
2361        since: "3.2.0",
2362        complexity: "O(1) per member asked about",
2363        summary: "Answer where each member is, as a longitude and a latitude.",
2364        group: "geo",
2365    },
2366    Spec {
2367        name: "geodist",
2368        arity: -4,
2369        flags: READ_SLOW,
2370        first_key: 1,
2371        last_key: 1,
2372        step: 1,
2373        acl: AC_GEO_READ,
2374        since: "3.2.0",
2375        complexity: "O(1)",
2376        summary: "Answer how far apart two members are, in the unit asked for.",
2377        group: "geo",
2378    },
2379    Spec {
2380        name: "geohash",
2381        arity: -2,
2382        flags: READ_SLOW,
2383        first_key: 1,
2384        last_key: 1,
2385        step: 1,
2386        acl: AC_GEO_READ,
2387        since: "3.2.0",
2388        complexity: "O(1) per member asked about",
2389        summary: "Answer each member's position as a standard eleven character geohash.",
2390        group: "geo",
2391    },
2392    Spec {
2393        name: "geosearch",
2394        arity: -7,
2395        flags: READ_SLOW,
2396        first_key: 1,
2397        last_key: 1,
2398        step: 1,
2399        acl: AC_GEO_READ,
2400        since: "6.2.0",
2401        complexity: "O(N+log(M)) with N the members in the boxes searched",
2402        summary: "Find the members inside a circle or a rectangle around a point.",
2403        group: "geo",
2404    },
2405    Spec {
2406        name: "geosearchstore",
2407        arity: -8,
2408        flags: WRITE_OOM,
2409        first_key: 1,
2410        last_key: 2,
2411        step: 1,
2412        acl: AC_GEO_WRITE,
2413        since: "6.2.0",
2414        complexity: "O(N+log(M)) with N the members in the boxes searched",
2415        summary: "Run a search and write what it found into another key.",
2416        group: "geo",
2417    },
2418    Spec {
2419        name: "georadius",
2420        arity: -6,
2421        flags: WRITE_MOVABLE,
2422        first_key: 1,
2423        last_key: 1,
2424        step: 1,
2425        acl: AC_GEO_WRITE,
2426        since: "3.2.0",
2427        complexity: "O(N+log(M)) with N the members in the boxes searched",
2428        summary: "The older spelling of a circular search, which can also store.",
2429        group: "geo",
2430    },
2431    Spec {
2432        name: "georadius_ro",
2433        arity: -6,
2434        flags: READ_SLOW,
2435        first_key: 1,
2436        last_key: 1,
2437        step: 1,
2438        acl: AC_GEO_READ,
2439        since: "3.2.10",
2440        complexity: "O(N+log(M)) with N the members in the boxes searched",
2441        summary: "GEORADIUS without the store options, so a replica can serve it.",
2442        group: "geo",
2443    },
2444    Spec {
2445        name: "georadiusbymember",
2446        arity: -5,
2447        flags: WRITE_MOVABLE,
2448        first_key: 1,
2449        last_key: 1,
2450        step: 1,
2451        acl: AC_GEO_WRITE,
2452        since: "3.2.0",
2453        complexity: "O(N+log(M)) with N the members in the boxes searched",
2454        summary: "The same search centred on a member rather than on a point.",
2455        group: "geo",
2456    },
2457    Spec {
2458        name: "georadiusbymember_ro",
2459        arity: -5,
2460        flags: READ_SLOW,
2461        first_key: 1,
2462        last_key: 1,
2463        step: 1,
2464        acl: AC_GEO_READ,
2465        since: "3.2.10",
2466        complexity: "O(N+log(M)) with N the members in the boxes searched",
2467        summary: "GEORADIUSBYMEMBER without the store options.",
2468        group: "geo",
2469    },
2470    // --------------------------------------------------------------- graph
2471    Spec {
2472        name: "g.nadd",
2473        arity: -3,
2474        flags: WRITE_FAST_OOM,
2475        first_key: 1,
2476        last_key: 1,
2477        step: 1,
2478        acl: AC_GRAPH_WRITE_FAST,
2479        since: "8.8.0",
2480        complexity: "O(N) with N the fields written",
2481        summary: "Write a node and its properties, creating it if it is new.",
2482        group: "graph",
2483    },
2484    Spec {
2485        name: "g.nget",
2486        arity: 3,
2487        flags: READ_FAST,
2488        first_key: 1,
2489        last_key: 1,
2490        step: 1,
2491        acl: AC_GRAPH_READ_FAST,
2492        since: "8.8.0",
2493        complexity: "O(N) with N the fields on the node",
2494        summary: "Every property on a node.",
2495        group: "graph",
2496    },
2497    Spec {
2498        name: "g.ndel",
2499        arity: 3,
2500        flags: WRITE_FAST,
2501        first_key: 1,
2502        last_key: 1,
2503        step: 1,
2504        acl: AC_GRAPH_WRITE_FAST,
2505        since: "8.8.0",
2506        complexity: "O(E) with E the edges on the node",
2507        summary: "Delete a node and every edge that touches it.",
2508        group: "graph",
2509    },
2510    Spec {
2511        name: "g.eadd",
2512        arity: -5,
2513        flags: WRITE_FAST_OOM,
2514        first_key: 1,
2515        last_key: 1,
2516        step: 1,
2517        acl: AC_GRAPH_WRITE_FAST,
2518        since: "8.8.0",
2519        complexity: "O(D) with D the outgoing degree under the label",
2520        summary: "Write an edge and its properties, creating either end if it is new.",
2521        group: "graph",
2522    },
2523    Spec {
2524        name: "g.edel",
2525        arity: 5,
2526        flags: WRITE_FAST,
2527        first_key: 1,
2528        last_key: 1,
2529        step: 1,
2530        acl: AC_GRAPH_WRITE_FAST,
2531        since: "8.8.0",
2532        complexity: "O(D) with D the outgoing degree under the label",
2533        summary: "Delete one edge between two nodes under a label.",
2534        group: "graph",
2535    },
2536    Spec {
2537        name: "g.out",
2538        arity: -4,
2539        flags: READ_FAST,
2540        first_key: 1,
2541        last_key: 1,
2542        step: 1,
2543        acl: AC_GRAPH_READ_FAST,
2544        since: "8.8.0",
2545        complexity: "O(N) with N the page asked for",
2546        summary: "Outgoing neighbours under a label, a page at a time.",
2547        group: "graph",
2548    },
2549    Spec {
2550        name: "g.in",
2551        arity: -4,
2552        flags: READ_FAST,
2553        first_key: 1,
2554        last_key: 1,
2555        step: 1,
2556        acl: AC_GRAPH_READ_FAST,
2557        since: "8.8.0",
2558        complexity: "O(N) with N the page asked for",
2559        summary: "Incoming neighbours under a label, a page at a time.",
2560        group: "graph",
2561    },
2562    Spec {
2563        name: "g.deg",
2564        arity: -4,
2565        flags: READ_FAST,
2566        first_key: 1,
2567        last_key: 1,
2568        step: 1,
2569        acl: AC_GRAPH_READ_FAST,
2570        since: "8.8.0",
2571        complexity: "O(1)",
2572        summary: "How many edges a node has under a label.",
2573        group: "graph",
2574    },
2575    Spec {
2576        name: "g.neigh",
2577        arity: -4,
2578        flags: READ_SLOW,
2579        first_key: 1,
2580        last_key: 1,
2581        step: 1,
2582        acl: AC_GRAPH_READ_SLOW,
2583        since: "8.8.0",
2584        complexity: "O(V + E) over the ball the depth reaches",
2585        summary: "Everything reachable within a depth, each node once.",
2586        group: "graph",
2587    },
2588    Spec {
2589        name: "g.path",
2590        arity: -4,
2591        flags: READ_SLOW,
2592        first_key: 1,
2593        last_key: 1,
2594        step: 1,
2595        acl: AC_GRAPH_READ_SLOW,
2596        since: "8.8.0",
2597        complexity: "O(b^(d/2)) with b the branching factor and d the distance",
2598        summary: "A shortest path between two nodes, searched from both ends.",
2599        group: "graph",
2600    },
2601    // ---------------------------------------------------------------- json
2602    Spec {
2603        name: "json.set",
2604        arity: -4,
2605        flags: JSON_WRITE_OOM,
2606        first_key: 1,
2607        last_key: 1,
2608        step: 1,
2609        acl: AC_JSON_WRITE,
2610        since: "1.0.0",
2611        complexity: "O(N) with N the size of the document",
2612        summary: "Set the value at a path, creating the document at the root.",
2613        group: "json",
2614    },
2615    Spec {
2616        name: "json.mset",
2617        arity: -4,
2618        flags: JSON_WRITE_OOM,
2619        first_key: 1,
2620        last_key: -1,
2621        step: 3,
2622        acl: AC_JSON_WRITE,
2623        since: "2.6.0",
2624        complexity: "O(K*N) with K the keys and N the size of each document",
2625        summary: "Set the value at a path in each of several documents.",
2626        group: "json",
2627    },
2628    Spec {
2629        name: "json.merge",
2630        arity: -4,
2631        flags: JSON_WRITE_OOM,
2632        first_key: 1,
2633        last_key: 1,
2634        step: 1,
2635        acl: AC_JSON_WRITE,
2636        since: "2.6.0",
2637        complexity: "O(N) with N the size of the document",
2638        summary: "Apply an RFC 7386 merge patch at a path.",
2639        group: "json",
2640    },
2641    Spec {
2642        name: "json.get",
2643        arity: -2,
2644        flags: JSON_READ,
2645        first_key: 1,
2646        last_key: 1,
2647        step: 1,
2648        acl: AC_JSON_READ,
2649        since: "1.0.0",
2650        complexity: "O(N) with N the size of what the paths matched",
2651        summary: "The values one or more paths match, as JSON text.",
2652        group: "json",
2653    },
2654    Spec {
2655        name: "json.mget",
2656        arity: -3,
2657        flags: JSON_READ,
2658        first_key: 1,
2659        last_key: -2,
2660        step: 1,
2661        acl: AC_JSON_READ,
2662        since: "1.0.0",
2663        complexity: "O(K*N) with K the keys and N the size of each document",
2664        summary: "One path against several documents, one answer per key.",
2665        group: "json",
2666    },
2667    Spec {
2668        name: "json.del",
2669        arity: -2,
2670        flags: JSON_WRITE,
2671        first_key: 1,
2672        last_key: 1,
2673        step: 1,
2674        acl: AC_JSON_WRITE,
2675        since: "1.0.0",
2676        complexity: "O(N) with N the size of the document",
2677        summary: "Remove what a path matched, or the key when it is the root.",
2678        group: "json",
2679    },
2680    Spec {
2681        name: "json.forget",
2682        arity: -2,
2683        flags: JSON_WRITE,
2684        first_key: 1,
2685        last_key: 1,
2686        step: 1,
2687        acl: AC_JSON_WRITE,
2688        since: "1.0.0",
2689        complexity: "O(N) with N the size of the document",
2690        summary: "The same command as JSON.DEL, under its other name.",
2691        group: "json",
2692    },
2693    Spec {
2694        name: "json.type",
2695        arity: -2,
2696        flags: JSON_READ,
2697        first_key: 1,
2698        last_key: 1,
2699        step: 1,
2700        acl: AC_JSON_READ,
2701        since: "1.0.0",
2702        complexity: "O(N) with N the size of the document",
2703        summary: "The JSON type of what a path matched.",
2704        group: "json",
2705    },
2706    Spec {
2707        name: "json.toggle",
2708        arity: 3,
2709        flags: JSON_WRITE,
2710        first_key: 1,
2711        last_key: 1,
2712        step: 1,
2713        acl: AC_JSON_WRITE,
2714        since: "2.0.0",
2715        complexity: "O(N) with N the size of the document",
2716        summary: "Flip every boolean a path matched.",
2717        group: "json",
2718    },
2719    Spec {
2720        name: "json.clear",
2721        arity: -2,
2722        flags: JSON_WRITE,
2723        first_key: 1,
2724        last_key: 1,
2725        step: 1,
2726        acl: AC_JSON_WRITE,
2727        since: "2.0.0",
2728        complexity: "O(N) with N the size of the document",
2729        summary: "Empty the containers and zero the numbers a path matched.",
2730        group: "json",
2731    },
2732    Spec {
2733        name: "json.arrlen",
2734        arity: -2,
2735        flags: JSON_READ,
2736        first_key: 1,
2737        last_key: 1,
2738        step: 1,
2739        acl: AC_JSON_READ,
2740        since: "1.0.0",
2741        complexity: "O(1)",
2742        summary: "How many elements are in the arrays a path matched.",
2743        group: "json",
2744    },
2745    Spec {
2746        name: "json.objlen",
2747        arity: -2,
2748        flags: JSON_READ,
2749        first_key: 1,
2750        last_key: 1,
2751        step: 1,
2752        acl: AC_JSON_READ,
2753        since: "1.0.0",
2754        complexity: "O(1)",
2755        summary: "How many members are in the objects a path matched.",
2756        group: "json",
2757    },
2758    Spec {
2759        name: "json.strlen",
2760        arity: -2,
2761        flags: JSON_READ,
2762        first_key: 1,
2763        last_key: 1,
2764        step: 1,
2765        acl: AC_JSON_READ,
2766        since: "1.0.0",
2767        complexity: "O(1)",
2768        summary: "How long the strings a path matched are, in bytes.",
2769        group: "json",
2770    },
2771    Spec {
2772        name: "json.objkeys",
2773        arity: -2,
2774        flags: JSON_READ,
2775        first_key: 1,
2776        last_key: 1,
2777        step: 1,
2778        acl: AC_JSON_READ,
2779        since: "1.0.0",
2780        complexity: "O(N) with N the number of members",
2781        summary: "The keys of the objects a path matched.",
2782        group: "json",
2783    },
2784    Spec {
2785        name: "json.arrappend",
2786        arity: -3,
2787        flags: JSON_WRITE_OOM,
2788        first_key: 1,
2789        last_key: 1,
2790        step: 1,
2791        acl: AC_JSON_WRITE,
2792        since: "1.0.0",
2793        complexity: "O(N) with N the size of the document",
2794        summary: "Add values to the end of the arrays a path matched.",
2795        group: "json",
2796    },
2797    Spec {
2798        name: "json.arrinsert",
2799        arity: -5,
2800        flags: JSON_WRITE_OOM,
2801        first_key: 1,
2802        last_key: 1,
2803        step: 1,
2804        acl: AC_JSON_WRITE,
2805        since: "1.0.0",
2806        complexity: "O(N) with N the size of the document",
2807        summary: "Put values into the arrays a path matched, at an index.",
2808        group: "json",
2809    },
2810    Spec {
2811        name: "json.arrtrim",
2812        arity: 5,
2813        flags: JSON_WRITE,
2814        first_key: 1,
2815        last_key: 1,
2816        step: 1,
2817        acl: AC_JSON_WRITE,
2818        since: "1.0.0",
2819        complexity: "O(N) with N the size of the document",
2820        summary: "Keep only a run of the arrays a path matched.",
2821        group: "json",
2822    },
2823    Spec {
2824        name: "json.arrpop",
2825        arity: -2,
2826        flags: JSON_WRITE,
2827        first_key: 1,
2828        last_key: 1,
2829        step: 1,
2830        acl: AC_JSON_WRITE,
2831        since: "1.0.0",
2832        complexity: "O(N) with N the size of the document",
2833        summary: "Take one element out of the arrays a path matched.",
2834        group: "json",
2835    },
2836    Spec {
2837        name: "json.arrindex",
2838        arity: -4,
2839        flags: JSON_READ,
2840        first_key: 1,
2841        last_key: 1,
2842        step: 1,
2843        acl: AC_JSON_READ,
2844        since: "1.0.0",
2845        complexity: "O(N) with N the number of elements",
2846        summary: "Where a value first sits in the arrays a path matched.",
2847        group: "json",
2848    },
2849    Spec {
2850        name: "json.numincrby",
2851        arity: 4,
2852        flags: JSON_WRITE,
2853        first_key: 1,
2854        last_key: 1,
2855        step: 1,
2856        acl: AC_JSON_WRITE,
2857        since: "1.0.0",
2858        complexity: "O(N) with N the size of the document",
2859        summary: "Add to every number a path matched.",
2860        group: "json",
2861    },
2862    Spec {
2863        name: "json.nummultby",
2864        arity: 4,
2865        flags: JSON_WRITE,
2866        first_key: 1,
2867        last_key: 1,
2868        step: 1,
2869        acl: AC_JSON_WRITE,
2870        since: "1.0.0",
2871        complexity: "O(N) with N the size of the document",
2872        summary: "Multiply every number a path matched.",
2873        group: "json",
2874    },
2875    Spec {
2876        name: "json.numpowby",
2877        arity: 4,
2878        flags: JSON_WRITE,
2879        first_key: 1,
2880        last_key: 1,
2881        step: 1,
2882        acl: AC_JSON_WRITE,
2883        since: "1.0.0",
2884        complexity: "O(N) with N the size of the document",
2885        summary: "Raise every number a path matched to a power.",
2886        group: "json",
2887    },
2888    Spec {
2889        name: "json.strappend",
2890        arity: -3,
2891        flags: JSON_WRITE_OOM,
2892        first_key: 1,
2893        last_key: 1,
2894        step: 1,
2895        acl: AC_JSON_WRITE,
2896        since: "1.0.0",
2897        complexity: "O(N) with N the size of the document",
2898        summary: "Add to the end of every string a path matched.",
2899        group: "json",
2900    },
2901    Spec {
2902        name: "json.resp",
2903        arity: -2,
2904        flags: JSON_READ,
2905        first_key: 1,
2906        last_key: 1,
2907        step: 1,
2908        acl: AC_JSON_READ,
2909        since: "1.0.0",
2910        complexity: "O(N) with N the size of what the path matched",
2911        summary: "What a path matched, as RESP types rather than as JSON text.",
2912        group: "json",
2913    },
2914    Spec {
2915        name: "json.debug",
2916        arity: -2,
2917        flags: JSON_READ_MOVABLE,
2918        first_key: 0,
2919        last_key: 0,
2920        step: 0,
2921        acl: AC_JSON_READ,
2922        since: "1.0.0",
2923        complexity: "O(N) with N the size of what the path matched",
2924        summary: "How much memory a document takes, and the help for that.",
2925        group: "json",
2926    },
2927    // -------------------------------------------------------------- vector
2928    Spec {
2929        name: "VADD",
2930        arity: -5,
2931        flags: VECTOR_WRITE_OOM,
2932        first_key: 1,
2933        last_key: 1,
2934        step: 1,
2935        acl: AC_NONE,
2936        since: "8.0.0",
2937        complexity: "O(P*D) with P the partitions probed and D the dimension",
2938        summary: "Add a vector to a vector set under an element name.",
2939        group: "vector",
2940    },
2941    Spec {
2942        name: "VSIM",
2943        arity: -4,
2944        flags: VECTOR_READ,
2945        first_key: 1,
2946        last_key: 1,
2947        step: 1,
2948        acl: AC_NONE,
2949        since: "8.0.0",
2950        complexity: "O(P*D) with P the partitions probed and D the dimension",
2951        summary: "The elements nearest a vector or nearest another element.",
2952        group: "vector",
2953    },
2954    Spec {
2955        name: "VREM",
2956        arity: 3,
2957        flags: VECTOR_WRITE,
2958        first_key: 1,
2959        last_key: 1,
2960        step: 1,
2961        acl: AC_NONE,
2962        since: "8.0.0",
2963        complexity: "O(1)",
2964        summary: "Remove an element and its vector from a vector set.",
2965        group: "vector",
2966    },
2967    Spec {
2968        name: "VCARD",
2969        arity: 2,
2970        flags: VECTOR_READ_FAST,
2971        first_key: 1,
2972        last_key: 1,
2973        step: 1,
2974        acl: AC_NONE,
2975        since: "8.0.0",
2976        complexity: "O(1)",
2977        summary: "How many elements a vector set holds.",
2978        group: "vector",
2979    },
2980    Spec {
2981        name: "VDIM",
2982        arity: 2,
2983        flags: VECTOR_READ_FAST,
2984        first_key: 1,
2985        last_key: 1,
2986        step: 1,
2987        acl: AC_NONE,
2988        since: "8.0.0",
2989        complexity: "O(1)",
2990        summary: "How many dimensions the vectors in a vector set have.",
2991        group: "vector",
2992    },
2993    Spec {
2994        name: "VEMB",
2995        arity: -3,
2996        flags: VECTOR_READ_FAST,
2997        first_key: 1,
2998        last_key: 1,
2999        step: 1,
3000        acl: AC_NONE,
3001        since: "8.0.0",
3002        complexity: "O(D) with D the dimension",
3003        summary: "The vector an element went in with.",
3004        group: "vector",
3005    },
3006    Spec {
3007        name: "VINFO",
3008        arity: 2,
3009        flags: VECTOR_READ_FAST,
3010        first_key: 1,
3011        last_key: 1,
3012        step: 1,
3013        acl: AC_NONE,
3014        since: "8.0.0",
3015        complexity: "O(N) with N the elements, for the attribute count",
3016        summary: "What a vector set is and how its index is tuned.",
3017        group: "vector",
3018    },
3019    Spec {
3020        name: "VISMEMBER",
3021        arity: 3,
3022        flags: VECTOR_READ,
3023        first_key: 1,
3024        last_key: 1,
3025        step: 1,
3026        acl: AC_NONE,
3027        since: "8.0.0",
3028        complexity: "O(1)",
3029        summary: "Whether an element is in a vector set.",
3030        group: "vector",
3031    },
3032    Spec {
3033        name: "VRANDMEMBER",
3034        arity: -2,
3035        flags: VECTOR_READ,
3036        first_key: 1,
3037        last_key: 1,
3038        step: 1,
3039        acl: AC_NONE,
3040        since: "8.0.0",
3041        complexity: "O(1) for one, O(N) for a positive count",
3042        summary: "Random elements of a vector set.",
3043        group: "vector",
3044    },
3045    Spec {
3046        name: "VLINKS",
3047        arity: -3,
3048        flags: VECTOR_READ_FAST,
3049        first_key: 1,
3050        last_key: 1,
3051        step: 1,
3052        acl: AC_NONE,
3053        since: "8.0.0",
3054        complexity: "O(P*D) with P the partitions probed and D the dimension",
3055        summary: "The elements an element is stored next to.",
3056        group: "vector",
3057    },
3058    Spec {
3059        name: "VSETATTR",
3060        arity: 4,
3061        flags: VECTOR_WRITE_FAST,
3062        first_key: 1,
3063        last_key: 1,
3064        step: 1,
3065        acl: AC_NONE,
3066        since: "8.0.0",
3067        complexity: "O(1)",
3068        summary: "Set the attribute string on an element, or clear it.",
3069        group: "vector",
3070    },
3071    Spec {
3072        name: "VGETATTR",
3073        arity: 3,
3074        flags: VECTOR_READ_FAST,
3075        first_key: 1,
3076        last_key: 1,
3077        step: 1,
3078        acl: AC_NONE,
3079        since: "8.0.0",
3080        complexity: "O(1)",
3081        summary: "The attribute string on an element.",
3082        group: "vector",
3083    },
3084    Spec {
3085        name: "VRANGE",
3086        arity: -4,
3087        flags: VECTOR_READ,
3088        first_key: 1,
3089        last_key: 1,
3090        step: 1,
3091        acl: AC_NONE,
3092        since: "8.4.0",
3093        complexity: "O(N log N) with N the elements the range covers",
3094        summary: "The elements of a vector set whose names fall in a range.",
3095        group: "vector",
3096    },
3097    // -------------------------------------------------------------- search
3098    //
3099    // None of these carries a key spec, and the three zeros are the module's
3100    // own answer rather than a gap here. An index name is not a key: it is not
3101    // in the keyspace, `TYPE` has nothing to say about it, and a cluster client
3102    // has nothing to route on. `FT.SUGADD` and the four deprecated document
3103    // commands do carry real keys, and they arrive with the rest of the search
3104    // surface rather than here.
3105    Spec {
3106        name: "FT.CREATE",
3107        arity: -5,
3108        flags: SEARCH_WRITE_OOM,
3109        first_key: 0,
3110        last_key: 0,
3111        step: 0,
3112        acl: AC_SEARCH,
3113        since: "1.0.0",
3114        complexity: "O(K) with K the fields declared, plus O(N) over the keyspace when the initial scan runs",
3115        summary: "Create an index over the keys with a prefix, with the given schema.",
3116        group: "search",
3117    },
3118    Spec {
3119        name: "FT._CREATEIFNX",
3120        arity: -5,
3121        flags: SEARCH_WRITE_OOM,
3122        first_key: 0,
3123        last_key: 0,
3124        step: 0,
3125        acl: AC_SEARCH,
3126        since: "1.0.0",
3127        complexity: "O(K) with K the fields declared, plus O(N) over the keyspace when the initial scan runs",
3128        summary: "Create an index, and say nothing if one of that name is already there.",
3129        group: "search",
3130    },
3131    Spec {
3132        name: "FT.ALTER",
3133        arity: -6,
3134        flags: SEARCH_WRITE_OOM,
3135        first_key: 0,
3136        last_key: 0,
3137        step: 0,
3138        acl: AC_SEARCH,
3139        since: "1.0.0",
3140        complexity: "O(N) over the keys the index follows, when the fields are backfilled",
3141        summary: "Add fields to an index's schema.",
3142        group: "search",
3143    },
3144    Spec {
3145        name: "FT._ALTERIFNX",
3146        arity: -6,
3147        flags: SEARCH_WRITE_OOM,
3148        first_key: 0,
3149        last_key: 0,
3150        step: 0,
3151        acl: AC_SEARCH,
3152        since: "1.0.0",
3153        complexity: "O(N) over the keys the index follows, when the fields are backfilled",
3154        summary: "Add fields to a schema, and say nothing about the ones already there.",
3155        group: "search",
3156    },
3157    Spec {
3158        name: "FT.DROPINDEX",
3159        arity: -2,
3160        flags: SEARCH_WRITE,
3161        first_key: 0,
3162        last_key: 0,
3163        step: 0,
3164        acl: AC_SEARCH_DROP,
3165        since: "2.0.0",
3166        complexity: "O(1), or O(N) over the documents when DD is given",
3167        summary: "Take an index away, and its documents with it when DD is given.",
3168        group: "search",
3169    },
3170    Spec {
3171        name: "FT._DROPINDEXIFX",
3172        arity: -2,
3173        flags: SEARCH_WRITE,
3174        first_key: 0,
3175        last_key: 0,
3176        step: 0,
3177        acl: AC_SEARCH_DROP,
3178        since: "2.0.0",
3179        complexity: "O(1), or O(N) over the documents when DD is given",
3180        summary: "Take an index away, and say nothing when there is none of that name.",
3181        group: "search",
3182    },
3183    Spec {
3184        name: "FT.DROP",
3185        arity: -1,
3186        flags: SEARCH_WRITE,
3187        first_key: 0,
3188        last_key: 0,
3189        step: 0,
3190        acl: AC_SEARCH_DROP,
3191        since: "1.0.0",
3192        complexity: "O(1)",
3193        summary: "Take an index away. Deprecated, and FT.DROPINDEX is the name to use.",
3194        group: "search",
3195    },
3196    Spec {
3197        name: "FT._DROPIFX",
3198        arity: -1,
3199        flags: SEARCH_WRITE,
3200        first_key: 0,
3201        last_key: 0,
3202        step: 0,
3203        acl: AC_SEARCH_WRITE,
3204        since: "1.0.0",
3205        complexity: "O(1)",
3206        summary: "Take an index away and say nothing when there is none. Deprecated.",
3207        group: "search",
3208    },
3209    Spec {
3210        name: "FT.INFO",
3211        arity: 2,
3212        flags: SEARCH_READ,
3213        first_key: 0,
3214        last_key: 0,
3215        step: 0,
3216        acl: AC_SEARCH,
3217        since: "1.0.0",
3218        complexity: "O(1)",
3219        summary: "Everything the server knows about one index.",
3220        group: "search",
3221    },
3222    Spec {
3223        name: "FT._LIST",
3224        arity: -1,
3225        flags: SEARCH_READ,
3226        first_key: 0,
3227        last_key: 0,
3228        step: 0,
3229        acl: AC_SEARCH_LIST,
3230        since: "2.0.0",
3231        complexity: "O(N) with N the indexes on the server",
3232        summary: "Every index on the server, by name.",
3233        group: "search",
3234    },
3235    Spec {
3236        name: "FT.ALIASADD",
3237        arity: 3,
3238        flags: SEARCH_WRITE_OOM,
3239        first_key: 0,
3240        last_key: 0,
3241        step: 0,
3242        acl: AC_SEARCH,
3243        since: "1.0.0",
3244        complexity: "O(1)",
3245        summary: "Point another name at an index.",
3246        group: "search",
3247    },
3248    Spec {
3249        name: "FT._ALIASADDIFNX",
3250        arity: 3,
3251        flags: SEARCH_WRITE_OOM,
3252        first_key: 0,
3253        last_key: 0,
3254        step: 0,
3255        acl: AC_SEARCH,
3256        since: "1.0.0",
3257        complexity: "O(1)",
3258        summary: "Point another name at an index, and say nothing if it is taken.",
3259        group: "search",
3260    },
3261    Spec {
3262        name: "FT.ALIASDEL",
3263        arity: 2,
3264        flags: SEARCH_WRITE,
3265        first_key: 0,
3266        last_key: 0,
3267        step: 0,
3268        acl: AC_SEARCH,
3269        since: "1.0.0",
3270        complexity: "O(1)",
3271        summary: "Take an alias away.",
3272        group: "search",
3273    },
3274    Spec {
3275        name: "FT._ALIASDELIFX",
3276        arity: 2,
3277        flags: SEARCH_WRITE,
3278        first_key: 0,
3279        last_key: 0,
3280        step: 0,
3281        acl: AC_SEARCH,
3282        since: "1.0.0",
3283        complexity: "O(1)",
3284        summary: "Take an alias away, and say nothing when there is none.",
3285        group: "search",
3286    },
3287    Spec {
3288        name: "FT.ALIASUPDATE",
3289        arity: 3,
3290        flags: SEARCH_WRITE_OOM,
3291        first_key: 0,
3292        last_key: 0,
3293        step: 0,
3294        acl: AC_SEARCH,
3295        since: "1.0.0",
3296        complexity: "O(1)",
3297        summary: "Move an alias to another index, adding it when it was not there.",
3298        group: "search",
3299    },
3300    Spec {
3301        name: "FT.ALIASLIST",
3302        arity: 2,
3303        flags: SEARCH_READ,
3304        first_key: 0,
3305        last_key: 0,
3306        step: 0,
3307        acl: AC_SEARCH,
3308        since: "8.10.0",
3309        complexity: "O(N) with N the aliases pointing at the index",
3310        summary: "The aliases pointing at one index.",
3311        group: "search",
3312    },
3313    Spec {
3314        name: "FT.EXPLAIN",
3315        arity: -3,
3316        flags: SEARCH_READ,
3317        first_key: 0,
3318        last_key: 0,
3319        step: 0,
3320        acl: AC_SEARCH,
3321        since: "1.0.0",
3322        complexity: "O(1)",
3323        summary: "The tree a query parses into, as text.",
3324        group: "search",
3325    },
3326    Spec {
3327        name: "FT.EXPLAINCLI",
3328        arity: -3,
3329        flags: SEARCH_READ,
3330        first_key: 0,
3331        last_key: 0,
3332        step: 0,
3333        acl: AC_SEARCH,
3334        since: "1.0.0",
3335        complexity: "O(1)",
3336        summary: "The tree a query parses into, one line per reply element.",
3337        group: "search",
3338    },
3339    // --------------------------------------------------------------- bloom
3340    Spec {
3341        name: "bf.reserve",
3342        arity: -4,
3343        flags: BLOOM_WRITE,
3344        first_key: 1,
3345        last_key: 1,
3346        step: 1,
3347        acl: AC_BLOOM_WRITE_FAST,
3348        since: "1.0.0",
3349        complexity: "O(1)",
3350        summary: "Make an empty filter with a given capacity and error rate.",
3351        group: "bloom",
3352    },
3353    Spec {
3354        name: "bf.add",
3355        arity: 3,
3356        flags: BLOOM_WRITE,
3357        first_key: 1,
3358        last_key: 1,
3359        step: 1,
3360        acl: AC_BLOOM_WRITE,
3361        since: "1.0.0",
3362        complexity: "O(K) with K the number of hash functions",
3363        summary: "Add an item, making the filter if the key is free.",
3364        group: "bloom",
3365    },
3366    Spec {
3367        name: "bf.madd",
3368        arity: -3,
3369        flags: BLOOM_WRITE,
3370        first_key: 1,
3371        last_key: 1,
3372        step: 1,
3373        acl: AC_BLOOM_WRITE,
3374        since: "1.0.0",
3375        complexity: "O(N * K) with N the number of items",
3376        summary: "Add several items, making the filter if the key is free.",
3377        group: "bloom",
3378    },
3379    Spec {
3380        name: "bf.insert",
3381        arity: -4,
3382        flags: BLOOM_WRITE,
3383        first_key: 1,
3384        last_key: 1,
3385        step: 1,
3386        acl: AC_BLOOM_WRITE,
3387        since: "1.0.0",
3388        complexity: "O(N * K) with N the number of items",
3389        summary: "Add several items to a filter described in the same command.",
3390        group: "bloom",
3391    },
3392    Spec {
3393        name: "bf.exists",
3394        arity: 3,
3395        flags: BLOOM_READ,
3396        first_key: 1,
3397        last_key: 1,
3398        step: 1,
3399        acl: AC_BLOOM_READ,
3400        since: "1.0.0",
3401        complexity: "O(K) with K the number of hash functions",
3402        summary: "Whether an item is probably in the filter.",
3403        group: "bloom",
3404    },
3405    Spec {
3406        name: "bf.mexists",
3407        arity: -3,
3408        flags: BLOOM_READ,
3409        first_key: 1,
3410        last_key: 1,
3411        step: 1,
3412        acl: AC_BLOOM_READ,
3413        since: "1.0.0",
3414        complexity: "O(N * K) with N the number of items",
3415        summary: "Whether each of several items is probably in the filter.",
3416        group: "bloom",
3417    },
3418    Spec {
3419        name: "bf.scandump",
3420        arity: 3,
3421        flags: BLOOM_READ,
3422        first_key: 1,
3423        last_key: 1,
3424        step: 1,
3425        acl: AC_BLOOM_READ,
3426        since: "1.0.0",
3427        complexity: "O(N) with N the size of the chunk",
3428        summary: "One chunk of the filter, to be replayed into BF.LOADCHUNK.",
3429        group: "bloom",
3430    },
3431    Spec {
3432        name: "bf.loadchunk",
3433        arity: 4,
3434        flags: BLOOM_WRITE,
3435        first_key: 1,
3436        last_key: 1,
3437        step: 1,
3438        acl: AC_BLOOM_WRITE,
3439        since: "1.0.0",
3440        complexity: "O(N) with N the size of the chunk",
3441        summary: "Put back a chunk that BF.SCANDUMP handed out.",
3442        group: "bloom",
3443    },
3444    Spec {
3445        name: "bf.info",
3446        arity: -2,
3447        flags: BLOOM_READ,
3448        first_key: 1,
3449        last_key: 1,
3450        step: 1,
3451        acl: AC_BLOOM_READ_FAST,
3452        since: "1.0.0",
3453        complexity: "O(1)",
3454        summary: "The shape of the filter, or one field of it.",
3455        group: "bloom",
3456    },
3457    Spec {
3458        name: "bf.card",
3459        arity: 2,
3460        flags: BLOOM_READ,
3461        first_key: 1,
3462        last_key: 1,
3463        step: 1,
3464        acl: AC_BLOOM_READ_FAST,
3465        since: "2.4.4",
3466        complexity: "O(1)",
3467        summary: "How many items were added to the filter.",
3468        group: "bloom",
3469    },
3470    Spec {
3471        name: "bf.debug",
3472        arity: 2,
3473        flags: BLOOM_READ,
3474        first_key: 1,
3475        last_key: 1,
3476        step: 1,
3477        acl: AC_BLOOM_READ,
3478        since: "1.0.0",
3479        complexity: "O(1)",
3480        summary: "The chain and a line for each of its links.",
3481        group: "bloom",
3482    },
3483    // -------------------------------------------------------------- cuckoo
3484    Spec {
3485        name: "cf.reserve",
3486        arity: -3,
3487        flags: CUCKOO_WRITE,
3488        first_key: 1,
3489        last_key: 1,
3490        step: 1,
3491        acl: AC_CUCKOO_WRITE_FAST,
3492        since: "1.0.0",
3493        complexity: "O(1)",
3494        summary: "Make an empty filter with a given capacity.",
3495        group: "cuckoo",
3496    },
3497    Spec {
3498        name: "cf.add",
3499        arity: 3,
3500        flags: CUCKOO_WRITE,
3501        first_key: 1,
3502        last_key: 1,
3503        step: 1,
3504        acl: AC_CUCKOO_WRITE,
3505        since: "1.0.0",
3506        complexity: "O(1) amortised, O(N) when the chain has to grow",
3507        summary: "Add an item, making the filter if the key is free.",
3508        group: "cuckoo",
3509    },
3510    Spec {
3511        name: "cf.addnx",
3512        arity: 3,
3513        flags: CUCKOO_WRITE,
3514        first_key: 1,
3515        last_key: 1,
3516        step: 1,
3517        acl: AC_CUCKOO_WRITE,
3518        since: "1.0.0",
3519        complexity: "O(1) amortised, O(N) when the chain has to grow",
3520        summary: "Add an item unless the filter already has it.",
3521        group: "cuckoo",
3522    },
3523    Spec {
3524        name: "cf.insert",
3525        arity: -4,
3526        flags: CUCKOO_WRITE,
3527        first_key: 1,
3528        last_key: 1,
3529        step: 1,
3530        acl: AC_CUCKOO_WRITE,
3531        since: "1.0.0",
3532        complexity: "O(N) with N the number of items",
3533        summary: "Add several items to a filter described in the same command.",
3534        group: "cuckoo",
3535    },
3536    Spec {
3537        name: "cf.insertnx",
3538        arity: -4,
3539        flags: CUCKOO_WRITE,
3540        first_key: 1,
3541        last_key: 1,
3542        step: 1,
3543        acl: AC_CUCKOO_WRITE,
3544        since: "1.0.0",
3545        complexity: "O(N) with N the number of items",
3546        summary: "Add several items the filter does not already have.",
3547        group: "cuckoo",
3548    },
3549    Spec {
3550        name: "cf.exists",
3551        arity: 3,
3552        flags: CUCKOO_READ,
3553        first_key: 1,
3554        last_key: 1,
3555        step: 1,
3556        acl: AC_CUCKOO_READ,
3557        since: "1.0.0",
3558        complexity: "O(1)",
3559        summary: "Whether an item is probably in the filter.",
3560        group: "cuckoo",
3561    },
3562    Spec {
3563        name: "cf.mexists",
3564        arity: -3,
3565        flags: CUCKOO_READ,
3566        first_key: 1,
3567        last_key: 1,
3568        step: 1,
3569        acl: AC_CUCKOO_READ,
3570        since: "1.0.0",
3571        complexity: "O(N) with N the number of items",
3572        summary: "Whether each of several items is probably in the filter.",
3573        group: "cuckoo",
3574    },
3575    Spec {
3576        name: "cf.count",
3577        arity: 3,
3578        flags: CUCKOO_READ,
3579        first_key: 1,
3580        last_key: 1,
3581        step: 1,
3582        acl: AC_CUCKOO_READ,
3583        since: "1.0.0",
3584        complexity: "O(1)",
3585        summary: "How many copies of an item the filter thinks it has.",
3586        group: "cuckoo",
3587    },
3588    Spec {
3589        name: "cf.del",
3590        arity: 3,
3591        flags: CUCKOO_DELETE,
3592        first_key: 1,
3593        last_key: 1,
3594        step: 1,
3595        acl: AC_CUCKOO_WRITE,
3596        since: "1.0.0",
3597        complexity: "O(1)",
3598        summary: "Take one copy of an item out of the filter.",
3599        group: "cuckoo",
3600    },
3601    Spec {
3602        name: "cf.scandump",
3603        arity: 3,
3604        flags: CUCKOO_READ,
3605        first_key: 1,
3606        last_key: 1,
3607        step: 1,
3608        acl: AC_CUCKOO_READ,
3609        since: "1.0.0",
3610        complexity: "O(N) with N the size of the chunk",
3611        summary: "One chunk of the filter, to be replayed into CF.LOADCHUNK.",
3612        group: "cuckoo",
3613    },
3614    Spec {
3615        name: "cf.loadchunk",
3616        arity: 4,
3617        flags: CUCKOO_WRITE,
3618        first_key: 1,
3619        last_key: 1,
3620        step: 1,
3621        acl: AC_CUCKOO_WRITE,
3622        since: "1.0.0",
3623        complexity: "O(N) with N the size of the chunk",
3624        summary: "Put back a chunk that CF.SCANDUMP handed out.",
3625        group: "cuckoo",
3626    },
3627    Spec {
3628        name: "cf.info",
3629        arity: 2,
3630        flags: CUCKOO_READ,
3631        first_key: 1,
3632        last_key: 1,
3633        step: 1,
3634        acl: AC_CUCKOO_READ_FAST,
3635        since: "1.0.0",
3636        complexity: "O(1)",
3637        summary: "The shape of the chain.",
3638        group: "cuckoo",
3639    },
3640    Spec {
3641        name: "cf.debug",
3642        arity: 2,
3643        flags: CUCKOO_READ,
3644        first_key: 1,
3645        last_key: 1,
3646        step: 1,
3647        acl: AC_CUCKOO_READ,
3648        since: "1.0.0",
3649        complexity: "O(1)",
3650        summary: "The chain's geometry on one line.",
3651        group: "cuckoo",
3652    },
3653    Spec {
3654        name: "cf.compact",
3655        arity: -1,
3656        flags: CUCKOO_READ,
3657        first_key: 1,
3658        last_key: 1,
3659        step: 1,
3660        acl: AC_CUCKOO_READ,
3661        since: "1.0.0",
3662        complexity: "O(N) with N the number of items in the newer filters",
3663        summary: "Pull the newer filters down into the older ones.",
3664        group: "cuckoo",
3665    },
3666    // ----------------------------------------------------------------- cms
3667    Spec {
3668        name: "cms.initbydim",
3669        arity: 4,
3670        flags: CMS_WRITE,
3671        first_key: 1,
3672        last_key: 1,
3673        step: 1,
3674        acl: AC_CMS_WRITE_FAST,
3675        since: "2.0.0",
3676        complexity: "O(1)",
3677        summary: "Make an empty sketch of a given width and depth.",
3678        group: "cms",
3679    },
3680    Spec {
3681        name: "cms.initbyprob",
3682        arity: 4,
3683        flags: CMS_WRITE,
3684        first_key: 1,
3685        last_key: 1,
3686        step: 1,
3687        acl: AC_CMS_WRITE_FAST,
3688        since: "2.0.0",
3689        complexity: "O(1)",
3690        summary: "Make an empty sketch wide enough for a stated tolerance.",
3691        group: "cms",
3692    },
3693    Spec {
3694        name: "cms.incrby",
3695        arity: -4,
3696        flags: CMS_WRITE,
3697        first_key: 1,
3698        last_key: 1,
3699        step: 1,
3700        acl: AC_CMS_WRITE,
3701        since: "2.0.0",
3702        complexity: "O(N) with N the number of items",
3703        summary: "Add to the count of one or more items.",
3704        group: "cms",
3705    },
3706    Spec {
3707        name: "cms.query",
3708        arity: -3,
3709        flags: CMS_READ,
3710        first_key: 1,
3711        last_key: 1,
3712        step: 1,
3713        acl: AC_CMS_READ,
3714        since: "2.0.0",
3715        complexity: "O(N) with N the number of items",
3716        summary: "How many times the sketch has seen each item.",
3717        group: "cms",
3718    },
3719    Spec {
3720        name: "cms.merge",
3721        arity: -4,
3722        flags: CMS_WRITE,
3723        first_key: 1,
3724        last_key: 1,
3725        step: 1,
3726        acl: AC_CMS_WRITE,
3727        since: "2.0.0",
3728        complexity: "O(N * M) with N the sources and M the counters in one",
3729        summary: "Replace a sketch with the weighted sum of others.",
3730        group: "cms",
3731    },
3732    Spec {
3733        name: "cms.info",
3734        arity: 2,
3735        flags: CMS_READ,
3736        first_key: 1,
3737        last_key: 1,
3738        step: 1,
3739        acl: AC_CMS_READ_FAST,
3740        since: "2.0.0",
3741        complexity: "O(1)",
3742        summary: "The width, the depth and everything ever added.",
3743        group: "cms",
3744    },
3745    // ---------------------------------------------------------------- topk
3746    Spec {
3747        name: "topk.reserve",
3748        arity: -3,
3749        flags: TOPK_WRITE,
3750        first_key: 1,
3751        last_key: 1,
3752        step: 1,
3753        acl: AC_TOPK_WRITE_FAST,
3754        since: "2.0.0",
3755        complexity: "O(1)",
3756        summary: "Make an empty sketch that keeps the k commonest items.",
3757        group: "topk",
3758    },
3759    Spec {
3760        name: "topk.add",
3761        arity: -3,
3762        flags: TOPK_WRITE,
3763        first_key: 1,
3764        last_key: 1,
3765        step: 1,
3766        acl: AC_TOPK_WRITE,
3767        since: "2.0.0",
3768        complexity: "O(N * K) with N the items and K the depth",
3769        summary: "Count one occurrence of each item.",
3770        group: "topk",
3771    },
3772    Spec {
3773        name: "topk.incrby",
3774        arity: -4,
3775        flags: TOPK_WRITE,
3776        first_key: 1,
3777        last_key: 1,
3778        step: 1,
3779        acl: AC_TOPK_WRITE,
3780        since: "2.0.0",
3781        complexity: "O(N * K) with N the items and K the depth",
3782        summary: "Count a stated number of occurrences of each item.",
3783        group: "topk",
3784    },
3785    Spec {
3786        name: "topk.query",
3787        arity: -3,
3788        flags: TOPK_READ,
3789        first_key: 1,
3790        last_key: 1,
3791        step: 1,
3792        acl: AC_TOPK_READ,
3793        since: "2.0.0",
3794        complexity: "O(N * K) with N the items and K the kept count",
3795        summary: "Whether each item is one of the ones being kept.",
3796        group: "topk",
3797    },
3798    Spec {
3799        name: "topk.count",
3800        arity: -3,
3801        flags: TOPK_READ,
3802        first_key: 1,
3803        last_key: 1,
3804        step: 1,
3805        acl: AC_TOPK_READ,
3806        since: "2.0.0",
3807        complexity: "O(N * K) with N the items and K the depth",
3808        summary: "How many times the sketch thinks it has seen each item.",
3809        group: "topk",
3810    },
3811    Spec {
3812        name: "topk.list",
3813        arity: -2,
3814        flags: TOPK_READ,
3815        first_key: 1,
3816        last_key: 1,
3817        step: 1,
3818        acl: AC_TOPK_READ,
3819        since: "2.0.0",
3820        complexity: "O(K log K) with K the kept count",
3821        summary: "The kept items, heaviest first.",
3822        group: "topk",
3823    },
3824    Spec {
3825        name: "topk.info",
3826        arity: 2,
3827        flags: TOPK_READ,
3828        first_key: 1,
3829        last_key: 1,
3830        step: 1,
3831        acl: AC_TOPK_READ_FAST,
3832        since: "2.0.0",
3833        complexity: "O(1)",
3834        summary: "The four numbers the sketch was made with.",
3835        group: "topk",
3836    },
3837    // ------------------------------------------------------------- tdigest
3838    Spec {
3839        name: "tdigest.create",
3840        arity: -2,
3841        flags: TDIGEST_WRITE,
3842        first_key: 1,
3843        last_key: 1,
3844        step: 1,
3845        acl: AC_TDIGEST_WRITE_FAST,
3846        since: "2.4.0",
3847        complexity: "O(1)",
3848        summary: "Make an empty digest of a stated compression.",
3849        group: "tdigest",
3850    },
3851    Spec {
3852        name: "tdigest.reset",
3853        arity: 2,
3854        flags: TDIGEST_WRITE,
3855        first_key: 1,
3856        last_key: 1,
3857        step: 1,
3858        acl: AC_TDIGEST_WRITE_FAST,
3859        since: "2.4.0",
3860        complexity: "O(1)",
3861        summary: "Throw away every sample and keep the shape.",
3862        group: "tdigest",
3863    },
3864    Spec {
3865        name: "tdigest.add",
3866        arity: -3,
3867        flags: TDIGEST_WRITE,
3868        first_key: 1,
3869        last_key: 1,
3870        step: 1,
3871        acl: AC_TDIGEST_WRITE,
3872        since: "2.4.0",
3873        complexity: "O(N) with N the number of samples",
3874        summary: "Add samples of weight one each.",
3875        group: "tdigest",
3876    },
3877    Spec {
3878        name: "tdigest.merge",
3879        arity: -4,
3880        flags: TDIGEST_MERGE,
3881        first_key: 1,
3882        last_key: 1,
3883        step: 1,
3884        acl: AC_TDIGEST_WRITE,
3885        since: "2.4.0",
3886        complexity: "O(N) with N the number of centroids in the inputs",
3887        summary: "Fold digests together into one.",
3888        group: "tdigest",
3889    },
3890    Spec {
3891        name: "tdigest.min",
3892        arity: 2,
3893        flags: TDIGEST_READ,
3894        first_key: 1,
3895        last_key: 1,
3896        step: 1,
3897        acl: AC_TDIGEST_READ_FAST,
3898        since: "2.4.0",
3899        complexity: "O(1)",
3900        summary: "The smallest sample ever added.",
3901        group: "tdigest",
3902    },
3903    Spec {
3904        name: "tdigest.max",
3905        arity: 2,
3906        flags: TDIGEST_READ,
3907        first_key: 1,
3908        last_key: 1,
3909        step: 1,
3910        acl: AC_TDIGEST_READ_FAST,
3911        since: "2.4.0",
3912        complexity: "O(1)",
3913        summary: "The largest sample ever added.",
3914        group: "tdigest",
3915    },
3916    Spec {
3917        name: "tdigest.quantile",
3918        arity: -3,
3919        flags: TDIGEST_READ,
3920        first_key: 1,
3921        last_key: 1,
3922        step: 1,
3923        acl: AC_TDIGEST_READ_FAST,
3924        since: "2.4.0",
3925        complexity: "O(N) with N the number of centroids",
3926        summary: "The value each fraction of the samples falls under.",
3927        group: "tdigest",
3928    },
3929    Spec {
3930        name: "tdigest.cdf",
3931        arity: -3,
3932        flags: TDIGEST_READ,
3933        first_key: 1,
3934        last_key: 1,
3935        step: 1,
3936        acl: AC_TDIGEST_READ_FAST,
3937        since: "2.4.0",
3938        complexity: "O(N) with N the number of centroids",
3939        summary: "The fraction of the samples at or below each value.",
3940        group: "tdigest",
3941    },
3942    Spec {
3943        name: "tdigest.trimmed_mean",
3944        arity: 4,
3945        flags: TDIGEST_READ,
3946        first_key: 1,
3947        last_key: 1,
3948        step: 1,
3949        acl: AC_TDIGEST_READ,
3950        since: "2.4.0",
3951        complexity: "O(N) with N the number of centroids",
3952        summary: "The mean of what is left once both tails are cut.",
3953        group: "tdigest",
3954    },
3955    Spec {
3956        name: "tdigest.rank",
3957        arity: -3,
3958        flags: TDIGEST_READ,
3959        first_key: 1,
3960        last_key: 1,
3961        step: 1,
3962        acl: AC_TDIGEST_READ_FAST,
3963        since: "2.4.0",
3964        complexity: "O(N) with N the number of centroids",
3965        summary: "How many samples each value is above.",
3966        group: "tdigest",
3967    },
3968    Spec {
3969        name: "tdigest.revrank",
3970        arity: -3,
3971        flags: TDIGEST_READ,
3972        first_key: 1,
3973        last_key: 1,
3974        step: 1,
3975        acl: AC_TDIGEST_READ_FAST,
3976        since: "2.4.0",
3977        complexity: "O(N) with N the number of centroids",
3978        summary: "How many samples each value is below.",
3979        group: "tdigest",
3980    },
3981    Spec {
3982        name: "tdigest.byrank",
3983        arity: -3,
3984        flags: TDIGEST_READ,
3985        first_key: 1,
3986        last_key: 1,
3987        step: 1,
3988        acl: AC_TDIGEST_READ_FAST,
3989        since: "2.4.0",
3990        complexity: "O(N) with N the number of centroids",
3991        summary: "The value at each rank counting up from the smallest.",
3992        group: "tdigest",
3993    },
3994    Spec {
3995        name: "tdigest.byrevrank",
3996        arity: -3,
3997        flags: TDIGEST_READ,
3998        first_key: 1,
3999        last_key: 1,
4000        step: 1,
4001        acl: AC_TDIGEST_READ_FAST,
4002        since: "2.4.0",
4003        complexity: "O(N) with N the number of centroids",
4004        summary: "The value at each rank counting down from the largest.",
4005        group: "tdigest",
4006    },
4007    Spec {
4008        name: "tdigest.info",
4009        arity: 2,
4010        flags: TDIGEST_READ,
4011        first_key: 1,
4012        last_key: 1,
4013        step: 1,
4014        acl: AC_TDIGEST_READ_FAST,
4015        since: "2.4.0",
4016        complexity: "O(1)",
4017        summary: "The nine numbers the digest keeps about itself.",
4018        group: "tdigest",
4019    },
4020    // ------------------------------------------------------------------ ts
4021    Spec {
4022        name: "ts.create",
4023        arity: -2,
4024        flags: TS_WRITE,
4025        first_key: 1,
4026        last_key: 1,
4027        step: 1,
4028        acl: AC_TS_WRITE_FAST,
4029        since: "1.0.0",
4030        complexity: "O(1)",
4031        summary: "Make an empty series and say how it should behave.",
4032        group: "ts",
4033    },
4034    Spec {
4035        name: "ts.alter",
4036        arity: -2,
4037        flags: TS_WRITE,
4038        first_key: 1,
4039        last_key: 1,
4040        step: 1,
4041        acl: AC_TS_WRITE,
4042        since: "1.0.0",
4043        complexity: "O(N) with N the labels being set",
4044        summary: "Change how a series behaves, leaving what was not named alone.",
4045        group: "ts",
4046    },
4047    Spec {
4048        name: "ts.add",
4049        arity: -4,
4050        flags: TS_WRITE,
4051        first_key: 1,
4052        last_key: 1,
4053        step: 1,
4054        acl: AC_TS_WRITE,
4055        since: "1.0.0",
4056        complexity: "O(M) with M the samples in the chunk a backfill lands in",
4057        summary: "Put a sample in, making the series if it is not there.",
4058        group: "ts",
4059    },
4060    Spec {
4061        name: "ts.madd",
4062        arity: -4,
4063        flags: TS_WRITE,
4064        first_key: 1,
4065        last_key: -1,
4066        step: 3,
4067        acl: AC_TS_WRITE,
4068        since: "1.0.0",
4069        complexity: "O(N * M) with N the samples given",
4070        summary: "Put a sample in each of several series.",
4071        group: "ts",
4072    },
4073    Spec {
4074        name: "ts.incrby",
4075        arity: -3,
4076        flags: TS_WRITE,
4077        first_key: 1,
4078        last_key: 1,
4079        step: 1,
4080        acl: AC_TS_WRITE,
4081        since: "1.0.0",
4082        complexity: "O(M) with M the samples in the last chunk",
4083        summary: "Add to the newest value and store the answer.",
4084        group: "ts",
4085    },
4086    Spec {
4087        name: "ts.decrby",
4088        arity: -3,
4089        flags: TS_WRITE,
4090        first_key: 1,
4091        last_key: 1,
4092        step: 1,
4093        acl: AC_TS_WRITE,
4094        since: "1.0.0",
4095        complexity: "O(M) with M the samples in the last chunk",
4096        summary: "Take away from the newest value and store the answer.",
4097        group: "ts",
4098    },
4099    Spec {
4100        name: "ts.del",
4101        arity: 4,
4102        flags: TS_DELETE,
4103        first_key: 1,
4104        last_key: 1,
4105        step: 1,
4106        acl: AC_TS_WRITE,
4107        since: "1.6.0",
4108        complexity: "O(N) with N the samples in the span",
4109        summary: "Take out every sample between two timestamps.",
4110        group: "ts",
4111    },
4112    Spec {
4113        name: "ts.get",
4114        arity: -2,
4115        flags: TS_READ,
4116        first_key: 1,
4117        last_key: 1,
4118        step: 1,
4119        acl: AC_TS_READ_FAST,
4120        since: "1.0.0",
4121        complexity: "O(1)",
4122        summary: "The newest sample in a series.",
4123        group: "ts",
4124    },
4125    Spec {
4126        name: "ts.info",
4127        arity: -2,
4128        flags: TS_READ,
4129        first_key: 1,
4130        last_key: 1,
4131        step: 1,
4132        acl: AC_TS_READ_FAST,
4133        since: "1.0.0",
4134        complexity: "O(1)",
4135        summary: "The fourteen things a series says about itself.",
4136        group: "ts",
4137    },
4138    Spec {
4139        name: "ts.range",
4140        arity: -4,
4141        flags: TS_READ,
4142        first_key: 1,
4143        last_key: 1,
4144        step: 1,
4145        acl: AC_TS_READ,
4146        since: "1.0.0",
4147        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4148        summary: "The samples in a span, oldest first, in buckets if asked for.",
4149        group: "ts",
4150    },
4151    Spec {
4152        name: "ts.revrange",
4153        arity: -4,
4154        flags: TS_READ,
4155        first_key: 1,
4156        last_key: 1,
4157        step: 1,
4158        acl: AC_TS_READ,
4159        since: "1.4.0",
4160        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4161        summary: "The same span, newest first.",
4162        group: "ts",
4163    },
4164    Spec {
4165        name: "ts.nrange",
4166        arity: -5,
4167        flags: TS_READ_MOVABLE,
4168        first_key: 0,
4169        last_key: 0,
4170        step: 0,
4171        acl: AC_TS_READ,
4172        since: "8.10.0",
4173        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4174        summary: "The same span out of several series, lined up on the timestamps.",
4175        group: "ts",
4176    },
4177    Spec {
4178        name: "ts.nrevrange",
4179        arity: -5,
4180        flags: TS_READ_MOVABLE,
4181        first_key: 0,
4182        last_key: 0,
4183        step: 0,
4184        acl: AC_TS_READ,
4185        since: "8.10.0",
4186        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4187        summary: "The same rows, newest first.",
4188        group: "ts",
4189    },
4190    Spec {
4191        name: "ts.read",
4192        arity: -3,
4193        flags: TS_READ,
4194        first_key: 1,
4195        last_key: 1,
4196        step: 1,
4197        acl: AC_TS_READ,
4198        since: "8.10.0",
4199        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples answered",
4200        summary: "Every sample from a timestamp to the end of the series.",
4201        group: "ts",
4202    },
4203    Spec {
4204        name: "ts.queryindex",
4205        arity: -2,
4206        flags: TS_READ,
4207        first_key: 0,
4208        last_key: 0,
4209        step: 0,
4210        acl: AC_TS_READ,
4211        since: "1.0.0",
4212        complexity: "O(n) with n the series in the keyspace",
4213        summary: "The series a filter list takes, by key name.",
4214        group: "ts",
4215    },
4216    Spec {
4217        name: "ts.querylabels",
4218        arity: -2,
4219        flags: TS_READ,
4220        first_key: 0,
4221        last_key: 0,
4222        step: 0,
4223        acl: AC_TS_READ,
4224        since: "8.10.0",
4225        complexity: "O(n) with n the series in the keyspace",
4226        summary: "The label names in use, or the values one of them takes.",
4227        group: "ts",
4228    },
4229    Spec {
4230        name: "ts.mget",
4231        arity: -3,
4232        flags: TS_READ,
4233        first_key: 0,
4234        last_key: 0,
4235        step: 0,
4236        acl: AC_TS_READ,
4237        since: "1.0.0",
4238        complexity: "O(n) with n the series in the keyspace",
4239        summary: "The newest sample of every series a filter list takes.",
4240        group: "ts",
4241    },
4242    Spec {
4243        name: "ts.mrange",
4244        arity: -4,
4245        flags: TS_READ,
4246        first_key: 0,
4247        last_key: 0,
4248        step: 0,
4249        acl: AC_TS_READ,
4250        since: "1.0.0",
4251        complexity: "O(n) with n the series in the keyspace",
4252        summary: "A span out of every series a filter list takes, oldest first.",
4253        group: "ts",
4254    },
4255    Spec {
4256        name: "ts.mrevrange",
4257        arity: -4,
4258        flags: TS_READ,
4259        first_key: 0,
4260        last_key: 0,
4261        step: 0,
4262        acl: AC_TS_READ,
4263        since: "1.4.0",
4264        complexity: "O(n) with n the series in the keyspace",
4265        summary: "The same spans, newest first.",
4266        group: "ts",
4267    },
4268    Spec {
4269        name: "ts.createrule",
4270        arity: -5,
4271        flags: TS_RULE,
4272        first_key: 1,
4273        last_key: 2,
4274        step: 1,
4275        acl: AC_TS_WRITE,
4276        since: "1.0.0",
4277        complexity: "O(1)",
4278        summary: "Fold one series into another as it is written to.",
4279        group: "ts",
4280    },
4281    Spec {
4282        name: "ts.deleterule",
4283        arity: 3,
4284        flags: TS_DELETE,
4285        first_key: 1,
4286        last_key: 2,
4287        step: 1,
4288        acl: AC_TS_WRITE_FAST,
4289        since: "1.0.0",
4290        complexity: "O(1)",
4291        summary: "Stop folding one series into another.",
4292        group: "ts",
4293    },
4294    // --------------------------------------------------------------- array
4295    Spec {
4296        name: "arset",
4297        arity: -4,
4298        flags: WRITE_FAST_OOM,
4299        first_key: 1,
4300        last_key: 1,
4301        step: 1,
4302        acl: AC_ARRAY_WRITE_FAST,
4303        since: "8.8.0",
4304        complexity: "O(N) with N the number of values",
4305        summary: "Write values into consecutive positions from an index.",
4306        group: "array",
4307    },
4308    Spec {
4309        name: "armset",
4310        arity: -4,
4311        flags: WRITE_FAST_OOM,
4312        first_key: 1,
4313        last_key: 1,
4314        step: 1,
4315        acl: AC_ARRAY_WRITE_FAST,
4316        since: "8.8.0",
4317        complexity: "O(N) with N the number of pairs",
4318        summary: "Write index and value pairs, which need not be neighbours.",
4319        group: "array",
4320    },
4321    Spec {
4322        name: "arget",
4323        arity: 3,
4324        flags: READ_FAST,
4325        first_key: 1,
4326        last_key: 1,
4327        step: 1,
4328        acl: AC_ARRAY_READ_FAST,
4329        since: "8.8.0",
4330        complexity: "O(1)",
4331        summary: "The value at one index, or a null if nothing is there.",
4332        group: "array",
4333    },
4334    Spec {
4335        name: "armget",
4336        arity: -3,
4337        flags: READ_FAST,
4338        first_key: 1,
4339        last_key: 1,
4340        step: 1,
4341        acl: AC_ARRAY_READ_FAST,
4342        since: "8.8.0",
4343        complexity: "O(N) with N the number of indices",
4344        summary: "The values at the indices named, in the order named.",
4345        group: "array",
4346    },
4347    Spec {
4348        name: "argetrange",
4349        arity: 4,
4350        flags: READ_SLOW,
4351        first_key: 1,
4352        last_key: 1,
4353        step: 1,
4354        acl: AC_ARRAY_READ_SLOW,
4355        since: "8.8.0",
4356        complexity: "O(N) with N the length of the range",
4357        summary: "One reply per position between two indices, holes included.",
4358        group: "array",
4359    },
4360    Spec {
4361        name: "arlen",
4362        arity: 2,
4363        flags: READ_FAST,
4364        first_key: 1,
4365        last_key: 1,
4366        step: 1,
4367        acl: AC_ARRAY_READ_FAST,
4368        since: "8.8.0",
4369        complexity: "O(1)",
4370        summary: "The highest populated index plus one.",
4371        group: "array",
4372    },
4373    Spec {
4374        name: "arcount",
4375        arity: 2,
4376        flags: READ_FAST,
4377        first_key: 1,
4378        last_key: 1,
4379        step: 1,
4380        acl: AC_ARRAY_READ_FAST,
4381        since: "8.8.0",
4382        complexity: "O(1)",
4383        summary: "How many indices hold something.",
4384        group: "array",
4385    },
4386    Spec {
4387        name: "ardel",
4388        arity: -3,
4389        flags: WRITE_FAST,
4390        first_key: 1,
4391        last_key: 1,
4392        step: 1,
4393        acl: AC_ARRAY_WRITE_FAST,
4394        since: "8.8.0",
4395        complexity: "O(N) with N the number of indices",
4396        summary: "Empty the indices named and say how many held something.",
4397        group: "array",
4398    },
4399    Spec {
4400        name: "ardelrange",
4401        arity: -4,
4402        flags: WRITE_SLOW,
4403        first_key: 1,
4404        last_key: 1,
4405        step: 1,
4406        acl: AC_ARRAY_WRITE_SLOW,
4407        since: "8.8.0",
4408        complexity: "O(N) with N the elements touched, not the span asked for",
4409        summary: "Empty one or more ranges of indices.",
4410        group: "array",
4411    },
4412    Spec {
4413        name: "arinsert",
4414        arity: -3,
4415        flags: WRITE_FAST_OOM,
4416        first_key: 1,
4417        last_key: 1,
4418        step: 1,
4419        acl: AC_ARRAY_WRITE_FAST,
4420        since: "8.8.0",
4421        complexity: "O(N) with N the number of values",
4422        summary: "Append values at the insert cursor.",
4423        group: "array",
4424    },
4425    Spec {
4426        name: "arring",
4427        arity: -4,
4428        flags: WRITE_OOM,
4429        first_key: 1,
4430        last_key: 1,
4431        step: 1,
4432        acl: AC_ARRAY_WRITE_SLOW,
4433        since: "8.8.0",
4434        complexity: "O(N) with N the values, plus the ring size when it changes",
4435        summary: "Append values into a ring of the given size.",
4436        group: "array",
4437    },
4438    Spec {
4439        name: "arnext",
4440        arity: 2,
4441        flags: READ_FAST,
4442        first_key: 1,
4443        last_key: 1,
4444        step: 1,
4445        acl: AC_ARRAY_READ_FAST,
4446        since: "8.8.0",
4447        complexity: "O(1)",
4448        summary: "The index the next append would write to.",
4449        group: "array",
4450    },
4451    Spec {
4452        name: "arseek",
4453        arity: 3,
4454        flags: WRITE_FAST,
4455        first_key: 1,
4456        last_key: 1,
4457        step: 1,
4458        acl: AC_ARRAY_WRITE_FAST,
4459        since: "8.8.0",
4460        complexity: "O(1)",
4461        summary: "Point the insert cursor at an index.",
4462        group: "array",
4463    },
4464    Spec {
4465        name: "arlastitems",
4466        arity: -3,
4467        flags: READ_SLOW,
4468        first_key: 1,
4469        last_key: 1,
4470        step: 1,
4471        acl: AC_ARRAY_READ_SLOW,
4472        since: "8.8.0",
4473        complexity: "O(N) with N the count asked for",
4474        summary: "The newest positions from the insert cursor, holes included.",
4475        group: "array",
4476    },
4477    Spec {
4478        name: "arscan",
4479        arity: -4,
4480        flags: READ_SLOW,
4481        first_key: 1,
4482        last_key: 1,
4483        step: 1,
4484        acl: AC_ARRAY_READ_SLOW,
4485        since: "8.8.0",
4486        complexity: "O(N) with N the elements found, not the span asked for",
4487        summary: "Index and value pairs for what a range holds, skipping holes.",
4488        group: "array",
4489    },
4490    Spec {
4491        name: "argrep",
4492        arity: -6,
4493        flags: READ_SLOW,
4494        first_key: 1,
4495        last_key: 1,
4496        step: 1,
4497        acl: AC_ARRAY_READ_SLOW,
4498        since: "8.8.0",
4499        complexity: "O(P * C) with P the positions visited and C the cost of the predicates on one element",
4500        summary: "The indexes in a range whose elements answer a set of textual predicates.",
4501        group: "array",
4502    },
4503    Spec {
4504        name: "arop",
4505        arity: -5,
4506        flags: READ_SLOW,
4507        first_key: 1,
4508        last_key: 1,
4509        step: 1,
4510        acl: AC_ARRAY_READ_SLOW,
4511        since: "8.8.0",
4512        complexity: "O(N) with N the elements found, not the span asked for",
4513        summary: "One number out of a range, added up or compared or counted.",
4514        group: "array",
4515    },
4516    Spec {
4517        name: "arinfo",
4518        arity: -2,
4519        flags: READ_SLOW,
4520        first_key: 1,
4521        last_key: 1,
4522        step: 1,
4523        acl: AC_ARRAY_READ_SLOW,
4524        since: "8.8.0",
4525        complexity: "O(1), or O(N) with N the slices when FULL is given",
4526        summary: "The shape of the array, and what its slices look like.",
4527        group: "array",
4528    },
4529    // ------------------------------------------------------------- streams
4530    Spec {
4531        name: "xadd",
4532        arity: -5,
4533        flags: WRITE_FAST_OOM,
4534        first_key: 1,
4535        last_key: 1,
4536        step: 1,
4537        acl: AC_STREAM_WRITE_FAST,
4538        since: "5.0.0",
4539        complexity: "O(1) for the append, plus what a trim removes.",
4540        summary: "Append an entry and answer with the ID it got.",
4541        group: "stream",
4542    },
4543    Spec {
4544        name: "xlen",
4545        arity: 2,
4546        flags: READ_FAST,
4547        first_key: 1,
4548        last_key: 1,
4549        step: 1,
4550        acl: AC_STREAM_READ_FAST,
4551        since: "5.0.0",
4552        complexity: "O(1)",
4553        summary: "How many entries the stream holds.",
4554        group: "stream",
4555    },
4556    Spec {
4557        name: "xdel",
4558        arity: -3,
4559        flags: WRITE_FAST,
4560        first_key: 1,
4561        last_key: 1,
4562        step: 1,
4563        acl: AC_STREAM_WRITE_FAST,
4564        since: "5.0.0",
4565        complexity: "O(1) per ID.",
4566        summary: "Remove entries by ID and say how many were there.",
4567        group: "stream",
4568    },
4569    Spec {
4570        name: "xdelex",
4571        arity: -5,
4572        flags: WRITE_FAST,
4573        first_key: 1,
4574        last_key: 1,
4575        step: 1,
4576        acl: AC_STREAM_WRITE_FAST,
4577        since: "8.2.0",
4578        complexity: "O(1) per ID.",
4579        summary: "Remove entries by ID, saying what to do about the groups.",
4580        group: "stream",
4581    },
4582    Spec {
4583        name: "xackdel",
4584        arity: -6,
4585        flags: WRITE_FAST,
4586        first_key: 1,
4587        last_key: 1,
4588        step: 1,
4589        acl: AC_STREAM_WRITE_FAST,
4590        since: "8.2.0",
4591        complexity: "O(1) per ID.",
4592        summary: "Acknowledge entries for a group and remove them.",
4593        group: "stream",
4594    },
4595    Spec {
4596        name: "xnack",
4597        arity: -7,
4598        flags: WRITE_FAST,
4599        first_key: 1,
4600        last_key: 1,
4601        step: 1,
4602        acl: AC_STREAM_WRITE_FAST,
4603        since: "8.8.0",
4604        complexity: "O(1) per ID.",
4605        summary: "Give entries back to the group for somebody else to claim.",
4606        group: "stream",
4607    },
4608    Spec {
4609        name: "xtrim",
4610        arity: -4,
4611        flags: WRITE_SLOW,
4612        first_key: 1,
4613        last_key: 1,
4614        step: 1,
4615        acl: AC_STREAM_WRITE_SLOW,
4616        since: "5.0.0",
4617        complexity: "O(N) in the entries removed.",
4618        summary: "Cut the stream down to a length or a minimum ID.",
4619        group: "stream",
4620    },
4621    Spec {
4622        name: "xrange",
4623        arity: -4,
4624        flags: READ_SLOW,
4625        first_key: 1,
4626        last_key: 1,
4627        step: 1,
4628        acl: AC_STREAM_READ_SLOW,
4629        since: "5.0.0",
4630        complexity: "O(N) in the entries returned.",
4631        summary: "The entries between two IDs, oldest first.",
4632        group: "stream",
4633    },
4634    Spec {
4635        name: "xrevrange",
4636        arity: -4,
4637        flags: READ_SLOW,
4638        first_key: 1,
4639        last_key: 1,
4640        step: 1,
4641        acl: AC_STREAM_READ_SLOW,
4642        since: "5.0.0",
4643        complexity: "O(N) in the entries returned.",
4644        summary: "The entries between two IDs, newest first.",
4645        group: "stream",
4646    },
4647    Spec {
4648        name: "xread",
4649        arity: -4,
4650        flags: READ_BLOCKING_MOVABLE,
4651        first_key: 0,
4652        last_key: 0,
4653        step: 0,
4654        acl: AC_STREAM_BLOCKING_READ,
4655        since: "5.0.0",
4656        complexity: "O(N) in the entries returned.",
4657        summary: "Read from one or more streams, waiting if asked to.",
4658        group: "stream",
4659    },
4660    Spec {
4661        name: "xreadgroup",
4662        arity: -7,
4663        flags: WRITE_BLOCKING_MOVABLE,
4664        first_key: 0,
4665        last_key: 0,
4666        step: 0,
4667        acl: AC_STREAM_BLOCKING_WRITE,
4668        since: "5.0.0",
4669        complexity: "O(N) in the entries returned.",
4670        summary: "Read as part of a consumer group, waiting if asked to.",
4671        group: "stream",
4672    },
4673    Spec {
4674        name: "xack",
4675        arity: -4,
4676        flags: WRITE_FAST,
4677        first_key: 1,
4678        last_key: 1,
4679        step: 1,
4680        acl: AC_STREAM_WRITE_FAST,
4681        since: "5.0.0",
4682        complexity: "O(1) per ID.",
4683        summary: "Drop entries from a group's pending list.",
4684        group: "stream",
4685    },
4686    Spec {
4687        name: "xsetid",
4688        arity: -3,
4689        flags: WRITE_FAST_OOM,
4690        first_key: 1,
4691        last_key: 1,
4692        step: 1,
4693        acl: AC_STREAM_WRITE_FAST,
4694        since: "5.0.0",
4695        complexity: "O(1)",
4696        summary: "Set the last ID, the entries added and the max deleted ID.",
4697        group: "stream",
4698    },
4699    Spec {
4700        name: "xgroup",
4701        arity: -2,
4702        flags: &[],
4703        first_key: 0,
4704        last_key: 0,
4705        step: 0,
4706        acl: AC_STREAM_CONTAINER,
4707        since: "5.0.0",
4708        complexity: "O(1) for all subcommands except DESTROY, which frees the group's pending list.",
4709        summary: "Make, move and unmake consumer groups.",
4710        group: "stream",
4711    },
4712    Spec {
4713        name: "xinfo",
4714        arity: -2,
4715        flags: &[],
4716        first_key: 0,
4717        last_key: 0,
4718        step: 0,
4719        acl: AC_STREAM_CONTAINER,
4720        since: "5.0.0",
4721        complexity: "O(1), or O(N) with N the entries and pending entries shown when FULL is given.",
4722        summary: "What a stream, its groups and its consumers look like.",
4723        group: "stream",
4724    },
4725    Spec {
4726        name: "xpending",
4727        arity: -3,
4728        flags: READ_SLOW,
4729        first_key: 1,
4730        last_key: 1,
4731        step: 1,
4732        acl: AC_STREAM_READ_SLOW,
4733        since: "5.0.0",
4734        complexity: "O(1) for the summary, O(N) in the entries returned for the list.",
4735        summary: "What a group has handed out and not had acknowledged.",
4736        group: "stream",
4737    },
4738    Spec {
4739        name: "xclaim",
4740        arity: -6,
4741        flags: WRITE_FAST,
4742        first_key: 1,
4743        last_key: 1,
4744        step: 1,
4745        acl: AC_STREAM_WRITE_FAST,
4746        since: "5.0.0",
4747        complexity: "O(1) per ID.",
4748        summary: "Move named pending entries to another consumer.",
4749        group: "stream",
4750    },
4751    Spec {
4752        name: "xautoclaim",
4753        arity: -6,
4754        flags: WRITE_FAST,
4755        first_key: 1,
4756        last_key: 1,
4757        step: 1,
4758        acl: AC_STREAM_WRITE_FAST,
4759        since: "6.2.0",
4760        complexity: "O(1) per entry claimed, plus what it skips getting there.",
4761        summary: "Sweep a group's pending list and take what has gone idle.",
4762        group: "stream",
4763    },
4764    // ------------------------------------------------------------ keyspace
4765    Spec {
4766        name: "del",
4767        arity: -2,
4768        flags: &["write"],
4769        first_key: 1,
4770        last_key: -1,
4771        step: 1,
4772        acl: AC_KEY_WRITE_SLOW,
4773        since: "1.0.0",
4774        complexity: "O(N) in the number of keys.",
4775        summary: "Delete keys and say how many were there.",
4776        group: "keyspace",
4777    },
4778    Spec {
4779        name: "unlink",
4780        arity: -2,
4781        flags: &["write", "fast"],
4782        first_key: 1,
4783        last_key: -1,
4784        step: 1,
4785        acl: AC_KEY_WRITE_FAST,
4786        since: "4.0.0",
4787        complexity: "O(1) per key, since the freeing is not on this thread.",
4788        summary: "Delete keys and free them out of the way of the reply.",
4789        group: "keyspace",
4790    },
4791    Spec {
4792        name: "exists",
4793        arity: -2,
4794        flags: READ_FAST,
4795        first_key: 1,
4796        last_key: -1,
4797        step: 1,
4798        acl: AC_KEY_READ,
4799        since: "1.0.0",
4800        complexity: "O(N) in the number of keys.",
4801        summary: "Count how many of these keys are there, naming one twice counting twice.",
4802        group: "keyspace",
4803    },
4804    Spec {
4805        name: "type",
4806        arity: 2,
4807        flags: READ_FAST,
4808        first_key: 1,
4809        last_key: 1,
4810        step: 1,
4811        acl: AC_KEY_READ,
4812        since: "1.0.0",
4813        complexity: "O(1)",
4814        summary: "What kind of value is under a key, or none.",
4815        group: "keyspace",
4816    },
4817    Spec {
4818        name: "touch",
4819        arity: -2,
4820        flags: READ_FAST,
4821        first_key: 1,
4822        last_key: -1,
4823        step: 1,
4824        acl: AC_KEY_READ,
4825        since: "3.2.1",
4826        complexity: "O(N) in the number of keys.",
4827        summary: "Count how many of these keys are there, and move them up the eviction order.",
4828        group: "keyspace",
4829    },
4830    // The three that look at keys nobody named. No key positions on any of
4831    // them, which is what the zeroes say, and it is also why a cluster client
4832    // sends them to a node rather than to a slot.
4833    Spec {
4834        name: "scan",
4835        arity: -2,
4836        flags: &["readonly"],
4837        first_key: 0,
4838        last_key: 0,
4839        step: 0,
4840        acl: AC_KEY_READ_SLOW,
4841        since: "2.8.0",
4842        complexity: "O(1) a call, O(N) for a whole iteration",
4843        summary: "Walk part of the keyspace and say where to carry on from.",
4844        group: "keyspace",
4845    },
4846    Spec {
4847        name: "keys",
4848        arity: 2,
4849        flags: &["readonly"],
4850        first_key: 0,
4851        last_key: 0,
4852        step: 0,
4853        acl: AC_KEY_READ_ALL,
4854        since: "1.0.0",
4855        complexity: "O(N) in the number of keys.",
4856        summary: "Every key matching a pattern, in one reply.",
4857        group: "keyspace",
4858    },
4859    Spec {
4860        name: "randomkey",
4861        arity: 1,
4862        flags: &["readonly"],
4863        first_key: 0,
4864        last_key: 0,
4865        step: 0,
4866        acl: AC_KEY_READ_SLOW,
4867        since: "1.0.0",
4868        complexity: "O(1)",
4869        summary: "One key from the database, chosen at random.",
4870        group: "keyspace",
4871    },
4872    // Two keys and not one, which is the 1 2 1 in the key positions. Every other
4873    // row in this group names a range that runs to the end of the arguments.
4874    Spec {
4875        name: "rename",
4876        arity: 3,
4877        flags: &["write"],
4878        first_key: 1,
4879        last_key: 2,
4880        step: 1,
4881        acl: AC_KEY_WRITE_SLOW,
4882        since: "1.0.0",
4883        complexity: "O(1)",
4884        summary: "Move a key to another name, over whatever was there.",
4885        group: "keyspace",
4886    },
4887    Spec {
4888        name: "renamenx",
4889        arity: 3,
4890        flags: WRITE_FAST,
4891        first_key: 1,
4892        last_key: 2,
4893        step: 1,
4894        acl: AC_KEY_WRITE_FAST,
4895        since: "1.0.0",
4896        complexity: "O(1)",
4897        summary: "Move a key to another name, but only if that name is free.",
4898        group: "keyspace",
4899    },
4900    // `denyoom` and no `fast`, because this is the one command in the group that
4901    // allocates a whole second value.
4902    Spec {
4903        name: "copy",
4904        arity: -3,
4905        flags: &["write", "denyoom"],
4906        first_key: 1,
4907        last_key: 2,
4908        step: 1,
4909        acl: AC_KEY_WRITE_SLOW,
4910        since: "6.2.0",
4911        complexity: "O(N) in the size of the value.",
4912        summary: "Copy a value to another key, in this database or another one.",
4913        group: "keyspace",
4914    },
4915    // `COPY` with the source deleted, and the only command in the group whose
4916    // second argument is a database rather than a key. The key spec is one key
4917    // at argument one and the database index is not a key, which is why this
4918    // does not look like `COPY` above it.
4919    Spec {
4920        name: "move",
4921        arity: 3,
4922        flags: WRITE_FAST,
4923        first_key: 1,
4924        last_key: 1,
4925        step: 1,
4926        acl: AC_KEY_WRITE_FAST,
4927        since: "1.0.0",
4928        complexity: "O(1)",
4929        summary: "Move a key to another database, if it is not already there.",
4930        group: "keyspace",
4931    },
4932    // The two that block on replication rather than on a key, so they name no
4933    // key at all and the three zeroes below are not a placeholder.
4934    Spec {
4935        name: "wait",
4936        arity: 3,
4937        flags: &["blocking"],
4938        first_key: 0,
4939        last_key: 0,
4940        step: 0,
4941        acl: AC_WAIT,
4942        since: "3.0.0",
4943        complexity: "O(1)",
4944        summary: "Wait for this connection's writes to reach a number of replicas.",
4945        group: "keyspace",
4946    },
4947    Spec {
4948        name: "waitaof",
4949        arity: 4,
4950        flags: &["blocking"],
4951        first_key: 0,
4952        last_key: 0,
4953        step: 0,
4954        acl: AC_WAIT,
4955        since: "7.2.0",
4956        complexity: "O(1)",
4957        summary: "Wait for this connection's writes to reach the append only files.",
4958        group: "keyspace",
4959    },
4960    // The two that speak the file format. A payload is a value standing on its
4961    // own outside the process, so these are the only two commands in the group
4962    // that move a value rather than a name.
4963    Spec {
4964        name: "dump",
4965        arity: 2,
4966        flags: READ_SLOW,
4967        first_key: 1,
4968        last_key: 1,
4969        step: 1,
4970        acl: AC_KEY_READ_SLOW,
4971        since: "2.6.0",
4972        complexity: "O(1) to find the key, then O(N) in the size of the value.",
4973        summary: "Serialize a value into a payload another server can load.",
4974        group: "keyspace",
4975    },
4976    Spec {
4977        name: "restore",
4978        arity: -4,
4979        flags: &["write", "denyoom"],
4980        first_key: 1,
4981        last_key: 1,
4982        step: 1,
4983        acl: AC_RESTORE,
4984        since: "2.6.0",
4985        complexity: "O(1) to find the key, then O(N) in the size of the payload.",
4986        summary: "Create a key from a payload produced by DUMP.",
4987        group: "keyspace",
4988    },
4989    // And the third one, which is the other two with a socket in between. Its
4990    // keys are movable for the same reason `SORT`'s are, though for a plainer
4991    // reason: the `KEYS` option moves them from argument three to everything
4992    // after the word, so where they are depends on what was written.
4993    Spec {
4994        name: "migrate",
4995        arity: -6,
4996        flags: MIGRATE_FLAGS,
4997        first_key: 3,
4998        last_key: 3,
4999        step: 1,
5000        acl: AC_RESTORE,
5001        since: "2.6.0",
5002        complexity: "A DUMP and a DEL here, a RESTORE there, and the bytes in between.",
5003        summary: "Move a key to another server.",
5004        group: "keyspace",
5005    },
5006    // The two whose keys cannot be read off the command. `SORT k BY w_* GET d_*`
5007    // touches every key those two patterns name and a client cannot know which
5008    // ones without the data, so both carry `movablekeys` and Redis's own key
5009    // specs give the same answer: the first key, and the STORE destination if
5010    // there is one.
5011    Spec {
5012        name: "sort",
5013        arity: -2,
5014        flags: WRITE_MOVABLE,
5015        first_key: 1,
5016        last_key: 1,
5017        step: 1,
5018        acl: AC_SORT_WRITE,
5019        since: "1.0.0",
5020        complexity: "O(N+M*log(M)) with N elements and M returned.",
5021        summary: "Sort a list, set or sorted set, optionally into another key.",
5022        group: "keyspace",
5023    },
5024    Spec {
5025        name: "sort_ro",
5026        arity: -2,
5027        flags: READ_MOVABLE,
5028        first_key: 1,
5029        last_key: 1,
5030        step: 1,
5031        acl: AC_SORT_READ,
5032        since: "7.0.0",
5033        complexity: "O(N+M*log(M)) with N elements and M returned.",
5034        summary: "Sort a list, set or sorted set, without the STORE option.",
5035        group: "keyspace",
5036    },
5037    // The four writers take an optional NX, XX, GT or LT, which is the -3 in
5038    // the arity, and they take the same one whichever unit they are in.
5039    Spec {
5040        name: "expire",
5041        arity: -3,
5042        flags: WRITE_FAST,
5043        first_key: 1,
5044        last_key: 1,
5045        step: 1,
5046        acl: AC_KEY_WRITE_FAST,
5047        since: "1.0.0",
5048        complexity: "O(1)",
5049        summary: "Put a deadline on a key, counted in seconds from now.",
5050        group: "keyspace",
5051    },
5052    Spec {
5053        name: "pexpire",
5054        arity: -3,
5055        flags: WRITE_FAST,
5056        first_key: 1,
5057        last_key: 1,
5058        step: 1,
5059        acl: AC_KEY_WRITE_FAST,
5060        since: "2.6.0",
5061        complexity: "O(1)",
5062        summary: "Put a deadline on a key, counted in milliseconds from now.",
5063        group: "keyspace",
5064    },
5065    Spec {
5066        name: "expireat",
5067        arity: -3,
5068        flags: WRITE_FAST,
5069        first_key: 1,
5070        last_key: 1,
5071        step: 1,
5072        acl: AC_KEY_WRITE_FAST,
5073        since: "1.2.0",
5074        complexity: "O(1)",
5075        summary: "Put a deadline on a key, as a unix time in seconds.",
5076        group: "keyspace",
5077    },
5078    Spec {
5079        name: "pexpireat",
5080        arity: -3,
5081        flags: WRITE_FAST,
5082        first_key: 1,
5083        last_key: 1,
5084        step: 1,
5085        acl: AC_KEY_WRITE_FAST,
5086        since: "2.6.0",
5087        complexity: "O(1)",
5088        summary: "Put a deadline on a key, as a unix time in milliseconds.",
5089        group: "keyspace",
5090    },
5091    Spec {
5092        name: "persist",
5093        arity: 2,
5094        flags: WRITE_FAST,
5095        first_key: 1,
5096        last_key: 1,
5097        step: 1,
5098        acl: AC_KEY_WRITE_FAST,
5099        since: "2.2.0",
5100        complexity: "O(1)",
5101        summary: "Take a key's deadline off, so it stops being temporary.",
5102        group: "keyspace",
5103    },
5104    Spec {
5105        name: "ttl",
5106        arity: 2,
5107        flags: READ_FAST,
5108        first_key: 1,
5109        last_key: 1,
5110        step: 1,
5111        acl: AC_KEY_READ,
5112        since: "1.0.0",
5113        complexity: "O(1)",
5114        summary: "How many seconds a key has left, -1 with no deadline, -2 if gone.",
5115        group: "keyspace",
5116    },
5117    Spec {
5118        name: "pttl",
5119        arity: 2,
5120        flags: READ_FAST,
5121        first_key: 1,
5122        last_key: 1,
5123        step: 1,
5124        acl: AC_KEY_READ,
5125        since: "2.6.0",
5126        complexity: "O(1)",
5127        summary: "How many milliseconds a key has left, -1 with no deadline, -2 if gone.",
5128        group: "keyspace",
5129    },
5130    Spec {
5131        name: "expiretime",
5132        arity: 2,
5133        flags: READ_FAST,
5134        first_key: 1,
5135        last_key: 1,
5136        step: 1,
5137        acl: AC_KEY_READ,
5138        since: "7.0.0",
5139        complexity: "O(1)",
5140        summary: "When a key falls due, as a unix time in seconds.",
5141        group: "keyspace",
5142    },
5143    Spec {
5144        name: "pexpiretime",
5145        arity: 2,
5146        flags: READ_FAST,
5147        first_key: 1,
5148        last_key: 1,
5149        step: 1,
5150        acl: AC_KEY_READ,
5151        since: "7.0.0",
5152        complexity: "O(1)",
5153        summary: "When a key falls due, as a unix time in milliseconds.",
5154        group: "keyspace",
5155    },
5156    // A container command, so no keys and no flags of its own: the key is the
5157    // subcommand's and a real server reports it on `object|encoding` rather
5158    // than here. `@slow` is the whole ACL, checked against 8.10.1.
5159    Spec {
5160        name: "object",
5161        arity: -2,
5162        flags: &[],
5163        first_key: 0,
5164        last_key: 0,
5165        step: 0,
5166        acl: &["@slow"],
5167        since: "2.2.3",
5168        complexity: "O(1)",
5169        summary: "Look at the machinery under a key rather than at its value.",
5170        group: "keyspace",
5171    },
5172    // ----------------------------------------------------------- scripting
5173    // Both are containers with no flags and no keys of their own, which is what
5174    // a real 8.10.1 reports: the flags live on the subcommands.
5175    Spec {
5176        name: "script",
5177        arity: -2,
5178        flags: &[],
5179        first_key: 0,
5180        last_key: 0,
5181        step: 0,
5182        acl: &["@slow"],
5183        since: "2.6.0",
5184        complexity: "O(1) for the subcommands that are here.",
5185        summary: "The script cache, which is empty and stays empty until M6.",
5186        group: "scripting",
5187    },
5188    Spec {
5189        name: "function",
5190        arity: -2,
5191        flags: &[],
5192        first_key: 0,
5193        last_key: 0,
5194        step: 0,
5195        acl: &["@slow"],
5196        since: "7.0.0",
5197        complexity: "O(1) for the subcommands that are here.",
5198        summary: "The function libraries, of which there are none until M6.",
5199        group: "scripting",
5200    },
5201    // ---------------------------------------------------------- connection
5202    Spec {
5203        name: "ping",
5204        arity: -1,
5205        flags: &["fast"],
5206        first_key: 0,
5207        last_key: 0,
5208        step: 0,
5209        acl: AC_CONN,
5210        since: "1.0.0",
5211        complexity: "O(1)",
5212        summary: "Ask whether the server is answering.",
5213        group: "connection",
5214    },
5215    Spec {
5216        name: "echo",
5217        arity: 2,
5218        flags: &["loading", "stale", "fast"],
5219        first_key: 0,
5220        last_key: 0,
5221        step: 0,
5222        acl: AC_CONN,
5223        since: "1.0.0",
5224        complexity: "O(1)",
5225        summary: "Send a string back unchanged.",
5226        group: "connection",
5227    },
5228    Spec {
5229        name: "hello",
5230        arity: -1,
5231        flags: &[
5232            "noscript",
5233            "loading",
5234            "stale",
5235            "fast",
5236            "no_auth",
5237            "allow_busy",
5238        ],
5239        first_key: 0,
5240        last_key: 0,
5241        step: 0,
5242        acl: AC_CONN,
5243        since: "6.0.0",
5244        complexity: "O(1)",
5245        summary: "Agree on a protocol version and describe the server.",
5246        group: "connection",
5247    },
5248    Spec {
5249        name: "select",
5250        arity: 2,
5251        flags: &["loading", "stale", "fast"],
5252        first_key: 0,
5253        last_key: 0,
5254        step: 0,
5255        acl: AC_CONN,
5256        since: "1.0.0",
5257        complexity: "O(1)",
5258        summary: "Choose which database this connection works in.",
5259        group: "connection",
5260    },
5261    Spec {
5262        name: "reset",
5263        arity: 1,
5264        flags: &[
5265            "noscript",
5266            "loading",
5267            "stale",
5268            "fast",
5269            "no_auth",
5270            "allow_busy",
5271        ],
5272        first_key: 0,
5273        last_key: 0,
5274        step: 0,
5275        acl: AC_CONN,
5276        since: "6.2.0",
5277        complexity: "O(1)",
5278        summary: "Put the connection back the way it was opened.",
5279        group: "connection",
5280    },
5281    Spec {
5282        name: "quit",
5283        arity: -1,
5284        flags: &[
5285            "noscript",
5286            "loading",
5287            "stale",
5288            "fast",
5289            "no_auth",
5290            "allow_busy",
5291        ],
5292        first_key: 0,
5293        last_key: 0,
5294        step: 0,
5295        acl: AC_CONN,
5296        since: "1.0.0",
5297        complexity: "O(1)",
5298        summary: "Close the connection after the replies already queued.",
5299        group: "connection",
5300    },
5301    // -------------------------------------------------------------- server
5302    // COMMAND is in the connection ACL category and in the server group, which
5303    // is not a contradiction: the category is about what a connection is
5304    // allowed to do and the group is about what the command is about. The group
5305    // is the one reported by COMMAND DOCS, so it is the one that has to match.
5306    Spec {
5307        name: "command",
5308        arity: -1,
5309        flags: &["loading", "stale"],
5310        first_key: 0,
5311        last_key: 0,
5312        step: 0,
5313        acl: &["@slow", "@connection"],
5314        since: "2.8.13",
5315        complexity: "O(N) with N the number of commands",
5316        summary: "What this server can do, in the shape client libraries read.",
5317        group: "server",
5318    },
5319    Spec {
5320        name: "config",
5321        arity: -2,
5322        flags: &[],
5323        first_key: 0,
5324        last_key: 0,
5325        step: 0,
5326        acl: &["@slow"],
5327        since: "2.0.0",
5328        complexity: "Depends on the subcommand.",
5329        summary: "Read and change the settings a running server exposes.",
5330        group: "server",
5331    },
5332    // Exactly two, which is what a real 8.10.1 reports for the container even
5333    // though every one of its subcommands carries its own arity underneath. All
5334    // seven of them take two words, so nothing legal is refused by it, and the
5335    // one thing that reads differently is the name inside the arity error for a
5336    // subcommand with an argument after it. That is D-46.
5337    Spec {
5338        name: "backup",
5339        arity: 2,
5340        flags: &[],
5341        first_key: 0,
5342        last_key: 0,
5343        step: 0,
5344        acl: &["@slow"],
5345        since: "8.10.0",
5346        complexity: "Depends on subcommand.",
5347        summary: "A container for backup management commands.",
5348        group: "server",
5349    },
5350    Spec {
5351        name: "info",
5352        arity: -1,
5353        flags: &["loading", "stale"],
5354        first_key: 0,
5355        last_key: 0,
5356        step: 0,
5357        acl: &["@slow", "@dangerous"],
5358        since: "1.0.0",
5359        complexity: "O(1)",
5360        summary: "The server's own numbers, in sections.",
5361        group: "server",
5362    },
5363    Spec {
5364        name: "dbsize",
5365        arity: 1,
5366        flags: READ_FAST,
5367        first_key: 0,
5368        last_key: 0,
5369        step: 0,
5370        acl: AC_KEY_READ,
5371        since: "1.0.0",
5372        complexity: "O(1)",
5373        summary: "How many keys are in the database this connection is on.",
5374        group: "server",
5375    },
5376    Spec {
5377        name: "flushall",
5378        arity: -1,
5379        flags: &["write"],
5380        first_key: 0,
5381        last_key: 0,
5382        step: 0,
5383        acl: AC_KEY_FLUSH,
5384        since: "1.0.0",
5385        complexity: "O(N) in the number of keys in every database.",
5386        summary: "Empty every database.",
5387        group: "server",
5388    },
5389    Spec {
5390        name: "flushdb",
5391        arity: -1,
5392        flags: &["write"],
5393        first_key: 0,
5394        last_key: 0,
5395        step: 0,
5396        acl: AC_KEY_FLUSH,
5397        since: "1.0.0",
5398        complexity: "O(N) in the number of keys in this database.",
5399        summary: "Empty the database this connection is on.",
5400        group: "server",
5401    },
5402    // In the server group and not the keyspace one, which is Redis's answer and
5403    // is the right one: it names no key, it takes two database indexes, and what
5404    // it changes is what every connected client is looking at.
5405    Spec {
5406        name: "swapdb",
5407        arity: 3,
5408        flags: WRITE_FAST,
5409        first_key: 0,
5410        last_key: 0,
5411        step: 0,
5412        acl: AC_SWAPDB,
5413        since: "4.0.0",
5414        complexity: "O(N) in the number of clients watching or blocked on either.",
5415        summary: "Swap two databases, so every client on one sees the other.",
5416        group: "server",
5417    },
5418    // No ACL category but `@fast`, which is Redis's answer and reads like an
5419    // omission. It is not: the categories are about what a command can reach and
5420    // this one reaches nothing.
5421    Spec {
5422        name: "time",
5423        arity: 1,
5424        flags: &["loading", "stale", "fast"],
5425        first_key: 0,
5426        last_key: 0,
5427        step: 0,
5428        acl: &["@fast"],
5429        since: "2.6.0",
5430        complexity: "O(1)",
5431        summary: "The server's clock, as seconds and microseconds.",
5432        group: "server",
5433    },
5434    Spec {
5435        name: "shutdown",
5436        arity: -1,
5437        flags: &[
5438            "admin",
5439            "noscript",
5440            "loading",
5441            "stale",
5442            "no_multi",
5443            "allow_busy",
5444        ],
5445        first_key: 0,
5446        last_key: 0,
5447        step: 0,
5448        acl: &["@admin", "@slow", "@dangerous"],
5449        since: "1.0.0",
5450        complexity: "O(1)",
5451        summary: "Stop the server, without answering.",
5452        group: "server",
5453    },
5454];
5455
5456/// The shortest and the longest command name.
5457///
5458/// Both are facts about [`COMMANDS`], pinned by a test, and both are checked
5459/// before anything is read, so a name that could not be a command is rejected on
5460/// its length alone.
5461const MIN_LEN: usize = 3;
5462const MAX_LEN: usize = 20;
5463
5464/// How many slots the index has, which is a power of two and a bit over three
5465/// times the number of commands.
5466///
5467/// Four kibibytes of `u16`, sixty four cache lines, and loose enough that a probe
5468/// for a name that is not a command stops at an empty slot almost immediately.
5469/// Tight enough that the whole thing stays resident next to the table it
5470/// indexes.
5471///
5472/// This was 512 for a long time, which was a bit over twice the number of
5473/// commands, and it stopped being enough at 282 of them. Then it was 1024, and
5474/// that stopped being enough at 337. The note on [`MIX`] has the whole story
5475/// both times, and the short version is the same one twice: at about half full
5476/// there is no multiplier left that keeps every command within two slots of
5477/// home, and at about a sixth full the multiplier that is already there keeps
5478/// every one of them within a single slot without being touched. Two kibibytes
5479/// is what it cost this time.
5480const SLOTS: usize = 2048;
5481
5482/// A slot nothing was put in.
5483///
5484/// `u16::MAX` and not zero, because zero is `set` and `set` is the command this
5485/// most wants to be able to find.
5486const FREE: u16 = u16::MAX;
5487
5488/// The multiplier, found by searching for one that spreads these 358 names well.
5489///
5490/// Not a magic constant in the bad sense: it is checked. Every command is looked
5491/// up by its own name in a test, and another test holds the worst probe length
5492/// at what it is now, so a command added later that made this multiplier bad
5493/// would fail rather than quietly cost every lookup an extra slot.
5494///
5495/// It has been searched for fifteen times, and each time because the test went red
5496/// rather than because somebody went looking. The first was against the 191 names
5497/// in the table then, the ten graph commands pushed its worst probe to three
5498/// slots, and the second search was run over all 201. The fifteen stream commands
5499/// pushed that one to four slots and fifty one extra probes, so the third was run
5500/// over all 216, and the three 8.x pending list commands cost that one two more
5501/// probes than the test allows. The fourth was over 219 and the seven bitmap
5502/// commands took it to three slots, and the fifth was over all 226. The five
5503/// HyperLogLog commands kept its worst probe at two and took it from forty nine
5504/// extra slots to fifty five, and the search over the 231 names found nothing
5505/// better, so that one stood. The ten geo commands took it to sixty, and the
5506/// sixth search, over eight million multipliers and all 241 names, found one at
5507/// fifty six. The twelve vector set commands took that one to four slots and
5508/// seventy extra probes, so the seventh search was run over all 254 names and
5509/// found one at two slots and seventy seven.
5510///
5511/// The eight JSON commands took that one to five slots, which is the worst any
5512/// of them has been, and the eighth search was run over four hundred million
5513/// multipliers and all 262 names. It found this one at two slots and fifty four,
5514/// which is the best the table has ever been and a third fewer extra probes than
5515/// the multiplier it replaced managed with eight fewer commands. Thirty one of
5516/// the names collide on the key itself and no multiplier can separate them, so
5517/// seventeen extra probes is the floor everything here is measured against.
5518/// `json.set` and `json.get` are one of those pairs, since every name in the
5519/// group starts `js` and the only thing left to tell them apart is the length
5520/// and the last byte.
5521///
5522/// The nine JSON array commands took that one to four slots, and this time the
5523/// search over the 271 names found nothing at two whatever it was given. That
5524/// was not the multiplier's fault. `json.arrlen`, `json.objlen` and
5525/// `json.strlen` all key to the same four bytes, and three names in one slot run
5526/// costs the third of them two probes before any other name has moved, so two
5527/// slots was the whole budget spent in one place. The fix was the key rather
5528/// than the multiplier, which is what [`key_of`] now folds the middle byte in
5529/// for, and the ninth search was run over the 271 names with the new key across
5530/// six shards. It found one at two slots and fifty seven, which is a shade over
5531/// a fifth of a probe a command, the same as the multiplier it replaced managed
5532/// over nine fewer names.
5533///
5534/// The number family and `json.strappend` took that one to three slots, and the
5535/// tenth search over the 275 names found one at two slots and sixty seven.
5536/// Three of the six shards converged on sixty seven from different seeds without
5537/// any of them bettering it, which is the sign that the key rather than the
5538/// multiplier is what is left: fourteen of the names collide on the key itself
5539/// and no multiplier can separate them, so fourteen extra probes is the floor
5540/// and that was within a quarter of it per name. The two new pairs were
5541/// `json.arrappend` with `json.strappend` and `json.numincrby` with
5542/// `json.nummultby`, and both are the same shape as the pairs already there,
5543/// which is a group whose names agree everywhere the key looks.
5544///
5545/// The last four JSON commands took it to three slots again, and the eleventh
5546/// search over the 279 names found this one at two slots and sixty two, which is
5547/// better than the table has ever been while carrying four more names. Only one
5548/// of the four collides on the key, `json.mset` with `json.mget`, so the floor
5549/// moved by one and the multiplier found five more probes than the floor moved.
5550/// Nine shards were run from different seeds and the spread was sixty two to a
5551/// hundred and three, which is worth knowing: one shard is not a search.
5552///
5553/// `SUNIONCARD` and `SDIFFCARD` took it to 281 names and sixty three probes, one
5554/// more than before, and the twelfth search is the first one that did not
5555/// replace it. Eight shards over 960 million multipliers did not find a single
5556/// one that kept the worst probe at two slots at all, let alone at two slots and
5557/// sixty two, and the best of them was three slots and eighty one. So that one
5558/// stayed and the bound went up by one, which is the opposite of what the first
5559/// eleven searches concluded and was the honest reading of the same procedure.
5560///
5561/// `LMOVEM` took it to 282 names and three slots, and that is where the search
5562/// stopped being the answer. Twelve searches had found a better multiplier
5563/// eleven times and the twelfth had found that there was none, which is not a
5564/// result about `LMOVEM`, it is a result about a 512 slot table holding 282
5565/// names. Fifty five percent full is where linear probing starts to cost real
5566/// runs, and no multiplier gets around that because the runs are the load
5567/// factor and not the hash.
5568///
5569/// So the other half of the remedy this note has always named was taken and the
5570/// table doubled. At 1024 slots the multiplier that was already here goes to two
5571/// slots and forty two extra probes without being touched, which on its own
5572/// would have been enough. A search over the doubled table across four shards
5573/// and eighty million multipliers then found this one at **one** slot and twenty
5574/// eight, so no command is more than a single slot from where it wants to be,
5575/// which the table has never managed at any size. Fourteen names collide on the
5576/// key itself and no multiplier can separate them, so fourteen is the floor and
5577/// this is twice it, against a floor the 512 slot table never came within four
5578/// times of.
5579///
5580/// The cost is a kibibyte, and the thing it buys beyond today is room. The
5581/// `FT.*` and `TS.*` families are still to be written and both are large, and at
5582/// 27 percent full there is somewhere for them to go.
5583///
5584/// The `BF.*` family is the first of those to arrive and it took the table to
5585/// 296 names, where the doubled table's multiplier went to two slots and thirty
5586/// six extra probes. That is well inside what a lookup is allowed to cost, so
5587/// the search was run to see whether the single slot result had been luck at 285
5588/// names or was a property of the table at this load, and eight shards over a
5589/// hundred and sixty million multipliers found this one at one slot and thirty
5590/// three. Eleven more names, five more probes, and the worst is still a single
5591/// slot. None of the eleven collides on the key, so the floor moved by one for
5592/// an unrelated reason and stands at fifteen, which this is a shade over twice.
5593///
5594/// The `CF.*` family took the table to 310 names and thirty five extra probes,
5595/// two more than the bound allowed, with the worst still a single slot. The
5596/// thirteenth search was run over that and it is the second one that did not
5597/// replace the multiplier. Ten shards over one and a half billion multipliers
5598/// found nothing better than thirty six at one slot, which is worse than the one
5599/// already here, and another two billion with the single slot rule relaxed found
5600/// one at two slots and thirty one. Four fewer probes spread over three hundred
5601/// and ten lookups is not worth giving up the property that no command is ever
5602/// more than one slot from home, so this one stayed and the bound went up by two.
5603/// None of the fourteen new names collides on the key, so the floor is still
5604/// fifteen and the table is at a shade over twice it while carrying fourteen more
5605/// commands than when that was first true.
5606///
5607/// The `CMS.*` family took it to 316 names and thirty seven extra probes, with
5608/// the worst still one slot. No search was run this time. The one before it
5609/// covered three and a half billion multipliers against a table only six names
5610/// smaller and found nothing better that keeps every command within a slot, and
5611/// six names is not enough of a change to expect a different answer, so the
5612/// bound went up by two again. Only one of the six new names collides on the
5613/// key, which is `CMS.QUERY` against `CMS.MERGE`, so the floor is sixteen and
5614/// the table is still a shade over twice it.
5615///
5616/// The `TOPK.*` family took it to 323 names and forty two extra probes, with the
5617/// worst still one slot. A short search of four hundred thousand multipliers ran
5618/// against the new table and the best it turned up was two slots and forty eight,
5619/// worse on both counts, which is what the two big searches before it already
5620/// said, so this multiplier stayed and the bound went up by five. None of the
5621/// seven new names collides on the key, so the floor is still sixteen.
5622///
5623/// The `TDIGEST.*` family took it to 337 names and broke the bound properly: the
5624/// worst probe went to three slots, which is the first time since the table was
5625/// doubled that a command was further from home than a lookup is allowed to be.
5626/// Fourteen names is a lot to add to a family of sketch commands that all start
5627/// with the same two bytes, and the key is built out of the first two bytes, so
5628/// the whole family lands in a handful of key values before the multiply ever
5629/// sees them.
5630///
5631/// So the fifteenth search ran, and it said the same thing the tenth one did at
5632/// 282 names. Three and a half million multipliers against the 1024 slot table
5633/// found nothing better than two slots and fifty two extra probes, against the
5634/// fifty two this one already spends at three slots. That is the shape of a
5635/// table that is too full rather than a multiplier that is bad, and at 337
5636/// names in 1024 slots it is a third full, which is where the 512 slot table
5637/// was when it ran out as well. Doubling the table to 2048 and touching nothing
5638/// else takes this same multiplier to **one** slot and thirty four, so the
5639/// answer was a bigger table again and not a new constant.
5640///
5641/// The search then ran over the doubled table anyway, because that is what
5642/// happened last time and it found something worth having. Four and a half
5643/// million multipliers turned up this one at one slot and twenty two, twelve
5644/// fewer probes than the old multiplier spends in the same table, against a
5645/// floor of sixteen from the names that collide on the key itself. Twelve
5646/// probes over three hundred and thirty seven lookups is not much, but it is
5647/// free, it moves both numbers the right way, and it is exactly the trade the
5648/// doubling from 512 made, so it was taken. The old multiplier was
5649/// `0x3e8668c9760e09c9` and it served for thirteen searches.
5650///
5651/// The room this buys is the same room as last time and it is worth writing down
5652/// again: `FT.*` and `TS.*` are still to come and both are large, and at a sixth
5653/// full there is somewhere for them to go.
5654///
5655/// `TS.*` then arrived and the last three of it, the two joined reads and
5656/// `TS.READ`, broke the bound in a way no multiplier could fix. `TS.NRANGE` made
5657/// `ts.create`, `ts.incrby`, `ts.mrange` and `ts.nrange` four names sharing one
5658/// key: all nine bytes long, all starting `ts`, and all with the same fold of
5659/// the last byte against the middle one. Four names in a slot run costs the
5660/// fourth of them three probes wherever the run starts, so the worst probe went
5661/// to three and doubling the table again would not have moved it, because the
5662/// cost is in the key and not in how much room the key has to land in.
5663///
5664/// So the sixteenth search was a search for a key rather than for a multiplier.
5665/// Folding the second to last byte in as well separates all four of them, and it
5666/// separates enough else besides to take the floor from twenty colliding names
5667/// down to twelve, which is the fewest of the handful of folds tried. It is the
5668/// cheapest byte to add, too, because it sits next to the last byte that is
5669/// already being read. Then the multiplier search ran over the new key, three
5670/// hundred and twenty million of them across eight shards, and the best keeps
5671/// every command within one slot at eighteen extra probes over the whole table,
5672/// against a floor of twelve. That is the best either number has ever been here
5673/// while carrying the most names it has ever carried. The old multiplier was
5674/// `0x2f0cc21a638ae49d` and it served for one search.
5675const MIX: u64 = 0x5525_1c10_f29d_4c29;
5676
5677/// The four bytes the index is computed from: the length, the first two bytes,
5678/// and the last byte with the second to last and the middle folded into it, all
5679/// lower cased.
5680///
5681/// `None` for a name no command could be spelled as, which is decided on the
5682/// length before a byte is read.
5683///
5684/// Four bytes and not the whole name because the whole name has to be compared
5685/// at the end anyway, so the hash only has to be good enough to get to the right
5686/// slot, and reading less of the name is a shorter dependency chain in front of
5687/// the multiply. Names that agree on all four collide whatever the multiplier is
5688/// and probe once more, and the probe is the same compare the lookup was always
5689/// going to do. Over the 358 commands there are twelve such pairs and no group
5690/// larger than a pair, so twelve extra probes is the floor.
5691///
5692/// The middle byte is the part that was added last and it is worth saying why,
5693/// because for a long time the key was the length and the first two bytes and
5694/// the last and nothing else. That was fine while the groups that agreed on a
5695/// prefix were small: `setnx` with `setex`, `g.nadd` with `g.eadd`, `getset`
5696/// with `getbit`, `setbit` with `select`. The JSON group broke it, because every
5697/// name in it starts `js` and so every name in it was keyed on nothing but its
5698/// length and its last byte, and `json.arrlen`, `json.objlen` and `json.strlen`
5699/// agree on both. Three names in one slot run costs the third of them two probes
5700/// on its own, which leaves a multiplier no room anywhere else, and the number
5701/// families still to come are the same shape again. Folding in the middle byte
5702/// separates all three, and it separates `json.set` from `json.get` as well.
5703/// It costs one more load off a cache line the first two bytes already pulled
5704/// in, and the xor is on the same dependency chain as the shifts rather than in
5705/// front of them.
5706///
5707/// The second to last byte went in for the same reason a family later. `TS.*`
5708/// is the JSON shape again and worse: every name starts `ts`, so a nine byte
5709/// name is keyed on nothing but its length and the fold of its last byte against
5710/// its middle one, and `ts.create`, `ts.incrby`, `ts.mrange` and `ts.nrange` all
5711/// land on the same fold. Four in a run is three probes for the last of them
5712/// whatever the multiplier does, so the key had to carry more. The byte before
5713/// the last one is the cheapest one left, being on the cache line the last byte
5714/// already pulled in, and it separates all four of those and takes the floor
5715/// from twenty down to twelve besides.
5716///
5717/// `| 0x20` lower cases a letter and does not have to be told which bytes are
5718/// letters. It maps the two cases of a name to the same number, which is all
5719/// this needs, and every command name is letters. It has to be applied to each
5720/// of the three folded bytes separately, before the xor rather than after,
5721/// because `.` and `n` differ in the bit `| 0x20` sets and an xor of the raw
5722/// bytes would keep that difference alive.
5723///
5724/// On a three or four byte name the middle byte and the second to last byte are
5725/// the same byte and cancel each other out, which leaves the fold as the last
5726/// byte alone. That is not a loss, because on a name that short every byte the
5727/// fold could carry is already in the key somewhere else.
5728const fn key_of(name: &[u8]) -> Option<u32> {
5729    if name.len() < MIN_LEN || name.len() > MAX_LEN {
5730        return None;
5731    }
5732    let last = name.len() - 1;
5733    let mid = name.len() / 2;
5734    Some(
5735        name.len() as u32
5736            | ((name[0] | 0x20) as u32) << 8
5737            | ((name[1] | 0x20) as u32) << 16
5738            | (((name[last] | 0x20) ^ (name[last - 1] | 0x20) ^ (name[mid] | 0x20)) as u32) << 24,
5739    )
5740}
5741
5742/// Where a key wants to sit.
5743///
5744/// The shift leaves the top eleven bits of the product, which are the ones the
5745/// multiply mixed the most, and the mask is what makes that a slot number. Eleven
5746/// because the table has 2048 slots, so both numbers have to move together if
5747/// [`SLOTS`] ever does. It was ten while the table was half this size.
5748const fn slot_of(key: u32) -> usize {
5749    ((key as u64).wrapping_mul(MIX) >> 53) as usize & (SLOTS - 1)
5750}
5751
5752/// The index, built at compile time by inserting every command in table order.
5753///
5754/// Table order is rough order of how often a command is sent, and inserting in
5755/// that order means the hotter of two commands that want the same slot gets it
5756/// and the colder one probes, which is the right way round.
5757const INDEX: [u16; SLOTS] = index();
5758
5759const fn index() -> [u16; SLOTS] {
5760    let mut out = [FREE; SLOTS];
5761    let mut i = 0;
5762    while i < COMMANDS.len() {
5763        let key = match key_of(COMMANDS[i].name.as_bytes()) {
5764            Some(key) => key,
5765            None => panic!("a command name is outside MIN_LEN..=MAX_LEN"),
5766        };
5767        let mut at = slot_of(key);
5768        while out[at] != FREE {
5769            at = (at + 1) & (SLOTS - 1);
5770        }
5771        out[at] = i as u16;
5772        i += 1;
5773    }
5774    out
5775}
5776
5777/// The command called `name`, whatever case the client spelled it in.
5778///
5779/// This used to walk the whole table comparing lengths, and the cost of that was
5780/// not what it looked like. The table is written in rough order of how often a
5781/// command is sent, so `set` and `get` were the first two entries and cost one
5782/// compare, but `exists` is the hundred and forty ninth and `del` the hundred and
5783/// forty seventh, and every one of those compares was paid twice per command,
5784/// once to work out the key hash and once to dispatch.
5785///
5786/// Measured, that walk was 104 nanoseconds a command, which is more than a whole
5787/// `GET` costs end to end. `EXISTS` on a missing key ran at three and a half
5788/// times `GET` and almost none of the difference was the command: short
5789/// circuiting the lookup alone took it from 8.7 microseconds a batch of sixty
5790/// four to 2.0, and left it faster than `GET`, which it should be, because it
5791/// does less.
5792///
5793/// So this is one multiply and one load into two kibibytes, and then the same name
5794/// compare it always ended with. What it costs the hot commands is a multiply
5795/// they did not use to pay and a load that hits, and what it saves the rest is
5796/// the whole walk.
5797#[must_use]
5798pub fn lookup(name: &[u8]) -> Option<&'static Spec> {
5799    at(lookup_index(name))
5800}
5801
5802/// The same, answering with a position in the table rather than a reference.
5803///
5804/// This is where the lookup actually ends, because the index is what the slots
5805/// hold. It is here as its own function because a position fits in a `u16` and a
5806/// reference does not fit anywhere a framed command can carry it cheaply, so the
5807/// engine resolves a command's name once when it frames it and hands the number
5808/// on to both the key hash and the dispatcher.
5809///
5810/// `u16::MAX` is the answer for a name that is not a command, which is not a
5811/// special case anybody has to write down: the table is 254 entries, so [`at`]
5812/// hands back `None` for it the same way it would for any other number past the
5813/// end.
5814#[must_use]
5815pub fn lookup_index(name: &[u8]) -> u16 {
5816    let Some(key) = key_of(name) else {
5817        return FREE;
5818    };
5819    let mut at = slot_of(key);
5820    loop {
5821        let i = INDEX[at];
5822        if i == FREE {
5823            return FREE;
5824        }
5825        if COMMANDS[i as usize]
5826            .name
5827            .as_bytes()
5828            .eq_ignore_ascii_case(name)
5829        {
5830            return i;
5831        }
5832        at = (at + 1) & (SLOTS - 1);
5833    }
5834}
5835
5836/// The command at `i`, or `None` if there is none there.
5837///
5838/// The other half of [`lookup_index`], and the only thing that should ever be
5839/// handed one of its answers.
5840#[must_use]
5841pub fn at(i: u16) -> Option<&'static Spec> {
5842    COMMANDS.get(i as usize)
5843}
5844
5845/// How many commands there are.
5846///
5847/// The length of a counter array that has a row per command, which is the only
5848/// thing that wants this number.
5849#[must_use]
5850pub const fn count() -> usize {
5851    COMMANDS.len()
5852}
5853
5854/// Where in [`COMMANDS`] this spec is.
5855///
5856/// Every `&'static Spec` a caller can hold came out of [`lookup`] and therefore
5857/// points into that array, so its position is the distance from the front
5858/// measured in whole `Spec`s. That is arithmetic on two addresses and not a
5859/// search, which is the point: a per command counter has to be reachable from
5860/// the spec the dispatcher is already holding without walking the table a second
5861/// time.
5862///
5863/// A spec from somewhere else would answer nonsense, which is why this takes a
5864/// `&'static Spec` rather than a `&Spec`: the only `'static` ones are in the
5865/// table.
5866#[must_use]
5867pub fn index_of(spec: &'static Spec) -> usize {
5868    let front = COMMANDS.as_ptr().addr();
5869    let here = std::ptr::from_ref(spec).addr();
5870    (here - front) / size_of::<Spec>()
5871}
5872
5873/// The name of the command at `at`, which is [`index_of`] the other way round.
5874///
5875/// # Panics
5876///
5877/// If `at` is past the end of the table, which only a caller that made the index
5878/// up rather than getting it from [`index_of`] can manage.
5879#[must_use]
5880pub fn name_at(at: usize) -> &'static str {
5881    COMMANDS[at].name
5882}
5883
5884/// Whether `n` arguments, counting the name, satisfy this command's arity.
5885#[must_use]
5886pub fn arity_ok(spec: &Spec, n: usize) -> bool {
5887    let n = n as i32;
5888    if spec.arity >= 0 {
5889        n == spec.arity
5890    } else {
5891        n >= -spec.arity
5892    }
5893}
5894
5895#[cfg(test)]
5896mod tests {
5897    use super::*;
5898
5899    /// The name here is the name a client reads back, so it is spelled the way
5900    /// the server that registered it spelled it.
5901    ///
5902    /// That is lower case for everything the server itself registers and upper
5903    /// case for the two groups that come out of a module, so `COMMAND INFO vadd`
5904    /// answers `VADD` and `COMMAND INFO ft.create` answers `FT.CREATE`, and the
5905    /// arity errors quote them the same way. Nothing else in the table cares,
5906    /// because a lookup compares without regard to case and the index key folds
5907    /// the case out before it hashes.
5908    #[test]
5909    fn every_name_is_spelled_the_way_it_was_registered_and_appears_once() {
5910        let mut seen = std::collections::BTreeSet::new();
5911        for c in COMMANDS {
5912            let want = if c.group == "vector" || c.group == "search" {
5913                c.name.to_uppercase()
5914            } else {
5915                c.name.to_lowercase()
5916            };
5917            assert_eq!(c.name, want, "{} is spelled wrong for its group", c.name);
5918            assert!(seen.insert(c.name), "{} is in the table twice", c.name);
5919        }
5920    }
5921
5922    /// Every command's index is where the table actually holds it.
5923    ///
5924    /// Checked against the position a search finds, over the whole table rather
5925    /// than a sample, because the arithmetic is the thing being tested and an
5926    /// off by one in it would put every counter on the wrong command.
5927    #[test]
5928    fn a_spec_knows_where_it_is_in_the_table() {
5929        assert_eq!(count(), COMMANDS.len());
5930        for (want, spec) in COMMANDS.iter().enumerate() {
5931            assert_eq!(index_of(spec), want, "{} is at the wrong index", spec.name);
5932        }
5933        assert_eq!(
5934            index_of(lookup(b"get").unwrap()),
5935            index_of(lookup(b"GET").unwrap())
5936        );
5937    }
5938
5939    #[test]
5940    fn lookup_ignores_case_and_does_not_match_a_prefix() {
5941        assert_eq!(lookup(b"GET").unwrap().name, "get");
5942        assert_eq!(lookup(b"gEt").unwrap().name, "get");
5943        assert!(lookup(b"ge").is_none());
5944        assert!(lookup(b"gets").is_none());
5945    }
5946
5947    /// Every command is findable under its own name, in either case.
5948    ///
5949    /// The index is built at compile time from the table it sits beside, so what
5950    /// a test can still catch is a command that the build put somewhere the
5951    /// lookup does not walk past, which is what a probe that stopped early would
5952    /// look like.
5953    #[test]
5954    fn every_command_is_findable_by_its_own_name() {
5955        for spec in COMMANDS {
5956            let found = lookup(spec.name.as_bytes()).expect(spec.name);
5957            assert_eq!(
5958                index_of(found),
5959                index_of(spec),
5960                "{} found the wrong spec",
5961                spec.name
5962            );
5963            assert_eq!(
5964                lookup(spec.name.to_ascii_uppercase().as_bytes()).map(index_of),
5965                Some(index_of(spec)),
5966                "{} is not found in upper case",
5967                spec.name,
5968            );
5969        }
5970    }
5971
5972    /// A name that cannot be a command is answered before anything is compared.
5973    #[test]
5974    fn a_name_that_cannot_be_a_command_is_rejected_on_its_shape() {
5975        assert!(lookup(b"").is_none());
5976        assert!(key_of(b"").is_none());
5977        assert!(key_of(&[b'g'; 256]).is_none());
5978        assert!(lookup(&[b'g'; 256]).is_none());
5979        assert!(lookup(b"9et").is_none());
5980    }
5981
5982    /// The two cases of a name give the same key and different names do not.
5983    #[test]
5984    fn a_key_folds_the_case_and_nothing_else() {
5985        assert_eq!(key_of(b"get"), key_of(b"GET"));
5986        assert_eq!(key_of(b"get"), key_of(b"gEt"));
5987        assert_ne!(key_of(b"get"), key_of(b"set"), "other first byte");
5988        assert_ne!(key_of(b"get"), key_of(b"gxt"), "other second byte");
5989        assert_ne!(key_of(b"get"), key_of(b"gex"), "other last byte");
5990        assert_ne!(key_of(b"get"), key_of(b"gett"), "other length");
5991        assert_ne!(key_of(b"abcde"), key_of(b"abxde"), "other middle byte");
5992        assert_eq!(key_of(b"abcde"), key_of(b"ABCDE"), "middle byte folds too");
5993    }
5994
5995    /// The index is still worth having, which is a thing that can rot.
5996    ///
5997    /// The multiplier was searched for against the 191 commands that were in the
5998    /// table when it was written, and fifteen times since. Adding commands cannot
5999    /// make a lookup wrong, because a probe walks to an empty slot and every
6000    /// candidate has its name compared, but it can make one slow, and a slow
6001    /// lookup is exactly the thing this replaced. So the worst probe is written
6002    /// down here: if a command added later pushes it up, somebody searches for a
6003    /// new multiplier or a bigger table rather than finding out from a benchmark
6004    /// six months later. Both of those have now happened, and the note on
6005    /// [`MIX`] says which one worked when.
6006    ///
6007    /// The bound is two slots because that is what a lookup is allowed to cost,
6008    /// and the table is better than its bound: the multiplier in it keeps every
6009    /// command within one slot. The total is held at exactly what it measures so
6010    /// that a command which quietly spends the headroom shows up here.
6011    #[test]
6012    fn no_command_is_more_than_two_slots_from_where_it_wants_to_be() {
6013        let mut worst = 0;
6014        let mut total = 0;
6015        for spec in COMMANDS {
6016            let key = key_of(spec.name.as_bytes()).expect(spec.name);
6017            let home = slot_of(key);
6018            let mut at = home;
6019            let mut steps = 0;
6020            while INDEX[at] as usize != index_of(spec) {
6021                at = (at + 1) & (SLOTS - 1);
6022                steps += 1;
6023                assert!(steps < SLOTS, "{} is not in the index at all", spec.name);
6024            }
6025            worst = worst.max(steps);
6026            total += steps;
6027        }
6028        assert!(worst <= 2, "worst probe is {worst} slots");
6029        assert_eq!(
6030            worst, 1,
6031            "the multiplier stopped keeping every command close"
6032        );
6033        assert!(
6034            total <= 22,
6035            "{total} extra slots walked over the whole table"
6036        );
6037    }
6038
6039    /// The table has room to probe in, which is what stops the loop.
6040    #[test]
6041    fn the_index_is_not_full() {
6042        assert!(
6043            COMMANDS.len() < SLOTS,
6044            "the probe would never find an empty"
6045        );
6046        assert!(
6047            COMMANDS.len() < FREE as usize,
6048            "an index would collide with FREE"
6049        );
6050        let free = INDEX.iter().filter(|&&i| i == FREE).count();
6051        assert_eq!(free, SLOTS - COMMANDS.len());
6052    }
6053
6054    #[test]
6055    fn arity_counts_the_command_name() {
6056        let get = lookup(b"get").unwrap();
6057        assert!(!arity_ok(get, 1));
6058        assert!(arity_ok(get, 2));
6059        assert!(!arity_ok(get, 3));
6060
6061        // A negative arity is a minimum, which is how SET takes its options.
6062        let set = lookup(b"set").unwrap();
6063        assert!(!arity_ok(set, 2));
6064        assert!(arity_ok(set, 3));
6065        assert!(arity_ok(set, 9));
6066    }
6067
6068    /// A key spec that is wrong sends a cluster client to the wrong node, so
6069    /// the pair commands are worth stating twice.
6070    #[test]
6071    fn the_pair_commands_step_two_keys_at_a_time() {
6072        for name in [b"mset".as_slice(), b"msetnx"] {
6073            let c = lookup(name).unwrap();
6074            assert_eq!((c.first_key, c.last_key, c.step), (1, -1, 2));
6075        }
6076        let mget = lookup(b"mget").unwrap();
6077        assert_eq!((mget.first_key, mget.last_key, mget.step), (1, -1, 1));
6078        // MSETEX counts its keys in an argument, so there is no static spec
6079        // for them and a client has to ask with COMMAND GETKEYS.
6080        let msetex = lookup(b"msetex").unwrap();
6081        assert_eq!((msetex.first_key, msetex.last_key, msetex.step), (0, 0, 0));
6082        assert!(msetex.flags.contains(&"movablekeys"));
6083    }
6084}