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