Skip to main content

yo_resp/dispatch/
table.rs

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