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