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.SEARCH",
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(N) with N the documents the query matches",
3323        summary: "The documents a query answers, with their fields.",
3324        group: "search",
3325    },
3326    Spec {
3327        name: "FT.AGGREGATE",
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.1.0",
3335        complexity: "O(N) with N the documents the query matches",
3336        summary: "The properties a query answers, run through a pipeline.",
3337        group: "search",
3338    },
3339    Spec {
3340        name: "FT.CURSOR",
3341        arity: -2,
3342        flags: SEARCH_READ,
3343        first_key: 0,
3344        last_key: 0,
3345        step: 0,
3346        acl: AC_SEARCH,
3347        since: "1.1.0",
3348        complexity: "O(1)",
3349        summary: "The next chunk of an answer a cursor was left open on.",
3350        group: "search",
3351    },
3352    Spec {
3353        name: "FT.EXPLAIN",
3354        arity: -3,
3355        flags: SEARCH_READ,
3356        first_key: 0,
3357        last_key: 0,
3358        step: 0,
3359        acl: AC_SEARCH,
3360        since: "1.0.0",
3361        complexity: "O(1)",
3362        summary: "The tree a query parses into, as text.",
3363        group: "search",
3364    },
3365    Spec {
3366        name: "FT.EXPLAINCLI",
3367        arity: -3,
3368        flags: SEARCH_READ,
3369        first_key: 0,
3370        last_key: 0,
3371        step: 0,
3372        acl: AC_SEARCH,
3373        since: "1.0.0",
3374        complexity: "O(1)",
3375        summary: "The tree a query parses into, one line per reply element.",
3376        group: "search",
3377    },
3378    // --------------------------------------------------------------- bloom
3379    Spec {
3380        name: "bf.reserve",
3381        arity: -4,
3382        flags: BLOOM_WRITE,
3383        first_key: 1,
3384        last_key: 1,
3385        step: 1,
3386        acl: AC_BLOOM_WRITE_FAST,
3387        since: "1.0.0",
3388        complexity: "O(1)",
3389        summary: "Make an empty filter with a given capacity and error rate.",
3390        group: "bloom",
3391    },
3392    Spec {
3393        name: "bf.add",
3394        arity: 3,
3395        flags: BLOOM_WRITE,
3396        first_key: 1,
3397        last_key: 1,
3398        step: 1,
3399        acl: AC_BLOOM_WRITE,
3400        since: "1.0.0",
3401        complexity: "O(K) with K the number of hash functions",
3402        summary: "Add an item, making the filter if the key is free.",
3403        group: "bloom",
3404    },
3405    Spec {
3406        name: "bf.madd",
3407        arity: -3,
3408        flags: BLOOM_WRITE,
3409        first_key: 1,
3410        last_key: 1,
3411        step: 1,
3412        acl: AC_BLOOM_WRITE,
3413        since: "1.0.0",
3414        complexity: "O(N * K) with N the number of items",
3415        summary: "Add several items, making the filter if the key is free.",
3416        group: "bloom",
3417    },
3418    Spec {
3419        name: "bf.insert",
3420        arity: -4,
3421        flags: BLOOM_WRITE,
3422        first_key: 1,
3423        last_key: 1,
3424        step: 1,
3425        acl: AC_BLOOM_WRITE,
3426        since: "1.0.0",
3427        complexity: "O(N * K) with N the number of items",
3428        summary: "Add several items to a filter described in the same command.",
3429        group: "bloom",
3430    },
3431    Spec {
3432        name: "bf.exists",
3433        arity: 3,
3434        flags: BLOOM_READ,
3435        first_key: 1,
3436        last_key: 1,
3437        step: 1,
3438        acl: AC_BLOOM_READ,
3439        since: "1.0.0",
3440        complexity: "O(K) with K the number of hash functions",
3441        summary: "Whether an item is probably in the filter.",
3442        group: "bloom",
3443    },
3444    Spec {
3445        name: "bf.mexists",
3446        arity: -3,
3447        flags: BLOOM_READ,
3448        first_key: 1,
3449        last_key: 1,
3450        step: 1,
3451        acl: AC_BLOOM_READ,
3452        since: "1.0.0",
3453        complexity: "O(N * K) with N the number of items",
3454        summary: "Whether each of several items is probably in the filter.",
3455        group: "bloom",
3456    },
3457    Spec {
3458        name: "bf.scandump",
3459        arity: 3,
3460        flags: BLOOM_READ,
3461        first_key: 1,
3462        last_key: 1,
3463        step: 1,
3464        acl: AC_BLOOM_READ,
3465        since: "1.0.0",
3466        complexity: "O(N) with N the size of the chunk",
3467        summary: "One chunk of the filter, to be replayed into BF.LOADCHUNK.",
3468        group: "bloom",
3469    },
3470    Spec {
3471        name: "bf.loadchunk",
3472        arity: 4,
3473        flags: BLOOM_WRITE,
3474        first_key: 1,
3475        last_key: 1,
3476        step: 1,
3477        acl: AC_BLOOM_WRITE,
3478        since: "1.0.0",
3479        complexity: "O(N) with N the size of the chunk",
3480        summary: "Put back a chunk that BF.SCANDUMP handed out.",
3481        group: "bloom",
3482    },
3483    Spec {
3484        name: "bf.info",
3485        arity: -2,
3486        flags: BLOOM_READ,
3487        first_key: 1,
3488        last_key: 1,
3489        step: 1,
3490        acl: AC_BLOOM_READ_FAST,
3491        since: "1.0.0",
3492        complexity: "O(1)",
3493        summary: "The shape of the filter, or one field of it.",
3494        group: "bloom",
3495    },
3496    Spec {
3497        name: "bf.card",
3498        arity: 2,
3499        flags: BLOOM_READ,
3500        first_key: 1,
3501        last_key: 1,
3502        step: 1,
3503        acl: AC_BLOOM_READ_FAST,
3504        since: "2.4.4",
3505        complexity: "O(1)",
3506        summary: "How many items were added to the filter.",
3507        group: "bloom",
3508    },
3509    Spec {
3510        name: "bf.debug",
3511        arity: 2,
3512        flags: BLOOM_READ,
3513        first_key: 1,
3514        last_key: 1,
3515        step: 1,
3516        acl: AC_BLOOM_READ,
3517        since: "1.0.0",
3518        complexity: "O(1)",
3519        summary: "The chain and a line for each of its links.",
3520        group: "bloom",
3521    },
3522    // -------------------------------------------------------------- cuckoo
3523    Spec {
3524        name: "cf.reserve",
3525        arity: -3,
3526        flags: CUCKOO_WRITE,
3527        first_key: 1,
3528        last_key: 1,
3529        step: 1,
3530        acl: AC_CUCKOO_WRITE_FAST,
3531        since: "1.0.0",
3532        complexity: "O(1)",
3533        summary: "Make an empty filter with a given capacity.",
3534        group: "cuckoo",
3535    },
3536    Spec {
3537        name: "cf.add",
3538        arity: 3,
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(1) amortised, O(N) when the chain has to grow",
3546        summary: "Add an item, making the filter if the key is free.",
3547        group: "cuckoo",
3548    },
3549    Spec {
3550        name: "cf.addnx",
3551        arity: 3,
3552        flags: CUCKOO_WRITE,
3553        first_key: 1,
3554        last_key: 1,
3555        step: 1,
3556        acl: AC_CUCKOO_WRITE,
3557        since: "1.0.0",
3558        complexity: "O(1) amortised, O(N) when the chain has to grow",
3559        summary: "Add an item unless the filter already has it.",
3560        group: "cuckoo",
3561    },
3562    Spec {
3563        name: "cf.insert",
3564        arity: -4,
3565        flags: CUCKOO_WRITE,
3566        first_key: 1,
3567        last_key: 1,
3568        step: 1,
3569        acl: AC_CUCKOO_WRITE,
3570        since: "1.0.0",
3571        complexity: "O(N) with N the number of items",
3572        summary: "Add several items to a filter described in the same command.",
3573        group: "cuckoo",
3574    },
3575    Spec {
3576        name: "cf.insertnx",
3577        arity: -4,
3578        flags: CUCKOO_WRITE,
3579        first_key: 1,
3580        last_key: 1,
3581        step: 1,
3582        acl: AC_CUCKOO_WRITE,
3583        since: "1.0.0",
3584        complexity: "O(N) with N the number of items",
3585        summary: "Add several items the filter does not already have.",
3586        group: "cuckoo",
3587    },
3588    Spec {
3589        name: "cf.exists",
3590        arity: 3,
3591        flags: CUCKOO_READ,
3592        first_key: 1,
3593        last_key: 1,
3594        step: 1,
3595        acl: AC_CUCKOO_READ,
3596        since: "1.0.0",
3597        complexity: "O(1)",
3598        summary: "Whether an item is probably in the filter.",
3599        group: "cuckoo",
3600    },
3601    Spec {
3602        name: "cf.mexists",
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 number of items",
3611        summary: "Whether each of several items is probably in the filter.",
3612        group: "cuckoo",
3613    },
3614    Spec {
3615        name: "cf.count",
3616        arity: 3,
3617        flags: CUCKOO_READ,
3618        first_key: 1,
3619        last_key: 1,
3620        step: 1,
3621        acl: AC_CUCKOO_READ,
3622        since: "1.0.0",
3623        complexity: "O(1)",
3624        summary: "How many copies of an item the filter thinks it has.",
3625        group: "cuckoo",
3626    },
3627    Spec {
3628        name: "cf.del",
3629        arity: 3,
3630        flags: CUCKOO_DELETE,
3631        first_key: 1,
3632        last_key: 1,
3633        step: 1,
3634        acl: AC_CUCKOO_WRITE,
3635        since: "1.0.0",
3636        complexity: "O(1)",
3637        summary: "Take one copy of an item out of the filter.",
3638        group: "cuckoo",
3639    },
3640    Spec {
3641        name: "cf.scandump",
3642        arity: 3,
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(N) with N the size of the chunk",
3650        summary: "One chunk of the filter, to be replayed into CF.LOADCHUNK.",
3651        group: "cuckoo",
3652    },
3653    Spec {
3654        name: "cf.loadchunk",
3655        arity: 4,
3656        flags: CUCKOO_WRITE,
3657        first_key: 1,
3658        last_key: 1,
3659        step: 1,
3660        acl: AC_CUCKOO_WRITE,
3661        since: "1.0.0",
3662        complexity: "O(N) with N the size of the chunk",
3663        summary: "Put back a chunk that CF.SCANDUMP handed out.",
3664        group: "cuckoo",
3665    },
3666    Spec {
3667        name: "cf.info",
3668        arity: 2,
3669        flags: CUCKOO_READ,
3670        first_key: 1,
3671        last_key: 1,
3672        step: 1,
3673        acl: AC_CUCKOO_READ_FAST,
3674        since: "1.0.0",
3675        complexity: "O(1)",
3676        summary: "The shape of the chain.",
3677        group: "cuckoo",
3678    },
3679    Spec {
3680        name: "cf.debug",
3681        arity: 2,
3682        flags: CUCKOO_READ,
3683        first_key: 1,
3684        last_key: 1,
3685        step: 1,
3686        acl: AC_CUCKOO_READ,
3687        since: "1.0.0",
3688        complexity: "O(1)",
3689        summary: "The chain's geometry on one line.",
3690        group: "cuckoo",
3691    },
3692    Spec {
3693        name: "cf.compact",
3694        arity: -1,
3695        flags: CUCKOO_READ,
3696        first_key: 1,
3697        last_key: 1,
3698        step: 1,
3699        acl: AC_CUCKOO_READ,
3700        since: "1.0.0",
3701        complexity: "O(N) with N the number of items in the newer filters",
3702        summary: "Pull the newer filters down into the older ones.",
3703        group: "cuckoo",
3704    },
3705    // ----------------------------------------------------------------- cms
3706    Spec {
3707        name: "cms.initbydim",
3708        arity: 4,
3709        flags: CMS_WRITE,
3710        first_key: 1,
3711        last_key: 1,
3712        step: 1,
3713        acl: AC_CMS_WRITE_FAST,
3714        since: "2.0.0",
3715        complexity: "O(1)",
3716        summary: "Make an empty sketch of a given width and depth.",
3717        group: "cms",
3718    },
3719    Spec {
3720        name: "cms.initbyprob",
3721        arity: 4,
3722        flags: CMS_WRITE,
3723        first_key: 1,
3724        last_key: 1,
3725        step: 1,
3726        acl: AC_CMS_WRITE_FAST,
3727        since: "2.0.0",
3728        complexity: "O(1)",
3729        summary: "Make an empty sketch wide enough for a stated tolerance.",
3730        group: "cms",
3731    },
3732    Spec {
3733        name: "cms.incrby",
3734        arity: -4,
3735        flags: CMS_WRITE,
3736        first_key: 1,
3737        last_key: 1,
3738        step: 1,
3739        acl: AC_CMS_WRITE,
3740        since: "2.0.0",
3741        complexity: "O(N) with N the number of items",
3742        summary: "Add to the count of one or more items.",
3743        group: "cms",
3744    },
3745    Spec {
3746        name: "cms.query",
3747        arity: -3,
3748        flags: CMS_READ,
3749        first_key: 1,
3750        last_key: 1,
3751        step: 1,
3752        acl: AC_CMS_READ,
3753        since: "2.0.0",
3754        complexity: "O(N) with N the number of items",
3755        summary: "How many times the sketch has seen each item.",
3756        group: "cms",
3757    },
3758    Spec {
3759        name: "cms.merge",
3760        arity: -4,
3761        flags: CMS_WRITE,
3762        first_key: 1,
3763        last_key: 1,
3764        step: 1,
3765        acl: AC_CMS_WRITE,
3766        since: "2.0.0",
3767        complexity: "O(N * M) with N the sources and M the counters in one",
3768        summary: "Replace a sketch with the weighted sum of others.",
3769        group: "cms",
3770    },
3771    Spec {
3772        name: "cms.info",
3773        arity: 2,
3774        flags: CMS_READ,
3775        first_key: 1,
3776        last_key: 1,
3777        step: 1,
3778        acl: AC_CMS_READ_FAST,
3779        since: "2.0.0",
3780        complexity: "O(1)",
3781        summary: "The width, the depth and everything ever added.",
3782        group: "cms",
3783    },
3784    // ---------------------------------------------------------------- topk
3785    Spec {
3786        name: "topk.reserve",
3787        arity: -3,
3788        flags: TOPK_WRITE,
3789        first_key: 1,
3790        last_key: 1,
3791        step: 1,
3792        acl: AC_TOPK_WRITE_FAST,
3793        since: "2.0.0",
3794        complexity: "O(1)",
3795        summary: "Make an empty sketch that keeps the k commonest items.",
3796        group: "topk",
3797    },
3798    Spec {
3799        name: "topk.add",
3800        arity: -3,
3801        flags: TOPK_WRITE,
3802        first_key: 1,
3803        last_key: 1,
3804        step: 1,
3805        acl: AC_TOPK_WRITE,
3806        since: "2.0.0",
3807        complexity: "O(N * K) with N the items and K the depth",
3808        summary: "Count one occurrence of each item.",
3809        group: "topk",
3810    },
3811    Spec {
3812        name: "topk.incrby",
3813        arity: -4,
3814        flags: TOPK_WRITE,
3815        first_key: 1,
3816        last_key: 1,
3817        step: 1,
3818        acl: AC_TOPK_WRITE,
3819        since: "2.0.0",
3820        complexity: "O(N * K) with N the items and K the depth",
3821        summary: "Count a stated number of occurrences of each item.",
3822        group: "topk",
3823    },
3824    Spec {
3825        name: "topk.query",
3826        arity: -3,
3827        flags: TOPK_READ,
3828        first_key: 1,
3829        last_key: 1,
3830        step: 1,
3831        acl: AC_TOPK_READ,
3832        since: "2.0.0",
3833        complexity: "O(N * K) with N the items and K the kept count",
3834        summary: "Whether each item is one of the ones being kept.",
3835        group: "topk",
3836    },
3837    Spec {
3838        name: "topk.count",
3839        arity: -3,
3840        flags: TOPK_READ,
3841        first_key: 1,
3842        last_key: 1,
3843        step: 1,
3844        acl: AC_TOPK_READ,
3845        since: "2.0.0",
3846        complexity: "O(N * K) with N the items and K the depth",
3847        summary: "How many times the sketch thinks it has seen each item.",
3848        group: "topk",
3849    },
3850    Spec {
3851        name: "topk.list",
3852        arity: -2,
3853        flags: TOPK_READ,
3854        first_key: 1,
3855        last_key: 1,
3856        step: 1,
3857        acl: AC_TOPK_READ,
3858        since: "2.0.0",
3859        complexity: "O(K log K) with K the kept count",
3860        summary: "The kept items, heaviest first.",
3861        group: "topk",
3862    },
3863    Spec {
3864        name: "topk.info",
3865        arity: 2,
3866        flags: TOPK_READ,
3867        first_key: 1,
3868        last_key: 1,
3869        step: 1,
3870        acl: AC_TOPK_READ_FAST,
3871        since: "2.0.0",
3872        complexity: "O(1)",
3873        summary: "The four numbers the sketch was made with.",
3874        group: "topk",
3875    },
3876    // ------------------------------------------------------------- tdigest
3877    Spec {
3878        name: "tdigest.create",
3879        arity: -2,
3880        flags: TDIGEST_WRITE,
3881        first_key: 1,
3882        last_key: 1,
3883        step: 1,
3884        acl: AC_TDIGEST_WRITE_FAST,
3885        since: "2.4.0",
3886        complexity: "O(1)",
3887        summary: "Make an empty digest of a stated compression.",
3888        group: "tdigest",
3889    },
3890    Spec {
3891        name: "tdigest.reset",
3892        arity: 2,
3893        flags: TDIGEST_WRITE,
3894        first_key: 1,
3895        last_key: 1,
3896        step: 1,
3897        acl: AC_TDIGEST_WRITE_FAST,
3898        since: "2.4.0",
3899        complexity: "O(1)",
3900        summary: "Throw away every sample and keep the shape.",
3901        group: "tdigest",
3902    },
3903    Spec {
3904        name: "tdigest.add",
3905        arity: -3,
3906        flags: TDIGEST_WRITE,
3907        first_key: 1,
3908        last_key: 1,
3909        step: 1,
3910        acl: AC_TDIGEST_WRITE,
3911        since: "2.4.0",
3912        complexity: "O(N) with N the number of samples",
3913        summary: "Add samples of weight one each.",
3914        group: "tdigest",
3915    },
3916    Spec {
3917        name: "tdigest.merge",
3918        arity: -4,
3919        flags: TDIGEST_MERGE,
3920        first_key: 1,
3921        last_key: 1,
3922        step: 1,
3923        acl: AC_TDIGEST_WRITE,
3924        since: "2.4.0",
3925        complexity: "O(N) with N the number of centroids in the inputs",
3926        summary: "Fold digests together into one.",
3927        group: "tdigest",
3928    },
3929    Spec {
3930        name: "tdigest.min",
3931        arity: 2,
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(1)",
3939        summary: "The smallest sample ever added.",
3940        group: "tdigest",
3941    },
3942    Spec {
3943        name: "tdigest.max",
3944        arity: 2,
3945        flags: TDIGEST_READ,
3946        first_key: 1,
3947        last_key: 1,
3948        step: 1,
3949        acl: AC_TDIGEST_READ_FAST,
3950        since: "2.4.0",
3951        complexity: "O(1)",
3952        summary: "The largest sample ever added.",
3953        group: "tdigest",
3954    },
3955    Spec {
3956        name: "tdigest.quantile",
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: "The value each fraction of the samples falls under.",
3966        group: "tdigest",
3967    },
3968    Spec {
3969        name: "tdigest.cdf",
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: "The fraction of the samples at or below each value.",
3979        group: "tdigest",
3980    },
3981    Spec {
3982        name: "tdigest.trimmed_mean",
3983        arity: 4,
3984        flags: TDIGEST_READ,
3985        first_key: 1,
3986        last_key: 1,
3987        step: 1,
3988        acl: AC_TDIGEST_READ,
3989        since: "2.4.0",
3990        complexity: "O(N) with N the number of centroids",
3991        summary: "The mean of what is left once both tails are cut.",
3992        group: "tdigest",
3993    },
3994    Spec {
3995        name: "tdigest.rank",
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: "How many samples each value is above.",
4005        group: "tdigest",
4006    },
4007    Spec {
4008        name: "tdigest.revrank",
4009        arity: -3,
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(N) with N the number of centroids",
4017        summary: "How many samples each value is below.",
4018        group: "tdigest",
4019    },
4020    Spec {
4021        name: "tdigest.byrank",
4022        arity: -3,
4023        flags: TDIGEST_READ,
4024        first_key: 1,
4025        last_key: 1,
4026        step: 1,
4027        acl: AC_TDIGEST_READ_FAST,
4028        since: "2.4.0",
4029        complexity: "O(N) with N the number of centroids",
4030        summary: "The value at each rank counting up from the smallest.",
4031        group: "tdigest",
4032    },
4033    Spec {
4034        name: "tdigest.byrevrank",
4035        arity: -3,
4036        flags: TDIGEST_READ,
4037        first_key: 1,
4038        last_key: 1,
4039        step: 1,
4040        acl: AC_TDIGEST_READ_FAST,
4041        since: "2.4.0",
4042        complexity: "O(N) with N the number of centroids",
4043        summary: "The value at each rank counting down from the largest.",
4044        group: "tdigest",
4045    },
4046    Spec {
4047        name: "tdigest.info",
4048        arity: 2,
4049        flags: TDIGEST_READ,
4050        first_key: 1,
4051        last_key: 1,
4052        step: 1,
4053        acl: AC_TDIGEST_READ_FAST,
4054        since: "2.4.0",
4055        complexity: "O(1)",
4056        summary: "The nine numbers the digest keeps about itself.",
4057        group: "tdigest",
4058    },
4059    // ------------------------------------------------------------------ ts
4060    Spec {
4061        name: "ts.create",
4062        arity: -2,
4063        flags: TS_WRITE,
4064        first_key: 1,
4065        last_key: 1,
4066        step: 1,
4067        acl: AC_TS_WRITE_FAST,
4068        since: "1.0.0",
4069        complexity: "O(1)",
4070        summary: "Make an empty series and say how it should behave.",
4071        group: "ts",
4072    },
4073    Spec {
4074        name: "ts.alter",
4075        arity: -2,
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(N) with N the labels being set",
4083        summary: "Change how a series behaves, leaving what was not named alone.",
4084        group: "ts",
4085    },
4086    Spec {
4087        name: "ts.add",
4088        arity: -4,
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 chunk a backfill lands in",
4096        summary: "Put a sample in, making the series if it is not there.",
4097        group: "ts",
4098    },
4099    Spec {
4100        name: "ts.madd",
4101        arity: -4,
4102        flags: TS_WRITE,
4103        first_key: 1,
4104        last_key: -1,
4105        step: 3,
4106        acl: AC_TS_WRITE,
4107        since: "1.0.0",
4108        complexity: "O(N * M) with N the samples given",
4109        summary: "Put a sample in each of several series.",
4110        group: "ts",
4111    },
4112    Spec {
4113        name: "ts.incrby",
4114        arity: -3,
4115        flags: TS_WRITE,
4116        first_key: 1,
4117        last_key: 1,
4118        step: 1,
4119        acl: AC_TS_WRITE,
4120        since: "1.0.0",
4121        complexity: "O(M) with M the samples in the last chunk",
4122        summary: "Add to the newest value and store the answer.",
4123        group: "ts",
4124    },
4125    Spec {
4126        name: "ts.decrby",
4127        arity: -3,
4128        flags: TS_WRITE,
4129        first_key: 1,
4130        last_key: 1,
4131        step: 1,
4132        acl: AC_TS_WRITE,
4133        since: "1.0.0",
4134        complexity: "O(M) with M the samples in the last chunk",
4135        summary: "Take away from the newest value and store the answer.",
4136        group: "ts",
4137    },
4138    Spec {
4139        name: "ts.del",
4140        arity: 4,
4141        flags: TS_DELETE,
4142        first_key: 1,
4143        last_key: 1,
4144        step: 1,
4145        acl: AC_TS_WRITE,
4146        since: "1.6.0",
4147        complexity: "O(N) with N the samples in the span",
4148        summary: "Take out every sample between two timestamps.",
4149        group: "ts",
4150    },
4151    Spec {
4152        name: "ts.get",
4153        arity: -2,
4154        flags: TS_READ,
4155        first_key: 1,
4156        last_key: 1,
4157        step: 1,
4158        acl: AC_TS_READ_FAST,
4159        since: "1.0.0",
4160        complexity: "O(1)",
4161        summary: "The newest sample in a series.",
4162        group: "ts",
4163    },
4164    Spec {
4165        name: "ts.info",
4166        arity: -2,
4167        flags: TS_READ,
4168        first_key: 1,
4169        last_key: 1,
4170        step: 1,
4171        acl: AC_TS_READ_FAST,
4172        since: "1.0.0",
4173        complexity: "O(1)",
4174        summary: "The fourteen things a series says about itself.",
4175        group: "ts",
4176    },
4177    Spec {
4178        name: "ts.range",
4179        arity: -4,
4180        flags: TS_READ,
4181        first_key: 1,
4182        last_key: 1,
4183        step: 1,
4184        acl: AC_TS_READ,
4185        since: "1.0.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 samples in a span, oldest first, in buckets if asked for.",
4188        group: "ts",
4189    },
4190    Spec {
4191        name: "ts.revrange",
4192        arity: -4,
4193        flags: TS_READ,
4194        first_key: 1,
4195        last_key: 1,
4196        step: 1,
4197        acl: AC_TS_READ,
4198        since: "1.4.0",
4199        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4200        summary: "The same span, newest first.",
4201        group: "ts",
4202    },
4203    Spec {
4204        name: "ts.nrange",
4205        arity: -5,
4206        flags: TS_READ_MOVABLE,
4207        first_key: 0,
4208        last_key: 0,
4209        step: 0,
4210        acl: AC_TS_READ,
4211        since: "8.10.0",
4212        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4213        summary: "The same span out of several series, lined up on the timestamps.",
4214        group: "ts",
4215    },
4216    Spec {
4217        name: "ts.nrevrange",
4218        arity: -5,
4219        flags: TS_READ_MOVABLE,
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/m+k) with n the samples, m the chunk size and k the samples in the span",
4226        summary: "The same rows, newest first.",
4227        group: "ts",
4228    },
4229    Spec {
4230        name: "ts.read",
4231        arity: -3,
4232        flags: TS_READ,
4233        first_key: 1,
4234        last_key: 1,
4235        step: 1,
4236        acl: AC_TS_READ,
4237        since: "8.10.0",
4238        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples answered",
4239        summary: "Every sample from a timestamp to the end of the series.",
4240        group: "ts",
4241    },
4242    Spec {
4243        name: "ts.queryindex",
4244        arity: -2,
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: "The series a filter list takes, by key name.",
4253        group: "ts",
4254    },
4255    Spec {
4256        name: "ts.querylabels",
4257        arity: -2,
4258        flags: TS_READ,
4259        first_key: 0,
4260        last_key: 0,
4261        step: 0,
4262        acl: AC_TS_READ,
4263        since: "8.10.0",
4264        complexity: "O(n) with n the series in the keyspace",
4265        summary: "The label names in use, or the values one of them takes.",
4266        group: "ts",
4267    },
4268    Spec {
4269        name: "ts.mget",
4270        arity: -3,
4271        flags: TS_READ,
4272        first_key: 0,
4273        last_key: 0,
4274        step: 0,
4275        acl: AC_TS_READ,
4276        since: "1.0.0",
4277        complexity: "O(n) with n the series in the keyspace",
4278        summary: "The newest sample of every series a filter list takes.",
4279        group: "ts",
4280    },
4281    Spec {
4282        name: "ts.mrange",
4283        arity: -4,
4284        flags: TS_READ,
4285        first_key: 0,
4286        last_key: 0,
4287        step: 0,
4288        acl: AC_TS_READ,
4289        since: "1.0.0",
4290        complexity: "O(n) with n the series in the keyspace",
4291        summary: "A span out of every series a filter list takes, oldest first.",
4292        group: "ts",
4293    },
4294    Spec {
4295        name: "ts.mrevrange",
4296        arity: -4,
4297        flags: TS_READ,
4298        first_key: 0,
4299        last_key: 0,
4300        step: 0,
4301        acl: AC_TS_READ,
4302        since: "1.4.0",
4303        complexity: "O(n) with n the series in the keyspace",
4304        summary: "The same spans, newest first.",
4305        group: "ts",
4306    },
4307    Spec {
4308        name: "ts.createrule",
4309        arity: -5,
4310        flags: TS_RULE,
4311        first_key: 1,
4312        last_key: 2,
4313        step: 1,
4314        acl: AC_TS_WRITE,
4315        since: "1.0.0",
4316        complexity: "O(1)",
4317        summary: "Fold one series into another as it is written to.",
4318        group: "ts",
4319    },
4320    Spec {
4321        name: "ts.deleterule",
4322        arity: 3,
4323        flags: TS_DELETE,
4324        first_key: 1,
4325        last_key: 2,
4326        step: 1,
4327        acl: AC_TS_WRITE_FAST,
4328        since: "1.0.0",
4329        complexity: "O(1)",
4330        summary: "Stop folding one series into another.",
4331        group: "ts",
4332    },
4333    // --------------------------------------------------------------- array
4334    Spec {
4335        name: "arset",
4336        arity: -4,
4337        flags: WRITE_FAST_OOM,
4338        first_key: 1,
4339        last_key: 1,
4340        step: 1,
4341        acl: AC_ARRAY_WRITE_FAST,
4342        since: "8.8.0",
4343        complexity: "O(N) with N the number of values",
4344        summary: "Write values into consecutive positions from an index.",
4345        group: "array",
4346    },
4347    Spec {
4348        name: "armset",
4349        arity: -4,
4350        flags: WRITE_FAST_OOM,
4351        first_key: 1,
4352        last_key: 1,
4353        step: 1,
4354        acl: AC_ARRAY_WRITE_FAST,
4355        since: "8.8.0",
4356        complexity: "O(N) with N the number of pairs",
4357        summary: "Write index and value pairs, which need not be neighbours.",
4358        group: "array",
4359    },
4360    Spec {
4361        name: "arget",
4362        arity: 3,
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 value at one index, or a null if nothing is there.",
4371        group: "array",
4372    },
4373    Spec {
4374        name: "armget",
4375        arity: -3,
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(N) with N the number of indices",
4383        summary: "The values at the indices named, in the order named.",
4384        group: "array",
4385    },
4386    Spec {
4387        name: "argetrange",
4388        arity: 4,
4389        flags: READ_SLOW,
4390        first_key: 1,
4391        last_key: 1,
4392        step: 1,
4393        acl: AC_ARRAY_READ_SLOW,
4394        since: "8.8.0",
4395        complexity: "O(N) with N the length of the range",
4396        summary: "One reply per position between two indices, holes included.",
4397        group: "array",
4398    },
4399    Spec {
4400        name: "arlen",
4401        arity: 2,
4402        flags: READ_FAST,
4403        first_key: 1,
4404        last_key: 1,
4405        step: 1,
4406        acl: AC_ARRAY_READ_FAST,
4407        since: "8.8.0",
4408        complexity: "O(1)",
4409        summary: "The highest populated index plus one.",
4410        group: "array",
4411    },
4412    Spec {
4413        name: "arcount",
4414        arity: 2,
4415        flags: READ_FAST,
4416        first_key: 1,
4417        last_key: 1,
4418        step: 1,
4419        acl: AC_ARRAY_READ_FAST,
4420        since: "8.8.0",
4421        complexity: "O(1)",
4422        summary: "How many indices hold something.",
4423        group: "array",
4424    },
4425    Spec {
4426        name: "ardel",
4427        arity: -3,
4428        flags: WRITE_FAST,
4429        first_key: 1,
4430        last_key: 1,
4431        step: 1,
4432        acl: AC_ARRAY_WRITE_FAST,
4433        since: "8.8.0",
4434        complexity: "O(N) with N the number of indices",
4435        summary: "Empty the indices named and say how many held something.",
4436        group: "array",
4437    },
4438    Spec {
4439        name: "ardelrange",
4440        arity: -4,
4441        flags: WRITE_SLOW,
4442        first_key: 1,
4443        last_key: 1,
4444        step: 1,
4445        acl: AC_ARRAY_WRITE_SLOW,
4446        since: "8.8.0",
4447        complexity: "O(N) with N the elements touched, not the span asked for",
4448        summary: "Empty one or more ranges of indices.",
4449        group: "array",
4450    },
4451    Spec {
4452        name: "arinsert",
4453        arity: -3,
4454        flags: WRITE_FAST_OOM,
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(N) with N the number of values",
4461        summary: "Append values at the insert cursor.",
4462        group: "array",
4463    },
4464    Spec {
4465        name: "arring",
4466        arity: -4,
4467        flags: WRITE_OOM,
4468        first_key: 1,
4469        last_key: 1,
4470        step: 1,
4471        acl: AC_ARRAY_WRITE_SLOW,
4472        since: "8.8.0",
4473        complexity: "O(N) with N the values, plus the ring size when it changes",
4474        summary: "Append values into a ring of the given size.",
4475        group: "array",
4476    },
4477    Spec {
4478        name: "arnext",
4479        arity: 2,
4480        flags: READ_FAST,
4481        first_key: 1,
4482        last_key: 1,
4483        step: 1,
4484        acl: AC_ARRAY_READ_FAST,
4485        since: "8.8.0",
4486        complexity: "O(1)",
4487        summary: "The index the next append would write to.",
4488        group: "array",
4489    },
4490    Spec {
4491        name: "arseek",
4492        arity: 3,
4493        flags: WRITE_FAST,
4494        first_key: 1,
4495        last_key: 1,
4496        step: 1,
4497        acl: AC_ARRAY_WRITE_FAST,
4498        since: "8.8.0",
4499        complexity: "O(1)",
4500        summary: "Point the insert cursor at an index.",
4501        group: "array",
4502    },
4503    Spec {
4504        name: "arlastitems",
4505        arity: -3,
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 count asked for",
4513        summary: "The newest positions from the insert cursor, holes included.",
4514        group: "array",
4515    },
4516    Spec {
4517        name: "arscan",
4518        arity: -4,
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(N) with N the elements found, not the span asked for",
4526        summary: "Index and value pairs for what a range holds, skipping holes.",
4527        group: "array",
4528    },
4529    Spec {
4530        name: "argrep",
4531        arity: -6,
4532        flags: READ_SLOW,
4533        first_key: 1,
4534        last_key: 1,
4535        step: 1,
4536        acl: AC_ARRAY_READ_SLOW,
4537        since: "8.8.0",
4538        complexity: "O(P * C) with P the positions visited and C the cost of the predicates on one element",
4539        summary: "The indexes in a range whose elements answer a set of textual predicates.",
4540        group: "array",
4541    },
4542    Spec {
4543        name: "arop",
4544        arity: -5,
4545        flags: READ_SLOW,
4546        first_key: 1,
4547        last_key: 1,
4548        step: 1,
4549        acl: AC_ARRAY_READ_SLOW,
4550        since: "8.8.0",
4551        complexity: "O(N) with N the elements found, not the span asked for",
4552        summary: "One number out of a range, added up or compared or counted.",
4553        group: "array",
4554    },
4555    Spec {
4556        name: "arinfo",
4557        arity: -2,
4558        flags: READ_SLOW,
4559        first_key: 1,
4560        last_key: 1,
4561        step: 1,
4562        acl: AC_ARRAY_READ_SLOW,
4563        since: "8.8.0",
4564        complexity: "O(1), or O(N) with N the slices when FULL is given",
4565        summary: "The shape of the array, and what its slices look like.",
4566        group: "array",
4567    },
4568    // ------------------------------------------------------------- streams
4569    Spec {
4570        name: "xadd",
4571        arity: -5,
4572        flags: WRITE_FAST_OOM,
4573        first_key: 1,
4574        last_key: 1,
4575        step: 1,
4576        acl: AC_STREAM_WRITE_FAST,
4577        since: "5.0.0",
4578        complexity: "O(1) for the append, plus what a trim removes.",
4579        summary: "Append an entry and answer with the ID it got.",
4580        group: "stream",
4581    },
4582    Spec {
4583        name: "xlen",
4584        arity: 2,
4585        flags: READ_FAST,
4586        first_key: 1,
4587        last_key: 1,
4588        step: 1,
4589        acl: AC_STREAM_READ_FAST,
4590        since: "5.0.0",
4591        complexity: "O(1)",
4592        summary: "How many entries the stream holds.",
4593        group: "stream",
4594    },
4595    Spec {
4596        name: "xdel",
4597        arity: -3,
4598        flags: WRITE_FAST,
4599        first_key: 1,
4600        last_key: 1,
4601        step: 1,
4602        acl: AC_STREAM_WRITE_FAST,
4603        since: "5.0.0",
4604        complexity: "O(1) per ID.",
4605        summary: "Remove entries by ID and say how many were there.",
4606        group: "stream",
4607    },
4608    Spec {
4609        name: "xdelex",
4610        arity: -5,
4611        flags: WRITE_FAST,
4612        first_key: 1,
4613        last_key: 1,
4614        step: 1,
4615        acl: AC_STREAM_WRITE_FAST,
4616        since: "8.2.0",
4617        complexity: "O(1) per ID.",
4618        summary: "Remove entries by ID, saying what to do about the groups.",
4619        group: "stream",
4620    },
4621    Spec {
4622        name: "xackdel",
4623        arity: -6,
4624        flags: WRITE_FAST,
4625        first_key: 1,
4626        last_key: 1,
4627        step: 1,
4628        acl: AC_STREAM_WRITE_FAST,
4629        since: "8.2.0",
4630        complexity: "O(1) per ID.",
4631        summary: "Acknowledge entries for a group and remove them.",
4632        group: "stream",
4633    },
4634    Spec {
4635        name: "xnack",
4636        arity: -7,
4637        flags: WRITE_FAST,
4638        first_key: 1,
4639        last_key: 1,
4640        step: 1,
4641        acl: AC_STREAM_WRITE_FAST,
4642        since: "8.8.0",
4643        complexity: "O(1) per ID.",
4644        summary: "Give entries back to the group for somebody else to claim.",
4645        group: "stream",
4646    },
4647    Spec {
4648        name: "xtrim",
4649        arity: -4,
4650        flags: WRITE_SLOW,
4651        first_key: 1,
4652        last_key: 1,
4653        step: 1,
4654        acl: AC_STREAM_WRITE_SLOW,
4655        since: "5.0.0",
4656        complexity: "O(N) in the entries removed.",
4657        summary: "Cut the stream down to a length or a minimum ID.",
4658        group: "stream",
4659    },
4660    Spec {
4661        name: "xrange",
4662        arity: -4,
4663        flags: READ_SLOW,
4664        first_key: 1,
4665        last_key: 1,
4666        step: 1,
4667        acl: AC_STREAM_READ_SLOW,
4668        since: "5.0.0",
4669        complexity: "O(N) in the entries returned.",
4670        summary: "The entries between two IDs, oldest first.",
4671        group: "stream",
4672    },
4673    Spec {
4674        name: "xrevrange",
4675        arity: -4,
4676        flags: READ_SLOW,
4677        first_key: 1,
4678        last_key: 1,
4679        step: 1,
4680        acl: AC_STREAM_READ_SLOW,
4681        since: "5.0.0",
4682        complexity: "O(N) in the entries returned.",
4683        summary: "The entries between two IDs, newest first.",
4684        group: "stream",
4685    },
4686    Spec {
4687        name: "xread",
4688        arity: -4,
4689        flags: READ_BLOCKING_MOVABLE,
4690        first_key: 0,
4691        last_key: 0,
4692        step: 0,
4693        acl: AC_STREAM_BLOCKING_READ,
4694        since: "5.0.0",
4695        complexity: "O(N) in the entries returned.",
4696        summary: "Read from one or more streams, waiting if asked to.",
4697        group: "stream",
4698    },
4699    Spec {
4700        name: "xreadgroup",
4701        arity: -7,
4702        flags: WRITE_BLOCKING_MOVABLE,
4703        first_key: 0,
4704        last_key: 0,
4705        step: 0,
4706        acl: AC_STREAM_BLOCKING_WRITE,
4707        since: "5.0.0",
4708        complexity: "O(N) in the entries returned.",
4709        summary: "Read as part of a consumer group, waiting if asked to.",
4710        group: "stream",
4711    },
4712    Spec {
4713        name: "xack",
4714        arity: -4,
4715        flags: WRITE_FAST,
4716        first_key: 1,
4717        last_key: 1,
4718        step: 1,
4719        acl: AC_STREAM_WRITE_FAST,
4720        since: "5.0.0",
4721        complexity: "O(1) per ID.",
4722        summary: "Drop entries from a group's pending list.",
4723        group: "stream",
4724    },
4725    Spec {
4726        name: "xsetid",
4727        arity: -3,
4728        flags: WRITE_FAST_OOM,
4729        first_key: 1,
4730        last_key: 1,
4731        step: 1,
4732        acl: AC_STREAM_WRITE_FAST,
4733        since: "5.0.0",
4734        complexity: "O(1)",
4735        summary: "Set the last ID, the entries added and the max deleted ID.",
4736        group: "stream",
4737    },
4738    Spec {
4739        name: "xgroup",
4740        arity: -2,
4741        flags: &[],
4742        first_key: 0,
4743        last_key: 0,
4744        step: 0,
4745        acl: AC_STREAM_CONTAINER,
4746        since: "5.0.0",
4747        complexity: "O(1) for all subcommands except DESTROY, which frees the group's pending list.",
4748        summary: "Make, move and unmake consumer groups.",
4749        group: "stream",
4750    },
4751    Spec {
4752        name: "xinfo",
4753        arity: -2,
4754        flags: &[],
4755        first_key: 0,
4756        last_key: 0,
4757        step: 0,
4758        acl: AC_STREAM_CONTAINER,
4759        since: "5.0.0",
4760        complexity: "O(1), or O(N) with N the entries and pending entries shown when FULL is given.",
4761        summary: "What a stream, its groups and its consumers look like.",
4762        group: "stream",
4763    },
4764    Spec {
4765        name: "xpending",
4766        arity: -3,
4767        flags: READ_SLOW,
4768        first_key: 1,
4769        last_key: 1,
4770        step: 1,
4771        acl: AC_STREAM_READ_SLOW,
4772        since: "5.0.0",
4773        complexity: "O(1) for the summary, O(N) in the entries returned for the list.",
4774        summary: "What a group has handed out and not had acknowledged.",
4775        group: "stream",
4776    },
4777    Spec {
4778        name: "xclaim",
4779        arity: -6,
4780        flags: WRITE_FAST,
4781        first_key: 1,
4782        last_key: 1,
4783        step: 1,
4784        acl: AC_STREAM_WRITE_FAST,
4785        since: "5.0.0",
4786        complexity: "O(1) per ID.",
4787        summary: "Move named pending entries to another consumer.",
4788        group: "stream",
4789    },
4790    Spec {
4791        name: "xautoclaim",
4792        arity: -6,
4793        flags: WRITE_FAST,
4794        first_key: 1,
4795        last_key: 1,
4796        step: 1,
4797        acl: AC_STREAM_WRITE_FAST,
4798        since: "6.2.0",
4799        complexity: "O(1) per entry claimed, plus what it skips getting there.",
4800        summary: "Sweep a group's pending list and take what has gone idle.",
4801        group: "stream",
4802    },
4803    // ------------------------------------------------------------ keyspace
4804    Spec {
4805        name: "del",
4806        arity: -2,
4807        flags: &["write"],
4808        first_key: 1,
4809        last_key: -1,
4810        step: 1,
4811        acl: AC_KEY_WRITE_SLOW,
4812        since: "1.0.0",
4813        complexity: "O(N) in the number of keys.",
4814        summary: "Delete keys and say how many were there.",
4815        group: "keyspace",
4816    },
4817    Spec {
4818        name: "unlink",
4819        arity: -2,
4820        flags: &["write", "fast"],
4821        first_key: 1,
4822        last_key: -1,
4823        step: 1,
4824        acl: AC_KEY_WRITE_FAST,
4825        since: "4.0.0",
4826        complexity: "O(1) per key, since the freeing is not on this thread.",
4827        summary: "Delete keys and free them out of the way of the reply.",
4828        group: "keyspace",
4829    },
4830    Spec {
4831        name: "exists",
4832        arity: -2,
4833        flags: READ_FAST,
4834        first_key: 1,
4835        last_key: -1,
4836        step: 1,
4837        acl: AC_KEY_READ,
4838        since: "1.0.0",
4839        complexity: "O(N) in the number of keys.",
4840        summary: "Count how many of these keys are there, naming one twice counting twice.",
4841        group: "keyspace",
4842    },
4843    Spec {
4844        name: "type",
4845        arity: 2,
4846        flags: READ_FAST,
4847        first_key: 1,
4848        last_key: 1,
4849        step: 1,
4850        acl: AC_KEY_READ,
4851        since: "1.0.0",
4852        complexity: "O(1)",
4853        summary: "What kind of value is under a key, or none.",
4854        group: "keyspace",
4855    },
4856    Spec {
4857        name: "touch",
4858        arity: -2,
4859        flags: READ_FAST,
4860        first_key: 1,
4861        last_key: -1,
4862        step: 1,
4863        acl: AC_KEY_READ,
4864        since: "3.2.1",
4865        complexity: "O(N) in the number of keys.",
4866        summary: "Count how many of these keys are there, and move them up the eviction order.",
4867        group: "keyspace",
4868    },
4869    // The three that look at keys nobody named. No key positions on any of
4870    // them, which is what the zeroes say, and it is also why a cluster client
4871    // sends them to a node rather than to a slot.
4872    Spec {
4873        name: "scan",
4874        arity: -2,
4875        flags: &["readonly"],
4876        first_key: 0,
4877        last_key: 0,
4878        step: 0,
4879        acl: AC_KEY_READ_SLOW,
4880        since: "2.8.0",
4881        complexity: "O(1) a call, O(N) for a whole iteration",
4882        summary: "Walk part of the keyspace and say where to carry on from.",
4883        group: "keyspace",
4884    },
4885    Spec {
4886        name: "keys",
4887        arity: 2,
4888        flags: &["readonly"],
4889        first_key: 0,
4890        last_key: 0,
4891        step: 0,
4892        acl: AC_KEY_READ_ALL,
4893        since: "1.0.0",
4894        complexity: "O(N) in the number of keys.",
4895        summary: "Every key matching a pattern, in one reply.",
4896        group: "keyspace",
4897    },
4898    Spec {
4899        name: "randomkey",
4900        arity: 1,
4901        flags: &["readonly"],
4902        first_key: 0,
4903        last_key: 0,
4904        step: 0,
4905        acl: AC_KEY_READ_SLOW,
4906        since: "1.0.0",
4907        complexity: "O(1)",
4908        summary: "One key from the database, chosen at random.",
4909        group: "keyspace",
4910    },
4911    // Two keys and not one, which is the 1 2 1 in the key positions. Every other
4912    // row in this group names a range that runs to the end of the arguments.
4913    Spec {
4914        name: "rename",
4915        arity: 3,
4916        flags: &["write"],
4917        first_key: 1,
4918        last_key: 2,
4919        step: 1,
4920        acl: AC_KEY_WRITE_SLOW,
4921        since: "1.0.0",
4922        complexity: "O(1)",
4923        summary: "Move a key to another name, over whatever was there.",
4924        group: "keyspace",
4925    },
4926    Spec {
4927        name: "renamenx",
4928        arity: 3,
4929        flags: WRITE_FAST,
4930        first_key: 1,
4931        last_key: 2,
4932        step: 1,
4933        acl: AC_KEY_WRITE_FAST,
4934        since: "1.0.0",
4935        complexity: "O(1)",
4936        summary: "Move a key to another name, but only if that name is free.",
4937        group: "keyspace",
4938    },
4939    // `denyoom` and no `fast`, because this is the one command in the group that
4940    // allocates a whole second value.
4941    Spec {
4942        name: "copy",
4943        arity: -3,
4944        flags: &["write", "denyoom"],
4945        first_key: 1,
4946        last_key: 2,
4947        step: 1,
4948        acl: AC_KEY_WRITE_SLOW,
4949        since: "6.2.0",
4950        complexity: "O(N) in the size of the value.",
4951        summary: "Copy a value to another key, in this database or another one.",
4952        group: "keyspace",
4953    },
4954    // `COPY` with the source deleted, and the only command in the group whose
4955    // second argument is a database rather than a key. The key spec is one key
4956    // at argument one and the database index is not a key, which is why this
4957    // does not look like `COPY` above it.
4958    Spec {
4959        name: "move",
4960        arity: 3,
4961        flags: WRITE_FAST,
4962        first_key: 1,
4963        last_key: 1,
4964        step: 1,
4965        acl: AC_KEY_WRITE_FAST,
4966        since: "1.0.0",
4967        complexity: "O(1)",
4968        summary: "Move a key to another database, if it is not already there.",
4969        group: "keyspace",
4970    },
4971    // The two that block on replication rather than on a key, so they name no
4972    // key at all and the three zeroes below are not a placeholder.
4973    Spec {
4974        name: "wait",
4975        arity: 3,
4976        flags: &["blocking"],
4977        first_key: 0,
4978        last_key: 0,
4979        step: 0,
4980        acl: AC_WAIT,
4981        since: "3.0.0",
4982        complexity: "O(1)",
4983        summary: "Wait for this connection's writes to reach a number of replicas.",
4984        group: "keyspace",
4985    },
4986    Spec {
4987        name: "waitaof",
4988        arity: 4,
4989        flags: &["blocking"],
4990        first_key: 0,
4991        last_key: 0,
4992        step: 0,
4993        acl: AC_WAIT,
4994        since: "7.2.0",
4995        complexity: "O(1)",
4996        summary: "Wait for this connection's writes to reach the append only files.",
4997        group: "keyspace",
4998    },
4999    // The two that speak the file format. A payload is a value standing on its
5000    // own outside the process, so these are the only two commands in the group
5001    // that move a value rather than a name.
5002    Spec {
5003        name: "dump",
5004        arity: 2,
5005        flags: READ_SLOW,
5006        first_key: 1,
5007        last_key: 1,
5008        step: 1,
5009        acl: AC_KEY_READ_SLOW,
5010        since: "2.6.0",
5011        complexity: "O(1) to find the key, then O(N) in the size of the value.",
5012        summary: "Serialize a value into a payload another server can load.",
5013        group: "keyspace",
5014    },
5015    Spec {
5016        name: "restore",
5017        arity: -4,
5018        flags: &["write", "denyoom"],
5019        first_key: 1,
5020        last_key: 1,
5021        step: 1,
5022        acl: AC_RESTORE,
5023        since: "2.6.0",
5024        complexity: "O(1) to find the key, then O(N) in the size of the payload.",
5025        summary: "Create a key from a payload produced by DUMP.",
5026        group: "keyspace",
5027    },
5028    // And the third one, which is the other two with a socket in between. Its
5029    // keys are movable for the same reason `SORT`'s are, though for a plainer
5030    // reason: the `KEYS` option moves them from argument three to everything
5031    // after the word, so where they are depends on what was written.
5032    Spec {
5033        name: "migrate",
5034        arity: -6,
5035        flags: MIGRATE_FLAGS,
5036        first_key: 3,
5037        last_key: 3,
5038        step: 1,
5039        acl: AC_RESTORE,
5040        since: "2.6.0",
5041        complexity: "A DUMP and a DEL here, a RESTORE there, and the bytes in between.",
5042        summary: "Move a key to another server.",
5043        group: "keyspace",
5044    },
5045    // The two whose keys cannot be read off the command. `SORT k BY w_* GET d_*`
5046    // touches every key those two patterns name and a client cannot know which
5047    // ones without the data, so both carry `movablekeys` and Redis's own key
5048    // specs give the same answer: the first key, and the STORE destination if
5049    // there is one.
5050    Spec {
5051        name: "sort",
5052        arity: -2,
5053        flags: WRITE_MOVABLE,
5054        first_key: 1,
5055        last_key: 1,
5056        step: 1,
5057        acl: AC_SORT_WRITE,
5058        since: "1.0.0",
5059        complexity: "O(N+M*log(M)) with N elements and M returned.",
5060        summary: "Sort a list, set or sorted set, optionally into another key.",
5061        group: "keyspace",
5062    },
5063    Spec {
5064        name: "sort_ro",
5065        arity: -2,
5066        flags: READ_MOVABLE,
5067        first_key: 1,
5068        last_key: 1,
5069        step: 1,
5070        acl: AC_SORT_READ,
5071        since: "7.0.0",
5072        complexity: "O(N+M*log(M)) with N elements and M returned.",
5073        summary: "Sort a list, set or sorted set, without the STORE option.",
5074        group: "keyspace",
5075    },
5076    // The four writers take an optional NX, XX, GT or LT, which is the -3 in
5077    // the arity, and they take the same one whichever unit they are in.
5078    Spec {
5079        name: "expire",
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: "1.0.0",
5087        complexity: "O(1)",
5088        summary: "Put a deadline on a key, counted in seconds from now.",
5089        group: "keyspace",
5090    },
5091    Spec {
5092        name: "pexpire",
5093        arity: -3,
5094        flags: WRITE_FAST,
5095        first_key: 1,
5096        last_key: 1,
5097        step: 1,
5098        acl: AC_KEY_WRITE_FAST,
5099        since: "2.6.0",
5100        complexity: "O(1)",
5101        summary: "Put a deadline on a key, counted in milliseconds from now.",
5102        group: "keyspace",
5103    },
5104    Spec {
5105        name: "expireat",
5106        arity: -3,
5107        flags: WRITE_FAST,
5108        first_key: 1,
5109        last_key: 1,
5110        step: 1,
5111        acl: AC_KEY_WRITE_FAST,
5112        since: "1.2.0",
5113        complexity: "O(1)",
5114        summary: "Put a deadline on a key, as a unix time in seconds.",
5115        group: "keyspace",
5116    },
5117    Spec {
5118        name: "pexpireat",
5119        arity: -3,
5120        flags: WRITE_FAST,
5121        first_key: 1,
5122        last_key: 1,
5123        step: 1,
5124        acl: AC_KEY_WRITE_FAST,
5125        since: "2.6.0",
5126        complexity: "O(1)",
5127        summary: "Put a deadline on a key, as a unix time in milliseconds.",
5128        group: "keyspace",
5129    },
5130    Spec {
5131        name: "persist",
5132        arity: 2,
5133        flags: WRITE_FAST,
5134        first_key: 1,
5135        last_key: 1,
5136        step: 1,
5137        acl: AC_KEY_WRITE_FAST,
5138        since: "2.2.0",
5139        complexity: "O(1)",
5140        summary: "Take a key's deadline off, so it stops being temporary.",
5141        group: "keyspace",
5142    },
5143    Spec {
5144        name: "ttl",
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: "1.0.0",
5152        complexity: "O(1)",
5153        summary: "How many seconds a key has left, -1 with no deadline, -2 if gone.",
5154        group: "keyspace",
5155    },
5156    Spec {
5157        name: "pttl",
5158        arity: 2,
5159        flags: READ_FAST,
5160        first_key: 1,
5161        last_key: 1,
5162        step: 1,
5163        acl: AC_KEY_READ,
5164        since: "2.6.0",
5165        complexity: "O(1)",
5166        summary: "How many milliseconds a key has left, -1 with no deadline, -2 if gone.",
5167        group: "keyspace",
5168    },
5169    Spec {
5170        name: "expiretime",
5171        arity: 2,
5172        flags: READ_FAST,
5173        first_key: 1,
5174        last_key: 1,
5175        step: 1,
5176        acl: AC_KEY_READ,
5177        since: "7.0.0",
5178        complexity: "O(1)",
5179        summary: "When a key falls due, as a unix time in seconds.",
5180        group: "keyspace",
5181    },
5182    Spec {
5183        name: "pexpiretime",
5184        arity: 2,
5185        flags: READ_FAST,
5186        first_key: 1,
5187        last_key: 1,
5188        step: 1,
5189        acl: AC_KEY_READ,
5190        since: "7.0.0",
5191        complexity: "O(1)",
5192        summary: "When a key falls due, as a unix time in milliseconds.",
5193        group: "keyspace",
5194    },
5195    // A container command, so no keys and no flags of its own: the key is the
5196    // subcommand's and a real server reports it on `object|encoding` rather
5197    // than here. `@slow` is the whole ACL, checked against 8.10.1.
5198    Spec {
5199        name: "object",
5200        arity: -2,
5201        flags: &[],
5202        first_key: 0,
5203        last_key: 0,
5204        step: 0,
5205        acl: &["@slow"],
5206        since: "2.2.3",
5207        complexity: "O(1)",
5208        summary: "Look at the machinery under a key rather than at its value.",
5209        group: "keyspace",
5210    },
5211    // ----------------------------------------------------------- scripting
5212    // Both are containers with no flags and no keys of their own, which is what
5213    // a real 8.10.1 reports: the flags live on the subcommands.
5214    Spec {
5215        name: "script",
5216        arity: -2,
5217        flags: &[],
5218        first_key: 0,
5219        last_key: 0,
5220        step: 0,
5221        acl: &["@slow"],
5222        since: "2.6.0",
5223        complexity: "O(1) for the subcommands that are here.",
5224        summary: "The script cache, which is empty and stays empty until M6.",
5225        group: "scripting",
5226    },
5227    Spec {
5228        name: "function",
5229        arity: -2,
5230        flags: &[],
5231        first_key: 0,
5232        last_key: 0,
5233        step: 0,
5234        acl: &["@slow"],
5235        since: "7.0.0",
5236        complexity: "O(1) for the subcommands that are here.",
5237        summary: "The function libraries, of which there are none until M6.",
5238        group: "scripting",
5239    },
5240    // ---------------------------------------------------------- connection
5241    Spec {
5242        name: "ping",
5243        arity: -1,
5244        flags: &["fast"],
5245        first_key: 0,
5246        last_key: 0,
5247        step: 0,
5248        acl: AC_CONN,
5249        since: "1.0.0",
5250        complexity: "O(1)",
5251        summary: "Ask whether the server is answering.",
5252        group: "connection",
5253    },
5254    Spec {
5255        name: "echo",
5256        arity: 2,
5257        flags: &["loading", "stale", "fast"],
5258        first_key: 0,
5259        last_key: 0,
5260        step: 0,
5261        acl: AC_CONN,
5262        since: "1.0.0",
5263        complexity: "O(1)",
5264        summary: "Send a string back unchanged.",
5265        group: "connection",
5266    },
5267    Spec {
5268        name: "hello",
5269        arity: -1,
5270        flags: &[
5271            "noscript",
5272            "loading",
5273            "stale",
5274            "fast",
5275            "no_auth",
5276            "allow_busy",
5277        ],
5278        first_key: 0,
5279        last_key: 0,
5280        step: 0,
5281        acl: AC_CONN,
5282        since: "6.0.0",
5283        complexity: "O(1)",
5284        summary: "Agree on a protocol version and describe the server.",
5285        group: "connection",
5286    },
5287    Spec {
5288        name: "select",
5289        arity: 2,
5290        flags: &["loading", "stale", "fast"],
5291        first_key: 0,
5292        last_key: 0,
5293        step: 0,
5294        acl: AC_CONN,
5295        since: "1.0.0",
5296        complexity: "O(1)",
5297        summary: "Choose which database this connection works in.",
5298        group: "connection",
5299    },
5300    Spec {
5301        name: "reset",
5302        arity: 1,
5303        flags: &[
5304            "noscript",
5305            "loading",
5306            "stale",
5307            "fast",
5308            "no_auth",
5309            "allow_busy",
5310        ],
5311        first_key: 0,
5312        last_key: 0,
5313        step: 0,
5314        acl: AC_CONN,
5315        since: "6.2.0",
5316        complexity: "O(1)",
5317        summary: "Put the connection back the way it was opened.",
5318        group: "connection",
5319    },
5320    Spec {
5321        name: "quit",
5322        arity: -1,
5323        flags: &[
5324            "noscript",
5325            "loading",
5326            "stale",
5327            "fast",
5328            "no_auth",
5329            "allow_busy",
5330        ],
5331        first_key: 0,
5332        last_key: 0,
5333        step: 0,
5334        acl: AC_CONN,
5335        since: "1.0.0",
5336        complexity: "O(1)",
5337        summary: "Close the connection after the replies already queued.",
5338        group: "connection",
5339    },
5340    // -------------------------------------------------------------- server
5341    // COMMAND is in the connection ACL category and in the server group, which
5342    // is not a contradiction: the category is about what a connection is
5343    // allowed to do and the group is about what the command is about. The group
5344    // is the one reported by COMMAND DOCS, so it is the one that has to match.
5345    Spec {
5346        name: "command",
5347        arity: -1,
5348        flags: &["loading", "stale"],
5349        first_key: 0,
5350        last_key: 0,
5351        step: 0,
5352        acl: &["@slow", "@connection"],
5353        since: "2.8.13",
5354        complexity: "O(N) with N the number of commands",
5355        summary: "What this server can do, in the shape client libraries read.",
5356        group: "server",
5357    },
5358    Spec {
5359        name: "config",
5360        arity: -2,
5361        flags: &[],
5362        first_key: 0,
5363        last_key: 0,
5364        step: 0,
5365        acl: &["@slow"],
5366        since: "2.0.0",
5367        complexity: "Depends on the subcommand.",
5368        summary: "Read and change the settings a running server exposes.",
5369        group: "server",
5370    },
5371    // Exactly two, which is what a real 8.10.1 reports for the container even
5372    // though every one of its subcommands carries its own arity underneath. All
5373    // seven of them take two words, so nothing legal is refused by it, and the
5374    // one thing that reads differently is the name inside the arity error for a
5375    // subcommand with an argument after it. That is D-46.
5376    Spec {
5377        name: "backup",
5378        arity: 2,
5379        flags: &[],
5380        first_key: 0,
5381        last_key: 0,
5382        step: 0,
5383        acl: &["@slow"],
5384        since: "8.10.0",
5385        complexity: "Depends on subcommand.",
5386        summary: "A container for backup management commands.",
5387        group: "server",
5388    },
5389    Spec {
5390        name: "info",
5391        arity: -1,
5392        flags: &["loading", "stale"],
5393        first_key: 0,
5394        last_key: 0,
5395        step: 0,
5396        acl: &["@slow", "@dangerous"],
5397        since: "1.0.0",
5398        complexity: "O(1)",
5399        summary: "The server's own numbers, in sections.",
5400        group: "server",
5401    },
5402    Spec {
5403        name: "dbsize",
5404        arity: 1,
5405        flags: READ_FAST,
5406        first_key: 0,
5407        last_key: 0,
5408        step: 0,
5409        acl: AC_KEY_READ,
5410        since: "1.0.0",
5411        complexity: "O(1)",
5412        summary: "How many keys are in the database this connection is on.",
5413        group: "server",
5414    },
5415    Spec {
5416        name: "flushall",
5417        arity: -1,
5418        flags: &["write"],
5419        first_key: 0,
5420        last_key: 0,
5421        step: 0,
5422        acl: AC_KEY_FLUSH,
5423        since: "1.0.0",
5424        complexity: "O(N) in the number of keys in every database.",
5425        summary: "Empty every database.",
5426        group: "server",
5427    },
5428    Spec {
5429        name: "flushdb",
5430        arity: -1,
5431        flags: &["write"],
5432        first_key: 0,
5433        last_key: 0,
5434        step: 0,
5435        acl: AC_KEY_FLUSH,
5436        since: "1.0.0",
5437        complexity: "O(N) in the number of keys in this database.",
5438        summary: "Empty the database this connection is on.",
5439        group: "server",
5440    },
5441    // In the server group and not the keyspace one, which is Redis's answer and
5442    // is the right one: it names no key, it takes two database indexes, and what
5443    // it changes is what every connected client is looking at.
5444    Spec {
5445        name: "swapdb",
5446        arity: 3,
5447        flags: WRITE_FAST,
5448        first_key: 0,
5449        last_key: 0,
5450        step: 0,
5451        acl: AC_SWAPDB,
5452        since: "4.0.0",
5453        complexity: "O(N) in the number of clients watching or blocked on either.",
5454        summary: "Swap two databases, so every client on one sees the other.",
5455        group: "server",
5456    },
5457    // No ACL category but `@fast`, which is Redis's answer and reads like an
5458    // omission. It is not: the categories are about what a command can reach and
5459    // this one reaches nothing.
5460    Spec {
5461        name: "time",
5462        arity: 1,
5463        flags: &["loading", "stale", "fast"],
5464        first_key: 0,
5465        last_key: 0,
5466        step: 0,
5467        acl: &["@fast"],
5468        since: "2.6.0",
5469        complexity: "O(1)",
5470        summary: "The server's clock, as seconds and microseconds.",
5471        group: "server",
5472    },
5473    Spec {
5474        name: "shutdown",
5475        arity: -1,
5476        flags: &[
5477            "admin",
5478            "noscript",
5479            "loading",
5480            "stale",
5481            "no_multi",
5482            "allow_busy",
5483        ],
5484        first_key: 0,
5485        last_key: 0,
5486        step: 0,
5487        acl: &["@admin", "@slow", "@dangerous"],
5488        since: "1.0.0",
5489        complexity: "O(1)",
5490        summary: "Stop the server, without answering.",
5491        group: "server",
5492    },
5493];
5494
5495/// The shortest and the longest command name.
5496///
5497/// Both are facts about [`COMMANDS`], pinned by a test, and both are checked
5498/// before anything is read, so a name that could not be a command is rejected on
5499/// its length alone.
5500const MIN_LEN: usize = 3;
5501const MAX_LEN: usize = 20;
5502
5503/// How many slots the index has, which is a power of two and a bit over three
5504/// times the number of commands.
5505///
5506/// Four kibibytes of `u16`, sixty four cache lines, and loose enough that a probe
5507/// for a name that is not a command stops at an empty slot almost immediately.
5508/// Tight enough that the whole thing stays resident next to the table it
5509/// indexes.
5510///
5511/// This was 512 for a long time, which was a bit over twice the number of
5512/// commands, and it stopped being enough at 282 of them. Then it was 1024, and
5513/// that stopped being enough at 337. The note on [`MIX`] has the whole story
5514/// both times, and the short version is the same one twice: at about half full
5515/// there is no multiplier left that keeps every command within two slots of
5516/// home, and at about a sixth full the multiplier that is already there keeps
5517/// every one of them within a single slot without being touched. Two kibibytes
5518/// is what it cost this time.
5519const SLOTS: usize = 2048;
5520
5521/// A slot nothing was put in.
5522///
5523/// `u16::MAX` and not zero, because zero is `set` and `set` is the command this
5524/// most wants to be able to find.
5525const FREE: u16 = u16::MAX;
5526
5527/// The multiplier, found by searching for one that spreads these 358 names well.
5528///
5529/// Not a magic constant in the bad sense: it is checked. Every command is looked
5530/// up by its own name in a test, and another test holds the worst probe length
5531/// at what it is now, so a command added later that made this multiplier bad
5532/// would fail rather than quietly cost every lookup an extra slot.
5533///
5534/// It has been searched for fifteen times, and each time because the test went red
5535/// rather than because somebody went looking. The first was against the 191 names
5536/// in the table then, the ten graph commands pushed its worst probe to three
5537/// slots, and the second search was run over all 201. The fifteen stream commands
5538/// pushed that one to four slots and fifty one extra probes, so the third was run
5539/// over all 216, and the three 8.x pending list commands cost that one two more
5540/// probes than the test allows. The fourth was over 219 and the seven bitmap
5541/// commands took it to three slots, and the fifth was over all 226. The five
5542/// HyperLogLog commands kept its worst probe at two and took it from forty nine
5543/// extra slots to fifty five, and the search over the 231 names found nothing
5544/// better, so that one stood. The ten geo commands took it to sixty, and the
5545/// sixth search, over eight million multipliers and all 241 names, found one at
5546/// fifty six. The twelve vector set commands took that one to four slots and
5547/// seventy extra probes, so the seventh search was run over all 254 names and
5548/// found one at two slots and seventy seven.
5549///
5550/// The eight JSON commands took that one to five slots, which is the worst any
5551/// of them has been, and the eighth search was run over four hundred million
5552/// multipliers and all 262 names. It found this one at two slots and fifty four,
5553/// which is the best the table has ever been and a third fewer extra probes than
5554/// the multiplier it replaced managed with eight fewer commands. Thirty one of
5555/// the names collide on the key itself and no multiplier can separate them, so
5556/// seventeen extra probes is the floor everything here is measured against.
5557/// `json.set` and `json.get` are one of those pairs, since every name in the
5558/// group starts `js` and the only thing left to tell them apart is the length
5559/// and the last byte.
5560///
5561/// The nine JSON array commands took that one to four slots, and this time the
5562/// search over the 271 names found nothing at two whatever it was given. That
5563/// was not the multiplier's fault. `json.arrlen`, `json.objlen` and
5564/// `json.strlen` all key to the same four bytes, and three names in one slot run
5565/// costs the third of them two probes before any other name has moved, so two
5566/// slots was the whole budget spent in one place. The fix was the key rather
5567/// than the multiplier, which is what [`key_of`] now folds the middle byte in
5568/// for, and the ninth search was run over the 271 names with the new key across
5569/// six shards. It found one at two slots and fifty seven, which is a shade over
5570/// a fifth of a probe a command, the same as the multiplier it replaced managed
5571/// over nine fewer names.
5572///
5573/// The number family and `json.strappend` took that one to three slots, and the
5574/// tenth search over the 275 names found one at two slots and sixty seven.
5575/// Three of the six shards converged on sixty seven from different seeds without
5576/// any of them bettering it, which is the sign that the key rather than the
5577/// multiplier is what is left: fourteen of the names collide on the key itself
5578/// and no multiplier can separate them, so fourteen extra probes is the floor
5579/// and that was within a quarter of it per name. The two new pairs were
5580/// `json.arrappend` with `json.strappend` and `json.numincrby` with
5581/// `json.nummultby`, and both are the same shape as the pairs already there,
5582/// which is a group whose names agree everywhere the key looks.
5583///
5584/// The last four JSON commands took it to three slots again, and the eleventh
5585/// search over the 279 names found this one at two slots and sixty two, which is
5586/// better than the table has ever been while carrying four more names. Only one
5587/// of the four collides on the key, `json.mset` with `json.mget`, so the floor
5588/// moved by one and the multiplier found five more probes than the floor moved.
5589/// Nine shards were run from different seeds and the spread was sixty two to a
5590/// hundred and three, which is worth knowing: one shard is not a search.
5591///
5592/// `SUNIONCARD` and `SDIFFCARD` took it to 281 names and sixty three probes, one
5593/// more than before, and the twelfth search is the first one that did not
5594/// replace it. Eight shards over 960 million multipliers did not find a single
5595/// one that kept the worst probe at two slots at all, let alone at two slots and
5596/// sixty two, and the best of them was three slots and eighty one. So that one
5597/// stayed and the bound went up by one, which is the opposite of what the first
5598/// eleven searches concluded and was the honest reading of the same procedure.
5599///
5600/// `LMOVEM` took it to 282 names and three slots, and that is where the search
5601/// stopped being the answer. Twelve searches had found a better multiplier
5602/// eleven times and the twelfth had found that there was none, which is not a
5603/// result about `LMOVEM`, it is a result about a 512 slot table holding 282
5604/// names. Fifty five percent full is where linear probing starts to cost real
5605/// runs, and no multiplier gets around that because the runs are the load
5606/// factor and not the hash.
5607///
5608/// So the other half of the remedy this note has always named was taken and the
5609/// table doubled. At 1024 slots the multiplier that was already here goes to two
5610/// slots and forty two extra probes without being touched, which on its own
5611/// would have been enough. A search over the doubled table across four shards
5612/// and eighty million multipliers then found this one at **one** slot and twenty
5613/// eight, so no command is more than a single slot from where it wants to be,
5614/// which the table has never managed at any size. Fourteen names collide on the
5615/// key itself and no multiplier can separate them, so fourteen is the floor and
5616/// this is twice it, against a floor the 512 slot table never came within four
5617/// times of.
5618///
5619/// The cost is a kibibyte, and the thing it buys beyond today is room. The
5620/// `FT.*` and `TS.*` families are still to be written and both are large, and at
5621/// 27 percent full there is somewhere for them to go.
5622///
5623/// The `BF.*` family is the first of those to arrive and it took the table to
5624/// 296 names, where the doubled table's multiplier went to two slots and thirty
5625/// six extra probes. That is well inside what a lookup is allowed to cost, so
5626/// the search was run to see whether the single slot result had been luck at 285
5627/// names or was a property of the table at this load, and eight shards over a
5628/// hundred and sixty million multipliers found this one at one slot and thirty
5629/// three. Eleven more names, five more probes, and the worst is still a single
5630/// slot. None of the eleven collides on the key, so the floor moved by one for
5631/// an unrelated reason and stands at fifteen, which this is a shade over twice.
5632///
5633/// The `CF.*` family took the table to 310 names and thirty five extra probes,
5634/// two more than the bound allowed, with the worst still a single slot. The
5635/// thirteenth search was run over that and it is the second one that did not
5636/// replace the multiplier. Ten shards over one and a half billion multipliers
5637/// found nothing better than thirty six at one slot, which is worse than the one
5638/// already here, and another two billion with the single slot rule relaxed found
5639/// one at two slots and thirty one. Four fewer probes spread over three hundred
5640/// and ten lookups is not worth giving up the property that no command is ever
5641/// more than one slot from home, so this one stayed and the bound went up by two.
5642/// None of the fourteen new names collides on the key, so the floor is still
5643/// fifteen and the table is at a shade over twice it while carrying fourteen more
5644/// commands than when that was first true.
5645///
5646/// The `CMS.*` family took it to 316 names and thirty seven extra probes, with
5647/// the worst still one slot. No search was run this time. The one before it
5648/// covered three and a half billion multipliers against a table only six names
5649/// smaller and found nothing better that keeps every command within a slot, and
5650/// six names is not enough of a change to expect a different answer, so the
5651/// bound went up by two again. Only one of the six new names collides on the
5652/// key, which is `CMS.QUERY` against `CMS.MERGE`, so the floor is sixteen and
5653/// the table is still a shade over twice it.
5654///
5655/// The `TOPK.*` family took it to 323 names and forty two extra probes, with the
5656/// worst still one slot. A short search of four hundred thousand multipliers ran
5657/// against the new table and the best it turned up was two slots and forty eight,
5658/// worse on both counts, which is what the two big searches before it already
5659/// said, so this multiplier stayed and the bound went up by five. None of the
5660/// seven new names collides on the key, so the floor is still sixteen.
5661///
5662/// The `TDIGEST.*` family took it to 337 names and broke the bound properly: the
5663/// worst probe went to three slots, which is the first time since the table was
5664/// doubled that a command was further from home than a lookup is allowed to be.
5665/// Fourteen names is a lot to add to a family of sketch commands that all start
5666/// with the same two bytes, and the key is built out of the first two bytes, so
5667/// the whole family lands in a handful of key values before the multiply ever
5668/// sees them.
5669///
5670/// So the fifteenth search ran, and it said the same thing the tenth one did at
5671/// 282 names. Three and a half million multipliers against the 1024 slot table
5672/// found nothing better than two slots and fifty two extra probes, against the
5673/// fifty two this one already spends at three slots. That is the shape of a
5674/// table that is too full rather than a multiplier that is bad, and at 337
5675/// names in 1024 slots it is a third full, which is where the 512 slot table
5676/// was when it ran out as well. Doubling the table to 2048 and touching nothing
5677/// else takes this same multiplier to **one** slot and thirty four, so the
5678/// answer was a bigger table again and not a new constant.
5679///
5680/// The search then ran over the doubled table anyway, because that is what
5681/// happened last time and it found something worth having. Four and a half
5682/// million multipliers turned up this one at one slot and twenty two, twelve
5683/// fewer probes than the old multiplier spends in the same table, against a
5684/// floor of sixteen from the names that collide on the key itself. Twelve
5685/// probes over three hundred and thirty seven lookups is not much, but it is
5686/// free, it moves both numbers the right way, and it is exactly the trade the
5687/// doubling from 512 made, so it was taken. The old multiplier was
5688/// `0x3e8668c9760e09c9` and it served for thirteen searches.
5689///
5690/// The room this buys is the same room as last time and it is worth writing down
5691/// again: `FT.*` and `TS.*` are still to come and both are large, and at a sixth
5692/// full there is somewhere for them to go.
5693///
5694/// `TS.*` then arrived and the last three of it, the two joined reads and
5695/// `TS.READ`, broke the bound in a way no multiplier could fix. `TS.NRANGE` made
5696/// `ts.create`, `ts.incrby`, `ts.mrange` and `ts.nrange` four names sharing one
5697/// key: all nine bytes long, all starting `ts`, and all with the same fold of
5698/// the last byte against the middle one. Four names in a slot run costs the
5699/// fourth of them three probes wherever the run starts, so the worst probe went
5700/// to three and doubling the table again would not have moved it, because the
5701/// cost is in the key and not in how much room the key has to land in.
5702///
5703/// So the sixteenth search was a search for a key rather than for a multiplier.
5704/// Folding the second to last byte in as well separates all four of them, and it
5705/// separates enough else besides to take the floor from twenty colliding names
5706/// down to twelve, which is the fewest of the handful of folds tried. It is the
5707/// cheapest byte to add, too, because it sits next to the last byte that is
5708/// already being read. Then the multiplier search ran over the new key, three
5709/// hundred and twenty million of them across eight shards, and the best keeps
5710/// every command within one slot at eighteen extra probes over the whole table,
5711/// against a floor of twelve. That is the best either number has ever been here
5712/// while carrying the most names it has ever carried. The old multiplier was
5713/// `0x2f0cc21a638ae49d` and it served for one search.
5714const MIX: u64 = 0x5525_1c10_f29d_4c29;
5715
5716/// The four bytes the index is computed from: the length, the first two bytes,
5717/// and the last byte with the second to last and the middle folded into it, all
5718/// lower cased.
5719///
5720/// `None` for a name no command could be spelled as, which is decided on the
5721/// length before a byte is read.
5722///
5723/// Four bytes and not the whole name because the whole name has to be compared
5724/// at the end anyway, so the hash only has to be good enough to get to the right
5725/// slot, and reading less of the name is a shorter dependency chain in front of
5726/// the multiply. Names that agree on all four collide whatever the multiplier is
5727/// and probe once more, and the probe is the same compare the lookup was always
5728/// going to do. Over the 358 commands there are twelve such pairs and no group
5729/// larger than a pair, so twelve extra probes is the floor.
5730///
5731/// The middle byte is the part that was added last and it is worth saying why,
5732/// because for a long time the key was the length and the first two bytes and
5733/// the last and nothing else. That was fine while the groups that agreed on a
5734/// prefix were small: `setnx` with `setex`, `g.nadd` with `g.eadd`, `getset`
5735/// with `getbit`, `setbit` with `select`. The JSON group broke it, because every
5736/// name in it starts `js` and so every name in it was keyed on nothing but its
5737/// length and its last byte, and `json.arrlen`, `json.objlen` and `json.strlen`
5738/// agree on both. Three names in one slot run costs the third of them two probes
5739/// on its own, which leaves a multiplier no room anywhere else, and the number
5740/// families still to come are the same shape again. Folding in the middle byte
5741/// separates all three, and it separates `json.set` from `json.get` as well.
5742/// It costs one more load off a cache line the first two bytes already pulled
5743/// in, and the xor is on the same dependency chain as the shifts rather than in
5744/// front of them.
5745///
5746/// The second to last byte went in for the same reason a family later. `TS.*`
5747/// is the JSON shape again and worse: every name starts `ts`, so a nine byte
5748/// name is keyed on nothing but its length and the fold of its last byte against
5749/// its middle one, and `ts.create`, `ts.incrby`, `ts.mrange` and `ts.nrange` all
5750/// land on the same fold. Four in a run is three probes for the last of them
5751/// whatever the multiplier does, so the key had to carry more. The byte before
5752/// the last one is the cheapest one left, being on the cache line the last byte
5753/// already pulled in, and it separates all four of those and takes the floor
5754/// from twenty down to twelve besides.
5755///
5756/// `| 0x20` lower cases a letter and does not have to be told which bytes are
5757/// letters. It maps the two cases of a name to the same number, which is all
5758/// this needs, and every command name is letters. It has to be applied to each
5759/// of the three folded bytes separately, before the xor rather than after,
5760/// because `.` and `n` differ in the bit `| 0x20` sets and an xor of the raw
5761/// bytes would keep that difference alive.
5762///
5763/// On a three or four byte name the middle byte and the second to last byte are
5764/// the same byte and cancel each other out, which leaves the fold as the last
5765/// byte alone. That is not a loss, because on a name that short every byte the
5766/// fold could carry is already in the key somewhere else.
5767const fn key_of(name: &[u8]) -> Option<u32> {
5768    if name.len() < MIN_LEN || name.len() > MAX_LEN {
5769        return None;
5770    }
5771    let last = name.len() - 1;
5772    let mid = name.len() / 2;
5773    Some(
5774        name.len() as u32
5775            | ((name[0] | 0x20) as u32) << 8
5776            | ((name[1] | 0x20) as u32) << 16
5777            | (((name[last] | 0x20) ^ (name[last - 1] | 0x20) ^ (name[mid] | 0x20)) as u32) << 24,
5778    )
5779}
5780
5781/// Where a key wants to sit.
5782///
5783/// The shift leaves the top eleven bits of the product, which are the ones the
5784/// multiply mixed the most, and the mask is what makes that a slot number. Eleven
5785/// because the table has 2048 slots, so both numbers have to move together if
5786/// [`SLOTS`] ever does. It was ten while the table was half this size.
5787const fn slot_of(key: u32) -> usize {
5788    ((key as u64).wrapping_mul(MIX) >> 53) as usize & (SLOTS - 1)
5789}
5790
5791/// The index, built at compile time by inserting every command in table order.
5792///
5793/// Table order is rough order of how often a command is sent, and inserting in
5794/// that order means the hotter of two commands that want the same slot gets it
5795/// and the colder one probes, which is the right way round.
5796const INDEX: [u16; SLOTS] = index();
5797
5798const fn index() -> [u16; SLOTS] {
5799    let mut out = [FREE; SLOTS];
5800    let mut i = 0;
5801    while i < COMMANDS.len() {
5802        let key = match key_of(COMMANDS[i].name.as_bytes()) {
5803            Some(key) => key,
5804            None => panic!("a command name is outside MIN_LEN..=MAX_LEN"),
5805        };
5806        let mut at = slot_of(key);
5807        while out[at] != FREE {
5808            at = (at + 1) & (SLOTS - 1);
5809        }
5810        out[at] = i as u16;
5811        i += 1;
5812    }
5813    out
5814}
5815
5816/// The command called `name`, whatever case the client spelled it in.
5817///
5818/// This used to walk the whole table comparing lengths, and the cost of that was
5819/// not what it looked like. The table is written in rough order of how often a
5820/// command is sent, so `set` and `get` were the first two entries and cost one
5821/// compare, but `exists` is the hundred and forty ninth and `del` the hundred and
5822/// forty seventh, and every one of those compares was paid twice per command,
5823/// once to work out the key hash and once to dispatch.
5824///
5825/// Measured, that walk was 104 nanoseconds a command, which is more than a whole
5826/// `GET` costs end to end. `EXISTS` on a missing key ran at three and a half
5827/// times `GET` and almost none of the difference was the command: short
5828/// circuiting the lookup alone took it from 8.7 microseconds a batch of sixty
5829/// four to 2.0, and left it faster than `GET`, which it should be, because it
5830/// does less.
5831///
5832/// So this is one multiply and one load into two kibibytes, and then the same name
5833/// compare it always ended with. What it costs the hot commands is a multiply
5834/// they did not use to pay and a load that hits, and what it saves the rest is
5835/// the whole walk.
5836#[must_use]
5837pub fn lookup(name: &[u8]) -> Option<&'static Spec> {
5838    at(lookup_index(name))
5839}
5840
5841/// The same, answering with a position in the table rather than a reference.
5842///
5843/// This is where the lookup actually ends, because the index is what the slots
5844/// hold. It is here as its own function because a position fits in a `u16` and a
5845/// reference does not fit anywhere a framed command can carry it cheaply, so the
5846/// engine resolves a command's name once when it frames it and hands the number
5847/// on to both the key hash and the dispatcher.
5848///
5849/// `u16::MAX` is the answer for a name that is not a command, which is not a
5850/// special case anybody has to write down: the table is 254 entries, so [`at`]
5851/// hands back `None` for it the same way it would for any other number past the
5852/// end.
5853#[must_use]
5854pub fn lookup_index(name: &[u8]) -> u16 {
5855    let Some(key) = key_of(name) else {
5856        return FREE;
5857    };
5858    let mut at = slot_of(key);
5859    loop {
5860        let i = INDEX[at];
5861        if i == FREE {
5862            return FREE;
5863        }
5864        if COMMANDS[i as usize]
5865            .name
5866            .as_bytes()
5867            .eq_ignore_ascii_case(name)
5868        {
5869            return i;
5870        }
5871        at = (at + 1) & (SLOTS - 1);
5872    }
5873}
5874
5875/// The command at `i`, or `None` if there is none there.
5876///
5877/// The other half of [`lookup_index`], and the only thing that should ever be
5878/// handed one of its answers.
5879#[must_use]
5880pub fn at(i: u16) -> Option<&'static Spec> {
5881    COMMANDS.get(i as usize)
5882}
5883
5884/// How many commands there are.
5885///
5886/// The length of a counter array that has a row per command, which is the only
5887/// thing that wants this number.
5888#[must_use]
5889pub const fn count() -> usize {
5890    COMMANDS.len()
5891}
5892
5893/// Where in [`COMMANDS`] this spec is.
5894///
5895/// Every `&'static Spec` a caller can hold came out of [`lookup`] and therefore
5896/// points into that array, so its position is the distance from the front
5897/// measured in whole `Spec`s. That is arithmetic on two addresses and not a
5898/// search, which is the point: a per command counter has to be reachable from
5899/// the spec the dispatcher is already holding without walking the table a second
5900/// time.
5901///
5902/// A spec from somewhere else would answer nonsense, which is why this takes a
5903/// `&'static Spec` rather than a `&Spec`: the only `'static` ones are in the
5904/// table.
5905#[must_use]
5906pub fn index_of(spec: &'static Spec) -> usize {
5907    let front = COMMANDS.as_ptr().addr();
5908    let here = std::ptr::from_ref(spec).addr();
5909    (here - front) / size_of::<Spec>()
5910}
5911
5912/// The name of the command at `at`, which is [`index_of`] the other way round.
5913///
5914/// # Panics
5915///
5916/// If `at` is past the end of the table, which only a caller that made the index
5917/// up rather than getting it from [`index_of`] can manage.
5918#[must_use]
5919pub fn name_at(at: usize) -> &'static str {
5920    COMMANDS[at].name
5921}
5922
5923/// Whether `n` arguments, counting the name, satisfy this command's arity.
5924#[must_use]
5925pub fn arity_ok(spec: &Spec, n: usize) -> bool {
5926    let n = n as i32;
5927    if spec.arity >= 0 {
5928        n == spec.arity
5929    } else {
5930        n >= -spec.arity
5931    }
5932}
5933
5934#[cfg(test)]
5935mod tests {
5936    use super::*;
5937
5938    /// The name here is the name a client reads back, so it is spelled the way
5939    /// the server that registered it spelled it.
5940    ///
5941    /// That is lower case for everything the server itself registers and upper
5942    /// case for the two groups that come out of a module, so `COMMAND INFO vadd`
5943    /// answers `VADD` and `COMMAND INFO ft.create` answers `FT.CREATE`, and the
5944    /// arity errors quote them the same way. Nothing else in the table cares,
5945    /// because a lookup compares without regard to case and the index key folds
5946    /// the case out before it hashes.
5947    #[test]
5948    fn every_name_is_spelled_the_way_it_was_registered_and_appears_once() {
5949        let mut seen = std::collections::BTreeSet::new();
5950        for c in COMMANDS {
5951            let want = if c.group == "vector" || c.group == "search" {
5952                c.name.to_uppercase()
5953            } else {
5954                c.name.to_lowercase()
5955            };
5956            assert_eq!(c.name, want, "{} is spelled wrong for its group", c.name);
5957            assert!(seen.insert(c.name), "{} is in the table twice", c.name);
5958        }
5959    }
5960
5961    /// Every command's index is where the table actually holds it.
5962    ///
5963    /// Checked against the position a search finds, over the whole table rather
5964    /// than a sample, because the arithmetic is the thing being tested and an
5965    /// off by one in it would put every counter on the wrong command.
5966    #[test]
5967    fn a_spec_knows_where_it_is_in_the_table() {
5968        assert_eq!(count(), COMMANDS.len());
5969        for (want, spec) in COMMANDS.iter().enumerate() {
5970            assert_eq!(index_of(spec), want, "{} is at the wrong index", spec.name);
5971        }
5972        assert_eq!(
5973            index_of(lookup(b"get").unwrap()),
5974            index_of(lookup(b"GET").unwrap())
5975        );
5976    }
5977
5978    #[test]
5979    fn lookup_ignores_case_and_does_not_match_a_prefix() {
5980        assert_eq!(lookup(b"GET").unwrap().name, "get");
5981        assert_eq!(lookup(b"gEt").unwrap().name, "get");
5982        assert!(lookup(b"ge").is_none());
5983        assert!(lookup(b"gets").is_none());
5984    }
5985
5986    /// Every command is findable under its own name, in either case.
5987    ///
5988    /// The index is built at compile time from the table it sits beside, so what
5989    /// a test can still catch is a command that the build put somewhere the
5990    /// lookup does not walk past, which is what a probe that stopped early would
5991    /// look like.
5992    #[test]
5993    fn every_command_is_findable_by_its_own_name() {
5994        for spec in COMMANDS {
5995            let found = lookup(spec.name.as_bytes()).expect(spec.name);
5996            assert_eq!(
5997                index_of(found),
5998                index_of(spec),
5999                "{} found the wrong spec",
6000                spec.name
6001            );
6002            assert_eq!(
6003                lookup(spec.name.to_ascii_uppercase().as_bytes()).map(index_of),
6004                Some(index_of(spec)),
6005                "{} is not found in upper case",
6006                spec.name,
6007            );
6008        }
6009    }
6010
6011    /// A name that cannot be a command is answered before anything is compared.
6012    #[test]
6013    fn a_name_that_cannot_be_a_command_is_rejected_on_its_shape() {
6014        assert!(lookup(b"").is_none());
6015        assert!(key_of(b"").is_none());
6016        assert!(key_of(&[b'g'; 256]).is_none());
6017        assert!(lookup(&[b'g'; 256]).is_none());
6018        assert!(lookup(b"9et").is_none());
6019    }
6020
6021    /// The two cases of a name give the same key and different names do not.
6022    #[test]
6023    fn a_key_folds_the_case_and_nothing_else() {
6024        assert_eq!(key_of(b"get"), key_of(b"GET"));
6025        assert_eq!(key_of(b"get"), key_of(b"gEt"));
6026        assert_ne!(key_of(b"get"), key_of(b"set"), "other first byte");
6027        assert_ne!(key_of(b"get"), key_of(b"gxt"), "other second byte");
6028        assert_ne!(key_of(b"get"), key_of(b"gex"), "other last byte");
6029        assert_ne!(key_of(b"get"), key_of(b"gett"), "other length");
6030        assert_ne!(key_of(b"abcde"), key_of(b"abxde"), "other middle byte");
6031        assert_eq!(key_of(b"abcde"), key_of(b"ABCDE"), "middle byte folds too");
6032    }
6033
6034    /// The index is still worth having, which is a thing that can rot.
6035    ///
6036    /// The multiplier was searched for against the 191 commands that were in the
6037    /// table when it was written, and fifteen times since. Adding commands cannot
6038    /// make a lookup wrong, because a probe walks to an empty slot and every
6039    /// candidate has its name compared, but it can make one slow, and a slow
6040    /// lookup is exactly the thing this replaced. So the worst probe is written
6041    /// down here: if a command added later pushes it up, somebody searches for a
6042    /// new multiplier or a bigger table rather than finding out from a benchmark
6043    /// six months later. Both of those have now happened, and the note on
6044    /// [`MIX`] says which one worked when.
6045    ///
6046    /// The bound is two slots because that is what a lookup is allowed to cost,
6047    /// and the table is better than its bound: the multiplier in it keeps every
6048    /// command within one slot. The total is held at exactly what it measures so
6049    /// that a command which quietly spends the headroom shows up here.
6050    #[test]
6051    fn no_command_is_more_than_two_slots_from_where_it_wants_to_be() {
6052        let mut worst = 0;
6053        let mut total = 0;
6054        for spec in COMMANDS {
6055            let key = key_of(spec.name.as_bytes()).expect(spec.name);
6056            let home = slot_of(key);
6057            let mut at = home;
6058            let mut steps = 0;
6059            while INDEX[at] as usize != index_of(spec) {
6060                at = (at + 1) & (SLOTS - 1);
6061                steps += 1;
6062                assert!(steps < SLOTS, "{} is not in the index at all", spec.name);
6063            }
6064            worst = worst.max(steps);
6065            total += steps;
6066        }
6067        assert!(worst <= 2, "worst probe is {worst} slots");
6068        assert_eq!(
6069            worst, 1,
6070            "the multiplier stopped keeping every command close"
6071        );
6072        assert!(
6073            total <= 22,
6074            "{total} extra slots walked over the whole table"
6075        );
6076    }
6077
6078    /// The table has room to probe in, which is what stops the loop.
6079    #[test]
6080    fn the_index_is_not_full() {
6081        assert!(
6082            COMMANDS.len() < SLOTS,
6083            "the probe would never find an empty"
6084        );
6085        assert!(
6086            COMMANDS.len() < FREE as usize,
6087            "an index would collide with FREE"
6088        );
6089        let free = INDEX.iter().filter(|&&i| i == FREE).count();
6090        assert_eq!(free, SLOTS - COMMANDS.len());
6091    }
6092
6093    #[test]
6094    fn arity_counts_the_command_name() {
6095        let get = lookup(b"get").unwrap();
6096        assert!(!arity_ok(get, 1));
6097        assert!(arity_ok(get, 2));
6098        assert!(!arity_ok(get, 3));
6099
6100        // A negative arity is a minimum, which is how SET takes its options.
6101        let set = lookup(b"set").unwrap();
6102        assert!(!arity_ok(set, 2));
6103        assert!(arity_ok(set, 3));
6104        assert!(arity_ok(set, 9));
6105    }
6106
6107    /// A key spec that is wrong sends a cluster client to the wrong node, so
6108    /// the pair commands are worth stating twice.
6109    #[test]
6110    fn the_pair_commands_step_two_keys_at_a_time() {
6111        for name in [b"mset".as_slice(), b"msetnx"] {
6112            let c = lookup(name).unwrap();
6113            assert_eq!((c.first_key, c.last_key, c.step), (1, -1, 2));
6114        }
6115        let mget = lookup(b"mget").unwrap();
6116        assert_eq!((mget.first_key, mget.last_key, mget.step), (1, -1, 1));
6117        // MSETEX counts its keys in an argument, so there is no static spec
6118        // for them and a client has to ask with COMMAND GETKEYS.
6119        let msetex = lookup(b"msetex").unwrap();
6120        assert_eq!((msetex.first_key, msetex.last_key, msetex.step), (0, 0, 0));
6121        assert!(msetex.flags.contains(&"movablekeys"));
6122    }
6123}