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/// Read only and not counted as fast, which is every list read that walks.
94const READ_SLOW: &[&str] = &["readonly"];
95/// A write that is not counted as fast and does not allocate, which on the list
96/// side is `LREM` and `LTRIM` and nothing else.
97const WRITE_SLOW: &[&str] = &["write"];
98/// The list read side, for the two that answer without walking the elements.
99const AC_LIST_READ_FAST: &[&str] = &["@read", "@list", "@fast"];
100/// The list read side for the ones that walk.
101const AC_LIST_READ_SLOW: &[&str] = &["@read", "@list", "@slow"];
102/// The list write side, which is the pushes and the pops. Redis counts a push
103/// as fast even though it can split a chunk, because the split is amortised.
104const AC_LIST_WRITE_FAST: &[&str] = &["@write", "@list", "@fast"];
105/// The list write side for the ones whose cost is the length of the list.
106const AC_LIST_WRITE_SLOW: &[&str] = &["@write", "@list", "@slow"];
107/// The five that can wait, which carry a category of their own so that an ACL
108/// can say "this user may not park a connection" without naming five commands.
109const AC_LIST_WRITE_BLOCKING: &[&str] = &["@write", "@list", "@slow", "@blocking"];
110/// The sorted set read side, for the ones that answer without walking members.
111const AC_ZSET_READ_FAST: &[&str] = &["@read", "@sortedset", "@fast"];
112/// The sorted set read side for the ones that walk members.
113const AC_ZSET_READ_SLOW: &[&str] = &["@read", "@sortedset", "@slow"];
114/// The sorted set write side.
115const AC_ZSET_WRITE_FAST: &[&str] = &["@write", "@sortedset", "@fast"];
116/// The sorted set write side for the ones whose cost is the size of the window
117/// they touch, which is the removals and `ZRANGESTORE`.
118const AC_ZSET_WRITE_SLOW: &[&str] = &["@write", "@sortedset", "@slow"];
119/// The two sorted set pops that can wait, which Redis counts as fast because
120/// each of them takes one member.
121const AC_ZSET_BLOCKING_FAST: &[&str] = &["@write", "@sortedset", "@fast", "@blocking"];
122/// And `BZMPOP`, whose cost is the number of keys named and the count popped.
123const AC_ZSET_BLOCKING_SLOW: &[&str] = &["@write", "@sortedset", "@slow", "@blocking"];
124/// The array read side, for the ones whose cost is the number of indices named
125/// and not the size of the array.
126/// The geo read side. Redis counts none of these as fast, not even GEODIST,
127/// which is two probes and some arithmetic.
128const AC_GEO_READ: &[&str] = &["@read", "@geo", "@slow"];
129/// The geo write side, which is GEOADD and the four forms that can store.
130const AC_GEO_WRITE: &[&str] = &["@write", "@geo", "@slow"];
131/// The graph read side, for the ones that answer without walking the plane.
132const AC_GRAPH_READ_FAST: &[&str] = &["@read", "@graph", "@fast"];
133/// The graph read side for the ones that walk it.
134const AC_GRAPH_READ_SLOW: &[&str] = &["@read", "@graph", "@slow"];
135/// The graph write side, all of which are a probe and a run.
136const AC_GRAPH_WRITE_FAST: &[&str] = &["@write", "@graph", "@fast"];
137const AC_ARRAY_READ_FAST: &[&str] = &["@read", "@array", "@fast"];
138/// The array read side for `ARGETRANGE`, which answers once per position in the
139/// range and so costs the range rather than the population.
140const AC_ARRAY_READ_SLOW: &[&str] = &["@read", "@array", "@slow"];
141/// The array write side.
142const AC_ARRAY_WRITE_FAST: &[&str] = &["@write", "@array", "@fast"];
143/// The array write side for `ARDELRANGE`, the one array command Redis does not
144/// mark fast.
145const AC_ARRAY_WRITE_SLOW: &[&str] = &["@write", "@array", "@slow"];
146/// The stream read side, for the ones that answer without walking entries.
147const AC_STREAM_READ_FAST: &[&str] = &["@read", "@stream", "@fast"];
148/// The stream read side for the ranges, whose cost is what they return.
149const AC_STREAM_READ_SLOW: &[&str] = &["@read", "@stream", "@slow"];
150/// The stream write side, which is everything that appends, deletes or moves an
151/// entry between pending lists.
152const AC_STREAM_WRITE_FAST: &[&str] = &["@write", "@stream", "@fast"];
153/// The stream write side for `XTRIM`, whose cost is what it removes.
154const AC_STREAM_WRITE_SLOW: &[&str] = &["@write", "@stream", "@slow"];
155/// `XREAD`, which waits and does not write.
156const AC_STREAM_BLOCKING_READ: &[&str] = &["@read", "@stream", "@slow", "@blocking"];
157/// `XREADGROUP`, which waits and does write, since handing an entry to a
158/// consumer puts it on that consumer's pending list.
159const AC_STREAM_BLOCKING_WRITE: &[&str] = &["@write", "@stream", "@slow", "@blocking"];
160/// `XGROUP` and `XINFO`, whose keys are on the subcommand and whose categories
161/// are therefore only the container's.
162const AC_STREAM_CONTAINER: &[&str] = &["@slow"];
163/// The two stream reads, whose keys come after `STREAMS` and are half of what
164/// follows it, so nothing positional can find them.
165const READ_BLOCKING_MOVABLE: &[&str] = &["readonly", "blocking", "movablekeys"];
166/// The same for `XREADGROUP`, which is a write.
167const WRITE_BLOCKING_MOVABLE: &[&str] = &["write", "blocking", "movablekeys"];
168/// Read only and not counted as fast, for a command whose keys are counted
169/// rather than positioned, so a client has to read the key specs to route it.
170const READ_MOVABLE: &[&str] = &["readonly", "movablekeys"];
171/// The same for a write, which is the three store forms.
172const WRITE_MOVABLE: &[&str] = &["write", "denyoom", "movablekeys"];
173/// `MIGRATE`, which is the one movable key write that is not `denyoom`.
174///
175/// It only ever frees here, since the local key goes away and nothing arrives,
176/// so a server with no room left can still migrate its way out of trouble. That
177/// is the same reasoning that leaves the flag off `DEL`.
178const MIGRATE_FLAGS: &[&str] = &["write", "movablekeys"];
179/// The connection commands' categories.
180const AC_CONN: &[&str] = &["@fast", "@connection"];
181/// The keyspace read side, which is `EXISTS` and `TYPE`.
182const AC_KEY_READ: &[&str] = &["@keyspace", "@read", "@fast"];
183/// The keyspace reads that walk something, which is `SCAN` and `RANDOMKEY`.
184const AC_KEY_READ_SLOW: &[&str] = &["@keyspace", "@read", "@slow"];
185/// And `KEYS`, which is the same walk without a bound on it and is the one read
186/// in this group Redis calls dangerous.
187const AC_KEY_READ_ALL: &[&str] = &["@keyspace", "@read", "@slow", "@dangerous"];
188/// The keyspace writes that are allowed to cost what the value costs. `DEL`
189/// frees on the spot and `COPY` clones a body, and `RENAME` is in here with
190/// them even though it moves thirteen bytes, because Redis says slow for it and
191/// this list is Redis's list rather than ours.
192const AC_KEY_WRITE_SLOW: &[&str] = &["@keyspace", "@write", "@slow"];
193/// `UNLINK`, which Redis does count as fast because it does not, and the
194/// expiry writers, which move a deadline and never touch a value.
195const AC_KEY_WRITE_FAST: &[&str] = &["@keyspace", "@write", "@fast"];
196/// The two that empty a database, which are in the dangerous category.
197const AC_KEY_FLUSH: &[&str] = &["@keyspace", "@write", "@slow", "@dangerous"];
198/// `SWAPDB`, which is fast and dangerous at the same time. It is two pointer
199/// writes and it changes what every connected client is looking at, so Redis
200/// puts it in `@fast` and in `@dangerous` and both are right.
201const AC_SWAPDB: &[&str] = &["@keyspace", "@write", "@fast", "@dangerous"];
202/// `RESTORE`, which is dangerous for a reason worth saying out loud: it is the
203/// one command that takes bytes from a client and turns them into a value
204/// without any command ever having built it. `DUMP` is only `@read`, because
205/// reading a value out is no more than reading it.
206const AC_RESTORE: &[&str] = &["@keyspace", "@write", "@slow", "@dangerous"];
207/// `WAIT` and `WAITAOF`, which are the two commands that block on something
208/// that is not a key. They are not in `@keyspace` at all, because they name no
209/// key and read nothing, and they carry `@blocking` for the same reason the
210/// five list commands do.
211const AC_WAIT: &[&str] = &["@slow", "@blocking", "@connection"];
212/// `SORT`, which names three type categories because it takes any of the three
213/// and a write because of `STORE`. Redis leaves `@keyspace` off both of these
214/// even though the command lives in that group, and this list is Redis's.
215const AC_SORT_WRITE: &[&str] = &[
216    "@write",
217    "@set",
218    "@sortedset",
219    "@list",
220    "@slow",
221    "@dangerous",
222];
223/// `SORT_RO`, which is the same list with the write turned into a read.
224const AC_SORT_READ: &[&str] = &[
225    "@read",
226    "@set",
227    "@sortedset",
228    "@list",
229    "@slow",
230    "@dangerous",
231];
232
233/// Every command this server answers, in the order the groups ship.
234pub static COMMANDS: &[Spec] = &[
235    // ------------------------------------------------------------- strings
236    Spec {
237        name: "set",
238        arity: -3,
239        flags: WRITE_OOM,
240        first_key: 1,
241        last_key: 1,
242        step: 1,
243        acl: AC_WRITE_SLOW,
244        since: "1.0.0",
245        complexity: "O(1)",
246        summary: "Set a key to a string value, whatever it held before.",
247        group: "string",
248    },
249    Spec {
250        name: "get",
251        arity: 2,
252        flags: READ_FAST,
253        first_key: 1,
254        last_key: 1,
255        step: 1,
256        acl: AC_READ_FAST,
257        since: "1.0.0",
258        complexity: "O(1)",
259        summary: "The string value of a key.",
260        group: "string",
261    },
262    Spec {
263        name: "getset",
264        arity: 3,
265        flags: WRITE_FAST_OOM,
266        first_key: 1,
267        last_key: 1,
268        step: 1,
269        acl: AC_WRITE_FAST,
270        since: "1.0.0",
271        complexity: "O(1)",
272        summary: "Set a key and hand back what it held.",
273        group: "string",
274    },
275    Spec {
276        name: "getdel",
277        arity: 2,
278        flags: &["write", "fast"],
279        first_key: 1,
280        last_key: 1,
281        step: 1,
282        acl: AC_WRITE_FAST,
283        since: "6.2.0",
284        complexity: "O(1)",
285        summary: "Read a key and delete it in the same step.",
286        group: "string",
287    },
288    Spec {
289        name: "getex",
290        arity: -2,
291        flags: &["write", "fast"],
292        first_key: 1,
293        last_key: 1,
294        step: 1,
295        acl: AC_WRITE_FAST,
296        since: "6.2.0",
297        complexity: "O(1)",
298        summary: "Read a key and change its deadline in the same step.",
299        group: "string",
300    },
301    Spec {
302        name: "setnx",
303        arity: 3,
304        flags: WRITE_FAST_OOM,
305        first_key: 1,
306        last_key: 1,
307        step: 1,
308        acl: AC_WRITE_FAST,
309        since: "1.0.0",
310        complexity: "O(1)",
311        summary: "Set a key only if it is not there.",
312        group: "string",
313    },
314    Spec {
315        name: "setex",
316        arity: 4,
317        flags: WRITE_OOM,
318        first_key: 1,
319        last_key: 1,
320        step: 1,
321        acl: AC_WRITE_SLOW,
322        since: "2.0.0",
323        complexity: "O(1)",
324        summary: "Set a key and give it a deadline in seconds.",
325        group: "string",
326    },
327    Spec {
328        name: "psetex",
329        arity: 4,
330        flags: WRITE_OOM,
331        first_key: 1,
332        last_key: 1,
333        step: 1,
334        acl: AC_WRITE_SLOW,
335        since: "2.6.0",
336        complexity: "O(1)",
337        summary: "Set a key and give it a deadline in milliseconds.",
338        group: "string",
339    },
340    Spec {
341        name: "mset",
342        arity: -3,
343        flags: WRITE_OOM,
344        first_key: 1,
345        last_key: -1,
346        step: 2,
347        acl: AC_WRITE_SLOW,
348        since: "1.0.1",
349        complexity: "O(N) with N the number of keys",
350        summary: "Set several keys, all of them or none.",
351        group: "string",
352    },
353    Spec {
354        name: "msetnx",
355        arity: -3,
356        flags: WRITE_OOM,
357        first_key: 1,
358        last_key: -1,
359        step: 2,
360        acl: AC_WRITE_SLOW,
361        since: "1.0.1",
362        complexity: "O(N) with N the number of keys",
363        summary: "Set several keys only if none of them are there.",
364        group: "string",
365    },
366    Spec {
367        name: "mget",
368        arity: -2,
369        flags: READ_FAST,
370        first_key: 1,
371        last_key: -1,
372        step: 1,
373        acl: AC_READ_FAST,
374        since: "1.0.0",
375        complexity: "O(N) with N the number of keys",
376        summary: "The values of several keys, in the order asked for.",
377        group: "string",
378    },
379    Spec {
380        name: "append",
381        arity: 3,
382        flags: WRITE_FAST_OOM,
383        first_key: 1,
384        last_key: 1,
385        step: 1,
386        acl: AC_WRITE_FAST,
387        since: "2.0.0",
388        complexity: "O(M) with M the length of the value being appended",
389        summary: "Add to the end of a string, creating it if it is not there.",
390        group: "string",
391    },
392    Spec {
393        name: "strlen",
394        arity: 2,
395        flags: READ_FAST,
396        first_key: 1,
397        last_key: 1,
398        step: 1,
399        acl: AC_READ_FAST,
400        since: "2.2.0",
401        complexity: "O(1)",
402        summary: "How long a string value is, without reading it.",
403        group: "string",
404    },
405    Spec {
406        name: "setrange",
407        arity: 4,
408        flags: WRITE_OOM,
409        first_key: 1,
410        last_key: 1,
411        step: 1,
412        acl: AC_WRITE_SLOW,
413        since: "2.2.0",
414        complexity: "O(M) with M the length of the replacement",
415        summary: "Overwrite part of a string at an offset, zero filling the gap.",
416        group: "string",
417    },
418    Spec {
419        name: "getrange",
420        arity: 4,
421        flags: &["readonly"],
422        first_key: 1,
423        last_key: 1,
424        step: 1,
425        acl: AC_READ_SLOW,
426        since: "2.4.0",
427        complexity: "O(N) with N the length of the answer",
428        summary: "Part of a string, by an inclusive range that may count backwards.",
429        group: "string",
430    },
431    Spec {
432        name: "substr",
433        arity: 4,
434        flags: &["readonly"],
435        first_key: 1,
436        last_key: 1,
437        step: 1,
438        acl: AC_READ_SLOW,
439        since: "1.0.0",
440        complexity: "O(N) with N the length of the answer",
441        summary: "GETRANGE under the name it had before 2.4.",
442        group: "string",
443    },
444    Spec {
445        name: "incr",
446        arity: 2,
447        flags: WRITE_FAST_OOM,
448        first_key: 1,
449        last_key: 1,
450        step: 1,
451        acl: AC_WRITE_FAST,
452        since: "1.0.0",
453        complexity: "O(1)",
454        summary: "Add one, starting from zero if the key is not there.",
455        group: "string",
456    },
457    Spec {
458        name: "decr",
459        arity: 2,
460        flags: WRITE_FAST_OOM,
461        first_key: 1,
462        last_key: 1,
463        step: 1,
464        acl: AC_WRITE_FAST,
465        since: "1.0.0",
466        complexity: "O(1)",
467        summary: "Take one away, starting from zero if the key is not there.",
468        group: "string",
469    },
470    Spec {
471        name: "incrby",
472        arity: 3,
473        flags: WRITE_FAST_OOM,
474        first_key: 1,
475        last_key: 1,
476        step: 1,
477        acl: AC_WRITE_FAST,
478        since: "1.0.0",
479        complexity: "O(1)",
480        summary: "Add a number, starting from zero if the key is not there.",
481        group: "string",
482    },
483    Spec {
484        name: "decrby",
485        arity: 3,
486        flags: WRITE_FAST_OOM,
487        first_key: 1,
488        last_key: 1,
489        step: 1,
490        acl: AC_WRITE_FAST,
491        since: "1.0.0",
492        complexity: "O(1)",
493        summary: "Take a number away, starting from zero if the key is not there.",
494        group: "string",
495    },
496    Spec {
497        name: "incrbyfloat",
498        arity: 3,
499        flags: WRITE_FAST_OOM,
500        first_key: 1,
501        last_key: 1,
502        step: 1,
503        acl: AC_WRITE_FAST,
504        since: "2.6.0",
505        complexity: "O(1)",
506        summary: "Add a float, starting from zero if the key is not there.",
507        group: "string",
508    },
509    Spec {
510        name: "lcs",
511        arity: -3,
512        flags: &["readonly"],
513        first_key: 1,
514        last_key: 2,
515        step: 1,
516        acl: AC_READ_SLOW,
517        since: "7.0.0",
518        complexity: "O(N*M) with N and M the lengths of the two values",
519        summary: "The longest subsequence two string values have in common.",
520        group: "string",
521    },
522    Spec {
523        name: "msetex",
524        arity: -4,
525        flags: &["write", "denyoom", "movablekeys"],
526        first_key: 0,
527        last_key: 0,
528        step: 0,
529        acl: AC_WRITE_SLOW,
530        since: "8.4.0",
531        complexity: "O(N) with N the number of keys",
532        summary: "Set several keys with one deadline and one condition over all of them.",
533        group: "string",
534    },
535    Spec {
536        name: "delex",
537        arity: -2,
538        flags: &["write", "fast"],
539        first_key: 1,
540        last_key: 1,
541        step: 1,
542        acl: AC_WRITE_FAST,
543        since: "8.4.0",
544        complexity: "O(1) by value, O(N) by digest",
545        summary: "Delete a key only if it still holds what the caller thinks.",
546        group: "string",
547    },
548    Spec {
549        name: "digest",
550        arity: 2,
551        flags: READ_FAST,
552        first_key: 1,
553        last_key: 1,
554        step: 1,
555        acl: AC_READ_FAST,
556        since: "8.4.0",
557        complexity: "O(N) with N the length of the value",
558        summary: "The XXH3 of a string value, as sixteen hex characters.",
559        group: "string",
560    },
561    Spec {
562        name: "increx",
563        arity: -2,
564        flags: WRITE_FAST_OOM,
565        first_key: 1,
566        last_key: 1,
567        step: 1,
568        acl: AC_WRITE_FAST,
569        since: "8.8.0",
570        complexity: "O(1)",
571        summary: "Count, with a bound, a saturation policy and a deadline.",
572        group: "string",
573    },
574    // -------------------------------------------------------------- bitmaps
575    Spec {
576        name: "setbit",
577        arity: 4,
578        flags: WRITE_OOM,
579        first_key: 1,
580        last_key: 1,
581        step: 1,
582        acl: AC_BIT_WRITE,
583        since: "2.2.0",
584        complexity: "O(1)",
585        summary: "Set one bit of a string, growing it to reach the offset.",
586        group: "bitmap",
587    },
588    Spec {
589        name: "getbit",
590        arity: 3,
591        flags: READ_FAST,
592        first_key: 1,
593        last_key: 1,
594        step: 1,
595        acl: AC_BIT_READ_FAST,
596        since: "2.2.0",
597        complexity: "O(1)",
598        summary: "Read one bit of a string, or nought past its end.",
599        group: "bitmap",
600    },
601    Spec {
602        name: "bitcount",
603        arity: -2,
604        flags: &["readonly"],
605        first_key: 1,
606        last_key: 1,
607        step: 1,
608        acl: AC_BIT_READ,
609        since: "2.6.0",
610        complexity: "O(N)",
611        summary: "Count the set bits of a string, or of a range of it.",
612        group: "bitmap",
613    },
614    Spec {
615        name: "bitpos",
616        arity: -3,
617        flags: &["readonly"],
618        first_key: 1,
619        last_key: 1,
620        step: 1,
621        acl: AC_BIT_READ,
622        since: "2.8.7",
623        complexity: "O(N)",
624        summary: "Find the first bit set to one or nought in a string.",
625        group: "bitmap",
626    },
627    Spec {
628        name: "bitop",
629        arity: -4,
630        flags: WRITE_OOM,
631        first_key: 2,
632        last_key: -1,
633        step: 1,
634        acl: AC_BIT_WRITE,
635        since: "2.6.0",
636        complexity: "O(N) with N the length of the longest source",
637        summary: "Combine strings bit by bit and store the result.",
638        group: "bitmap",
639    },
640    Spec {
641        name: "bitfield",
642        arity: -2,
643        flags: WRITE_OOM,
644        first_key: 1,
645        last_key: 1,
646        step: 1,
647        acl: AC_BIT_WRITE,
648        since: "3.2.0",
649        complexity: "O(1) per subcommand",
650        summary: "Read and write packed integer fields inside a string.",
651        group: "bitmap",
652    },
653    Spec {
654        name: "bitfield_ro",
655        arity: -2,
656        flags: READ_FAST,
657        first_key: 1,
658        last_key: 1,
659        step: 1,
660        acl: AC_BIT_READ_FAST,
661        since: "6.0.0",
662        complexity: "O(1) per subcommand",
663        summary: "The read only half of BITFIELD, for a replica to answer.",
664        group: "bitmap",
665    },
666    // --------------------------------------------------------- hyperloglogs
667    Spec {
668        name: "pfadd",
669        arity: -2,
670        flags: WRITE_OOM,
671        first_key: 1,
672        last_key: 1,
673        step: 1,
674        acl: AC_HLL_WRITE_FAST,
675        since: "2.8.9",
676        complexity: "O(1) an element",
677        summary: "Add elements to a sketch, answering whether it changed.",
678        group: "hyperloglog",
679    },
680    Spec {
681        name: "pfcount",
682        arity: -2,
683        flags: &["readonly"],
684        first_key: 1,
685        last_key: -1,
686        step: 1,
687        acl: AC_HLL_READ,
688        since: "2.8.9",
689        complexity: "O(1) for one key, O(N) for N of them",
690        summary: "Estimate how many distinct elements the sketches hold.",
691        group: "hyperloglog",
692    },
693    Spec {
694        name: "pfmerge",
695        arity: -2,
696        flags: WRITE_OOM,
697        first_key: 1,
698        last_key: -1,
699        step: 1,
700        acl: AC_HLL_WRITE,
701        since: "2.8.9",
702        complexity: "O(N) in the number of sketches",
703        summary: "Merge sketches into the first one, which is a union.",
704        group: "hyperloglog",
705    },
706    Spec {
707        name: "pfdebug",
708        arity: 3,
709        flags: WRITE_OOM_ADMIN,
710        first_key: 2,
711        last_key: 2,
712        step: 1,
713        acl: AC_HLL_ADMIN,
714        since: "2.8.9",
715        complexity: "O(N)",
716        summary: "Look inside a sketch, and in one case convert it.",
717        group: "hyperloglog",
718    },
719    Spec {
720        name: "pfselftest",
721        arity: 1,
722        flags: &["admin"],
723        first_key: 0,
724        last_key: 0,
725        step: 0,
726        acl: AC_HLL_ADMIN,
727        since: "2.8.9",
728        complexity: "O(1)",
729        summary: "Check the sketch code, which our tests do at build time.",
730        group: "hyperloglog",
731    },
732    // ----------------------------------------------------------------- sets
733    Spec {
734        name: "sadd",
735        arity: -3,
736        flags: WRITE_FAST_OOM,
737        first_key: 1,
738        last_key: 1,
739        step: 1,
740        acl: AC_SET_WRITE_FAST,
741        since: "1.0.0",
742        complexity: "O(N) with N the number of members being added",
743        summary: "Add members to a set, creating it if it is not there.",
744        group: "set",
745    },
746    Spec {
747        name: "srem",
748        arity: -3,
749        flags: WRITE_FAST,
750        first_key: 1,
751        last_key: 1,
752        step: 1,
753        acl: AC_SET_WRITE_FAST,
754        since: "1.0.0",
755        complexity: "O(N) with N the number of members being removed",
756        summary: "Take members out of a set, deleting the key if none are left.",
757        group: "set",
758    },
759    Spec {
760        name: "scard",
761        arity: 2,
762        flags: READ_FAST,
763        first_key: 1,
764        last_key: 1,
765        step: 1,
766        acl: AC_SET_READ_FAST,
767        since: "1.0.0",
768        complexity: "O(1)",
769        summary: "How many members a set has.",
770        group: "set",
771    },
772    Spec {
773        name: "sismember",
774        arity: 3,
775        flags: READ_FAST,
776        first_key: 1,
777        last_key: 1,
778        step: 1,
779        acl: AC_SET_READ_FAST,
780        since: "1.0.0",
781        complexity: "O(1)",
782        summary: "Whether a member is in a set.",
783        group: "set",
784    },
785    Spec {
786        name: "smismember",
787        arity: -3,
788        flags: READ_FAST,
789        first_key: 1,
790        last_key: 1,
791        step: 1,
792        acl: AC_SET_READ_FAST,
793        since: "6.2.0",
794        complexity: "O(N) with N the number of members being asked about",
795        summary: "Whether each of several members is in a set, in the order asked.",
796        group: "set",
797    },
798    Spec {
799        name: "smembers",
800        arity: 2,
801        flags: &["readonly"],
802        first_key: 1,
803        last_key: 1,
804        step: 1,
805        acl: AC_SET_READ_SLOW,
806        since: "1.0.0",
807        complexity: "O(N) with N the size of the set",
808        summary: "Every member of a set.",
809        group: "set",
810    },
811    Spec {
812        name: "spop",
813        arity: -2,
814        flags: WRITE_FAST,
815        first_key: 1,
816        last_key: 1,
817        step: 1,
818        acl: AC_SET_WRITE_FAST,
819        since: "1.0.0",
820        complexity: "O(1) without a count, O(N) with one",
821        summary: "Take members out of a set at random and hand them back.",
822        group: "set",
823    },
824    Spec {
825        name: "srandmember",
826        arity: -2,
827        flags: &["readonly"],
828        first_key: 1,
829        last_key: 1,
830        step: 1,
831        acl: AC_SET_READ_SLOW,
832        since: "1.0.0",
833        complexity: "O(1) without a count, O(N) with one",
834        summary: "Members of a set at random, leaving the set as it was.",
835        group: "set",
836    },
837    Spec {
838        name: "smove",
839        arity: 4,
840        flags: WRITE_FAST,
841        first_key: 1,
842        last_key: 2,
843        step: 1,
844        acl: AC_SET_WRITE_FAST,
845        since: "1.0.0",
846        complexity: "O(1)",
847        summary: "Move one member from one set to another.",
848        group: "set",
849    },
850    Spec {
851        name: "sscan",
852        arity: -3,
853        flags: &["readonly"],
854        first_key: 1,
855        last_key: 1,
856        step: 1,
857        acl: AC_SET_READ_SLOW,
858        since: "2.8.0",
859        complexity: "O(1) a call, O(N) for a whole iteration",
860        summary: "Walk part of a set and say where to carry on from.",
861        group: "set",
862    },
863    Spec {
864        name: "sinter",
865        arity: -2,
866        flags: &["readonly"],
867        first_key: 1,
868        last_key: -1,
869        step: 1,
870        acl: AC_SET_READ_SLOW,
871        since: "1.0.0",
872        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
873        summary: "The members every one of these sets has.",
874        group: "set",
875    },
876    Spec {
877        name: "sintercard",
878        arity: -3,
879        // The only set command whose keys are counted rather than positioned,
880        // so the legacy key range cannot describe it and Redis reports zeroes
881        // in these three fields too. A client that wants the keys reads the key
882        // specs, which is what the count is for, and movablekeys is how it is
883        // told to go and read them.
884        flags: READ_MOVABLE,
885        first_key: 0,
886        last_key: 0,
887        step: 0,
888        acl: AC_SET_READ_SLOW,
889        since: "7.0.0",
890        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
891        summary: "How many members every one of these sets has, up to a limit.",
892        group: "set",
893    },
894    Spec {
895        name: "sinterstore",
896        arity: -3,
897        flags: WRITE_OOM,
898        first_key: 1,
899        last_key: -1,
900        step: 1,
901        acl: AC_SET_WRITE_SLOW,
902        since: "1.0.0",
903        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
904        summary: "Store the members every one of these sets has.",
905        group: "set",
906    },
907    Spec {
908        name: "sunion",
909        arity: -2,
910        flags: &["readonly"],
911        first_key: 1,
912        last_key: -1,
913        step: 1,
914        acl: AC_SET_READ_SLOW,
915        since: "1.0.0",
916        complexity: "O(N) in the total number of members",
917        summary: "The members any of these sets has, each once.",
918        group: "set",
919    },
920    Spec {
921        name: "sunionstore",
922        arity: -3,
923        flags: WRITE_OOM,
924        first_key: 1,
925        last_key: -1,
926        step: 1,
927        acl: AC_SET_WRITE_SLOW,
928        since: "1.0.0",
929        complexity: "O(N) in the total number of members",
930        summary: "Store the members any of these sets has.",
931        group: "set",
932    },
933    Spec {
934        name: "sdiff",
935        arity: -2,
936        flags: &["readonly"],
937        first_key: 1,
938        last_key: -1,
939        step: 1,
940        acl: AC_SET_READ_SLOW,
941        since: "1.0.0",
942        complexity: "O(N) in the total number of members",
943        summary: "The members of the first set that no later set has.",
944        group: "set",
945    },
946    Spec {
947        name: "sdiffstore",
948        arity: -3,
949        flags: WRITE_OOM,
950        first_key: 1,
951        last_key: -1,
952        step: 1,
953        acl: AC_SET_WRITE_SLOW,
954        since: "1.0.0",
955        complexity: "O(N) in the total number of members",
956        summary: "Store the members of the first set that no later set has.",
957        group: "set",
958    },
959    // -------------------------------------------------------------- hashes
960    Spec {
961        name: "hset",
962        arity: -4,
963        flags: WRITE_FAST_OOM,
964        first_key: 1,
965        last_key: 1,
966        step: 1,
967        acl: AC_HASH_WRITE_FAST,
968        since: "2.0.0",
969        complexity: "O(N) with N the number of pairs being written",
970        summary: "Write fields into a hash, creating it if it is not there.",
971        group: "hash",
972    },
973    Spec {
974        name: "hsetnx",
975        arity: 4,
976        flags: WRITE_FAST_OOM,
977        first_key: 1,
978        last_key: 1,
979        step: 1,
980        acl: AC_HASH_WRITE_FAST,
981        since: "2.0.0",
982        complexity: "O(1)",
983        summary: "Write a field only if the hash does not have it already.",
984        group: "hash",
985    },
986    // Deprecated since 4.0 and still sent by a great deal of code, so it is
987    // here rather than left out. It is HSET with an OK instead of a count.
988    Spec {
989        name: "hmset",
990        arity: -4,
991        flags: WRITE_FAST_OOM,
992        first_key: 1,
993        last_key: 1,
994        step: 1,
995        acl: AC_HASH_WRITE_FAST,
996        since: "2.0.0",
997        complexity: "O(N) with N the number of pairs being written",
998        summary: "Write fields into a hash and answer OK. Use HSET.",
999        group: "hash",
1000    },
1001    Spec {
1002        name: "hget",
1003        arity: 3,
1004        flags: READ_FAST,
1005        first_key: 1,
1006        last_key: 1,
1007        step: 1,
1008        acl: AC_HASH_READ_FAST,
1009        since: "2.0.0",
1010        complexity: "O(1)",
1011        summary: "The value of one field of a hash.",
1012        group: "hash",
1013    },
1014    Spec {
1015        name: "hmget",
1016        arity: -3,
1017        flags: READ_FAST,
1018        first_key: 1,
1019        last_key: 1,
1020        step: 1,
1021        acl: AC_HASH_READ_FAST,
1022        since: "2.0.0",
1023        complexity: "O(N) with N the number of fields asked for",
1024        summary: "The values of several fields, one reply entry each.",
1025        group: "hash",
1026    },
1027    Spec {
1028        name: "hdel",
1029        arity: -3,
1030        flags: WRITE_FAST,
1031        first_key: 1,
1032        last_key: 1,
1033        step: 1,
1034        acl: AC_HASH_WRITE_FAST,
1035        since: "2.0.0",
1036        complexity: "O(N) with N the number of fields being removed",
1037        summary: "Take fields out of a hash, deleting the key if none are left.",
1038        group: "hash",
1039    },
1040    Spec {
1041        name: "hlen",
1042        arity: 2,
1043        flags: READ_FAST,
1044        first_key: 1,
1045        last_key: 1,
1046        step: 1,
1047        acl: AC_HASH_READ_FAST,
1048        since: "2.0.0",
1049        complexity: "O(1)",
1050        summary: "How many fields a hash has.",
1051        group: "hash",
1052    },
1053    Spec {
1054        name: "hexists",
1055        arity: 3,
1056        flags: READ_FAST,
1057        first_key: 1,
1058        last_key: 1,
1059        step: 1,
1060        acl: AC_HASH_READ_FAST,
1061        since: "2.0.0",
1062        complexity: "O(1)",
1063        summary: "Whether a hash has a field.",
1064        group: "hash",
1065    },
1066    Spec {
1067        name: "hstrlen",
1068        arity: 3,
1069        flags: READ_FAST,
1070        first_key: 1,
1071        last_key: 1,
1072        step: 1,
1073        acl: AC_HASH_READ_FAST,
1074        since: "3.2.0",
1075        complexity: "O(1)",
1076        summary: "How many bytes a field's value is, without sending it.",
1077        group: "hash",
1078    },
1079    Spec {
1080        name: "hgetall",
1081        arity: 2,
1082        flags: &["readonly"],
1083        first_key: 1,
1084        last_key: 1,
1085        step: 1,
1086        acl: AC_HASH_READ_SLOW,
1087        since: "2.0.0",
1088        complexity: "O(N) in the size of the hash",
1089        summary: "Every field and value, as a map on RESP3.",
1090        group: "hash",
1091    },
1092    Spec {
1093        name: "hkeys",
1094        arity: 2,
1095        flags: &["readonly"],
1096        first_key: 1,
1097        last_key: 1,
1098        step: 1,
1099        acl: AC_HASH_READ_SLOW,
1100        since: "2.0.0",
1101        complexity: "O(N) in the size of the hash",
1102        summary: "Every field of a hash.",
1103        group: "hash",
1104    },
1105    Spec {
1106        name: "hvals",
1107        arity: 2,
1108        flags: &["readonly"],
1109        first_key: 1,
1110        last_key: 1,
1111        step: 1,
1112        acl: AC_HASH_READ_SLOW,
1113        since: "2.0.0",
1114        complexity: "O(N) in the size of the hash",
1115        summary: "Every value of a hash.",
1116        group: "hash",
1117    },
1118    Spec {
1119        name: "hincrby",
1120        arity: 4,
1121        flags: WRITE_FAST_OOM,
1122        first_key: 1,
1123        last_key: 1,
1124        step: 1,
1125        acl: AC_HASH_WRITE_FAST,
1126        since: "2.0.0",
1127        complexity: "O(1)",
1128        summary: "Add an integer to a field, treating a missing one as zero.",
1129        group: "hash",
1130    },
1131    Spec {
1132        name: "hincrbyfloat",
1133        arity: 4,
1134        flags: WRITE_FAST_OOM,
1135        first_key: 1,
1136        last_key: 1,
1137        step: 1,
1138        acl: AC_HASH_WRITE_FAST,
1139        since: "2.6.0",
1140        complexity: "O(1)",
1141        summary: "Add a float to a field, treating a missing one as zero.",
1142        group: "hash",
1143    },
1144    Spec {
1145        name: "hrandfield",
1146        arity: -2,
1147        flags: &["readonly"],
1148        first_key: 1,
1149        last_key: 1,
1150        step: 1,
1151        acl: AC_HASH_READ_SLOW,
1152        since: "6.2.0",
1153        complexity: "O(1) without a count, O(N) with one",
1154        summary: "Fields of a hash at random, leaving the hash as it was.",
1155        group: "hash",
1156    },
1157    Spec {
1158        name: "hscan",
1159        arity: -3,
1160        flags: &["readonly"],
1161        first_key: 1,
1162        last_key: 1,
1163        step: 1,
1164        acl: AC_HASH_READ_SLOW,
1165        since: "2.8.0",
1166        complexity: "O(1) a call, O(N) for a whole iteration",
1167        summary: "Walk part of a hash and say where to carry on from.",
1168        group: "hash",
1169    },
1170    Spec {
1171        name: "hexpire",
1172        arity: -6,
1173        flags: WRITE_FAST,
1174        first_key: 1,
1175        last_key: 1,
1176        step: 1,
1177        acl: AC_HASH_WRITE_FAST,
1178        since: "7.4.0",
1179        complexity: "O(N) with N the number of fields named",
1180        summary: "Put a deadline in seconds on hash fields.",
1181        group: "hash",
1182    },
1183    Spec {
1184        name: "hpexpire",
1185        arity: -6,
1186        flags: WRITE_FAST,
1187        first_key: 1,
1188        last_key: 1,
1189        step: 1,
1190        acl: AC_HASH_WRITE_FAST,
1191        since: "7.4.0",
1192        complexity: "O(N) with N the number of fields named",
1193        summary: "Put a deadline in milliseconds on hash fields.",
1194        group: "hash",
1195    },
1196    Spec {
1197        name: "hexpireat",
1198        arity: -6,
1199        flags: WRITE_FAST,
1200        first_key: 1,
1201        last_key: 1,
1202        step: 1,
1203        acl: AC_HASH_WRITE_FAST,
1204        since: "7.4.0",
1205        complexity: "O(N) with N the number of fields named",
1206        summary: "Put an absolute deadline in unix seconds on hash fields.",
1207        group: "hash",
1208    },
1209    Spec {
1210        name: "hpexpireat",
1211        arity: -6,
1212        flags: WRITE_FAST,
1213        first_key: 1,
1214        last_key: 1,
1215        step: 1,
1216        acl: AC_HASH_WRITE_FAST,
1217        since: "7.4.0",
1218        complexity: "O(N) with N the number of fields named",
1219        summary: "Put an absolute deadline in unix milliseconds on hash fields.",
1220        group: "hash",
1221    },
1222    Spec {
1223        name: "httl",
1224        arity: -5,
1225        flags: READ_FAST,
1226        first_key: 1,
1227        last_key: 1,
1228        step: 1,
1229        acl: AC_HASH_READ_FAST,
1230        since: "7.4.0",
1231        complexity: "O(N) with N the number of fields named",
1232        summary: "How long hash fields have left, in seconds.",
1233        group: "hash",
1234    },
1235    Spec {
1236        name: "hpttl",
1237        arity: -5,
1238        flags: READ_FAST,
1239        first_key: 1,
1240        last_key: 1,
1241        step: 1,
1242        acl: AC_HASH_READ_FAST,
1243        since: "7.4.0",
1244        complexity: "O(N) with N the number of fields named",
1245        summary: "How long hash fields have left, in milliseconds.",
1246        group: "hash",
1247    },
1248    Spec {
1249        name: "hexpiretime",
1250        arity: -5,
1251        flags: READ_FAST,
1252        first_key: 1,
1253        last_key: 1,
1254        step: 1,
1255        acl: AC_HASH_READ_FAST,
1256        since: "7.4.0",
1257        complexity: "O(N) with N the number of fields named",
1258        summary: "When hash fields fall due, in unix seconds.",
1259        group: "hash",
1260    },
1261    Spec {
1262        name: "hpexpiretime",
1263        arity: -5,
1264        flags: READ_FAST,
1265        first_key: 1,
1266        last_key: 1,
1267        step: 1,
1268        acl: AC_HASH_READ_FAST,
1269        since: "7.4.0",
1270        complexity: "O(N) with N the number of fields named",
1271        summary: "When hash fields fall due, in unix milliseconds.",
1272        group: "hash",
1273    },
1274    Spec {
1275        name: "hpersist",
1276        arity: -5,
1277        flags: WRITE_FAST,
1278        first_key: 1,
1279        last_key: 1,
1280        step: 1,
1281        acl: AC_HASH_WRITE_FAST,
1282        since: "7.4.0",
1283        complexity: "O(N) with N the number of fields named",
1284        summary: "Take the deadlines off hash fields.",
1285        group: "hash",
1286    },
1287    Spec {
1288        name: "hgetdel",
1289        arity: -5,
1290        flags: WRITE_FAST,
1291        first_key: 1,
1292        last_key: 1,
1293        step: 1,
1294        acl: AC_HASH_WRITE_FAST,
1295        since: "8.0.0",
1296        complexity: "O(N) with N the number of fields named",
1297        summary: "Read hash fields and delete them.",
1298        group: "hash",
1299    },
1300    Spec {
1301        name: "hgetex",
1302        arity: -5,
1303        flags: WRITE_FAST,
1304        first_key: 1,
1305        last_key: 1,
1306        step: 1,
1307        acl: AC_HASH_WRITE_FAST,
1308        since: "8.0.0",
1309        complexity: "O(N) with N the number of fields named",
1310        summary: "Read hash fields and set their deadlines.",
1311        group: "hash",
1312    },
1313    Spec {
1314        name: "hsetex",
1315        arity: -6,
1316        flags: WRITE_FAST_OOM,
1317        first_key: 1,
1318        last_key: 1,
1319        step: 1,
1320        acl: AC_HASH_WRITE_FAST,
1321        since: "8.0.0",
1322        complexity: "O(N) with N the number of fields being set",
1323        summary: "Set hash fields and their deadlines together.",
1324        group: "hash",
1325    },
1326    // ---------------------------------------------------------------- lists
1327    Spec {
1328        name: "lpush",
1329        arity: -3,
1330        flags: WRITE_FAST_OOM,
1331        first_key: 1,
1332        last_key: 1,
1333        step: 1,
1334        acl: AC_LIST_WRITE_FAST,
1335        since: "1.0.0",
1336        complexity: "O(N) with N the number of elements pushed",
1337        summary: "Push elements onto the head of a list.",
1338        group: "list",
1339    },
1340    Spec {
1341        name: "rpush",
1342        arity: -3,
1343        flags: WRITE_FAST_OOM,
1344        first_key: 1,
1345        last_key: 1,
1346        step: 1,
1347        acl: AC_LIST_WRITE_FAST,
1348        since: "1.0.0",
1349        complexity: "O(N) with N the number of elements pushed",
1350        summary: "Push elements onto the tail of a list.",
1351        group: "list",
1352    },
1353    Spec {
1354        name: "lpushx",
1355        arity: -3,
1356        flags: WRITE_FAST_OOM,
1357        first_key: 1,
1358        last_key: 1,
1359        step: 1,
1360        acl: AC_LIST_WRITE_FAST,
1361        since: "2.2.0",
1362        complexity: "O(N) with N the number of elements pushed",
1363        summary: "Push elements onto the head of a list that already exists.",
1364        group: "list",
1365    },
1366    Spec {
1367        name: "rpushx",
1368        arity: -3,
1369        flags: WRITE_FAST_OOM,
1370        first_key: 1,
1371        last_key: 1,
1372        step: 1,
1373        acl: AC_LIST_WRITE_FAST,
1374        since: "2.2.0",
1375        complexity: "O(N) with N the number of elements pushed",
1376        summary: "Push elements onto the tail of a list that already exists.",
1377        group: "list",
1378    },
1379    Spec {
1380        name: "lpop",
1381        arity: -2,
1382        flags: WRITE_FAST,
1383        first_key: 1,
1384        last_key: 1,
1385        step: 1,
1386        acl: AC_LIST_WRITE_FAST,
1387        since: "1.0.0",
1388        complexity: "O(N) with N the count asked for",
1389        summary: "Take elements off the head of a list.",
1390        group: "list",
1391    },
1392    Spec {
1393        name: "rpop",
1394        arity: -2,
1395        flags: WRITE_FAST,
1396        first_key: 1,
1397        last_key: 1,
1398        step: 1,
1399        acl: AC_LIST_WRITE_FAST,
1400        since: "1.0.0",
1401        complexity: "O(N) with N the count asked for",
1402        summary: "Take elements off the tail of a list.",
1403        group: "list",
1404    },
1405    Spec {
1406        name: "llen",
1407        arity: 2,
1408        flags: READ_FAST,
1409        first_key: 1,
1410        last_key: 1,
1411        step: 1,
1412        acl: AC_LIST_READ_FAST,
1413        since: "1.0.0",
1414        complexity: "O(1)",
1415        summary: "How many elements a list holds.",
1416        group: "list",
1417    },
1418    Spec {
1419        name: "lrange",
1420        arity: 4,
1421        flags: READ_SLOW,
1422        first_key: 1,
1423        last_key: 1,
1424        step: 1,
1425        acl: AC_LIST_READ_SLOW,
1426        since: "1.0.0",
1427        complexity: "O(S+N) with S the offset of the first element and N the range",
1428        summary: "Read a range of a list, both ends included.",
1429        group: "list",
1430    },
1431    Spec {
1432        name: "lindex",
1433        arity: 3,
1434        flags: READ_SLOW,
1435        first_key: 1,
1436        last_key: 1,
1437        step: 1,
1438        acl: AC_LIST_READ_SLOW,
1439        since: "1.0.0",
1440        complexity: "O(N) with N the distance to the index from the nearer end",
1441        summary: "Read one element of a list by index.",
1442        group: "list",
1443    },
1444    Spec {
1445        name: "lset",
1446        arity: 4,
1447        flags: WRITE_OOM,
1448        first_key: 1,
1449        last_key: 1,
1450        step: 1,
1451        acl: AC_LIST_WRITE_SLOW,
1452        since: "1.0.0",
1453        complexity: "O(N) with N the distance to the index from the nearer end",
1454        summary: "Replace one element of a list by index.",
1455        group: "list",
1456    },
1457    Spec {
1458        name: "linsert",
1459        arity: 5,
1460        flags: WRITE_OOM,
1461        first_key: 1,
1462        last_key: 1,
1463        step: 1,
1464        acl: AC_LIST_WRITE_SLOW,
1465        since: "2.2.0",
1466        complexity: "O(N) with N the distance to the pivot from the head",
1467        summary: "Insert an element before or after another one.",
1468        group: "list",
1469    },
1470    Spec {
1471        name: "lrem",
1472        arity: 4,
1473        flags: WRITE_SLOW,
1474        first_key: 1,
1475        last_key: 1,
1476        step: 1,
1477        acl: AC_LIST_WRITE_SLOW,
1478        since: "1.0.0",
1479        complexity: "O(N) with N the length of the list",
1480        summary: "Remove elements equal to a value from a list.",
1481        group: "list",
1482    },
1483    Spec {
1484        name: "ltrim",
1485        arity: 4,
1486        flags: WRITE_SLOW,
1487        first_key: 1,
1488        last_key: 1,
1489        step: 1,
1490        acl: AC_LIST_WRITE_SLOW,
1491        since: "1.0.0",
1492        complexity: "O(N) with N the number of elements thrown away",
1493        summary: "Keep a range of a list and throw the rest away.",
1494        group: "list",
1495    },
1496    Spec {
1497        name: "lpos",
1498        arity: -3,
1499        flags: READ_SLOW,
1500        first_key: 1,
1501        last_key: 1,
1502        step: 1,
1503        acl: AC_LIST_READ_SLOW,
1504        since: "6.0.6",
1505        complexity: "O(N) with N the length of the list",
1506        summary: "Find where a value sits in a list.",
1507        group: "list",
1508    },
1509    Spec {
1510        name: "rpoplpush",
1511        arity: 3,
1512        flags: WRITE_OOM,
1513        first_key: 1,
1514        last_key: 2,
1515        step: 1,
1516        acl: AC_LIST_WRITE_SLOW,
1517        since: "1.2.0",
1518        complexity: "O(1)",
1519        summary: "Move an element from the tail of one list to the head of another.",
1520        group: "list",
1521    },
1522    Spec {
1523        name: "lmove",
1524        arity: 5,
1525        flags: WRITE_OOM,
1526        first_key: 1,
1527        last_key: 2,
1528        step: 1,
1529        acl: AC_LIST_WRITE_SLOW,
1530        since: "6.2.0",
1531        complexity: "O(1)",
1532        summary: "Move an element from either end of one list to either end of another.",
1533        group: "list",
1534    },
1535    // The keys are behind a count, so `first_key` is zero and a cluster client
1536    // has to ask `COMMAND GETKEYS` rather than read a position out of this row.
1537    // That is what `movablekeys` means and it is why the three key fields are
1538    // all zero rather than pointing at argument two.
1539    Spec {
1540        name: "lmpop",
1541        arity: -4,
1542        flags: &["write", "movablekeys"],
1543        first_key: 0,
1544        last_key: 0,
1545        step: 0,
1546        acl: AC_LIST_WRITE_SLOW,
1547        since: "7.0.0",
1548        complexity: "O(N+M) with N the number of keys and M the count popped",
1549        summary: "Pop from the first of several lists that has anything in it.",
1550        group: "list",
1551    },
1552    // The five that wait. `blocking` is what the dispatcher branches on to send
1553    // them somewhere that can park a client, so it is load bearing here rather
1554    // than only being reported.
1555    //
1556    // `BLPOP` and `BRPOP` take their keys up to the timeout, which is the one
1557    // shape in the list group where `last_key` is negative: everything from
1558    // argument one to the second from last.
1559    Spec {
1560        name: "blpop",
1561        arity: -3,
1562        flags: &["write", "blocking"],
1563        first_key: 1,
1564        last_key: -2,
1565        step: 1,
1566        acl: AC_LIST_WRITE_BLOCKING,
1567        since: "2.0.0",
1568        complexity: "O(N) with N the number of keys named",
1569        summary: "Pop the head of the first list that has anything, waiting if none does.",
1570        group: "list",
1571    },
1572    Spec {
1573        name: "brpop",
1574        arity: -3,
1575        flags: &["write", "blocking"],
1576        first_key: 1,
1577        last_key: -2,
1578        step: 1,
1579        acl: AC_LIST_WRITE_BLOCKING,
1580        since: "2.0.0",
1581        complexity: "O(N) with N the number of keys named",
1582        summary: "Pop the tail of the first list that has anything, waiting if none does.",
1583        group: "list",
1584    },
1585    // Redis marks the two that push somewhere `denyoom` and does not mark the
1586    // pops, because these are the blocking commands that can grow the keyspace.
1587    Spec {
1588        name: "blmove",
1589        arity: 6,
1590        flags: &["write", "denyoom", "blocking"],
1591        first_key: 1,
1592        last_key: 2,
1593        step: 1,
1594        acl: AC_LIST_WRITE_BLOCKING,
1595        since: "6.2.0",
1596        complexity: "O(1)",
1597        summary: "Move an element between two lists, waiting for one to arrive.",
1598        group: "list",
1599    },
1600    Spec {
1601        name: "brpoplpush",
1602        arity: 4,
1603        flags: &["write", "denyoom", "blocking"],
1604        first_key: 1,
1605        last_key: 2,
1606        step: 1,
1607        acl: AC_LIST_WRITE_BLOCKING,
1608        since: "2.2.0",
1609        complexity: "O(1)",
1610        summary: "Move a tail element to another list's head, waiting for one to arrive.",
1611        group: "list",
1612    },
1613    // Keys behind a count again, so the same three zeroes `LMPOP` has.
1614    Spec {
1615        name: "blmpop",
1616        arity: -5,
1617        flags: &["write", "blocking", "movablekeys"],
1618        first_key: 0,
1619        last_key: 0,
1620        step: 0,
1621        acl: AC_LIST_WRITE_BLOCKING,
1622        since: "7.0.0",
1623        complexity: "O(N+M) with N the number of keys and M the count popped",
1624        summary: "Pop from the first of several lists that has anything, waiting if none does.",
1625        group: "list",
1626    },
1627    // ------------------------------------------------------------ sorted set
1628    Spec {
1629        name: "zadd",
1630        arity: -4,
1631        flags: WRITE_FAST_OOM,
1632        first_key: 1,
1633        last_key: 1,
1634        step: 1,
1635        acl: AC_ZSET_WRITE_FAST,
1636        since: "1.2.0",
1637        complexity: "O(log(N)) for each member added",
1638        summary: "Add members with scores, or move the scores of members already there.",
1639        group: "zset",
1640    },
1641    Spec {
1642        name: "zincrby",
1643        arity: 4,
1644        flags: WRITE_FAST_OOM,
1645        first_key: 1,
1646        last_key: 1,
1647        step: 1,
1648        acl: AC_ZSET_WRITE_FAST,
1649        since: "1.2.0",
1650        complexity: "O(log(N))",
1651        summary: "Add to a member's score, creating the member at zero if it is not there.",
1652        group: "zset",
1653    },
1654    Spec {
1655        name: "zcard",
1656        arity: 2,
1657        flags: READ_FAST,
1658        first_key: 1,
1659        last_key: 1,
1660        step: 1,
1661        acl: AC_ZSET_READ_FAST,
1662        since: "1.2.0",
1663        complexity: "O(1)",
1664        summary: "How many members a sorted set has.",
1665        group: "zset",
1666    },
1667    Spec {
1668        name: "zscore",
1669        arity: 3,
1670        flags: READ_FAST,
1671        first_key: 1,
1672        last_key: 1,
1673        step: 1,
1674        acl: AC_ZSET_READ_FAST,
1675        since: "1.2.0",
1676        complexity: "O(1)",
1677        summary: "A member's score, or nothing if it is not there.",
1678        group: "zset",
1679    },
1680    Spec {
1681        name: "zmscore",
1682        arity: -3,
1683        flags: READ_FAST,
1684        first_key: 1,
1685        last_key: 1,
1686        step: 1,
1687        acl: AC_ZSET_READ_FAST,
1688        since: "6.2.0",
1689        complexity: "O(N) with N the number of members asked about",
1690        summary: "The scores of several members in one round trip.",
1691        group: "zset",
1692    },
1693    Spec {
1694        name: "zrem",
1695        arity: -3,
1696        flags: WRITE_FAST,
1697        first_key: 1,
1698        last_key: 1,
1699        step: 1,
1700        acl: AC_ZSET_WRITE_FAST,
1701        since: "1.2.0",
1702        complexity: "O(M*log(N)) with M the number of members removed",
1703        summary: "Remove members, deleting the key if the last one goes.",
1704        group: "zset",
1705    },
1706    Spec {
1707        name: "zrank",
1708        arity: -3,
1709        flags: READ_FAST,
1710        first_key: 1,
1711        last_key: 1,
1712        step: 1,
1713        acl: AC_ZSET_READ_FAST,
1714        since: "2.0.0",
1715        complexity: "O(log(N))",
1716        summary: "Where a member sits counting up from the lowest score.",
1717        group: "zset",
1718    },
1719    Spec {
1720        name: "zrevrank",
1721        arity: -3,
1722        flags: READ_FAST,
1723        first_key: 1,
1724        last_key: 1,
1725        step: 1,
1726        acl: AC_ZSET_READ_FAST,
1727        since: "2.0.0",
1728        complexity: "O(log(N))",
1729        summary: "Where a member sits counting down from the highest score.",
1730        group: "zset",
1731    },
1732    Spec {
1733        name: "zcount",
1734        arity: 4,
1735        flags: READ_FAST,
1736        first_key: 1,
1737        last_key: 1,
1738        step: 1,
1739        acl: AC_ZSET_READ_FAST,
1740        since: "2.0.0",
1741        complexity: "O(log(N))",
1742        summary: "How many members have scores between two bounds.",
1743        group: "zset",
1744    },
1745    Spec {
1746        name: "zlexcount",
1747        arity: 4,
1748        flags: READ_FAST,
1749        first_key: 1,
1750        last_key: 1,
1751        step: 1,
1752        acl: AC_ZSET_READ_FAST,
1753        since: "2.8.9",
1754        complexity: "O(log(N))",
1755        summary: "How many members fall between two members, by name.",
1756        group: "zset",
1757    },
1758    Spec {
1759        name: "zrange",
1760        arity: -4,
1761        flags: READ_SLOW,
1762        first_key: 1,
1763        last_key: 1,
1764        step: 1,
1765        acl: AC_ZSET_READ_SLOW,
1766        since: "1.2.0",
1767        complexity: "O(log(N)+M) with M the number of members answered",
1768        summary: "A window of members, by rank or by score or by name, either way round.",
1769        group: "zset",
1770    },
1771    Spec {
1772        name: "zrevrange",
1773        arity: -4,
1774        flags: READ_SLOW,
1775        first_key: 1,
1776        last_key: 1,
1777        step: 1,
1778        acl: AC_ZSET_READ_SLOW,
1779        since: "1.2.0",
1780        complexity: "O(log(N)+M) with M the number of members answered",
1781        summary: "A window by rank, counting down from the highest score.",
1782        group: "zset",
1783    },
1784    Spec {
1785        name: "zrangebyscore",
1786        arity: -4,
1787        flags: READ_SLOW,
1788        first_key: 1,
1789        last_key: 1,
1790        step: 1,
1791        acl: AC_ZSET_READ_SLOW,
1792        since: "1.0.5",
1793        complexity: "O(log(N)+M) with M the number of members answered",
1794        summary: "The members whose scores fall between two bounds.",
1795        group: "zset",
1796    },
1797    Spec {
1798        name: "zrevrangebyscore",
1799        arity: -4,
1800        flags: READ_SLOW,
1801        first_key: 1,
1802        last_key: 1,
1803        step: 1,
1804        acl: AC_ZSET_READ_SLOW,
1805        since: "2.2.0",
1806        complexity: "O(log(N)+M) with M the number of members answered",
1807        summary: "The same window as ZRANGEBYSCORE, highest score first and named high end first.",
1808        group: "zset",
1809    },
1810    Spec {
1811        name: "zrangebylex",
1812        arity: -4,
1813        flags: READ_SLOW,
1814        first_key: 1,
1815        last_key: 1,
1816        step: 1,
1817        acl: AC_ZSET_READ_SLOW,
1818        since: "2.8.9",
1819        complexity: "O(log(N)+M) with M the number of members answered",
1820        summary: "The members that fall between two names, for a set where every score is the same.",
1821        group: "zset",
1822    },
1823    Spec {
1824        name: "zrevrangebylex",
1825        arity: -4,
1826        flags: READ_SLOW,
1827        first_key: 1,
1828        last_key: 1,
1829        step: 1,
1830        acl: AC_ZSET_READ_SLOW,
1831        since: "2.8.9",
1832        complexity: "O(log(N)+M) with M the number of members answered",
1833        summary: "The same window as ZRANGEBYLEX, backwards and named high end first.",
1834        group: "zset",
1835    },
1836    Spec {
1837        name: "zrangestore",
1838        arity: -5,
1839        flags: WRITE_OOM,
1840        first_key: 1,
1841        last_key: 2,
1842        step: 1,
1843        acl: AC_ZSET_WRITE_SLOW,
1844        since: "6.2.0",
1845        complexity: "O(log(N)+M) with M the number of members stored",
1846        summary: "Write a window of one sorted set into another key.",
1847        group: "zset",
1848    },
1849    Spec {
1850        name: "zremrangebyrank",
1851        arity: 4,
1852        flags: WRITE_SLOW,
1853        first_key: 1,
1854        last_key: 1,
1855        step: 1,
1856        acl: AC_ZSET_WRITE_SLOW,
1857        since: "2.0.0",
1858        complexity: "O(log(N)+M) with M the number of members removed",
1859        summary: "Remove the members in a range of ranks.",
1860        group: "zset",
1861    },
1862    Spec {
1863        name: "zremrangebyscore",
1864        arity: 4,
1865        flags: WRITE_SLOW,
1866        first_key: 1,
1867        last_key: 1,
1868        step: 1,
1869        acl: AC_ZSET_WRITE_SLOW,
1870        since: "1.2.0",
1871        complexity: "O(log(N)+M) with M the number of members removed",
1872        summary: "Remove the members whose scores fall between two bounds.",
1873        group: "zset",
1874    },
1875    Spec {
1876        name: "zremrangebylex",
1877        arity: 4,
1878        flags: WRITE_SLOW,
1879        first_key: 1,
1880        last_key: 1,
1881        step: 1,
1882        acl: AC_ZSET_WRITE_SLOW,
1883        since: "2.8.9",
1884        complexity: "O(log(N)+M) with M the number of members removed",
1885        summary: "Remove the members that fall between two names.",
1886        group: "zset",
1887    },
1888    Spec {
1889        name: "zunion",
1890        arity: -3,
1891        flags: READ_MOVABLE,
1892        first_key: 0,
1893        last_key: 0,
1894        step: 0,
1895        acl: AC_ZSET_READ_SLOW,
1896        since: "6.2.0",
1897        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1898        summary: "Every member of these sorted sets, with the scores combined.",
1899        group: "zset",
1900    },
1901    Spec {
1902        name: "zinter",
1903        arity: -3,
1904        flags: READ_MOVABLE,
1905        first_key: 0,
1906        last_key: 0,
1907        step: 0,
1908        acl: AC_ZSET_READ_SLOW,
1909        since: "6.2.0",
1910        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1911        summary: "Only the members all of these sorted sets have, with the scores combined.",
1912        group: "zset",
1913    },
1914    Spec {
1915        name: "zdiff",
1916        arity: -3,
1917        flags: READ_MOVABLE,
1918        first_key: 0,
1919        last_key: 0,
1920        step: 0,
1921        acl: AC_ZSET_READ_SLOW,
1922        since: "6.2.0",
1923        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1924        summary: "The members of the first that none of the rest have.",
1925        group: "zset",
1926    },
1927    Spec {
1928        name: "zunionstore",
1929        arity: -4,
1930        flags: WRITE_MOVABLE,
1931        first_key: 1,
1932        last_key: 1,
1933        step: 1,
1934        acl: AC_ZSET_WRITE_SLOW,
1935        since: "2.0.0",
1936        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1937        summary: "Store the union in another key and say how big it is.",
1938        group: "zset",
1939    },
1940    Spec {
1941        name: "zinterstore",
1942        arity: -4,
1943        flags: WRITE_MOVABLE,
1944        first_key: 1,
1945        last_key: 1,
1946        step: 1,
1947        acl: AC_ZSET_WRITE_SLOW,
1948        since: "2.0.0",
1949        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1950        summary: "Store the intersection in another key and say how big it is.",
1951        group: "zset",
1952    },
1953    Spec {
1954        name: "zdiffstore",
1955        arity: -4,
1956        flags: WRITE_MOVABLE,
1957        first_key: 1,
1958        last_key: 1,
1959        step: 1,
1960        acl: AC_ZSET_WRITE_SLOW,
1961        since: "6.2.0",
1962        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1963        summary: "Store the difference in another key and say how big it is.",
1964        group: "zset",
1965    },
1966    Spec {
1967        name: "zintercard",
1968        arity: -3,
1969        flags: READ_MOVABLE,
1970        first_key: 0,
1971        last_key: 0,
1972        step: 0,
1973        acl: AC_ZSET_READ_SLOW,
1974        since: "7.0.0",
1975        complexity: "O(N*M) worst case, N the smallest input and M the number of inputs",
1976        summary: "How many members the intersection would have, without building it.",
1977        group: "zset",
1978    },
1979    Spec {
1980        name: "zrandmember",
1981        arity: -2,
1982        flags: READ_SLOW,
1983        first_key: 1,
1984        last_key: 1,
1985        step: 1,
1986        acl: AC_ZSET_READ_SLOW,
1987        since: "6.2.0",
1988        complexity: "O(N) with N the number of members drawn",
1989        summary: "Draw members at random, with or without replacement.",
1990        group: "zset",
1991    },
1992    Spec {
1993        name: "zscan",
1994        arity: -3,
1995        flags: READ_SLOW,
1996        first_key: 1,
1997        last_key: 1,
1998        step: 1,
1999        acl: AC_ZSET_READ_SLOW,
2000        since: "2.8.0",
2001        complexity: "O(1) per call, O(N) over a full walk",
2002        summary: "Walk the members and their scores a batch at a time.",
2003        group: "zset",
2004    },
2005    // The pops. Redis calls the two single key ones fast even though they cost a
2006    // logarithm, on the grounds that the logarithm is of a size a client chose.
2007    Spec {
2008        name: "zpopmin",
2009        arity: -2,
2010        flags: WRITE_FAST,
2011        first_key: 1,
2012        last_key: 1,
2013        step: 1,
2014        acl: AC_ZSET_WRITE_FAST,
2015        since: "5.0.0",
2016        complexity: "O(log(N)*M) with M the number of members popped",
2017        summary: "Take the lowest scoring members off and answer them.",
2018        group: "zset",
2019    },
2020    Spec {
2021        name: "zpopmax",
2022        arity: -2,
2023        flags: WRITE_FAST,
2024        first_key: 1,
2025        last_key: 1,
2026        step: 1,
2027        acl: AC_ZSET_WRITE_FAST,
2028        since: "5.0.0",
2029        complexity: "O(log(N)*M) with M the number of members popped",
2030        summary: "Take the highest scoring members off and answer them.",
2031        group: "zset",
2032    },
2033    // Keys behind a count, so the same three zeroes `LMPOP` has, and `write`
2034    // without `denyoom` because a pop cannot grow the keyspace.
2035    Spec {
2036        name: "zmpop",
2037        arity: -4,
2038        flags: &["write", "movablekeys"],
2039        first_key: 0,
2040        last_key: 0,
2041        step: 0,
2042        acl: AC_ZSET_WRITE_SLOW,
2043        since: "7.0.0",
2044        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
2045        summary: "Pop from the first of several sorted sets that has anything in it.",
2046        group: "zset",
2047    },
2048    // The three that wait. `blocking` is what the dispatcher branches on, the
2049    // same as it is for the five list ones.
2050    Spec {
2051        name: "bzpopmin",
2052        arity: -3,
2053        flags: &["write", "blocking", "fast"],
2054        first_key: 1,
2055        last_key: -2,
2056        step: 1,
2057        acl: AC_ZSET_BLOCKING_FAST,
2058        since: "5.0.0",
2059        complexity: "O(log(N)) with N the size of the sorted set that answers",
2060        summary: "Take the lowest scoring member off the first sorted set that has one, waiting if none does.",
2061        group: "zset",
2062    },
2063    Spec {
2064        name: "bzpopmax",
2065        arity: -3,
2066        flags: &["write", "blocking", "fast"],
2067        first_key: 1,
2068        last_key: -2,
2069        step: 1,
2070        acl: AC_ZSET_BLOCKING_FAST,
2071        since: "5.0.0",
2072        complexity: "O(log(N)) with N the size of the sorted set that answers",
2073        summary: "Take the highest scoring member off the first sorted set that has one, waiting if none does.",
2074        group: "zset",
2075    },
2076    Spec {
2077        name: "bzmpop",
2078        arity: -5,
2079        flags: &["write", "blocking", "movablekeys"],
2080        first_key: 0,
2081        last_key: 0,
2082        step: 0,
2083        acl: AC_ZSET_BLOCKING_SLOW,
2084        since: "7.0.0",
2085        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
2086        summary: "Pop from the first of several sorted sets that has anything, waiting if none does.",
2087        group: "zset",
2088    },
2089    // ----------------------------------------------------------------- geo
2090    Spec {
2091        name: "geoadd",
2092        arity: -5,
2093        flags: WRITE_OOM,
2094        first_key: 1,
2095        last_key: 1,
2096        step: 1,
2097        acl: AC_GEO_WRITE,
2098        since: "3.2.0",
2099        complexity: "O(log(N)) per point added",
2100        summary: "Add places to a geo key, which is a sorted set of position hashes.",
2101        group: "geo",
2102    },
2103    Spec {
2104        name: "geopos",
2105        arity: -2,
2106        flags: READ_SLOW,
2107        first_key: 1,
2108        last_key: 1,
2109        step: 1,
2110        acl: AC_GEO_READ,
2111        since: "3.2.0",
2112        complexity: "O(1) per member asked about",
2113        summary: "Answer where each member is, as a longitude and a latitude.",
2114        group: "geo",
2115    },
2116    Spec {
2117        name: "geodist",
2118        arity: -4,
2119        flags: READ_SLOW,
2120        first_key: 1,
2121        last_key: 1,
2122        step: 1,
2123        acl: AC_GEO_READ,
2124        since: "3.2.0",
2125        complexity: "O(1)",
2126        summary: "Answer how far apart two members are, in the unit asked for.",
2127        group: "geo",
2128    },
2129    Spec {
2130        name: "geohash",
2131        arity: -2,
2132        flags: READ_SLOW,
2133        first_key: 1,
2134        last_key: 1,
2135        step: 1,
2136        acl: AC_GEO_READ,
2137        since: "3.2.0",
2138        complexity: "O(1) per member asked about",
2139        summary: "Answer each member's position as a standard eleven character geohash.",
2140        group: "geo",
2141    },
2142    Spec {
2143        name: "geosearch",
2144        arity: -7,
2145        flags: READ_SLOW,
2146        first_key: 1,
2147        last_key: 1,
2148        step: 1,
2149        acl: AC_GEO_READ,
2150        since: "6.2.0",
2151        complexity: "O(N+log(M)) with N the members in the boxes searched",
2152        summary: "Find the members inside a circle or a rectangle around a point.",
2153        group: "geo",
2154    },
2155    Spec {
2156        name: "geosearchstore",
2157        arity: -8,
2158        flags: WRITE_OOM,
2159        first_key: 1,
2160        last_key: 2,
2161        step: 1,
2162        acl: AC_GEO_WRITE,
2163        since: "6.2.0",
2164        complexity: "O(N+log(M)) with N the members in the boxes searched",
2165        summary: "Run a search and write what it found into another key.",
2166        group: "geo",
2167    },
2168    Spec {
2169        name: "georadius",
2170        arity: -6,
2171        flags: WRITE_MOVABLE,
2172        first_key: 1,
2173        last_key: 1,
2174        step: 1,
2175        acl: AC_GEO_WRITE,
2176        since: "3.2.0",
2177        complexity: "O(N+log(M)) with N the members in the boxes searched",
2178        summary: "The older spelling of a circular search, which can also store.",
2179        group: "geo",
2180    },
2181    Spec {
2182        name: "georadius_ro",
2183        arity: -6,
2184        flags: READ_SLOW,
2185        first_key: 1,
2186        last_key: 1,
2187        step: 1,
2188        acl: AC_GEO_READ,
2189        since: "3.2.10",
2190        complexity: "O(N+log(M)) with N the members in the boxes searched",
2191        summary: "GEORADIUS without the store options, so a replica can serve it.",
2192        group: "geo",
2193    },
2194    Spec {
2195        name: "georadiusbymember",
2196        arity: -5,
2197        flags: WRITE_MOVABLE,
2198        first_key: 1,
2199        last_key: 1,
2200        step: 1,
2201        acl: AC_GEO_WRITE,
2202        since: "3.2.0",
2203        complexity: "O(N+log(M)) with N the members in the boxes searched",
2204        summary: "The same search centred on a member rather than on a point.",
2205        group: "geo",
2206    },
2207    Spec {
2208        name: "georadiusbymember_ro",
2209        arity: -5,
2210        flags: READ_SLOW,
2211        first_key: 1,
2212        last_key: 1,
2213        step: 1,
2214        acl: AC_GEO_READ,
2215        since: "3.2.10",
2216        complexity: "O(N+log(M)) with N the members in the boxes searched",
2217        summary: "GEORADIUSBYMEMBER without the store options.",
2218        group: "geo",
2219    },
2220    // --------------------------------------------------------------- graph
2221    Spec {
2222        name: "g.nadd",
2223        arity: -3,
2224        flags: WRITE_FAST_OOM,
2225        first_key: 1,
2226        last_key: 1,
2227        step: 1,
2228        acl: AC_GRAPH_WRITE_FAST,
2229        since: "8.8.0",
2230        complexity: "O(N) with N the fields written",
2231        summary: "Write a node and its properties, creating it if it is new.",
2232        group: "graph",
2233    },
2234    Spec {
2235        name: "g.nget",
2236        arity: 3,
2237        flags: READ_FAST,
2238        first_key: 1,
2239        last_key: 1,
2240        step: 1,
2241        acl: AC_GRAPH_READ_FAST,
2242        since: "8.8.0",
2243        complexity: "O(N) with N the fields on the node",
2244        summary: "Every property on a node.",
2245        group: "graph",
2246    },
2247    Spec {
2248        name: "g.ndel",
2249        arity: 3,
2250        flags: WRITE_FAST,
2251        first_key: 1,
2252        last_key: 1,
2253        step: 1,
2254        acl: AC_GRAPH_WRITE_FAST,
2255        since: "8.8.0",
2256        complexity: "O(E) with E the edges on the node",
2257        summary: "Delete a node and every edge that touches it.",
2258        group: "graph",
2259    },
2260    Spec {
2261        name: "g.eadd",
2262        arity: -5,
2263        flags: WRITE_FAST_OOM,
2264        first_key: 1,
2265        last_key: 1,
2266        step: 1,
2267        acl: AC_GRAPH_WRITE_FAST,
2268        since: "8.8.0",
2269        complexity: "O(D) with D the outgoing degree under the label",
2270        summary: "Write an edge and its properties, creating either end if it is new.",
2271        group: "graph",
2272    },
2273    Spec {
2274        name: "g.edel",
2275        arity: 5,
2276        flags: WRITE_FAST,
2277        first_key: 1,
2278        last_key: 1,
2279        step: 1,
2280        acl: AC_GRAPH_WRITE_FAST,
2281        since: "8.8.0",
2282        complexity: "O(D) with D the outgoing degree under the label",
2283        summary: "Delete one edge between two nodes under a label.",
2284        group: "graph",
2285    },
2286    Spec {
2287        name: "g.out",
2288        arity: -4,
2289        flags: READ_FAST,
2290        first_key: 1,
2291        last_key: 1,
2292        step: 1,
2293        acl: AC_GRAPH_READ_FAST,
2294        since: "8.8.0",
2295        complexity: "O(N) with N the page asked for",
2296        summary: "Outgoing neighbours under a label, a page at a time.",
2297        group: "graph",
2298    },
2299    Spec {
2300        name: "g.in",
2301        arity: -4,
2302        flags: READ_FAST,
2303        first_key: 1,
2304        last_key: 1,
2305        step: 1,
2306        acl: AC_GRAPH_READ_FAST,
2307        since: "8.8.0",
2308        complexity: "O(N) with N the page asked for",
2309        summary: "Incoming neighbours under a label, a page at a time.",
2310        group: "graph",
2311    },
2312    Spec {
2313        name: "g.deg",
2314        arity: -4,
2315        flags: READ_FAST,
2316        first_key: 1,
2317        last_key: 1,
2318        step: 1,
2319        acl: AC_GRAPH_READ_FAST,
2320        since: "8.8.0",
2321        complexity: "O(1)",
2322        summary: "How many edges a node has under a label.",
2323        group: "graph",
2324    },
2325    Spec {
2326        name: "g.neigh",
2327        arity: -4,
2328        flags: READ_SLOW,
2329        first_key: 1,
2330        last_key: 1,
2331        step: 1,
2332        acl: AC_GRAPH_READ_SLOW,
2333        since: "8.8.0",
2334        complexity: "O(V + E) over the ball the depth reaches",
2335        summary: "Everything reachable within a depth, each node once.",
2336        group: "graph",
2337    },
2338    Spec {
2339        name: "g.path",
2340        arity: -4,
2341        flags: READ_SLOW,
2342        first_key: 1,
2343        last_key: 1,
2344        step: 1,
2345        acl: AC_GRAPH_READ_SLOW,
2346        since: "8.8.0",
2347        complexity: "O(b^(d/2)) with b the branching factor and d the distance",
2348        summary: "A shortest path between two nodes, searched from both ends.",
2349        group: "graph",
2350    },
2351    // --------------------------------------------------------------- array
2352    Spec {
2353        name: "arset",
2354        arity: -4,
2355        flags: WRITE_FAST_OOM,
2356        first_key: 1,
2357        last_key: 1,
2358        step: 1,
2359        acl: AC_ARRAY_WRITE_FAST,
2360        since: "8.8.0",
2361        complexity: "O(N) with N the number of values",
2362        summary: "Write values into consecutive positions from an index.",
2363        group: "array",
2364    },
2365    Spec {
2366        name: "armset",
2367        arity: -4,
2368        flags: WRITE_FAST_OOM,
2369        first_key: 1,
2370        last_key: 1,
2371        step: 1,
2372        acl: AC_ARRAY_WRITE_FAST,
2373        since: "8.8.0",
2374        complexity: "O(N) with N the number of pairs",
2375        summary: "Write index and value pairs, which need not be neighbours.",
2376        group: "array",
2377    },
2378    Spec {
2379        name: "arget",
2380        arity: 3,
2381        flags: READ_FAST,
2382        first_key: 1,
2383        last_key: 1,
2384        step: 1,
2385        acl: AC_ARRAY_READ_FAST,
2386        since: "8.8.0",
2387        complexity: "O(1)",
2388        summary: "The value at one index, or a null if nothing is there.",
2389        group: "array",
2390    },
2391    Spec {
2392        name: "armget",
2393        arity: -3,
2394        flags: READ_FAST,
2395        first_key: 1,
2396        last_key: 1,
2397        step: 1,
2398        acl: AC_ARRAY_READ_FAST,
2399        since: "8.8.0",
2400        complexity: "O(N) with N the number of indices",
2401        summary: "The values at the indices named, in the order named.",
2402        group: "array",
2403    },
2404    Spec {
2405        name: "argetrange",
2406        arity: 4,
2407        flags: READ_SLOW,
2408        first_key: 1,
2409        last_key: 1,
2410        step: 1,
2411        acl: AC_ARRAY_READ_SLOW,
2412        since: "8.8.0",
2413        complexity: "O(N) with N the length of the range",
2414        summary: "One reply per position between two indices, holes included.",
2415        group: "array",
2416    },
2417    Spec {
2418        name: "arlen",
2419        arity: 2,
2420        flags: READ_FAST,
2421        first_key: 1,
2422        last_key: 1,
2423        step: 1,
2424        acl: AC_ARRAY_READ_FAST,
2425        since: "8.8.0",
2426        complexity: "O(1)",
2427        summary: "The highest populated index plus one.",
2428        group: "array",
2429    },
2430    Spec {
2431        name: "arcount",
2432        arity: 2,
2433        flags: READ_FAST,
2434        first_key: 1,
2435        last_key: 1,
2436        step: 1,
2437        acl: AC_ARRAY_READ_FAST,
2438        since: "8.8.0",
2439        complexity: "O(1)",
2440        summary: "How many indices hold something.",
2441        group: "array",
2442    },
2443    Spec {
2444        name: "ardel",
2445        arity: -3,
2446        flags: WRITE_FAST,
2447        first_key: 1,
2448        last_key: 1,
2449        step: 1,
2450        acl: AC_ARRAY_WRITE_FAST,
2451        since: "8.8.0",
2452        complexity: "O(N) with N the number of indices",
2453        summary: "Empty the indices named and say how many held something.",
2454        group: "array",
2455    },
2456    Spec {
2457        name: "ardelrange",
2458        arity: -4,
2459        flags: WRITE_SLOW,
2460        first_key: 1,
2461        last_key: 1,
2462        step: 1,
2463        acl: AC_ARRAY_WRITE_SLOW,
2464        since: "8.8.0",
2465        complexity: "O(N) with N the elements touched, not the span asked for",
2466        summary: "Empty one or more ranges of indices.",
2467        group: "array",
2468    },
2469    Spec {
2470        name: "arinsert",
2471        arity: -3,
2472        flags: WRITE_FAST_OOM,
2473        first_key: 1,
2474        last_key: 1,
2475        step: 1,
2476        acl: AC_ARRAY_WRITE_FAST,
2477        since: "8.8.0",
2478        complexity: "O(N) with N the number of values",
2479        summary: "Append values at the insert cursor.",
2480        group: "array",
2481    },
2482    Spec {
2483        name: "arring",
2484        arity: -4,
2485        flags: WRITE_OOM,
2486        first_key: 1,
2487        last_key: 1,
2488        step: 1,
2489        acl: AC_ARRAY_WRITE_SLOW,
2490        since: "8.8.0",
2491        complexity: "O(N) with N the values, plus the ring size when it changes",
2492        summary: "Append values into a ring of the given size.",
2493        group: "array",
2494    },
2495    Spec {
2496        name: "arnext",
2497        arity: 2,
2498        flags: READ_FAST,
2499        first_key: 1,
2500        last_key: 1,
2501        step: 1,
2502        acl: AC_ARRAY_READ_FAST,
2503        since: "8.8.0",
2504        complexity: "O(1)",
2505        summary: "The index the next append would write to.",
2506        group: "array",
2507    },
2508    Spec {
2509        name: "arseek",
2510        arity: 3,
2511        flags: WRITE_FAST,
2512        first_key: 1,
2513        last_key: 1,
2514        step: 1,
2515        acl: AC_ARRAY_WRITE_FAST,
2516        since: "8.8.0",
2517        complexity: "O(1)",
2518        summary: "Point the insert cursor at an index.",
2519        group: "array",
2520    },
2521    Spec {
2522        name: "arlastitems",
2523        arity: -3,
2524        flags: READ_SLOW,
2525        first_key: 1,
2526        last_key: 1,
2527        step: 1,
2528        acl: AC_ARRAY_READ_SLOW,
2529        since: "8.8.0",
2530        complexity: "O(N) with N the count asked for",
2531        summary: "The newest positions from the insert cursor, holes included.",
2532        group: "array",
2533    },
2534    Spec {
2535        name: "arscan",
2536        arity: -4,
2537        flags: READ_SLOW,
2538        first_key: 1,
2539        last_key: 1,
2540        step: 1,
2541        acl: AC_ARRAY_READ_SLOW,
2542        since: "8.8.0",
2543        complexity: "O(N) with N the elements found, not the span asked for",
2544        summary: "Index and value pairs for what a range holds, skipping holes.",
2545        group: "array",
2546    },
2547    Spec {
2548        name: "argrep",
2549        arity: -6,
2550        flags: READ_SLOW,
2551        first_key: 1,
2552        last_key: 1,
2553        step: 1,
2554        acl: AC_ARRAY_READ_SLOW,
2555        since: "8.8.0",
2556        complexity: "O(P * C) with P the positions visited and C the cost of the predicates on one element",
2557        summary: "The indexes in a range whose elements answer a set of textual predicates.",
2558        group: "array",
2559    },
2560    Spec {
2561        name: "arop",
2562        arity: -5,
2563        flags: READ_SLOW,
2564        first_key: 1,
2565        last_key: 1,
2566        step: 1,
2567        acl: AC_ARRAY_READ_SLOW,
2568        since: "8.8.0",
2569        complexity: "O(N) with N the elements found, not the span asked for",
2570        summary: "One number out of a range, added up or compared or counted.",
2571        group: "array",
2572    },
2573    Spec {
2574        name: "arinfo",
2575        arity: -2,
2576        flags: READ_SLOW,
2577        first_key: 1,
2578        last_key: 1,
2579        step: 1,
2580        acl: AC_ARRAY_READ_SLOW,
2581        since: "8.8.0",
2582        complexity: "O(1), or O(N) with N the slices when FULL is given",
2583        summary: "The shape of the array, and what its slices look like.",
2584        group: "array",
2585    },
2586    // ------------------------------------------------------------- streams
2587    Spec {
2588        name: "xadd",
2589        arity: -5,
2590        flags: WRITE_FAST_OOM,
2591        first_key: 1,
2592        last_key: 1,
2593        step: 1,
2594        acl: AC_STREAM_WRITE_FAST,
2595        since: "5.0.0",
2596        complexity: "O(1) for the append, plus what a trim removes.",
2597        summary: "Append an entry and answer with the ID it got.",
2598        group: "stream",
2599    },
2600    Spec {
2601        name: "xlen",
2602        arity: 2,
2603        flags: READ_FAST,
2604        first_key: 1,
2605        last_key: 1,
2606        step: 1,
2607        acl: AC_STREAM_READ_FAST,
2608        since: "5.0.0",
2609        complexity: "O(1)",
2610        summary: "How many entries the stream holds.",
2611        group: "stream",
2612    },
2613    Spec {
2614        name: "xdel",
2615        arity: -3,
2616        flags: WRITE_FAST,
2617        first_key: 1,
2618        last_key: 1,
2619        step: 1,
2620        acl: AC_STREAM_WRITE_FAST,
2621        since: "5.0.0",
2622        complexity: "O(1) per ID.",
2623        summary: "Remove entries by ID and say how many were there.",
2624        group: "stream",
2625    },
2626    Spec {
2627        name: "xdelex",
2628        arity: -5,
2629        flags: WRITE_FAST,
2630        first_key: 1,
2631        last_key: 1,
2632        step: 1,
2633        acl: AC_STREAM_WRITE_FAST,
2634        since: "8.2.0",
2635        complexity: "O(1) per ID.",
2636        summary: "Remove entries by ID, saying what to do about the groups.",
2637        group: "stream",
2638    },
2639    Spec {
2640        name: "xackdel",
2641        arity: -6,
2642        flags: WRITE_FAST,
2643        first_key: 1,
2644        last_key: 1,
2645        step: 1,
2646        acl: AC_STREAM_WRITE_FAST,
2647        since: "8.2.0",
2648        complexity: "O(1) per ID.",
2649        summary: "Acknowledge entries for a group and remove them.",
2650        group: "stream",
2651    },
2652    Spec {
2653        name: "xnack",
2654        arity: -7,
2655        flags: WRITE_FAST,
2656        first_key: 1,
2657        last_key: 1,
2658        step: 1,
2659        acl: AC_STREAM_WRITE_FAST,
2660        since: "8.8.0",
2661        complexity: "O(1) per ID.",
2662        summary: "Give entries back to the group for somebody else to claim.",
2663        group: "stream",
2664    },
2665    Spec {
2666        name: "xtrim",
2667        arity: -4,
2668        flags: WRITE_SLOW,
2669        first_key: 1,
2670        last_key: 1,
2671        step: 1,
2672        acl: AC_STREAM_WRITE_SLOW,
2673        since: "5.0.0",
2674        complexity: "O(N) in the entries removed.",
2675        summary: "Cut the stream down to a length or a minimum ID.",
2676        group: "stream",
2677    },
2678    Spec {
2679        name: "xrange",
2680        arity: -4,
2681        flags: READ_SLOW,
2682        first_key: 1,
2683        last_key: 1,
2684        step: 1,
2685        acl: AC_STREAM_READ_SLOW,
2686        since: "5.0.0",
2687        complexity: "O(N) in the entries returned.",
2688        summary: "The entries between two IDs, oldest first.",
2689        group: "stream",
2690    },
2691    Spec {
2692        name: "xrevrange",
2693        arity: -4,
2694        flags: READ_SLOW,
2695        first_key: 1,
2696        last_key: 1,
2697        step: 1,
2698        acl: AC_STREAM_READ_SLOW,
2699        since: "5.0.0",
2700        complexity: "O(N) in the entries returned.",
2701        summary: "The entries between two IDs, newest first.",
2702        group: "stream",
2703    },
2704    Spec {
2705        name: "xread",
2706        arity: -4,
2707        flags: READ_BLOCKING_MOVABLE,
2708        first_key: 0,
2709        last_key: 0,
2710        step: 0,
2711        acl: AC_STREAM_BLOCKING_READ,
2712        since: "5.0.0",
2713        complexity: "O(N) in the entries returned.",
2714        summary: "Read from one or more streams, waiting if asked to.",
2715        group: "stream",
2716    },
2717    Spec {
2718        name: "xreadgroup",
2719        arity: -7,
2720        flags: WRITE_BLOCKING_MOVABLE,
2721        first_key: 0,
2722        last_key: 0,
2723        step: 0,
2724        acl: AC_STREAM_BLOCKING_WRITE,
2725        since: "5.0.0",
2726        complexity: "O(N) in the entries returned.",
2727        summary: "Read as part of a consumer group, waiting if asked to.",
2728        group: "stream",
2729    },
2730    Spec {
2731        name: "xack",
2732        arity: -4,
2733        flags: WRITE_FAST,
2734        first_key: 1,
2735        last_key: 1,
2736        step: 1,
2737        acl: AC_STREAM_WRITE_FAST,
2738        since: "5.0.0",
2739        complexity: "O(1) per ID.",
2740        summary: "Drop entries from a group's pending list.",
2741        group: "stream",
2742    },
2743    Spec {
2744        name: "xsetid",
2745        arity: -3,
2746        flags: WRITE_FAST_OOM,
2747        first_key: 1,
2748        last_key: 1,
2749        step: 1,
2750        acl: AC_STREAM_WRITE_FAST,
2751        since: "5.0.0",
2752        complexity: "O(1)",
2753        summary: "Set the last ID, the entries added and the max deleted ID.",
2754        group: "stream",
2755    },
2756    Spec {
2757        name: "xgroup",
2758        arity: -2,
2759        flags: &[],
2760        first_key: 0,
2761        last_key: 0,
2762        step: 0,
2763        acl: AC_STREAM_CONTAINER,
2764        since: "5.0.0",
2765        complexity: "O(1) for all subcommands except DESTROY, which frees the group's pending list.",
2766        summary: "Make, move and unmake consumer groups.",
2767        group: "stream",
2768    },
2769    Spec {
2770        name: "xinfo",
2771        arity: -2,
2772        flags: &[],
2773        first_key: 0,
2774        last_key: 0,
2775        step: 0,
2776        acl: AC_STREAM_CONTAINER,
2777        since: "5.0.0",
2778        complexity: "O(1), or O(N) with N the entries and pending entries shown when FULL is given.",
2779        summary: "What a stream, its groups and its consumers look like.",
2780        group: "stream",
2781    },
2782    Spec {
2783        name: "xpending",
2784        arity: -3,
2785        flags: READ_SLOW,
2786        first_key: 1,
2787        last_key: 1,
2788        step: 1,
2789        acl: AC_STREAM_READ_SLOW,
2790        since: "5.0.0",
2791        complexity: "O(1) for the summary, O(N) in the entries returned for the list.",
2792        summary: "What a group has handed out and not had acknowledged.",
2793        group: "stream",
2794    },
2795    Spec {
2796        name: "xclaim",
2797        arity: -6,
2798        flags: WRITE_FAST,
2799        first_key: 1,
2800        last_key: 1,
2801        step: 1,
2802        acl: AC_STREAM_WRITE_FAST,
2803        since: "5.0.0",
2804        complexity: "O(1) per ID.",
2805        summary: "Move named pending entries to another consumer.",
2806        group: "stream",
2807    },
2808    Spec {
2809        name: "xautoclaim",
2810        arity: -6,
2811        flags: WRITE_FAST,
2812        first_key: 1,
2813        last_key: 1,
2814        step: 1,
2815        acl: AC_STREAM_WRITE_FAST,
2816        since: "6.2.0",
2817        complexity: "O(1) per entry claimed, plus what it skips getting there.",
2818        summary: "Sweep a group's pending list and take what has gone idle.",
2819        group: "stream",
2820    },
2821    // ------------------------------------------------------------ keyspace
2822    Spec {
2823        name: "del",
2824        arity: -2,
2825        flags: &["write"],
2826        first_key: 1,
2827        last_key: -1,
2828        step: 1,
2829        acl: AC_KEY_WRITE_SLOW,
2830        since: "1.0.0",
2831        complexity: "O(N) in the number of keys.",
2832        summary: "Delete keys and say how many were there.",
2833        group: "keyspace",
2834    },
2835    Spec {
2836        name: "unlink",
2837        arity: -2,
2838        flags: &["write", "fast"],
2839        first_key: 1,
2840        last_key: -1,
2841        step: 1,
2842        acl: AC_KEY_WRITE_FAST,
2843        since: "4.0.0",
2844        complexity: "O(1) per key, since the freeing is not on this thread.",
2845        summary: "Delete keys and free them out of the way of the reply.",
2846        group: "keyspace",
2847    },
2848    Spec {
2849        name: "exists",
2850        arity: -2,
2851        flags: READ_FAST,
2852        first_key: 1,
2853        last_key: -1,
2854        step: 1,
2855        acl: AC_KEY_READ,
2856        since: "1.0.0",
2857        complexity: "O(N) in the number of keys.",
2858        summary: "Count how many of these keys are there, naming one twice counting twice.",
2859        group: "keyspace",
2860    },
2861    Spec {
2862        name: "type",
2863        arity: 2,
2864        flags: READ_FAST,
2865        first_key: 1,
2866        last_key: 1,
2867        step: 1,
2868        acl: AC_KEY_READ,
2869        since: "1.0.0",
2870        complexity: "O(1)",
2871        summary: "What kind of value is under a key, or none.",
2872        group: "keyspace",
2873    },
2874    Spec {
2875        name: "touch",
2876        arity: -2,
2877        flags: READ_FAST,
2878        first_key: 1,
2879        last_key: -1,
2880        step: 1,
2881        acl: AC_KEY_READ,
2882        since: "3.2.1",
2883        complexity: "O(N) in the number of keys.",
2884        summary: "Count how many of these keys are there, and move them up the eviction order.",
2885        group: "keyspace",
2886    },
2887    // The three that look at keys nobody named. No key positions on any of
2888    // them, which is what the zeroes say, and it is also why a cluster client
2889    // sends them to a node rather than to a slot.
2890    Spec {
2891        name: "scan",
2892        arity: -2,
2893        flags: &["readonly"],
2894        first_key: 0,
2895        last_key: 0,
2896        step: 0,
2897        acl: AC_KEY_READ_SLOW,
2898        since: "2.8.0",
2899        complexity: "O(1) a call, O(N) for a whole iteration",
2900        summary: "Walk part of the keyspace and say where to carry on from.",
2901        group: "keyspace",
2902    },
2903    Spec {
2904        name: "keys",
2905        arity: 2,
2906        flags: &["readonly"],
2907        first_key: 0,
2908        last_key: 0,
2909        step: 0,
2910        acl: AC_KEY_READ_ALL,
2911        since: "1.0.0",
2912        complexity: "O(N) in the number of keys.",
2913        summary: "Every key matching a pattern, in one reply.",
2914        group: "keyspace",
2915    },
2916    Spec {
2917        name: "randomkey",
2918        arity: 1,
2919        flags: &["readonly"],
2920        first_key: 0,
2921        last_key: 0,
2922        step: 0,
2923        acl: AC_KEY_READ_SLOW,
2924        since: "1.0.0",
2925        complexity: "O(1)",
2926        summary: "One key from the database, chosen at random.",
2927        group: "keyspace",
2928    },
2929    // Two keys and not one, which is the 1 2 1 in the key positions. Every other
2930    // row in this group names a range that runs to the end of the arguments.
2931    Spec {
2932        name: "rename",
2933        arity: 3,
2934        flags: &["write"],
2935        first_key: 1,
2936        last_key: 2,
2937        step: 1,
2938        acl: AC_KEY_WRITE_SLOW,
2939        since: "1.0.0",
2940        complexity: "O(1)",
2941        summary: "Move a key to another name, over whatever was there.",
2942        group: "keyspace",
2943    },
2944    Spec {
2945        name: "renamenx",
2946        arity: 3,
2947        flags: WRITE_FAST,
2948        first_key: 1,
2949        last_key: 2,
2950        step: 1,
2951        acl: AC_KEY_WRITE_FAST,
2952        since: "1.0.0",
2953        complexity: "O(1)",
2954        summary: "Move a key to another name, but only if that name is free.",
2955        group: "keyspace",
2956    },
2957    // `denyoom` and no `fast`, because this is the one command in the group that
2958    // allocates a whole second value.
2959    Spec {
2960        name: "copy",
2961        arity: -3,
2962        flags: &["write", "denyoom"],
2963        first_key: 1,
2964        last_key: 2,
2965        step: 1,
2966        acl: AC_KEY_WRITE_SLOW,
2967        since: "6.2.0",
2968        complexity: "O(N) in the size of the value.",
2969        summary: "Copy a value to another key, in this database or another one.",
2970        group: "keyspace",
2971    },
2972    // `COPY` with the source deleted, and the only command in the group whose
2973    // second argument is a database rather than a key. The key spec is one key
2974    // at argument one and the database index is not a key, which is why this
2975    // does not look like `COPY` above it.
2976    Spec {
2977        name: "move",
2978        arity: 3,
2979        flags: WRITE_FAST,
2980        first_key: 1,
2981        last_key: 1,
2982        step: 1,
2983        acl: AC_KEY_WRITE_FAST,
2984        since: "1.0.0",
2985        complexity: "O(1)",
2986        summary: "Move a key to another database, if it is not already there.",
2987        group: "keyspace",
2988    },
2989    // The two that block on replication rather than on a key, so they name no
2990    // key at all and the three zeroes below are not a placeholder.
2991    Spec {
2992        name: "wait",
2993        arity: 3,
2994        flags: &["blocking"],
2995        first_key: 0,
2996        last_key: 0,
2997        step: 0,
2998        acl: AC_WAIT,
2999        since: "3.0.0",
3000        complexity: "O(1)",
3001        summary: "Wait for this connection's writes to reach a number of replicas.",
3002        group: "keyspace",
3003    },
3004    Spec {
3005        name: "waitaof",
3006        arity: 4,
3007        flags: &["blocking"],
3008        first_key: 0,
3009        last_key: 0,
3010        step: 0,
3011        acl: AC_WAIT,
3012        since: "7.2.0",
3013        complexity: "O(1)",
3014        summary: "Wait for this connection's writes to reach the append only files.",
3015        group: "keyspace",
3016    },
3017    // The two that speak the file format. A payload is a value standing on its
3018    // own outside the process, so these are the only two commands in the group
3019    // that move a value rather than a name.
3020    Spec {
3021        name: "dump",
3022        arity: 2,
3023        flags: READ_SLOW,
3024        first_key: 1,
3025        last_key: 1,
3026        step: 1,
3027        acl: AC_KEY_READ_SLOW,
3028        since: "2.6.0",
3029        complexity: "O(1) to find the key, then O(N) in the size of the value.",
3030        summary: "Serialize a value into a payload another server can load.",
3031        group: "keyspace",
3032    },
3033    Spec {
3034        name: "restore",
3035        arity: -4,
3036        flags: &["write", "denyoom"],
3037        first_key: 1,
3038        last_key: 1,
3039        step: 1,
3040        acl: AC_RESTORE,
3041        since: "2.6.0",
3042        complexity: "O(1) to find the key, then O(N) in the size of the payload.",
3043        summary: "Create a key from a payload produced by DUMP.",
3044        group: "keyspace",
3045    },
3046    // And the third one, which is the other two with a socket in between. Its
3047    // keys are movable for the same reason `SORT`'s are, though for a plainer
3048    // reason: the `KEYS` option moves them from argument three to everything
3049    // after the word, so where they are depends on what was written.
3050    Spec {
3051        name: "migrate",
3052        arity: -6,
3053        flags: MIGRATE_FLAGS,
3054        first_key: 3,
3055        last_key: 3,
3056        step: 1,
3057        acl: AC_RESTORE,
3058        since: "2.6.0",
3059        complexity: "A DUMP and a DEL here, a RESTORE there, and the bytes in between.",
3060        summary: "Move a key to another server.",
3061        group: "keyspace",
3062    },
3063    // The two whose keys cannot be read off the command. `SORT k BY w_* GET d_*`
3064    // touches every key those two patterns name and a client cannot know which
3065    // ones without the data, so both carry `movablekeys` and Redis's own key
3066    // specs give the same answer: the first key, and the STORE destination if
3067    // there is one.
3068    Spec {
3069        name: "sort",
3070        arity: -2,
3071        flags: WRITE_MOVABLE,
3072        first_key: 1,
3073        last_key: 1,
3074        step: 1,
3075        acl: AC_SORT_WRITE,
3076        since: "1.0.0",
3077        complexity: "O(N+M*log(M)) with N elements and M returned.",
3078        summary: "Sort a list, set or sorted set, optionally into another key.",
3079        group: "keyspace",
3080    },
3081    Spec {
3082        name: "sort_ro",
3083        arity: -2,
3084        flags: READ_MOVABLE,
3085        first_key: 1,
3086        last_key: 1,
3087        step: 1,
3088        acl: AC_SORT_READ,
3089        since: "7.0.0",
3090        complexity: "O(N+M*log(M)) with N elements and M returned.",
3091        summary: "Sort a list, set or sorted set, without the STORE option.",
3092        group: "keyspace",
3093    },
3094    // The four writers take an optional NX, XX, GT or LT, which is the -3 in
3095    // the arity, and they take the same one whichever unit they are in.
3096    Spec {
3097        name: "expire",
3098        arity: -3,
3099        flags: WRITE_FAST,
3100        first_key: 1,
3101        last_key: 1,
3102        step: 1,
3103        acl: AC_KEY_WRITE_FAST,
3104        since: "1.0.0",
3105        complexity: "O(1)",
3106        summary: "Put a deadline on a key, counted in seconds from now.",
3107        group: "keyspace",
3108    },
3109    Spec {
3110        name: "pexpire",
3111        arity: -3,
3112        flags: WRITE_FAST,
3113        first_key: 1,
3114        last_key: 1,
3115        step: 1,
3116        acl: AC_KEY_WRITE_FAST,
3117        since: "2.6.0",
3118        complexity: "O(1)",
3119        summary: "Put a deadline on a key, counted in milliseconds from now.",
3120        group: "keyspace",
3121    },
3122    Spec {
3123        name: "expireat",
3124        arity: -3,
3125        flags: WRITE_FAST,
3126        first_key: 1,
3127        last_key: 1,
3128        step: 1,
3129        acl: AC_KEY_WRITE_FAST,
3130        since: "1.2.0",
3131        complexity: "O(1)",
3132        summary: "Put a deadline on a key, as a unix time in seconds.",
3133        group: "keyspace",
3134    },
3135    Spec {
3136        name: "pexpireat",
3137        arity: -3,
3138        flags: WRITE_FAST,
3139        first_key: 1,
3140        last_key: 1,
3141        step: 1,
3142        acl: AC_KEY_WRITE_FAST,
3143        since: "2.6.0",
3144        complexity: "O(1)",
3145        summary: "Put a deadline on a key, as a unix time in milliseconds.",
3146        group: "keyspace",
3147    },
3148    Spec {
3149        name: "persist",
3150        arity: 2,
3151        flags: WRITE_FAST,
3152        first_key: 1,
3153        last_key: 1,
3154        step: 1,
3155        acl: AC_KEY_WRITE_FAST,
3156        since: "2.2.0",
3157        complexity: "O(1)",
3158        summary: "Take a key's deadline off, so it stops being temporary.",
3159        group: "keyspace",
3160    },
3161    Spec {
3162        name: "ttl",
3163        arity: 2,
3164        flags: READ_FAST,
3165        first_key: 1,
3166        last_key: 1,
3167        step: 1,
3168        acl: AC_KEY_READ,
3169        since: "1.0.0",
3170        complexity: "O(1)",
3171        summary: "How many seconds a key has left, -1 with no deadline, -2 if gone.",
3172        group: "keyspace",
3173    },
3174    Spec {
3175        name: "pttl",
3176        arity: 2,
3177        flags: READ_FAST,
3178        first_key: 1,
3179        last_key: 1,
3180        step: 1,
3181        acl: AC_KEY_READ,
3182        since: "2.6.0",
3183        complexity: "O(1)",
3184        summary: "How many milliseconds a key has left, -1 with no deadline, -2 if gone.",
3185        group: "keyspace",
3186    },
3187    Spec {
3188        name: "expiretime",
3189        arity: 2,
3190        flags: READ_FAST,
3191        first_key: 1,
3192        last_key: 1,
3193        step: 1,
3194        acl: AC_KEY_READ,
3195        since: "7.0.0",
3196        complexity: "O(1)",
3197        summary: "When a key falls due, as a unix time in seconds.",
3198        group: "keyspace",
3199    },
3200    Spec {
3201        name: "pexpiretime",
3202        arity: 2,
3203        flags: READ_FAST,
3204        first_key: 1,
3205        last_key: 1,
3206        step: 1,
3207        acl: AC_KEY_READ,
3208        since: "7.0.0",
3209        complexity: "O(1)",
3210        summary: "When a key falls due, as a unix time in milliseconds.",
3211        group: "keyspace",
3212    },
3213    // A container command, so no keys and no flags of its own: the key is the
3214    // subcommand's and a real server reports it on `object|encoding` rather
3215    // than here. `@slow` is the whole ACL, checked against 8.10.1.
3216    Spec {
3217        name: "object",
3218        arity: -2,
3219        flags: &[],
3220        first_key: 0,
3221        last_key: 0,
3222        step: 0,
3223        acl: &["@slow"],
3224        since: "2.2.3",
3225        complexity: "O(1)",
3226        summary: "Look at the machinery under a key rather than at its value.",
3227        group: "keyspace",
3228    },
3229    // ----------------------------------------------------------- scripting
3230    // Both are containers with no flags and no keys of their own, which is what
3231    // a real 8.10.1 reports: the flags live on the subcommands.
3232    Spec {
3233        name: "script",
3234        arity: -2,
3235        flags: &[],
3236        first_key: 0,
3237        last_key: 0,
3238        step: 0,
3239        acl: &["@slow"],
3240        since: "2.6.0",
3241        complexity: "O(1) for the subcommands that are here.",
3242        summary: "The script cache, which is empty and stays empty until M6.",
3243        group: "scripting",
3244    },
3245    Spec {
3246        name: "function",
3247        arity: -2,
3248        flags: &[],
3249        first_key: 0,
3250        last_key: 0,
3251        step: 0,
3252        acl: &["@slow"],
3253        since: "7.0.0",
3254        complexity: "O(1) for the subcommands that are here.",
3255        summary: "The function libraries, of which there are none until M6.",
3256        group: "scripting",
3257    },
3258    // ---------------------------------------------------------- connection
3259    Spec {
3260        name: "ping",
3261        arity: -1,
3262        flags: &["fast"],
3263        first_key: 0,
3264        last_key: 0,
3265        step: 0,
3266        acl: AC_CONN,
3267        since: "1.0.0",
3268        complexity: "O(1)",
3269        summary: "Ask whether the server is answering.",
3270        group: "connection",
3271    },
3272    Spec {
3273        name: "echo",
3274        arity: 2,
3275        flags: &["loading", "stale", "fast"],
3276        first_key: 0,
3277        last_key: 0,
3278        step: 0,
3279        acl: AC_CONN,
3280        since: "1.0.0",
3281        complexity: "O(1)",
3282        summary: "Send a string back unchanged.",
3283        group: "connection",
3284    },
3285    Spec {
3286        name: "hello",
3287        arity: -1,
3288        flags: &[
3289            "noscript",
3290            "loading",
3291            "stale",
3292            "fast",
3293            "no_auth",
3294            "allow_busy",
3295        ],
3296        first_key: 0,
3297        last_key: 0,
3298        step: 0,
3299        acl: AC_CONN,
3300        since: "6.0.0",
3301        complexity: "O(1)",
3302        summary: "Agree on a protocol version and describe the server.",
3303        group: "connection",
3304    },
3305    Spec {
3306        name: "select",
3307        arity: 2,
3308        flags: &["loading", "stale", "fast"],
3309        first_key: 0,
3310        last_key: 0,
3311        step: 0,
3312        acl: AC_CONN,
3313        since: "1.0.0",
3314        complexity: "O(1)",
3315        summary: "Choose which database this connection works in.",
3316        group: "connection",
3317    },
3318    Spec {
3319        name: "reset",
3320        arity: 1,
3321        flags: &[
3322            "noscript",
3323            "loading",
3324            "stale",
3325            "fast",
3326            "no_auth",
3327            "allow_busy",
3328        ],
3329        first_key: 0,
3330        last_key: 0,
3331        step: 0,
3332        acl: AC_CONN,
3333        since: "6.2.0",
3334        complexity: "O(1)",
3335        summary: "Put the connection back the way it was opened.",
3336        group: "connection",
3337    },
3338    Spec {
3339        name: "quit",
3340        arity: -1,
3341        flags: &[
3342            "noscript",
3343            "loading",
3344            "stale",
3345            "fast",
3346            "no_auth",
3347            "allow_busy",
3348        ],
3349        first_key: 0,
3350        last_key: 0,
3351        step: 0,
3352        acl: AC_CONN,
3353        since: "1.0.0",
3354        complexity: "O(1)",
3355        summary: "Close the connection after the replies already queued.",
3356        group: "connection",
3357    },
3358    // -------------------------------------------------------------- server
3359    // COMMAND is in the connection ACL category and in the server group, which
3360    // is not a contradiction: the category is about what a connection is
3361    // allowed to do and the group is about what the command is about. The group
3362    // is the one reported by COMMAND DOCS, so it is the one that has to match.
3363    Spec {
3364        name: "command",
3365        arity: -1,
3366        flags: &["loading", "stale"],
3367        first_key: 0,
3368        last_key: 0,
3369        step: 0,
3370        acl: &["@slow", "@connection"],
3371        since: "2.8.13",
3372        complexity: "O(N) with N the number of commands",
3373        summary: "What this server can do, in the shape client libraries read.",
3374        group: "server",
3375    },
3376    Spec {
3377        name: "config",
3378        arity: -2,
3379        flags: &[],
3380        first_key: 0,
3381        last_key: 0,
3382        step: 0,
3383        acl: &["@slow"],
3384        since: "2.0.0",
3385        complexity: "Depends on the subcommand.",
3386        summary: "Read and change the settings a running server exposes.",
3387        group: "server",
3388    },
3389    Spec {
3390        name: "info",
3391        arity: -1,
3392        flags: &["loading", "stale"],
3393        first_key: 0,
3394        last_key: 0,
3395        step: 0,
3396        acl: &["@slow", "@dangerous"],
3397        since: "1.0.0",
3398        complexity: "O(1)",
3399        summary: "The server's own numbers, in sections.",
3400        group: "server",
3401    },
3402    Spec {
3403        name: "dbsize",
3404        arity: 1,
3405        flags: READ_FAST,
3406        first_key: 0,
3407        last_key: 0,
3408        step: 0,
3409        acl: AC_KEY_READ,
3410        since: "1.0.0",
3411        complexity: "O(1)",
3412        summary: "How many keys are in the database this connection is on.",
3413        group: "server",
3414    },
3415    Spec {
3416        name: "flushall",
3417        arity: -1,
3418        flags: &["write"],
3419        first_key: 0,
3420        last_key: 0,
3421        step: 0,
3422        acl: AC_KEY_FLUSH,
3423        since: "1.0.0",
3424        complexity: "O(N) in the number of keys in every database.",
3425        summary: "Empty every database.",
3426        group: "server",
3427    },
3428    Spec {
3429        name: "flushdb",
3430        arity: -1,
3431        flags: &["write"],
3432        first_key: 0,
3433        last_key: 0,
3434        step: 0,
3435        acl: AC_KEY_FLUSH,
3436        since: "1.0.0",
3437        complexity: "O(N) in the number of keys in this database.",
3438        summary: "Empty the database this connection is on.",
3439        group: "server",
3440    },
3441    // In the server group and not the keyspace one, which is Redis's answer and
3442    // is the right one: it names no key, it takes two database indexes, and what
3443    // it changes is what every connected client is looking at.
3444    Spec {
3445        name: "swapdb",
3446        arity: 3,
3447        flags: WRITE_FAST,
3448        first_key: 0,
3449        last_key: 0,
3450        step: 0,
3451        acl: AC_SWAPDB,
3452        since: "4.0.0",
3453        complexity: "O(N) in the number of clients watching or blocked on either.",
3454        summary: "Swap two databases, so every client on one sees the other.",
3455        group: "server",
3456    },
3457    // No ACL category but `@fast`, which is Redis's answer and reads like an
3458    // omission. It is not: the categories are about what a command can reach and
3459    // this one reaches nothing.
3460    Spec {
3461        name: "time",
3462        arity: 1,
3463        flags: &["loading", "stale", "fast"],
3464        first_key: 0,
3465        last_key: 0,
3466        step: 0,
3467        acl: &["@fast"],
3468        since: "2.6.0",
3469        complexity: "O(1)",
3470        summary: "The server's clock, as seconds and microseconds.",
3471        group: "server",
3472    },
3473];
3474
3475/// The shortest and the longest command name.
3476///
3477/// Both are facts about [`COMMANDS`], pinned by a test, and both are checked
3478/// before anything is read, so a name that could not be a command is rejected on
3479/// its length alone.
3480const MIN_LEN: usize = 3;
3481const MAX_LEN: usize = 20;
3482
3483/// How many slots the index has, which is a power of two and a bit over twice
3484/// the number of commands.
3485///
3486/// One kibibyte of `u16`, eight cache lines, and loose enough that a probe for a
3487/// name that is not a command stops at an empty slot almost immediately. Tight
3488/// enough that the whole thing stays resident next to the table it indexes.
3489const SLOTS: usize = 512;
3490
3491/// A slot nothing was put in.
3492///
3493/// `u16::MAX` and not zero, because zero is `set` and `set` is the command this
3494/// most wants to be able to find.
3495const FREE: u16 = u16::MAX;
3496
3497/// The multiplier, found by searching for one that spreads these 241 names well.
3498///
3499/// Not a magic constant in the bad sense: it is checked. Every command is looked
3500/// up by its own name in a test, and another test holds the worst probe length
3501/// at what it is now, so a command added later that made this multiplier bad
3502/// would fail rather than quietly cost every lookup an extra slot.
3503///
3504/// It has been searched for six times, and each time because the test went red
3505/// rather than because somebody went looking. The first was against the 191 names
3506/// in the table then, the ten graph commands pushed its worst probe to three
3507/// slots, and the second search was run over all 201. The fifteen stream commands
3508/// pushed that one to four slots and fifty one extra probes, so the third was run
3509/// over all 216, and the three 8.x pending list commands cost that one two more
3510/// probes than the test allows. The fourth was over 219 and the seven bitmap
3511/// commands took it to three slots, and the fifth was over all 226. The five
3512/// HyperLogLog commands kept its worst probe at two and took it from forty nine
3513/// extra slots to fifty five, and the search over the 231 names found nothing
3514/// better, so that one stood. The ten geo commands took it to sixty, and the
3515/// sixth search, over eight million multipliers and all 241 names, found this
3516/// one at fifty six. Twenty nine of the names collide on the key itself and no
3517/// multiplier can separate them, which is the floor everything here is measured
3518/// against.
3519const MIX: u64 = 0xf9e1_1b95_048d_6851;
3520
3521/// The four bytes the index is computed from: the length, the first two bytes
3522/// and the last, lower cased.
3523///
3524/// `None` for a name no command could be spelled as, which is decided on the
3525/// length before a byte is read.
3526///
3527/// Four bytes and not the whole name because the whole name has to be compared
3528/// at the end anyway, so the hash only has to be good enough to get to the right
3529/// slot, and reading less of the name is a shorter dependency chain in front of
3530/// the multiply. These four leave 226 distinct values over the 241 commands, so
3531/// twenty nine names collide whatever the multiplier is and probe once more, and
3532/// the probe is the same compare the lookup was always going to do. `setnx` and
3533/// `setex` are one of those groups and `g.nadd` and `g.eadd` are another, and the
3534/// bitmaps added two more, `getset` with `getbit` and `setbit` with `select`. The
3535/// eighteen stream commands are in none of them, neither are the five
3536/// HyperLogLog ones, and neither are the ten geo ones, since a name is keyed on
3537/// its first two bytes and its last and no two of either agree on all three.
3538///
3539/// `| 0x20` lower cases a letter and does not have to be told which bytes are
3540/// letters. It maps the two cases of a name to the same number, which is all
3541/// this needs, and every command name is letters.
3542const fn key_of(name: &[u8]) -> Option<u32> {
3543    if name.len() < MIN_LEN || name.len() > MAX_LEN {
3544        return None;
3545    }
3546    let last = name.len() - 1;
3547    Some(
3548        name.len() as u32
3549            | ((name[0] | 0x20) as u32) << 8
3550            | ((name[1] | 0x20) as u32) << 16
3551            | ((name[last] | 0x20) as u32) << 24,
3552    )
3553}
3554
3555/// Where a key wants to sit.
3556const fn slot_of(key: u32) -> usize {
3557    ((key as u64).wrapping_mul(MIX) >> 55) as usize & (SLOTS - 1)
3558}
3559
3560/// The index, built at compile time by inserting every command in table order.
3561///
3562/// Table order is rough order of how often a command is sent, and inserting in
3563/// that order means the hotter of two commands that want the same slot gets it
3564/// and the colder one probes, which is the right way round.
3565const INDEX: [u16; SLOTS] = index();
3566
3567const fn index() -> [u16; SLOTS] {
3568    let mut out = [FREE; SLOTS];
3569    let mut i = 0;
3570    while i < COMMANDS.len() {
3571        let key = match key_of(COMMANDS[i].name.as_bytes()) {
3572            Some(key) => key,
3573            None => panic!("a command name is outside MIN_LEN..=MAX_LEN"),
3574        };
3575        let mut at = slot_of(key);
3576        while out[at] != FREE {
3577            at = (at + 1) & (SLOTS - 1);
3578        }
3579        out[at] = i as u16;
3580        i += 1;
3581    }
3582    out
3583}
3584
3585/// The command called `name`, whatever case the client spelled it in.
3586///
3587/// This used to walk the whole table comparing lengths, and the cost of that was
3588/// not what it looked like. The table is written in rough order of how often a
3589/// command is sent, so `set` and `get` were the first two entries and cost one
3590/// compare, but `exists` is the hundred and forty ninth and `del` the hundred and
3591/// forty seventh, and every one of those compares was paid twice per command,
3592/// once to work out the key hash and once to dispatch.
3593///
3594/// Measured, that walk was 104 nanoseconds a command, which is more than a whole
3595/// `GET` costs end to end. `EXISTS` on a missing key ran at three and a half
3596/// times `GET` and almost none of the difference was the command: short
3597/// circuiting the lookup alone took it from 8.7 microseconds a batch of sixty
3598/// four to 2.0, and left it faster than `GET`, which it should be, because it
3599/// does less.
3600///
3601/// So this is one multiply and one load into a kibibyte, and then the same name
3602/// compare it always ended with. What it costs the hot commands is a multiply
3603/// they did not use to pay and a load that hits, and what it saves the rest is
3604/// the whole walk.
3605#[must_use]
3606pub fn lookup(name: &[u8]) -> Option<&'static Spec> {
3607    at(lookup_index(name))
3608}
3609
3610/// The same, answering with a position in the table rather than a reference.
3611///
3612/// This is where the lookup actually ends, because the index is what the slots
3613/// hold. It is here as its own function because a position fits in a `u16` and a
3614/// reference does not fit anywhere a framed command can carry it cheaply, so the
3615/// engine resolves a command's name once when it frames it and hands the number
3616/// on to both the key hash and the dispatcher.
3617///
3618/// `u16::MAX` is the answer for a name that is not a command, which is not a
3619/// special case anybody has to write down: the table is 191 entries, so [`at`]
3620/// hands back `None` for it the same way it would for any other number past the
3621/// end.
3622#[must_use]
3623pub fn lookup_index(name: &[u8]) -> u16 {
3624    let Some(key) = key_of(name) else {
3625        return FREE;
3626    };
3627    let mut at = slot_of(key);
3628    loop {
3629        let i = INDEX[at];
3630        if i == FREE {
3631            return FREE;
3632        }
3633        if COMMANDS[i as usize]
3634            .name
3635            .as_bytes()
3636            .eq_ignore_ascii_case(name)
3637        {
3638            return i;
3639        }
3640        at = (at + 1) & (SLOTS - 1);
3641    }
3642}
3643
3644/// The command at `i`, or `None` if there is none there.
3645///
3646/// The other half of [`lookup_index`], and the only thing that should ever be
3647/// handed one of its answers.
3648#[must_use]
3649pub fn at(i: u16) -> Option<&'static Spec> {
3650    COMMANDS.get(i as usize)
3651}
3652
3653/// How many commands there are.
3654///
3655/// The length of a counter array that has a row per command, which is the only
3656/// thing that wants this number.
3657#[must_use]
3658pub const fn count() -> usize {
3659    COMMANDS.len()
3660}
3661
3662/// Where in [`COMMANDS`] this spec is.
3663///
3664/// Every `&'static Spec` a caller can hold came out of [`lookup`] and therefore
3665/// points into that array, so its position is the distance from the front
3666/// measured in whole `Spec`s. That is arithmetic on two addresses and not a
3667/// search, which is the point: a per command counter has to be reachable from
3668/// the spec the dispatcher is already holding without walking the table a second
3669/// time.
3670///
3671/// A spec from somewhere else would answer nonsense, which is why this takes a
3672/// `&'static Spec` rather than a `&Spec`: the only `'static` ones are in the
3673/// table.
3674#[must_use]
3675pub fn index_of(spec: &'static Spec) -> usize {
3676    let front = COMMANDS.as_ptr().addr();
3677    let here = std::ptr::from_ref(spec).addr();
3678    (here - front) / size_of::<Spec>()
3679}
3680
3681/// The name of the command at `at`, which is [`index_of`] the other way round.
3682///
3683/// # Panics
3684///
3685/// If `at` is past the end of the table, which only a caller that made the index
3686/// up rather than getting it from [`index_of`] can manage.
3687#[must_use]
3688pub fn name_at(at: usize) -> &'static str {
3689    COMMANDS[at].name
3690}
3691
3692/// Whether `n` arguments, counting the name, satisfy this command's arity.
3693#[must_use]
3694pub fn arity_ok(spec: &Spec, n: usize) -> bool {
3695    let n = n as i32;
3696    if spec.arity >= 0 {
3697        n == spec.arity
3698    } else {
3699        n >= -spec.arity
3700    }
3701}
3702
3703#[cfg(test)]
3704mod tests {
3705    use super::*;
3706
3707    #[test]
3708    fn every_name_is_lower_case_and_appears_once() {
3709        let mut seen = std::collections::BTreeSet::new();
3710        for c in COMMANDS {
3711            assert_eq!(
3712                c.name,
3713                c.name.to_lowercase(),
3714                "{} is not lower case",
3715                c.name
3716            );
3717            assert!(seen.insert(c.name), "{} is in the table twice", c.name);
3718        }
3719    }
3720
3721    /// Every command's index is where the table actually holds it.
3722    ///
3723    /// Checked against the position a search finds, over the whole table rather
3724    /// than a sample, because the arithmetic is the thing being tested and an
3725    /// off by one in it would put every counter on the wrong command.
3726    #[test]
3727    fn a_spec_knows_where_it_is_in_the_table() {
3728        assert_eq!(count(), COMMANDS.len());
3729        for (want, spec) in COMMANDS.iter().enumerate() {
3730            assert_eq!(index_of(spec), want, "{} is at the wrong index", spec.name);
3731        }
3732        assert_eq!(
3733            index_of(lookup(b"get").unwrap()),
3734            index_of(lookup(b"GET").unwrap())
3735        );
3736    }
3737
3738    #[test]
3739    fn lookup_ignores_case_and_does_not_match_a_prefix() {
3740        assert_eq!(lookup(b"GET").unwrap().name, "get");
3741        assert_eq!(lookup(b"gEt").unwrap().name, "get");
3742        assert!(lookup(b"ge").is_none());
3743        assert!(lookup(b"gets").is_none());
3744    }
3745
3746    /// Every command is findable under its own name, in either case.
3747    ///
3748    /// The index is built at compile time from the table it sits beside, so what
3749    /// a test can still catch is a command that the build put somewhere the
3750    /// lookup does not walk past, which is what a probe that stopped early would
3751    /// look like.
3752    #[test]
3753    fn every_command_is_findable_by_its_own_name() {
3754        for spec in COMMANDS {
3755            let found = lookup(spec.name.as_bytes()).expect(spec.name);
3756            assert_eq!(
3757                index_of(found),
3758                index_of(spec),
3759                "{} found the wrong spec",
3760                spec.name
3761            );
3762            assert_eq!(
3763                lookup(spec.name.to_ascii_uppercase().as_bytes()).map(index_of),
3764                Some(index_of(spec)),
3765                "{} is not found in upper case",
3766                spec.name,
3767            );
3768        }
3769    }
3770
3771    /// A name that cannot be a command is answered before anything is compared.
3772    #[test]
3773    fn a_name_that_cannot_be_a_command_is_rejected_on_its_shape() {
3774        assert!(lookup(b"").is_none());
3775        assert!(key_of(b"").is_none());
3776        assert!(key_of(&[b'g'; 256]).is_none());
3777        assert!(lookup(&[b'g'; 256]).is_none());
3778        assert!(lookup(b"9et").is_none());
3779    }
3780
3781    /// The two cases of a name give the same key and different names do not.
3782    #[test]
3783    fn a_key_folds_the_case_and_nothing_else() {
3784        assert_eq!(key_of(b"get"), key_of(b"GET"));
3785        assert_eq!(key_of(b"get"), key_of(b"gEt"));
3786        assert_ne!(key_of(b"get"), key_of(b"set"), "other first byte");
3787        assert_ne!(key_of(b"get"), key_of(b"gxt"), "other second byte");
3788        assert_ne!(key_of(b"get"), key_of(b"gex"), "other last byte");
3789        assert_ne!(key_of(b"get"), key_of(b"gett"), "other length");
3790    }
3791
3792    /// The index is still worth having, which is a thing that can rot.
3793    ///
3794    /// The multiplier was searched for against the 191 commands that were in the
3795    /// table when it was written, and five times since. Adding commands cannot make a lookup wrong,
3796    /// because a probe walks to an empty slot and every candidate has its name
3797    /// compared, but it can make one slow, and a slow lookup is exactly the thing
3798    /// this replaced. So the worst probe is written down here: if a command
3799    /// added later pushes it up, somebody searches for a new multiplier or a
3800    /// bigger table rather than finding out from a benchmark six months later.
3801    #[test]
3802    fn no_command_is_more_than_two_slots_from_where_it_wants_to_be() {
3803        let mut worst = 0;
3804        let mut total = 0;
3805        for spec in COMMANDS {
3806            let key = key_of(spec.name.as_bytes()).expect(spec.name);
3807            let home = slot_of(key);
3808            let mut at = home;
3809            let mut steps = 0;
3810            while INDEX[at] as usize != index_of(spec) {
3811                at = (at + 1) & (SLOTS - 1);
3812                steps += 1;
3813                assert!(steps < SLOTS, "{} is not in the index at all", spec.name);
3814            }
3815            worst = worst.max(steps);
3816            total += steps;
3817        }
3818        assert!(worst <= 2, "worst probe is {worst} slots");
3819        assert!(
3820            total <= 56,
3821            "{total} extra slots walked over the whole table"
3822        );
3823    }
3824
3825    /// The table has room to probe in, which is what stops the loop.
3826    #[test]
3827    fn the_index_is_not_full() {
3828        assert!(
3829            COMMANDS.len() < SLOTS,
3830            "the probe would never find an empty"
3831        );
3832        assert!(
3833            COMMANDS.len() < FREE as usize,
3834            "an index would collide with FREE"
3835        );
3836        let free = INDEX.iter().filter(|&&i| i == FREE).count();
3837        assert_eq!(free, SLOTS - COMMANDS.len());
3838    }
3839
3840    #[test]
3841    fn arity_counts_the_command_name() {
3842        let get = lookup(b"get").unwrap();
3843        assert!(!arity_ok(get, 1));
3844        assert!(arity_ok(get, 2));
3845        assert!(!arity_ok(get, 3));
3846
3847        // A negative arity is a minimum, which is how SET takes its options.
3848        let set = lookup(b"set").unwrap();
3849        assert!(!arity_ok(set, 2));
3850        assert!(arity_ok(set, 3));
3851        assert!(arity_ok(set, 9));
3852    }
3853
3854    /// A key spec that is wrong sends a cluster client to the wrong node, so
3855    /// the pair commands are worth stating twice.
3856    #[test]
3857    fn the_pair_commands_step_two_keys_at_a_time() {
3858        for name in [b"mset".as_slice(), b"msetnx"] {
3859            let c = lookup(name).unwrap();
3860            assert_eq!((c.first_key, c.last_key, c.step), (1, -1, 2));
3861        }
3862        let mget = lookup(b"mget").unwrap();
3863        assert_eq!((mget.first_key, mget.last_key, mget.step), (1, -1, 1));
3864        // MSETEX counts its keys in an argument, so there is no static spec
3865        // for them and a client has to ask with COMMAND GETKEYS.
3866        let msetex = lookup(b"msetex").unwrap();
3867        assert_eq!((msetex.first_key, msetex.last_key, msetex.step), (0, 0, 0));
3868        assert!(msetex.flags.contains(&"movablekeys"));
3869    }
3870}