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/// The read side categories.
52const AC_READ_FAST: &[&str] = &["@read", "@string", "@fast"];
53/// The read side categories for the ones that walk the value.
54const AC_READ_SLOW: &[&str] = &["@read", "@string", "@slow"];
55/// The write side categories.
56const AC_WRITE_FAST: &[&str] = &["@write", "@string", "@fast"];
57/// The write side categories for the ones that are not counted as fast.
58const AC_WRITE_SLOW: &[&str] = &["@write", "@string", "@slow"];
59/// A write that frees rather than allocates, so Redis does not mark it denyoom.
60const WRITE_FAST: &[&str] = &["write", "fast"];
61/// The set read side, for the ones that answer without walking the members.
62const AC_SET_READ_FAST: &[&str] = &["@read", "@set", "@fast"];
63/// The set read side for the ones that walk the members.
64const AC_SET_READ_SLOW: &[&str] = &["@read", "@set", "@slow"];
65/// The set write side.
66const AC_SET_WRITE_FAST: &[&str] = &["@write", "@set", "@fast"];
67/// The set write side for the ones that walk whole sets to decide what to
68/// write, which is the whole `*STORE` family.
69const AC_SET_WRITE_SLOW: &[&str] = &["@write", "@set", "@slow"];
70/// The hash read side, for the ones that answer without walking the fields.
71const AC_HASH_READ_FAST: &[&str] = &["@read", "@hash", "@fast"];
72/// The hash read side for the ones that walk the fields.
73const AC_HASH_READ_SLOW: &[&str] = &["@read", "@hash", "@slow"];
74/// The hash write side.
75const AC_HASH_WRITE_FAST: &[&str] = &["@write", "@hash", "@fast"];
76/// Read only and not counted as fast, which is every list read that walks.
77const READ_SLOW: &[&str] = &["readonly"];
78/// A write that is not counted as fast and does not allocate, which on the list
79/// side is `LREM` and `LTRIM` and nothing else.
80const WRITE_SLOW: &[&str] = &["write"];
81/// The list read side, for the two that answer without walking the elements.
82const AC_LIST_READ_FAST: &[&str] = &["@read", "@list", "@fast"];
83/// The list read side for the ones that walk.
84const AC_LIST_READ_SLOW: &[&str] = &["@read", "@list", "@slow"];
85/// The list write side, which is the pushes and the pops. Redis counts a push
86/// as fast even though it can split a chunk, because the split is amortised.
87const AC_LIST_WRITE_FAST: &[&str] = &["@write", "@list", "@fast"];
88/// The list write side for the ones whose cost is the length of the list.
89const AC_LIST_WRITE_SLOW: &[&str] = &["@write", "@list", "@slow"];
90/// The five that can wait, which carry a category of their own so that an ACL
91/// can say "this user may not park a connection" without naming five commands.
92const AC_LIST_WRITE_BLOCKING: &[&str] = &["@write", "@list", "@slow", "@blocking"];
93/// The sorted set read side, for the ones that answer without walking members.
94const AC_ZSET_READ_FAST: &[&str] = &["@read", "@sortedset", "@fast"];
95/// The sorted set read side for the ones that walk members.
96const AC_ZSET_READ_SLOW: &[&str] = &["@read", "@sortedset", "@slow"];
97/// The sorted set write side.
98const AC_ZSET_WRITE_FAST: &[&str] = &["@write", "@sortedset", "@fast"];
99/// The sorted set write side for the ones whose cost is the size of the window
100/// they touch, which is the removals and `ZRANGESTORE`.
101const AC_ZSET_WRITE_SLOW: &[&str] = &["@write", "@sortedset", "@slow"];
102/// The two sorted set pops that can wait, which Redis counts as fast because
103/// each of them takes one member.
104const AC_ZSET_BLOCKING_FAST: &[&str] = &["@write", "@sortedset", "@fast", "@blocking"];
105/// And `BZMPOP`, whose cost is the number of keys named and the count popped.
106const AC_ZSET_BLOCKING_SLOW: &[&str] = &["@write", "@sortedset", "@slow", "@blocking"];
107/// The array read side, for the ones whose cost is the number of indices named
108/// and not the size of the array.
109const AC_ARRAY_READ_FAST: &[&str] = &["@read", "@array", "@fast"];
110/// The array read side for `ARGETRANGE`, which answers once per position in the
111/// range and so costs the range rather than the population.
112const AC_ARRAY_READ_SLOW: &[&str] = &["@read", "@array", "@slow"];
113/// The array write side.
114const AC_ARRAY_WRITE_FAST: &[&str] = &["@write", "@array", "@fast"];
115/// The array write side for `ARDELRANGE`, the one array command Redis does not
116/// mark fast.
117const AC_ARRAY_WRITE_SLOW: &[&str] = &["@write", "@array", "@slow"];
118/// Read only and not counted as fast, for a command whose keys are counted
119/// rather than positioned, so a client has to read the key specs to route it.
120const READ_MOVABLE: &[&str] = &["readonly", "movablekeys"];
121/// The same for a write, which is the three store forms.
122const WRITE_MOVABLE: &[&str] = &["write", "denyoom", "movablekeys"];
123/// The connection commands' categories.
124const AC_CONN: &[&str] = &["@fast", "@connection"];
125/// The keyspace read side, which is `EXISTS` and `TYPE`.
126const AC_KEY_READ: &[&str] = &["@keyspace", "@read", "@fast"];
127/// The keyspace reads that walk something, which is `SCAN` and `RANDOMKEY`.
128const AC_KEY_READ_SLOW: &[&str] = &["@keyspace", "@read", "@slow"];
129/// And `KEYS`, which is the same walk without a bound on it and is the one read
130/// in this group Redis calls dangerous.
131const AC_KEY_READ_ALL: &[&str] = &["@keyspace", "@read", "@slow", "@dangerous"];
132/// The keyspace writes that are allowed to cost what the value costs. `DEL`
133/// frees on the spot and `COPY` clones a body, and `RENAME` is in here with
134/// them even though it moves thirteen bytes, because Redis says slow for it and
135/// this list is Redis's list rather than ours.
136const AC_KEY_WRITE_SLOW: &[&str] = &["@keyspace", "@write", "@slow"];
137/// `UNLINK`, which Redis does count as fast because it does not, and the
138/// expiry writers, which move a deadline and never touch a value.
139const AC_KEY_WRITE_FAST: &[&str] = &["@keyspace", "@write", "@fast"];
140/// The two that empty a database, which are in the dangerous category.
141const AC_KEY_FLUSH: &[&str] = &["@keyspace", "@write", "@slow", "@dangerous"];
142
143/// Every command this server answers, in the order the groups ship.
144pub static COMMANDS: &[Spec] = &[
145    // ------------------------------------------------------------- strings
146    Spec {
147        name: "set",
148        arity: -3,
149        flags: WRITE_OOM,
150        first_key: 1,
151        last_key: 1,
152        step: 1,
153        acl: AC_WRITE_SLOW,
154        since: "1.0.0",
155        complexity: "O(1)",
156        summary: "Set a key to a string value, whatever it held before.",
157        group: "string",
158    },
159    Spec {
160        name: "get",
161        arity: 2,
162        flags: READ_FAST,
163        first_key: 1,
164        last_key: 1,
165        step: 1,
166        acl: AC_READ_FAST,
167        since: "1.0.0",
168        complexity: "O(1)",
169        summary: "The string value of a key.",
170        group: "string",
171    },
172    Spec {
173        name: "getset",
174        arity: 3,
175        flags: WRITE_FAST_OOM,
176        first_key: 1,
177        last_key: 1,
178        step: 1,
179        acl: AC_WRITE_FAST,
180        since: "1.0.0",
181        complexity: "O(1)",
182        summary: "Set a key and hand back what it held.",
183        group: "string",
184    },
185    Spec {
186        name: "getdel",
187        arity: 2,
188        flags: &["write", "fast"],
189        first_key: 1,
190        last_key: 1,
191        step: 1,
192        acl: AC_WRITE_FAST,
193        since: "6.2.0",
194        complexity: "O(1)",
195        summary: "Read a key and delete it in the same step.",
196        group: "string",
197    },
198    Spec {
199        name: "getex",
200        arity: -2,
201        flags: &["write", "fast"],
202        first_key: 1,
203        last_key: 1,
204        step: 1,
205        acl: AC_WRITE_FAST,
206        since: "6.2.0",
207        complexity: "O(1)",
208        summary: "Read a key and change its deadline in the same step.",
209        group: "string",
210    },
211    Spec {
212        name: "setnx",
213        arity: 3,
214        flags: WRITE_FAST_OOM,
215        first_key: 1,
216        last_key: 1,
217        step: 1,
218        acl: AC_WRITE_FAST,
219        since: "1.0.0",
220        complexity: "O(1)",
221        summary: "Set a key only if it is not there.",
222        group: "string",
223    },
224    Spec {
225        name: "setex",
226        arity: 4,
227        flags: WRITE_OOM,
228        first_key: 1,
229        last_key: 1,
230        step: 1,
231        acl: AC_WRITE_SLOW,
232        since: "2.0.0",
233        complexity: "O(1)",
234        summary: "Set a key and give it a deadline in seconds.",
235        group: "string",
236    },
237    Spec {
238        name: "psetex",
239        arity: 4,
240        flags: WRITE_OOM,
241        first_key: 1,
242        last_key: 1,
243        step: 1,
244        acl: AC_WRITE_SLOW,
245        since: "2.6.0",
246        complexity: "O(1)",
247        summary: "Set a key and give it a deadline in milliseconds.",
248        group: "string",
249    },
250    Spec {
251        name: "mset",
252        arity: -3,
253        flags: WRITE_OOM,
254        first_key: 1,
255        last_key: -1,
256        step: 2,
257        acl: AC_WRITE_SLOW,
258        since: "1.0.1",
259        complexity: "O(N) with N the number of keys",
260        summary: "Set several keys, all of them or none.",
261        group: "string",
262    },
263    Spec {
264        name: "msetnx",
265        arity: -3,
266        flags: WRITE_OOM,
267        first_key: 1,
268        last_key: -1,
269        step: 2,
270        acl: AC_WRITE_SLOW,
271        since: "1.0.1",
272        complexity: "O(N) with N the number of keys",
273        summary: "Set several keys only if none of them are there.",
274        group: "string",
275    },
276    Spec {
277        name: "mget",
278        arity: -2,
279        flags: READ_FAST,
280        first_key: 1,
281        last_key: -1,
282        step: 1,
283        acl: AC_READ_FAST,
284        since: "1.0.0",
285        complexity: "O(N) with N the number of keys",
286        summary: "The values of several keys, in the order asked for.",
287        group: "string",
288    },
289    Spec {
290        name: "append",
291        arity: 3,
292        flags: WRITE_FAST_OOM,
293        first_key: 1,
294        last_key: 1,
295        step: 1,
296        acl: AC_WRITE_FAST,
297        since: "2.0.0",
298        complexity: "O(M) with M the length of the value being appended",
299        summary: "Add to the end of a string, creating it if it is not there.",
300        group: "string",
301    },
302    Spec {
303        name: "strlen",
304        arity: 2,
305        flags: READ_FAST,
306        first_key: 1,
307        last_key: 1,
308        step: 1,
309        acl: AC_READ_FAST,
310        since: "2.2.0",
311        complexity: "O(1)",
312        summary: "How long a string value is, without reading it.",
313        group: "string",
314    },
315    Spec {
316        name: "setrange",
317        arity: 4,
318        flags: WRITE_OOM,
319        first_key: 1,
320        last_key: 1,
321        step: 1,
322        acl: AC_WRITE_SLOW,
323        since: "2.2.0",
324        complexity: "O(M) with M the length of the replacement",
325        summary: "Overwrite part of a string at an offset, zero filling the gap.",
326        group: "string",
327    },
328    Spec {
329        name: "getrange",
330        arity: 4,
331        flags: &["readonly"],
332        first_key: 1,
333        last_key: 1,
334        step: 1,
335        acl: AC_READ_SLOW,
336        since: "2.4.0",
337        complexity: "O(N) with N the length of the answer",
338        summary: "Part of a string, by an inclusive range that may count backwards.",
339        group: "string",
340    },
341    Spec {
342        name: "substr",
343        arity: 4,
344        flags: &["readonly"],
345        first_key: 1,
346        last_key: 1,
347        step: 1,
348        acl: AC_READ_SLOW,
349        since: "1.0.0",
350        complexity: "O(N) with N the length of the answer",
351        summary: "GETRANGE under the name it had before 2.4.",
352        group: "string",
353    },
354    Spec {
355        name: "incr",
356        arity: 2,
357        flags: WRITE_FAST_OOM,
358        first_key: 1,
359        last_key: 1,
360        step: 1,
361        acl: AC_WRITE_FAST,
362        since: "1.0.0",
363        complexity: "O(1)",
364        summary: "Add one, starting from zero if the key is not there.",
365        group: "string",
366    },
367    Spec {
368        name: "decr",
369        arity: 2,
370        flags: WRITE_FAST_OOM,
371        first_key: 1,
372        last_key: 1,
373        step: 1,
374        acl: AC_WRITE_FAST,
375        since: "1.0.0",
376        complexity: "O(1)",
377        summary: "Take one away, starting from zero if the key is not there.",
378        group: "string",
379    },
380    Spec {
381        name: "incrby",
382        arity: 3,
383        flags: WRITE_FAST_OOM,
384        first_key: 1,
385        last_key: 1,
386        step: 1,
387        acl: AC_WRITE_FAST,
388        since: "1.0.0",
389        complexity: "O(1)",
390        summary: "Add a number, starting from zero if the key is not there.",
391        group: "string",
392    },
393    Spec {
394        name: "decrby",
395        arity: 3,
396        flags: WRITE_FAST_OOM,
397        first_key: 1,
398        last_key: 1,
399        step: 1,
400        acl: AC_WRITE_FAST,
401        since: "1.0.0",
402        complexity: "O(1)",
403        summary: "Take a number away, starting from zero if the key is not there.",
404        group: "string",
405    },
406    Spec {
407        name: "incrbyfloat",
408        arity: 3,
409        flags: WRITE_FAST_OOM,
410        first_key: 1,
411        last_key: 1,
412        step: 1,
413        acl: AC_WRITE_FAST,
414        since: "2.6.0",
415        complexity: "O(1)",
416        summary: "Add a float, starting from zero if the key is not there.",
417        group: "string",
418    },
419    Spec {
420        name: "lcs",
421        arity: -3,
422        flags: &["readonly"],
423        first_key: 1,
424        last_key: 2,
425        step: 1,
426        acl: AC_READ_SLOW,
427        since: "7.0.0",
428        complexity: "O(N*M) with N and M the lengths of the two values",
429        summary: "The longest subsequence two string values have in common.",
430        group: "string",
431    },
432    Spec {
433        name: "msetex",
434        arity: -4,
435        flags: &["write", "denyoom", "movablekeys"],
436        first_key: 0,
437        last_key: 0,
438        step: 0,
439        acl: AC_WRITE_SLOW,
440        since: "8.4.0",
441        complexity: "O(N) with N the number of keys",
442        summary: "Set several keys with one deadline and one condition over all of them.",
443        group: "string",
444    },
445    Spec {
446        name: "delex",
447        arity: -2,
448        flags: &["write", "fast"],
449        first_key: 1,
450        last_key: 1,
451        step: 1,
452        acl: AC_WRITE_FAST,
453        since: "8.4.0",
454        complexity: "O(1) by value, O(N) by digest",
455        summary: "Delete a key only if it still holds what the caller thinks.",
456        group: "string",
457    },
458    Spec {
459        name: "digest",
460        arity: 2,
461        flags: READ_FAST,
462        first_key: 1,
463        last_key: 1,
464        step: 1,
465        acl: AC_READ_FAST,
466        since: "8.4.0",
467        complexity: "O(N) with N the length of the value",
468        summary: "The XXH3 of a string value, as sixteen hex characters.",
469        group: "string",
470    },
471    Spec {
472        name: "increx",
473        arity: -2,
474        flags: WRITE_FAST_OOM,
475        first_key: 1,
476        last_key: 1,
477        step: 1,
478        acl: AC_WRITE_FAST,
479        since: "8.8.0",
480        complexity: "O(1)",
481        summary: "Count, with a bound, a saturation policy and a deadline.",
482        group: "string",
483    },
484    // ----------------------------------------------------------------- sets
485    Spec {
486        name: "sadd",
487        arity: -3,
488        flags: WRITE_FAST_OOM,
489        first_key: 1,
490        last_key: 1,
491        step: 1,
492        acl: AC_SET_WRITE_FAST,
493        since: "1.0.0",
494        complexity: "O(N) with N the number of members being added",
495        summary: "Add members to a set, creating it if it is not there.",
496        group: "set",
497    },
498    Spec {
499        name: "srem",
500        arity: -3,
501        flags: WRITE_FAST,
502        first_key: 1,
503        last_key: 1,
504        step: 1,
505        acl: AC_SET_WRITE_FAST,
506        since: "1.0.0",
507        complexity: "O(N) with N the number of members being removed",
508        summary: "Take members out of a set, deleting the key if none are left.",
509        group: "set",
510    },
511    Spec {
512        name: "scard",
513        arity: 2,
514        flags: READ_FAST,
515        first_key: 1,
516        last_key: 1,
517        step: 1,
518        acl: AC_SET_READ_FAST,
519        since: "1.0.0",
520        complexity: "O(1)",
521        summary: "How many members a set has.",
522        group: "set",
523    },
524    Spec {
525        name: "sismember",
526        arity: 3,
527        flags: READ_FAST,
528        first_key: 1,
529        last_key: 1,
530        step: 1,
531        acl: AC_SET_READ_FAST,
532        since: "1.0.0",
533        complexity: "O(1)",
534        summary: "Whether a member is in a set.",
535        group: "set",
536    },
537    Spec {
538        name: "smismember",
539        arity: -3,
540        flags: READ_FAST,
541        first_key: 1,
542        last_key: 1,
543        step: 1,
544        acl: AC_SET_READ_FAST,
545        since: "6.2.0",
546        complexity: "O(N) with N the number of members being asked about",
547        summary: "Whether each of several members is in a set, in the order asked.",
548        group: "set",
549    },
550    Spec {
551        name: "smembers",
552        arity: 2,
553        flags: &["readonly"],
554        first_key: 1,
555        last_key: 1,
556        step: 1,
557        acl: AC_SET_READ_SLOW,
558        since: "1.0.0",
559        complexity: "O(N) with N the size of the set",
560        summary: "Every member of a set.",
561        group: "set",
562    },
563    Spec {
564        name: "spop",
565        arity: -2,
566        flags: WRITE_FAST,
567        first_key: 1,
568        last_key: 1,
569        step: 1,
570        acl: AC_SET_WRITE_FAST,
571        since: "1.0.0",
572        complexity: "O(1) without a count, O(N) with one",
573        summary: "Take members out of a set at random and hand them back.",
574        group: "set",
575    },
576    Spec {
577        name: "srandmember",
578        arity: -2,
579        flags: &["readonly"],
580        first_key: 1,
581        last_key: 1,
582        step: 1,
583        acl: AC_SET_READ_SLOW,
584        since: "1.0.0",
585        complexity: "O(1) without a count, O(N) with one",
586        summary: "Members of a set at random, leaving the set as it was.",
587        group: "set",
588    },
589    Spec {
590        name: "smove",
591        arity: 4,
592        flags: WRITE_FAST,
593        first_key: 1,
594        last_key: 2,
595        step: 1,
596        acl: AC_SET_WRITE_FAST,
597        since: "1.0.0",
598        complexity: "O(1)",
599        summary: "Move one member from one set to another.",
600        group: "set",
601    },
602    Spec {
603        name: "sscan",
604        arity: -3,
605        flags: &["readonly"],
606        first_key: 1,
607        last_key: 1,
608        step: 1,
609        acl: AC_SET_READ_SLOW,
610        since: "2.8.0",
611        complexity: "O(1) a call, O(N) for a whole iteration",
612        summary: "Walk part of a set and say where to carry on from.",
613        group: "set",
614    },
615    Spec {
616        name: "sinter",
617        arity: -2,
618        flags: &["readonly"],
619        first_key: 1,
620        last_key: -1,
621        step: 1,
622        acl: AC_SET_READ_SLOW,
623        since: "1.0.0",
624        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
625        summary: "The members every one of these sets has.",
626        group: "set",
627    },
628    Spec {
629        name: "sintercard",
630        arity: -3,
631        // The only set command whose keys are counted rather than positioned,
632        // so the legacy key range cannot describe it and Redis reports zeroes
633        // in these three fields too. A client that wants the keys reads the key
634        // specs, which is what the count is for, and movablekeys is how it is
635        // told to go and read them.
636        flags: READ_MOVABLE,
637        first_key: 0,
638        last_key: 0,
639        step: 0,
640        acl: AC_SET_READ_SLOW,
641        since: "7.0.0",
642        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
643        summary: "How many members every one of these sets has, up to a limit.",
644        group: "set",
645    },
646    Spec {
647        name: "sinterstore",
648        arity: -3,
649        flags: WRITE_OOM,
650        first_key: 1,
651        last_key: -1,
652        step: 1,
653        acl: AC_SET_WRITE_SLOW,
654        since: "1.0.0",
655        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
656        summary: "Store the members every one of these sets has.",
657        group: "set",
658    },
659    Spec {
660        name: "sunion",
661        arity: -2,
662        flags: &["readonly"],
663        first_key: 1,
664        last_key: -1,
665        step: 1,
666        acl: AC_SET_READ_SLOW,
667        since: "1.0.0",
668        complexity: "O(N) in the total number of members",
669        summary: "The members any of these sets has, each once.",
670        group: "set",
671    },
672    Spec {
673        name: "sunionstore",
674        arity: -3,
675        flags: WRITE_OOM,
676        first_key: 1,
677        last_key: -1,
678        step: 1,
679        acl: AC_SET_WRITE_SLOW,
680        since: "1.0.0",
681        complexity: "O(N) in the total number of members",
682        summary: "Store the members any of these sets has.",
683        group: "set",
684    },
685    Spec {
686        name: "sdiff",
687        arity: -2,
688        flags: &["readonly"],
689        first_key: 1,
690        last_key: -1,
691        step: 1,
692        acl: AC_SET_READ_SLOW,
693        since: "1.0.0",
694        complexity: "O(N) in the total number of members",
695        summary: "The members of the first set that no later set has.",
696        group: "set",
697    },
698    Spec {
699        name: "sdiffstore",
700        arity: -3,
701        flags: WRITE_OOM,
702        first_key: 1,
703        last_key: -1,
704        step: 1,
705        acl: AC_SET_WRITE_SLOW,
706        since: "1.0.0",
707        complexity: "O(N) in the total number of members",
708        summary: "Store the members of the first set that no later set has.",
709        group: "set",
710    },
711    // -------------------------------------------------------------- hashes
712    Spec {
713        name: "hset",
714        arity: -4,
715        flags: WRITE_FAST_OOM,
716        first_key: 1,
717        last_key: 1,
718        step: 1,
719        acl: AC_HASH_WRITE_FAST,
720        since: "2.0.0",
721        complexity: "O(N) with N the number of pairs being written",
722        summary: "Write fields into a hash, creating it if it is not there.",
723        group: "hash",
724    },
725    Spec {
726        name: "hsetnx",
727        arity: 4,
728        flags: WRITE_FAST_OOM,
729        first_key: 1,
730        last_key: 1,
731        step: 1,
732        acl: AC_HASH_WRITE_FAST,
733        since: "2.0.0",
734        complexity: "O(1)",
735        summary: "Write a field only if the hash does not have it already.",
736        group: "hash",
737    },
738    // Deprecated since 4.0 and still sent by a great deal of code, so it is
739    // here rather than left out. It is HSET with an OK instead of a count.
740    Spec {
741        name: "hmset",
742        arity: -4,
743        flags: WRITE_FAST_OOM,
744        first_key: 1,
745        last_key: 1,
746        step: 1,
747        acl: AC_HASH_WRITE_FAST,
748        since: "2.0.0",
749        complexity: "O(N) with N the number of pairs being written",
750        summary: "Write fields into a hash and answer OK. Use HSET.",
751        group: "hash",
752    },
753    Spec {
754        name: "hget",
755        arity: 3,
756        flags: READ_FAST,
757        first_key: 1,
758        last_key: 1,
759        step: 1,
760        acl: AC_HASH_READ_FAST,
761        since: "2.0.0",
762        complexity: "O(1)",
763        summary: "The value of one field of a hash.",
764        group: "hash",
765    },
766    Spec {
767        name: "hmget",
768        arity: -3,
769        flags: READ_FAST,
770        first_key: 1,
771        last_key: 1,
772        step: 1,
773        acl: AC_HASH_READ_FAST,
774        since: "2.0.0",
775        complexity: "O(N) with N the number of fields asked for",
776        summary: "The values of several fields, one reply entry each.",
777        group: "hash",
778    },
779    Spec {
780        name: "hdel",
781        arity: -3,
782        flags: WRITE_FAST,
783        first_key: 1,
784        last_key: 1,
785        step: 1,
786        acl: AC_HASH_WRITE_FAST,
787        since: "2.0.0",
788        complexity: "O(N) with N the number of fields being removed",
789        summary: "Take fields out of a hash, deleting the key if none are left.",
790        group: "hash",
791    },
792    Spec {
793        name: "hlen",
794        arity: 2,
795        flags: READ_FAST,
796        first_key: 1,
797        last_key: 1,
798        step: 1,
799        acl: AC_HASH_READ_FAST,
800        since: "2.0.0",
801        complexity: "O(1)",
802        summary: "How many fields a hash has.",
803        group: "hash",
804    },
805    Spec {
806        name: "hexists",
807        arity: 3,
808        flags: READ_FAST,
809        first_key: 1,
810        last_key: 1,
811        step: 1,
812        acl: AC_HASH_READ_FAST,
813        since: "2.0.0",
814        complexity: "O(1)",
815        summary: "Whether a hash has a field.",
816        group: "hash",
817    },
818    Spec {
819        name: "hstrlen",
820        arity: 3,
821        flags: READ_FAST,
822        first_key: 1,
823        last_key: 1,
824        step: 1,
825        acl: AC_HASH_READ_FAST,
826        since: "3.2.0",
827        complexity: "O(1)",
828        summary: "How many bytes a field's value is, without sending it.",
829        group: "hash",
830    },
831    Spec {
832        name: "hgetall",
833        arity: 2,
834        flags: &["readonly"],
835        first_key: 1,
836        last_key: 1,
837        step: 1,
838        acl: AC_HASH_READ_SLOW,
839        since: "2.0.0",
840        complexity: "O(N) in the size of the hash",
841        summary: "Every field and value, as a map on RESP3.",
842        group: "hash",
843    },
844    Spec {
845        name: "hkeys",
846        arity: 2,
847        flags: &["readonly"],
848        first_key: 1,
849        last_key: 1,
850        step: 1,
851        acl: AC_HASH_READ_SLOW,
852        since: "2.0.0",
853        complexity: "O(N) in the size of the hash",
854        summary: "Every field of a hash.",
855        group: "hash",
856    },
857    Spec {
858        name: "hvals",
859        arity: 2,
860        flags: &["readonly"],
861        first_key: 1,
862        last_key: 1,
863        step: 1,
864        acl: AC_HASH_READ_SLOW,
865        since: "2.0.0",
866        complexity: "O(N) in the size of the hash",
867        summary: "Every value of a hash.",
868        group: "hash",
869    },
870    Spec {
871        name: "hincrby",
872        arity: 4,
873        flags: WRITE_FAST_OOM,
874        first_key: 1,
875        last_key: 1,
876        step: 1,
877        acl: AC_HASH_WRITE_FAST,
878        since: "2.0.0",
879        complexity: "O(1)",
880        summary: "Add an integer to a field, treating a missing one as zero.",
881        group: "hash",
882    },
883    Spec {
884        name: "hincrbyfloat",
885        arity: 4,
886        flags: WRITE_FAST_OOM,
887        first_key: 1,
888        last_key: 1,
889        step: 1,
890        acl: AC_HASH_WRITE_FAST,
891        since: "2.6.0",
892        complexity: "O(1)",
893        summary: "Add a float to a field, treating a missing one as zero.",
894        group: "hash",
895    },
896    Spec {
897        name: "hrandfield",
898        arity: -2,
899        flags: &["readonly"],
900        first_key: 1,
901        last_key: 1,
902        step: 1,
903        acl: AC_HASH_READ_SLOW,
904        since: "6.2.0",
905        complexity: "O(1) without a count, O(N) with one",
906        summary: "Fields of a hash at random, leaving the hash as it was.",
907        group: "hash",
908    },
909    Spec {
910        name: "hscan",
911        arity: -3,
912        flags: &["readonly"],
913        first_key: 1,
914        last_key: 1,
915        step: 1,
916        acl: AC_HASH_READ_SLOW,
917        since: "2.8.0",
918        complexity: "O(1) a call, O(N) for a whole iteration",
919        summary: "Walk part of a hash and say where to carry on from.",
920        group: "hash",
921    },
922    Spec {
923        name: "hexpire",
924        arity: -6,
925        flags: WRITE_FAST,
926        first_key: 1,
927        last_key: 1,
928        step: 1,
929        acl: AC_HASH_WRITE_FAST,
930        since: "7.4.0",
931        complexity: "O(N) with N the number of fields named",
932        summary: "Put a deadline in seconds on hash fields.",
933        group: "hash",
934    },
935    Spec {
936        name: "hpexpire",
937        arity: -6,
938        flags: WRITE_FAST,
939        first_key: 1,
940        last_key: 1,
941        step: 1,
942        acl: AC_HASH_WRITE_FAST,
943        since: "7.4.0",
944        complexity: "O(N) with N the number of fields named",
945        summary: "Put a deadline in milliseconds on hash fields.",
946        group: "hash",
947    },
948    Spec {
949        name: "hexpireat",
950        arity: -6,
951        flags: WRITE_FAST,
952        first_key: 1,
953        last_key: 1,
954        step: 1,
955        acl: AC_HASH_WRITE_FAST,
956        since: "7.4.0",
957        complexity: "O(N) with N the number of fields named",
958        summary: "Put an absolute deadline in unix seconds on hash fields.",
959        group: "hash",
960    },
961    Spec {
962        name: "hpexpireat",
963        arity: -6,
964        flags: WRITE_FAST,
965        first_key: 1,
966        last_key: 1,
967        step: 1,
968        acl: AC_HASH_WRITE_FAST,
969        since: "7.4.0",
970        complexity: "O(N) with N the number of fields named",
971        summary: "Put an absolute deadline in unix milliseconds on hash fields.",
972        group: "hash",
973    },
974    Spec {
975        name: "httl",
976        arity: -5,
977        flags: READ_FAST,
978        first_key: 1,
979        last_key: 1,
980        step: 1,
981        acl: AC_HASH_READ_FAST,
982        since: "7.4.0",
983        complexity: "O(N) with N the number of fields named",
984        summary: "How long hash fields have left, in seconds.",
985        group: "hash",
986    },
987    Spec {
988        name: "hpttl",
989        arity: -5,
990        flags: READ_FAST,
991        first_key: 1,
992        last_key: 1,
993        step: 1,
994        acl: AC_HASH_READ_FAST,
995        since: "7.4.0",
996        complexity: "O(N) with N the number of fields named",
997        summary: "How long hash fields have left, in milliseconds.",
998        group: "hash",
999    },
1000    Spec {
1001        name: "hexpiretime",
1002        arity: -5,
1003        flags: READ_FAST,
1004        first_key: 1,
1005        last_key: 1,
1006        step: 1,
1007        acl: AC_HASH_READ_FAST,
1008        since: "7.4.0",
1009        complexity: "O(N) with N the number of fields named",
1010        summary: "When hash fields fall due, in unix seconds.",
1011        group: "hash",
1012    },
1013    Spec {
1014        name: "hpexpiretime",
1015        arity: -5,
1016        flags: READ_FAST,
1017        first_key: 1,
1018        last_key: 1,
1019        step: 1,
1020        acl: AC_HASH_READ_FAST,
1021        since: "7.4.0",
1022        complexity: "O(N) with N the number of fields named",
1023        summary: "When hash fields fall due, in unix milliseconds.",
1024        group: "hash",
1025    },
1026    Spec {
1027        name: "hpersist",
1028        arity: -5,
1029        flags: WRITE_FAST,
1030        first_key: 1,
1031        last_key: 1,
1032        step: 1,
1033        acl: AC_HASH_WRITE_FAST,
1034        since: "7.4.0",
1035        complexity: "O(N) with N the number of fields named",
1036        summary: "Take the deadlines off hash fields.",
1037        group: "hash",
1038    },
1039    Spec {
1040        name: "hgetdel",
1041        arity: -5,
1042        flags: WRITE_FAST,
1043        first_key: 1,
1044        last_key: 1,
1045        step: 1,
1046        acl: AC_HASH_WRITE_FAST,
1047        since: "8.0.0",
1048        complexity: "O(N) with N the number of fields named",
1049        summary: "Read hash fields and delete them.",
1050        group: "hash",
1051    },
1052    Spec {
1053        name: "hgetex",
1054        arity: -5,
1055        flags: WRITE_FAST,
1056        first_key: 1,
1057        last_key: 1,
1058        step: 1,
1059        acl: AC_HASH_WRITE_FAST,
1060        since: "8.0.0",
1061        complexity: "O(N) with N the number of fields named",
1062        summary: "Read hash fields and set their deadlines.",
1063        group: "hash",
1064    },
1065    Spec {
1066        name: "hsetex",
1067        arity: -6,
1068        flags: WRITE_FAST_OOM,
1069        first_key: 1,
1070        last_key: 1,
1071        step: 1,
1072        acl: AC_HASH_WRITE_FAST,
1073        since: "8.0.0",
1074        complexity: "O(N) with N the number of fields being set",
1075        summary: "Set hash fields and their deadlines together.",
1076        group: "hash",
1077    },
1078    // ---------------------------------------------------------------- lists
1079    Spec {
1080        name: "lpush",
1081        arity: -3,
1082        flags: WRITE_FAST_OOM,
1083        first_key: 1,
1084        last_key: 1,
1085        step: 1,
1086        acl: AC_LIST_WRITE_FAST,
1087        since: "1.0.0",
1088        complexity: "O(N) with N the number of elements pushed",
1089        summary: "Push elements onto the head of a list.",
1090        group: "list",
1091    },
1092    Spec {
1093        name: "rpush",
1094        arity: -3,
1095        flags: WRITE_FAST_OOM,
1096        first_key: 1,
1097        last_key: 1,
1098        step: 1,
1099        acl: AC_LIST_WRITE_FAST,
1100        since: "1.0.0",
1101        complexity: "O(N) with N the number of elements pushed",
1102        summary: "Push elements onto the tail of a list.",
1103        group: "list",
1104    },
1105    Spec {
1106        name: "lpushx",
1107        arity: -3,
1108        flags: WRITE_FAST_OOM,
1109        first_key: 1,
1110        last_key: 1,
1111        step: 1,
1112        acl: AC_LIST_WRITE_FAST,
1113        since: "2.2.0",
1114        complexity: "O(N) with N the number of elements pushed",
1115        summary: "Push elements onto the head of a list that already exists.",
1116        group: "list",
1117    },
1118    Spec {
1119        name: "rpushx",
1120        arity: -3,
1121        flags: WRITE_FAST_OOM,
1122        first_key: 1,
1123        last_key: 1,
1124        step: 1,
1125        acl: AC_LIST_WRITE_FAST,
1126        since: "2.2.0",
1127        complexity: "O(N) with N the number of elements pushed",
1128        summary: "Push elements onto the tail of a list that already exists.",
1129        group: "list",
1130    },
1131    Spec {
1132        name: "lpop",
1133        arity: -2,
1134        flags: WRITE_FAST,
1135        first_key: 1,
1136        last_key: 1,
1137        step: 1,
1138        acl: AC_LIST_WRITE_FAST,
1139        since: "1.0.0",
1140        complexity: "O(N) with N the count asked for",
1141        summary: "Take elements off the head of a list.",
1142        group: "list",
1143    },
1144    Spec {
1145        name: "rpop",
1146        arity: -2,
1147        flags: WRITE_FAST,
1148        first_key: 1,
1149        last_key: 1,
1150        step: 1,
1151        acl: AC_LIST_WRITE_FAST,
1152        since: "1.0.0",
1153        complexity: "O(N) with N the count asked for",
1154        summary: "Take elements off the tail of a list.",
1155        group: "list",
1156    },
1157    Spec {
1158        name: "llen",
1159        arity: 2,
1160        flags: READ_FAST,
1161        first_key: 1,
1162        last_key: 1,
1163        step: 1,
1164        acl: AC_LIST_READ_FAST,
1165        since: "1.0.0",
1166        complexity: "O(1)",
1167        summary: "How many elements a list holds.",
1168        group: "list",
1169    },
1170    Spec {
1171        name: "lrange",
1172        arity: 4,
1173        flags: READ_SLOW,
1174        first_key: 1,
1175        last_key: 1,
1176        step: 1,
1177        acl: AC_LIST_READ_SLOW,
1178        since: "1.0.0",
1179        complexity: "O(S+N) with S the offset of the first element and N the range",
1180        summary: "Read a range of a list, both ends included.",
1181        group: "list",
1182    },
1183    Spec {
1184        name: "lindex",
1185        arity: 3,
1186        flags: READ_SLOW,
1187        first_key: 1,
1188        last_key: 1,
1189        step: 1,
1190        acl: AC_LIST_READ_SLOW,
1191        since: "1.0.0",
1192        complexity: "O(N) with N the distance to the index from the nearer end",
1193        summary: "Read one element of a list by index.",
1194        group: "list",
1195    },
1196    Spec {
1197        name: "lset",
1198        arity: 4,
1199        flags: WRITE_OOM,
1200        first_key: 1,
1201        last_key: 1,
1202        step: 1,
1203        acl: AC_LIST_WRITE_SLOW,
1204        since: "1.0.0",
1205        complexity: "O(N) with N the distance to the index from the nearer end",
1206        summary: "Replace one element of a list by index.",
1207        group: "list",
1208    },
1209    Spec {
1210        name: "linsert",
1211        arity: 5,
1212        flags: WRITE_OOM,
1213        first_key: 1,
1214        last_key: 1,
1215        step: 1,
1216        acl: AC_LIST_WRITE_SLOW,
1217        since: "2.2.0",
1218        complexity: "O(N) with N the distance to the pivot from the head",
1219        summary: "Insert an element before or after another one.",
1220        group: "list",
1221    },
1222    Spec {
1223        name: "lrem",
1224        arity: 4,
1225        flags: WRITE_SLOW,
1226        first_key: 1,
1227        last_key: 1,
1228        step: 1,
1229        acl: AC_LIST_WRITE_SLOW,
1230        since: "1.0.0",
1231        complexity: "O(N) with N the length of the list",
1232        summary: "Remove elements equal to a value from a list.",
1233        group: "list",
1234    },
1235    Spec {
1236        name: "ltrim",
1237        arity: 4,
1238        flags: WRITE_SLOW,
1239        first_key: 1,
1240        last_key: 1,
1241        step: 1,
1242        acl: AC_LIST_WRITE_SLOW,
1243        since: "1.0.0",
1244        complexity: "O(N) with N the number of elements thrown away",
1245        summary: "Keep a range of a list and throw the rest away.",
1246        group: "list",
1247    },
1248    Spec {
1249        name: "lpos",
1250        arity: -3,
1251        flags: READ_SLOW,
1252        first_key: 1,
1253        last_key: 1,
1254        step: 1,
1255        acl: AC_LIST_READ_SLOW,
1256        since: "6.0.6",
1257        complexity: "O(N) with N the length of the list",
1258        summary: "Find where a value sits in a list.",
1259        group: "list",
1260    },
1261    Spec {
1262        name: "rpoplpush",
1263        arity: 3,
1264        flags: WRITE_OOM,
1265        first_key: 1,
1266        last_key: 2,
1267        step: 1,
1268        acl: AC_LIST_WRITE_SLOW,
1269        since: "1.2.0",
1270        complexity: "O(1)",
1271        summary: "Move an element from the tail of one list to the head of another.",
1272        group: "list",
1273    },
1274    Spec {
1275        name: "lmove",
1276        arity: 5,
1277        flags: WRITE_OOM,
1278        first_key: 1,
1279        last_key: 2,
1280        step: 1,
1281        acl: AC_LIST_WRITE_SLOW,
1282        since: "6.2.0",
1283        complexity: "O(1)",
1284        summary: "Move an element from either end of one list to either end of another.",
1285        group: "list",
1286    },
1287    // The keys are behind a count, so `first_key` is zero and a cluster client
1288    // has to ask `COMMAND GETKEYS` rather than read a position out of this row.
1289    // That is what `movablekeys` means and it is why the three key fields are
1290    // all zero rather than pointing at argument two.
1291    Spec {
1292        name: "lmpop",
1293        arity: -4,
1294        flags: &["write", "movablekeys"],
1295        first_key: 0,
1296        last_key: 0,
1297        step: 0,
1298        acl: AC_LIST_WRITE_SLOW,
1299        since: "7.0.0",
1300        complexity: "O(N+M) with N the number of keys and M the count popped",
1301        summary: "Pop from the first of several lists that has anything in it.",
1302        group: "list",
1303    },
1304    // The five that wait. `blocking` is what the dispatcher branches on to send
1305    // them somewhere that can park a client, so it is load bearing here rather
1306    // than only being reported.
1307    //
1308    // `BLPOP` and `BRPOP` take their keys up to the timeout, which is the one
1309    // shape in the list group where `last_key` is negative: everything from
1310    // argument one to the second from last.
1311    Spec {
1312        name: "blpop",
1313        arity: -3,
1314        flags: &["write", "blocking"],
1315        first_key: 1,
1316        last_key: -2,
1317        step: 1,
1318        acl: AC_LIST_WRITE_BLOCKING,
1319        since: "2.0.0",
1320        complexity: "O(N) with N the number of keys named",
1321        summary: "Pop the head of the first list that has anything, waiting if none does.",
1322        group: "list",
1323    },
1324    Spec {
1325        name: "brpop",
1326        arity: -3,
1327        flags: &["write", "blocking"],
1328        first_key: 1,
1329        last_key: -2,
1330        step: 1,
1331        acl: AC_LIST_WRITE_BLOCKING,
1332        since: "2.0.0",
1333        complexity: "O(N) with N the number of keys named",
1334        summary: "Pop the tail of the first list that has anything, waiting if none does.",
1335        group: "list",
1336    },
1337    // Redis marks the two that push somewhere `denyoom` and does not mark the
1338    // pops, because these are the blocking commands that can grow the keyspace.
1339    Spec {
1340        name: "blmove",
1341        arity: 6,
1342        flags: &["write", "denyoom", "blocking"],
1343        first_key: 1,
1344        last_key: 2,
1345        step: 1,
1346        acl: AC_LIST_WRITE_BLOCKING,
1347        since: "6.2.0",
1348        complexity: "O(1)",
1349        summary: "Move an element between two lists, waiting for one to arrive.",
1350        group: "list",
1351    },
1352    Spec {
1353        name: "brpoplpush",
1354        arity: 4,
1355        flags: &["write", "denyoom", "blocking"],
1356        first_key: 1,
1357        last_key: 2,
1358        step: 1,
1359        acl: AC_LIST_WRITE_BLOCKING,
1360        since: "2.2.0",
1361        complexity: "O(1)",
1362        summary: "Move a tail element to another list's head, waiting for one to arrive.",
1363        group: "list",
1364    },
1365    // Keys behind a count again, so the same three zeroes `LMPOP` has.
1366    Spec {
1367        name: "blmpop",
1368        arity: -5,
1369        flags: &["write", "blocking", "movablekeys"],
1370        first_key: 0,
1371        last_key: 0,
1372        step: 0,
1373        acl: AC_LIST_WRITE_BLOCKING,
1374        since: "7.0.0",
1375        complexity: "O(N+M) with N the number of keys and M the count popped",
1376        summary: "Pop from the first of several lists that has anything, waiting if none does.",
1377        group: "list",
1378    },
1379    // ------------------------------------------------------------ sorted set
1380    Spec {
1381        name: "zadd",
1382        arity: -4,
1383        flags: WRITE_FAST_OOM,
1384        first_key: 1,
1385        last_key: 1,
1386        step: 1,
1387        acl: AC_ZSET_WRITE_FAST,
1388        since: "1.2.0",
1389        complexity: "O(log(N)) for each member added",
1390        summary: "Add members with scores, or move the scores of members already there.",
1391        group: "zset",
1392    },
1393    Spec {
1394        name: "zincrby",
1395        arity: 4,
1396        flags: WRITE_FAST_OOM,
1397        first_key: 1,
1398        last_key: 1,
1399        step: 1,
1400        acl: AC_ZSET_WRITE_FAST,
1401        since: "1.2.0",
1402        complexity: "O(log(N))",
1403        summary: "Add to a member's score, creating the member at zero if it is not there.",
1404        group: "zset",
1405    },
1406    Spec {
1407        name: "zcard",
1408        arity: 2,
1409        flags: READ_FAST,
1410        first_key: 1,
1411        last_key: 1,
1412        step: 1,
1413        acl: AC_ZSET_READ_FAST,
1414        since: "1.2.0",
1415        complexity: "O(1)",
1416        summary: "How many members a sorted set has.",
1417        group: "zset",
1418    },
1419    Spec {
1420        name: "zscore",
1421        arity: 3,
1422        flags: READ_FAST,
1423        first_key: 1,
1424        last_key: 1,
1425        step: 1,
1426        acl: AC_ZSET_READ_FAST,
1427        since: "1.2.0",
1428        complexity: "O(1)",
1429        summary: "A member's score, or nothing if it is not there.",
1430        group: "zset",
1431    },
1432    Spec {
1433        name: "zmscore",
1434        arity: -3,
1435        flags: READ_FAST,
1436        first_key: 1,
1437        last_key: 1,
1438        step: 1,
1439        acl: AC_ZSET_READ_FAST,
1440        since: "6.2.0",
1441        complexity: "O(N) with N the number of members asked about",
1442        summary: "The scores of several members in one round trip.",
1443        group: "zset",
1444    },
1445    Spec {
1446        name: "zrem",
1447        arity: -3,
1448        flags: WRITE_FAST,
1449        first_key: 1,
1450        last_key: 1,
1451        step: 1,
1452        acl: AC_ZSET_WRITE_FAST,
1453        since: "1.2.0",
1454        complexity: "O(M*log(N)) with M the number of members removed",
1455        summary: "Remove members, deleting the key if the last one goes.",
1456        group: "zset",
1457    },
1458    Spec {
1459        name: "zrank",
1460        arity: -3,
1461        flags: READ_FAST,
1462        first_key: 1,
1463        last_key: 1,
1464        step: 1,
1465        acl: AC_ZSET_READ_FAST,
1466        since: "2.0.0",
1467        complexity: "O(log(N))",
1468        summary: "Where a member sits counting up from the lowest score.",
1469        group: "zset",
1470    },
1471    Spec {
1472        name: "zrevrank",
1473        arity: -3,
1474        flags: READ_FAST,
1475        first_key: 1,
1476        last_key: 1,
1477        step: 1,
1478        acl: AC_ZSET_READ_FAST,
1479        since: "2.0.0",
1480        complexity: "O(log(N))",
1481        summary: "Where a member sits counting down from the highest score.",
1482        group: "zset",
1483    },
1484    Spec {
1485        name: "zcount",
1486        arity: 4,
1487        flags: READ_FAST,
1488        first_key: 1,
1489        last_key: 1,
1490        step: 1,
1491        acl: AC_ZSET_READ_FAST,
1492        since: "2.0.0",
1493        complexity: "O(log(N))",
1494        summary: "How many members have scores between two bounds.",
1495        group: "zset",
1496    },
1497    Spec {
1498        name: "zlexcount",
1499        arity: 4,
1500        flags: READ_FAST,
1501        first_key: 1,
1502        last_key: 1,
1503        step: 1,
1504        acl: AC_ZSET_READ_FAST,
1505        since: "2.8.9",
1506        complexity: "O(log(N))",
1507        summary: "How many members fall between two members, by name.",
1508        group: "zset",
1509    },
1510    Spec {
1511        name: "zrange",
1512        arity: -4,
1513        flags: READ_SLOW,
1514        first_key: 1,
1515        last_key: 1,
1516        step: 1,
1517        acl: AC_ZSET_READ_SLOW,
1518        since: "1.2.0",
1519        complexity: "O(log(N)+M) with M the number of members answered",
1520        summary: "A window of members, by rank or by score or by name, either way round.",
1521        group: "zset",
1522    },
1523    Spec {
1524        name: "zrevrange",
1525        arity: -4,
1526        flags: READ_SLOW,
1527        first_key: 1,
1528        last_key: 1,
1529        step: 1,
1530        acl: AC_ZSET_READ_SLOW,
1531        since: "1.2.0",
1532        complexity: "O(log(N)+M) with M the number of members answered",
1533        summary: "A window by rank, counting down from the highest score.",
1534        group: "zset",
1535    },
1536    Spec {
1537        name: "zrangebyscore",
1538        arity: -4,
1539        flags: READ_SLOW,
1540        first_key: 1,
1541        last_key: 1,
1542        step: 1,
1543        acl: AC_ZSET_READ_SLOW,
1544        since: "1.0.5",
1545        complexity: "O(log(N)+M) with M the number of members answered",
1546        summary: "The members whose scores fall between two bounds.",
1547        group: "zset",
1548    },
1549    Spec {
1550        name: "zrevrangebyscore",
1551        arity: -4,
1552        flags: READ_SLOW,
1553        first_key: 1,
1554        last_key: 1,
1555        step: 1,
1556        acl: AC_ZSET_READ_SLOW,
1557        since: "2.2.0",
1558        complexity: "O(log(N)+M) with M the number of members answered",
1559        summary: "The same window as ZRANGEBYSCORE, highest score first and named high end first.",
1560        group: "zset",
1561    },
1562    Spec {
1563        name: "zrangebylex",
1564        arity: -4,
1565        flags: READ_SLOW,
1566        first_key: 1,
1567        last_key: 1,
1568        step: 1,
1569        acl: AC_ZSET_READ_SLOW,
1570        since: "2.8.9",
1571        complexity: "O(log(N)+M) with M the number of members answered",
1572        summary: "The members that fall between two names, for a set where every score is the same.",
1573        group: "zset",
1574    },
1575    Spec {
1576        name: "zrevrangebylex",
1577        arity: -4,
1578        flags: READ_SLOW,
1579        first_key: 1,
1580        last_key: 1,
1581        step: 1,
1582        acl: AC_ZSET_READ_SLOW,
1583        since: "2.8.9",
1584        complexity: "O(log(N)+M) with M the number of members answered",
1585        summary: "The same window as ZRANGEBYLEX, backwards and named high end first.",
1586        group: "zset",
1587    },
1588    Spec {
1589        name: "zrangestore",
1590        arity: -5,
1591        flags: WRITE_OOM,
1592        first_key: 1,
1593        last_key: 2,
1594        step: 1,
1595        acl: AC_ZSET_WRITE_SLOW,
1596        since: "6.2.0",
1597        complexity: "O(log(N)+M) with M the number of members stored",
1598        summary: "Write a window of one sorted set into another key.",
1599        group: "zset",
1600    },
1601    Spec {
1602        name: "zremrangebyrank",
1603        arity: 4,
1604        flags: WRITE_SLOW,
1605        first_key: 1,
1606        last_key: 1,
1607        step: 1,
1608        acl: AC_ZSET_WRITE_SLOW,
1609        since: "2.0.0",
1610        complexity: "O(log(N)+M) with M the number of members removed",
1611        summary: "Remove the members in a range of ranks.",
1612        group: "zset",
1613    },
1614    Spec {
1615        name: "zremrangebyscore",
1616        arity: 4,
1617        flags: WRITE_SLOW,
1618        first_key: 1,
1619        last_key: 1,
1620        step: 1,
1621        acl: AC_ZSET_WRITE_SLOW,
1622        since: "1.2.0",
1623        complexity: "O(log(N)+M) with M the number of members removed",
1624        summary: "Remove the members whose scores fall between two bounds.",
1625        group: "zset",
1626    },
1627    Spec {
1628        name: "zremrangebylex",
1629        arity: 4,
1630        flags: WRITE_SLOW,
1631        first_key: 1,
1632        last_key: 1,
1633        step: 1,
1634        acl: AC_ZSET_WRITE_SLOW,
1635        since: "2.8.9",
1636        complexity: "O(log(N)+M) with M the number of members removed",
1637        summary: "Remove the members that fall between two names.",
1638        group: "zset",
1639    },
1640    Spec {
1641        name: "zunion",
1642        arity: -3,
1643        flags: READ_MOVABLE,
1644        first_key: 0,
1645        last_key: 0,
1646        step: 0,
1647        acl: AC_ZSET_READ_SLOW,
1648        since: "6.2.0",
1649        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1650        summary: "Every member of these sorted sets, with the scores combined.",
1651        group: "zset",
1652    },
1653    Spec {
1654        name: "zinter",
1655        arity: -3,
1656        flags: READ_MOVABLE,
1657        first_key: 0,
1658        last_key: 0,
1659        step: 0,
1660        acl: AC_ZSET_READ_SLOW,
1661        since: "6.2.0",
1662        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1663        summary: "Only the members all of these sorted sets have, with the scores combined.",
1664        group: "zset",
1665    },
1666    Spec {
1667        name: "zdiff",
1668        arity: -3,
1669        flags: READ_MOVABLE,
1670        first_key: 0,
1671        last_key: 0,
1672        step: 0,
1673        acl: AC_ZSET_READ_SLOW,
1674        since: "6.2.0",
1675        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1676        summary: "The members of the first that none of the rest have.",
1677        group: "zset",
1678    },
1679    Spec {
1680        name: "zunionstore",
1681        arity: -4,
1682        flags: WRITE_MOVABLE,
1683        first_key: 1,
1684        last_key: 1,
1685        step: 1,
1686        acl: AC_ZSET_WRITE_SLOW,
1687        since: "2.0.0",
1688        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1689        summary: "Store the union in another key and say how big it is.",
1690        group: "zset",
1691    },
1692    Spec {
1693        name: "zinterstore",
1694        arity: -4,
1695        flags: WRITE_MOVABLE,
1696        first_key: 1,
1697        last_key: 1,
1698        step: 1,
1699        acl: AC_ZSET_WRITE_SLOW,
1700        since: "2.0.0",
1701        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1702        summary: "Store the intersection in another key and say how big it is.",
1703        group: "zset",
1704    },
1705    Spec {
1706        name: "zdiffstore",
1707        arity: -4,
1708        flags: WRITE_MOVABLE,
1709        first_key: 1,
1710        last_key: 1,
1711        step: 1,
1712        acl: AC_ZSET_WRITE_SLOW,
1713        since: "6.2.0",
1714        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
1715        summary: "Store the difference in another key and say how big it is.",
1716        group: "zset",
1717    },
1718    Spec {
1719        name: "zintercard",
1720        arity: -3,
1721        flags: READ_MOVABLE,
1722        first_key: 0,
1723        last_key: 0,
1724        step: 0,
1725        acl: AC_ZSET_READ_SLOW,
1726        since: "7.0.0",
1727        complexity: "O(N*M) worst case, N the smallest input and M the number of inputs",
1728        summary: "How many members the intersection would have, without building it.",
1729        group: "zset",
1730    },
1731    Spec {
1732        name: "zrandmember",
1733        arity: -2,
1734        flags: READ_SLOW,
1735        first_key: 1,
1736        last_key: 1,
1737        step: 1,
1738        acl: AC_ZSET_READ_SLOW,
1739        since: "6.2.0",
1740        complexity: "O(N) with N the number of members drawn",
1741        summary: "Draw members at random, with or without replacement.",
1742        group: "zset",
1743    },
1744    Spec {
1745        name: "zscan",
1746        arity: -3,
1747        flags: READ_SLOW,
1748        first_key: 1,
1749        last_key: 1,
1750        step: 1,
1751        acl: AC_ZSET_READ_SLOW,
1752        since: "2.8.0",
1753        complexity: "O(1) per call, O(N) over a full walk",
1754        summary: "Walk the members and their scores a batch at a time.",
1755        group: "zset",
1756    },
1757    // The pops. Redis calls the two single key ones fast even though they cost a
1758    // logarithm, on the grounds that the logarithm is of a size a client chose.
1759    Spec {
1760        name: "zpopmin",
1761        arity: -2,
1762        flags: WRITE_FAST,
1763        first_key: 1,
1764        last_key: 1,
1765        step: 1,
1766        acl: AC_ZSET_WRITE_FAST,
1767        since: "5.0.0",
1768        complexity: "O(log(N)*M) with M the number of members popped",
1769        summary: "Take the lowest scoring members off and answer them.",
1770        group: "zset",
1771    },
1772    Spec {
1773        name: "zpopmax",
1774        arity: -2,
1775        flags: WRITE_FAST,
1776        first_key: 1,
1777        last_key: 1,
1778        step: 1,
1779        acl: AC_ZSET_WRITE_FAST,
1780        since: "5.0.0",
1781        complexity: "O(log(N)*M) with M the number of members popped",
1782        summary: "Take the highest scoring members off and answer them.",
1783        group: "zset",
1784    },
1785    // Keys behind a count, so the same three zeroes `LMPOP` has, and `write`
1786    // without `denyoom` because a pop cannot grow the keyspace.
1787    Spec {
1788        name: "zmpop",
1789        arity: -4,
1790        flags: &["write", "movablekeys"],
1791        first_key: 0,
1792        last_key: 0,
1793        step: 0,
1794        acl: AC_ZSET_WRITE_SLOW,
1795        since: "7.0.0",
1796        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
1797        summary: "Pop from the first of several sorted sets that has anything in it.",
1798        group: "zset",
1799    },
1800    // The three that wait. `blocking` is what the dispatcher branches on, the
1801    // same as it is for the five list ones.
1802    Spec {
1803        name: "bzpopmin",
1804        arity: -3,
1805        flags: &["write", "blocking", "fast"],
1806        first_key: 1,
1807        last_key: -2,
1808        step: 1,
1809        acl: AC_ZSET_BLOCKING_FAST,
1810        since: "5.0.0",
1811        complexity: "O(log(N)) with N the size of the sorted set that answers",
1812        summary: "Take the lowest scoring member off the first sorted set that has one, waiting if none does.",
1813        group: "zset",
1814    },
1815    Spec {
1816        name: "bzpopmax",
1817        arity: -3,
1818        flags: &["write", "blocking", "fast"],
1819        first_key: 1,
1820        last_key: -2,
1821        step: 1,
1822        acl: AC_ZSET_BLOCKING_FAST,
1823        since: "5.0.0",
1824        complexity: "O(log(N)) with N the size of the sorted set that answers",
1825        summary: "Take the highest scoring member off the first sorted set that has one, waiting if none does.",
1826        group: "zset",
1827    },
1828    Spec {
1829        name: "bzmpop",
1830        arity: -5,
1831        flags: &["write", "blocking", "movablekeys"],
1832        first_key: 0,
1833        last_key: 0,
1834        step: 0,
1835        acl: AC_ZSET_BLOCKING_SLOW,
1836        since: "7.0.0",
1837        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
1838        summary: "Pop from the first of several sorted sets that has anything, waiting if none does.",
1839        group: "zset",
1840    },
1841    // --------------------------------------------------------------- array
1842    Spec {
1843        name: "arset",
1844        arity: -4,
1845        flags: WRITE_FAST_OOM,
1846        first_key: 1,
1847        last_key: 1,
1848        step: 1,
1849        acl: AC_ARRAY_WRITE_FAST,
1850        since: "8.8.0",
1851        complexity: "O(N) with N the number of values",
1852        summary: "Write values into consecutive positions from an index.",
1853        group: "array",
1854    },
1855    Spec {
1856        name: "armset",
1857        arity: -4,
1858        flags: WRITE_FAST_OOM,
1859        first_key: 1,
1860        last_key: 1,
1861        step: 1,
1862        acl: AC_ARRAY_WRITE_FAST,
1863        since: "8.8.0",
1864        complexity: "O(N) with N the number of pairs",
1865        summary: "Write index and value pairs, which need not be neighbours.",
1866        group: "array",
1867    },
1868    Spec {
1869        name: "arget",
1870        arity: 3,
1871        flags: READ_FAST,
1872        first_key: 1,
1873        last_key: 1,
1874        step: 1,
1875        acl: AC_ARRAY_READ_FAST,
1876        since: "8.8.0",
1877        complexity: "O(1)",
1878        summary: "The value at one index, or a null if nothing is there.",
1879        group: "array",
1880    },
1881    Spec {
1882        name: "armget",
1883        arity: -3,
1884        flags: READ_FAST,
1885        first_key: 1,
1886        last_key: 1,
1887        step: 1,
1888        acl: AC_ARRAY_READ_FAST,
1889        since: "8.8.0",
1890        complexity: "O(N) with N the number of indices",
1891        summary: "The values at the indices named, in the order named.",
1892        group: "array",
1893    },
1894    Spec {
1895        name: "argetrange",
1896        arity: 4,
1897        flags: READ_SLOW,
1898        first_key: 1,
1899        last_key: 1,
1900        step: 1,
1901        acl: AC_ARRAY_READ_SLOW,
1902        since: "8.8.0",
1903        complexity: "O(N) with N the length of the range",
1904        summary: "One reply per position between two indices, holes included.",
1905        group: "array",
1906    },
1907    Spec {
1908        name: "arlen",
1909        arity: 2,
1910        flags: READ_FAST,
1911        first_key: 1,
1912        last_key: 1,
1913        step: 1,
1914        acl: AC_ARRAY_READ_FAST,
1915        since: "8.8.0",
1916        complexity: "O(1)",
1917        summary: "The highest populated index plus one.",
1918        group: "array",
1919    },
1920    Spec {
1921        name: "arcount",
1922        arity: 2,
1923        flags: READ_FAST,
1924        first_key: 1,
1925        last_key: 1,
1926        step: 1,
1927        acl: AC_ARRAY_READ_FAST,
1928        since: "8.8.0",
1929        complexity: "O(1)",
1930        summary: "How many indices hold something.",
1931        group: "array",
1932    },
1933    Spec {
1934        name: "ardel",
1935        arity: -3,
1936        flags: WRITE_FAST,
1937        first_key: 1,
1938        last_key: 1,
1939        step: 1,
1940        acl: AC_ARRAY_WRITE_FAST,
1941        since: "8.8.0",
1942        complexity: "O(N) with N the number of indices",
1943        summary: "Empty the indices named and say how many held something.",
1944        group: "array",
1945    },
1946    Spec {
1947        name: "ardelrange",
1948        arity: -4,
1949        flags: WRITE_SLOW,
1950        first_key: 1,
1951        last_key: 1,
1952        step: 1,
1953        acl: AC_ARRAY_WRITE_SLOW,
1954        since: "8.8.0",
1955        complexity: "O(N) with N the elements touched, not the span asked for",
1956        summary: "Empty one or more ranges of indices.",
1957        group: "array",
1958    },
1959    Spec {
1960        name: "arinsert",
1961        arity: -3,
1962        flags: WRITE_FAST_OOM,
1963        first_key: 1,
1964        last_key: 1,
1965        step: 1,
1966        acl: AC_ARRAY_WRITE_FAST,
1967        since: "8.8.0",
1968        complexity: "O(N) with N the number of values",
1969        summary: "Append values at the insert cursor.",
1970        group: "array",
1971    },
1972    Spec {
1973        name: "arring",
1974        arity: -4,
1975        flags: WRITE_OOM,
1976        first_key: 1,
1977        last_key: 1,
1978        step: 1,
1979        acl: AC_ARRAY_WRITE_SLOW,
1980        since: "8.8.0",
1981        complexity: "O(N) with N the values, plus the ring size when it changes",
1982        summary: "Append values into a ring of the given size.",
1983        group: "array",
1984    },
1985    Spec {
1986        name: "arnext",
1987        arity: 2,
1988        flags: READ_FAST,
1989        first_key: 1,
1990        last_key: 1,
1991        step: 1,
1992        acl: AC_ARRAY_READ_FAST,
1993        since: "8.8.0",
1994        complexity: "O(1)",
1995        summary: "The index the next append would write to.",
1996        group: "array",
1997    },
1998    Spec {
1999        name: "arseek",
2000        arity: 3,
2001        flags: WRITE_FAST,
2002        first_key: 1,
2003        last_key: 1,
2004        step: 1,
2005        acl: AC_ARRAY_WRITE_FAST,
2006        since: "8.8.0",
2007        complexity: "O(1)",
2008        summary: "Point the insert cursor at an index.",
2009        group: "array",
2010    },
2011    Spec {
2012        name: "arlastitems",
2013        arity: -3,
2014        flags: READ_SLOW,
2015        first_key: 1,
2016        last_key: 1,
2017        step: 1,
2018        acl: AC_ARRAY_READ_SLOW,
2019        since: "8.8.0",
2020        complexity: "O(N) with N the count asked for",
2021        summary: "The newest positions from the insert cursor, holes included.",
2022        group: "array",
2023    },
2024    Spec {
2025        name: "arscan",
2026        arity: -4,
2027        flags: READ_SLOW,
2028        first_key: 1,
2029        last_key: 1,
2030        step: 1,
2031        acl: AC_ARRAY_READ_SLOW,
2032        since: "8.8.0",
2033        complexity: "O(N) with N the elements found, not the span asked for",
2034        summary: "Index and value pairs for what a range holds, skipping holes.",
2035        group: "array",
2036    },
2037    Spec {
2038        name: "arop",
2039        arity: -5,
2040        flags: READ_SLOW,
2041        first_key: 1,
2042        last_key: 1,
2043        step: 1,
2044        acl: AC_ARRAY_READ_SLOW,
2045        since: "8.8.0",
2046        complexity: "O(N) with N the elements found, not the span asked for",
2047        summary: "One number out of a range, added up or compared or counted.",
2048        group: "array",
2049    },
2050    Spec {
2051        name: "arinfo",
2052        arity: -2,
2053        flags: READ_SLOW,
2054        first_key: 1,
2055        last_key: 1,
2056        step: 1,
2057        acl: AC_ARRAY_READ_SLOW,
2058        since: "8.8.0",
2059        complexity: "O(1), or O(N) with N the slices when FULL is given",
2060        summary: "The shape of the array, and what its slices look like.",
2061        group: "array",
2062    },
2063    // ------------------------------------------------------------ keyspace
2064    Spec {
2065        name: "del",
2066        arity: -2,
2067        flags: &["write"],
2068        first_key: 1,
2069        last_key: -1,
2070        step: 1,
2071        acl: AC_KEY_WRITE_SLOW,
2072        since: "1.0.0",
2073        complexity: "O(N) in the number of keys.",
2074        summary: "Delete keys and say how many were there.",
2075        group: "keyspace",
2076    },
2077    Spec {
2078        name: "unlink",
2079        arity: -2,
2080        flags: &["write", "fast"],
2081        first_key: 1,
2082        last_key: -1,
2083        step: 1,
2084        acl: AC_KEY_WRITE_FAST,
2085        since: "4.0.0",
2086        complexity: "O(1) per key, since the freeing is not on this thread.",
2087        summary: "Delete keys and free them out of the way of the reply.",
2088        group: "keyspace",
2089    },
2090    Spec {
2091        name: "exists",
2092        arity: -2,
2093        flags: READ_FAST,
2094        first_key: 1,
2095        last_key: -1,
2096        step: 1,
2097        acl: AC_KEY_READ,
2098        since: "1.0.0",
2099        complexity: "O(N) in the number of keys.",
2100        summary: "Count how many of these keys are there, naming one twice counting twice.",
2101        group: "keyspace",
2102    },
2103    Spec {
2104        name: "type",
2105        arity: 2,
2106        flags: READ_FAST,
2107        first_key: 1,
2108        last_key: 1,
2109        step: 1,
2110        acl: AC_KEY_READ,
2111        since: "1.0.0",
2112        complexity: "O(1)",
2113        summary: "What kind of value is under a key, or none.",
2114        group: "keyspace",
2115    },
2116    Spec {
2117        name: "touch",
2118        arity: -2,
2119        flags: READ_FAST,
2120        first_key: 1,
2121        last_key: -1,
2122        step: 1,
2123        acl: AC_KEY_READ,
2124        since: "3.2.1",
2125        complexity: "O(N) in the number of keys.",
2126        summary: "Count how many of these keys are there, and move them up the eviction order.",
2127        group: "keyspace",
2128    },
2129    // The three that look at keys nobody named. No key positions on any of
2130    // them, which is what the zeroes say, and it is also why a cluster client
2131    // sends them to a node rather than to a slot.
2132    Spec {
2133        name: "scan",
2134        arity: -2,
2135        flags: &["readonly"],
2136        first_key: 0,
2137        last_key: 0,
2138        step: 0,
2139        acl: AC_KEY_READ_SLOW,
2140        since: "2.8.0",
2141        complexity: "O(1) a call, O(N) for a whole iteration",
2142        summary: "Walk part of the keyspace and say where to carry on from.",
2143        group: "keyspace",
2144    },
2145    Spec {
2146        name: "keys",
2147        arity: 2,
2148        flags: &["readonly"],
2149        first_key: 0,
2150        last_key: 0,
2151        step: 0,
2152        acl: AC_KEY_READ_ALL,
2153        since: "1.0.0",
2154        complexity: "O(N) in the number of keys.",
2155        summary: "Every key matching a pattern, in one reply.",
2156        group: "keyspace",
2157    },
2158    Spec {
2159        name: "randomkey",
2160        arity: 1,
2161        flags: &["readonly"],
2162        first_key: 0,
2163        last_key: 0,
2164        step: 0,
2165        acl: AC_KEY_READ_SLOW,
2166        since: "1.0.0",
2167        complexity: "O(1)",
2168        summary: "One key from the database, chosen at random.",
2169        group: "keyspace",
2170    },
2171    // Two keys and not one, which is the 1 2 1 in the key positions. Every other
2172    // row in this group names a range that runs to the end of the arguments.
2173    Spec {
2174        name: "rename",
2175        arity: 3,
2176        flags: &["write"],
2177        first_key: 1,
2178        last_key: 2,
2179        step: 1,
2180        acl: AC_KEY_WRITE_SLOW,
2181        since: "1.0.0",
2182        complexity: "O(1)",
2183        summary: "Move a key to another name, over whatever was there.",
2184        group: "keyspace",
2185    },
2186    Spec {
2187        name: "renamenx",
2188        arity: 3,
2189        flags: WRITE_FAST,
2190        first_key: 1,
2191        last_key: 2,
2192        step: 1,
2193        acl: AC_KEY_WRITE_FAST,
2194        since: "1.0.0",
2195        complexity: "O(1)",
2196        summary: "Move a key to another name, but only if that name is free.",
2197        group: "keyspace",
2198    },
2199    // `denyoom` and no `fast`, because this is the one command in the group that
2200    // allocates a whole second value.
2201    Spec {
2202        name: "copy",
2203        arity: -3,
2204        flags: &["write", "denyoom"],
2205        first_key: 1,
2206        last_key: 2,
2207        step: 1,
2208        acl: AC_KEY_WRITE_SLOW,
2209        since: "6.2.0",
2210        complexity: "O(N) in the size of the value.",
2211        summary: "Copy a value to another key, in this database or another one.",
2212        group: "keyspace",
2213    },
2214    // The four writers take an optional NX, XX, GT or LT, which is the -3 in
2215    // the arity, and they take the same one whichever unit they are in.
2216    Spec {
2217        name: "expire",
2218        arity: -3,
2219        flags: WRITE_FAST,
2220        first_key: 1,
2221        last_key: 1,
2222        step: 1,
2223        acl: AC_KEY_WRITE_FAST,
2224        since: "1.0.0",
2225        complexity: "O(1)",
2226        summary: "Put a deadline on a key, counted in seconds from now.",
2227        group: "keyspace",
2228    },
2229    Spec {
2230        name: "pexpire",
2231        arity: -3,
2232        flags: WRITE_FAST,
2233        first_key: 1,
2234        last_key: 1,
2235        step: 1,
2236        acl: AC_KEY_WRITE_FAST,
2237        since: "2.6.0",
2238        complexity: "O(1)",
2239        summary: "Put a deadline on a key, counted in milliseconds from now.",
2240        group: "keyspace",
2241    },
2242    Spec {
2243        name: "expireat",
2244        arity: -3,
2245        flags: WRITE_FAST,
2246        first_key: 1,
2247        last_key: 1,
2248        step: 1,
2249        acl: AC_KEY_WRITE_FAST,
2250        since: "1.2.0",
2251        complexity: "O(1)",
2252        summary: "Put a deadline on a key, as a unix time in seconds.",
2253        group: "keyspace",
2254    },
2255    Spec {
2256        name: "pexpireat",
2257        arity: -3,
2258        flags: WRITE_FAST,
2259        first_key: 1,
2260        last_key: 1,
2261        step: 1,
2262        acl: AC_KEY_WRITE_FAST,
2263        since: "2.6.0",
2264        complexity: "O(1)",
2265        summary: "Put a deadline on a key, as a unix time in milliseconds.",
2266        group: "keyspace",
2267    },
2268    Spec {
2269        name: "persist",
2270        arity: 2,
2271        flags: WRITE_FAST,
2272        first_key: 1,
2273        last_key: 1,
2274        step: 1,
2275        acl: AC_KEY_WRITE_FAST,
2276        since: "2.2.0",
2277        complexity: "O(1)",
2278        summary: "Take a key's deadline off, so it stops being temporary.",
2279        group: "keyspace",
2280    },
2281    Spec {
2282        name: "ttl",
2283        arity: 2,
2284        flags: READ_FAST,
2285        first_key: 1,
2286        last_key: 1,
2287        step: 1,
2288        acl: AC_KEY_READ,
2289        since: "1.0.0",
2290        complexity: "O(1)",
2291        summary: "How many seconds a key has left, -1 with no deadline, -2 if gone.",
2292        group: "keyspace",
2293    },
2294    Spec {
2295        name: "pttl",
2296        arity: 2,
2297        flags: READ_FAST,
2298        first_key: 1,
2299        last_key: 1,
2300        step: 1,
2301        acl: AC_KEY_READ,
2302        since: "2.6.0",
2303        complexity: "O(1)",
2304        summary: "How many milliseconds a key has left, -1 with no deadline, -2 if gone.",
2305        group: "keyspace",
2306    },
2307    Spec {
2308        name: "expiretime",
2309        arity: 2,
2310        flags: READ_FAST,
2311        first_key: 1,
2312        last_key: 1,
2313        step: 1,
2314        acl: AC_KEY_READ,
2315        since: "7.0.0",
2316        complexity: "O(1)",
2317        summary: "When a key falls due, as a unix time in seconds.",
2318        group: "keyspace",
2319    },
2320    Spec {
2321        name: "pexpiretime",
2322        arity: 2,
2323        flags: READ_FAST,
2324        first_key: 1,
2325        last_key: 1,
2326        step: 1,
2327        acl: AC_KEY_READ,
2328        since: "7.0.0",
2329        complexity: "O(1)",
2330        summary: "When a key falls due, as a unix time in milliseconds.",
2331        group: "keyspace",
2332    },
2333    // A container command, so no keys and no flags of its own: the key is the
2334    // subcommand's and a real server reports it on `object|encoding` rather
2335    // than here. `@slow` is the whole ACL, checked against 8.10.1.
2336    Spec {
2337        name: "object",
2338        arity: -2,
2339        flags: &[],
2340        first_key: 0,
2341        last_key: 0,
2342        step: 0,
2343        acl: &["@slow"],
2344        since: "2.2.3",
2345        complexity: "O(1)",
2346        summary: "Look at the machinery under a key rather than at its value.",
2347        group: "keyspace",
2348    },
2349    // ----------------------------------------------------------- scripting
2350    // Both are containers with no flags and no keys of their own, which is what
2351    // a real 8.10.1 reports: the flags live on the subcommands.
2352    Spec {
2353        name: "script",
2354        arity: -2,
2355        flags: &[],
2356        first_key: 0,
2357        last_key: 0,
2358        step: 0,
2359        acl: &["@slow"],
2360        since: "2.6.0",
2361        complexity: "O(1) for the subcommands that are here.",
2362        summary: "The script cache, which is empty and stays empty until M6.",
2363        group: "scripting",
2364    },
2365    Spec {
2366        name: "function",
2367        arity: -2,
2368        flags: &[],
2369        first_key: 0,
2370        last_key: 0,
2371        step: 0,
2372        acl: &["@slow"],
2373        since: "7.0.0",
2374        complexity: "O(1) for the subcommands that are here.",
2375        summary: "The function libraries, of which there are none until M6.",
2376        group: "scripting",
2377    },
2378    // ---------------------------------------------------------- connection
2379    Spec {
2380        name: "ping",
2381        arity: -1,
2382        flags: &["fast"],
2383        first_key: 0,
2384        last_key: 0,
2385        step: 0,
2386        acl: AC_CONN,
2387        since: "1.0.0",
2388        complexity: "O(1)",
2389        summary: "Ask whether the server is answering.",
2390        group: "connection",
2391    },
2392    Spec {
2393        name: "echo",
2394        arity: 2,
2395        flags: &["loading", "stale", "fast"],
2396        first_key: 0,
2397        last_key: 0,
2398        step: 0,
2399        acl: AC_CONN,
2400        since: "1.0.0",
2401        complexity: "O(1)",
2402        summary: "Send a string back unchanged.",
2403        group: "connection",
2404    },
2405    Spec {
2406        name: "hello",
2407        arity: -1,
2408        flags: &[
2409            "noscript",
2410            "loading",
2411            "stale",
2412            "fast",
2413            "no_auth",
2414            "allow_busy",
2415        ],
2416        first_key: 0,
2417        last_key: 0,
2418        step: 0,
2419        acl: AC_CONN,
2420        since: "6.0.0",
2421        complexity: "O(1)",
2422        summary: "Agree on a protocol version and describe the server.",
2423        group: "connection",
2424    },
2425    Spec {
2426        name: "select",
2427        arity: 2,
2428        flags: &["loading", "stale", "fast"],
2429        first_key: 0,
2430        last_key: 0,
2431        step: 0,
2432        acl: AC_CONN,
2433        since: "1.0.0",
2434        complexity: "O(1)",
2435        summary: "Choose which database this connection works in.",
2436        group: "connection",
2437    },
2438    Spec {
2439        name: "reset",
2440        arity: 1,
2441        flags: &[
2442            "noscript",
2443            "loading",
2444            "stale",
2445            "fast",
2446            "no_auth",
2447            "allow_busy",
2448        ],
2449        first_key: 0,
2450        last_key: 0,
2451        step: 0,
2452        acl: AC_CONN,
2453        since: "6.2.0",
2454        complexity: "O(1)",
2455        summary: "Put the connection back the way it was opened.",
2456        group: "connection",
2457    },
2458    Spec {
2459        name: "quit",
2460        arity: -1,
2461        flags: &[
2462            "noscript",
2463            "loading",
2464            "stale",
2465            "fast",
2466            "no_auth",
2467            "allow_busy",
2468        ],
2469        first_key: 0,
2470        last_key: 0,
2471        step: 0,
2472        acl: AC_CONN,
2473        since: "1.0.0",
2474        complexity: "O(1)",
2475        summary: "Close the connection after the replies already queued.",
2476        group: "connection",
2477    },
2478    // -------------------------------------------------------------- server
2479    // COMMAND is in the connection ACL category and in the server group, which
2480    // is not a contradiction: the category is about what a connection is
2481    // allowed to do and the group is about what the command is about. The group
2482    // is the one reported by COMMAND DOCS, so it is the one that has to match.
2483    Spec {
2484        name: "command",
2485        arity: -1,
2486        flags: &["loading", "stale"],
2487        first_key: 0,
2488        last_key: 0,
2489        step: 0,
2490        acl: &["@slow", "@connection"],
2491        since: "2.8.13",
2492        complexity: "O(N) with N the number of commands",
2493        summary: "What this server can do, in the shape client libraries read.",
2494        group: "server",
2495    },
2496    Spec {
2497        name: "config",
2498        arity: -2,
2499        flags: &[],
2500        first_key: 0,
2501        last_key: 0,
2502        step: 0,
2503        acl: &["@slow"],
2504        since: "2.0.0",
2505        complexity: "Depends on the subcommand.",
2506        summary: "Read and change the settings a running server exposes.",
2507        group: "server",
2508    },
2509    Spec {
2510        name: "info",
2511        arity: -1,
2512        flags: &["loading", "stale"],
2513        first_key: 0,
2514        last_key: 0,
2515        step: 0,
2516        acl: &["@slow", "@dangerous"],
2517        since: "1.0.0",
2518        complexity: "O(1)",
2519        summary: "The server's own numbers, in sections.",
2520        group: "server",
2521    },
2522    Spec {
2523        name: "dbsize",
2524        arity: 1,
2525        flags: READ_FAST,
2526        first_key: 0,
2527        last_key: 0,
2528        step: 0,
2529        acl: AC_KEY_READ,
2530        since: "1.0.0",
2531        complexity: "O(1)",
2532        summary: "How many keys are in the database this connection is on.",
2533        group: "server",
2534    },
2535    Spec {
2536        name: "flushall",
2537        arity: -1,
2538        flags: &["write"],
2539        first_key: 0,
2540        last_key: 0,
2541        step: 0,
2542        acl: AC_KEY_FLUSH,
2543        since: "1.0.0",
2544        complexity: "O(N) in the number of keys in every database.",
2545        summary: "Empty every database.",
2546        group: "server",
2547    },
2548    Spec {
2549        name: "flushdb",
2550        arity: -1,
2551        flags: &["write"],
2552        first_key: 0,
2553        last_key: 0,
2554        step: 0,
2555        acl: AC_KEY_FLUSH,
2556        since: "1.0.0",
2557        complexity: "O(N) in the number of keys in this database.",
2558        summary: "Empty the database this connection is on.",
2559        group: "server",
2560    },
2561    // No ACL category but `@fast`, which is Redis's answer and reads like an
2562    // omission. It is not: the categories are about what a command can reach and
2563    // this one reaches nothing.
2564    Spec {
2565        name: "time",
2566        arity: 1,
2567        flags: &["loading", "stale", "fast"],
2568        first_key: 0,
2569        last_key: 0,
2570        step: 0,
2571        acl: &["@fast"],
2572        since: "2.6.0",
2573        complexity: "O(1)",
2574        summary: "The server's clock, as seconds and microseconds.",
2575        group: "server",
2576    },
2577];
2578
2579/// The command called `name`, whatever case the client spelled it in.
2580///
2581/// Linear over a table of this size, which is a handful of length compares
2582/// against a table that fits in one page and is in cache because the previous
2583/// command looked at it too. A hash would be a hash of the name plus a probe,
2584/// and the name is already in a register. The table grows to about 250 by M8,
2585/// at which point this becomes a perfect hash built at compile time, and the
2586/// signature does not change when it does.
2587#[must_use]
2588pub fn lookup(name: &[u8]) -> Option<&'static Spec> {
2589    COMMANDS
2590        .iter()
2591        .find(|c| c.name.len() == name.len() && c.name.as_bytes().eq_ignore_ascii_case(name))
2592}
2593
2594/// Whether `n` arguments, counting the name, satisfy this command's arity.
2595#[must_use]
2596pub fn arity_ok(spec: &Spec, n: usize) -> bool {
2597    let n = n as i32;
2598    if spec.arity >= 0 {
2599        n == spec.arity
2600    } else {
2601        n >= -spec.arity
2602    }
2603}
2604
2605#[cfg(test)]
2606mod tests {
2607    use super::*;
2608
2609    #[test]
2610    fn every_name_is_lower_case_and_appears_once() {
2611        let mut seen = std::collections::BTreeSet::new();
2612        for c in COMMANDS {
2613            assert_eq!(
2614                c.name,
2615                c.name.to_lowercase(),
2616                "{} is not lower case",
2617                c.name
2618            );
2619            assert!(seen.insert(c.name), "{} is in the table twice", c.name);
2620        }
2621    }
2622
2623    #[test]
2624    fn lookup_ignores_case_and_does_not_match_a_prefix() {
2625        assert_eq!(lookup(b"GET").unwrap().name, "get");
2626        assert_eq!(lookup(b"gEt").unwrap().name, "get");
2627        assert!(lookup(b"ge").is_none());
2628        assert!(lookup(b"gets").is_none());
2629    }
2630
2631    #[test]
2632    fn arity_counts_the_command_name() {
2633        let get = lookup(b"get").unwrap();
2634        assert!(!arity_ok(get, 1));
2635        assert!(arity_ok(get, 2));
2636        assert!(!arity_ok(get, 3));
2637
2638        // A negative arity is a minimum, which is how SET takes its options.
2639        let set = lookup(b"set").unwrap();
2640        assert!(!arity_ok(set, 2));
2641        assert!(arity_ok(set, 3));
2642        assert!(arity_ok(set, 9));
2643    }
2644
2645    /// A key spec that is wrong sends a cluster client to the wrong node, so
2646    /// the pair commands are worth stating twice.
2647    #[test]
2648    fn the_pair_commands_step_two_keys_at_a_time() {
2649        for name in [b"mset".as_slice(), b"msetnx"] {
2650            let c = lookup(name).unwrap();
2651            assert_eq!((c.first_key, c.last_key, c.step), (1, -1, 2));
2652        }
2653        let mget = lookup(b"mget").unwrap();
2654        assert_eq!((mget.first_key, mget.last_key, mget.step), (1, -1, 1));
2655        // MSETEX counts its keys in an argument, so there is no static spec
2656        // for them and a client has to ask with COMMAND GETKEYS.
2657        let msetex = lookup(b"msetex").unwrap();
2658        assert_eq!((msetex.first_key, msetex.last_key, msetex.step), (0, 0, 0));
2659        assert!(msetex.flags.contains(&"movablekeys"));
2660    }
2661}