Skip to main content

yo_resp/dispatch/
table.rs

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