Skip to main content

yo_resp/dispatch/
table.rs

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