Skip to main content

yo_resp/dispatch/
table.rs

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