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
16use super::Args;
17// Every name here is a key spec shape, spelled in capitals and used once a row,
18// so a glob is what keeps four hundred and twenty nine rows from carrying an
19// import list longer than they are.
20use super::keyspec::*;
21use yo_common::parse_i64;
22
23/// Everything `COMMAND` has to be able to say about one command.
24#[derive(Debug, Clone, Copy)]
25pub struct Spec {
26    /// The name, lower case, which is how `COMMAND` reports it whatever case
27    /// the client used.
28    pub name: &'static str,
29    /// Redis's arity: a positive number is exact, a negative one is a minimum
30    /// of its magnitude, and both count the command name itself.
31    pub arity: i32,
32    /// The command flags, in the order `COMMAND INFO` lists them.
33    pub flags: &'static [&'static str],
34    /// The first argument that is a key, or zero when there are none.
35    pub first_key: i32,
36    /// The last argument that is a key, negative counting back from the end.
37    pub last_key: i32,
38    /// How far apart the keys are, for the commands that take pairs.
39    pub step: i32,
40    /// The ACL categories, which are what `COMMAND LIST FILTERBY ACLCAT` reads.
41    ///
42    /// A container row carries only what the container itself is in, which is
43    /// what a real server reports for it. The categories its subcommands add on
44    /// top are in [`SUBCATS`].
45    pub acl: &'static [&'static str],
46    /// The Redis this command first appeared in.
47    pub since: &'static str,
48    /// The cost, in the shape `COMMAND DOCS` uses.
49    pub complexity: &'static str,
50    /// One line about what it does, in our words.
51    pub summary: &'static str,
52    /// The group in `commands.toml`, which is how the two files are compared.
53    pub group: &'static str,
54    /// Where this command's keys are and what it does to each of them.
55    ///
56    /// The triple above is Redis's legacy answer and is kept because
57    /// `COMMAND INFO` still reports it. This is the one that is right, and it is
58    /// the one the ACL reads. See the `keyspec` module for what the difference
59    /// costs.
60    pub keys: &'static [KeySpec],
61}
62
63/// The four transaction commands Redis counts as fast, which is all of them
64/// except `EXEC`, whose cost is whatever it was asked to run.
65const AC_TX_FAST: &[&str] = &["@fast", "@transaction"];
66/// The six subscribe and unsubscribe commands, none of which Redis counts as
67/// fast because all of them take a list.
68const AC_PUBSUB_SLOW: &[&str] = &["@pubsub", "@slow"];
69/// The two publishes, which Redis does count as fast.
70const AC_PUBSUB_FAST: &[&str] = &["@pubsub", "@fast"];
71/// Read only, fast, one key at argument one, which is most of the getters.
72const READ_FAST: &[&str] = &["readonly", "fast"];
73/// A write that allocates, fast, one key at argument one.
74const WRITE_FAST_OOM: &[&str] = &["write", "denyoom", "fast"];
75/// A write that allocates and is not counted as fast.
76const WRITE_OOM: &[&str] = &["write", "denyoom"];
77/// A write that allocates and is not for ordinary clients, which is `PFDEBUG`.
78const WRITE_OOM_ADMIN: &[&str] = &["write", "denyoom", "admin"];
79/// The read side categories.
80const AC_READ_FAST: &[&str] = &["@read", "@string", "@fast"];
81/// The bitmap read side, for the two that answer without walking the value.
82const AC_BIT_READ_FAST: &[&str] = &["@read", "@bitmap", "@fast"];
83/// The bitmap read side for the ones that walk it.
84const AC_BIT_READ: &[&str] = &["@read", "@bitmap", "@slow"];
85/// The bitmap write side. Redis counts none of these as fast, `SETBIT` included.
86const AC_BIT_WRITE: &[&str] = &["@write", "@bitmap", "@slow"];
87
88/// The sketch write side, which Redis counts as fast for `PFADD` alone.
89const AC_HLL_WRITE_FAST: &[&str] = &["@write", "@hyperloglog", "@fast"];
90/// The sketch write side for the ones that walk every register.
91const AC_HLL_WRITE: &[&str] = &["@write", "@hyperloglog", "@slow"];
92/// The sketch read side, which is `PFCOUNT` and only `PFCOUNT`.
93const AC_HLL_READ: &[&str] = &["@read", "@hyperloglog", "@slow"];
94/// The two that are not for clients, and are tagged so an ACL can say so.
95const AC_HLL_ADMIN: &[&str] = &["@hyperloglog", "@admin", "@slow", "@dangerous"];
96
97/// What the three replication commands are in, which is the same three every
98/// other administrative command is in.
99const AC_ADMIN_SLOW: &[&str] = &["@admin", "@slow", "@dangerous"];
100/// The read side categories for the ones that walk the value.
101const AC_READ_SLOW: &[&str] = &["@read", "@string", "@slow"];
102/// The write side categories.
103const AC_WRITE_FAST: &[&str] = &["@write", "@string", "@fast"];
104/// The write side categories for the ones that are not counted as fast.
105const AC_WRITE_SLOW: &[&str] = &["@write", "@string", "@slow"];
106/// A write that frees rather than allocates, so Redis does not mark it denyoom.
107const WRITE_FAST: &[&str] = &["write", "fast"];
108/// The set read side, for the ones that answer without walking the members.
109const AC_SET_READ_FAST: &[&str] = &["@read", "@set", "@fast"];
110/// The set read side for the ones that walk the members.
111const AC_SET_READ_SLOW: &[&str] = &["@read", "@set", "@slow"];
112/// The set write side.
113const AC_SET_WRITE_FAST: &[&str] = &["@write", "@set", "@fast"];
114/// The set write side for the ones that walk whole sets to decide what to
115/// write, which is the whole `*STORE` family.
116const AC_SET_WRITE_SLOW: &[&str] = &["@write", "@set", "@slow"];
117/// The hash read side, for the ones that answer without walking the fields.
118const AC_HASH_READ_FAST: &[&str] = &["@read", "@hash", "@fast"];
119/// The hash read side for the ones that walk the fields.
120const AC_HASH_READ_SLOW: &[&str] = &["@read", "@hash", "@slow"];
121/// The hash write side.
122const AC_HASH_WRITE_FAST: &[&str] = &["@write", "@hash", "@fast"];
123/// `HIMPORT`, which is a container and so has no write category of its own. The
124/// write flags and the key live on its `SET` subcommand, which the table does
125/// not carry any more than it carries `OBJECT ENCODING`.
126const AC_HASH_SLOW: &[&str] = &["@hash", "@slow"];
127/// Read only and not counted as fast, which is every list read that walks.
128const READ_SLOW: &[&str] = &["readonly"];
129/// A write that is not counted as fast and does not allocate, which on the list
130/// side is `LREM` and `LTRIM` and nothing else.
131const WRITE_SLOW: &[&str] = &["write"];
132/// The list read side, for the two that answer without walking the elements.
133const AC_LIST_READ_FAST: &[&str] = &["@read", "@list", "@fast"];
134/// The list read side for the ones that walk.
135const AC_LIST_READ_SLOW: &[&str] = &["@read", "@list", "@slow"];
136/// The list write side, which is the pushes and the pops. Redis counts a push
137/// as fast even though it can split a chunk, because the split is amortised.
138const AC_LIST_WRITE_FAST: &[&str] = &["@write", "@list", "@fast"];
139/// The list write side for the ones whose cost is the length of the list.
140const AC_LIST_WRITE_SLOW: &[&str] = &["@write", "@list", "@slow"];
141/// The five that can wait, which carry a category of their own so that an ACL
142/// can say "this user may not park a connection" without naming five commands.
143const AC_LIST_WRITE_BLOCKING: &[&str] = &["@write", "@list", "@slow", "@blocking"];
144/// The sorted set read side, for the ones that answer without walking members.
145const AC_ZSET_READ_FAST: &[&str] = &["@read", "@sortedset", "@fast"];
146/// The sorted set read side for the ones that walk members.
147const AC_ZSET_READ_SLOW: &[&str] = &["@read", "@sortedset", "@slow"];
148/// The sorted set write side.
149const AC_ZSET_WRITE_FAST: &[&str] = &["@write", "@sortedset", "@fast"];
150/// The sorted set write side for the ones whose cost is the size of the window
151/// they touch, which is the removals and `ZRANGESTORE`.
152const AC_ZSET_WRITE_SLOW: &[&str] = &["@write", "@sortedset", "@slow"];
153/// The two sorted set pops that can wait, which Redis counts as fast because
154/// each of them takes one member.
155const AC_ZSET_BLOCKING_FAST: &[&str] = &["@write", "@sortedset", "@fast", "@blocking"];
156/// And `BZMPOP`, whose cost is the number of keys named and the count popped.
157const AC_ZSET_BLOCKING_SLOW: &[&str] = &["@write", "@sortedset", "@slow", "@blocking"];
158/// The array read side, for the ones whose cost is the number of indices named
159/// and not the size of the array.
160/// The geo read side. Redis counts none of these as fast, not even GEODIST,
161/// which is two probes and some arithmetic.
162const AC_GEO_READ: &[&str] = &["@read", "@geo", "@slow"];
163/// The geo write side, which is GEOADD and the four forms that can store.
164const AC_GEO_WRITE: &[&str] = &["@write", "@geo", "@slow"];
165/// No ACL categories at all, which is what the vector set module registers.
166///
167/// It is the only group here with an empty list and it is not an omission. The
168/// module never calls the categories in, so a real server has no `vectorset`
169/// category to name, `ACL CAT vectorset` is an unknown category there, and
170/// `COMMAND LIST FILTERBY ACLCAT read` does not answer `VSIM`. Inventing a
171/// category would make a rule written against this server mean something it
172/// does not mean against a real one, which is the one thing a compatible ACL
173/// must not do, and it would do it in the direction that grants access rather
174/// than the direction that refuses it.
175const AC_NONE: &[&str] = &[];
176/// A vector set read, with the `module` flag every vector set command carries
177/// for the same reason the JSON and Bloom ones do.
178const VECTOR_READ: &[&str] = &["readonly", "module"];
179/// A vector set read the module also marks fast, which is the ones that answer
180/// about one element without searching.
181const VECTOR_READ_FAST: &[&str] = &["readonly", "module", "fast"];
182/// `VADD`, which is the one command here that can grow the set.
183const VECTOR_WRITE_OOM: &[&str] = &["write", "denyoom", "module"];
184/// `VREM`, which only ever frees, so the module does not mark it denyoom.
185const VECTOR_WRITE: &[&str] = &["write", "module"];
186/// `VSETATTR`, which the module marks fast and, though it takes a string off
187/// the wire, does not mark denyoom.
188const VECTOR_WRITE_FAST: &[&str] = &["write", "module", "fast"];
189/// The one category nearly every search command is in. The module registers
190/// `@search` on its own for most of them rather than pairing it with `@read` or
191/// `@write` the way RedisJSON does, which is the module's own answer to
192/// `COMMAND INFO` and is copied rather than tidied up.
193const AC_SEARCH: &[&str] = &["@search"];
194/// `FT.DROPINDEX` and the two spellings of it, which the module puts in the
195/// dangerous category because dropping an index with `DD` deletes the documents
196/// it followed.
197const AC_SEARCH_DROP: &[&str] = &["@write", "@slow", "@dangerous", "@search"];
198/// `FT._DROPIFX`, which is the same command with a shorter list. The module
199/// leaves the slow and dangerous categories off this one and there is no
200/// reading of it that makes it less dangerous than the others.
201const AC_SEARCH_WRITE: &[&str] = &["@write", "@search"];
202/// `FT._LIST`, which the module puts in `@admin` because listing every index is
203/// a question about the server rather than about anything in it.
204const AC_SEARCH_LIST: &[&str] = &["@admin", "@slow", "@search"];
205/// `FT.CONFIG`, which is a question about the server rather than about an index
206/// and is the only search command the module leaves the `@slow` category off
207/// while still calling it admin.
208const AC_SEARCH_ADMIN: &[&str] = &["@admin", "@search"];
209/// `_FT.DEBUG`, which the module calls dangerous as well as admin because what
210/// it answers is the module's own bookkeeping and nothing about it is promised
211/// to stay the same shape between versions.
212const AC_SEARCH_DEBUG: &[&str] = &["@admin", "@slow", "@dangerous", "@search"];
213/// `FT.TAGVALS`, the one search read the module bothers to put in `@read` as
214/// well, and the only one it calls dangerous without also calling it a write.
215/// Both are fair: the whole of a tag index goes into one reply and there is no
216/// way to ask for less of it.
217const AC_SEARCH_TAGS: &[&str] = &["@read", "@slow", "@dangerous", "@search"];
218/// The two suggestion reads. These are the search commands that work on a real
219/// key, so the module pairs `@search` with the category the key access deserves
220/// rather than leaving it on its own.
221const AC_SEARCH_READ: &[&str] = &["@read", "@search"];
222/// A search read, with the `module` flag every search command carries for the
223/// same reason the JSON and vector set ones do.
224const SEARCH_READ: &[&str] = &["readonly", "module"];
225/// A search write that takes a schema or an alias off the wire.
226const SEARCH_WRITE_OOM: &[&str] = &["write", "denyoom", "module"];
227/// A search write that only ever frees, which is dropping an index or an alias.
228const SEARCH_WRITE: &[&str] = &["write", "module"];
229/// The JSON read side. Two categories and no speed one, which is RedisJSON's
230/// own answer to `COMMAND INFO` and not an omission: the module registers
231/// `@read @json` and leaves it there.
232const AC_JSON_READ: &[&str] = &["@read", "@json"];
233/// The JSON write side, the same way.
234const AC_JSON_WRITE: &[&str] = &["@write", "@json"];
235/// A JSON read, with the `module` flag every RedisJSON command carries. It is
236/// there because the command came from a module on a real server, and a client
237/// that reads the flags off `COMMAND INFO` should see the same list from both.
238const JSON_READ: &[&str] = &["readonly", "module"];
239/// A JSON write that does not grow the document.
240const JSON_WRITE: &[&str] = &["write", "module"];
241/// A JSON write that does, which is the four that take a value off the wire.
242const JSON_WRITE_OOM: &[&str] = &["write", "denyoom", "module"];
243/// A JSON read whose key is not where the arity says it is, which is
244/// `JSON.DEBUG` and its subcommand.
245const JSON_READ_MOVABLE: &[&str] = &["readonly", "module", "movablekeys"];
246/// The Bloom filter read side. RedisBloom marks all of these `@fast` on top of
247/// the two categories, including the ones that walk the whole filter, which is
248/// the module's own answer to `COMMAND INFO` and is copied rather than judged.
249const AC_BLOOM_READ: &[&str] = &["@read", "@bloom"];
250/// The two reads the module also puts in `@fast` as a category of its own,
251/// which is `BF.INFO` and `BF.CARD`. Neither reads the bits at all.
252const AC_BLOOM_READ_FAST: &[&str] = &["@read", "@fast", "@bloom"];
253/// The Bloom filter write side.
254const AC_BLOOM_WRITE: &[&str] = &["@write", "@bloom"];
255/// `BF.RESERVE`, which is the one write that does no hashing.
256const AC_BLOOM_WRITE_FAST: &[&str] = &["@write", "@fast", "@bloom"];
257/// A Bloom read, with the `module` flag every RedisBloom command carries for
258/// the same reason the JSON ones do.
259const BLOOM_READ: &[&str] = &["readonly", "module", "fast"];
260/// A Bloom write. All of them can grow the filter, `BF.LOADCHUNK` included, so
261/// all of them deny out of memory.
262const BLOOM_WRITE: &[&str] = &["write", "denyoom", "module"];
263/// The cuckoo filter read side, which is the same three flags under a category
264/// of its own. `CF.COMPACT` is in here too, because the module has it down as a
265/// read even though it moves fingerprints between filters.
266const AC_CUCKOO_READ: &[&str] = &["@read", "@cuckoo"];
267/// The one read the module also calls fast, which is `CF.INFO`.
268const AC_CUCKOO_READ_FAST: &[&str] = &["@read", "@fast", "@cuckoo"];
269/// The cuckoo filter write side.
270const AC_CUCKOO_WRITE: &[&str] = &["@write", "@cuckoo"];
271/// `CF.RESERVE`, which is the one write that does no hashing.
272const AC_CUCKOO_WRITE_FAST: &[&str] = &["@write", "@fast", "@cuckoo"];
273/// A cuckoo read, with the `module` flag the whole family carries.
274const CUCKOO_READ: &[&str] = &["readonly", "module", "fast"];
275/// A cuckoo write, all of which can grow the chain.
276const CUCKOO_WRITE: &[&str] = &["write", "denyoom", "module"];
277/// `CF.DEL`, the one write that only ever frees a slot and so does not deny out
278/// of memory.
279const CUCKOO_DELETE: &[&str] = &["write", "module", "fast"];
280/// The count min sketch read side. The module does not call either of these
281/// fast in the flags even though it puts `CMS.INFO` in the fast category, which
282/// is a disagreement in RedisBloom's own table and is copied as it stands.
283const AC_CMS_READ: &[&str] = &["@read", "@cms"];
284/// `CMS.INFO`, which is the one read in the fast category.
285const AC_CMS_READ_FAST: &[&str] = &["@read", "@fast", "@cms"];
286/// The count min sketch write side.
287const AC_CMS_WRITE: &[&str] = &["@write", "@cms"];
288/// The two constructors, which allocate and then do nothing.
289const AC_CMS_WRITE_FAST: &[&str] = &["@write", "@fast", "@cms"];
290/// A count min sketch read, which carries `module` and not `fast`.
291const CMS_READ: &[&str] = &["readonly", "module"];
292/// A count min sketch write. The table never grows after it is made, so the two
293/// that can allocate are the constructors, and all four deny out of memory
294/// because the module marks all four.
295const CMS_WRITE: &[&str] = &["write", "denyoom", "module"];
296/// The top k read side, which the module does not call fast except for the one
297/// that reads four numbers off the header.
298const AC_TOPK_READ: &[&str] = &["@read", "@topk"];
299/// `TOPK.INFO`.
300const AC_TOPK_READ_FAST: &[&str] = &["@read", "@fast", "@topk"];
301/// The top k write side, which is the two that count things.
302const AC_TOPK_WRITE: &[&str] = &["@write", "@topk"];
303/// `TOPK.RESERVE`, the one write that only allocates.
304const AC_TOPK_WRITE_FAST: &[&str] = &["@write", "@fast", "@topk"];
305/// A top k read, which carries `module` and not `fast`.
306const TOPK_READ: &[&str] = &["readonly", "module"];
307/// A top k write. The table never grows after it is made, so the only one that
308/// can allocate is the constructor, and all three deny out of memory because the
309/// module marks all three.
310const TOPK_WRITE: &[&str] = &["write", "denyoom", "module"];
311/// The t digest read side for the ones that answer off the header or off one
312/// sweep of the centroids, which the module calls fast and which is all of them
313/// bar the trimmed mean.
314const AC_TDIGEST_READ_FAST: &[&str] = &["@read", "@fast", "@tdigest"];
315/// `TDIGEST.TRIMMED_MEAN`, the one read the module does not call fast.
316const AC_TDIGEST_READ: &[&str] = &["@read", "@tdigest"];
317/// The t digest write side for the two that only shape a digest.
318const AC_TDIGEST_WRITE_FAST: &[&str] = &["@write", "@fast", "@tdigest"];
319/// The two that move weight around.
320const AC_TDIGEST_WRITE: &[&str] = &["@write", "@tdigest"];
321/// A t digest read, which carries `module` and not `fast`.
322const TDIGEST_READ: &[&str] = &["readonly", "module"];
323/// A t digest write. All four deny out of memory because all four can end up
324/// asking for a set of centroids.
325const TDIGEST_WRITE: &[&str] = &["write", "denyoom", "module"];
326/// `TDIGEST.MERGE`, whose keys are behind a count and so cannot be found by the
327/// first, last and step the rest of the table uses.
328const TDIGEST_MERGE: &[&str] = &["write", "denyoom", "module", "movablekeys"];
329/// The time series read side for the two that answer off the header or off the
330/// last sample, which the module calls fast.
331const AC_TS_READ_FAST: &[&str] = &["@read", "@fast", "@timeseries"];
332/// The time series write side, which is everything that puts a sample in or
333/// changes what a series does with one.
334const AC_TS_WRITE: &[&str] = &["@write", "@timeseries"];
335/// The time series read side for the two that walk a span, which the module
336/// does not call fast.
337const AC_TS_READ: &[&str] = &["@read", "@timeseries"];
338/// `TS.CREATE`, the one write the module calls fast, because making an empty
339/// series is an allocation and nothing else.
340const AC_TS_WRITE_FAST: &[&str] = &["@write", "@fast", "@timeseries"];
341/// A time series read, which carries `module` and not `fast` whatever the ACL
342/// category says. That disagreement is RedisTimeSeries's own and is copied as it
343/// stands, the same way the count min sketch one is.
344const TS_READ: &[&str] = &["readonly", "module"];
345/// The two joined reads, which carry their keys behind a count and so cannot be
346/// described by the first, last and step triple.
347const TS_READ_MOVABLE: &[&str] = &["readonly", "module", "movablekeys"];
348/// A time series write, all of which can ask for another chunk.
349const TS_WRITE: &[&str] = &["write", "denyoom", "module"];
350/// `TS.DEL`, the one write that only ever frees samples and so does not deny out
351/// of memory.
352const TS_DELETE: &[&str] = &["write", "module"];
353/// `TS.CREATERULE`, which is the one time series write that says `fast` in the
354/// flags rather than only in the ACL categories, and the one that never asks for
355/// room of its own.
356const TS_RULE: &[&str] = &["write", "module", "fast"];
357/// The graph read side, for the ones that answer without walking the plane.
358const AC_GRAPH_READ_FAST: &[&str] = &["@read", "@graph", "@fast"];
359/// The graph read side for the ones that walk it.
360const AC_GRAPH_READ_SLOW: &[&str] = &["@read", "@graph", "@slow"];
361/// The graph write side, all of which are a probe and a run.
362const AC_GRAPH_WRITE_FAST: &[&str] = &["@write", "@graph", "@fast"];
363const AC_ARRAY_READ_FAST: &[&str] = &["@read", "@array", "@fast"];
364/// The array read side for `ARGETRANGE`, which answers once per position in the
365/// range and so costs the range rather than the population.
366const AC_ARRAY_READ_SLOW: &[&str] = &["@read", "@array", "@slow"];
367/// The array write side.
368const AC_ARRAY_WRITE_FAST: &[&str] = &["@write", "@array", "@fast"];
369/// The array write side for `ARDELRANGE`, the one array command Redis does not
370/// mark fast.
371const AC_ARRAY_WRITE_SLOW: &[&str] = &["@write", "@array", "@slow"];
372/// The stream read side, for the ones that answer without walking entries.
373const AC_STREAM_READ_FAST: &[&str] = &["@read", "@stream", "@fast"];
374/// The stream read side for the ranges, whose cost is what they return.
375const AC_STREAM_READ_SLOW: &[&str] = &["@read", "@stream", "@slow"];
376/// The stream write side, which is everything that appends, deletes or moves an
377/// entry between pending lists.
378const AC_STREAM_WRITE_FAST: &[&str] = &["@write", "@stream", "@fast"];
379/// The stream write side for `XTRIM`, whose cost is what it removes.
380const AC_STREAM_WRITE_SLOW: &[&str] = &["@write", "@stream", "@slow"];
381/// `XREAD`, which waits and does not write.
382const AC_STREAM_BLOCKING_READ: &[&str] = &["@read", "@stream", "@slow", "@blocking"];
383/// `XREADGROUP`, which waits and does write, since handing an entry to a
384/// consumer puts it on that consumer's pending list.
385const AC_STREAM_BLOCKING_WRITE: &[&str] = &["@write", "@stream", "@slow", "@blocking"];
386/// `XGROUP` and `XINFO`, whose keys are on the subcommand and whose categories
387/// are therefore only the container's.
388const AC_STREAM_CONTAINER: &[&str] = &["@slow"];
389/// The two stream reads, whose keys come after `STREAMS` and are half of what
390/// follows it, so nothing positional can find them.
391const READ_BLOCKING_MOVABLE: &[&str] = &["readonly", "blocking", "movablekeys"];
392/// The same for `XREADGROUP`, which is a write.
393const WRITE_BLOCKING_MOVABLE: &[&str] = &["write", "blocking", "movablekeys"];
394/// Read only and not counted as fast, for a command whose keys are counted
395/// rather than positioned, so a client has to read the key specs to route it.
396const READ_MOVABLE: &[&str] = &["readonly", "movablekeys"];
397/// The same for a write, which is the three store forms.
398const WRITE_MOVABLE: &[&str] = &["write", "denyoom", "movablekeys"];
399/// `MIGRATE`, which is the one movable key write that is not `denyoom`.
400///
401/// It only ever frees here, since the local key goes away and nothing arrives,
402/// so a server with no room left can still migrate its way out of trouble. That
403/// is the same reasoning that leaves the flag off `DEL`.
404const MIGRATE_FLAGS: &[&str] = &["write", "movablekeys"];
405/// The connection commands' categories.
406const AC_CONN: &[&str] = &["@fast", "@connection"];
407/// The keyspace read side, which is `EXISTS` and `TYPE`.
408const AC_KEY_READ: &[&str] = &["@keyspace", "@read", "@fast"];
409/// The keyspace reads that walk something, which is `SCAN` and `RANDOMKEY`.
410const AC_KEY_READ_SLOW: &[&str] = &["@keyspace", "@read", "@slow"];
411/// And `KEYS`, which is the same walk without a bound on it and is the one read
412/// in this group Redis calls dangerous.
413const AC_KEY_READ_ALL: &[&str] = &["@keyspace", "@read", "@slow", "@dangerous"];
414/// The keyspace writes that are allowed to cost what the value costs. `DEL`
415/// frees on the spot and `COPY` clones a body, and `RENAME` is in here with
416/// them even though it moves thirteen bytes, because Redis says slow for it and
417/// this list is Redis's list rather than ours.
418const AC_KEY_WRITE_SLOW: &[&str] = &["@keyspace", "@write", "@slow"];
419/// `UNLINK`, which Redis does count as fast because it does not, and the
420/// expiry writers, which move a deadline and never touch a value.
421const AC_KEY_WRITE_FAST: &[&str] = &["@keyspace", "@write", "@fast"];
422/// The two that empty a database, which are in the dangerous category.
423const AC_KEY_FLUSH: &[&str] = &["@keyspace", "@write", "@slow", "@dangerous"];
424/// `SWAPDB`, which is fast and dangerous at the same time. It is two pointer
425/// writes and it changes what every connected client is looking at, so Redis
426/// puts it in `@fast` and in `@dangerous` and both are right.
427const AC_SWAPDB: &[&str] = &["@keyspace", "@write", "@fast", "@dangerous"];
428/// `RESTORE`, which is dangerous for a reason worth saying out loud: it is the
429/// one command that takes bytes from a client and turns them into a value
430/// without any command ever having built it. `DUMP` is only `@read`, because
431/// reading a value out is no more than reading it.
432const AC_RESTORE: &[&str] = &["@keyspace", "@write", "@slow", "@dangerous"];
433/// `WAIT` and `WAITAOF`, which are the two commands that block on something
434/// that is not a key. They are not in `@keyspace` at all, because they name no
435/// key and read nothing, and they carry `@blocking` for the same reason the
436/// five list commands do.
437const AC_WAIT: &[&str] = &["@slow", "@blocking", "@connection"];
438/// `SORT`, which names three type categories because it takes any of the three
439/// and a write because of `STORE`. Redis leaves `@keyspace` off both of these
440/// even though the command lives in that group, and this list is Redis's.
441const AC_SORT_WRITE: &[&str] = &[
442    "@write",
443    "@set",
444    "@sortedset",
445    "@list",
446    "@slow",
447    "@dangerous",
448];
449/// `SORT_RO`, which is the same list with the write turned into a read.
450const AC_SORT_READ: &[&str] = &[
451    "@read",
452    "@set",
453    "@sortedset",
454    "@list",
455    "@slow",
456    "@dangerous",
457];
458
459/// The categories a container's subcommands hold on top of the container's own.
460///
461/// A real server has a row a subcommand, so `config|get` is `@admin @dangerous
462/// @slow` while `config` itself is only `@slow`, and `-@admin` on a user takes
463/// CONFIG GET away without touching CONFIG HELP. This table has one row a
464/// container that has none, so it needs this on the side: a rule about a
465/// category walks the commands for the ones it names outright and then walks
466/// this for the subcommands, allowing or refusing them one first argument at a
467/// time. It goes away with D-114, when the subcommands get rows of their own and
468/// carry their own categories like everything else.
469///
470/// Every subcommand also holds every category its container holds, which was
471/// read off 8.10.1 rather than assumed, and is why a rule about the container's
472/// own categories can still set one bit and be done.
473///
474/// The list is sorted by container and then by subcommand, and a subcommand with
475/// nothing to add is left out, which is why COMMAND has no rows at all.
476pub static SUBCATS: &[(&str, &str, &[&str])] = &[
477    ("acl", "deluser", AC_SUB_ADMIN),
478    ("acl", "dryrun", AC_SUB_ADMIN),
479    ("acl", "getuser", AC_SUB_ADMIN),
480    ("acl", "list", AC_SUB_ADMIN),
481    ("acl", "load", AC_SUB_ADMIN),
482    ("acl", "log", AC_SUB_ADMIN),
483    ("acl", "save", AC_SUB_ADMIN),
484    ("acl", "setuser", AC_SUB_ADMIN),
485    ("acl", "users", AC_SUB_ADMIN),
486    ("client", "caching", AC_SUB_CONNECTION),
487    ("client", "getname", AC_SUB_CONNECTION),
488    ("client", "getredir", AC_SUB_CONNECTION),
489    ("client", "help", AC_SUB_CONNECTION),
490    ("client", "id", AC_SUB_CONNECTION),
491    ("client", "info", AC_SUB_CONNECTION),
492    ("client", "kill", AC_SUB_ADMIN_CONNECTION),
493    ("client", "list", AC_SUB_ADMIN_CONNECTION),
494    ("client", "no-evict", AC_SUB_ADMIN_CONNECTION),
495    ("client", "no-touch", AC_SUB_CONNECTION),
496    ("client", "pause", AC_SUB_ADMIN_CONNECTION),
497    ("client", "reply", AC_SUB_CONNECTION),
498    ("client", "setinfo", AC_SUB_CONNECTION),
499    ("client", "setname", AC_SUB_CONNECTION),
500    ("client", "tracking", AC_SUB_CONNECTION),
501    ("client", "trackinginfo", AC_SUB_CONNECTION),
502    ("client", "unblock", AC_SUB_ADMIN_CONNECTION),
503    ("client", "unpause", AC_SUB_ADMIN_CONNECTION),
504    ("config", "get", AC_SUB_ADMIN),
505    ("config", "resetstat", AC_SUB_ADMIN),
506    ("config", "rewrite", AC_SUB_ADMIN),
507    ("config", "set", AC_SUB_ADMIN),
508    ("function", "delete", AC_SUB_SCRIPTING_WRITE),
509    ("function", "dump", AC_SUB_SCRIPTING),
510    ("function", "flush", AC_SUB_SCRIPTING_WRITE),
511    ("function", "help", AC_SUB_SCRIPTING),
512    ("function", "kill", AC_SUB_SCRIPTING),
513    ("function", "list", AC_SUB_SCRIPTING),
514    ("function", "load", AC_SUB_SCRIPTING_WRITE),
515    ("function", "restore", AC_SUB_SCRIPTING_WRITE),
516    ("function", "stats", AC_SUB_SCRIPTING),
517    ("memory", "usage", AC_SUB_READ),
518    ("object", "encoding", AC_SUB_KEYSPACE_READ),
519    ("object", "freq", AC_SUB_KEYSPACE_READ),
520    ("object", "help", AC_SUB_KEYSPACE),
521    ("object", "idletime", AC_SUB_KEYSPACE_READ),
522    ("object", "refcount", AC_SUB_KEYSPACE_READ),
523    ("pubsub", "channels", AC_SUB_PUBSUB),
524    ("pubsub", "numpat", AC_SUB_PUBSUB),
525    ("pubsub", "numsub", AC_SUB_PUBSUB),
526    ("pubsub", "shardchannels", AC_SUB_PUBSUB),
527    ("pubsub", "shardnumsub", AC_SUB_PUBSUB),
528    ("script", "debug", AC_SUB_SCRIPTING),
529    ("script", "exists", AC_SUB_SCRIPTING),
530    ("script", "flush", AC_SUB_SCRIPTING),
531    ("script", "help", AC_SUB_SCRIPTING),
532    ("script", "kill", AC_SUB_SCRIPTING),
533    ("script", "load", AC_SUB_SCRIPTING),
534    ("xgroup", "create", AC_SUB_STREAM_WRITE),
535    ("xgroup", "createconsumer", AC_SUB_STREAM_WRITE),
536    ("xgroup", "delconsumer", AC_SUB_STREAM_WRITE),
537    ("xgroup", "destroy", AC_SUB_STREAM_WRITE),
538    ("xgroup", "help", AC_SUB_STREAM),
539    ("xgroup", "setid", AC_SUB_STREAM_WRITE),
540    ("xinfo", "consumers", AC_SUB_STREAM_READ),
541    ("xinfo", "groups", AC_SUB_STREAM_READ),
542    ("xinfo", "help", AC_SUB_STREAM),
543    ("xinfo", "stream", AC_SUB_STREAM_READ),
544];
545
546/// The subcommands of ACL and CONFIG that only an administrator should have.
547const AC_SUB_ADMIN: &[&str] = &["@admin", "@dangerous"];
548/// The subcommands of CLIENT that anybody may use.
549const AC_SUB_CONNECTION: &[&str] = &["@connection"];
550/// The subcommands of CLIENT that reach another connection.
551const AC_SUB_ADMIN_CONNECTION: &[&str] = &["@admin", "@connection", "@dangerous"];
552/// The subcommands of FUNCTION and SCRIPT that only read.
553const AC_SUB_SCRIPTING: &[&str] = &["@scripting"];
554/// The subcommands of FUNCTION that change what is loaded.
555const AC_SUB_SCRIPTING_WRITE: &[&str] = &["@scripting", "@write"];
556/// `MEMORY USAGE`, which is the one subcommand of MEMORY that reads a key and
557/// so the one that is in a category its container is not.
558const AC_SUB_READ: &[&str] = &["@read"];
559/// `OBJECT HELP`, which names the keyspace without reading one.
560const AC_SUB_KEYSPACE: &[&str] = &["@keyspace"];
561/// The subcommands of OBJECT that read a key.
562const AC_SUB_KEYSPACE_READ: &[&str] = &["@keyspace", "@read"];
563/// The subcommands of PUBSUB, all but HELP.
564const AC_SUB_PUBSUB: &[&str] = &["@pubsub"];
565/// `XGROUP HELP` and `XINFO HELP`.
566const AC_SUB_STREAM: &[&str] = &["@stream"];
567/// The subcommands of XGROUP that change a group.
568const AC_SUB_STREAM_WRITE: &[&str] = &["@stream", "@write"];
569/// The subcommands of XINFO that read a stream.
570const AC_SUB_STREAM_READ: &[&str] = &["@read", "@stream"];
571
572/// Every command this server answers, in the order the groups ship.
573pub static COMMANDS: &[Spec] = &[
574    // ------------------------------------------------------------- strings
575    Spec {
576        name: "set",
577        arity: -3,
578        flags: WRITE_OOM,
579        first_key: 1,
580        last_key: 1,
581        step: 1,
582        keys: &[SET_KEY],
583        acl: AC_WRITE_SLOW,
584        since: "1.0.0",
585        complexity: "O(1)",
586        summary: "Set a key to a string value, whatever it held before.",
587        group: "string",
588    },
589    Spec {
590        name: "get",
591        arity: 2,
592        flags: READ_FAST,
593        first_key: 1,
594        last_key: 1,
595        step: 1,
596        keys: &[RO_ACCESS_AT1],
597        acl: AC_READ_FAST,
598        since: "1.0.0",
599        complexity: "O(1)",
600        summary: "The string value of a key.",
601        group: "string",
602    },
603    Spec {
604        name: "getset",
605        arity: 3,
606        flags: WRITE_FAST_OOM,
607        first_key: 1,
608        last_key: 1,
609        step: 1,
610        keys: &[RW_ACCESS_UPDATE_AT1],
611        acl: AC_WRITE_FAST,
612        since: "1.0.0",
613        complexity: "O(1)",
614        summary: "Set a key and hand back what it held.",
615        group: "string",
616    },
617    Spec {
618        name: "getdel",
619        arity: 2,
620        flags: &["write", "fast"],
621        first_key: 1,
622        last_key: 1,
623        step: 1,
624        keys: &[RW_ACCESS_DELETE_AT1],
625        acl: AC_WRITE_FAST,
626        since: "6.2.0",
627        complexity: "O(1)",
628        summary: "Read a key and delete it in the same step.",
629        group: "string",
630    },
631    Spec {
632        name: "getex",
633        arity: -2,
634        flags: &["write", "fast"],
635        first_key: 1,
636        last_key: 1,
637        step: 1,
638        keys: &[RW_ACCESS_UPDATE_AT1_TTL],
639        acl: AC_WRITE_FAST,
640        since: "6.2.0",
641        complexity: "O(1)",
642        summary: "Read a key and change its deadline in the same step.",
643        group: "string",
644    },
645    Spec {
646        name: "setnx",
647        arity: 3,
648        flags: WRITE_FAST_OOM,
649        first_key: 1,
650        last_key: 1,
651        step: 1,
652        keys: &[OW_INSERT_AT1],
653        acl: AC_WRITE_FAST,
654        since: "1.0.0",
655        complexity: "O(1)",
656        summary: "Set a key only if it is not there.",
657        group: "string",
658    },
659    Spec {
660        name: "setex",
661        arity: 4,
662        flags: WRITE_OOM,
663        first_key: 1,
664        last_key: 1,
665        step: 1,
666        keys: &[OW_UPDATE_AT1],
667        acl: AC_WRITE_SLOW,
668        since: "2.0.0",
669        complexity: "O(1)",
670        summary: "Set a key and give it a deadline in seconds.",
671        group: "string",
672    },
673    Spec {
674        name: "psetex",
675        arity: 4,
676        flags: WRITE_OOM,
677        first_key: 1,
678        last_key: 1,
679        step: 1,
680        keys: &[OW_UPDATE_AT1],
681        acl: AC_WRITE_SLOW,
682        since: "2.6.0",
683        complexity: "O(1)",
684        summary: "Set a key and give it a deadline in milliseconds.",
685        group: "string",
686    },
687    Spec {
688        name: "mset",
689        arity: -3,
690        flags: WRITE_OOM,
691        first_key: 1,
692        last_key: -1,
693        step: 2,
694        keys: &[OW_UPDATE_AT1_RM1_2_0],
695        acl: AC_WRITE_SLOW,
696        since: "1.0.1",
697        complexity: "O(N) with N the number of keys",
698        summary: "Set several keys, all of them or none.",
699        group: "string",
700    },
701    Spec {
702        name: "msetnx",
703        arity: -3,
704        flags: WRITE_OOM,
705        first_key: 1,
706        last_key: -1,
707        step: 2,
708        keys: &[OW_INSERT_AT1_RM1_2_0],
709        acl: AC_WRITE_SLOW,
710        since: "1.0.1",
711        complexity: "O(N) with N the number of keys",
712        summary: "Set several keys only if none of them are there.",
713        group: "string",
714    },
715    Spec {
716        name: "mget",
717        arity: -2,
718        flags: READ_FAST,
719        first_key: 1,
720        last_key: -1,
721        step: 1,
722        keys: &[RO_ACCESS_AT1_RM1_1_0],
723        acl: AC_READ_FAST,
724        since: "1.0.0",
725        complexity: "O(N) with N the number of keys",
726        summary: "The values of several keys, in the order asked for.",
727        group: "string",
728    },
729    Spec {
730        name: "append",
731        arity: 3,
732        flags: WRITE_FAST_OOM,
733        first_key: 1,
734        last_key: 1,
735        step: 1,
736        keys: &[RW_INSERT_AT1],
737        acl: AC_WRITE_FAST,
738        since: "2.0.0",
739        complexity: "O(M) with M the length of the value being appended",
740        summary: "Add to the end of a string, creating it if it is not there.",
741        group: "string",
742    },
743    Spec {
744        name: "strlen",
745        arity: 2,
746        flags: READ_FAST,
747        first_key: 1,
748        last_key: 1,
749        step: 1,
750        keys: &[RO_AT1],
751        acl: AC_READ_FAST,
752        since: "2.2.0",
753        complexity: "O(1)",
754        summary: "How long a string value is, without reading it.",
755        group: "string",
756    },
757    Spec {
758        name: "setrange",
759        arity: 4,
760        flags: WRITE_OOM,
761        first_key: 1,
762        last_key: 1,
763        step: 1,
764        keys: &[RW_UPDATE_AT1],
765        acl: AC_WRITE_SLOW,
766        since: "2.2.0",
767        complexity: "O(M) with M the length of the replacement",
768        summary: "Overwrite part of a string at an offset, zero filling the gap.",
769        group: "string",
770    },
771    Spec {
772        name: "getrange",
773        arity: 4,
774        flags: &["readonly"],
775        first_key: 1,
776        last_key: 1,
777        step: 1,
778        keys: &[RO_ACCESS_AT1],
779        acl: AC_READ_SLOW,
780        since: "2.4.0",
781        complexity: "O(N) with N the length of the answer",
782        summary: "Part of a string, by an inclusive range that may count backwards.",
783        group: "string",
784    },
785    Spec {
786        name: "substr",
787        arity: 4,
788        flags: &["readonly"],
789        first_key: 1,
790        last_key: 1,
791        step: 1,
792        keys: &[RO_ACCESS_AT1],
793        acl: AC_READ_SLOW,
794        since: "1.0.0",
795        complexity: "O(N) with N the length of the answer",
796        summary: "GETRANGE under the name it had before 2.4.",
797        group: "string",
798    },
799    Spec {
800        name: "incr",
801        arity: 2,
802        flags: WRITE_FAST_OOM,
803        first_key: 1,
804        last_key: 1,
805        step: 1,
806        keys: &[RW_ACCESS_UPDATE_AT1],
807        acl: AC_WRITE_FAST,
808        since: "1.0.0",
809        complexity: "O(1)",
810        summary: "Add one, starting from zero if the key is not there.",
811        group: "string",
812    },
813    Spec {
814        name: "decr",
815        arity: 2,
816        flags: WRITE_FAST_OOM,
817        first_key: 1,
818        last_key: 1,
819        step: 1,
820        keys: &[RW_ACCESS_UPDATE_AT1],
821        acl: AC_WRITE_FAST,
822        since: "1.0.0",
823        complexity: "O(1)",
824        summary: "Take one away, starting from zero if the key is not there.",
825        group: "string",
826    },
827    Spec {
828        name: "incrby",
829        arity: 3,
830        flags: WRITE_FAST_OOM,
831        first_key: 1,
832        last_key: 1,
833        step: 1,
834        keys: &[RW_ACCESS_UPDATE_AT1],
835        acl: AC_WRITE_FAST,
836        since: "1.0.0",
837        complexity: "O(1)",
838        summary: "Add a number, starting from zero if the key is not there.",
839        group: "string",
840    },
841    Spec {
842        name: "decrby",
843        arity: 3,
844        flags: WRITE_FAST_OOM,
845        first_key: 1,
846        last_key: 1,
847        step: 1,
848        keys: &[RW_ACCESS_UPDATE_AT1],
849        acl: AC_WRITE_FAST,
850        since: "1.0.0",
851        complexity: "O(1)",
852        summary: "Take a number away, starting from zero if the key is not there.",
853        group: "string",
854    },
855    Spec {
856        name: "incrbyfloat",
857        arity: 3,
858        flags: WRITE_FAST_OOM,
859        first_key: 1,
860        last_key: 1,
861        step: 1,
862        keys: &[RW_ACCESS_UPDATE_AT1],
863        acl: AC_WRITE_FAST,
864        since: "2.6.0",
865        complexity: "O(1)",
866        summary: "Add a float, starting from zero if the key is not there.",
867        group: "string",
868    },
869    Spec {
870        name: "lcs",
871        arity: -3,
872        flags: &["readonly"],
873        first_key: 1,
874        last_key: 2,
875        step: 1,
876        keys: &[RO_ACCESS_AT1_R1_1_0],
877        acl: AC_READ_SLOW,
878        since: "7.0.0",
879        complexity: "O(N*M) with N and M the lengths of the two values",
880        summary: "The longest subsequence two string values have in common.",
881        group: "string",
882    },
883    Spec {
884        name: "msetex",
885        arity: -4,
886        flags: &["write", "denyoom", "movablekeys"],
887        first_key: 0,
888        last_key: 0,
889        step: 0,
890        keys: &[OW_UPDATE_AT1_COUNTED],
891        acl: AC_WRITE_SLOW,
892        since: "8.4.0",
893        complexity: "O(N) with N the number of keys",
894        summary: "Set several keys with one deadline and one condition over all of them.",
895        group: "string",
896    },
897    Spec {
898        name: "delex",
899        arity: -2,
900        flags: &["write", "fast"],
901        first_key: 1,
902        last_key: 1,
903        step: 1,
904        keys: &[DELEX_KEY],
905        acl: AC_WRITE_FAST,
906        since: "8.4.0",
907        complexity: "O(1) by value, O(N) by digest",
908        summary: "Delete a key only if it still holds what the caller thinks.",
909        group: "string",
910    },
911    Spec {
912        name: "digest",
913        arity: 2,
914        flags: READ_FAST,
915        first_key: 1,
916        last_key: 1,
917        step: 1,
918        keys: &[RO_ACCESS_AT1],
919        acl: AC_READ_FAST,
920        since: "8.4.0",
921        complexity: "O(N) with N the length of the value",
922        summary: "The XXH3 of a string value, as sixteen hex characters.",
923        group: "string",
924    },
925    Spec {
926        name: "increx",
927        arity: -2,
928        flags: WRITE_FAST_OOM,
929        first_key: 1,
930        last_key: 1,
931        step: 1,
932        keys: &[RW_ACCESS_UPDATE_AT1],
933        acl: AC_WRITE_FAST,
934        since: "8.8.0",
935        complexity: "O(1)",
936        summary: "Count, with a bound, a saturation policy and a deadline.",
937        group: "string",
938    },
939    // -------------------------------------------------------------- bitmaps
940    Spec {
941        name: "setbit",
942        arity: 4,
943        flags: WRITE_OOM,
944        first_key: 1,
945        last_key: 1,
946        step: 1,
947        keys: &[RW_ACCESS_UPDATE_AT1],
948        acl: AC_BIT_WRITE,
949        since: "2.2.0",
950        complexity: "O(1)",
951        summary: "Set one bit of a string, growing it to reach the offset.",
952        group: "bitmap",
953    },
954    Spec {
955        name: "getbit",
956        arity: 3,
957        flags: READ_FAST,
958        first_key: 1,
959        last_key: 1,
960        step: 1,
961        keys: &[RO_ACCESS_AT1],
962        acl: AC_BIT_READ_FAST,
963        since: "2.2.0",
964        complexity: "O(1)",
965        summary: "Read one bit of a string, or nought past its end.",
966        group: "bitmap",
967    },
968    Spec {
969        name: "bitcount",
970        arity: -2,
971        flags: &["readonly"],
972        first_key: 1,
973        last_key: 1,
974        step: 1,
975        keys: &[RO_ACCESS_AT1],
976        acl: AC_BIT_READ,
977        since: "2.6.0",
978        complexity: "O(N)",
979        summary: "Count the set bits of a string, or of a range of it.",
980        group: "bitmap",
981    },
982    Spec {
983        name: "bitpos",
984        arity: -3,
985        flags: &["readonly"],
986        first_key: 1,
987        last_key: 1,
988        step: 1,
989        keys: &[RO_ACCESS_AT1],
990        acl: AC_BIT_READ,
991        since: "2.8.7",
992        complexity: "O(N)",
993        summary: "Find the first bit set to one or nought in a string.",
994        group: "bitmap",
995    },
996    Spec {
997        name: "bitop",
998        arity: -4,
999        flags: WRITE_OOM,
1000        first_key: 2,
1001        last_key: -1,
1002        step: 1,
1003        keys: &[OW_UPDATE_AT2, RO_ACCESS_AT3_RM1_1_0],
1004        acl: AC_BIT_WRITE,
1005        since: "2.6.0",
1006        complexity: "O(N) with N the length of the longest source",
1007        summary: "Combine strings bit by bit and store the result.",
1008        group: "bitmap",
1009    },
1010    Spec {
1011        name: "bitfield",
1012        arity: -2,
1013        flags: WRITE_OOM,
1014        first_key: 1,
1015        last_key: 1,
1016        step: 1,
1017        keys: &[BITFIELD_KEY],
1018        acl: AC_BIT_WRITE,
1019        since: "3.2.0",
1020        complexity: "O(1) per subcommand",
1021        summary: "Read and write packed integer fields inside a string.",
1022        group: "bitmap",
1023    },
1024    Spec {
1025        name: "bitfield_ro",
1026        arity: -2,
1027        flags: READ_FAST,
1028        first_key: 1,
1029        last_key: 1,
1030        step: 1,
1031        keys: &[RO_ACCESS_AT1],
1032        acl: AC_BIT_READ_FAST,
1033        since: "6.0.0",
1034        complexity: "O(1) per subcommand",
1035        summary: "The read only half of BITFIELD, for a replica to answer.",
1036        group: "bitmap",
1037    },
1038    // --------------------------------------------------------- hyperloglogs
1039    Spec {
1040        name: "pfadd",
1041        arity: -2,
1042        flags: WRITE_OOM,
1043        first_key: 1,
1044        last_key: 1,
1045        step: 1,
1046        keys: &[RW_INSERT_AT1],
1047        acl: AC_HLL_WRITE_FAST,
1048        since: "2.8.9",
1049        complexity: "O(1) an element",
1050        summary: "Add elements to a sketch, answering whether it changed.",
1051        group: "hyperloglog",
1052    },
1053    Spec {
1054        name: "pfcount",
1055        arity: -2,
1056        flags: &["readonly"],
1057        first_key: 1,
1058        last_key: -1,
1059        step: 1,
1060        keys: &[RW_ACCESS_AT1_RM1_1_0],
1061        acl: AC_HLL_READ,
1062        since: "2.8.9",
1063        complexity: "O(1) for one key, O(N) for N of them",
1064        summary: "Estimate how many distinct elements the sketches hold.",
1065        group: "hyperloglog",
1066    },
1067    Spec {
1068        name: "pfmerge",
1069        arity: -2,
1070        flags: WRITE_OOM,
1071        first_key: 1,
1072        last_key: -1,
1073        step: 1,
1074        keys: &[RW_ACCESS_INSERT_AT1, RO_ACCESS_AT2_RM1_1_0],
1075        acl: AC_HLL_WRITE,
1076        since: "2.8.9",
1077        complexity: "O(N) in the number of sketches",
1078        summary: "Merge sketches into the first one, which is a union.",
1079        group: "hyperloglog",
1080    },
1081    Spec {
1082        name: "pfdebug",
1083        arity: 3,
1084        flags: WRITE_OOM_ADMIN,
1085        first_key: 2,
1086        last_key: 2,
1087        step: 1,
1088        keys: &[RW_ACCESS_AT2],
1089        acl: AC_HLL_ADMIN,
1090        since: "2.8.9",
1091        complexity: "O(N)",
1092        summary: "Look inside a sketch, and in one case convert it.",
1093        group: "hyperloglog",
1094    },
1095    Spec {
1096        name: "pfselftest",
1097        arity: 1,
1098        flags: &["admin"],
1099        first_key: 0,
1100        last_key: 0,
1101        step: 0,
1102        keys: &[],
1103        acl: AC_HLL_ADMIN,
1104        since: "2.8.9",
1105        complexity: "O(1)",
1106        summary: "Check the sketch code, which our tests do at build time.",
1107        group: "hyperloglog",
1108    },
1109    // ----------------------------------------------------------------- sets
1110    Spec {
1111        name: "sadd",
1112        arity: -3,
1113        flags: WRITE_FAST_OOM,
1114        first_key: 1,
1115        last_key: 1,
1116        step: 1,
1117        keys: &[RW_INSERT_AT1],
1118        acl: AC_SET_WRITE_FAST,
1119        since: "1.0.0",
1120        complexity: "O(N) with N the number of members being added",
1121        summary: "Add members to a set, creating it if it is not there.",
1122        group: "set",
1123    },
1124    Spec {
1125        name: "srem",
1126        arity: -3,
1127        flags: WRITE_FAST,
1128        first_key: 1,
1129        last_key: 1,
1130        step: 1,
1131        keys: &[RW_DELETE_AT1],
1132        acl: AC_SET_WRITE_FAST,
1133        since: "1.0.0",
1134        complexity: "O(N) with N the number of members being removed",
1135        summary: "Take members out of a set, deleting the key if none are left.",
1136        group: "set",
1137    },
1138    Spec {
1139        name: "scard",
1140        arity: 2,
1141        flags: READ_FAST,
1142        first_key: 1,
1143        last_key: 1,
1144        step: 1,
1145        keys: &[RO_AT1],
1146        acl: AC_SET_READ_FAST,
1147        since: "1.0.0",
1148        complexity: "O(1)",
1149        summary: "How many members a set has.",
1150        group: "set",
1151    },
1152    Spec {
1153        name: "sismember",
1154        arity: 3,
1155        flags: READ_FAST,
1156        first_key: 1,
1157        last_key: 1,
1158        step: 1,
1159        keys: &[RO_AT1],
1160        acl: AC_SET_READ_FAST,
1161        since: "1.0.0",
1162        complexity: "O(1)",
1163        summary: "Whether a member is in a set.",
1164        group: "set",
1165    },
1166    Spec {
1167        name: "smismember",
1168        arity: -3,
1169        flags: READ_FAST,
1170        first_key: 1,
1171        last_key: 1,
1172        step: 1,
1173        keys: &[RO_ACCESS_AT1],
1174        acl: AC_SET_READ_FAST,
1175        since: "6.2.0",
1176        complexity: "O(N) with N the number of members being asked about",
1177        summary: "Whether each of several members is in a set, in the order asked.",
1178        group: "set",
1179    },
1180    Spec {
1181        name: "smembers",
1182        arity: 2,
1183        flags: &["readonly"],
1184        first_key: 1,
1185        last_key: 1,
1186        step: 1,
1187        keys: &[RO_ACCESS_AT1],
1188        acl: AC_SET_READ_SLOW,
1189        since: "1.0.0",
1190        complexity: "O(N) with N the size of the set",
1191        summary: "Every member of a set.",
1192        group: "set",
1193    },
1194    Spec {
1195        name: "spop",
1196        arity: -2,
1197        flags: WRITE_FAST,
1198        first_key: 1,
1199        last_key: 1,
1200        step: 1,
1201        keys: &[RW_ACCESS_DELETE_AT1],
1202        acl: AC_SET_WRITE_FAST,
1203        since: "1.0.0",
1204        complexity: "O(1) without a count, O(N) with one",
1205        summary: "Take members out of a set at random and hand them back.",
1206        group: "set",
1207    },
1208    Spec {
1209        name: "srandmember",
1210        arity: -2,
1211        flags: &["readonly"],
1212        first_key: 1,
1213        last_key: 1,
1214        step: 1,
1215        keys: &[RO_ACCESS_AT1],
1216        acl: AC_SET_READ_SLOW,
1217        since: "1.0.0",
1218        complexity: "O(1) without a count, O(N) with one",
1219        summary: "Members of a set at random, leaving the set as it was.",
1220        group: "set",
1221    },
1222    Spec {
1223        name: "smove",
1224        arity: 4,
1225        flags: WRITE_FAST,
1226        first_key: 1,
1227        last_key: 2,
1228        step: 1,
1229        keys: &[RW_ACCESS_DELETE_AT1, RW_INSERT_AT2],
1230        acl: AC_SET_WRITE_FAST,
1231        since: "1.0.0",
1232        complexity: "O(1)",
1233        summary: "Move one member from one set to another.",
1234        group: "set",
1235    },
1236    Spec {
1237        name: "sscan",
1238        arity: -3,
1239        flags: &["readonly"],
1240        first_key: 1,
1241        last_key: 1,
1242        step: 1,
1243        keys: &[RO_ACCESS_AT1],
1244        acl: AC_SET_READ_SLOW,
1245        since: "2.8.0",
1246        complexity: "O(1) a call, O(N) for a whole iteration",
1247        summary: "Walk part of a set and say where to carry on from.",
1248        group: "set",
1249    },
1250    Spec {
1251        name: "sinter",
1252        arity: -2,
1253        flags: &["readonly"],
1254        first_key: 1,
1255        last_key: -1,
1256        step: 1,
1257        keys: &[RO_ACCESS_AT1_RM1_1_0],
1258        acl: AC_SET_READ_SLOW,
1259        since: "1.0.0",
1260        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
1261        summary: "The members every one of these sets has.",
1262        group: "set",
1263    },
1264    Spec {
1265        name: "sintercard",
1266        arity: -3,
1267        // The only set command whose keys are counted rather than positioned,
1268        // so the legacy key range cannot describe it and Redis reports zeroes
1269        // in these three fields too. A client that wants the keys reads the key
1270        // specs, which is what the count is for, and movablekeys is how it is
1271        // told to go and read them.
1272        flags: READ_MOVABLE,
1273        first_key: 0,
1274        last_key: 0,
1275        step: 0,
1276        keys: &[RO_ACCESS_AT1_COUNTED],
1277        acl: AC_SET_READ_SLOW,
1278        since: "7.0.0",
1279        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
1280        summary: "How many members every one of these sets has, up to a limit.",
1281        group: "set",
1282    },
1283    Spec {
1284        name: "sinterstore",
1285        arity: -3,
1286        flags: WRITE_OOM,
1287        first_key: 1,
1288        last_key: -1,
1289        step: 1,
1290        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2_RM1_1_0],
1291        acl: AC_SET_WRITE_SLOW,
1292        since: "1.0.0",
1293        complexity: "O(N*M) worst case, N the smallest set and M the number of sets",
1294        summary: "Store the members every one of these sets has.",
1295        group: "set",
1296    },
1297    Spec {
1298        name: "sunion",
1299        arity: -2,
1300        flags: &["readonly"],
1301        first_key: 1,
1302        last_key: -1,
1303        step: 1,
1304        keys: &[RO_ACCESS_AT1_RM1_1_0],
1305        acl: AC_SET_READ_SLOW,
1306        since: "1.0.0",
1307        complexity: "O(N) in the total number of members",
1308        summary: "The members any of these sets has, each once.",
1309        group: "set",
1310    },
1311    Spec {
1312        name: "sunionstore",
1313        arity: -3,
1314        flags: WRITE_OOM,
1315        first_key: 1,
1316        last_key: -1,
1317        step: 1,
1318        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2_RM1_1_0],
1319        acl: AC_SET_WRITE_SLOW,
1320        since: "1.0.0",
1321        complexity: "O(N) in the total number of members",
1322        summary: "Store the members any of these sets has.",
1323        group: "set",
1324    },
1325    Spec {
1326        name: "sdiff",
1327        arity: -2,
1328        flags: &["readonly"],
1329        first_key: 1,
1330        last_key: -1,
1331        step: 1,
1332        keys: &[RO_ACCESS_AT1_RM1_1_0],
1333        acl: AC_SET_READ_SLOW,
1334        since: "1.0.0",
1335        complexity: "O(N) in the total number of members",
1336        summary: "The members of the first set that no later set has.",
1337        group: "set",
1338    },
1339    Spec {
1340        name: "sdiffstore",
1341        arity: -3,
1342        flags: WRITE_OOM,
1343        first_key: 1,
1344        last_key: -1,
1345        step: 1,
1346        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2_RM1_1_0],
1347        acl: AC_SET_WRITE_SLOW,
1348        since: "1.0.0",
1349        complexity: "O(N) in the total number of members",
1350        summary: "Store the members of the first set that no later set has.",
1351        group: "set",
1352    },
1353    // The two 8.10 added, which are to SUNION and SDIFF what SINTERCARD is to
1354    // SINTER, and which describe their keys the same way it does and for the
1355    // same reason.
1356    Spec {
1357        name: "sunioncard",
1358        arity: -3,
1359        flags: READ_MOVABLE,
1360        first_key: 0,
1361        last_key: 0,
1362        step: 0,
1363        keys: &[RO_ACCESS_AT1_COUNTED],
1364        acl: AC_SET_READ_SLOW,
1365        since: "8.10.0",
1366        complexity: "O(N) in the total number of members",
1367        summary: "How many members any of these sets has, up to a limit.",
1368        group: "set",
1369    },
1370    Spec {
1371        name: "sdiffcard",
1372        arity: -3,
1373        flags: READ_MOVABLE,
1374        first_key: 0,
1375        last_key: 0,
1376        step: 0,
1377        keys: &[RO_ACCESS_AT1_COUNTED],
1378        acl: AC_SET_READ_SLOW,
1379        since: "8.10.0",
1380        complexity: "O(N) in the total number of members",
1381        summary: "How many members the first set has that no later set has, up to a limit.",
1382        group: "set",
1383    },
1384    // -------------------------------------------------------------- hashes
1385    Spec {
1386        name: "hset",
1387        arity: -4,
1388        flags: WRITE_FAST_OOM,
1389        first_key: 1,
1390        last_key: 1,
1391        step: 1,
1392        keys: &[RW_UPDATE_AT1],
1393        acl: AC_HASH_WRITE_FAST,
1394        since: "2.0.0",
1395        complexity: "O(N) with N the number of pairs being written",
1396        summary: "Write fields into a hash, creating it if it is not there.",
1397        group: "hash",
1398    },
1399    Spec {
1400        name: "hsetnx",
1401        arity: 4,
1402        flags: WRITE_FAST_OOM,
1403        first_key: 1,
1404        last_key: 1,
1405        step: 1,
1406        keys: &[RW_INSERT_AT1],
1407        acl: AC_HASH_WRITE_FAST,
1408        since: "2.0.0",
1409        complexity: "O(1)",
1410        summary: "Write a field only if the hash does not have it already.",
1411        group: "hash",
1412    },
1413    // Deprecated since 4.0 and still sent by a great deal of code, so it is
1414    // here rather than left out. It is HSET with an OK instead of a count.
1415    Spec {
1416        name: "hmset",
1417        arity: -4,
1418        flags: WRITE_FAST_OOM,
1419        first_key: 1,
1420        last_key: 1,
1421        step: 1,
1422        keys: &[RW_UPDATE_AT1],
1423        acl: AC_HASH_WRITE_FAST,
1424        since: "2.0.0",
1425        complexity: "O(N) with N the number of pairs being written",
1426        summary: "Write fields into a hash and answer OK. Use HSET.",
1427        group: "hash",
1428    },
1429    Spec {
1430        name: "hget",
1431        arity: 3,
1432        flags: READ_FAST,
1433        first_key: 1,
1434        last_key: 1,
1435        step: 1,
1436        keys: &[RO_ACCESS_AT1],
1437        acl: AC_HASH_READ_FAST,
1438        since: "2.0.0",
1439        complexity: "O(1)",
1440        summary: "The value of one field of a hash.",
1441        group: "hash",
1442    },
1443    Spec {
1444        name: "hmget",
1445        arity: -3,
1446        flags: READ_FAST,
1447        first_key: 1,
1448        last_key: 1,
1449        step: 1,
1450        keys: &[RO_ACCESS_AT1],
1451        acl: AC_HASH_READ_FAST,
1452        since: "2.0.0",
1453        complexity: "O(N) with N the number of fields asked for",
1454        summary: "The values of several fields, one reply entry each.",
1455        group: "hash",
1456    },
1457    Spec {
1458        name: "hdel",
1459        arity: -3,
1460        flags: WRITE_FAST,
1461        first_key: 1,
1462        last_key: 1,
1463        step: 1,
1464        keys: &[RW_DELETE_AT1],
1465        acl: AC_HASH_WRITE_FAST,
1466        since: "2.0.0",
1467        complexity: "O(N) with N the number of fields being removed",
1468        summary: "Take fields out of a hash, deleting the key if none are left.",
1469        group: "hash",
1470    },
1471    Spec {
1472        name: "hlen",
1473        arity: 2,
1474        flags: READ_FAST,
1475        first_key: 1,
1476        last_key: 1,
1477        step: 1,
1478        keys: &[RO_AT1],
1479        acl: AC_HASH_READ_FAST,
1480        since: "2.0.0",
1481        complexity: "O(1)",
1482        summary: "How many fields a hash has.",
1483        group: "hash",
1484    },
1485    Spec {
1486        name: "hexists",
1487        arity: 3,
1488        flags: READ_FAST,
1489        first_key: 1,
1490        last_key: 1,
1491        step: 1,
1492        keys: &[RO_AT1],
1493        acl: AC_HASH_READ_FAST,
1494        since: "2.0.0",
1495        complexity: "O(1)",
1496        summary: "Whether a hash has a field.",
1497        group: "hash",
1498    },
1499    Spec {
1500        name: "hstrlen",
1501        arity: 3,
1502        flags: READ_FAST,
1503        first_key: 1,
1504        last_key: 1,
1505        step: 1,
1506        keys: &[RO_AT1],
1507        acl: AC_HASH_READ_FAST,
1508        since: "3.2.0",
1509        complexity: "O(1)",
1510        summary: "How many bytes a field's value is, without sending it.",
1511        group: "hash",
1512    },
1513    Spec {
1514        name: "hgetall",
1515        arity: 2,
1516        flags: &["readonly"],
1517        first_key: 1,
1518        last_key: 1,
1519        step: 1,
1520        keys: &[RO_ACCESS_AT1],
1521        acl: AC_HASH_READ_SLOW,
1522        since: "2.0.0",
1523        complexity: "O(N) in the size of the hash",
1524        summary: "Every field and value, as a map on RESP3.",
1525        group: "hash",
1526    },
1527    Spec {
1528        name: "hkeys",
1529        arity: 2,
1530        flags: &["readonly"],
1531        first_key: 1,
1532        last_key: 1,
1533        step: 1,
1534        keys: &[RO_ACCESS_AT1],
1535        acl: AC_HASH_READ_SLOW,
1536        since: "2.0.0",
1537        complexity: "O(N) in the size of the hash",
1538        summary: "Every field of a hash.",
1539        group: "hash",
1540    },
1541    Spec {
1542        name: "hvals",
1543        arity: 2,
1544        flags: &["readonly"],
1545        first_key: 1,
1546        last_key: 1,
1547        step: 1,
1548        keys: &[RO_ACCESS_AT1],
1549        acl: AC_HASH_READ_SLOW,
1550        since: "2.0.0",
1551        complexity: "O(N) in the size of the hash",
1552        summary: "Every value of a hash.",
1553        group: "hash",
1554    },
1555    Spec {
1556        name: "hincrby",
1557        arity: 4,
1558        flags: WRITE_FAST_OOM,
1559        first_key: 1,
1560        last_key: 1,
1561        step: 1,
1562        keys: &[RW_ACCESS_UPDATE_AT1],
1563        acl: AC_HASH_WRITE_FAST,
1564        since: "2.0.0",
1565        complexity: "O(1)",
1566        summary: "Add an integer to a field, treating a missing one as zero.",
1567        group: "hash",
1568    },
1569    Spec {
1570        name: "hincrbyfloat",
1571        arity: 4,
1572        flags: WRITE_FAST_OOM,
1573        first_key: 1,
1574        last_key: 1,
1575        step: 1,
1576        keys: &[RW_ACCESS_UPDATE_AT1],
1577        acl: AC_HASH_WRITE_FAST,
1578        since: "2.6.0",
1579        complexity: "O(1)",
1580        summary: "Add a float to a field, treating a missing one as zero.",
1581        group: "hash",
1582    },
1583    Spec {
1584        name: "hrandfield",
1585        arity: -2,
1586        flags: &["readonly"],
1587        first_key: 1,
1588        last_key: 1,
1589        step: 1,
1590        keys: &[RO_ACCESS_AT1],
1591        acl: AC_HASH_READ_SLOW,
1592        since: "6.2.0",
1593        complexity: "O(1) without a count, O(N) with one",
1594        summary: "Fields of a hash at random, leaving the hash as it was.",
1595        group: "hash",
1596    },
1597    Spec {
1598        name: "hscan",
1599        arity: -3,
1600        flags: &["readonly"],
1601        first_key: 1,
1602        last_key: 1,
1603        step: 1,
1604        keys: &[RO_ACCESS_AT1],
1605        acl: AC_HASH_READ_SLOW,
1606        since: "2.8.0",
1607        complexity: "O(1) a call, O(N) for a whole iteration",
1608        summary: "Walk part of a hash and say where to carry on from.",
1609        group: "hash",
1610    },
1611    Spec {
1612        name: "hexpire",
1613        arity: -6,
1614        flags: WRITE_FAST,
1615        first_key: 1,
1616        last_key: 1,
1617        step: 1,
1618        keys: &[RW_UPDATE_AT1],
1619        acl: AC_HASH_WRITE_FAST,
1620        since: "7.4.0",
1621        complexity: "O(N) with N the number of fields named",
1622        summary: "Put a deadline in seconds on hash fields.",
1623        group: "hash",
1624    },
1625    Spec {
1626        name: "hpexpire",
1627        arity: -6,
1628        flags: WRITE_FAST,
1629        first_key: 1,
1630        last_key: 1,
1631        step: 1,
1632        keys: &[RW_UPDATE_AT1],
1633        acl: AC_HASH_WRITE_FAST,
1634        since: "7.4.0",
1635        complexity: "O(N) with N the number of fields named",
1636        summary: "Put a deadline in milliseconds on hash fields.",
1637        group: "hash",
1638    },
1639    Spec {
1640        name: "hexpireat",
1641        arity: -6,
1642        flags: WRITE_FAST,
1643        first_key: 1,
1644        last_key: 1,
1645        step: 1,
1646        keys: &[RW_UPDATE_AT1],
1647        acl: AC_HASH_WRITE_FAST,
1648        since: "7.4.0",
1649        complexity: "O(N) with N the number of fields named",
1650        summary: "Put an absolute deadline in unix seconds on hash fields.",
1651        group: "hash",
1652    },
1653    Spec {
1654        name: "hpexpireat",
1655        arity: -6,
1656        flags: WRITE_FAST,
1657        first_key: 1,
1658        last_key: 1,
1659        step: 1,
1660        keys: &[RW_UPDATE_AT1],
1661        acl: AC_HASH_WRITE_FAST,
1662        since: "7.4.0",
1663        complexity: "O(N) with N the number of fields named",
1664        summary: "Put an absolute deadline in unix milliseconds on hash fields.",
1665        group: "hash",
1666    },
1667    Spec {
1668        name: "httl",
1669        arity: -5,
1670        flags: READ_FAST,
1671        first_key: 1,
1672        last_key: 1,
1673        step: 1,
1674        keys: &[RO_ACCESS_AT1],
1675        acl: AC_HASH_READ_FAST,
1676        since: "7.4.0",
1677        complexity: "O(N) with N the number of fields named",
1678        summary: "How long hash fields have left, in seconds.",
1679        group: "hash",
1680    },
1681    Spec {
1682        name: "hpttl",
1683        arity: -5,
1684        flags: READ_FAST,
1685        first_key: 1,
1686        last_key: 1,
1687        step: 1,
1688        keys: &[RO_ACCESS_AT1],
1689        acl: AC_HASH_READ_FAST,
1690        since: "7.4.0",
1691        complexity: "O(N) with N the number of fields named",
1692        summary: "How long hash fields have left, in milliseconds.",
1693        group: "hash",
1694    },
1695    Spec {
1696        name: "hexpiretime",
1697        arity: -5,
1698        flags: READ_FAST,
1699        first_key: 1,
1700        last_key: 1,
1701        step: 1,
1702        keys: &[RO_ACCESS_AT1],
1703        acl: AC_HASH_READ_FAST,
1704        since: "7.4.0",
1705        complexity: "O(N) with N the number of fields named",
1706        summary: "When hash fields fall due, in unix seconds.",
1707        group: "hash",
1708    },
1709    Spec {
1710        name: "hpexpiretime",
1711        arity: -5,
1712        flags: READ_FAST,
1713        first_key: 1,
1714        last_key: 1,
1715        step: 1,
1716        keys: &[RO_ACCESS_AT1],
1717        acl: AC_HASH_READ_FAST,
1718        since: "7.4.0",
1719        complexity: "O(N) with N the number of fields named",
1720        summary: "When hash fields fall due, in unix milliseconds.",
1721        group: "hash",
1722    },
1723    Spec {
1724        name: "hpersist",
1725        arity: -5,
1726        flags: WRITE_FAST,
1727        first_key: 1,
1728        last_key: 1,
1729        step: 1,
1730        keys: &[RW_UPDATE_AT1],
1731        acl: AC_HASH_WRITE_FAST,
1732        since: "7.4.0",
1733        complexity: "O(N) with N the number of fields named",
1734        summary: "Take the deadlines off hash fields.",
1735        group: "hash",
1736    },
1737    Spec {
1738        name: "hgetdel",
1739        arity: -5,
1740        flags: WRITE_FAST,
1741        first_key: 1,
1742        last_key: 1,
1743        step: 1,
1744        keys: &[RW_ACCESS_DELETE_AT1],
1745        acl: AC_HASH_WRITE_FAST,
1746        since: "8.0.0",
1747        complexity: "O(N) with N the number of fields named",
1748        summary: "Read hash fields and delete them.",
1749        group: "hash",
1750    },
1751    Spec {
1752        name: "hgetex",
1753        arity: -5,
1754        flags: WRITE_FAST,
1755        first_key: 1,
1756        last_key: 1,
1757        step: 1,
1758        keys: &[RW_ACCESS_UPDATE_AT1_TTL],
1759        acl: AC_HASH_WRITE_FAST,
1760        since: "8.0.0",
1761        complexity: "O(N) with N the number of fields named",
1762        summary: "Read hash fields and set their deadlines.",
1763        group: "hash",
1764    },
1765    Spec {
1766        name: "hsetex",
1767        arity: -6,
1768        flags: WRITE_FAST_OOM,
1769        first_key: 1,
1770        last_key: 1,
1771        step: 1,
1772        keys: &[RW_UPDATE_AT1],
1773        acl: AC_HASH_WRITE_FAST,
1774        since: "8.0.0",
1775        complexity: "O(N) with N the number of fields being set",
1776        summary: "Set hash fields and their deadlines together.",
1777        group: "hash",
1778    },
1779    // A container with no flags and no keys of its own, which is what a real
1780    // 8.10.1 reports: the write flags and the key index live on `HIMPORT SET`
1781    // and this row is only the name and the categories.
1782    Spec {
1783        name: "himport",
1784        arity: -2,
1785        flags: &[],
1786        first_key: 0,
1787        last_key: 0,
1788        step: 0,
1789        keys: &[],
1790        acl: AC_HASH_SLOW,
1791        since: "8.10.0",
1792        complexity: "Depends on subcommand.",
1793        summary: "A container for session-based hash import commands using fieldsets.",
1794        group: "hash",
1795    },
1796    // ---------------------------------------------------------------- lists
1797    Spec {
1798        name: "lpush",
1799        arity: -3,
1800        flags: WRITE_FAST_OOM,
1801        first_key: 1,
1802        last_key: 1,
1803        step: 1,
1804        keys: &[RW_INSERT_AT1],
1805        acl: AC_LIST_WRITE_FAST,
1806        since: "1.0.0",
1807        complexity: "O(N) with N the number of elements pushed",
1808        summary: "Push elements onto the head of a list.",
1809        group: "list",
1810    },
1811    Spec {
1812        name: "rpush",
1813        arity: -3,
1814        flags: WRITE_FAST_OOM,
1815        first_key: 1,
1816        last_key: 1,
1817        step: 1,
1818        keys: &[RW_INSERT_AT1],
1819        acl: AC_LIST_WRITE_FAST,
1820        since: "1.0.0",
1821        complexity: "O(N) with N the number of elements pushed",
1822        summary: "Push elements onto the tail of a list.",
1823        group: "list",
1824    },
1825    Spec {
1826        name: "lpushx",
1827        arity: -3,
1828        flags: WRITE_FAST_OOM,
1829        first_key: 1,
1830        last_key: 1,
1831        step: 1,
1832        keys: &[RW_INSERT_AT1],
1833        acl: AC_LIST_WRITE_FAST,
1834        since: "2.2.0",
1835        complexity: "O(N) with N the number of elements pushed",
1836        summary: "Push elements onto the head of a list that already exists.",
1837        group: "list",
1838    },
1839    Spec {
1840        name: "rpushx",
1841        arity: -3,
1842        flags: WRITE_FAST_OOM,
1843        first_key: 1,
1844        last_key: 1,
1845        step: 1,
1846        keys: &[RW_INSERT_AT1],
1847        acl: AC_LIST_WRITE_FAST,
1848        since: "2.2.0",
1849        complexity: "O(N) with N the number of elements pushed",
1850        summary: "Push elements onto the tail of a list that already exists.",
1851        group: "list",
1852    },
1853    Spec {
1854        name: "lpop",
1855        arity: -2,
1856        flags: WRITE_FAST,
1857        first_key: 1,
1858        last_key: 1,
1859        step: 1,
1860        keys: &[RW_ACCESS_DELETE_AT1],
1861        acl: AC_LIST_WRITE_FAST,
1862        since: "1.0.0",
1863        complexity: "O(N) with N the count asked for",
1864        summary: "Take elements off the head of a list.",
1865        group: "list",
1866    },
1867    Spec {
1868        name: "rpop",
1869        arity: -2,
1870        flags: WRITE_FAST,
1871        first_key: 1,
1872        last_key: 1,
1873        step: 1,
1874        keys: &[RW_ACCESS_DELETE_AT1],
1875        acl: AC_LIST_WRITE_FAST,
1876        since: "1.0.0",
1877        complexity: "O(N) with N the count asked for",
1878        summary: "Take elements off the tail of a list.",
1879        group: "list",
1880    },
1881    Spec {
1882        name: "llen",
1883        arity: 2,
1884        flags: READ_FAST,
1885        first_key: 1,
1886        last_key: 1,
1887        step: 1,
1888        keys: &[RO_AT1],
1889        acl: AC_LIST_READ_FAST,
1890        since: "1.0.0",
1891        complexity: "O(1)",
1892        summary: "How many elements a list holds.",
1893        group: "list",
1894    },
1895    Spec {
1896        name: "lrange",
1897        arity: 4,
1898        flags: READ_SLOW,
1899        first_key: 1,
1900        last_key: 1,
1901        step: 1,
1902        keys: &[RO_ACCESS_AT1],
1903        acl: AC_LIST_READ_SLOW,
1904        since: "1.0.0",
1905        complexity: "O(S+N) with S the offset of the first element and N the range",
1906        summary: "Read a range of a list, both ends included.",
1907        group: "list",
1908    },
1909    Spec {
1910        name: "lindex",
1911        arity: 3,
1912        flags: READ_SLOW,
1913        first_key: 1,
1914        last_key: 1,
1915        step: 1,
1916        keys: &[RO_ACCESS_AT1],
1917        acl: AC_LIST_READ_SLOW,
1918        since: "1.0.0",
1919        complexity: "O(N) with N the distance to the index from the nearer end",
1920        summary: "Read one element of a list by index.",
1921        group: "list",
1922    },
1923    Spec {
1924        name: "lset",
1925        arity: 4,
1926        flags: WRITE_OOM,
1927        first_key: 1,
1928        last_key: 1,
1929        step: 1,
1930        keys: &[RW_UPDATE_AT1],
1931        acl: AC_LIST_WRITE_SLOW,
1932        since: "1.0.0",
1933        complexity: "O(N) with N the distance to the index from the nearer end",
1934        summary: "Replace one element of a list by index.",
1935        group: "list",
1936    },
1937    Spec {
1938        name: "linsert",
1939        arity: 5,
1940        flags: WRITE_OOM,
1941        first_key: 1,
1942        last_key: 1,
1943        step: 1,
1944        keys: &[RW_INSERT_AT1],
1945        acl: AC_LIST_WRITE_SLOW,
1946        since: "2.2.0",
1947        complexity: "O(N) with N the distance to the pivot from the head",
1948        summary: "Insert an element before or after another one.",
1949        group: "list",
1950    },
1951    Spec {
1952        name: "lrem",
1953        arity: 4,
1954        flags: WRITE_SLOW,
1955        first_key: 1,
1956        last_key: 1,
1957        step: 1,
1958        keys: &[RW_DELETE_AT1],
1959        acl: AC_LIST_WRITE_SLOW,
1960        since: "1.0.0",
1961        complexity: "O(N) with N the length of the list",
1962        summary: "Remove elements equal to a value from a list.",
1963        group: "list",
1964    },
1965    Spec {
1966        name: "ltrim",
1967        arity: 4,
1968        flags: WRITE_SLOW,
1969        first_key: 1,
1970        last_key: 1,
1971        step: 1,
1972        keys: &[RW_DELETE_AT1],
1973        acl: AC_LIST_WRITE_SLOW,
1974        since: "1.0.0",
1975        complexity: "O(N) with N the number of elements thrown away",
1976        summary: "Keep a range of a list and throw the rest away.",
1977        group: "list",
1978    },
1979    Spec {
1980        name: "lpos",
1981        arity: -3,
1982        flags: READ_SLOW,
1983        first_key: 1,
1984        last_key: 1,
1985        step: 1,
1986        keys: &[RO_ACCESS_AT1],
1987        acl: AC_LIST_READ_SLOW,
1988        since: "6.0.6",
1989        complexity: "O(N) with N the length of the list",
1990        summary: "Find where a value sits in a list.",
1991        group: "list",
1992    },
1993    Spec {
1994        name: "rpoplpush",
1995        arity: 3,
1996        flags: WRITE_OOM,
1997        first_key: 1,
1998        last_key: 2,
1999        step: 1,
2000        keys: &[RW_ACCESS_DELETE_AT1, RW_INSERT_AT2],
2001        acl: AC_LIST_WRITE_SLOW,
2002        since: "1.2.0",
2003        complexity: "O(1)",
2004        summary: "Move an element from the tail of one list to the head of another.",
2005        group: "list",
2006    },
2007    Spec {
2008        name: "lmove",
2009        arity: 5,
2010        flags: WRITE_OOM,
2011        first_key: 1,
2012        last_key: 2,
2013        step: 1,
2014        keys: &[RW_ACCESS_DELETE_AT1, RW_INSERT_AT2],
2015        acl: AC_LIST_WRITE_SLOW,
2016        since: "6.2.0",
2017        complexity: "O(1)",
2018        summary: "Move an element from either end of one list to either end of another.",
2019        group: "list",
2020    },
2021    Spec {
2022        name: "lmovem",
2023        arity: -5,
2024        flags: WRITE_OOM,
2025        first_key: 1,
2026        last_key: 2,
2027        step: 1,
2028        keys: &[RW_ACCESS_DELETE_AT1, RW_INSERT_AT2],
2029        acl: AC_LIST_WRITE_SLOW,
2030        since: "8.10.0",
2031        complexity: "O(N) in the number of elements moved",
2032        summary: "Move several elements from either end of one list to either end of another.",
2033        group: "list",
2034    },
2035    // The keys are behind a count, so `first_key` is zero and a cluster client
2036    // has to ask `COMMAND GETKEYS` rather than read a position out of this row.
2037    // That is what `movablekeys` means and it is why the three key fields are
2038    // all zero rather than pointing at argument two.
2039    Spec {
2040        name: "lmpop",
2041        arity: -4,
2042        flags: &["write", "movablekeys"],
2043        first_key: 0,
2044        last_key: 0,
2045        step: 0,
2046        keys: &[RW_ACCESS_DELETE_AT1_COUNTED],
2047        acl: AC_LIST_WRITE_SLOW,
2048        since: "7.0.0",
2049        complexity: "O(N+M) with N the number of keys and M the count popped",
2050        summary: "Pop from the first of several lists that has anything in it.",
2051        group: "list",
2052    },
2053    // The five that wait. `blocking` is what the dispatcher branches on to send
2054    // them somewhere that can park a client, so it is load bearing here rather
2055    // than only being reported.
2056    //
2057    // `BLPOP` and `BRPOP` take their keys up to the timeout, which is the one
2058    // shape in the list group where `last_key` is negative: everything from
2059    // argument one to the second from last.
2060    Spec {
2061        name: "blpop",
2062        arity: -3,
2063        flags: &["write", "blocking"],
2064        first_key: 1,
2065        last_key: -2,
2066        step: 1,
2067        keys: &[RW_ACCESS_DELETE_AT1_RM2_1_0],
2068        acl: AC_LIST_WRITE_BLOCKING,
2069        since: "2.0.0",
2070        complexity: "O(N) with N the number of keys named",
2071        summary: "Pop the head of the first list that has anything, waiting if none does.",
2072        group: "list",
2073    },
2074    Spec {
2075        name: "brpop",
2076        arity: -3,
2077        flags: &["write", "blocking"],
2078        first_key: 1,
2079        last_key: -2,
2080        step: 1,
2081        keys: &[RW_ACCESS_DELETE_AT1_RM2_1_0],
2082        acl: AC_LIST_WRITE_BLOCKING,
2083        since: "2.0.0",
2084        complexity: "O(N) with N the number of keys named",
2085        summary: "Pop the tail of the first list that has anything, waiting if none does.",
2086        group: "list",
2087    },
2088    // Redis marks the two that push somewhere `denyoom` and does not mark the
2089    // pops, because these are the blocking commands that can grow the keyspace.
2090    Spec {
2091        name: "blmove",
2092        arity: 6,
2093        flags: &["write", "denyoom", "blocking"],
2094        first_key: 1,
2095        last_key: 2,
2096        step: 1,
2097        keys: &[RW_ACCESS_DELETE_AT1, RW_INSERT_AT2],
2098        acl: AC_LIST_WRITE_BLOCKING,
2099        since: "6.2.0",
2100        complexity: "O(1)",
2101        summary: "Move an element between two lists, waiting for one to arrive.",
2102        group: "list",
2103    },
2104    Spec {
2105        name: "blmovem",
2106        arity: -6,
2107        flags: &["write", "denyoom", "blocking"],
2108        first_key: 1,
2109        last_key: 2,
2110        step: 1,
2111        keys: &[RW_ACCESS_DELETE_AT1, RW_INSERT_AT2],
2112        acl: AC_LIST_WRITE_BLOCKING,
2113        since: "8.10.0",
2114        complexity: "O(N) in the number of elements moved",
2115        summary: "Move several elements between two lists, waiting for them to arrive.",
2116        group: "list",
2117    },
2118    Spec {
2119        name: "brpoplpush",
2120        arity: 4,
2121        flags: &["write", "denyoom", "blocking"],
2122        first_key: 1,
2123        last_key: 2,
2124        step: 1,
2125        keys: &[RW_ACCESS_DELETE_AT1, RW_INSERT_AT2],
2126        acl: AC_LIST_WRITE_BLOCKING,
2127        since: "2.2.0",
2128        complexity: "O(1)",
2129        summary: "Move a tail element to another list's head, waiting for one to arrive.",
2130        group: "list",
2131    },
2132    // Keys behind a count again, so the same three zeroes `LMPOP` has.
2133    Spec {
2134        name: "blmpop",
2135        arity: -5,
2136        flags: &["write", "blocking", "movablekeys"],
2137        first_key: 0,
2138        last_key: 0,
2139        step: 0,
2140        keys: &[RW_ACCESS_DELETE_AT2_COUNTED],
2141        acl: AC_LIST_WRITE_BLOCKING,
2142        since: "7.0.0",
2143        complexity: "O(N+M) with N the number of keys and M the count popped",
2144        summary: "Pop from the first of several lists that has anything, waiting if none does.",
2145        group: "list",
2146    },
2147    // ------------------------------------------------------------ sorted set
2148    Spec {
2149        name: "zadd",
2150        arity: -4,
2151        flags: WRITE_FAST_OOM,
2152        first_key: 1,
2153        last_key: 1,
2154        step: 1,
2155        keys: &[RW_UPDATE_AT1],
2156        acl: AC_ZSET_WRITE_FAST,
2157        since: "1.2.0",
2158        complexity: "O(log(N)) for each member added",
2159        summary: "Add members with scores, or move the scores of members already there.",
2160        group: "zset",
2161    },
2162    Spec {
2163        name: "zincrby",
2164        arity: 4,
2165        flags: WRITE_FAST_OOM,
2166        first_key: 1,
2167        last_key: 1,
2168        step: 1,
2169        keys: &[RW_ACCESS_UPDATE_AT1],
2170        acl: AC_ZSET_WRITE_FAST,
2171        since: "1.2.0",
2172        complexity: "O(log(N))",
2173        summary: "Add to a member's score, creating the member at zero if it is not there.",
2174        group: "zset",
2175    },
2176    Spec {
2177        name: "zcard",
2178        arity: 2,
2179        flags: READ_FAST,
2180        first_key: 1,
2181        last_key: 1,
2182        step: 1,
2183        keys: &[RO_AT1],
2184        acl: AC_ZSET_READ_FAST,
2185        since: "1.2.0",
2186        complexity: "O(1)",
2187        summary: "How many members a sorted set has.",
2188        group: "zset",
2189    },
2190    Spec {
2191        name: "zscore",
2192        arity: 3,
2193        flags: READ_FAST,
2194        first_key: 1,
2195        last_key: 1,
2196        step: 1,
2197        keys: &[RO_ACCESS_AT1],
2198        acl: AC_ZSET_READ_FAST,
2199        since: "1.2.0",
2200        complexity: "O(1)",
2201        summary: "A member's score, or nothing if it is not there.",
2202        group: "zset",
2203    },
2204    Spec {
2205        name: "zmscore",
2206        arity: -3,
2207        flags: READ_FAST,
2208        first_key: 1,
2209        last_key: 1,
2210        step: 1,
2211        keys: &[RO_ACCESS_AT1],
2212        acl: AC_ZSET_READ_FAST,
2213        since: "6.2.0",
2214        complexity: "O(N) with N the number of members asked about",
2215        summary: "The scores of several members in one round trip.",
2216        group: "zset",
2217    },
2218    Spec {
2219        name: "zrem",
2220        arity: -3,
2221        flags: WRITE_FAST,
2222        first_key: 1,
2223        last_key: 1,
2224        step: 1,
2225        keys: &[RW_DELETE_AT1],
2226        acl: AC_ZSET_WRITE_FAST,
2227        since: "1.2.0",
2228        complexity: "O(M*log(N)) with M the number of members removed",
2229        summary: "Remove members, deleting the key if the last one goes.",
2230        group: "zset",
2231    },
2232    Spec {
2233        name: "zrank",
2234        arity: -3,
2235        flags: READ_FAST,
2236        first_key: 1,
2237        last_key: 1,
2238        step: 1,
2239        keys: &[RO_ACCESS_AT1],
2240        acl: AC_ZSET_READ_FAST,
2241        since: "2.0.0",
2242        complexity: "O(log(N))",
2243        summary: "Where a member sits counting up from the lowest score.",
2244        group: "zset",
2245    },
2246    Spec {
2247        name: "zrevrank",
2248        arity: -3,
2249        flags: READ_FAST,
2250        first_key: 1,
2251        last_key: 1,
2252        step: 1,
2253        keys: &[RO_ACCESS_AT1],
2254        acl: AC_ZSET_READ_FAST,
2255        since: "2.0.0",
2256        complexity: "O(log(N))",
2257        summary: "Where a member sits counting down from the highest score.",
2258        group: "zset",
2259    },
2260    Spec {
2261        name: "zcount",
2262        arity: 4,
2263        flags: READ_FAST,
2264        first_key: 1,
2265        last_key: 1,
2266        step: 1,
2267        keys: &[RO_ACCESS_AT1],
2268        acl: AC_ZSET_READ_FAST,
2269        since: "2.0.0",
2270        complexity: "O(log(N))",
2271        summary: "How many members have scores between two bounds.",
2272        group: "zset",
2273    },
2274    Spec {
2275        name: "zlexcount",
2276        arity: 4,
2277        flags: READ_FAST,
2278        first_key: 1,
2279        last_key: 1,
2280        step: 1,
2281        keys: &[RO_ACCESS_AT1],
2282        acl: AC_ZSET_READ_FAST,
2283        since: "2.8.9",
2284        complexity: "O(log(N))",
2285        summary: "How many members fall between two members, by name.",
2286        group: "zset",
2287    },
2288    Spec {
2289        name: "zrange",
2290        arity: -4,
2291        flags: READ_SLOW,
2292        first_key: 1,
2293        last_key: 1,
2294        step: 1,
2295        keys: &[RO_ACCESS_AT1],
2296        acl: AC_ZSET_READ_SLOW,
2297        since: "1.2.0",
2298        complexity: "O(log(N)+M) with M the number of members answered",
2299        summary: "A window of members, by rank or by score or by name, either way round.",
2300        group: "zset",
2301    },
2302    Spec {
2303        name: "zrevrange",
2304        arity: -4,
2305        flags: READ_SLOW,
2306        first_key: 1,
2307        last_key: 1,
2308        step: 1,
2309        keys: &[RO_ACCESS_AT1],
2310        acl: AC_ZSET_READ_SLOW,
2311        since: "1.2.0",
2312        complexity: "O(log(N)+M) with M the number of members answered",
2313        summary: "A window by rank, counting down from the highest score.",
2314        group: "zset",
2315    },
2316    Spec {
2317        name: "zrangebyscore",
2318        arity: -4,
2319        flags: READ_SLOW,
2320        first_key: 1,
2321        last_key: 1,
2322        step: 1,
2323        keys: &[RO_ACCESS_AT1],
2324        acl: AC_ZSET_READ_SLOW,
2325        since: "1.0.5",
2326        complexity: "O(log(N)+M) with M the number of members answered",
2327        summary: "The members whose scores fall between two bounds.",
2328        group: "zset",
2329    },
2330    Spec {
2331        name: "zrevrangebyscore",
2332        arity: -4,
2333        flags: READ_SLOW,
2334        first_key: 1,
2335        last_key: 1,
2336        step: 1,
2337        keys: &[RO_ACCESS_AT1],
2338        acl: AC_ZSET_READ_SLOW,
2339        since: "2.2.0",
2340        complexity: "O(log(N)+M) with M the number of members answered",
2341        summary: "The same window as ZRANGEBYSCORE, highest score first and named high end first.",
2342        group: "zset",
2343    },
2344    Spec {
2345        name: "zrangebylex",
2346        arity: -4,
2347        flags: READ_SLOW,
2348        first_key: 1,
2349        last_key: 1,
2350        step: 1,
2351        keys: &[RO_ACCESS_AT1],
2352        acl: AC_ZSET_READ_SLOW,
2353        since: "2.8.9",
2354        complexity: "O(log(N)+M) with M the number of members answered",
2355        summary: "The members that fall between two names, for a set where every score is the same.",
2356        group: "zset",
2357    },
2358    Spec {
2359        name: "zrevrangebylex",
2360        arity: -4,
2361        flags: READ_SLOW,
2362        first_key: 1,
2363        last_key: 1,
2364        step: 1,
2365        keys: &[RO_ACCESS_AT1],
2366        acl: AC_ZSET_READ_SLOW,
2367        since: "2.8.9",
2368        complexity: "O(log(N)+M) with M the number of members answered",
2369        summary: "The same window as ZRANGEBYLEX, backwards and named high end first.",
2370        group: "zset",
2371    },
2372    Spec {
2373        name: "zrangestore",
2374        arity: -5,
2375        flags: WRITE_OOM,
2376        first_key: 1,
2377        last_key: 2,
2378        step: 1,
2379        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2],
2380        acl: AC_ZSET_WRITE_SLOW,
2381        since: "6.2.0",
2382        complexity: "O(log(N)+M) with M the number of members stored",
2383        summary: "Write a window of one sorted set into another key.",
2384        group: "zset",
2385    },
2386    Spec {
2387        name: "zremrangebyrank",
2388        arity: 4,
2389        flags: WRITE_SLOW,
2390        first_key: 1,
2391        last_key: 1,
2392        step: 1,
2393        keys: &[RW_DELETE_AT1],
2394        acl: AC_ZSET_WRITE_SLOW,
2395        since: "2.0.0",
2396        complexity: "O(log(N)+M) with M the number of members removed",
2397        summary: "Remove the members in a range of ranks.",
2398        group: "zset",
2399    },
2400    Spec {
2401        name: "zremrangebyscore",
2402        arity: 4,
2403        flags: WRITE_SLOW,
2404        first_key: 1,
2405        last_key: 1,
2406        step: 1,
2407        keys: &[RW_DELETE_AT1],
2408        acl: AC_ZSET_WRITE_SLOW,
2409        since: "1.2.0",
2410        complexity: "O(log(N)+M) with M the number of members removed",
2411        summary: "Remove the members whose scores fall between two bounds.",
2412        group: "zset",
2413    },
2414    Spec {
2415        name: "zremrangebylex",
2416        arity: 4,
2417        flags: WRITE_SLOW,
2418        first_key: 1,
2419        last_key: 1,
2420        step: 1,
2421        keys: &[RW_DELETE_AT1],
2422        acl: AC_ZSET_WRITE_SLOW,
2423        since: "2.8.9",
2424        complexity: "O(log(N)+M) with M the number of members removed",
2425        summary: "Remove the members that fall between two names.",
2426        group: "zset",
2427    },
2428    Spec {
2429        name: "zunion",
2430        arity: -3,
2431        flags: READ_MOVABLE,
2432        first_key: 0,
2433        last_key: 0,
2434        step: 0,
2435        keys: &[RO_ACCESS_AT1_COUNTED],
2436        acl: AC_ZSET_READ_SLOW,
2437        since: "6.2.0",
2438        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2439        summary: "Every member of these sorted sets, with the scores combined.",
2440        group: "zset",
2441    },
2442    Spec {
2443        name: "zinter",
2444        arity: -3,
2445        flags: READ_MOVABLE,
2446        first_key: 0,
2447        last_key: 0,
2448        step: 0,
2449        keys: &[RO_ACCESS_AT1_COUNTED],
2450        acl: AC_ZSET_READ_SLOW,
2451        since: "6.2.0",
2452        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2453        summary: "Only the members all of these sorted sets have, with the scores combined.",
2454        group: "zset",
2455    },
2456    Spec {
2457        name: "zdiff",
2458        arity: -3,
2459        flags: READ_MOVABLE,
2460        first_key: 0,
2461        last_key: 0,
2462        step: 0,
2463        keys: &[RO_ACCESS_AT1_COUNTED],
2464        acl: AC_ZSET_READ_SLOW,
2465        since: "6.2.0",
2466        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2467        summary: "The members of the first that none of the rest have.",
2468        group: "zset",
2469    },
2470    Spec {
2471        name: "zunionstore",
2472        arity: -4,
2473        flags: WRITE_MOVABLE,
2474        first_key: 1,
2475        last_key: 1,
2476        step: 1,
2477        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2_COUNTED],
2478        acl: AC_ZSET_WRITE_SLOW,
2479        since: "2.0.0",
2480        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2481        summary: "Store the union in another key and say how big it is.",
2482        group: "zset",
2483    },
2484    Spec {
2485        name: "zinterstore",
2486        arity: -4,
2487        flags: WRITE_MOVABLE,
2488        first_key: 1,
2489        last_key: 1,
2490        step: 1,
2491        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2_COUNTED],
2492        acl: AC_ZSET_WRITE_SLOW,
2493        since: "2.0.0",
2494        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2495        summary: "Store the intersection in another key and say how big it is.",
2496        group: "zset",
2497    },
2498    Spec {
2499        name: "zdiffstore",
2500        arity: -4,
2501        flags: WRITE_MOVABLE,
2502        first_key: 1,
2503        last_key: 1,
2504        step: 1,
2505        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2_COUNTED],
2506        acl: AC_ZSET_WRITE_SLOW,
2507        since: "6.2.0",
2508        complexity: "O(N)+O(M*log(M)) with N the total number of members and M the number in the answer",
2509        summary: "Store the difference in another key and say how big it is.",
2510        group: "zset",
2511    },
2512    Spec {
2513        name: "zintercard",
2514        arity: -3,
2515        flags: READ_MOVABLE,
2516        first_key: 0,
2517        last_key: 0,
2518        step: 0,
2519        keys: &[RO_ACCESS_AT1_COUNTED],
2520        acl: AC_ZSET_READ_SLOW,
2521        since: "7.0.0",
2522        complexity: "O(N*M) worst case, N the smallest input and M the number of inputs",
2523        summary: "How many members the intersection would have, without building it.",
2524        group: "zset",
2525    },
2526    Spec {
2527        name: "zrandmember",
2528        arity: -2,
2529        flags: READ_SLOW,
2530        first_key: 1,
2531        last_key: 1,
2532        step: 1,
2533        keys: &[RO_ACCESS_AT1],
2534        acl: AC_ZSET_READ_SLOW,
2535        since: "6.2.0",
2536        complexity: "O(N) with N the number of members drawn",
2537        summary: "Draw members at random, with or without replacement.",
2538        group: "zset",
2539    },
2540    Spec {
2541        name: "zscan",
2542        arity: -3,
2543        flags: READ_SLOW,
2544        first_key: 1,
2545        last_key: 1,
2546        step: 1,
2547        keys: &[RO_ACCESS_AT1],
2548        acl: AC_ZSET_READ_SLOW,
2549        since: "2.8.0",
2550        complexity: "O(1) per call, O(N) over a full walk",
2551        summary: "Walk the members and their scores a batch at a time.",
2552        group: "zset",
2553    },
2554    // The pops. Redis calls the two single key ones fast even though they cost a
2555    // logarithm, on the grounds that the logarithm is of a size a client chose.
2556    Spec {
2557        name: "zpopmin",
2558        arity: -2,
2559        flags: WRITE_FAST,
2560        first_key: 1,
2561        last_key: 1,
2562        step: 1,
2563        keys: &[RW_ACCESS_DELETE_AT1],
2564        acl: AC_ZSET_WRITE_FAST,
2565        since: "5.0.0",
2566        complexity: "O(log(N)*M) with M the number of members popped",
2567        summary: "Take the lowest scoring members off and answer them.",
2568        group: "zset",
2569    },
2570    Spec {
2571        name: "zpopmax",
2572        arity: -2,
2573        flags: WRITE_FAST,
2574        first_key: 1,
2575        last_key: 1,
2576        step: 1,
2577        keys: &[RW_ACCESS_DELETE_AT1],
2578        acl: AC_ZSET_WRITE_FAST,
2579        since: "5.0.0",
2580        complexity: "O(log(N)*M) with M the number of members popped",
2581        summary: "Take the highest scoring members off and answer them.",
2582        group: "zset",
2583    },
2584    // Keys behind a count, so the same three zeroes `LMPOP` has, and `write`
2585    // without `denyoom` because a pop cannot grow the keyspace.
2586    Spec {
2587        name: "zmpop",
2588        arity: -4,
2589        flags: &["write", "movablekeys"],
2590        first_key: 0,
2591        last_key: 0,
2592        step: 0,
2593        keys: &[RW_ACCESS_DELETE_AT1_COUNTED],
2594        acl: AC_ZSET_WRITE_SLOW,
2595        since: "7.0.0",
2596        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
2597        summary: "Pop from the first of several sorted sets that has anything in it.",
2598        group: "zset",
2599    },
2600    // The three that wait. `blocking` is what the dispatcher branches on, the
2601    // same as it is for the five list ones.
2602    Spec {
2603        name: "bzpopmin",
2604        arity: -3,
2605        flags: &["write", "blocking", "fast"],
2606        first_key: 1,
2607        last_key: -2,
2608        step: 1,
2609        keys: &[RW_ACCESS_DELETE_AT1_RM2_1_0],
2610        acl: AC_ZSET_BLOCKING_FAST,
2611        since: "5.0.0",
2612        complexity: "O(log(N)) with N the size of the sorted set that answers",
2613        summary: "Take the lowest scoring member off the first sorted set that has one, waiting if none does.",
2614        group: "zset",
2615    },
2616    Spec {
2617        name: "bzpopmax",
2618        arity: -3,
2619        flags: &["write", "blocking", "fast"],
2620        first_key: 1,
2621        last_key: -2,
2622        step: 1,
2623        keys: &[RW_ACCESS_DELETE_AT1_RM2_1_0],
2624        acl: AC_ZSET_BLOCKING_FAST,
2625        since: "5.0.0",
2626        complexity: "O(log(N)) with N the size of the sorted set that answers",
2627        summary: "Take the highest scoring member off the first sorted set that has one, waiting if none does.",
2628        group: "zset",
2629    },
2630    Spec {
2631        name: "bzmpop",
2632        arity: -5,
2633        flags: &["write", "blocking", "movablekeys"],
2634        first_key: 0,
2635        last_key: 0,
2636        step: 0,
2637        keys: &[RW_ACCESS_DELETE_AT2_COUNTED],
2638        acl: AC_ZSET_BLOCKING_SLOW,
2639        since: "7.0.0",
2640        complexity: "O(K) + O(M*log(N)) with K the keys named and M the count popped",
2641        summary: "Pop from the first of several sorted sets that has anything, waiting if none does.",
2642        group: "zset",
2643    },
2644    // ----------------------------------------------------------------- geo
2645    Spec {
2646        name: "geoadd",
2647        arity: -5,
2648        flags: WRITE_OOM,
2649        first_key: 1,
2650        last_key: 1,
2651        step: 1,
2652        keys: &[RW_UPDATE_AT1],
2653        acl: AC_GEO_WRITE,
2654        since: "3.2.0",
2655        complexity: "O(log(N)) per point added",
2656        summary: "Add places to a geo key, which is a sorted set of position hashes.",
2657        group: "geo",
2658    },
2659    Spec {
2660        name: "geopos",
2661        arity: -2,
2662        flags: READ_SLOW,
2663        first_key: 1,
2664        last_key: 1,
2665        step: 1,
2666        keys: &[RO_ACCESS_AT1],
2667        acl: AC_GEO_READ,
2668        since: "3.2.0",
2669        complexity: "O(1) per member asked about",
2670        summary: "Answer where each member is, as a longitude and a latitude.",
2671        group: "geo",
2672    },
2673    Spec {
2674        name: "geodist",
2675        arity: -4,
2676        flags: READ_SLOW,
2677        first_key: 1,
2678        last_key: 1,
2679        step: 1,
2680        keys: &[RO_ACCESS_AT1],
2681        acl: AC_GEO_READ,
2682        since: "3.2.0",
2683        complexity: "O(1)",
2684        summary: "Answer how far apart two members are, in the unit asked for.",
2685        group: "geo",
2686    },
2687    Spec {
2688        name: "geohash",
2689        arity: -2,
2690        flags: READ_SLOW,
2691        first_key: 1,
2692        last_key: 1,
2693        step: 1,
2694        keys: &[RO_ACCESS_AT1],
2695        acl: AC_GEO_READ,
2696        since: "3.2.0",
2697        complexity: "O(1) per member asked about",
2698        summary: "Answer each member's position as a standard eleven character geohash.",
2699        group: "geo",
2700    },
2701    Spec {
2702        name: "geosearch",
2703        arity: -7,
2704        flags: READ_SLOW,
2705        first_key: 1,
2706        last_key: 1,
2707        step: 1,
2708        keys: &[RO_ACCESS_AT1],
2709        acl: AC_GEO_READ,
2710        since: "6.2.0",
2711        complexity: "O(N+log(M)) with N the members in the boxes searched",
2712        summary: "Find the members inside a circle or a rectangle around a point.",
2713        group: "geo",
2714    },
2715    Spec {
2716        name: "geosearchstore",
2717        arity: -8,
2718        flags: WRITE_OOM,
2719        first_key: 1,
2720        last_key: 2,
2721        step: 1,
2722        keys: &[OW_UPDATE_AT1, RO_ACCESS_AT2],
2723        acl: AC_GEO_WRITE,
2724        since: "6.2.0",
2725        complexity: "O(N+log(M)) with N the members in the boxes searched",
2726        summary: "Run a search and write what it found into another key.",
2727        group: "geo",
2728    },
2729    Spec {
2730        name: "georadius",
2731        arity: -6,
2732        flags: WRITE_MOVABLE,
2733        first_key: 1,
2734        last_key: 1,
2735        step: 1,
2736        keys: &[RO_ACCESS_AT1, GEORADIUS_STORE, GEORADIUS_STOREDIST],
2737        acl: AC_GEO_WRITE,
2738        since: "3.2.0",
2739        complexity: "O(N+log(M)) with N the members in the boxes searched",
2740        summary: "The older spelling of a circular search, which can also store.",
2741        group: "geo",
2742    },
2743    Spec {
2744        name: "georadius_ro",
2745        arity: -6,
2746        flags: READ_SLOW,
2747        first_key: 1,
2748        last_key: 1,
2749        step: 1,
2750        keys: &[RO_ACCESS_AT1],
2751        acl: AC_GEO_READ,
2752        since: "3.2.10",
2753        complexity: "O(N+log(M)) with N the members in the boxes searched",
2754        summary: "GEORADIUS without the store options, so a replica can serve it.",
2755        group: "geo",
2756    },
2757    Spec {
2758        name: "georadiusbymember",
2759        arity: -5,
2760        flags: WRITE_MOVABLE,
2761        first_key: 1,
2762        last_key: 1,
2763        step: 1,
2764        keys: &[RO_ACCESS_AT1, BYMEMBER_STORE, BYMEMBER_STOREDIST],
2765        acl: AC_GEO_WRITE,
2766        since: "3.2.0",
2767        complexity: "O(N+log(M)) with N the members in the boxes searched",
2768        summary: "The same search centred on a member rather than on a point.",
2769        group: "geo",
2770    },
2771    Spec {
2772        name: "georadiusbymember_ro",
2773        arity: -5,
2774        flags: READ_SLOW,
2775        first_key: 1,
2776        last_key: 1,
2777        step: 1,
2778        keys: &[RO_ACCESS_AT1],
2779        acl: AC_GEO_READ,
2780        since: "3.2.10",
2781        complexity: "O(N+log(M)) with N the members in the boxes searched",
2782        summary: "GEORADIUSBYMEMBER without the store options.",
2783        group: "geo",
2784    },
2785    // --------------------------------------------------------------- graph
2786    Spec {
2787        name: "g.nadd",
2788        arity: -3,
2789        flags: WRITE_FAST_OOM,
2790        first_key: 1,
2791        last_key: 1,
2792        step: 1,
2793        keys: &[RW_ACCESS_UPDATE_AT1],
2794        acl: AC_GRAPH_WRITE_FAST,
2795        since: "8.8.0",
2796        complexity: "O(N) with N the fields written",
2797        summary: "Write a node and its properties, creating it if it is new.",
2798        group: "graph",
2799    },
2800    Spec {
2801        name: "g.nget",
2802        arity: 3,
2803        flags: READ_FAST,
2804        first_key: 1,
2805        last_key: 1,
2806        step: 1,
2807        keys: &[RO_ACCESS_AT1],
2808        acl: AC_GRAPH_READ_FAST,
2809        since: "8.8.0",
2810        complexity: "O(N) with N the fields on the node",
2811        summary: "Every property on a node.",
2812        group: "graph",
2813    },
2814    Spec {
2815        name: "g.ndel",
2816        arity: 3,
2817        flags: WRITE_FAST,
2818        first_key: 1,
2819        last_key: 1,
2820        step: 1,
2821        keys: &[RW_ACCESS_UPDATE_AT1],
2822        acl: AC_GRAPH_WRITE_FAST,
2823        since: "8.8.0",
2824        complexity: "O(E) with E the edges on the node",
2825        summary: "Delete a node and every edge that touches it.",
2826        group: "graph",
2827    },
2828    Spec {
2829        name: "g.eadd",
2830        arity: -5,
2831        flags: WRITE_FAST_OOM,
2832        first_key: 1,
2833        last_key: 1,
2834        step: 1,
2835        keys: &[RW_ACCESS_UPDATE_AT1],
2836        acl: AC_GRAPH_WRITE_FAST,
2837        since: "8.8.0",
2838        complexity: "O(D) with D the outgoing degree under the label",
2839        summary: "Write an edge and its properties, creating either end if it is new.",
2840        group: "graph",
2841    },
2842    Spec {
2843        name: "g.edel",
2844        arity: 5,
2845        flags: WRITE_FAST,
2846        first_key: 1,
2847        last_key: 1,
2848        step: 1,
2849        keys: &[RW_ACCESS_UPDATE_AT1],
2850        acl: AC_GRAPH_WRITE_FAST,
2851        since: "8.8.0",
2852        complexity: "O(D) with D the outgoing degree under the label",
2853        summary: "Delete one edge between two nodes under a label.",
2854        group: "graph",
2855    },
2856    Spec {
2857        name: "g.out",
2858        arity: -4,
2859        flags: READ_FAST,
2860        first_key: 1,
2861        last_key: 1,
2862        step: 1,
2863        keys: &[RO_ACCESS_AT1],
2864        acl: AC_GRAPH_READ_FAST,
2865        since: "8.8.0",
2866        complexity: "O(N) with N the page asked for",
2867        summary: "Outgoing neighbours under a label, a page at a time.",
2868        group: "graph",
2869    },
2870    Spec {
2871        name: "g.in",
2872        arity: -4,
2873        flags: READ_FAST,
2874        first_key: 1,
2875        last_key: 1,
2876        step: 1,
2877        keys: &[RO_ACCESS_AT1],
2878        acl: AC_GRAPH_READ_FAST,
2879        since: "8.8.0",
2880        complexity: "O(N) with N the page asked for",
2881        summary: "Incoming neighbours under a label, a page at a time.",
2882        group: "graph",
2883    },
2884    Spec {
2885        name: "g.deg",
2886        arity: -4,
2887        flags: READ_FAST,
2888        first_key: 1,
2889        last_key: 1,
2890        step: 1,
2891        keys: &[RO_ACCESS_AT1],
2892        acl: AC_GRAPH_READ_FAST,
2893        since: "8.8.0",
2894        complexity: "O(1)",
2895        summary: "How many edges a node has under a label.",
2896        group: "graph",
2897    },
2898    Spec {
2899        name: "g.neigh",
2900        arity: -4,
2901        flags: READ_SLOW,
2902        first_key: 1,
2903        last_key: 1,
2904        step: 1,
2905        keys: &[RO_ACCESS_AT1],
2906        acl: AC_GRAPH_READ_SLOW,
2907        since: "8.8.0",
2908        complexity: "O(V + E) over the ball the depth reaches",
2909        summary: "Everything reachable within a depth, each node once.",
2910        group: "graph",
2911    },
2912    Spec {
2913        name: "g.path",
2914        arity: -4,
2915        flags: READ_SLOW,
2916        first_key: 1,
2917        last_key: 1,
2918        step: 1,
2919        keys: &[RO_ACCESS_AT1],
2920        acl: AC_GRAPH_READ_SLOW,
2921        since: "8.8.0",
2922        complexity: "O(b^(d/2)) with b the branching factor and d the distance",
2923        summary: "A shortest path between two nodes, searched from both ends.",
2924        group: "graph",
2925    },
2926    // ---------------------------------------------------------------- json
2927    Spec {
2928        name: "json.set",
2929        arity: -4,
2930        flags: JSON_WRITE_OOM,
2931        first_key: 1,
2932        last_key: 1,
2933        step: 1,
2934        keys: &[RW_ACCESS_UPDATE_AT1],
2935        acl: AC_JSON_WRITE,
2936        since: "1.0.0",
2937        complexity: "O(N) with N the size of the document",
2938        summary: "Set the value at a path, creating the document at the root.",
2939        group: "json",
2940    },
2941    Spec {
2942        name: "json.mset",
2943        arity: -4,
2944        flags: JSON_WRITE_OOM,
2945        first_key: 1,
2946        last_key: -1,
2947        step: 3,
2948        keys: &[RW_ACCESS_UPDATE_AT1_RM1_3_0],
2949        acl: AC_JSON_WRITE,
2950        since: "2.6.0",
2951        complexity: "O(K*N) with K the keys and N the size of each document",
2952        summary: "Set the value at a path in each of several documents.",
2953        group: "json",
2954    },
2955    Spec {
2956        name: "json.merge",
2957        arity: -4,
2958        flags: JSON_WRITE_OOM,
2959        first_key: 1,
2960        last_key: 1,
2961        step: 1,
2962        keys: &[RW_ACCESS_UPDATE_AT1],
2963        acl: AC_JSON_WRITE,
2964        since: "2.6.0",
2965        complexity: "O(N) with N the size of the document",
2966        summary: "Apply an RFC 7386 merge patch at a path.",
2967        group: "json",
2968    },
2969    Spec {
2970        name: "json.get",
2971        arity: -2,
2972        flags: JSON_READ,
2973        first_key: 1,
2974        last_key: 1,
2975        step: 1,
2976        keys: &[RO_ACCESS_AT1],
2977        acl: AC_JSON_READ,
2978        since: "1.0.0",
2979        complexity: "O(N) with N the size of what the paths matched",
2980        summary: "The values one or more paths match, as JSON text.",
2981        group: "json",
2982    },
2983    Spec {
2984        name: "json.mget",
2985        arity: -3,
2986        flags: JSON_READ,
2987        first_key: 1,
2988        last_key: -2,
2989        step: 1,
2990        keys: &[RO_ACCESS_AT1_RM2_1_0],
2991        acl: AC_JSON_READ,
2992        since: "1.0.0",
2993        complexity: "O(K*N) with K the keys and N the size of each document",
2994        summary: "One path against several documents, one answer per key.",
2995        group: "json",
2996    },
2997    Spec {
2998        name: "json.del",
2999        arity: -2,
3000        flags: JSON_WRITE,
3001        first_key: 1,
3002        last_key: 1,
3003        step: 1,
3004        keys: &[RW_ACCESS_UPDATE_AT1],
3005        acl: AC_JSON_WRITE,
3006        since: "1.0.0",
3007        complexity: "O(N) with N the size of the document",
3008        summary: "Remove what a path matched, or the key when it is the root.",
3009        group: "json",
3010    },
3011    Spec {
3012        name: "json.forget",
3013        arity: -2,
3014        flags: JSON_WRITE,
3015        first_key: 1,
3016        last_key: 1,
3017        step: 1,
3018        keys: &[RW_ACCESS_UPDATE_AT1],
3019        acl: AC_JSON_WRITE,
3020        since: "1.0.0",
3021        complexity: "O(N) with N the size of the document",
3022        summary: "The same command as JSON.DEL, under its other name.",
3023        group: "json",
3024    },
3025    Spec {
3026        name: "json.type",
3027        arity: -2,
3028        flags: JSON_READ,
3029        first_key: 1,
3030        last_key: 1,
3031        step: 1,
3032        keys: &[RO_ACCESS_AT1],
3033        acl: AC_JSON_READ,
3034        since: "1.0.0",
3035        complexity: "O(N) with N the size of the document",
3036        summary: "The JSON type of what a path matched.",
3037        group: "json",
3038    },
3039    Spec {
3040        name: "json.toggle",
3041        arity: 3,
3042        flags: JSON_WRITE,
3043        first_key: 1,
3044        last_key: 1,
3045        step: 1,
3046        keys: &[RW_ACCESS_UPDATE_AT1],
3047        acl: AC_JSON_WRITE,
3048        since: "2.0.0",
3049        complexity: "O(N) with N the size of the document",
3050        summary: "Flip every boolean a path matched.",
3051        group: "json",
3052    },
3053    Spec {
3054        name: "json.clear",
3055        arity: -2,
3056        flags: JSON_WRITE,
3057        first_key: 1,
3058        last_key: 1,
3059        step: 1,
3060        keys: &[RW_ACCESS_UPDATE_AT1],
3061        acl: AC_JSON_WRITE,
3062        since: "2.0.0",
3063        complexity: "O(N) with N the size of the document",
3064        summary: "Empty the containers and zero the numbers a path matched.",
3065        group: "json",
3066    },
3067    Spec {
3068        name: "json.arrlen",
3069        arity: -2,
3070        flags: JSON_READ,
3071        first_key: 1,
3072        last_key: 1,
3073        step: 1,
3074        keys: &[RO_ACCESS_AT1],
3075        acl: AC_JSON_READ,
3076        since: "1.0.0",
3077        complexity: "O(1)",
3078        summary: "How many elements are in the arrays a path matched.",
3079        group: "json",
3080    },
3081    Spec {
3082        name: "json.objlen",
3083        arity: -2,
3084        flags: JSON_READ,
3085        first_key: 1,
3086        last_key: 1,
3087        step: 1,
3088        keys: &[RO_ACCESS_AT1],
3089        acl: AC_JSON_READ,
3090        since: "1.0.0",
3091        complexity: "O(1)",
3092        summary: "How many members are in the objects a path matched.",
3093        group: "json",
3094    },
3095    Spec {
3096        name: "json.strlen",
3097        arity: -2,
3098        flags: JSON_READ,
3099        first_key: 1,
3100        last_key: 1,
3101        step: 1,
3102        keys: &[RO_ACCESS_AT1],
3103        acl: AC_JSON_READ,
3104        since: "1.0.0",
3105        complexity: "O(1)",
3106        summary: "How long the strings a path matched are, in bytes.",
3107        group: "json",
3108    },
3109    Spec {
3110        name: "json.objkeys",
3111        arity: -2,
3112        flags: JSON_READ,
3113        first_key: 1,
3114        last_key: 1,
3115        step: 1,
3116        keys: &[RO_ACCESS_AT1],
3117        acl: AC_JSON_READ,
3118        since: "1.0.0",
3119        complexity: "O(N) with N the number of members",
3120        summary: "The keys of the objects a path matched.",
3121        group: "json",
3122    },
3123    Spec {
3124        name: "json.arrappend",
3125        arity: -3,
3126        flags: JSON_WRITE_OOM,
3127        first_key: 1,
3128        last_key: 1,
3129        step: 1,
3130        keys: &[RW_ACCESS_UPDATE_AT1],
3131        acl: AC_JSON_WRITE,
3132        since: "1.0.0",
3133        complexity: "O(N) with N the size of the document",
3134        summary: "Add values to the end of the arrays a path matched.",
3135        group: "json",
3136    },
3137    Spec {
3138        name: "json.arrinsert",
3139        arity: -5,
3140        flags: JSON_WRITE_OOM,
3141        first_key: 1,
3142        last_key: 1,
3143        step: 1,
3144        keys: &[RW_ACCESS_UPDATE_AT1],
3145        acl: AC_JSON_WRITE,
3146        since: "1.0.0",
3147        complexity: "O(N) with N the size of the document",
3148        summary: "Put values into the arrays a path matched, at an index.",
3149        group: "json",
3150    },
3151    Spec {
3152        name: "json.arrtrim",
3153        arity: 5,
3154        flags: JSON_WRITE,
3155        first_key: 1,
3156        last_key: 1,
3157        step: 1,
3158        keys: &[RW_ACCESS_UPDATE_AT1],
3159        acl: AC_JSON_WRITE,
3160        since: "1.0.0",
3161        complexity: "O(N) with N the size of the document",
3162        summary: "Keep only a run of the arrays a path matched.",
3163        group: "json",
3164    },
3165    Spec {
3166        name: "json.arrpop",
3167        arity: -2,
3168        flags: JSON_WRITE,
3169        first_key: 1,
3170        last_key: 1,
3171        step: 1,
3172        keys: &[RW_ACCESS_UPDATE_AT1],
3173        acl: AC_JSON_WRITE,
3174        since: "1.0.0",
3175        complexity: "O(N) with N the size of the document",
3176        summary: "Take one element out of the arrays a path matched.",
3177        group: "json",
3178    },
3179    Spec {
3180        name: "json.arrindex",
3181        arity: -4,
3182        flags: JSON_READ,
3183        first_key: 1,
3184        last_key: 1,
3185        step: 1,
3186        keys: &[RO_ACCESS_AT1],
3187        acl: AC_JSON_READ,
3188        since: "1.0.0",
3189        complexity: "O(N) with N the number of elements",
3190        summary: "Where a value first sits in the arrays a path matched.",
3191        group: "json",
3192    },
3193    Spec {
3194        name: "json.numincrby",
3195        arity: 4,
3196        flags: JSON_WRITE,
3197        first_key: 1,
3198        last_key: 1,
3199        step: 1,
3200        keys: &[RW_ACCESS_UPDATE_AT1],
3201        acl: AC_JSON_WRITE,
3202        since: "1.0.0",
3203        complexity: "O(N) with N the size of the document",
3204        summary: "Add to every number a path matched.",
3205        group: "json",
3206    },
3207    Spec {
3208        name: "json.nummultby",
3209        arity: 4,
3210        flags: JSON_WRITE,
3211        first_key: 1,
3212        last_key: 1,
3213        step: 1,
3214        keys: &[RW_ACCESS_UPDATE_AT1],
3215        acl: AC_JSON_WRITE,
3216        since: "1.0.0",
3217        complexity: "O(N) with N the size of the document",
3218        summary: "Multiply every number a path matched.",
3219        group: "json",
3220    },
3221    Spec {
3222        name: "json.numpowby",
3223        arity: 4,
3224        flags: JSON_WRITE,
3225        first_key: 1,
3226        last_key: 1,
3227        step: 1,
3228        keys: &[RW_ACCESS_UPDATE_AT1],
3229        acl: AC_JSON_WRITE,
3230        since: "1.0.0",
3231        complexity: "O(N) with N the size of the document",
3232        summary: "Raise every number a path matched to a power.",
3233        group: "json",
3234    },
3235    Spec {
3236        name: "json.strappend",
3237        arity: -3,
3238        flags: JSON_WRITE_OOM,
3239        first_key: 1,
3240        last_key: 1,
3241        step: 1,
3242        keys: &[RW_ACCESS_UPDATE_AT1],
3243        acl: AC_JSON_WRITE,
3244        since: "1.0.0",
3245        complexity: "O(N) with N the size of the document",
3246        summary: "Add to the end of every string a path matched.",
3247        group: "json",
3248    },
3249    Spec {
3250        name: "json.resp",
3251        arity: -2,
3252        flags: JSON_READ,
3253        first_key: 1,
3254        last_key: 1,
3255        step: 1,
3256        keys: &[RO_ACCESS_AT1],
3257        acl: AC_JSON_READ,
3258        since: "1.0.0",
3259        complexity: "O(N) with N the size of what the path matched",
3260        summary: "What a path matched, as RESP types rather than as JSON text.",
3261        group: "json",
3262    },
3263    Spec {
3264        name: "json.debug",
3265        arity: -2,
3266        flags: JSON_READ_MOVABLE,
3267        first_key: 0,
3268        last_key: 0,
3269        step: 0,
3270        keys: &[],
3271        acl: AC_JSON_READ,
3272        since: "1.0.0",
3273        complexity: "O(N) with N the size of what the path matched",
3274        summary: "How much memory a document takes, and the help for that.",
3275        group: "json",
3276    },
3277    // -------------------------------------------------------------- vector
3278    Spec {
3279        name: "VADD",
3280        arity: -5,
3281        flags: VECTOR_WRITE_OOM,
3282        first_key: 1,
3283        last_key: 1,
3284        step: 1,
3285        keys: &[RW_ACCESS_UPDATE_AT1],
3286        acl: AC_NONE,
3287        since: "8.0.0",
3288        complexity: "O(P*D) with P the partitions probed and D the dimension",
3289        summary: "Add a vector to a vector set under an element name.",
3290        group: "vector",
3291    },
3292    Spec {
3293        name: "VSIM",
3294        arity: -4,
3295        flags: VECTOR_READ,
3296        first_key: 1,
3297        last_key: 1,
3298        step: 1,
3299        keys: &[RW_ACCESS_UPDATE_AT1],
3300        acl: AC_NONE,
3301        since: "8.0.0",
3302        complexity: "O(P*D) with P the partitions probed and D the dimension",
3303        summary: "The elements nearest a vector or nearest another element.",
3304        group: "vector",
3305    },
3306    Spec {
3307        name: "VREM",
3308        arity: 3,
3309        flags: VECTOR_WRITE,
3310        first_key: 1,
3311        last_key: 1,
3312        step: 1,
3313        keys: &[RW_ACCESS_UPDATE_AT1],
3314        acl: AC_NONE,
3315        since: "8.0.0",
3316        complexity: "O(1)",
3317        summary: "Remove an element and its vector from a vector set.",
3318        group: "vector",
3319    },
3320    Spec {
3321        name: "VCARD",
3322        arity: 2,
3323        flags: VECTOR_READ_FAST,
3324        first_key: 1,
3325        last_key: 1,
3326        step: 1,
3327        keys: &[RW_ACCESS_UPDATE_AT1],
3328        acl: AC_NONE,
3329        since: "8.0.0",
3330        complexity: "O(1)",
3331        summary: "How many elements a vector set holds.",
3332        group: "vector",
3333    },
3334    Spec {
3335        name: "VDIM",
3336        arity: 2,
3337        flags: VECTOR_READ_FAST,
3338        first_key: 1,
3339        last_key: 1,
3340        step: 1,
3341        keys: &[RW_ACCESS_UPDATE_AT1],
3342        acl: AC_NONE,
3343        since: "8.0.0",
3344        complexity: "O(1)",
3345        summary: "How many dimensions the vectors in a vector set have.",
3346        group: "vector",
3347    },
3348    Spec {
3349        name: "VEMB",
3350        arity: -3,
3351        flags: VECTOR_READ_FAST,
3352        first_key: 1,
3353        last_key: 1,
3354        step: 1,
3355        keys: &[RW_ACCESS_UPDATE_AT1],
3356        acl: AC_NONE,
3357        since: "8.0.0",
3358        complexity: "O(D) with D the dimension",
3359        summary: "The vector an element went in with.",
3360        group: "vector",
3361    },
3362    Spec {
3363        name: "VINFO",
3364        arity: 2,
3365        flags: VECTOR_READ_FAST,
3366        first_key: 1,
3367        last_key: 1,
3368        step: 1,
3369        keys: &[RW_ACCESS_UPDATE_AT1],
3370        acl: AC_NONE,
3371        since: "8.0.0",
3372        complexity: "O(N) with N the elements, for the attribute count",
3373        summary: "What a vector set is and how its index is tuned.",
3374        group: "vector",
3375    },
3376    Spec {
3377        name: "VISMEMBER",
3378        arity: 3,
3379        flags: VECTOR_READ,
3380        first_key: 1,
3381        last_key: 1,
3382        step: 1,
3383        keys: &[RW_ACCESS_UPDATE_AT1],
3384        acl: AC_NONE,
3385        since: "8.0.0",
3386        complexity: "O(1)",
3387        summary: "Whether an element is in a vector set.",
3388        group: "vector",
3389    },
3390    Spec {
3391        name: "VRANDMEMBER",
3392        arity: -2,
3393        flags: VECTOR_READ,
3394        first_key: 1,
3395        last_key: 1,
3396        step: 1,
3397        keys: &[RW_ACCESS_UPDATE_AT1],
3398        acl: AC_NONE,
3399        since: "8.0.0",
3400        complexity: "O(1) for one, O(N) for a positive count",
3401        summary: "Random elements of a vector set.",
3402        group: "vector",
3403    },
3404    Spec {
3405        name: "VLINKS",
3406        arity: -3,
3407        flags: VECTOR_READ_FAST,
3408        first_key: 1,
3409        last_key: 1,
3410        step: 1,
3411        keys: &[RW_ACCESS_UPDATE_AT1],
3412        acl: AC_NONE,
3413        since: "8.0.0",
3414        complexity: "O(P*D) with P the partitions probed and D the dimension",
3415        summary: "The elements an element is stored next to.",
3416        group: "vector",
3417    },
3418    Spec {
3419        name: "VSETATTR",
3420        arity: 4,
3421        flags: VECTOR_WRITE_FAST,
3422        first_key: 1,
3423        last_key: 1,
3424        step: 1,
3425        keys: &[RW_ACCESS_UPDATE_AT1],
3426        acl: AC_NONE,
3427        since: "8.0.0",
3428        complexity: "O(1)",
3429        summary: "Set the attribute string on an element, or clear it.",
3430        group: "vector",
3431    },
3432    Spec {
3433        name: "VGETATTR",
3434        arity: 3,
3435        flags: VECTOR_READ_FAST,
3436        first_key: 1,
3437        last_key: 1,
3438        step: 1,
3439        keys: &[RW_ACCESS_UPDATE_AT1],
3440        acl: AC_NONE,
3441        since: "8.0.0",
3442        complexity: "O(1)",
3443        summary: "The attribute string on an element.",
3444        group: "vector",
3445    },
3446    Spec {
3447        name: "VRANGE",
3448        arity: -4,
3449        flags: VECTOR_READ,
3450        first_key: 1,
3451        last_key: 1,
3452        step: 1,
3453        keys: &[RW_ACCESS_UPDATE_AT1],
3454        acl: AC_NONE,
3455        since: "8.4.0",
3456        complexity: "O(N log N) with N the elements the range covers",
3457        summary: "The elements of a vector set whose names fall in a range.",
3458        group: "vector",
3459    },
3460    // -------------------------------------------------------------- search
3461    //
3462    // Most of these carry no key spec, and the three zeros are the module's own
3463    // answer rather than a gap here. An index name is not a key: it is not in
3464    // the keyspace, `TYPE` has nothing to say about it, and a cluster client has
3465    // nothing to route on. The suggestion family and four of the five deprecated
3466    // document commands do name real keys and say so. `FT.MGET` is the odd one:
3467    // it names as many keys as a client cares to send and reports none of them,
3468    // which is the module's answer and is copied rather than tidied up.
3469    Spec {
3470        name: "FT.CREATE",
3471        arity: -5,
3472        flags: SEARCH_WRITE_OOM,
3473        first_key: 0,
3474        last_key: 0,
3475        step: 0,
3476        keys: &[],
3477        acl: AC_SEARCH,
3478        since: "1.0.0",
3479        complexity: "O(K) with K the fields declared, plus O(N) over the keyspace when the initial scan runs",
3480        summary: "Create an index over the keys with a prefix, with the given schema.",
3481        group: "search",
3482    },
3483    Spec {
3484        name: "FT._CREATEIFNX",
3485        arity: -5,
3486        flags: SEARCH_WRITE_OOM,
3487        first_key: 0,
3488        last_key: 0,
3489        step: 0,
3490        keys: &[],
3491        acl: AC_SEARCH,
3492        since: "1.0.0",
3493        complexity: "O(K) with K the fields declared, plus O(N) over the keyspace when the initial scan runs",
3494        summary: "Create an index, and say nothing if one of that name is already there.",
3495        group: "search",
3496    },
3497    Spec {
3498        name: "FT.ALTER",
3499        arity: -6,
3500        flags: SEARCH_WRITE_OOM,
3501        first_key: 0,
3502        last_key: 0,
3503        step: 0,
3504        keys: &[],
3505        acl: AC_SEARCH,
3506        since: "1.0.0",
3507        complexity: "O(N) over the keys the index follows, when the fields are backfilled",
3508        summary: "Add fields to an index's schema.",
3509        group: "search",
3510    },
3511    Spec {
3512        name: "FT._ALTERIFNX",
3513        arity: -6,
3514        flags: SEARCH_WRITE_OOM,
3515        first_key: 0,
3516        last_key: 0,
3517        step: 0,
3518        keys: &[],
3519        acl: AC_SEARCH,
3520        since: "1.0.0",
3521        complexity: "O(N) over the keys the index follows, when the fields are backfilled",
3522        summary: "Add fields to a schema, and say nothing about the ones already there.",
3523        group: "search",
3524    },
3525    Spec {
3526        name: "FT.DROPINDEX",
3527        arity: -2,
3528        flags: SEARCH_WRITE,
3529        first_key: 0,
3530        last_key: 0,
3531        step: 0,
3532        keys: &[],
3533        acl: AC_SEARCH_DROP,
3534        since: "2.0.0",
3535        complexity: "O(1), or O(N) over the documents when DD is given",
3536        summary: "Take an index away, and its documents with it when DD is given.",
3537        group: "search",
3538    },
3539    Spec {
3540        name: "FT._DROPINDEXIFX",
3541        arity: -2,
3542        flags: SEARCH_WRITE,
3543        first_key: 0,
3544        last_key: 0,
3545        step: 0,
3546        keys: &[],
3547        acl: AC_SEARCH_DROP,
3548        since: "2.0.0",
3549        complexity: "O(1), or O(N) over the documents when DD is given",
3550        summary: "Take an index away, and say nothing when there is none of that name.",
3551        group: "search",
3552    },
3553    Spec {
3554        name: "FT.DROP",
3555        arity: -1,
3556        flags: SEARCH_WRITE,
3557        first_key: 0,
3558        last_key: 0,
3559        step: 0,
3560        keys: &[],
3561        acl: AC_SEARCH_DROP,
3562        since: "1.0.0",
3563        complexity: "O(1)",
3564        summary: "Take an index away. Deprecated, and FT.DROPINDEX is the name to use.",
3565        group: "search",
3566    },
3567    Spec {
3568        name: "FT._DROPIFX",
3569        arity: -1,
3570        flags: SEARCH_WRITE,
3571        first_key: 0,
3572        last_key: 0,
3573        step: 0,
3574        keys: &[],
3575        acl: AC_SEARCH_WRITE,
3576        since: "1.0.0",
3577        complexity: "O(1)",
3578        summary: "Take an index away and say nothing when there is none. Deprecated.",
3579        group: "search",
3580    },
3581    Spec {
3582        name: "FT.INFO",
3583        arity: 2,
3584        flags: SEARCH_READ,
3585        first_key: 0,
3586        last_key: 0,
3587        step: 0,
3588        keys: &[],
3589        acl: AC_SEARCH,
3590        since: "1.0.0",
3591        complexity: "O(1)",
3592        summary: "Everything the server knows about one index.",
3593        group: "search",
3594    },
3595    Spec {
3596        name: "FT._LIST",
3597        arity: -1,
3598        flags: SEARCH_READ,
3599        first_key: 0,
3600        last_key: 0,
3601        step: 0,
3602        keys: &[],
3603        acl: AC_SEARCH_LIST,
3604        since: "2.0.0",
3605        complexity: "O(N) with N the indexes on the server",
3606        summary: "Every index on the server, by name.",
3607        group: "search",
3608    },
3609    Spec {
3610        name: "FT.CONFIG",
3611        arity: -2,
3612        flags: SEARCH_READ,
3613        first_key: 0,
3614        last_key: 0,
3615        step: 0,
3616        keys: &[],
3617        acl: AC_SEARCH_ADMIN,
3618        since: "1.0.0",
3619        complexity: "O(1)",
3620        summary: "Read, write or describe the search module's settings.",
3621        group: "search",
3622    },
3623    Spec {
3624        name: "_FT.DEBUG",
3625        arity: -2,
3626        flags: SEARCH_READ,
3627        first_key: 0,
3628        last_key: 0,
3629        step: 0,
3630        keys: &[],
3631        acl: AC_SEARCH_DEBUG,
3632        since: "1.0.0",
3633        complexity: "O(N) with N the size of whatever is being dumped.",
3634        summary: "Read an index's own structures back.",
3635        group: "search",
3636    },
3637    Spec {
3638        name: "FT.ALIASADD",
3639        arity: 3,
3640        flags: SEARCH_WRITE_OOM,
3641        first_key: 0,
3642        last_key: 0,
3643        step: 0,
3644        keys: &[],
3645        acl: AC_SEARCH,
3646        since: "1.0.0",
3647        complexity: "O(1)",
3648        summary: "Point another name at an index.",
3649        group: "search",
3650    },
3651    Spec {
3652        name: "FT._ALIASADDIFNX",
3653        arity: 3,
3654        flags: SEARCH_WRITE_OOM,
3655        first_key: 0,
3656        last_key: 0,
3657        step: 0,
3658        keys: &[],
3659        acl: AC_SEARCH,
3660        since: "1.0.0",
3661        complexity: "O(1)",
3662        summary: "Point another name at an index, and say nothing if it is taken.",
3663        group: "search",
3664    },
3665    Spec {
3666        name: "FT.ALIASDEL",
3667        arity: 2,
3668        flags: SEARCH_WRITE,
3669        first_key: 0,
3670        last_key: 0,
3671        step: 0,
3672        keys: &[],
3673        acl: AC_SEARCH,
3674        since: "1.0.0",
3675        complexity: "O(1)",
3676        summary: "Take an alias away.",
3677        group: "search",
3678    },
3679    Spec {
3680        name: "FT._ALIASDELIFX",
3681        arity: 2,
3682        flags: SEARCH_WRITE,
3683        first_key: 0,
3684        last_key: 0,
3685        step: 0,
3686        keys: &[],
3687        acl: AC_SEARCH,
3688        since: "1.0.0",
3689        complexity: "O(1)",
3690        summary: "Take an alias away, and say nothing when there is none.",
3691        group: "search",
3692    },
3693    Spec {
3694        name: "FT.ALIASUPDATE",
3695        arity: 3,
3696        flags: SEARCH_WRITE_OOM,
3697        first_key: 0,
3698        last_key: 0,
3699        step: 0,
3700        keys: &[],
3701        acl: AC_SEARCH,
3702        since: "1.0.0",
3703        complexity: "O(1)",
3704        summary: "Move an alias to another index, adding it when it was not there.",
3705        group: "search",
3706    },
3707    Spec {
3708        name: "FT.ALIASLIST",
3709        arity: 2,
3710        flags: SEARCH_READ,
3711        first_key: 0,
3712        last_key: 0,
3713        step: 0,
3714        keys: &[],
3715        acl: AC_SEARCH,
3716        since: "8.10.0",
3717        complexity: "O(N) with N the aliases pointing at the index",
3718        summary: "The aliases pointing at one index.",
3719        group: "search",
3720    },
3721    Spec {
3722        name: "FT.SEARCH",
3723        arity: -3,
3724        flags: SEARCH_READ,
3725        first_key: 0,
3726        last_key: 0,
3727        step: 0,
3728        keys: &[],
3729        acl: AC_SEARCH,
3730        since: "1.0.0",
3731        complexity: "O(N) with N the documents the query matches",
3732        summary: "The documents a query answers, with their fields.",
3733        group: "search",
3734    },
3735    Spec {
3736        name: "FT.AGGREGATE",
3737        arity: -3,
3738        flags: SEARCH_READ,
3739        first_key: 0,
3740        last_key: 0,
3741        step: 0,
3742        keys: &[],
3743        acl: AC_SEARCH,
3744        since: "1.1.0",
3745        complexity: "O(N) with N the documents the query matches",
3746        summary: "The properties a query answers, run through a pipeline.",
3747        group: "search",
3748    },
3749    Spec {
3750        name: "FT.HYBRID",
3751        arity: -7,
3752        flags: SEARCH_READ,
3753        first_key: 0,
3754        last_key: 0,
3755        step: 0,
3756        keys: &[],
3757        acl: AC_SEARCH,
3758        since: "8.4.0",
3759        complexity: "O(N) with N the documents either branch matches",
3760        summary: "A text query and a vector query over one index, folded into one ranking.",
3761        group: "search",
3762    },
3763    Spec {
3764        name: "FT.PROFILE",
3765        arity: -5,
3766        flags: SEARCH_READ,
3767        first_key: 0,
3768        last_key: 0,
3769        step: 0,
3770        keys: &[],
3771        acl: AC_SEARCH_READ,
3772        since: "2.2.0",
3773        complexity: "O(N) with N the documents the query matches",
3774        summary: "A search or an aggregation with the working shown.",
3775        group: "search",
3776    },
3777    Spec {
3778        name: "FT.CURSOR",
3779        arity: -2,
3780        flags: SEARCH_READ,
3781        first_key: 0,
3782        last_key: 0,
3783        step: 0,
3784        keys: &[],
3785        acl: AC_SEARCH,
3786        since: "1.1.0",
3787        complexity: "O(1)",
3788        summary: "The next chunk of an answer a cursor was left open on.",
3789        group: "search",
3790    },
3791    Spec {
3792        name: "FT.EXPLAIN",
3793        arity: -3,
3794        flags: SEARCH_READ,
3795        first_key: 0,
3796        last_key: 0,
3797        step: 0,
3798        keys: &[],
3799        acl: AC_SEARCH,
3800        since: "1.0.0",
3801        complexity: "O(1)",
3802        summary: "The tree a query parses into, as text.",
3803        group: "search",
3804    },
3805    Spec {
3806        name: "FT.EXPLAINCLI",
3807        arity: -3,
3808        flags: SEARCH_READ,
3809        first_key: 0,
3810        last_key: 0,
3811        step: 0,
3812        keys: &[],
3813        acl: AC_SEARCH,
3814        since: "1.0.0",
3815        complexity: "O(1)",
3816        summary: "The tree a query parses into, one line per reply element.",
3817        group: "search",
3818    },
3819    Spec {
3820        name: "FT.TAGVALS",
3821        arity: 3,
3822        flags: SEARCH_READ,
3823        first_key: 0,
3824        last_key: 0,
3825        step: 0,
3826        keys: &[],
3827        acl: AC_SEARCH_TAGS,
3828        since: "1.0.0",
3829        complexity: "O(N)",
3830        summary: "Every distinct value a tag field holds.",
3831        group: "search",
3832    },
3833    Spec {
3834        name: "FT.DICTADD",
3835        arity: -3,
3836        flags: SEARCH_WRITE_OOM,
3837        first_key: 0,
3838        last_key: 0,
3839        step: 0,
3840        keys: &[],
3841        acl: AC_SEARCH,
3842        since: "1.4.0",
3843        complexity: "O(1)",
3844        summary: "Put terms into a dictionary, making it if it is not there.",
3845        group: "search",
3846    },
3847    Spec {
3848        name: "FT.DICTDEL",
3849        arity: -3,
3850        flags: SEARCH_WRITE,
3851        first_key: 0,
3852        last_key: 0,
3853        step: 0,
3854        keys: &[],
3855        acl: AC_SEARCH,
3856        since: "1.4.0",
3857        complexity: "O(1)",
3858        summary: "Take terms back out of a dictionary.",
3859        group: "search",
3860    },
3861    Spec {
3862        name: "FT.DICTDUMP",
3863        arity: 2,
3864        flags: SEARCH_READ,
3865        first_key: 0,
3866        last_key: 0,
3867        step: 0,
3868        keys: &[],
3869        acl: AC_SEARCH,
3870        since: "1.4.0",
3871        complexity: "O(N)",
3872        summary: "Every term in a dictionary.",
3873        group: "search",
3874    },
3875    Spec {
3876        name: "FT.SYNUPDATE",
3877        arity: -4,
3878        flags: SEARCH_WRITE_OOM,
3879        first_key: 0,
3880        last_key: 0,
3881        step: 0,
3882        keys: &[],
3883        acl: AC_SEARCH,
3884        since: "1.2.0",
3885        complexity: "O(1)",
3886        summary: "Put terms in a synonym group.",
3887        group: "search",
3888    },
3889    Spec {
3890        name: "FT.SYNDUMP",
3891        arity: 2,
3892        flags: SEARCH_READ,
3893        first_key: 0,
3894        last_key: 0,
3895        step: 0,
3896        keys: &[],
3897        acl: AC_SEARCH,
3898        since: "1.2.0",
3899        complexity: "O(1)",
3900        summary: "Every term an index treats as a synonym, and the groups it is in.",
3901        group: "search",
3902    },
3903    Spec {
3904        name: "FT.SPELLCHECK",
3905        arity: -3,
3906        flags: SEARCH_READ,
3907        first_key: 0,
3908        last_key: 0,
3909        step: 0,
3910        keys: &[],
3911        acl: AC_SEARCH,
3912        since: "1.4.0",
3913        complexity: "O(1)",
3914        summary: "Suggestions for the words in a query the index does not hold.",
3915        group: "search",
3916    },
3917    Spec {
3918        name: "FT.ADD",
3919        arity: -1,
3920        flags: SEARCH_WRITE_OOM,
3921        first_key: 2,
3922        last_key: 2,
3923        step: 1,
3924        keys: &[RW_ACCESS_UPDATE_AT2],
3925        acl: AC_SEARCH_WRITE,
3926        since: "1.0.0",
3927        complexity: "O(N) with N the tokens in the document",
3928        summary: "Write a hash and record what the index should think it is worth.",
3929        group: "search",
3930    },
3931    Spec {
3932        name: "FT.SAFEADD",
3933        arity: -1,
3934        flags: SEARCH_WRITE_OOM,
3935        first_key: 2,
3936        last_key: 2,
3937        step: 1,
3938        keys: &[RW_ACCESS_UPDATE_AT2],
3939        acl: AC_SEARCH_WRITE,
3940        since: "1.0.0",
3941        complexity: "O(N) with N the tokens in the document",
3942        summary: "The same write, under the name a cluster client used to send.",
3943        group: "search",
3944    },
3945    Spec {
3946        name: "FT.GET",
3947        arity: -1,
3948        flags: SEARCH_READ,
3949        first_key: 2,
3950        last_key: 2,
3951        step: 1,
3952        keys: &[RO_ACCESS_AT2],
3953        acl: AC_SEARCH_READ,
3954        since: "1.0.0",
3955        complexity: "O(1)",
3956        summary: "The hash under a key, when the index is holding it.",
3957        group: "search",
3958    },
3959    Spec {
3960        name: "FT.MGET",
3961        arity: -1,
3962        flags: SEARCH_READ,
3963        first_key: 0,
3964        last_key: 0,
3965        step: 0,
3966        keys: &[],
3967        acl: AC_SEARCH_READ,
3968        since: "1.0.0",
3969        complexity: "O(N) with N the keys asked about",
3970        summary: "The same, for as many keys as were named.",
3971        group: "search",
3972    },
3973    Spec {
3974        name: "FT.DEL",
3975        arity: -1,
3976        flags: SEARCH_WRITE,
3977        first_key: 2,
3978        last_key: 2,
3979        step: 1,
3980        keys: &[RW_ACCESS_UPDATE_AT2],
3981        acl: AC_SEARCH_WRITE,
3982        since: "1.0.0",
3983        complexity: "O(1)",
3984        summary: "Delete a key, with an index name in front of it.",
3985        group: "search",
3986    },
3987    Spec {
3988        name: "FT.SUGADD",
3989        arity: -4,
3990        flags: SEARCH_WRITE_OOM,
3991        first_key: 1,
3992        last_key: 1,
3993        step: 1,
3994        keys: &[RW_ACCESS_UPDATE_AT1],
3995        acl: AC_SEARCH_WRITE,
3996        since: "1.0.0",
3997        complexity: "O(1)",
3998        summary: "Put a suggestion in a dictionary, or change the one that is there.",
3999        group: "search",
4000    },
4001    Spec {
4002        name: "FT.SUGGET",
4003        arity: -3,
4004        flags: SEARCH_READ,
4005        first_key: 1,
4006        last_key: 1,
4007        step: 1,
4008        keys: &[RO_ACCESS_AT1],
4009        acl: AC_SEARCH_READ,
4010        since: "1.0.0",
4011        complexity: "O(N) with N the suggestions the prefix reaches",
4012        summary: "The best suggestions starting with a prefix.",
4013        group: "search",
4014    },
4015    Spec {
4016        name: "FT.SUGDEL",
4017        arity: 3,
4018        flags: SEARCH_WRITE,
4019        first_key: 1,
4020        last_key: 1,
4021        step: 1,
4022        keys: &[RW_ACCESS_UPDATE_AT1],
4023        acl: AC_SEARCH_WRITE,
4024        since: "1.0.0",
4025        complexity: "O(1)",
4026        summary: "Take a suggestion out of a dictionary.",
4027        group: "search",
4028    },
4029    Spec {
4030        name: "FT.SUGLEN",
4031        arity: 2,
4032        flags: SEARCH_READ,
4033        first_key: 1,
4034        last_key: 1,
4035        step: 1,
4036        keys: &[RO_ACCESS_AT1],
4037        acl: AC_SEARCH_READ,
4038        since: "1.0.0",
4039        complexity: "O(1)",
4040        summary: "How many suggestions a dictionary holds.",
4041        group: "search",
4042    },
4043    // --------------------------------------------------------------- bloom
4044    Spec {
4045        name: "bf.reserve",
4046        arity: -4,
4047        flags: BLOOM_WRITE,
4048        first_key: 1,
4049        last_key: 1,
4050        step: 1,
4051        keys: &[RW_ACCESS_UPDATE_AT1],
4052        acl: AC_BLOOM_WRITE_FAST,
4053        since: "1.0.0",
4054        complexity: "O(1)",
4055        summary: "Make an empty filter with a given capacity and error rate.",
4056        group: "bloom",
4057    },
4058    Spec {
4059        name: "bf.add",
4060        arity: 3,
4061        flags: BLOOM_WRITE,
4062        first_key: 1,
4063        last_key: 1,
4064        step: 1,
4065        keys: &[RW_ACCESS_UPDATE_AT1],
4066        acl: AC_BLOOM_WRITE,
4067        since: "1.0.0",
4068        complexity: "O(K) with K the number of hash functions",
4069        summary: "Add an item, making the filter if the key is free.",
4070        group: "bloom",
4071    },
4072    Spec {
4073        name: "bf.madd",
4074        arity: -3,
4075        flags: BLOOM_WRITE,
4076        first_key: 1,
4077        last_key: 1,
4078        step: 1,
4079        keys: &[RW_ACCESS_UPDATE_AT1],
4080        acl: AC_BLOOM_WRITE,
4081        since: "1.0.0",
4082        complexity: "O(N * K) with N the number of items",
4083        summary: "Add several items, making the filter if the key is free.",
4084        group: "bloom",
4085    },
4086    Spec {
4087        name: "bf.insert",
4088        arity: -4,
4089        flags: BLOOM_WRITE,
4090        first_key: 1,
4091        last_key: 1,
4092        step: 1,
4093        keys: &[RW_ACCESS_UPDATE_AT1],
4094        acl: AC_BLOOM_WRITE,
4095        since: "1.0.0",
4096        complexity: "O(N * K) with N the number of items",
4097        summary: "Add several items to a filter described in the same command.",
4098        group: "bloom",
4099    },
4100    Spec {
4101        name: "bf.exists",
4102        arity: 3,
4103        flags: BLOOM_READ,
4104        first_key: 1,
4105        last_key: 1,
4106        step: 1,
4107        keys: &[RO_ACCESS_AT1],
4108        acl: AC_BLOOM_READ,
4109        since: "1.0.0",
4110        complexity: "O(K) with K the number of hash functions",
4111        summary: "Whether an item is probably in the filter.",
4112        group: "bloom",
4113    },
4114    Spec {
4115        name: "bf.mexists",
4116        arity: -3,
4117        flags: BLOOM_READ,
4118        first_key: 1,
4119        last_key: 1,
4120        step: 1,
4121        keys: &[RO_ACCESS_AT1],
4122        acl: AC_BLOOM_READ,
4123        since: "1.0.0",
4124        complexity: "O(N * K) with N the number of items",
4125        summary: "Whether each of several items is probably in the filter.",
4126        group: "bloom",
4127    },
4128    Spec {
4129        name: "bf.scandump",
4130        arity: 3,
4131        flags: BLOOM_READ,
4132        first_key: 1,
4133        last_key: 1,
4134        step: 1,
4135        keys: &[RO_ACCESS_AT1],
4136        acl: AC_BLOOM_READ,
4137        since: "1.0.0",
4138        complexity: "O(N) with N the size of the chunk",
4139        summary: "One chunk of the filter, to be replayed into BF.LOADCHUNK.",
4140        group: "bloom",
4141    },
4142    Spec {
4143        name: "bf.loadchunk",
4144        arity: 4,
4145        flags: BLOOM_WRITE,
4146        first_key: 1,
4147        last_key: 1,
4148        step: 1,
4149        keys: &[RW_ACCESS_UPDATE_AT1],
4150        acl: AC_BLOOM_WRITE,
4151        since: "1.0.0",
4152        complexity: "O(N) with N the size of the chunk",
4153        summary: "Put back a chunk that BF.SCANDUMP handed out.",
4154        group: "bloom",
4155    },
4156    Spec {
4157        name: "bf.info",
4158        arity: -2,
4159        flags: BLOOM_READ,
4160        first_key: 1,
4161        last_key: 1,
4162        step: 1,
4163        keys: &[RO_ACCESS_AT1],
4164        acl: AC_BLOOM_READ_FAST,
4165        since: "1.0.0",
4166        complexity: "O(1)",
4167        summary: "The shape of the filter, or one field of it.",
4168        group: "bloom",
4169    },
4170    Spec {
4171        name: "bf.card",
4172        arity: 2,
4173        flags: BLOOM_READ,
4174        first_key: 1,
4175        last_key: 1,
4176        step: 1,
4177        keys: &[RO_ACCESS_AT1],
4178        acl: AC_BLOOM_READ_FAST,
4179        since: "2.4.4",
4180        complexity: "O(1)",
4181        summary: "How many items were added to the filter.",
4182        group: "bloom",
4183    },
4184    Spec {
4185        name: "bf.debug",
4186        arity: 2,
4187        flags: BLOOM_READ,
4188        first_key: 1,
4189        last_key: 1,
4190        step: 1,
4191        keys: &[RO_ACCESS_AT1],
4192        acl: AC_BLOOM_READ,
4193        since: "1.0.0",
4194        complexity: "O(1)",
4195        summary: "The chain and a line for each of its links.",
4196        group: "bloom",
4197    },
4198    // -------------------------------------------------------------- cuckoo
4199    Spec {
4200        name: "cf.reserve",
4201        arity: -3,
4202        flags: CUCKOO_WRITE,
4203        first_key: 1,
4204        last_key: 1,
4205        step: 1,
4206        keys: &[RW_ACCESS_UPDATE_AT1],
4207        acl: AC_CUCKOO_WRITE_FAST,
4208        since: "1.0.0",
4209        complexity: "O(1)",
4210        summary: "Make an empty filter with a given capacity.",
4211        group: "cuckoo",
4212    },
4213    Spec {
4214        name: "cf.add",
4215        arity: 3,
4216        flags: CUCKOO_WRITE,
4217        first_key: 1,
4218        last_key: 1,
4219        step: 1,
4220        keys: &[RW_ACCESS_UPDATE_AT1],
4221        acl: AC_CUCKOO_WRITE,
4222        since: "1.0.0",
4223        complexity: "O(1) amortised, O(N) when the chain has to grow",
4224        summary: "Add an item, making the filter if the key is free.",
4225        group: "cuckoo",
4226    },
4227    Spec {
4228        name: "cf.addnx",
4229        arity: 3,
4230        flags: CUCKOO_WRITE,
4231        first_key: 1,
4232        last_key: 1,
4233        step: 1,
4234        keys: &[RW_ACCESS_UPDATE_AT1],
4235        acl: AC_CUCKOO_WRITE,
4236        since: "1.0.0",
4237        complexity: "O(1) amortised, O(N) when the chain has to grow",
4238        summary: "Add an item unless the filter already has it.",
4239        group: "cuckoo",
4240    },
4241    Spec {
4242        name: "cf.insert",
4243        arity: -4,
4244        flags: CUCKOO_WRITE,
4245        first_key: 1,
4246        last_key: 1,
4247        step: 1,
4248        keys: &[RW_ACCESS_UPDATE_AT1],
4249        acl: AC_CUCKOO_WRITE,
4250        since: "1.0.0",
4251        complexity: "O(N) with N the number of items",
4252        summary: "Add several items to a filter described in the same command.",
4253        group: "cuckoo",
4254    },
4255    Spec {
4256        name: "cf.insertnx",
4257        arity: -4,
4258        flags: CUCKOO_WRITE,
4259        first_key: 1,
4260        last_key: 1,
4261        step: 1,
4262        keys: &[RW_ACCESS_UPDATE_AT1],
4263        acl: AC_CUCKOO_WRITE,
4264        since: "1.0.0",
4265        complexity: "O(N) with N the number of items",
4266        summary: "Add several items the filter does not already have.",
4267        group: "cuckoo",
4268    },
4269    Spec {
4270        name: "cf.exists",
4271        arity: 3,
4272        flags: CUCKOO_READ,
4273        first_key: 1,
4274        last_key: 1,
4275        step: 1,
4276        keys: &[RO_ACCESS_AT1],
4277        acl: AC_CUCKOO_READ,
4278        since: "1.0.0",
4279        complexity: "O(1)",
4280        summary: "Whether an item is probably in the filter.",
4281        group: "cuckoo",
4282    },
4283    Spec {
4284        name: "cf.mexists",
4285        arity: -3,
4286        flags: CUCKOO_READ,
4287        first_key: 1,
4288        last_key: 1,
4289        step: 1,
4290        keys: &[RO_ACCESS_AT1],
4291        acl: AC_CUCKOO_READ,
4292        since: "1.0.0",
4293        complexity: "O(N) with N the number of items",
4294        summary: "Whether each of several items is probably in the filter.",
4295        group: "cuckoo",
4296    },
4297    Spec {
4298        name: "cf.count",
4299        arity: 3,
4300        flags: CUCKOO_READ,
4301        first_key: 1,
4302        last_key: 1,
4303        step: 1,
4304        keys: &[RO_ACCESS_AT1],
4305        acl: AC_CUCKOO_READ,
4306        since: "1.0.0",
4307        complexity: "O(1)",
4308        summary: "How many copies of an item the filter thinks it has.",
4309        group: "cuckoo",
4310    },
4311    Spec {
4312        name: "cf.del",
4313        arity: 3,
4314        flags: CUCKOO_DELETE,
4315        first_key: 1,
4316        last_key: 1,
4317        step: 1,
4318        keys: &[RW_ACCESS_UPDATE_AT1],
4319        acl: AC_CUCKOO_WRITE,
4320        since: "1.0.0",
4321        complexity: "O(1)",
4322        summary: "Take one copy of an item out of the filter.",
4323        group: "cuckoo",
4324    },
4325    Spec {
4326        name: "cf.scandump",
4327        arity: 3,
4328        flags: CUCKOO_READ,
4329        first_key: 1,
4330        last_key: 1,
4331        step: 1,
4332        keys: &[RO_ACCESS_AT1],
4333        acl: AC_CUCKOO_READ,
4334        since: "1.0.0",
4335        complexity: "O(N) with N the size of the chunk",
4336        summary: "One chunk of the filter, to be replayed into CF.LOADCHUNK.",
4337        group: "cuckoo",
4338    },
4339    Spec {
4340        name: "cf.loadchunk",
4341        arity: 4,
4342        flags: CUCKOO_WRITE,
4343        first_key: 1,
4344        last_key: 1,
4345        step: 1,
4346        keys: &[RW_ACCESS_UPDATE_AT1],
4347        acl: AC_CUCKOO_WRITE,
4348        since: "1.0.0",
4349        complexity: "O(N) with N the size of the chunk",
4350        summary: "Put back a chunk that CF.SCANDUMP handed out.",
4351        group: "cuckoo",
4352    },
4353    Spec {
4354        name: "cf.info",
4355        arity: 2,
4356        flags: CUCKOO_READ,
4357        first_key: 1,
4358        last_key: 1,
4359        step: 1,
4360        keys: &[RO_ACCESS_AT1],
4361        acl: AC_CUCKOO_READ_FAST,
4362        since: "1.0.0",
4363        complexity: "O(1)",
4364        summary: "The shape of the chain.",
4365        group: "cuckoo",
4366    },
4367    Spec {
4368        name: "cf.debug",
4369        arity: 2,
4370        flags: CUCKOO_READ,
4371        first_key: 1,
4372        last_key: 1,
4373        step: 1,
4374        keys: &[RO_ACCESS_AT1],
4375        acl: AC_CUCKOO_READ,
4376        since: "1.0.0",
4377        complexity: "O(1)",
4378        summary: "The chain's geometry on one line.",
4379        group: "cuckoo",
4380    },
4381    Spec {
4382        name: "cf.compact",
4383        arity: -1,
4384        flags: CUCKOO_READ,
4385        first_key: 1,
4386        last_key: 1,
4387        step: 1,
4388        keys: &[RO_ACCESS_AT1],
4389        acl: AC_CUCKOO_READ,
4390        since: "1.0.0",
4391        complexity: "O(N) with N the number of items in the newer filters",
4392        summary: "Pull the newer filters down into the older ones.",
4393        group: "cuckoo",
4394    },
4395    // ----------------------------------------------------------------- cms
4396    Spec {
4397        name: "cms.initbydim",
4398        arity: 4,
4399        flags: CMS_WRITE,
4400        first_key: 1,
4401        last_key: 1,
4402        step: 1,
4403        keys: &[RW_ACCESS_UPDATE_AT1],
4404        acl: AC_CMS_WRITE_FAST,
4405        since: "2.0.0",
4406        complexity: "O(1)",
4407        summary: "Make an empty sketch of a given width and depth.",
4408        group: "cms",
4409    },
4410    Spec {
4411        name: "cms.initbyprob",
4412        arity: 4,
4413        flags: CMS_WRITE,
4414        first_key: 1,
4415        last_key: 1,
4416        step: 1,
4417        keys: &[RW_ACCESS_UPDATE_AT1],
4418        acl: AC_CMS_WRITE_FAST,
4419        since: "2.0.0",
4420        complexity: "O(1)",
4421        summary: "Make an empty sketch wide enough for a stated tolerance.",
4422        group: "cms",
4423    },
4424    Spec {
4425        name: "cms.incrby",
4426        arity: -4,
4427        flags: CMS_WRITE,
4428        first_key: 1,
4429        last_key: 1,
4430        step: 1,
4431        keys: &[RW_ACCESS_UPDATE_AT1],
4432        acl: AC_CMS_WRITE,
4433        since: "2.0.0",
4434        complexity: "O(N) with N the number of items",
4435        summary: "Add to the count of one or more items.",
4436        group: "cms",
4437    },
4438    Spec {
4439        name: "cms.query",
4440        arity: -3,
4441        flags: CMS_READ,
4442        first_key: 1,
4443        last_key: 1,
4444        step: 1,
4445        keys: &[RO_ACCESS_AT1],
4446        acl: AC_CMS_READ,
4447        since: "2.0.0",
4448        complexity: "O(N) with N the number of items",
4449        summary: "How many times the sketch has seen each item.",
4450        group: "cms",
4451    },
4452    Spec {
4453        name: "cms.merge",
4454        arity: -4,
4455        flags: CMS_WRITE,
4456        first_key: 1,
4457        last_key: 1,
4458        step: 1,
4459        keys: &[RW_ACCESS_UPDATE_AT1],
4460        acl: AC_CMS_WRITE,
4461        since: "2.0.0",
4462        complexity: "O(N * M) with N the sources and M the counters in one",
4463        summary: "Replace a sketch with the weighted sum of others.",
4464        group: "cms",
4465    },
4466    Spec {
4467        name: "cms.info",
4468        arity: 2,
4469        flags: CMS_READ,
4470        first_key: 1,
4471        last_key: 1,
4472        step: 1,
4473        keys: &[RO_ACCESS_AT1],
4474        acl: AC_CMS_READ_FAST,
4475        since: "2.0.0",
4476        complexity: "O(1)",
4477        summary: "The width, the depth and everything ever added.",
4478        group: "cms",
4479    },
4480    // ---------------------------------------------------------------- topk
4481    Spec {
4482        name: "topk.reserve",
4483        arity: -3,
4484        flags: TOPK_WRITE,
4485        first_key: 1,
4486        last_key: 1,
4487        step: 1,
4488        keys: &[RW_ACCESS_UPDATE_AT1],
4489        acl: AC_TOPK_WRITE_FAST,
4490        since: "2.0.0",
4491        complexity: "O(1)",
4492        summary: "Make an empty sketch that keeps the k commonest items.",
4493        group: "topk",
4494    },
4495    Spec {
4496        name: "topk.add",
4497        arity: -3,
4498        flags: TOPK_WRITE,
4499        first_key: 1,
4500        last_key: 1,
4501        step: 1,
4502        keys: &[RW_ACCESS_UPDATE_AT1],
4503        acl: AC_TOPK_WRITE,
4504        since: "2.0.0",
4505        complexity: "O(N * K) with N the items and K the depth",
4506        summary: "Count one occurrence of each item.",
4507        group: "topk",
4508    },
4509    Spec {
4510        name: "topk.incrby",
4511        arity: -4,
4512        flags: TOPK_WRITE,
4513        first_key: 1,
4514        last_key: 1,
4515        step: 1,
4516        keys: &[RW_ACCESS_UPDATE_AT1],
4517        acl: AC_TOPK_WRITE,
4518        since: "2.0.0",
4519        complexity: "O(N * K) with N the items and K the depth",
4520        summary: "Count a stated number of occurrences of each item.",
4521        group: "topk",
4522    },
4523    Spec {
4524        name: "topk.query",
4525        arity: -3,
4526        flags: TOPK_READ,
4527        first_key: 1,
4528        last_key: 1,
4529        step: 1,
4530        keys: &[RO_ACCESS_AT1],
4531        acl: AC_TOPK_READ,
4532        since: "2.0.0",
4533        complexity: "O(N * K) with N the items and K the kept count",
4534        summary: "Whether each item is one of the ones being kept.",
4535        group: "topk",
4536    },
4537    Spec {
4538        name: "topk.count",
4539        arity: -3,
4540        flags: TOPK_READ,
4541        first_key: 1,
4542        last_key: 1,
4543        step: 1,
4544        keys: &[RO_ACCESS_AT1],
4545        acl: AC_TOPK_READ,
4546        since: "2.0.0",
4547        complexity: "O(N * K) with N the items and K the depth",
4548        summary: "How many times the sketch thinks it has seen each item.",
4549        group: "topk",
4550    },
4551    Spec {
4552        name: "topk.list",
4553        arity: -2,
4554        flags: TOPK_READ,
4555        first_key: 1,
4556        last_key: 1,
4557        step: 1,
4558        keys: &[RO_ACCESS_AT1],
4559        acl: AC_TOPK_READ,
4560        since: "2.0.0",
4561        complexity: "O(K log K) with K the kept count",
4562        summary: "The kept items, heaviest first.",
4563        group: "topk",
4564    },
4565    Spec {
4566        name: "topk.info",
4567        arity: 2,
4568        flags: TOPK_READ,
4569        first_key: 1,
4570        last_key: 1,
4571        step: 1,
4572        keys: &[RO_ACCESS_AT1],
4573        acl: AC_TOPK_READ_FAST,
4574        since: "2.0.0",
4575        complexity: "O(1)",
4576        summary: "The four numbers the sketch was made with.",
4577        group: "topk",
4578    },
4579    // ------------------------------------------------------------- tdigest
4580    Spec {
4581        name: "tdigest.create",
4582        arity: -2,
4583        flags: TDIGEST_WRITE,
4584        first_key: 1,
4585        last_key: 1,
4586        step: 1,
4587        keys: &[RW_ACCESS_UPDATE_AT1],
4588        acl: AC_TDIGEST_WRITE_FAST,
4589        since: "2.4.0",
4590        complexity: "O(1)",
4591        summary: "Make an empty digest of a stated compression.",
4592        group: "tdigest",
4593    },
4594    Spec {
4595        name: "tdigest.reset",
4596        arity: 2,
4597        flags: TDIGEST_WRITE,
4598        first_key: 1,
4599        last_key: 1,
4600        step: 1,
4601        keys: &[RW_ACCESS_UPDATE_AT1],
4602        acl: AC_TDIGEST_WRITE_FAST,
4603        since: "2.4.0",
4604        complexity: "O(1)",
4605        summary: "Throw away every sample and keep the shape.",
4606        group: "tdigest",
4607    },
4608    Spec {
4609        name: "tdigest.add",
4610        arity: -3,
4611        flags: TDIGEST_WRITE,
4612        first_key: 1,
4613        last_key: 1,
4614        step: 1,
4615        keys: &[RW_ACCESS_UPDATE_AT1],
4616        acl: AC_TDIGEST_WRITE,
4617        since: "2.4.0",
4618        complexity: "O(N) with N the number of samples",
4619        summary: "Add samples of weight one each.",
4620        group: "tdigest",
4621    },
4622    Spec {
4623        name: "tdigest.merge",
4624        arity: -4,
4625        flags: TDIGEST_MERGE,
4626        first_key: 1,
4627        last_key: 1,
4628        step: 1,
4629        keys: &[RW_ACCESS_UPDATE_AT1, RO_ACCESS_AT2_COUNTED],
4630        acl: AC_TDIGEST_WRITE,
4631        since: "2.4.0",
4632        complexity: "O(N) with N the number of centroids in the inputs",
4633        summary: "Fold digests together into one.",
4634        group: "tdigest",
4635    },
4636    Spec {
4637        name: "tdigest.min",
4638        arity: 2,
4639        flags: TDIGEST_READ,
4640        first_key: 1,
4641        last_key: 1,
4642        step: 1,
4643        keys: &[RO_ACCESS_AT1],
4644        acl: AC_TDIGEST_READ_FAST,
4645        since: "2.4.0",
4646        complexity: "O(1)",
4647        summary: "The smallest sample ever added.",
4648        group: "tdigest",
4649    },
4650    Spec {
4651        name: "tdigest.max",
4652        arity: 2,
4653        flags: TDIGEST_READ,
4654        first_key: 1,
4655        last_key: 1,
4656        step: 1,
4657        keys: &[RO_ACCESS_AT1],
4658        acl: AC_TDIGEST_READ_FAST,
4659        since: "2.4.0",
4660        complexity: "O(1)",
4661        summary: "The largest sample ever added.",
4662        group: "tdigest",
4663    },
4664    Spec {
4665        name: "tdigest.quantile",
4666        arity: -3,
4667        flags: TDIGEST_READ,
4668        first_key: 1,
4669        last_key: 1,
4670        step: 1,
4671        keys: &[RO_ACCESS_AT1],
4672        acl: AC_TDIGEST_READ_FAST,
4673        since: "2.4.0",
4674        complexity: "O(N) with N the number of centroids",
4675        summary: "The value each fraction of the samples falls under.",
4676        group: "tdigest",
4677    },
4678    Spec {
4679        name: "tdigest.cdf",
4680        arity: -3,
4681        flags: TDIGEST_READ,
4682        first_key: 1,
4683        last_key: 1,
4684        step: 1,
4685        keys: &[RO_ACCESS_AT1],
4686        acl: AC_TDIGEST_READ_FAST,
4687        since: "2.4.0",
4688        complexity: "O(N) with N the number of centroids",
4689        summary: "The fraction of the samples at or below each value.",
4690        group: "tdigest",
4691    },
4692    Spec {
4693        name: "tdigest.trimmed_mean",
4694        arity: 4,
4695        flags: TDIGEST_READ,
4696        first_key: 1,
4697        last_key: 1,
4698        step: 1,
4699        keys: &[RO_ACCESS_AT1],
4700        acl: AC_TDIGEST_READ,
4701        since: "2.4.0",
4702        complexity: "O(N) with N the number of centroids",
4703        summary: "The mean of what is left once both tails are cut.",
4704        group: "tdigest",
4705    },
4706    Spec {
4707        name: "tdigest.rank",
4708        arity: -3,
4709        flags: TDIGEST_READ,
4710        first_key: 1,
4711        last_key: 1,
4712        step: 1,
4713        keys: &[RO_ACCESS_AT1],
4714        acl: AC_TDIGEST_READ_FAST,
4715        since: "2.4.0",
4716        complexity: "O(N) with N the number of centroids",
4717        summary: "How many samples each value is above.",
4718        group: "tdigest",
4719    },
4720    Spec {
4721        name: "tdigest.revrank",
4722        arity: -3,
4723        flags: TDIGEST_READ,
4724        first_key: 1,
4725        last_key: 1,
4726        step: 1,
4727        keys: &[RO_ACCESS_AT1],
4728        acl: AC_TDIGEST_READ_FAST,
4729        since: "2.4.0",
4730        complexity: "O(N) with N the number of centroids",
4731        summary: "How many samples each value is below.",
4732        group: "tdigest",
4733    },
4734    Spec {
4735        name: "tdigest.byrank",
4736        arity: -3,
4737        flags: TDIGEST_READ,
4738        first_key: 1,
4739        last_key: 1,
4740        step: 1,
4741        keys: &[RO_ACCESS_AT1],
4742        acl: AC_TDIGEST_READ_FAST,
4743        since: "2.4.0",
4744        complexity: "O(N) with N the number of centroids",
4745        summary: "The value at each rank counting up from the smallest.",
4746        group: "tdigest",
4747    },
4748    Spec {
4749        name: "tdigest.byrevrank",
4750        arity: -3,
4751        flags: TDIGEST_READ,
4752        first_key: 1,
4753        last_key: 1,
4754        step: 1,
4755        keys: &[RO_ACCESS_AT1],
4756        acl: AC_TDIGEST_READ_FAST,
4757        since: "2.4.0",
4758        complexity: "O(N) with N the number of centroids",
4759        summary: "The value at each rank counting down from the largest.",
4760        group: "tdigest",
4761    },
4762    Spec {
4763        name: "tdigest.info",
4764        arity: 2,
4765        flags: TDIGEST_READ,
4766        first_key: 1,
4767        last_key: 1,
4768        step: 1,
4769        keys: &[RO_ACCESS_AT1],
4770        acl: AC_TDIGEST_READ_FAST,
4771        since: "2.4.0",
4772        complexity: "O(1)",
4773        summary: "The nine numbers the digest keeps about itself.",
4774        group: "tdigest",
4775    },
4776    // ------------------------------------------------------------------ ts
4777    Spec {
4778        name: "ts.create",
4779        arity: -2,
4780        flags: TS_WRITE,
4781        first_key: 1,
4782        last_key: 1,
4783        step: 1,
4784        keys: &[RW_ACCESS_UPDATE_AT1],
4785        acl: AC_TS_WRITE_FAST,
4786        since: "1.0.0",
4787        complexity: "O(1)",
4788        summary: "Make an empty series and say how it should behave.",
4789        group: "ts",
4790    },
4791    Spec {
4792        name: "ts.alter",
4793        arity: -2,
4794        flags: TS_WRITE,
4795        first_key: 1,
4796        last_key: 1,
4797        step: 1,
4798        keys: &[RW_ACCESS_UPDATE_AT1],
4799        acl: AC_TS_WRITE,
4800        since: "1.0.0",
4801        complexity: "O(N) with N the labels being set",
4802        summary: "Change how a series behaves, leaving what was not named alone.",
4803        group: "ts",
4804    },
4805    Spec {
4806        name: "ts.add",
4807        arity: -4,
4808        flags: TS_WRITE,
4809        first_key: 1,
4810        last_key: 1,
4811        step: 1,
4812        keys: &[RW_ACCESS_UPDATE_AT1],
4813        acl: AC_TS_WRITE,
4814        since: "1.0.0",
4815        complexity: "O(M) with M the samples in the chunk a backfill lands in",
4816        summary: "Put a sample in, making the series if it is not there.",
4817        group: "ts",
4818    },
4819    Spec {
4820        name: "ts.madd",
4821        arity: -4,
4822        flags: TS_WRITE,
4823        first_key: 1,
4824        last_key: -1,
4825        step: 3,
4826        keys: &[RW_ACCESS_UPDATE_AT1_RM1_3_0],
4827        acl: AC_TS_WRITE,
4828        since: "1.0.0",
4829        complexity: "O(N * M) with N the samples given",
4830        summary: "Put a sample in each of several series.",
4831        group: "ts",
4832    },
4833    Spec {
4834        name: "ts.incrby",
4835        arity: -3,
4836        flags: TS_WRITE,
4837        first_key: 1,
4838        last_key: 1,
4839        step: 1,
4840        keys: &[RW_ACCESS_UPDATE_AT1],
4841        acl: AC_TS_WRITE,
4842        since: "1.0.0",
4843        complexity: "O(M) with M the samples in the last chunk",
4844        summary: "Add to the newest value and store the answer.",
4845        group: "ts",
4846    },
4847    Spec {
4848        name: "ts.decrby",
4849        arity: -3,
4850        flags: TS_WRITE,
4851        first_key: 1,
4852        last_key: 1,
4853        step: 1,
4854        keys: &[RW_ACCESS_UPDATE_AT1],
4855        acl: AC_TS_WRITE,
4856        since: "1.0.0",
4857        complexity: "O(M) with M the samples in the last chunk",
4858        summary: "Take away from the newest value and store the answer.",
4859        group: "ts",
4860    },
4861    Spec {
4862        name: "ts.del",
4863        arity: 4,
4864        flags: TS_DELETE,
4865        first_key: 1,
4866        last_key: 1,
4867        step: 1,
4868        keys: &[RW_ACCESS_UPDATE_AT1],
4869        acl: AC_TS_WRITE,
4870        since: "1.6.0",
4871        complexity: "O(N) with N the samples in the span",
4872        summary: "Take out every sample between two timestamps.",
4873        group: "ts",
4874    },
4875    Spec {
4876        name: "ts.get",
4877        arity: -2,
4878        flags: TS_READ,
4879        first_key: 1,
4880        last_key: 1,
4881        step: 1,
4882        keys: &[RO_ACCESS_AT1],
4883        acl: AC_TS_READ_FAST,
4884        since: "1.0.0",
4885        complexity: "O(1)",
4886        summary: "The newest sample in a series.",
4887        group: "ts",
4888    },
4889    Spec {
4890        name: "ts.info",
4891        arity: -2,
4892        flags: TS_READ,
4893        first_key: 1,
4894        last_key: 1,
4895        step: 1,
4896        keys: &[RO_ACCESS_AT1],
4897        acl: AC_TS_READ_FAST,
4898        since: "1.0.0",
4899        complexity: "O(1)",
4900        summary: "The fourteen things a series says about itself.",
4901        group: "ts",
4902    },
4903    Spec {
4904        name: "ts.range",
4905        arity: -4,
4906        flags: TS_READ,
4907        first_key: 1,
4908        last_key: 1,
4909        step: 1,
4910        keys: &[RO_ACCESS_AT1],
4911        acl: AC_TS_READ,
4912        since: "1.0.0",
4913        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4914        summary: "The samples in a span, oldest first, in buckets if asked for.",
4915        group: "ts",
4916    },
4917    Spec {
4918        name: "ts.revrange",
4919        arity: -4,
4920        flags: TS_READ,
4921        first_key: 1,
4922        last_key: 1,
4923        step: 1,
4924        keys: &[RO_ACCESS_AT1],
4925        acl: AC_TS_READ,
4926        since: "1.4.0",
4927        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4928        summary: "The same span, newest first.",
4929        group: "ts",
4930    },
4931    Spec {
4932        name: "ts.nrange",
4933        arity: -5,
4934        flags: TS_READ_MOVABLE,
4935        first_key: 0,
4936        last_key: 0,
4937        step: 0,
4938        keys: &[RO_ACCESS_AT1_COUNTED],
4939        acl: AC_TS_READ,
4940        since: "8.10.0",
4941        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4942        summary: "The same span out of several series, lined up on the timestamps.",
4943        group: "ts",
4944    },
4945    Spec {
4946        name: "ts.nrevrange",
4947        arity: -5,
4948        flags: TS_READ_MOVABLE,
4949        first_key: 0,
4950        last_key: 0,
4951        step: 0,
4952        keys: &[RO_ACCESS_AT1_COUNTED],
4953        acl: AC_TS_READ,
4954        since: "8.10.0",
4955        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples in the span",
4956        summary: "The same rows, newest first.",
4957        group: "ts",
4958    },
4959    Spec {
4960        name: "ts.read",
4961        arity: -3,
4962        flags: TS_READ,
4963        first_key: 1,
4964        last_key: 1,
4965        step: 1,
4966        keys: &[RO_ACCESS_AT1],
4967        acl: AC_TS_READ,
4968        since: "8.10.0",
4969        complexity: "O(n/m+k) with n the samples, m the chunk size and k the samples answered",
4970        summary: "Every sample from a timestamp to the end of the series.",
4971        group: "ts",
4972    },
4973    Spec {
4974        name: "ts.queryindex",
4975        arity: -2,
4976        flags: TS_READ,
4977        first_key: 0,
4978        last_key: 0,
4979        step: 0,
4980        keys: &[],
4981        acl: AC_TS_READ,
4982        since: "1.0.0",
4983        complexity: "O(n) with n the series in the keyspace",
4984        summary: "The series a filter list takes, by key name.",
4985        group: "ts",
4986    },
4987    Spec {
4988        name: "ts.querylabels",
4989        arity: -2,
4990        flags: TS_READ,
4991        first_key: 0,
4992        last_key: 0,
4993        step: 0,
4994        keys: &[],
4995        acl: AC_TS_READ,
4996        since: "8.10.0",
4997        complexity: "O(n) with n the series in the keyspace",
4998        summary: "The label names in use, or the values one of them takes.",
4999        group: "ts",
5000    },
5001    Spec {
5002        name: "ts.mget",
5003        arity: -3,
5004        flags: TS_READ,
5005        first_key: 0,
5006        last_key: 0,
5007        step: 0,
5008        keys: &[],
5009        acl: AC_TS_READ,
5010        since: "1.0.0",
5011        complexity: "O(n) with n the series in the keyspace",
5012        summary: "The newest sample of every series a filter list takes.",
5013        group: "ts",
5014    },
5015    Spec {
5016        name: "ts.mrange",
5017        arity: -4,
5018        flags: TS_READ,
5019        first_key: 0,
5020        last_key: 0,
5021        step: 0,
5022        keys: &[],
5023        acl: AC_TS_READ,
5024        since: "1.0.0",
5025        complexity: "O(n) with n the series in the keyspace",
5026        summary: "A span out of every series a filter list takes, oldest first.",
5027        group: "ts",
5028    },
5029    Spec {
5030        name: "ts.mrevrange",
5031        arity: -4,
5032        flags: TS_READ,
5033        first_key: 0,
5034        last_key: 0,
5035        step: 0,
5036        keys: &[],
5037        acl: AC_TS_READ,
5038        since: "1.4.0",
5039        complexity: "O(n) with n the series in the keyspace",
5040        summary: "The same spans, newest first.",
5041        group: "ts",
5042    },
5043    Spec {
5044        name: "ts.createrule",
5045        arity: -5,
5046        flags: TS_RULE,
5047        first_key: 1,
5048        last_key: 2,
5049        step: 1,
5050        keys: &[RW_ACCESS_UPDATE_AT1_R1_1_0],
5051        acl: AC_TS_WRITE,
5052        since: "1.0.0",
5053        complexity: "O(1)",
5054        summary: "Fold one series into another as it is written to.",
5055        group: "ts",
5056    },
5057    Spec {
5058        name: "ts.deleterule",
5059        arity: 3,
5060        flags: TS_DELETE,
5061        first_key: 1,
5062        last_key: 2,
5063        step: 1,
5064        keys: &[RW_ACCESS_UPDATE_AT1_R1_1_0],
5065        acl: AC_TS_WRITE_FAST,
5066        since: "1.0.0",
5067        complexity: "O(1)",
5068        summary: "Stop folding one series into another.",
5069        group: "ts",
5070    },
5071    // --------------------------------------------------------------- array
5072    Spec {
5073        name: "arset",
5074        arity: -4,
5075        flags: WRITE_FAST_OOM,
5076        first_key: 1,
5077        last_key: 1,
5078        step: 1,
5079        keys: &[RW_UPDATE_AT1],
5080        acl: AC_ARRAY_WRITE_FAST,
5081        since: "8.8.0",
5082        complexity: "O(N) with N the number of values",
5083        summary: "Write values into consecutive positions from an index.",
5084        group: "array",
5085    },
5086    Spec {
5087        name: "armset",
5088        arity: -4,
5089        flags: WRITE_FAST_OOM,
5090        first_key: 1,
5091        last_key: 1,
5092        step: 1,
5093        keys: &[RW_UPDATE_AT1],
5094        acl: AC_ARRAY_WRITE_FAST,
5095        since: "8.8.0",
5096        complexity: "O(N) with N the number of pairs",
5097        summary: "Write index and value pairs, which need not be neighbours.",
5098        group: "array",
5099    },
5100    Spec {
5101        name: "arget",
5102        arity: 3,
5103        flags: READ_FAST,
5104        first_key: 1,
5105        last_key: 1,
5106        step: 1,
5107        keys: &[RO_ACCESS_AT1],
5108        acl: AC_ARRAY_READ_FAST,
5109        since: "8.8.0",
5110        complexity: "O(1)",
5111        summary: "The value at one index, or a null if nothing is there.",
5112        group: "array",
5113    },
5114    Spec {
5115        name: "armget",
5116        arity: -3,
5117        flags: READ_FAST,
5118        first_key: 1,
5119        last_key: 1,
5120        step: 1,
5121        keys: &[RO_ACCESS_AT1],
5122        acl: AC_ARRAY_READ_FAST,
5123        since: "8.8.0",
5124        complexity: "O(N) with N the number of indices",
5125        summary: "The values at the indices named, in the order named.",
5126        group: "array",
5127    },
5128    Spec {
5129        name: "argetrange",
5130        arity: 4,
5131        flags: READ_SLOW,
5132        first_key: 1,
5133        last_key: 1,
5134        step: 1,
5135        keys: &[RO_ACCESS_AT1],
5136        acl: AC_ARRAY_READ_SLOW,
5137        since: "8.8.0",
5138        complexity: "O(N) with N the length of the range",
5139        summary: "One reply per position between two indices, holes included.",
5140        group: "array",
5141    },
5142    Spec {
5143        name: "arlen",
5144        arity: 2,
5145        flags: READ_FAST,
5146        first_key: 1,
5147        last_key: 1,
5148        step: 1,
5149        keys: &[RO_ACCESS_AT1],
5150        acl: AC_ARRAY_READ_FAST,
5151        since: "8.8.0",
5152        complexity: "O(1)",
5153        summary: "The highest populated index plus one.",
5154        group: "array",
5155    },
5156    Spec {
5157        name: "arcount",
5158        arity: 2,
5159        flags: READ_FAST,
5160        first_key: 1,
5161        last_key: 1,
5162        step: 1,
5163        keys: &[RO_ACCESS_AT1],
5164        acl: AC_ARRAY_READ_FAST,
5165        since: "8.8.0",
5166        complexity: "O(1)",
5167        summary: "How many indices hold something.",
5168        group: "array",
5169    },
5170    Spec {
5171        name: "ardel",
5172        arity: -3,
5173        flags: WRITE_FAST,
5174        first_key: 1,
5175        last_key: 1,
5176        step: 1,
5177        keys: &[RW_DELETE_AT1],
5178        acl: AC_ARRAY_WRITE_FAST,
5179        since: "8.8.0",
5180        complexity: "O(N) with N the number of indices",
5181        summary: "Empty the indices named and say how many held something.",
5182        group: "array",
5183    },
5184    Spec {
5185        name: "ardelrange",
5186        arity: -4,
5187        flags: WRITE_SLOW,
5188        first_key: 1,
5189        last_key: 1,
5190        step: 1,
5191        keys: &[RW_DELETE_AT1],
5192        acl: AC_ARRAY_WRITE_SLOW,
5193        since: "8.8.0",
5194        complexity: "O(N) with N the elements touched, not the span asked for",
5195        summary: "Empty one or more ranges of indices.",
5196        group: "array",
5197    },
5198    Spec {
5199        name: "arinsert",
5200        arity: -3,
5201        flags: WRITE_FAST_OOM,
5202        first_key: 1,
5203        last_key: 1,
5204        step: 1,
5205        keys: &[RW_UPDATE_AT1],
5206        acl: AC_ARRAY_WRITE_FAST,
5207        since: "8.8.0",
5208        complexity: "O(N) with N the number of values",
5209        summary: "Append values at the insert cursor.",
5210        group: "array",
5211    },
5212    Spec {
5213        name: "arring",
5214        arity: -4,
5215        flags: WRITE_OOM,
5216        first_key: 1,
5217        last_key: 1,
5218        step: 1,
5219        keys: &[RW_UPDATE_AT1],
5220        acl: AC_ARRAY_WRITE_SLOW,
5221        since: "8.8.0",
5222        complexity: "O(N) with N the values, plus the ring size when it changes",
5223        summary: "Append values into a ring of the given size.",
5224        group: "array",
5225    },
5226    Spec {
5227        name: "arnext",
5228        arity: 2,
5229        flags: READ_FAST,
5230        first_key: 1,
5231        last_key: 1,
5232        step: 1,
5233        keys: &[RO_ACCESS_AT1],
5234        acl: AC_ARRAY_READ_FAST,
5235        since: "8.8.0",
5236        complexity: "O(1)",
5237        summary: "The index the next append would write to.",
5238        group: "array",
5239    },
5240    Spec {
5241        name: "arseek",
5242        arity: 3,
5243        flags: WRITE_FAST,
5244        first_key: 1,
5245        last_key: 1,
5246        step: 1,
5247        keys: &[RW_UPDATE_AT1],
5248        acl: AC_ARRAY_WRITE_FAST,
5249        since: "8.8.0",
5250        complexity: "O(1)",
5251        summary: "Point the insert cursor at an index.",
5252        group: "array",
5253    },
5254    Spec {
5255        name: "arlastitems",
5256        arity: -3,
5257        flags: READ_SLOW,
5258        first_key: 1,
5259        last_key: 1,
5260        step: 1,
5261        keys: &[RO_ACCESS_AT1],
5262        acl: AC_ARRAY_READ_SLOW,
5263        since: "8.8.0",
5264        complexity: "O(N) with N the count asked for",
5265        summary: "The newest positions from the insert cursor, holes included.",
5266        group: "array",
5267    },
5268    Spec {
5269        name: "arscan",
5270        arity: -4,
5271        flags: READ_SLOW,
5272        first_key: 1,
5273        last_key: 1,
5274        step: 1,
5275        keys: &[RO_ACCESS_AT1],
5276        acl: AC_ARRAY_READ_SLOW,
5277        since: "8.8.0",
5278        complexity: "O(N) with N the elements found, not the span asked for",
5279        summary: "Index and value pairs for what a range holds, skipping holes.",
5280        group: "array",
5281    },
5282    Spec {
5283        name: "argrep",
5284        arity: -6,
5285        flags: READ_SLOW,
5286        first_key: 1,
5287        last_key: 1,
5288        step: 1,
5289        keys: &[RO_ACCESS_AT1],
5290        acl: AC_ARRAY_READ_SLOW,
5291        since: "8.8.0",
5292        complexity: "O(P * C) with P the positions visited and C the cost of the predicates on one element",
5293        summary: "The indexes in a range whose elements answer a set of textual predicates.",
5294        group: "array",
5295    },
5296    Spec {
5297        name: "arop",
5298        arity: -5,
5299        flags: READ_SLOW,
5300        first_key: 1,
5301        last_key: 1,
5302        step: 1,
5303        keys: &[RO_ACCESS_AT1],
5304        acl: AC_ARRAY_READ_SLOW,
5305        since: "8.8.0",
5306        complexity: "O(N) with N the elements found, not the span asked for",
5307        summary: "One number out of a range, added up or compared or counted.",
5308        group: "array",
5309    },
5310    Spec {
5311        name: "arinfo",
5312        arity: -2,
5313        flags: READ_SLOW,
5314        first_key: 1,
5315        last_key: 1,
5316        step: 1,
5317        keys: &[RO_ACCESS_AT1],
5318        acl: AC_ARRAY_READ_SLOW,
5319        since: "8.8.0",
5320        complexity: "O(1), or O(N) with N the slices when FULL is given",
5321        summary: "The shape of the array, and what its slices look like.",
5322        group: "array",
5323    },
5324    // ------------------------------------------------------------- streams
5325    Spec {
5326        name: "xadd",
5327        arity: -5,
5328        flags: WRITE_FAST_OOM,
5329        first_key: 1,
5330        last_key: 1,
5331        step: 1,
5332        keys: &[RW_UPDATE_AT1_TRIMMING],
5333        acl: AC_STREAM_WRITE_FAST,
5334        since: "5.0.0",
5335        complexity: "O(1) for the append, plus what a trim removes.",
5336        summary: "Append an entry and answer with the ID it got.",
5337        group: "stream",
5338    },
5339    Spec {
5340        name: "xlen",
5341        arity: 2,
5342        flags: READ_FAST,
5343        first_key: 1,
5344        last_key: 1,
5345        step: 1,
5346        keys: &[RO_AT1],
5347        acl: AC_STREAM_READ_FAST,
5348        since: "5.0.0",
5349        complexity: "O(1)",
5350        summary: "How many entries the stream holds.",
5351        group: "stream",
5352    },
5353    Spec {
5354        name: "xdel",
5355        arity: -3,
5356        flags: WRITE_FAST,
5357        first_key: 1,
5358        last_key: 1,
5359        step: 1,
5360        keys: &[RW_DELETE_AT1],
5361        acl: AC_STREAM_WRITE_FAST,
5362        since: "5.0.0",
5363        complexity: "O(1) per ID.",
5364        summary: "Remove entries by ID and say how many were there.",
5365        group: "stream",
5366    },
5367    Spec {
5368        name: "xdelex",
5369        arity: -5,
5370        flags: WRITE_FAST,
5371        first_key: 1,
5372        last_key: 1,
5373        step: 1,
5374        keys: &[RW_DELETE_AT1],
5375        acl: AC_STREAM_WRITE_FAST,
5376        since: "8.2.0",
5377        complexity: "O(1) per ID.",
5378        summary: "Remove entries by ID, saying what to do about the groups.",
5379        group: "stream",
5380    },
5381    Spec {
5382        name: "xackdel",
5383        arity: -6,
5384        flags: WRITE_FAST,
5385        first_key: 1,
5386        last_key: 1,
5387        step: 1,
5388        keys: &[RW_UPDATE_DELETE_AT1],
5389        acl: AC_STREAM_WRITE_FAST,
5390        since: "8.2.0",
5391        complexity: "O(1) per ID.",
5392        summary: "Acknowledge entries for a group and remove them.",
5393        group: "stream",
5394    },
5395    Spec {
5396        name: "xnack",
5397        arity: -7,
5398        flags: WRITE_FAST,
5399        first_key: 1,
5400        last_key: 1,
5401        step: 1,
5402        keys: &[RW_UPDATE_AT1],
5403        acl: AC_STREAM_WRITE_FAST,
5404        since: "8.8.0",
5405        complexity: "O(1) per ID.",
5406        summary: "Give entries back to the group for somebody else to claim.",
5407        group: "stream",
5408    },
5409    Spec {
5410        name: "xtrim",
5411        arity: -4,
5412        flags: WRITE_SLOW,
5413        first_key: 1,
5414        last_key: 1,
5415        step: 1,
5416        keys: &[RW_DELETE_AT1],
5417        acl: AC_STREAM_WRITE_SLOW,
5418        since: "5.0.0",
5419        complexity: "O(N) in the entries removed.",
5420        summary: "Cut the stream down to a length or a minimum ID.",
5421        group: "stream",
5422    },
5423    Spec {
5424        name: "xrange",
5425        arity: -4,
5426        flags: READ_SLOW,
5427        first_key: 1,
5428        last_key: 1,
5429        step: 1,
5430        keys: &[RO_ACCESS_AT1],
5431        acl: AC_STREAM_READ_SLOW,
5432        since: "5.0.0",
5433        complexity: "O(N) in the entries returned.",
5434        summary: "The entries between two IDs, oldest first.",
5435        group: "stream",
5436    },
5437    Spec {
5438        name: "xrevrange",
5439        arity: -4,
5440        flags: READ_SLOW,
5441        first_key: 1,
5442        last_key: 1,
5443        step: 1,
5444        keys: &[RO_ACCESS_AT1],
5445        acl: AC_STREAM_READ_SLOW,
5446        since: "5.0.0",
5447        complexity: "O(N) in the entries returned.",
5448        summary: "The entries between two IDs, newest first.",
5449        group: "stream",
5450    },
5451    Spec {
5452        name: "xread",
5453        arity: -4,
5454        flags: READ_BLOCKING_MOVABLE,
5455        first_key: 0,
5456        last_key: 0,
5457        step: 0,
5458        keys: &[XREAD_STREAMS],
5459        acl: AC_STREAM_BLOCKING_READ,
5460        since: "5.0.0",
5461        complexity: "O(N) in the entries returned.",
5462        summary: "Read from one or more streams, waiting if asked to.",
5463        group: "stream",
5464    },
5465    Spec {
5466        name: "xreadgroup",
5467        arity: -7,
5468        flags: WRITE_BLOCKING_MOVABLE,
5469        first_key: 0,
5470        last_key: 0,
5471        step: 0,
5472        keys: &[XREADGROUP_STREAMS],
5473        acl: AC_STREAM_BLOCKING_WRITE,
5474        since: "5.0.0",
5475        complexity: "O(N) in the entries returned.",
5476        summary: "Read as part of a consumer group, waiting if asked to.",
5477        group: "stream",
5478    },
5479    Spec {
5480        name: "xack",
5481        arity: -4,
5482        flags: WRITE_FAST,
5483        first_key: 1,
5484        last_key: 1,
5485        step: 1,
5486        keys: &[RW_UPDATE_AT1],
5487        acl: AC_STREAM_WRITE_FAST,
5488        since: "5.0.0",
5489        complexity: "O(1) per ID.",
5490        summary: "Drop entries from a group's pending list.",
5491        group: "stream",
5492    },
5493    Spec {
5494        name: "xsetid",
5495        arity: -3,
5496        flags: WRITE_FAST_OOM,
5497        first_key: 1,
5498        last_key: 1,
5499        step: 1,
5500        keys: &[RW_UPDATE_AT1],
5501        acl: AC_STREAM_WRITE_FAST,
5502        since: "5.0.0",
5503        complexity: "O(1)",
5504        summary: "Set the last ID, the entries added and the max deleted ID.",
5505        group: "stream",
5506    },
5507    Spec {
5508        name: "xgroup",
5509        arity: -2,
5510        flags: &[],
5511        first_key: 0,
5512        last_key: 0,
5513        step: 0,
5514        keys: &[],
5515        acl: AC_STREAM_CONTAINER,
5516        since: "5.0.0",
5517        complexity: "O(1) for all subcommands except DESTROY, which frees the group's pending list.",
5518        summary: "Make, move and unmake consumer groups.",
5519        group: "stream",
5520    },
5521    Spec {
5522        name: "xinfo",
5523        arity: -2,
5524        flags: &[],
5525        first_key: 0,
5526        last_key: 0,
5527        step: 0,
5528        keys: &[],
5529        acl: AC_STREAM_CONTAINER,
5530        since: "5.0.0",
5531        complexity: "O(1), or O(N) with N the entries and pending entries shown when FULL is given.",
5532        summary: "What a stream, its groups and its consumers look like.",
5533        group: "stream",
5534    },
5535    Spec {
5536        name: "xpending",
5537        arity: -3,
5538        flags: READ_SLOW,
5539        first_key: 1,
5540        last_key: 1,
5541        step: 1,
5542        keys: &[RO_ACCESS_AT1],
5543        acl: AC_STREAM_READ_SLOW,
5544        since: "5.0.0",
5545        complexity: "O(1) for the summary, O(N) in the entries returned for the list.",
5546        summary: "What a group has handed out and not had acknowledged.",
5547        group: "stream",
5548    },
5549    Spec {
5550        name: "xclaim",
5551        arity: -6,
5552        flags: WRITE_FAST,
5553        first_key: 1,
5554        last_key: 1,
5555        step: 1,
5556        keys: &[RW_UPDATE_AT1],
5557        acl: AC_STREAM_WRITE_FAST,
5558        since: "5.0.0",
5559        complexity: "O(1) per ID.",
5560        summary: "Move named pending entries to another consumer.",
5561        group: "stream",
5562    },
5563    Spec {
5564        name: "xautoclaim",
5565        arity: -6,
5566        flags: WRITE_FAST,
5567        first_key: 1,
5568        last_key: 1,
5569        step: 1,
5570        keys: &[RW_DELETE_AT1],
5571        acl: AC_STREAM_WRITE_FAST,
5572        since: "6.2.0",
5573        complexity: "O(1) per entry claimed, plus what it skips getting there.",
5574        summary: "Sweep a group's pending list and take what has gone idle.",
5575        group: "stream",
5576    },
5577    // ------------------------------------------------------------ keyspace
5578    Spec {
5579        name: "del",
5580        arity: -2,
5581        flags: &["write"],
5582        first_key: 1,
5583        last_key: -1,
5584        step: 1,
5585        keys: &[RM_DELETE_AT1_RM1_1_0],
5586        acl: AC_KEY_WRITE_SLOW,
5587        since: "1.0.0",
5588        complexity: "O(N) in the number of keys.",
5589        summary: "Delete keys and say how many were there.",
5590        group: "keyspace",
5591    },
5592    Spec {
5593        name: "unlink",
5594        arity: -2,
5595        flags: &["write", "fast"],
5596        first_key: 1,
5597        last_key: -1,
5598        step: 1,
5599        keys: &[RM_DELETE_AT1_RM1_1_0],
5600        acl: AC_KEY_WRITE_FAST,
5601        since: "4.0.0",
5602        complexity: "O(1) per key, since the freeing is not on this thread.",
5603        summary: "Delete keys and free them out of the way of the reply.",
5604        group: "keyspace",
5605    },
5606    Spec {
5607        name: "exists",
5608        arity: -2,
5609        flags: READ_FAST,
5610        first_key: 1,
5611        last_key: -1,
5612        step: 1,
5613        keys: &[RO_AT1_RM1_1_0],
5614        acl: AC_KEY_READ,
5615        since: "1.0.0",
5616        complexity: "O(N) in the number of keys.",
5617        summary: "Count how many of these keys are there, naming one twice counting twice.",
5618        group: "keyspace",
5619    },
5620    Spec {
5621        name: "type",
5622        arity: 2,
5623        flags: READ_FAST,
5624        first_key: 1,
5625        last_key: 1,
5626        step: 1,
5627        keys: &[RO_AT1],
5628        acl: AC_KEY_READ,
5629        since: "1.0.0",
5630        complexity: "O(1)",
5631        summary: "What kind of value is under a key, or none.",
5632        group: "keyspace",
5633    },
5634    Spec {
5635        name: "touch",
5636        arity: -2,
5637        flags: READ_FAST,
5638        first_key: 1,
5639        last_key: -1,
5640        step: 1,
5641        keys: &[RO_AT1_RM1_1_0],
5642        acl: AC_KEY_READ,
5643        since: "3.2.1",
5644        complexity: "O(N) in the number of keys.",
5645        summary: "Count how many of these keys are there, and move them up the eviction order.",
5646        group: "keyspace",
5647    },
5648    // The three that look at keys nobody named. No key positions on any of
5649    // them, which is what the zeroes say, and it is also why a cluster client
5650    // sends them to a node rather than to a slot.
5651    Spec {
5652        name: "scan",
5653        arity: -2,
5654        flags: &["readonly"],
5655        first_key: 0,
5656        last_key: 0,
5657        step: 0,
5658        keys: &[],
5659        acl: AC_KEY_READ_SLOW,
5660        since: "2.8.0",
5661        complexity: "O(1) a call, O(N) for a whole iteration",
5662        summary: "Walk part of the keyspace and say where to carry on from.",
5663        group: "keyspace",
5664    },
5665    Spec {
5666        name: "keys",
5667        arity: 2,
5668        flags: &["readonly"],
5669        first_key: 0,
5670        last_key: 0,
5671        step: 0,
5672        keys: &[],
5673        acl: AC_KEY_READ_ALL,
5674        since: "1.0.0",
5675        complexity: "O(N) in the number of keys.",
5676        summary: "Every key matching a pattern, in one reply.",
5677        group: "keyspace",
5678    },
5679    Spec {
5680        name: "randomkey",
5681        arity: 1,
5682        flags: &["readonly"],
5683        first_key: 0,
5684        last_key: 0,
5685        step: 0,
5686        keys: &[],
5687        acl: AC_KEY_READ_SLOW,
5688        since: "1.0.0",
5689        complexity: "O(1)",
5690        summary: "One key from the database, chosen at random.",
5691        group: "keyspace",
5692    },
5693    // Two keys and not one, which is the 1 2 1 in the key positions. Every other
5694    // row in this group names a range that runs to the end of the arguments.
5695    Spec {
5696        name: "rename",
5697        arity: 3,
5698        flags: &["write"],
5699        first_key: 1,
5700        last_key: 2,
5701        step: 1,
5702        keys: &[RW_ACCESS_DELETE_AT1, OW_UPDATE_AT2],
5703        acl: AC_KEY_WRITE_SLOW,
5704        since: "1.0.0",
5705        complexity: "O(1)",
5706        summary: "Move a key to another name, over whatever was there.",
5707        group: "keyspace",
5708    },
5709    Spec {
5710        name: "renamenx",
5711        arity: 3,
5712        flags: WRITE_FAST,
5713        first_key: 1,
5714        last_key: 2,
5715        step: 1,
5716        keys: &[RW_ACCESS_DELETE_AT1, OW_INSERT_AT2],
5717        acl: AC_KEY_WRITE_FAST,
5718        since: "1.0.0",
5719        complexity: "O(1)",
5720        summary: "Move a key to another name, but only if that name is free.",
5721        group: "keyspace",
5722    },
5723    // `denyoom` and no `fast`, because this is the one command in the group that
5724    // allocates a whole second value.
5725    Spec {
5726        name: "copy",
5727        arity: -3,
5728        flags: &["write", "denyoom"],
5729        first_key: 1,
5730        last_key: 2,
5731        step: 1,
5732        keys: &[RO_ACCESS_AT1, OW_UPDATE_AT2],
5733        acl: AC_KEY_WRITE_SLOW,
5734        since: "6.2.0",
5735        complexity: "O(N) in the size of the value.",
5736        summary: "Copy a value to another key, in this database or another one.",
5737        group: "keyspace",
5738    },
5739    // `COPY` with the source deleted, and the only command in the group whose
5740    // second argument is a database rather than a key. The key spec is one key
5741    // at argument one and the database index is not a key, which is why this
5742    // does not look like `COPY` above it.
5743    Spec {
5744        name: "move",
5745        arity: 3,
5746        flags: WRITE_FAST,
5747        first_key: 1,
5748        last_key: 1,
5749        step: 1,
5750        keys: &[RW_ACCESS_UPDATE_AT1],
5751        acl: AC_KEY_WRITE_FAST,
5752        since: "1.0.0",
5753        complexity: "O(1)",
5754        summary: "Move a key to another database, if it is not already there.",
5755        group: "keyspace",
5756    },
5757    // The two that block on replication rather than on a key, so they name no
5758    // key at all and the three zeroes below are not a placeholder.
5759    Spec {
5760        name: "wait",
5761        arity: 3,
5762        flags: &["blocking"],
5763        first_key: 0,
5764        last_key: 0,
5765        step: 0,
5766        keys: &[],
5767        acl: AC_WAIT,
5768        since: "3.0.0",
5769        complexity: "O(1)",
5770        summary: "Wait for this connection's writes to reach a number of replicas.",
5771        group: "keyspace",
5772    },
5773    Spec {
5774        name: "waitaof",
5775        arity: 4,
5776        flags: &["blocking"],
5777        first_key: 0,
5778        last_key: 0,
5779        step: 0,
5780        keys: &[],
5781        acl: AC_WAIT,
5782        since: "7.2.0",
5783        complexity: "O(1)",
5784        summary: "Wait for this connection's writes to reach the append only files.",
5785        group: "keyspace",
5786    },
5787    // The two that speak the file format. A payload is a value standing on its
5788    // own outside the process, so these are the only two commands in the group
5789    // that move a value rather than a name.
5790    Spec {
5791        name: "dump",
5792        arity: 2,
5793        flags: READ_SLOW,
5794        first_key: 1,
5795        last_key: 1,
5796        step: 1,
5797        keys: &[RO_ACCESS_AT1],
5798        acl: AC_KEY_READ_SLOW,
5799        since: "2.6.0",
5800        complexity: "O(1) to find the key, then O(N) in the size of the value.",
5801        summary: "Serialize a value into a payload another server can load.",
5802        group: "keyspace",
5803    },
5804    Spec {
5805        name: "restore",
5806        arity: -4,
5807        flags: &["write", "denyoom"],
5808        first_key: 1,
5809        last_key: 1,
5810        step: 1,
5811        keys: &[OW_UPDATE_AT1],
5812        acl: AC_RESTORE,
5813        since: "2.6.0",
5814        complexity: "O(1) to find the key, then O(N) in the size of the payload.",
5815        summary: "Create a key from a payload produced by DUMP.",
5816        group: "keyspace",
5817    },
5818    // The same command with an `ASKING` built into it, which is what `MIGRATE`
5819    // sends when the sending server is a cluster node. Without it every key of a
5820    // slot migration would come back as a `MOVED` pointing at the node doing the
5821    // sending, since the node being sent the keys does not own the slot yet. The
5822    // group says server rather than keyspace because that is where the reference
5823    // files it, and the body it runs is named for it in `dispatch::run`.
5824    Spec {
5825        name: "restore-asking",
5826        arity: -4,
5827        flags: &["write", "denyoom", "asking"],
5828        first_key: 1,
5829        last_key: 1,
5830        step: 1,
5831        keys: &[OW_UPDATE_AT1],
5832        acl: AC_RESTORE,
5833        since: "3.0.0",
5834        complexity: "O(1) to find the key, then O(N) in the size of the payload.",
5835        summary: "An internal command for migrating keys in a cluster.",
5836        group: "server",
5837    },
5838    // And the third one, which is the other two with a socket in between. Its
5839    // keys are movable for the same reason `SORT`'s are, though for a plainer
5840    // reason: the `KEYS` option moves them from argument three to everything
5841    // after the word, so where they are depends on what was written.
5842    Spec {
5843        name: "migrate",
5844        arity: -6,
5845        flags: MIGRATE_FLAGS,
5846        first_key: 3,
5847        last_key: 3,
5848        step: 1,
5849        keys: &[RW_ACCESS_DELETE_AT3, MIGRATE_KEYS],
5850        acl: AC_RESTORE,
5851        since: "2.6.0",
5852        complexity: "A DUMP and a DEL here, a RESTORE there, and the bytes in between.",
5853        summary: "Move a key to another server.",
5854        group: "keyspace",
5855    },
5856    // The two whose keys cannot be read off the command. `SORT k BY w_* GET d_*`
5857    // touches every key those two patterns name and a client cannot know which
5858    // ones without the data, so both carry `movablekeys` and Redis's own key
5859    // specs give the same answer: the first key, and the STORE destination if
5860    // there is one.
5861    Spec {
5862        name: "sort",
5863        arity: -2,
5864        flags: WRITE_MOVABLE,
5865        first_key: 1,
5866        last_key: 1,
5867        step: 1,
5868        keys: &[RO_ACCESS_AT1, SORT_BY_AND_GET, SORT_STORE],
5869        acl: AC_SORT_WRITE,
5870        since: "1.0.0",
5871        complexity: "O(N+M*log(M)) with N elements and M returned.",
5872        summary: "Sort a list, set or sorted set, optionally into another key.",
5873        group: "keyspace",
5874    },
5875    Spec {
5876        name: "sort_ro",
5877        arity: -2,
5878        flags: READ_MOVABLE,
5879        first_key: 1,
5880        last_key: 1,
5881        step: 1,
5882        keys: &[RO_ACCESS_AT1, SORT_BY_AND_GET],
5883        acl: AC_SORT_READ,
5884        since: "7.0.0",
5885        complexity: "O(N+M*log(M)) with N elements and M returned.",
5886        summary: "Sort a list, set or sorted set, without the STORE option.",
5887        group: "keyspace",
5888    },
5889    // The four writers take an optional NX, XX, GT or LT, which is the -3 in
5890    // the arity, and they take the same one whichever unit they are in.
5891    Spec {
5892        name: "expire",
5893        arity: -3,
5894        flags: WRITE_FAST,
5895        first_key: 1,
5896        last_key: 1,
5897        step: 1,
5898        keys: &[RW_UPDATE_AT1],
5899        acl: AC_KEY_WRITE_FAST,
5900        since: "1.0.0",
5901        complexity: "O(1)",
5902        summary: "Put a deadline on a key, counted in seconds from now.",
5903        group: "keyspace",
5904    },
5905    Spec {
5906        name: "pexpire",
5907        arity: -3,
5908        flags: WRITE_FAST,
5909        first_key: 1,
5910        last_key: 1,
5911        step: 1,
5912        keys: &[RW_UPDATE_AT1],
5913        acl: AC_KEY_WRITE_FAST,
5914        since: "2.6.0",
5915        complexity: "O(1)",
5916        summary: "Put a deadline on a key, counted in milliseconds from now.",
5917        group: "keyspace",
5918    },
5919    Spec {
5920        name: "expireat",
5921        arity: -3,
5922        flags: WRITE_FAST,
5923        first_key: 1,
5924        last_key: 1,
5925        step: 1,
5926        keys: &[RW_UPDATE_AT1],
5927        acl: AC_KEY_WRITE_FAST,
5928        since: "1.2.0",
5929        complexity: "O(1)",
5930        summary: "Put a deadline on a key, as a unix time in seconds.",
5931        group: "keyspace",
5932    },
5933    Spec {
5934        name: "pexpireat",
5935        arity: -3,
5936        flags: WRITE_FAST,
5937        first_key: 1,
5938        last_key: 1,
5939        step: 1,
5940        keys: &[RW_UPDATE_AT1],
5941        acl: AC_KEY_WRITE_FAST,
5942        since: "2.6.0",
5943        complexity: "O(1)",
5944        summary: "Put a deadline on a key, as a unix time in milliseconds.",
5945        group: "keyspace",
5946    },
5947    Spec {
5948        name: "persist",
5949        arity: 2,
5950        flags: WRITE_FAST,
5951        first_key: 1,
5952        last_key: 1,
5953        step: 1,
5954        keys: &[RW_UPDATE_AT1],
5955        acl: AC_KEY_WRITE_FAST,
5956        since: "2.2.0",
5957        complexity: "O(1)",
5958        summary: "Take a key's deadline off, so it stops being temporary.",
5959        group: "keyspace",
5960    },
5961    Spec {
5962        name: "ttl",
5963        arity: 2,
5964        flags: READ_FAST,
5965        first_key: 1,
5966        last_key: 1,
5967        step: 1,
5968        keys: &[RO_ACCESS_AT1],
5969        acl: AC_KEY_READ,
5970        since: "1.0.0",
5971        complexity: "O(1)",
5972        summary: "How many seconds a key has left, -1 with no deadline, -2 if gone.",
5973        group: "keyspace",
5974    },
5975    Spec {
5976        name: "pttl",
5977        arity: 2,
5978        flags: READ_FAST,
5979        first_key: 1,
5980        last_key: 1,
5981        step: 1,
5982        keys: &[RO_ACCESS_AT1],
5983        acl: AC_KEY_READ,
5984        since: "2.6.0",
5985        complexity: "O(1)",
5986        summary: "How many milliseconds a key has left, -1 with no deadline, -2 if gone.",
5987        group: "keyspace",
5988    },
5989    Spec {
5990        name: "expiretime",
5991        arity: 2,
5992        flags: READ_FAST,
5993        first_key: 1,
5994        last_key: 1,
5995        step: 1,
5996        keys: &[RO_ACCESS_AT1],
5997        acl: AC_KEY_READ,
5998        since: "7.0.0",
5999        complexity: "O(1)",
6000        summary: "When a key falls due, as a unix time in seconds.",
6001        group: "keyspace",
6002    },
6003    Spec {
6004        name: "pexpiretime",
6005        arity: 2,
6006        flags: READ_FAST,
6007        first_key: 1,
6008        last_key: 1,
6009        step: 1,
6010        keys: &[RO_ACCESS_AT1],
6011        acl: AC_KEY_READ,
6012        since: "7.0.0",
6013        complexity: "O(1)",
6014        summary: "When a key falls due, as a unix time in milliseconds.",
6015        group: "keyspace",
6016    },
6017    // A container command, so no keys and no flags of its own: the key is the
6018    // subcommand's and a real server reports it on `object|encoding` rather
6019    // than here. `@slow` is the whole ACL, checked against 8.10.1.
6020    Spec {
6021        name: "object",
6022        arity: -2,
6023        flags: &[],
6024        first_key: 0,
6025        last_key: 0,
6026        step: 0,
6027        keys: &[],
6028        acl: &["@slow"],
6029        since: "2.2.3",
6030        complexity: "O(1)",
6031        summary: "Look at the machinery under a key rather than at its value.",
6032        group: "keyspace",
6033    },
6034    // ----------------------------------------------------------- scripting
6035    // The four spellings of running a script differ in two things and nothing
6036    // else: whether the client sent the body or its digest, and whether the
6037    // script may write. So the four rows are the same row four times, and the
6038    // `_RO` pair says `readonly` first because that is the order a real 8.10.1
6039    // lists them in and `COMMAND INFO` is compared byte for byte.
6040    //
6041    // `movablekeys` is on all four because the keys are not at a fixed offset:
6042    // the client says how many there are. `no_mandatory_keys` is on all four
6043    // because it may say none. `script_runner` is what tells a client that the
6044    // command's real cost is whatever the script does.
6045    Spec {
6046        name: "eval",
6047        arity: -3,
6048        flags: &[
6049            "noscript",
6050            "stale",
6051            "skip_monitor",
6052            "no_mandatory_keys",
6053            "movablekeys",
6054            "script_runner",
6055        ],
6056        first_key: 0,
6057        last_key: 0,
6058        step: 0,
6059        keys: &[SCRIPT_KEYS_RW],
6060        acl: &["@slow", "@scripting"],
6061        since: "2.6.0",
6062        complexity: "Whatever the script does.",
6063        summary: "Run a Lua script sent with the command.",
6064        group: "scripting",
6065    },
6066    Spec {
6067        name: "evalsha",
6068        arity: -3,
6069        flags: &[
6070            "noscript",
6071            "stale",
6072            "skip_monitor",
6073            "no_mandatory_keys",
6074            "movablekeys",
6075            "script_runner",
6076        ],
6077        first_key: 0,
6078        last_key: 0,
6079        step: 0,
6080        keys: &[RW_ACCESS_UPDATE_AT2_COUNTED],
6081        acl: &["@slow", "@scripting"],
6082        since: "2.6.0",
6083        complexity: "Whatever the script does.",
6084        summary: "Run a Lua script the cache already holds.",
6085        group: "scripting",
6086    },
6087    Spec {
6088        name: "eval_ro",
6089        arity: -3,
6090        flags: &[
6091            "readonly",
6092            "noscript",
6093            "stale",
6094            "skip_monitor",
6095            "no_mandatory_keys",
6096            "movablekeys",
6097            "script_runner",
6098        ],
6099        first_key: 0,
6100        last_key: 0,
6101        step: 0,
6102        keys: &[SCRIPT_KEYS_RO],
6103        acl: &["@slow", "@scripting"],
6104        since: "7.0.0",
6105        complexity: "Whatever the script does.",
6106        summary: "Run a Lua script that is not allowed to write.",
6107        group: "scripting",
6108    },
6109    Spec {
6110        name: "evalsha_ro",
6111        arity: -3,
6112        flags: &[
6113            "readonly",
6114            "noscript",
6115            "stale",
6116            "skip_monitor",
6117            "no_mandatory_keys",
6118            "movablekeys",
6119            "script_runner",
6120        ],
6121        first_key: 0,
6122        last_key: 0,
6123        step: 0,
6124        keys: &[RO_ACCESS_AT2_COUNTED],
6125        acl: &["@slow", "@scripting"],
6126        since: "7.0.0",
6127        complexity: "Whatever the script does.",
6128        summary: "Run a cached Lua script that is not allowed to write.",
6129        group: "scripting",
6130    },
6131    Spec {
6132        name: "fcall",
6133        arity: -3,
6134        flags: &[
6135            "noscript",
6136            "stale",
6137            "skip_monitor",
6138            "no_mandatory_keys",
6139            "movablekeys",
6140            "script_runner",
6141        ],
6142        first_key: 0,
6143        last_key: 0,
6144        step: 0,
6145        keys: &[SCRIPT_KEYS_RW],
6146        acl: &["@slow", "@scripting"],
6147        since: "7.0.0",
6148        complexity: "Whatever the function does.",
6149        summary: "Run a function out of a loaded library.",
6150        group: "scripting",
6151    },
6152    Spec {
6153        name: "fcall_ro",
6154        arity: -3,
6155        flags: &[
6156            "readonly",
6157            "noscript",
6158            "stale",
6159            "skip_monitor",
6160            "no_mandatory_keys",
6161            "movablekeys",
6162            "script_runner",
6163        ],
6164        first_key: 0,
6165        last_key: 0,
6166        step: 0,
6167        keys: &[SCRIPT_KEYS_RO],
6168        acl: &["@slow", "@scripting"],
6169        since: "7.0.0",
6170        complexity: "Whatever the function does.",
6171        summary: "Run a function that was registered no-writes.",
6172        group: "scripting",
6173    },
6174    // Both containers have no flags and no keys of their own, which is what a
6175    // real 8.10.1 reports: the flags live on the subcommands.
6176    Spec {
6177        name: "script",
6178        arity: -2,
6179        flags: &[],
6180        first_key: 0,
6181        last_key: 0,
6182        step: 0,
6183        keys: &[],
6184        acl: &["@slow"],
6185        since: "2.6.0",
6186        complexity: "O(1) for the subcommands that are here.",
6187        summary: "The cache EVALSHA runs scripts out of.",
6188        group: "scripting",
6189    },
6190    Spec {
6191        name: "function",
6192        arity: -2,
6193        flags: &[],
6194        first_key: 0,
6195        last_key: 0,
6196        step: 0,
6197        keys: &[],
6198        acl: &["@slow"],
6199        since: "7.0.0",
6200        complexity: "O(1) for the subcommands that are here.",
6201        summary: "The libraries FCALL runs functions out of.",
6202        group: "scripting",
6203    },
6204    // ---------------------------------------------------------- connection
6205    Spec {
6206        name: "ping",
6207        arity: -1,
6208        flags: &["fast"],
6209        first_key: 0,
6210        last_key: 0,
6211        step: 0,
6212        keys: &[],
6213        acl: AC_CONN,
6214        since: "1.0.0",
6215        complexity: "O(1)",
6216        summary: "Ask whether the server is answering.",
6217        group: "connection",
6218    },
6219    Spec {
6220        name: "echo",
6221        arity: 2,
6222        flags: &["loading", "stale", "fast"],
6223        first_key: 0,
6224        last_key: 0,
6225        step: 0,
6226        keys: &[],
6227        acl: AC_CONN,
6228        since: "1.0.0",
6229        complexity: "O(1)",
6230        summary: "Send a string back unchanged.",
6231        group: "connection",
6232    },
6233    // `no_auth` is the flag that lets a command through before the connection
6234    // has a password accepted, and four commands carry it: this one, `HELLO`,
6235    // which carries the option that authenticates, `RESET`, which is how a
6236    // client says it is starting over, and `QUIT`. Everything else on the server
6237    // is refused until one of them has done its work.
6238    Spec {
6239        name: "auth",
6240        arity: -2,
6241        flags: &[
6242            "noscript",
6243            "loading",
6244            "stale",
6245            "fast",
6246            "no_auth",
6247            "allow_busy",
6248        ],
6249        first_key: 0,
6250        last_key: 0,
6251        step: 0,
6252        keys: &[],
6253        acl: AC_CONN,
6254        since: "1.0.0",
6255        complexity: "O(1)",
6256        summary: "Hand over the password this connection is asked for.",
6257        group: "connection",
6258    },
6259    Spec {
6260        name: "hello",
6261        arity: -1,
6262        flags: &[
6263            "noscript",
6264            "loading",
6265            "stale",
6266            "fast",
6267            "no_auth",
6268            "allow_busy",
6269        ],
6270        first_key: 0,
6271        last_key: 0,
6272        step: 0,
6273        keys: &[],
6274        acl: AC_CONN,
6275        since: "6.0.0",
6276        complexity: "O(1)",
6277        summary: "Agree on a protocol version and describe the server.",
6278        group: "connection",
6279    },
6280    Spec {
6281        name: "select",
6282        arity: 2,
6283        flags: &["loading", "stale", "fast"],
6284        first_key: 0,
6285        last_key: 0,
6286        step: 0,
6287        keys: &[],
6288        acl: AC_CONN,
6289        since: "1.0.0",
6290        complexity: "O(1)",
6291        summary: "Choose which database this connection works in.",
6292        group: "connection",
6293    },
6294    Spec {
6295        name: "reset",
6296        arity: 1,
6297        flags: &[
6298            "noscript",
6299            "loading",
6300            "stale",
6301            "fast",
6302            "no_auth",
6303            "allow_busy",
6304        ],
6305        first_key: 0,
6306        last_key: 0,
6307        step: 0,
6308        keys: &[],
6309        acl: AC_CONN,
6310        since: "6.2.0",
6311        complexity: "O(1)",
6312        summary: "Put the connection back the way it was opened.",
6313        group: "connection",
6314    },
6315    Spec {
6316        name: "quit",
6317        arity: -1,
6318        flags: &[
6319            "noscript",
6320            "loading",
6321            "stale",
6322            "fast",
6323            "no_auth",
6324            "allow_busy",
6325        ],
6326        first_key: 0,
6327        last_key: 0,
6328        step: 0,
6329        keys: &[],
6330        acl: AC_CONN,
6331        since: "1.0.0",
6332        complexity: "O(1)",
6333        summary: "Close the connection after the replies already queued.",
6334        group: "connection",
6335    },
6336    // -------------------------------------------------------- transactions
6337    // None of the five is a write and none of them names a key the table can
6338    // describe, `WATCH` included: a watched key is read, and the key spec Redis
6339    // publishes for it says `RO`. What makes them their own group is that they
6340    // are the only commands the funnel looks at before it decides whether to run
6341    // anything at all.
6342    Spec {
6343        name: "multi",
6344        arity: 1,
6345        flags: &["noscript", "loading", "stale", "fast", "allow_busy"],
6346        first_key: 0,
6347        last_key: 0,
6348        step: 0,
6349        keys: &[],
6350        acl: AC_TX_FAST,
6351        since: "2.0.0",
6352        complexity: "O(1)",
6353        summary: "Start holding commands instead of running them.",
6354        group: "transactions",
6355    },
6356    Spec {
6357        name: "exec",
6358        arity: 1,
6359        flags: &["noscript", "loading", "stale", "skip_slowlog"],
6360        first_key: 0,
6361        last_key: 0,
6362        step: 0,
6363        keys: &[],
6364        acl: &["@slow", "@transaction"],
6365        since: "1.2.0",
6366        complexity: "Whatever the queued commands cost.",
6367        summary: "Run everything held since MULTI.",
6368        group: "transactions",
6369    },
6370    Spec {
6371        name: "discard",
6372        arity: 1,
6373        flags: &["noscript", "loading", "stale", "fast", "allow_busy"],
6374        first_key: 0,
6375        last_key: 0,
6376        step: 0,
6377        keys: &[],
6378        acl: AC_TX_FAST,
6379        since: "2.0.0",
6380        complexity: "O(N) in the number of commands held.",
6381        summary: "Throw away everything held since MULTI.",
6382        group: "transactions",
6383    },
6384    Spec {
6385        name: "watch",
6386        arity: -2,
6387        flags: &["noscript", "loading", "stale", "fast", "allow_busy"],
6388        first_key: 1,
6389        last_key: -1,
6390        step: 1,
6391        keys: &[RO_AT1_RM1_1_0],
6392        acl: AC_TX_FAST,
6393        since: "2.2.0",
6394        complexity: "O(1) a key.",
6395        summary: "Fail the next EXEC if any of these keys changes.",
6396        group: "transactions",
6397    },
6398    Spec {
6399        name: "unwatch",
6400        arity: 1,
6401        flags: &["noscript", "loading", "stale", "fast", "allow_busy"],
6402        first_key: 0,
6403        last_key: 0,
6404        step: 0,
6405        keys: &[],
6406        acl: AC_TX_FAST,
6407        since: "2.2.0",
6408        complexity: "O(N) in the number of keys watched.",
6409        summary: "Stop watching everything this connection was watching.",
6410        group: "transactions",
6411    },
6412    // -------------------------------------------------------------- pubsub
6413    // The three shard commands carry a key at argument one and are not about a
6414    // key at all. Redis marks that spec `not_key`, which is its way of saying
6415    // that the argument is there to be hashed to a cluster slot and nothing
6416    // else, so that a shard channel and the keys it belongs with land on the
6417    // same node. The triple is what `COMMAND GETKEYS` reads, so it is kept.
6418    Spec {
6419        name: "subscribe",
6420        arity: -2,
6421        flags: &["denyoom", "pubsub", "noscript", "loading", "stale"],
6422        first_key: 0,
6423        last_key: 0,
6424        step: 0,
6425        keys: &[],
6426        acl: AC_PUBSUB_SLOW,
6427        since: "2.0.0",
6428        complexity: "O(N) in the number of channels named.",
6429        summary: "Listen on these channels.",
6430        group: "pubsub",
6431    },
6432    Spec {
6433        name: "unsubscribe",
6434        arity: -1,
6435        flags: &["pubsub", "noscript", "loading", "stale"],
6436        first_key: 0,
6437        last_key: 0,
6438        step: 0,
6439        keys: &[],
6440        acl: AC_PUBSUB_SLOW,
6441        since: "2.0.0",
6442        complexity: "O(N) in the number of channels named, or held if none are.",
6443        summary: "Stop listening on these channels, or on all of them.",
6444        group: "pubsub",
6445    },
6446    Spec {
6447        name: "psubscribe",
6448        arity: -2,
6449        flags: &["denyoom", "pubsub", "noscript", "loading", "stale"],
6450        first_key: 0,
6451        last_key: 0,
6452        step: 0,
6453        keys: &[],
6454        acl: AC_PUBSUB_SLOW,
6455        since: "2.0.0",
6456        complexity: "O(N) in the number of patterns named.",
6457        summary: "Listen on every channel matching these patterns.",
6458        group: "pubsub",
6459    },
6460    Spec {
6461        name: "punsubscribe",
6462        arity: -1,
6463        flags: &["pubsub", "noscript", "loading", "stale"],
6464        first_key: 0,
6465        last_key: 0,
6466        step: 0,
6467        keys: &[],
6468        acl: AC_PUBSUB_SLOW,
6469        since: "2.0.0",
6470        complexity: "O(N) in the number of patterns named, or held if none are.",
6471        summary: "Stop listening on these patterns, or on all of them.",
6472        group: "pubsub",
6473    },
6474    Spec {
6475        name: "ssubscribe",
6476        arity: -2,
6477        flags: &["denyoom", "pubsub", "noscript", "loading", "stale"],
6478        first_key: 1,
6479        last_key: -1,
6480        step: 1,
6481        keys: &[NOT_KEY_AT1_RM1_1_0],
6482        acl: AC_PUBSUB_SLOW,
6483        since: "7.0.0",
6484        complexity: "O(N) in the number of shard channels named.",
6485        summary: "Listen on these shard channels.",
6486        group: "pubsub",
6487    },
6488    Spec {
6489        name: "sunsubscribe",
6490        arity: -1,
6491        flags: &["pubsub", "noscript", "loading", "stale"],
6492        first_key: 1,
6493        last_key: -1,
6494        step: 1,
6495        keys: &[NOT_KEY_AT1_RM1_1_0],
6496        acl: AC_PUBSUB_SLOW,
6497        since: "7.0.0",
6498        complexity: "O(N) in the number of shard channels named, or held if none are.",
6499        summary: "Stop listening on these shard channels, or on all of them.",
6500        group: "pubsub",
6501    },
6502    Spec {
6503        name: "publish",
6504        arity: 3,
6505        flags: &["pubsub", "loading", "stale", "fast"],
6506        first_key: 0,
6507        last_key: 0,
6508        step: 0,
6509        keys: &[],
6510        acl: AC_PUBSUB_FAST,
6511        since: "2.0.0",
6512        complexity: "O(N+M) with N the subscribers and M the patterns.",
6513        summary: "Send a message to everybody listening on a channel.",
6514        group: "pubsub",
6515    },
6516    Spec {
6517        name: "spublish",
6518        arity: 3,
6519        flags: &["pubsub", "loading", "stale", "fast"],
6520        first_key: 1,
6521        last_key: 1,
6522        step: 1,
6523        keys: &[NOT_KEY_AT1],
6524        acl: AC_PUBSUB_FAST,
6525        since: "7.0.0",
6526        complexity: "O(N) in the shard channel's subscribers.",
6527        summary: "Send a message to everybody listening on a shard channel.",
6528        group: "pubsub",
6529    },
6530    Spec {
6531        name: "pubsub",
6532        arity: -2,
6533        flags: &[],
6534        first_key: 0,
6535        last_key: 0,
6536        step: 0,
6537        keys: &[],
6538        acl: &["@slow"],
6539        since: "2.8.0",
6540        complexity: "O(N) in the number of channels or patterns on the server.",
6541        summary: "What the server's subscriptions look like from outside.",
6542        group: "pubsub",
6543    },
6544    // -------------------------------------------------------------- server
6545    // COMMAND is in the connection ACL category and in the server group, which
6546    // is not a contradiction: the category is about what a connection is
6547    // allowed to do and the group is about what the command is about. The group
6548    // is the one reported by COMMAND DOCS, so it is the one that has to match.
6549    Spec {
6550        name: "command",
6551        arity: -1,
6552        flags: &["loading", "stale"],
6553        first_key: 0,
6554        last_key: 0,
6555        step: 0,
6556        keys: &[],
6557        acl: &["@slow", "@connection"],
6558        since: "2.8.13",
6559        complexity: "O(N) with N the number of commands",
6560        summary: "What this server can do, in the shape client libraries read.",
6561        group: "server",
6562    },
6563    Spec {
6564        name: "client",
6565        arity: -2,
6566        flags: &[],
6567        first_key: 0,
6568        last_key: 0,
6569        step: 0,
6570        keys: &[],
6571        acl: &["@slow"],
6572        since: "2.4.0",
6573        complexity: "Depends on the subcommand.",
6574        summary: "Ask about or change the connection the command arrived on.",
6575        group: "connection",
6576    },
6577    Spec {
6578        name: "acl",
6579        arity: -2,
6580        flags: &[],
6581        first_key: 0,
6582        last_key: 0,
6583        step: 0,
6584        keys: &[],
6585        acl: &["@slow"],
6586        since: "6.0.0",
6587        complexity: "Depends on subcommand.",
6588        summary: "A container for Access List Control commands.",
6589        group: "server",
6590    },
6591    Spec {
6592        name: "config",
6593        arity: -2,
6594        flags: &[],
6595        first_key: 0,
6596        last_key: 0,
6597        step: 0,
6598        keys: &[],
6599        acl: &["@slow"],
6600        since: "2.0.0",
6601        complexity: "Depends on the subcommand.",
6602        summary: "Read and change the settings a running server exposes.",
6603        group: "server",
6604    },
6605    // Exactly two, which is what a real 8.10.1 reports for the container even
6606    // though every one of its subcommands carries its own arity underneath. All
6607    // seven of them take two words, so nothing legal is refused by it, and the
6608    // one thing that reads differently is the name inside the arity error for a
6609    // subcommand with an argument after it. That is D-46.
6610    Spec {
6611        name: "backup",
6612        arity: 2,
6613        flags: &[],
6614        first_key: 0,
6615        last_key: 0,
6616        step: 0,
6617        keys: &[],
6618        acl: &["@slow"],
6619        since: "8.10.0",
6620        complexity: "Depends on subcommand.",
6621        summary: "A container for backup management commands.",
6622        group: "server",
6623    },
6624    Spec {
6625        name: "info",
6626        arity: -1,
6627        flags: &["loading", "stale"],
6628        first_key: 0,
6629        last_key: 0,
6630        step: 0,
6631        keys: &[],
6632        acl: &["@slow", "@dangerous"],
6633        since: "1.0.0",
6634        complexity: "O(1)",
6635        summary: "The server's own numbers, in sections.",
6636        group: "server",
6637    },
6638    Spec {
6639        name: "debug",
6640        arity: -2,
6641        flags: &["admin", "noscript", "loading", "stale"],
6642        first_key: 0,
6643        last_key: 0,
6644        step: 0,
6645        keys: &[],
6646        acl: &["@admin", "@slow", "@dangerous"],
6647        since: "1.0.0",
6648        complexity: "Depends on subcommand.",
6649        summary: "A container for debugging commands.",
6650        group: "server",
6651    },
6652    Spec {
6653        name: "dbsize",
6654        arity: 1,
6655        flags: READ_FAST,
6656        first_key: 0,
6657        last_key: 0,
6658        step: 0,
6659        keys: &[],
6660        acl: AC_KEY_READ,
6661        since: "1.0.0",
6662        complexity: "O(1)",
6663        summary: "How many keys are in the database this connection is on.",
6664        group: "server",
6665    },
6666    Spec {
6667        name: "flushall",
6668        arity: -1,
6669        flags: &["write"],
6670        first_key: 0,
6671        last_key: 0,
6672        step: 0,
6673        keys: &[],
6674        acl: AC_KEY_FLUSH,
6675        since: "1.0.0",
6676        complexity: "O(N) in the number of keys in every database.",
6677        summary: "Empty every database.",
6678        group: "server",
6679    },
6680    Spec {
6681        name: "flushdb",
6682        arity: -1,
6683        flags: &["write"],
6684        first_key: 0,
6685        last_key: 0,
6686        step: 0,
6687        keys: &[],
6688        acl: AC_KEY_FLUSH,
6689        since: "1.0.0",
6690        complexity: "O(N) in the number of keys in this database.",
6691        summary: "Empty the database this connection is on.",
6692        group: "server",
6693    },
6694    // In the server group and not the keyspace one, which is Redis's answer and
6695    // is the right one: it names no key, it takes two database indexes, and what
6696    // it changes is what every connected client is looking at.
6697    Spec {
6698        name: "swapdb",
6699        arity: 3,
6700        flags: WRITE_FAST,
6701        first_key: 0,
6702        last_key: 0,
6703        step: 0,
6704        keys: &[],
6705        acl: AC_SWAPDB,
6706        since: "4.0.0",
6707        complexity: "O(N) in the number of clients watching or blocked on either.",
6708        summary: "Swap two databases, so every client on one sees the other.",
6709        group: "server",
6710    },
6711    // No ACL category but `@fast`, which is Redis's answer and reads like an
6712    // omission. It is not: the categories are about what a command can reach and
6713    // this one reaches nothing.
6714    Spec {
6715        name: "time",
6716        arity: 1,
6717        flags: &["loading", "stale", "fast"],
6718        first_key: 0,
6719        last_key: 0,
6720        step: 0,
6721        keys: &[],
6722        acl: &["@fast"],
6723        since: "2.6.0",
6724        complexity: "O(1)",
6725        summary: "The server's clock, as seconds and microseconds.",
6726        group: "server",
6727    },
6728    // The four about writing the dataset out and the one about who this server
6729    // is. The first three are `admin` and so stay off a monitor's feed, and the
6730    // last two are not and so are reported, which was measured rather than read
6731    // off the flags.
6732    //
6733    // `no_multi` on `SAVE` alone. A transaction has been promised that nothing
6734    // runs in between, and a save that blocks the server for as long as the
6735    // dataset takes to write is the one command here that would break that
6736    // promise rather than defer it.
6737    Spec {
6738        name: "save",
6739        arity: 1,
6740        flags: &["admin", "noscript", "no_async_loading", "no_multi"],
6741        first_key: 0,
6742        last_key: 0,
6743        step: 0,
6744        keys: &[],
6745        acl: &["@admin", "@slow", "@dangerous"],
6746        since: "1.0.0",
6747        complexity: "O(N) in the number of keys",
6748        summary: "Write the whole dataset out as an RDB file, and wait for it.",
6749        group: "server",
6750    },
6751    Spec {
6752        name: "bgsave",
6753        arity: -1,
6754        flags: &["admin", "noscript", "no_async_loading"],
6755        first_key: 0,
6756        last_key: 0,
6757        step: 0,
6758        keys: &[],
6759        acl: &["@admin", "@slow", "@dangerous"],
6760        since: "1.0.0",
6761        complexity: "O(N) in the number of keys",
6762        summary: "Write the whole dataset out as an RDB file.",
6763        group: "server",
6764    },
6765    Spec {
6766        name: "bgrewriteaof",
6767        arity: 1,
6768        flags: &["admin", "noscript", "no_async_loading"],
6769        first_key: 0,
6770        last_key: 0,
6771        step: 0,
6772        keys: &[],
6773        acl: &["@admin", "@slow", "@dangerous"],
6774        since: "1.0.0",
6775        complexity: "O(1)",
6776        summary: "Rewrite the append only file, which this server has not got.",
6777        group: "server",
6778    },
6779    Spec {
6780        name: "lastsave",
6781        arity: 1,
6782        flags: &["loading", "stale", "fast"],
6783        first_key: 0,
6784        last_key: 0,
6785        step: 0,
6786        keys: &[],
6787        acl: &["@admin", "@fast", "@dangerous"],
6788        since: "1.0.0",
6789        complexity: "O(1)",
6790        summary: "When the dataset was last written out, in seconds.",
6791        group: "server",
6792    },
6793    // No flags at all on the container, because the container is a name and
6794    // every property that matters belongs to one of the six subcommands under
6795    // it. `USAGE` is the only one that reads a key and the only one that is
6796    // marked `readonly`, and the key spec on this row is what says where the key
6797    // is when it does.
6798    Spec {
6799        name: "memory",
6800        arity: -2,
6801        flags: &[],
6802        first_key: 0,
6803        last_key: 0,
6804        step: 0,
6805        keys: &[],
6806        acl: &["@slow"],
6807        since: "4.0.0",
6808        complexity: "O(1)",
6809        summary: "What the server is holding, and what one key of it costs.",
6810        group: "server",
6811    },
6812    // The three a replica sends and nothing else ever does. `REPLCONF` is the
6813    // only one that stays a command: by the time a replica sends the other two
6814    // the connection has stopped being a client, so what they answer is the head
6815    // of a stream rather than a reply, which is why neither is in a transaction.
6816    Spec {
6817        name: "replconf",
6818        arity: -1,
6819        flags: &["admin", "noscript", "loading", "stale", "allow_busy"],
6820        first_key: 0,
6821        last_key: 0,
6822        step: 0,
6823        keys: &[],
6824        acl: AC_ADMIN_SLOW,
6825        since: "3.0.0",
6826        complexity: "O(1)",
6827        summary: "What a replica tells a master about itself.",
6828        group: "server",
6829    },
6830    Spec {
6831        name: "psync",
6832        arity: -3,
6833        flags: &["admin", "noscript", "no_async_loading", "no_multi"],
6834        first_key: 0,
6835        last_key: 0,
6836        step: 0,
6837        keys: &[],
6838        acl: AC_ADMIN_SLOW,
6839        since: "2.8.0",
6840        complexity: "O(N) for a full resync, where N is the dataset.",
6841        summary: "Become a replica of this server, from here or from scratch.",
6842        group: "server",
6843    },
6844    Spec {
6845        name: "sync",
6846        arity: 1,
6847        flags: &["admin", "noscript", "no_async_loading", "no_multi"],
6848        first_key: 0,
6849        last_key: 0,
6850        step: 0,
6851        keys: &[],
6852        acl: AC_ADMIN_SLOW,
6853        since: "1.0.0",
6854        complexity: "O(N), where N is the dataset.",
6855        summary: "The same, from before there was anything to negotiate.",
6856        group: "server",
6857    },
6858    // The same command under the name it had and the name it has. Both rows are
6859    // real rows rather than one being an alias, because `COMMAND INFO` answers
6860    // for both names and a client that asks about the old one is asking a fair
6861    // question.
6862    Spec {
6863        name: "replicaof",
6864        arity: 3,
6865        flags: &["admin", "noscript", "stale", "no_async_loading"],
6866        first_key: 0,
6867        last_key: 0,
6868        step: 0,
6869        keys: &[],
6870        acl: AC_ADMIN_SLOW,
6871        since: "5.0.0",
6872        complexity: "O(1)",
6873        summary: "Follow another server, or stop following one.",
6874        group: "server",
6875    },
6876    Spec {
6877        name: "slaveof",
6878        arity: 3,
6879        flags: &["admin", "noscript", "stale", "no_async_loading"],
6880        first_key: 0,
6881        last_key: 0,
6882        step: 0,
6883        keys: &[],
6884        acl: AC_ADMIN_SLOW,
6885        since: "1.0.0",
6886        complexity: "O(1)",
6887        summary: "What REPLICAOF was called before it was called that.",
6888        group: "server",
6889    },
6890    // Arity is a minimum of one, because every word is optional and the command
6891    // with no words at all is a failover to whichever replica catches up first.
6892    Spec {
6893        name: "failover",
6894        arity: -1,
6895        flags: &["admin", "noscript", "stale"],
6896        first_key: 0,
6897        last_key: 0,
6898        step: 0,
6899        keys: &[],
6900        acl: AC_ADMIN_SLOW,
6901        since: "6.2.0",
6902        complexity: "O(1)",
6903        summary: "Hand the master's job to a replica, on purpose.",
6904        group: "server",
6905    },
6906    // The container for everything about slots. Arity is a minimum of two
6907    // because the word on its own is a wrong number of arguments, and the flags
6908    // are empty on the container itself, since each subcommand carries its own
6909    // and the container is never the thing that runs.
6910    Spec {
6911        name: "cluster",
6912        arity: -2,
6913        flags: &[],
6914        first_key: 0,
6915        last_key: 0,
6916        step: 0,
6917        keys: &[],
6918        acl: &["@slow"],
6919        since: "3.0.0",
6920        complexity: "O(1)",
6921        summary: "The sixteen thousand slots and who owns each of them.",
6922        group: "server",
6923    },
6924    // The three connection commands cluster mode adds. All three are `fast`,
6925    // none of them touches a key, and `ASKING` is the only one that changes
6926    // anything about the connection it is sent on.
6927    Spec {
6928        name: "asking",
6929        arity: 1,
6930        flags: &["fast"],
6931        first_key: 0,
6932        last_key: 0,
6933        step: 0,
6934        keys: &[],
6935        acl: AC_CONN,
6936        since: "3.0.0",
6937        complexity: "O(1)",
6938        summary: "Let the next command into a slot this node is receiving.",
6939        group: "connection",
6940    },
6941    Spec {
6942        name: "readonly",
6943        arity: 1,
6944        flags: &["loading", "stale", "fast"],
6945        first_key: 0,
6946        last_key: 0,
6947        step: 0,
6948        keys: &[],
6949        acl: AC_CONN,
6950        since: "3.0.0",
6951        complexity: "O(1)",
6952        summary: "Take reads from a replica rather than being redirected.",
6953        group: "connection",
6954    },
6955    Spec {
6956        name: "readwrite",
6957        arity: 1,
6958        flags: &["loading", "stale", "fast"],
6959        first_key: 0,
6960        last_key: 0,
6961        step: 0,
6962        keys: &[],
6963        acl: AC_CONN,
6964        since: "3.0.0",
6965        complexity: "O(1)",
6966        summary: "Undo READONLY and go back to being redirected.",
6967        group: "connection",
6968    },
6969    Spec {
6970        name: "role",
6971        arity: 1,
6972        flags: &["noscript", "loading", "stale", "fast"],
6973        first_key: 0,
6974        last_key: 0,
6975        step: 0,
6976        keys: &[],
6977        acl: &["@admin", "@fast", "@dangerous"],
6978        since: "2.8.12",
6979        complexity: "O(1)",
6980        summary: "Whether this server is a master or a replica, and of what.",
6981        group: "server",
6982    },
6983    // Marked `admin` for the usual reason and one more: the flag is what keeps a
6984    // command out of the feed, and a `MONITOR` that reported itself to the
6985    // monitor it had just made would be reporting the audience to itself.
6986    Spec {
6987        name: "monitor",
6988        arity: 1,
6989        flags: &["admin", "noscript", "loading", "stale"],
6990        first_key: 0,
6991        last_key: 0,
6992        step: 0,
6993        keys: &[],
6994        acl: &["@admin", "@slow", "@dangerous"],
6995        since: "1.0.0",
6996        complexity: "O(1)",
6997        summary: "Watch every command the server runs, as it runs them.",
6998        group: "server",
6999    },
7000    Spec {
7001        name: "shutdown",
7002        arity: -1,
7003        flags: &[
7004            "admin",
7005            "noscript",
7006            "loading",
7007            "stale",
7008            "no_multi",
7009            "allow_busy",
7010        ],
7011        first_key: 0,
7012        last_key: 0,
7013        step: 0,
7014        keys: &[],
7015        acl: &["@admin", "@slow", "@dangerous"],
7016        since: "1.0.0",
7017        complexity: "O(1)",
7018        summary: "Stop the server, without answering.",
7019        group: "server",
7020    },
7021];
7022
7023/// The shortest and the longest command name.
7024///
7025/// Both are facts about [`COMMANDS`], pinned by a test, and both are checked
7026/// before anything is read, so a name that could not be a command is rejected on
7027/// its length alone.
7028const MIN_LEN: usize = 3;
7029const MAX_LEN: usize = 20;
7030
7031/// How many slots the index has, which is a power of two and a bit over three
7032/// times the number of commands.
7033///
7034/// Four kibibytes of `u16`, sixty four cache lines, and loose enough that a probe
7035/// for a name that is not a command stops at an empty slot almost immediately.
7036/// Tight enough that the whole thing stays resident next to the table it
7037/// indexes.
7038///
7039/// This was 512 for a long time, which was a bit over twice the number of
7040/// commands, and it stopped being enough at 282 of them. Then it was 1024, and
7041/// that stopped being enough at 337. The note on [`MIX`] has the whole story
7042/// both times, and the short version is the same one twice: at about half full
7043/// there is no multiplier left that keeps every command within two slots of
7044/// home, and at about a sixth full the multiplier that is already there keeps
7045/// every one of them within a single slot without being touched. Two kibibytes
7046/// is what it cost this time.
7047const SLOTS: usize = 2048;
7048
7049/// A slot nothing was put in.
7050///
7051/// `u16::MAX` and not zero, because zero is `set` and `set` is the command this
7052/// most wants to be able to find.
7053const FREE: u16 = u16::MAX;
7054
7055/// The multiplier, found by searching for one that spreads these 358 names well.
7056///
7057/// Not a magic constant in the bad sense: it is checked. Every command is looked
7058/// up by its own name in a test, and another test holds the worst probe length
7059/// at what it is now, so a command added later that made this multiplier bad
7060/// would fail rather than quietly cost every lookup an extra slot.
7061///
7062/// It has been searched for fifteen times, and each time because the test went red
7063/// rather than because somebody went looking. The first was against the 191 names
7064/// in the table then, the ten graph commands pushed its worst probe to three
7065/// slots, and the second search was run over all 201. The fifteen stream commands
7066/// pushed that one to four slots and fifty one extra probes, so the third was run
7067/// over all 216, and the three 8.x pending list commands cost that one two more
7068/// probes than the test allows. The fourth was over 219 and the seven bitmap
7069/// commands took it to three slots, and the fifth was over all 226. The five
7070/// HyperLogLog commands kept its worst probe at two and took it from forty nine
7071/// extra slots to fifty five, and the search over the 231 names found nothing
7072/// better, so that one stood. The ten geo commands took it to sixty, and the
7073/// sixth search, over eight million multipliers and all 241 names, found one at
7074/// fifty six. The twelve vector set commands took that one to four slots and
7075/// seventy extra probes, so the seventh search was run over all 254 names and
7076/// found one at two slots and seventy seven.
7077///
7078/// The eight JSON commands took that one to five slots, which is the worst any
7079/// of them has been, and the eighth search was run over four hundred million
7080/// multipliers and all 262 names. It found this one at two slots and fifty four,
7081/// which is the best the table has ever been and a third fewer extra probes than
7082/// the multiplier it replaced managed with eight fewer commands. Thirty one of
7083/// the names collide on the key itself and no multiplier can separate them, so
7084/// seventeen extra probes is the floor everything here is measured against.
7085/// `json.set` and `json.get` are one of those pairs, since every name in the
7086/// group starts `js` and the only thing left to tell them apart is the length
7087/// and the last byte.
7088///
7089/// The nine JSON array commands took that one to four slots, and this time the
7090/// search over the 271 names found nothing at two whatever it was given. That
7091/// was not the multiplier's fault. `json.arrlen`, `json.objlen` and
7092/// `json.strlen` all key to the same four bytes, and three names in one slot run
7093/// costs the third of them two probes before any other name has moved, so two
7094/// slots was the whole budget spent in one place. The fix was the key rather
7095/// than the multiplier, which is what [`key_of`] now folds the middle byte in
7096/// for, and the ninth search was run over the 271 names with the new key across
7097/// six shards. It found one at two slots and fifty seven, which is a shade over
7098/// a fifth of a probe a command, the same as the multiplier it replaced managed
7099/// over nine fewer names.
7100///
7101/// The number family and `json.strappend` took that one to three slots, and the
7102/// tenth search over the 275 names found one at two slots and sixty seven.
7103/// Three of the six shards converged on sixty seven from different seeds without
7104/// any of them bettering it, which is the sign that the key rather than the
7105/// multiplier is what is left: fourteen of the names collide on the key itself
7106/// and no multiplier can separate them, so fourteen extra probes is the floor
7107/// and that was within a quarter of it per name. The two new pairs were
7108/// `json.arrappend` with `json.strappend` and `json.numincrby` with
7109/// `json.nummultby`, and both are the same shape as the pairs already there,
7110/// which is a group whose names agree everywhere the key looks.
7111///
7112/// The last four JSON commands took it to three slots again, and the eleventh
7113/// search over the 279 names found this one at two slots and sixty two, which is
7114/// better than the table has ever been while carrying four more names. Only one
7115/// of the four collides on the key, `json.mset` with `json.mget`, so the floor
7116/// moved by one and the multiplier found five more probes than the floor moved.
7117/// Nine shards were run from different seeds and the spread was sixty two to a
7118/// hundred and three, which is worth knowing: one shard is not a search.
7119///
7120/// `SUNIONCARD` and `SDIFFCARD` took it to 281 names and sixty three probes, one
7121/// more than before, and the twelfth search is the first one that did not
7122/// replace it. Eight shards over 960 million multipliers did not find a single
7123/// one that kept the worst probe at two slots at all, let alone at two slots and
7124/// sixty two, and the best of them was three slots and eighty one. So that one
7125/// stayed and the bound went up by one, which is the opposite of what the first
7126/// eleven searches concluded and was the honest reading of the same procedure.
7127///
7128/// `LMOVEM` took it to 282 names and three slots, and that is where the search
7129/// stopped being the answer. Twelve searches had found a better multiplier
7130/// eleven times and the twelfth had found that there was none, which is not a
7131/// result about `LMOVEM`, it is a result about a 512 slot table holding 282
7132/// names. Fifty five percent full is where linear probing starts to cost real
7133/// runs, and no multiplier gets around that because the runs are the load
7134/// factor and not the hash.
7135///
7136/// So the other half of the remedy this note has always named was taken and the
7137/// table doubled. At 1024 slots the multiplier that was already here goes to two
7138/// slots and forty two extra probes without being touched, which on its own
7139/// would have been enough. A search over the doubled table across four shards
7140/// and eighty million multipliers then found this one at **one** slot and twenty
7141/// eight, so no command is more than a single slot from where it wants to be,
7142/// which the table has never managed at any size. Fourteen names collide on the
7143/// key itself and no multiplier can separate them, so fourteen is the floor and
7144/// this is twice it, against a floor the 512 slot table never came within four
7145/// times of.
7146///
7147/// The cost is a kibibyte, and the thing it buys beyond today is room. The
7148/// `FT.*` and `TS.*` families are still to be written and both are large, and at
7149/// 27 percent full there is somewhere for them to go.
7150///
7151/// The `BF.*` family is the first of those to arrive and it took the table to
7152/// 296 names, where the doubled table's multiplier went to two slots and thirty
7153/// six extra probes. That is well inside what a lookup is allowed to cost, so
7154/// the search was run to see whether the single slot result had been luck at 285
7155/// names or was a property of the table at this load, and eight shards over a
7156/// hundred and sixty million multipliers found this one at one slot and thirty
7157/// three. Eleven more names, five more probes, and the worst is still a single
7158/// slot. None of the eleven collides on the key, so the floor moved by one for
7159/// an unrelated reason and stands at fifteen, which this is a shade over twice.
7160///
7161/// The `CF.*` family took the table to 310 names and thirty five extra probes,
7162/// two more than the bound allowed, with the worst still a single slot. The
7163/// thirteenth search was run over that and it is the second one that did not
7164/// replace the multiplier. Ten shards over one and a half billion multipliers
7165/// found nothing better than thirty six at one slot, which is worse than the one
7166/// already here, and another two billion with the single slot rule relaxed found
7167/// one at two slots and thirty one. Four fewer probes spread over three hundred
7168/// and ten lookups is not worth giving up the property that no command is ever
7169/// more than one slot from home, so this one stayed and the bound went up by two.
7170/// None of the fourteen new names collides on the key, so the floor is still
7171/// fifteen and the table is at a shade over twice it while carrying fourteen more
7172/// commands than when that was first true.
7173///
7174/// The `CMS.*` family took it to 316 names and thirty seven extra probes, with
7175/// the worst still one slot. No search was run this time. The one before it
7176/// covered three and a half billion multipliers against a table only six names
7177/// smaller and found nothing better that keeps every command within a slot, and
7178/// six names is not enough of a change to expect a different answer, so the
7179/// bound went up by two again. Only one of the six new names collides on the
7180/// key, which is `CMS.QUERY` against `CMS.MERGE`, so the floor is sixteen and
7181/// the table is still a shade over twice it.
7182///
7183/// The `TOPK.*` family took it to 323 names and forty two extra probes, with the
7184/// worst still one slot. A short search of four hundred thousand multipliers ran
7185/// against the new table and the best it turned up was two slots and forty eight,
7186/// worse on both counts, which is what the two big searches before it already
7187/// said, so this multiplier stayed and the bound went up by five. None of the
7188/// seven new names collides on the key, so the floor is still sixteen.
7189///
7190/// The `TDIGEST.*` family took it to 337 names and broke the bound properly: the
7191/// worst probe went to three slots, which is the first time since the table was
7192/// doubled that a command was further from home than a lookup is allowed to be.
7193/// Fourteen names is a lot to add to a family of sketch commands that all start
7194/// with the same two bytes, and the key is built out of the first two bytes, so
7195/// the whole family lands in a handful of key values before the multiply ever
7196/// sees them.
7197///
7198/// So the fifteenth search ran, and it said the same thing the tenth one did at
7199/// 282 names. Three and a half million multipliers against the 1024 slot table
7200/// found nothing better than two slots and fifty two extra probes, against the
7201/// fifty two this one already spends at three slots. That is the shape of a
7202/// table that is too full rather than a multiplier that is bad, and at 337
7203/// names in 1024 slots it is a third full, which is where the 512 slot table
7204/// was when it ran out as well. Doubling the table to 2048 and touching nothing
7205/// else takes this same multiplier to **one** slot and thirty four, so the
7206/// answer was a bigger table again and not a new constant.
7207///
7208/// The search then ran over the doubled table anyway, because that is what
7209/// happened last time and it found something worth having. Four and a half
7210/// million multipliers turned up this one at one slot and twenty two, twelve
7211/// fewer probes than the old multiplier spends in the same table, against a
7212/// floor of sixteen from the names that collide on the key itself. Twelve
7213/// probes over three hundred and thirty seven lookups is not much, but it is
7214/// free, it moves both numbers the right way, and it is exactly the trade the
7215/// doubling from 512 made, so it was taken. The old multiplier was
7216/// `0x3e8668c9760e09c9` and it served for thirteen searches.
7217///
7218/// The room this buys is the same room as last time and it is worth writing down
7219/// again: `FT.*` and `TS.*` are still to come and both are large, and at a sixth
7220/// full there is somewhere for them to go.
7221///
7222/// `TS.*` then arrived and the last three of it, the two joined reads and
7223/// `TS.READ`, broke the bound in a way no multiplier could fix. `TS.NRANGE` made
7224/// `ts.create`, `ts.incrby`, `ts.mrange` and `ts.nrange` four names sharing one
7225/// key: all nine bytes long, all starting `ts`, and all with the same fold of
7226/// the last byte against the middle one. Four names in a slot run costs the
7227/// fourth of them three probes wherever the run starts, so the worst probe went
7228/// to three and doubling the table again would not have moved it, because the
7229/// cost is in the key and not in how much room the key has to land in.
7230///
7231/// So the sixteenth search was a search for a key rather than for a multiplier.
7232/// Folding the second to last byte in as well separates all four of them, and it
7233/// separates enough else besides to take the floor from twenty colliding names
7234/// down to twelve, which is the fewest of the handful of folds tried. It is the
7235/// cheapest byte to add, too, because it sits next to the last byte that is
7236/// already being read. Then the multiplier search ran over the new key, three
7237/// hundred and twenty million of them across eight shards, and the best keeps
7238/// every command within one slot at eighteen extra probes over the whole table,
7239/// against a floor of twelve. That is the best either number has ever been here
7240/// while carrying the most names it has ever carried. The old multiplier was
7241/// `0x2f0cc21a638ae49d` and it served for one search.
7242///
7243/// `FT.CONFIG` then took the table to 392 names and put the worst probe back to
7244/// three slots on its own. It does not collide on the key with anything, so the
7245/// floor is still twelve and this was a slot run and not a key problem, which
7246/// meant a seventeenth multiplier search rather than another key. Eight hundred
7247/// million multipliers over two independent seeds both bottomed out at one slot
7248/// and twenty one extra probes and neither ever went below it, so twenty one
7249/// looks like where this key and this table actually sit with 392 names in
7250/// them. Three more probes than the last search spent over one more name is the
7251/// ordinary cost of a name, and every command is still within one slot, which
7252/// is the number a lookup feels. The old multiplier was `0x55251c10f29d4c29`
7253/// and it served for one search as well.
7254///
7255/// The five deprecated document commands took the table to 399 names and the
7256/// worst probe to two slots, which is inside the bound and outside what this
7257/// table has held itself to since it was doubled, so an eighteenth search ran.
7258/// One of the five raises the floor as well: `FT.SAFEADD` agrees with
7259/// `FT.PROFILE` on all four key bytes, which makes thirteen colliding pairs
7260/// instead of twelve and thirteen the fewest extra probes any multiplier can
7261/// spend. Four billion multipliers over two independent seeds both reached one
7262/// slot, one of them at twenty two extra probes and the other at twenty one,
7263/// which is the same twenty one the last search settled on while carrying one
7264/// more collision than it did. The old multiplier was `0xda8bd262ac598c57` and
7265/// it served for one search as well.
7266///
7267/// The five transaction commands took the table to 411 names and the total to
7268/// twenty five, which is over what this table had been holding and is the first
7269/// time the number moved without the worst probe moving with it. A nineteenth
7270/// search ran anyway, four billion multipliers, and the best of them spends
7271/// twenty four. One probe over four hundred and eleven commands is not worth
7272/// changing a constant that is already at one slot for every name, so this is
7273/// the first search that ended by keeping the multiplier it started with. The
7274/// floor is still thirteen and the number a lookup feels is still one.
7275///
7276/// The nine pub/sub commands took the table to 420 names and the worst probe to
7277/// two slots, so the multiplier the last search declined to replace had to be
7278/// replaced after all. A twentieth search ran, four billion multipliers over ten
7279/// threads, and the best of them is back to one slot for every name at twenty
7280/// four extra probes, which is the same twenty four the nineteenth search found
7281/// and did not take. The floor is still thirteen, because none of the nine
7282/// agrees with anything already in the table on all four key bytes. The old
7283/// multiplier was `0x91de5d5e21661fbd` and it served for two searches.
7284///
7285/// The five persistence commands took the table to 427 names and the worst probe
7286/// to two slots again. A twenty first search ran, four billion multipliers over
7287/// ten threads on each of two independent seeds, and the two seeds reached one
7288/// slot for every name at twenty eight extra probes and at twenty nine. Four
7289/// probes more than the last search over seven more names is the ordinary cost
7290/// of a name and none of the seven collides with anything already there, so the
7291/// floor is still thirteen and the number a lookup feels is still one. The old
7292/// multiplier was `0x71ee9b00ab8a5fd7` and it served for one search.
7293const MIX: u64 = 0xc7f9_d8be_b27e_8381;
7294
7295/// The four bytes the index is computed from: the length, the first two bytes,
7296/// and the last byte with the second to last and the middle folded into it, all
7297/// lower cased.
7298///
7299/// `None` for a name no command could be spelled as, which is decided on the
7300/// length before a byte is read.
7301///
7302/// Four bytes and not the whole name because the whole name has to be compared
7303/// at the end anyway, so the hash only has to be good enough to get to the right
7304/// slot, and reading less of the name is a shorter dependency chain in front of
7305/// the multiply. Names that agree on all four collide whatever the multiplier is
7306/// and probe once more, and the probe is the same compare the lookup was always
7307/// going to do. Over the 420 commands there are thirteen such pairs and no group
7308/// larger than a pair, so thirteen extra probes is the floor.
7309///
7310/// The middle byte is the part that was added last and it is worth saying why,
7311/// because for a long time the key was the length and the first two bytes and
7312/// the last and nothing else. That was fine while the groups that agreed on a
7313/// prefix were small: `setnx` with `setex`, `g.nadd` with `g.eadd`, `getset`
7314/// with `getbit`, `setbit` with `select`. The JSON group broke it, because every
7315/// name in it starts `js` and so every name in it was keyed on nothing but its
7316/// length and its last byte, and `json.arrlen`, `json.objlen` and `json.strlen`
7317/// agree on both. Three names in one slot run costs the third of them two probes
7318/// on its own, which leaves a multiplier no room anywhere else, and the number
7319/// families still to come are the same shape again. Folding in the middle byte
7320/// separates all three, and it separates `json.set` from `json.get` as well.
7321/// It costs one more load off a cache line the first two bytes already pulled
7322/// in, and the xor is on the same dependency chain as the shifts rather than in
7323/// front of them.
7324///
7325/// The second to last byte went in for the same reason a family later. `TS.*`
7326/// is the JSON shape again and worse: every name starts `ts`, so a nine byte
7327/// name is keyed on nothing but its length and the fold of its last byte against
7328/// its middle one, and `ts.create`, `ts.incrby`, `ts.mrange` and `ts.nrange` all
7329/// land on the same fold. Four in a run is three probes for the last of them
7330/// whatever the multiplier does, so the key had to carry more. The byte before
7331/// the last one is the cheapest one left, being on the cache line the last byte
7332/// already pulled in, and it separates all four of those and takes the floor
7333/// from twenty down to twelve besides.
7334///
7335/// `| 0x20` lower cases a letter and does not have to be told which bytes are
7336/// letters. It maps the two cases of a name to the same number, which is all
7337/// this needs, and every command name is letters. It has to be applied to each
7338/// of the three folded bytes separately, before the xor rather than after,
7339/// because `.` and `n` differ in the bit `| 0x20` sets and an xor of the raw
7340/// bytes would keep that difference alive.
7341///
7342/// On a three or four byte name the middle byte and the second to last byte are
7343/// the same byte and cancel each other out, which leaves the fold as the last
7344/// byte alone. That is not a loss, because on a name that short every byte the
7345/// fold could carry is already in the key somewhere else.
7346const fn key_of(name: &[u8]) -> Option<u32> {
7347    if name.len() < MIN_LEN || name.len() > MAX_LEN {
7348        return None;
7349    }
7350    let last = name.len() - 1;
7351    let mid = name.len() / 2;
7352    Some(
7353        name.len() as u32
7354            | ((name[0] | 0x20) as u32) << 8
7355            | ((name[1] | 0x20) as u32) << 16
7356            | (((name[last] | 0x20) ^ (name[last - 1] | 0x20) ^ (name[mid] | 0x20)) as u32) << 24,
7357    )
7358}
7359
7360/// Where a key wants to sit.
7361///
7362/// The shift leaves the top eleven bits of the product, which are the ones the
7363/// multiply mixed the most, and the mask is what makes that a slot number. Eleven
7364/// because the table has 2048 slots, so both numbers have to move together if
7365/// [`SLOTS`] ever does. It was ten while the table was half this size.
7366const fn slot_of(key: u32) -> usize {
7367    ((key as u64).wrapping_mul(MIX) >> 53) as usize & (SLOTS - 1)
7368}
7369
7370/// The index, built at compile time by inserting every command in table order.
7371///
7372/// Table order is rough order of how often a command is sent, and inserting in
7373/// that order means the hotter of two commands that want the same slot gets it
7374/// and the colder one probes, which is the right way round.
7375const INDEX: [u16; SLOTS] = index();
7376
7377const fn index() -> [u16; SLOTS] {
7378    let mut out = [FREE; SLOTS];
7379    let mut i = 0;
7380    while i < COMMANDS.len() {
7381        let key = match key_of(COMMANDS[i].name.as_bytes()) {
7382            Some(key) => key,
7383            None => panic!("a command name is outside MIN_LEN..=MAX_LEN"),
7384        };
7385        let mut at = slot_of(key);
7386        while out[at] != FREE {
7387            at = (at + 1) & (SLOTS - 1);
7388        }
7389        out[at] = i as u16;
7390        i += 1;
7391    }
7392    out
7393}
7394
7395/// The command called `name`, whatever case the client spelled it in.
7396///
7397/// This used to walk the whole table comparing lengths, and the cost of that was
7398/// not what it looked like. The table is written in rough order of how often a
7399/// command is sent, so `set` and `get` were the first two entries and cost one
7400/// compare, but `exists` is the hundred and forty ninth and `del` the hundred and
7401/// forty seventh, and every one of those compares was paid twice per command,
7402/// once to work out the key hash and once to dispatch.
7403///
7404/// Measured, that walk was 104 nanoseconds a command, which is more than a whole
7405/// `GET` costs end to end. `EXISTS` on a missing key ran at three and a half
7406/// times `GET` and almost none of the difference was the command: short
7407/// circuiting the lookup alone took it from 8.7 microseconds a batch of sixty
7408/// four to 2.0, and left it faster than `GET`, which it should be, because it
7409/// does less.
7410///
7411/// So this is one multiply and one load into two kibibytes, and then the same name
7412/// compare it always ended with. What it costs the hot commands is a multiply
7413/// they did not use to pay and a load that hits, and what it saves the rest is
7414/// the whole walk.
7415#[must_use]
7416pub fn lookup(name: &[u8]) -> Option<&'static Spec> {
7417    at(lookup_index(name))
7418}
7419
7420/// The same, answering with a position in the table rather than a reference.
7421///
7422/// This is where the lookup actually ends, because the index is what the slots
7423/// hold. It is here as its own function because a position fits in a `u16` and a
7424/// reference does not fit anywhere a framed command can carry it cheaply, so the
7425/// engine resolves a command's name once when it frames it and hands the number
7426/// on to both the key hash and the dispatcher.
7427///
7428/// `u16::MAX` is the answer for a name that is not a command, which is not a
7429/// special case anybody has to write down: the table is 254 entries, so [`at`]
7430/// hands back `None` for it the same way it would for any other number past the
7431/// end.
7432#[must_use]
7433pub fn lookup_index(name: &[u8]) -> u16 {
7434    let Some(key) = key_of(name) else {
7435        return FREE;
7436    };
7437    let mut at = slot_of(key);
7438    loop {
7439        let i = INDEX[at];
7440        if i == FREE {
7441            return FREE;
7442        }
7443        if COMMANDS[i as usize]
7444            .name
7445            .as_bytes()
7446            .eq_ignore_ascii_case(name)
7447        {
7448            return i;
7449        }
7450        at = (at + 1) & (SLOTS - 1);
7451    }
7452}
7453
7454/// The command at `i`, or `None` if there is none there.
7455///
7456/// The other half of [`lookup_index`], and the only thing that should ever be
7457/// handed one of its answers.
7458#[must_use]
7459pub fn at(i: u16) -> Option<&'static Spec> {
7460    COMMANDS.get(i as usize)
7461}
7462
7463/// How many commands there are.
7464///
7465/// The length of a counter array that has a row per command, which is the only
7466/// thing that wants this number.
7467#[must_use]
7468pub const fn count() -> usize {
7469    COMMANDS.len()
7470}
7471
7472/// Where in [`COMMANDS`] this spec is.
7473///
7474/// Every `&'static Spec` a caller can hold came out of [`lookup`] and therefore
7475/// points into that array, so its position is the distance from the front
7476/// measured in whole `Spec`s. That is arithmetic on two addresses and not a
7477/// search, which is the point: a per command counter has to be reachable from
7478/// the spec the dispatcher is already holding without walking the table a second
7479/// time.
7480///
7481/// A spec from somewhere else would answer nonsense, which is why this takes a
7482/// `&'static Spec` rather than a `&Spec`: the only `'static` ones are in the
7483/// table.
7484#[must_use]
7485pub fn index_of(spec: &'static Spec) -> usize {
7486    let front = COMMANDS.as_ptr().addr();
7487    let here = std::ptr::from_ref(spec).addr();
7488    (here - front) / size_of::<Spec>()
7489}
7490
7491/// The name of the command at `at`, which is [`index_of`] the other way round.
7492///
7493/// # Panics
7494///
7495/// If `at` is past the end of the table, which only a caller that made the index
7496/// up rather than getting it from [`index_of`] can manage.
7497#[must_use]
7498pub fn name_at(at: usize) -> &'static str {
7499    COMMANDS[at].name
7500}
7501
7502/// Whether `n` arguments, counting the name, satisfy this command's arity.
7503#[must_use]
7504pub fn arity_ok(spec: &Spec, n: usize) -> bool {
7505    let n = n as i32;
7506    if spec.arity >= 0 {
7507        n == spec.arity
7508    } else {
7509        n >= -spec.arity
7510    }
7511}
7512
7513/// Where a command's key arguments are: the first one, how many there are, and
7514/// how far apart they sit.
7515///
7516/// Three numbers is enough for every command in this table, the ones Redis marks
7517/// `movablekeys` included, because those keep their keys behind a count and a
7518/// count still gives a first, a many and a step. What three numbers cannot
7519/// describe is a command whose keys are found by scanning for a keyword, and
7520/// there are none of those here.
7521#[derive(Debug, Clone, Copy)]
7522pub(crate) struct KeySpan {
7523    /// The argument index of the first key.
7524    pub first: usize,
7525    /// How many keys there are.
7526    pub count: usize,
7527    /// How many arguments apart consecutive keys are.
7528    pub step: usize,
7529}
7530
7531/// Why a command has no key span.
7532#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7533pub(crate) enum NoKeys {
7534    /// It names no keys at all, whatever it is sent.
7535    Never,
7536    /// It keeps its keys behind a count, and the count is not a usable number.
7537    BadCount,
7538}
7539
7540/// Work out where `spec`'s keys are in `args`.
7541///
7542/// `base` is how many arguments sit in front of the command itself, which is two
7543/// for `COMMAND GETKEYS <command> ...` and zero for a command that is running.
7544/// The positions in the answer are absolute, so they go straight to
7545/// [`Args::get`].
7546///
7547/// A few commands keep their keys somewhere the first, last and step triple
7548/// cannot describe, behind a count of how many there are. That is why a real
7549/// server marks them `movablekeys` and why a cluster aware client has to ask
7550/// `COMMAND GETKEYS` about them at all. `MSETEX` counts pairs and the rest count
7551/// single keys, so what differs between them is the step and where the count
7552/// sits: the script family has the body in front of it and the others have
7553/// nothing.
7554///
7555/// The script family is also the only one where none is a real answer. A script
7556/// with no keys is an ordinary thing to write and `EVAL body 0` answers an empty
7557/// list rather than complaining, where `MSETEX 0` is a command that would do
7558/// nothing and is refused. So a count that makes no sense at all is an empty
7559/// span for the script family and a [`NoKeys::BadCount`] for the others, which
7560/// is a real server reading the script family through a key spec that finds no
7561/// keys and refusing the rest.
7562pub(crate) fn key_span(spec: &Spec, args: Args<'_>, base: usize) -> Result<KeySpan, NoKeys> {
7563    if let Some((rel, step, least, lenient)) = match spec.name {
7564        "msetex" => Some((1, 2, 1, false)),
7565        "ts.nrange" | "ts.nrevrange" => Some((1, 1, 1, false)),
7566        "eval" | "eval_ro" | "evalsha" | "evalsha_ro" | "fcall" | "fcall_ro" => {
7567            Some((2, 1, 0, true))
7568        }
7569        _ => None,
7570    } {
7571        let at = base + rel;
7572        let found = parse_i64(args.get(at))
7573            .filter(|&n| n >= least)
7574            .and_then(|n| usize::try_from(n).ok())
7575            .filter(|&n| at + 1 + step * n <= args.len());
7576        let count = match found {
7577            Some(n) => n,
7578            None if lenient => 0,
7579            None => return Err(NoKeys::BadCount),
7580        };
7581        return Ok(KeySpan {
7582            first: at + 1,
7583            count,
7584            step,
7585        });
7586    }
7587    if spec.first_key == 0 {
7588        return Err(NoKeys::Never);
7589    }
7590    let argc = args.len() - base;
7591    let last = if spec.last_key < 0 {
7592        (argc as i64) + i64::from(spec.last_key)
7593    } else {
7594        i64::from(spec.last_key)
7595    };
7596    let step = i64::from(spec.step).max(1);
7597    let first = i64::from(spec.first_key);
7598    let count = if last < first {
7599        0
7600    } else {
7601        ((last - first) / step + 1) as usize
7602    };
7603    Ok(KeySpan {
7604        first: base + first as usize,
7605        count,
7606        step: step as usize,
7607    })
7608}
7609
7610#[cfg(test)]
7611mod tests {
7612    use super::*;
7613
7614    /// The name here is the name a client reads back, so it is spelled the way
7615    /// the server that registered it spelled it.
7616    ///
7617    /// That is lower case for everything the server itself registers and upper
7618    /// case for the two groups that come out of a module, so `COMMAND INFO vadd`
7619    /// answers `VADD` and `COMMAND INFO ft.create` answers `FT.CREATE`, and the
7620    /// arity errors quote them the same way. Nothing else in the table cares,
7621    /// because a lookup compares without regard to case and the index key folds
7622    /// the case out before it hashes.
7623    #[test]
7624    fn every_name_is_spelled_the_way_it_was_registered_and_appears_once() {
7625        let mut seen = std::collections::BTreeSet::new();
7626        for c in COMMANDS {
7627            let want = if c.group == "vector" || c.group == "search" {
7628                c.name.to_uppercase()
7629            } else {
7630                c.name.to_lowercase()
7631            };
7632            assert_eq!(c.name, want, "{} is spelled wrong for its group", c.name);
7633            assert!(seen.insert(c.name), "{} is in the table twice", c.name);
7634        }
7635    }
7636
7637    /// Every command's index is where the table actually holds it.
7638    ///
7639    /// Checked against the position a search finds, over the whole table rather
7640    /// than a sample, because the arithmetic is the thing being tested and an
7641    /// off by one in it would put every counter on the wrong command.
7642    #[test]
7643    fn a_spec_knows_where_it_is_in_the_table() {
7644        assert_eq!(count(), COMMANDS.len());
7645        for (want, spec) in COMMANDS.iter().enumerate() {
7646            assert_eq!(index_of(spec), want, "{} is at the wrong index", spec.name);
7647        }
7648        assert_eq!(
7649            index_of(lookup(b"get").unwrap()),
7650            index_of(lookup(b"GET").unwrap())
7651        );
7652    }
7653
7654    #[test]
7655    fn lookup_ignores_case_and_does_not_match_a_prefix() {
7656        assert_eq!(lookup(b"GET").unwrap().name, "get");
7657        assert_eq!(lookup(b"gEt").unwrap().name, "get");
7658        assert!(lookup(b"ge").is_none());
7659        assert!(lookup(b"gets").is_none());
7660    }
7661
7662    /// Every command is findable under its own name, in either case.
7663    ///
7664    /// The index is built at compile time from the table it sits beside, so what
7665    /// a test can still catch is a command that the build put somewhere the
7666    /// lookup does not walk past, which is what a probe that stopped early would
7667    /// look like.
7668    #[test]
7669    fn every_command_is_findable_by_its_own_name() {
7670        for spec in COMMANDS {
7671            let found = lookup(spec.name.as_bytes()).expect(spec.name);
7672            assert_eq!(
7673                index_of(found),
7674                index_of(spec),
7675                "{} found the wrong spec",
7676                spec.name
7677            );
7678            assert_eq!(
7679                lookup(spec.name.to_ascii_uppercase().as_bytes()).map(index_of),
7680                Some(index_of(spec)),
7681                "{} is not found in upper case",
7682                spec.name,
7683            );
7684        }
7685    }
7686
7687    /// A name that cannot be a command is answered before anything is compared.
7688    #[test]
7689    fn a_name_that_cannot_be_a_command_is_rejected_on_its_shape() {
7690        assert!(lookup(b"").is_none());
7691        assert!(key_of(b"").is_none());
7692        assert!(key_of(&[b'g'; 256]).is_none());
7693        assert!(lookup(&[b'g'; 256]).is_none());
7694        assert!(lookup(b"9et").is_none());
7695    }
7696
7697    /// The two cases of a name give the same key and different names do not.
7698    #[test]
7699    fn a_key_folds_the_case_and_nothing_else() {
7700        assert_eq!(key_of(b"get"), key_of(b"GET"));
7701        assert_eq!(key_of(b"get"), key_of(b"gEt"));
7702        assert_ne!(key_of(b"get"), key_of(b"set"), "other first byte");
7703        assert_ne!(key_of(b"get"), key_of(b"gxt"), "other second byte");
7704        assert_ne!(key_of(b"get"), key_of(b"gex"), "other last byte");
7705        assert_ne!(key_of(b"get"), key_of(b"gett"), "other length");
7706        assert_ne!(key_of(b"abcde"), key_of(b"abxde"), "other middle byte");
7707        assert_eq!(key_of(b"abcde"), key_of(b"ABCDE"), "middle byte folds too");
7708    }
7709
7710    /// The index is still worth having, which is a thing that can rot.
7711    ///
7712    /// The multiplier was searched for against the 191 commands that were in the
7713    /// table when it was written, and twenty one times since. Adding commands cannot
7714    /// make a lookup wrong, because a probe walks to an empty slot and every
7715    /// candidate has its name compared, but it can make one slow, and a slow
7716    /// lookup is exactly the thing this replaced. So the worst probe is written
7717    /// down here: if a command added later pushes it up, somebody searches for a
7718    /// new multiplier or a bigger table rather than finding out from a benchmark
7719    /// six months later. Both of those have now happened, and the note on
7720    /// [`MIX`] says which one worked when.
7721    ///
7722    /// The bound is two slots because that is what a lookup is allowed to cost,
7723    /// and the table is better than its bound: the multiplier in it keeps every
7724    /// command within one slot. The total is held at exactly what it measures so
7725    /// that a command which quietly spends the headroom shows up here.
7726    #[test]
7727    fn no_command_is_more_than_two_slots_from_where_it_wants_to_be() {
7728        let mut worst = 0;
7729        let mut total = 0;
7730        for spec in COMMANDS {
7731            let key = key_of(spec.name.as_bytes()).expect(spec.name);
7732            let home = slot_of(key);
7733            let mut at = home;
7734            let mut steps = 0;
7735            while INDEX[at] as usize != index_of(spec) {
7736                at = (at + 1) & (SLOTS - 1);
7737                steps += 1;
7738                assert!(steps < SLOTS, "{} is not in the index at all", spec.name);
7739            }
7740            worst = worst.max(steps);
7741            total += steps;
7742        }
7743        assert!(worst <= 2, "worst probe is {worst} slots");
7744        assert_eq!(
7745            worst, 1,
7746            "the multiplier stopped keeping every command close"
7747        );
7748        assert!(
7749            total <= 29,
7750            "{total} extra slots walked over the whole table"
7751        );
7752    }
7753
7754    /// The table has room to probe in, which is what stops the loop.
7755    #[test]
7756    fn the_index_is_not_full() {
7757        assert!(
7758            COMMANDS.len() < SLOTS,
7759            "the probe would never find an empty"
7760        );
7761        assert!(
7762            COMMANDS.len() < FREE as usize,
7763            "an index would collide with FREE"
7764        );
7765        let free = INDEX.iter().filter(|&&i| i == FREE).count();
7766        assert_eq!(free, SLOTS - COMMANDS.len());
7767    }
7768
7769    #[test]
7770    fn arity_counts_the_command_name() {
7771        let get = lookup(b"get").unwrap();
7772        assert!(!arity_ok(get, 1));
7773        assert!(arity_ok(get, 2));
7774        assert!(!arity_ok(get, 3));
7775
7776        // A negative arity is a minimum, which is how SET takes its options.
7777        let set = lookup(b"set").unwrap();
7778        assert!(!arity_ok(set, 2));
7779        assert!(arity_ok(set, 3));
7780        assert!(arity_ok(set, 9));
7781    }
7782
7783    /// A key spec that is wrong sends a cluster client to the wrong node, so
7784    /// the pair commands are worth stating twice.
7785    #[test]
7786    fn the_pair_commands_step_two_keys_at_a_time() {
7787        for name in [b"mset".as_slice(), b"msetnx"] {
7788            let c = lookup(name).unwrap();
7789            assert_eq!((c.first_key, c.last_key, c.step), (1, -1, 2));
7790        }
7791        let mget = lookup(b"mget").unwrap();
7792        assert_eq!((mget.first_key, mget.last_key, mget.step), (1, -1, 1));
7793        // MSETEX counts its keys in an argument, so there is no static spec
7794        // for them and a client has to ask with COMMAND GETKEYS.
7795        let msetex = lookup(b"msetex").unwrap();
7796        assert_eq!((msetex.first_key, msetex.last_key, msetex.step), (0, 0, 0));
7797        assert!(msetex.flags.contains(&"movablekeys"));
7798    }
7799}