Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Six sorted-set commands that were `unknown command`, and `ZADD ... INCR`**
(moon#959). `ZRANGEBYLEX`, `ZREVRANGEBYLEX`, `ZREMRANGEBYRANK`,
`ZREMRANGEBYSCORE`, `ZREMRANGEBYLEX` and `ZDIFFSTORE` are implemented, wired
into every dispatch path, registered as `@sortedset`, and covered by rows in
both parity harnesses; `docs/commands.md` had advertised `ZRANGEBYLEX` while
dispatch rejected it. `ZADD ... INCR` — which `redis-py`'s `zadd(...,
incr=True)` sends — replies the new score as a bulk string, or nil when
`NX`/`XX`/`GT`/`LT` refuse, in Redis's decision order. Every reply, error
surface included, was read off redis-server 8.6.1 before the code was
written: the range grammar is checked before the key is consulted, a
`ZREMRANGEBY*` that drains a key deletes it, a listpack zset is trimmed in
place and never converted, and the `used_memory` ledger stays exact on both
encodings. `ZDIFFSTORE` joins the `ZUNIONSTORE` family's `numkeys` and
option rules, refusing `WEIGHTS`/`AGGREGATE` as `syntax error`, and — because
it writes a destination it is not routed on — it also joins the moon#592
cross-shard WRITE guard, so `ZDIFFSTORE` across shards is `CROSSSLOT` rather
than an ack whose destination lands nowhere. (It shares the guard's
`(10, b'z')` match arm with moon#962's `ZINTERCARD`; both spellings are
named there.)

### Changed

- **`Check (macOS)` and `Check (Windows)` run their tests in three shards**,
Expand Down Expand Up @@ -90,6 +112,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
family is deferred by the #438 early-flush guard one statement above this
predicate in both handlers whenever remote work is pending, and the derived
predicate now says wait for them regardless.

- **`ZUNIONSTORE`/`ZINTERSTORE` report `WRONGTYPE` before an option error, and
no longer flatten a listpack source** (moon#959). Redis looks every source up
before it parses `WEIGHTS`/`AGGREGATE`, so `ZUNIONSTORE d 1 <string-key>
BOGUS` is `WRONGTYPE` on redis 8.6.1; moon answered `syntax error`. The store
family also read its sources through the promoting accessor, converting a
`listpack` source to `skiplist` as a side effect of reading it — the moon#928
defect the read-only set operations were already cured of. Both fixes came
with the shared implementation `ZDIFFSTORE` now uses.
- **Commands routed to another shard are counted and timed** (moon#982).
At `--shards > 1` a command whose key lives on a shard other than the
connection's went through no telemetry probe at all — neither the
Expand Down
7 changes: 5 additions & 2 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ Per-field return code on the TTL commands: `-2` = no such field, `-1` = no TTL,

`SADD`, `SREM`, `SMEMBERS`, `SCARD`, `SISMEMBER`, `SMISMEMBER`, `SINTER`, `SUNION`, `SDIFF`, `SINTERSTORE`, `SUNIONSTORE`, `SDIFFSTORE`, `SRANDMEMBER`, `SPOP`, `SSCAN`

## Sorted sets (21)
## Sorted sets (35)

`ZADD`, `ZREM`, `ZSCORE`, `ZCARD`, `ZINCRBY`, `ZRANK`, `ZREVRANK`, `ZPOPMIN`, `ZPOPMAX`, `ZSCAN`, `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`, `ZREVRANGEBYSCORE`, `ZRANGEBYLEX`, `ZCOUNT`, `ZLEXCOUNT`, `ZUNIONSTORE`, `ZINTERSTORE`, `BZPOPMIN`, `BZPOPMAX`
`ZADD`, `ZREM`, `ZSCORE`, `ZCARD`, `ZINCRBY`, `ZRANK`, `ZREVRANK`, `ZPOPMIN`, `ZPOPMAX`, `ZSCAN`, `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`, `ZREVRANGEBYSCORE`, `ZRANGEBYLEX`, `ZREVRANGEBYLEX`, `ZCOUNT`, `ZLEXCOUNT`, `ZREMRANGEBYRANK`, `ZREMRANGEBYSCORE`, `ZREMRANGEBYLEX`, `ZUNIONSTORE`, `ZINTERSTORE`, `ZDIFFSTORE`, `ZRANGESTORE`, `ZDIFF`, `ZUNION`, `ZINTER`, `ZINTERCARD`, `ZMSCORE`, `ZRANDMEMBER`, `ZMPOP`, `BZPOPMIN`, `BZPOPMAX`, `BZMPOP`

!!! tip
`ZADD` supports `NX`, `XX`, `GT`, `LT`, `CH` and `INCR`, matching Redis 6.2+ behavior. Every command in this list was sent to a live moon server and answered (moon#959 re-verified the table command by command, not from `COMMAND INFO`, after it had advertised an unimplemented one).

## Geospatial (8)

Expand Down
39 changes: 39 additions & 0 deletions scripts/test-commands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,45 @@ if should_run "sorted_set"; then
rcli ZADD z:ch 1 m >/dev/null 2>&1; mcli ZADD z:ch 1 m >/dev/null 2>&1
assert_match "ZADD CH sub-epsilon" ZADD z:ch CH 1.0000000000000002 m
assert_match "ZADD CH moved the score" ZSCORE z:ch m

# moon#959 -- six commands that answered `ERR unknown command` on moon
# (ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYRANK, ZREMRANGEBYSCORE,
# ZREMRANGEBYLEX, ZDIFFSTORE) plus `ZADD ... INCR`, which answered an
# arity error. Neither harness named any of them, which is how a command
# docs/commands.md advertised went missing. Every reply was read off
# redis 8.6.1 before the commands were written.
rcli ZADD z:959:lex 0 a 0 b 0 c 0 d 0 e >/dev/null 2>&1; mcli ZADD z:959:lex 0 a 0 b 0 c 0 d 0 e >/dev/null 2>&1
assert_match "ZRANGEBYLEX" ZRANGEBYLEX z:959:lex - +
assert_match "ZRANGEBYLEX bounds" ZRANGEBYLEX z:959:lex '[b' '(d'
assert_match "ZRANGEBYLEX LIMIT" ZRANGEBYLEX z:959:lex - + LIMIT 1 2
assert_match "ZRANGEBYLEX bad bound" ZRANGEBYLEX z:959:lex a b
assert_match "ZRANGEBYLEX WITHSCORES" ZRANGEBYLEX z:959:lex - + WITHSCORES
assert_match "ZRANGEBYLEX WITHSCORES 1st" ZRANGEBYLEX z:959:lex a b WITHSCORES
assert_match "ZREVRANGEBYLEX" ZREVRANGEBYLEX z:959:lex + -
assert_match "ZREVRANGEBYLEX bounds" ZREVRANGEBYLEX z:959:lex '(d' '[b' LIMIT 0 1
rcli ZADD z:959:r 1 a 2 b 3 c 4 d 5 e >/dev/null 2>&1; mcli ZADD z:959:r 1 a 2 b 3 c 4 d 5 e >/dev/null 2>&1
assert_match "ZREMRANGEBYRANK" ZREMRANGEBYRANK z:959:r 0 0
assert_match "ZREMRANGEBYRANK neg stop" ZREMRANGEBYRANK z:959:r -10 -6
assert_match "ZREMRANGEBYSCORE" ZREMRANGEBYSCORE z:959:r '(2' 3
assert_match "ZREMRANGEBYSCORE bad" ZREMRANGEBYSCORE z:959:r nan 1
assert_match "ZREMRANGEBYLEX" ZREMRANGEBYLEX z:959:lex '[b' '(d'
assert_match "ZREMRANGEBYLEX arity" ZREMRANGEBYLEX z:959:lex - + x
assert_match "ZRANGE after ZREMRANGE" ZRANGE z:959:r 0 -1 WITHSCORES
assert_match "ZREMRANGEBYSCORE drains" ZREMRANGEBYSCORE z:959:r -inf +inf
assert_match "ZREMRANGE drained key" EXISTS z:959:r
assert_match "ZDIFFSTORE" ZDIFFSTORE {z}:diff 2 {z}:A {z}:B
assert_match "ZDIFFSTORE result" ZRANGE {z}:diff 0 -1 WITHSCORES
assert_match "ZDIFFSTORE numkeys 0" ZDIFFSTORE {z}:diff 0 {z}:A
assert_match "ZDIFFSTORE WEIGHTS" ZDIFFSTORE {z}:diff 1 {z}:A WEIGHTS 1
assert_match "ZDIFFSTORE empty deletes" ZDIFFSTORE {z}:diff 2 {z}:A {z}:A
assert_match "ZDIFFSTORE dest gone" EXISTS {z}:diff
assert_match "ZADD INCR" ZADD z:959:i INCR 5 a
assert_match "ZADD INCR again" ZADD z:959:i INCR 2.5 a
assert_match "ZADD NX INCR refused" ZADD z:959:i NX INCR 1 a
assert_match "ZADD XX INCR refused" ZADD z:959:i XX INCR 1 nope
assert_match "ZADD GT INCR refused" ZADD z:959:i GT INCR -1 a
assert_match "ZADD LT INCR" ZADD z:959:i LT INCR -1 a
assert_match "ZADD INCR two pairs" ZADD z:959:i INCR 1 a 2 b
fi

# ===========================================================================
Expand Down
121 changes: 121 additions & 0 deletions scripts/test-consistency.sh
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,122 @@ both ZADD z:792:bt 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
assert_both "ZADD CH sub-epsilon (bptree)" ZADD z:792:bt CH 1.0000000000000002 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
assert_both "ZADD CH bptree moved score" ZSCORE z:792:bt bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

# moon#959 -- six commands that answered `ERR unknown command` on moon
# (ZRANGEBYLEX, ZREVRANGEBYLEX, ZREMRANGEBYRANK, ZREMRANGEBYSCORE,
# ZREMRANGEBYLEX, ZDIFFSTORE) plus `ZADD ... INCR`, which answered an arity
# error. Every reply was read off redis 8.6.1 before the commands were
# written, error surface included: the bounds grammar is checked BEFORE the
# key (a bad bound on a missing key is an error, not an empty array), a
# drained key is deleted, and WRONGTYPE never clobbers the value it refused.
both ZADD z:959:lex 0 a 0 b 0 c 0 d 0 e
assert_both "ZRANGEBYLEX all" ZRANGEBYLEX z:959:lex - +
assert_both "ZRANGEBYLEX [b (d" ZRANGEBYLEX z:959:lex '[b' '(d'
assert_both "ZRANGEBYLEX LIMIT 1 2" ZRANGEBYLEX z:959:lex - + LIMIT 1 2
assert_both "ZRANGEBYLEX LIMIT -1 2" ZRANGEBYLEX z:959:lex - + LIMIT -1 2
assert_both "ZRANGEBYLEX reversed bounds" ZRANGEBYLEX z:959:lex + -
assert_both "ZRANGEBYLEX bad bound" ZRANGEBYLEX z:959:lex a b
assert_both "ZRANGEBYLEX bad bound missing key" ZRANGEBYLEX z:959:nokey a b
assert_both "ZRANGEBYLEX WITHSCORES" ZRANGEBYLEX z:959:lex - + WITHSCORES
assert_both "ZRANGEBYLEX WITHSCORES beats bound" ZRANGEBYLEX z:959:lex a b WITHSCORES
assert_both "ZRANGEBYLEX LIMIT beats WITHSCORES" ZRANGEBYLEX z:959:lex - + WITHSCORES LIMIT 1
assert_both "ZRANGEBYLEX dangling LIMIT" ZRANGEBYLEX z:959:lex - + LIMIT 1
assert_both "ZRANGEBYLEX LIMIT notanint" ZRANGEBYLEX z:959:lex - + LIMIT notanint 1
assert_both "ZRANGEBYLEX unknown token" ZRANGEBYLEX z:959:lex - + BOGUS
assert_both "ZRANGEBYLEX missing key" ZRANGEBYLEX z:959:nokey - +
assert_both "ZREVRANGEBYLEX all" ZREVRANGEBYLEX z:959:lex + -
assert_both "ZREVRANGEBYLEX (d [b" ZREVRANGEBYLEX z:959:lex '(d' '[b'
assert_both "ZREVRANGEBYLEX LIMIT" ZREVRANGEBYLEX z:959:lex + - LIMIT 1 2
assert_both "ZREVRANGEBYLEX reversed bounds" ZREVRANGEBYLEX z:959:lex - +
both ZADD z:959:rank 1 a 2 b 3 c 4 d 5 e
assert_both "ZREMRANGEBYRANK 0 0" ZREMRANGEBYRANK z:959:rank 0 0
# A stop still negative after normalisation is NOT clamped to 0 -- nothing
# is removed. (ZRANGE's own helper clamps it; that divergence is out of
# moon#959's scope and is reported separately.)
assert_both "ZREMRANGEBYRANK -10 -6" ZREMRANGEBYRANK z:959:rank -10 -6
assert_both "ZREMRANGEBYRANK 3 1" ZREMRANGEBYRANK z:959:rank 3 1
assert_both "ZREMRANGEBYRANK 1 -2" ZREMRANGEBYRANK z:959:rank 1 -2
assert_both "ZREMRANGEBYRANK left" ZRANGE z:959:rank 0 -1 WITHSCORES
assert_both "ZREMRANGEBYRANK notanint" ZREMRANGEBYRANK z:959:rank notanint 1
assert_both "ZREMRANGEBYRANK arity" ZREMRANGEBYRANK z:959:rank 1
assert_both "ZREMRANGEBYRANK missing key" ZREMRANGEBYRANK z:959:nokey 0 1
assert_both "ZREMRANGEBYRANK drains" ZREMRANGEBYRANK z:959:rank 0 -1
assert_both "ZREMRANGEBYRANK drained key gone" EXISTS z:959:rank
both ZADD z:959:score 1 a 2 b 3 c 4 d 5 e
assert_both "ZREMRANGEBYSCORE (2 3" ZREMRANGEBYSCORE z:959:score '(2' 3
assert_both "ZREMRANGEBYSCORE 3 1" ZREMRANGEBYSCORE z:959:score 3 1
assert_both "ZREMRANGEBYSCORE 5 inf" ZREMRANGEBYSCORE z:959:score 5 inf
assert_both "ZREMRANGEBYSCORE left" ZRANGE z:959:score 0 -1 WITHSCORES
assert_both "ZREMRANGEBYSCORE nan" ZREMRANGEBYSCORE z:959:score nan 1
assert_both "ZREMRANGEBYSCORE bad on missing" ZREMRANGEBYSCORE z:959:nokey a 1
assert_both "ZREMRANGEBYSCORE drains" ZREMRANGEBYSCORE z:959:score -inf +inf
assert_both "ZREMRANGEBYSCORE drained key gone" EXISTS z:959:score
both ZADD z:959:lex2 0 a 0 b 0 c 0 d 0 e
assert_both "ZREMRANGEBYLEX [b (d" ZREMRANGEBYLEX z:959:lex2 '[b' '(d'
assert_both "ZREMRANGEBYLEX (c +" ZREMRANGEBYLEX z:959:lex2 '(c' +
assert_both "ZREMRANGEBYLEX left" ZRANGE z:959:lex2 0 -1
assert_both "ZREMRANGEBYLEX bad bound" ZREMRANGEBYLEX z:959:lex2 a b
assert_both "ZREMRANGEBYLEX arity" ZREMRANGEBYLEX z:959:lex2 - + x
assert_both "ZREMRANGEBYLEX drains" ZREMRANGEBYLEX z:959:lex2 - +
assert_both "ZREMRANGEBYLEX drained key gone" EXISTS z:959:lex2
both SET z:959:str v
assert_both "ZRANGEBYLEX WRONGTYPE" ZRANGEBYLEX z:959:str - +
assert_both "ZREVRANGEBYLEX WRONGTYPE" ZREVRANGEBYLEX z:959:str + -
assert_both "ZREMRANGEBYRANK WRONGTYPE" ZREMRANGEBYRANK z:959:str 0 1
assert_both "ZREMRANGEBYSCORE WRONGTYPE" ZREMRANGEBYSCORE z:959:str 0 1
assert_both "ZREMRANGEBYLEX WRONGTYPE" ZREMRANGEBYLEX z:959:str - +
assert_both "WRONGTYPE left the string" GET z:959:str
# ZDIFFSTORE joins the ZUNIONSTORE family: the same two numkeys classes, the
# same overrun rule, and EVERY option token refused (it takes none). Redis
# looks the sources up before it parses the options, so WRONGTYPE outranks
# an option error on all three STORE commands. `{z959}` co-locates the
# destination with its sources (moon#592).
both ZADD {z959}:a 1 a 2 b 3 c 4 d 5 e
both ZADD {z959}:b 1 a 2 b
both ZADD {z959}:c 2 b 9 x
both SET {z959}:str v
assert_both "ZDIFFSTORE two sources" ZDIFFSTORE {z959}:diff 2 {z959}:a {z959}:b
assert_both "ZDIFFSTORE result" ZRANGE {z959}:diff 0 -1 WITHSCORES
assert_both "ZDIFFSTORE three sources" ZDIFFSTORE {z959}:diff 3 {z959}:a {z959}:b {z959}:c
assert_both "ZDIFFSTORE result 3" ZRANGE {z959}:diff 0 -1 WITHSCORES
assert_both "ZDIFFSTORE missing first source" ZDIFFSTORE {z959}:diff 2 {z959}:nokey {z959}:a
assert_both "ZDIFFSTORE empty deletes dest" EXISTS {z959}:diff
assert_both "ZDIFFSTORE dest is a source" ZDIFFSTORE {z959}:c 2 {z959}:a {z959}:c
assert_both "ZDIFFSTORE dest-as-source result" ZRANGE {z959}:c 0 -1 WITHSCORES
assert_both "ZDIFFSTORE numkeys 0" ZDIFFSTORE {z959}:diff 0 {z959}:a
assert_both "ZDIFFSTORE numkeys -1" ZDIFFSTORE {z959}:diff -1 {z959}:a
assert_both "ZDIFFSTORE numkeys notanint" ZDIFFSTORE {z959}:diff notanint {z959}:a
assert_both "ZDIFFSTORE numkeys overruns" ZDIFFSTORE {z959}:diff 2 {z959}:a
assert_both "ZDIFFSTORE WEIGHTS refused" ZDIFFSTORE {z959}:diff 1 {z959}:a WEIGHTS 1
assert_both "ZDIFFSTORE AGGREGATE refused" ZDIFFSTORE {z959}:diff 1 {z959}:a AGGREGATE SUM
assert_both "ZDIFFSTORE unknown token" ZDIFFSTORE {z959}:diff 1 {z959}:a BOGUS
assert_both "ZDIFFSTORE arity" ZDIFFSTORE {z959}:diff 1
assert_both "ZDIFFSTORE WRONGTYPE source" ZDIFFSTORE {z959}:diff 2 {z959}:a {z959}:str
assert_both "ZDIFFSTORE WRONGTYPE beats option" ZDIFFSTORE {z959}:diff 1 {z959}:str BOGUS
assert_both "ZUNIONSTORE WRONGTYPE beats option" ZUNIONSTORE {z959}:diff 1 {z959}:str BOGUS
assert_both "ZINTERSTORE WRONGTYPE beats WEIGHTS" ZINTERSTORE {z959}:diff 2 {z959}:a {z959}:str WEIGHTS 1 1
assert_both "ZDIFFSTORE errors made no dest" EXISTS {z959}:diff
# ZADD ... INCR: ZINCRBY's arithmetic under ZADD's flags, the new score as
# a bulk string, nil when a flag refuses.
assert_both "ZADD INCR new member" ZADD z:959:incr INCR 5 a
assert_both "ZADD INCR existing" ZADD z:959:incr INCR 2.5 a
assert_both "ZADD NX INCR present" ZADD z:959:incr NX INCR 1 a
assert_both "ZADD NX INCR absent" ZADD z:959:incr NX INCR 1 n
assert_both "ZADD XX INCR absent" ZADD z:959:incr XX INCR 1 nope
assert_both "ZADD XX INCR present" ZADD z:959:incr XX INCR 1 a
assert_both "ZADD GT INCR refused" ZADD z:959:incr GT INCR -1 a
assert_both "ZADD GT INCR zero refused" ZADD z:959:incr GT INCR 0 a
assert_both "ZADD LT INCR" ZADD z:959:incr LT INCR -1 a
assert_both "ZADD XX GT INCR absent" ZADD z:959:incr XX GT INCR 1 q
assert_both "ZADD INCR CH" ZADD z:959:incr INCR CH 1 a
assert_both "ZADD INCR two pairs" ZADD z:959:incr INCR 1 a 2 b
assert_both "ZADD INCR odd tail" ZADD z:959:incr INCR 1
assert_both "ZADD INCR nan" ZADD z:959:incr INCR nan a
assert_both "ZADD INCR inf" ZADD z:959:incr INCR inf a
assert_both "ZADD INCR inf + -inf" ZADD z:959:incr INCR -inf a
assert_both "ZADD INCR after refusals" ZRANGE z:959:incr 0 -1 WITHSCORES
assert_both "ZADD XX INCR on missing key" ZADD z:959:incr:xx XX INCR 1 a
assert_both "ZADD XX INCR made no key" EXISTS z:959:incr:xx

# Exactly zset-max-listpack-entries (128) members is STILL a listpack; one
# more promotes to a skiplist on both. One ZADD per step, not 129 — each
# `both` spawns two redis-cli processes.
Expand Down Expand Up @@ -1826,6 +1942,11 @@ if [[ "$SHARDS" -gt 1 ]]; then
"zrangestore|ZADD %S 1 a 2 b|ZRANGESTORE %D %S 0 -1|ZCARD %D"
"zunionstore|ZADD %S 1 a 2 b|ZUNIONSTORE %D 1 %S|ZCARD %D"
"zinterstore|ZADD %S 1 a 2 b|ZINTERSTORE %D 1 %S|ZCARD %D"
# moon#959: ZDIFFSTORE joined the family the moment it stopped being
# `unknown command` -- same shape as its two siblings above, routed on
# the destination and reading every source. Without the guard arm it
# acks :2 and the destination is empty on a normally-routed read.
"zdiffstore|ZADD %S 1 a 2 b|ZDIFFSTORE %D 1 %S|ZCARD %D"
"pfmerge|PFADD %S a b c|PFMERGE %D %S|PFCOUNT %D"
"geosearchstore|GEOADD %S 15 37 Here|GEOSEARCHSTORE %D %S FROMLONLAT 15 37 BYRADIUS 200 km ASC|ZCARD %D"
"sortstore|RPUSH %S 3 1 2|SORT %S STORE %D|LLEN %D"
Expand Down
14 changes: 14 additions & 0 deletions src/command/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@ pub static COMMAND_META: phf::Map<&'static str, CommandMeta> = phf_map! {
"ZMSCORE" => CommandMeta { name: "ZMSCORE", arity: -3, flags: RFP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },
"ZRANDMEMBER" => CommandMeta { name: "ZRANDMEMBER", arity: -2, flags: RP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },
"ZMPOP" => CommandMeta { name: "ZMPOP", arity: -4, flags: WP, first_key: 0, last_key: 0, step: 0, acl_categories: ZST },
// moon#959. Arities match redis 8.6.1 as SENT — every arity error below
// was compared on the wire, not read off `COMMAND INFO`.
"ZRANGEBYLEX" => CommandMeta { name: "ZRANGEBYLEX", arity: -4, flags: RP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },
"ZREVRANGEBYLEX" => CommandMeta { name: "ZREVRANGEBYLEX", arity: -4, flags: RP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },
"ZREMRANGEBYRANK" => CommandMeta { name: "ZREMRANGEBYRANK", arity: 4, flags: WP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },
"ZREMRANGEBYSCORE" => CommandMeta { name: "ZREMRANGEBYSCORE", arity: 4, flags: WP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },
"ZREMRANGEBYLEX" => CommandMeta { name: "ZREMRANGEBYLEX", arity: 4, flags: WP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },
"ZDIFFSTORE" => CommandMeta { name: "ZDIFFSTORE", arity: -4, flags: WP, first_key: 1, last_key: 1, step: 1, acl_categories: ZST },

// ---- Stream commands ----
"XADD" => CommandMeta { name: "XADD", arity: -5, flags: WFP, first_key: 1, last_key: 1, step: 1, acl_categories: STM },
Expand Down Expand Up @@ -1560,6 +1568,10 @@ mod tests {
b"ZINTERSTORE",
b"ZRANGESTORE",
b"ZMPOP",
b"ZREMRANGEBYRANK",
b"ZREMRANGEBYSCORE",
b"ZREMRANGEBYLEX",
b"ZDIFFSTORE",
b"HINCRBYFLOAT",
b"LSET",
b"LREM",
Expand Down Expand Up @@ -1597,6 +1609,8 @@ mod tests {
b"SMEMBERS",
b"SISMEMBER",
b"ZRANGEBYSCORE",
b"ZRANGEBYLEX",
b"ZREVRANGEBYLEX",
b"BITFIELD_RO",
b"SORT_RO",
b"GEORADIUS_RO",
Expand Down
Loading
Loading