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