Files
redis/tests/assets/test_cli_hint_suite.txt
41ae52c706 Optimistic locking for string objects - compare-and-set and compare-and-delete (#14435)
# Description

Add optimistic locking for string objects via compare-and-set and
compare-and-delete mechanism.

## What's changed

Introduction of new DIGEST command for string objects calculated via
XXH3 hash.

Extend SET command with new parameters supporting optimistic locking.
The new value is set only if checks against a given (old) value or a
given string digest pass.

Introduction of new DELEX command to support conditionally deleting a
key. Conditions are also checks against string value or string digest.

## Motivation

For developers who need to to implement a compare-and-set and
compare-and-delete single-key optimistic concurrency control this PR
provides single-command based implementation.

Compare-and-set and compare-and-delete are mostly used for [Optimistic
concurrency
control](https://en.wikipedia.org/wiki/Optimistic_concurrency_control):
a client (1) fetches the value, keeps the old value (or its digest, for
a large string) in memory, (2) manipulates a local copy of the value,
(3) applies the local changes to the server, but only if the server’s
value hasn’t been changed (still equal to the old value).

Note that compare-and-set [can also be
implemented](https://redis.io/docs/latest/develop/using-commands/transactions/#optimistic-locking-using-check-and-set)
with WATCH … MULTI … EXEC and Lua scripts. The new SET optional
arguments and the DELEX command do not enable new functionality,
however, they are much simpler and faster to use for the very common use
case of single-key optimistic concurrency control.

## Related issues and PRs

https://github.com/redis/redis/issues/12485
https://github.com/redis/redis/pull/8361
https://github.com/redis/redis/pull/4258

## Description of the new commands

### DIGEST

```
DIGEST key
```

Get the hash digest of the value stored in key, as an hex string.

Reply:
- Null if key does not exist
- error if key exists but holds a value which is not a string
- (bulk string) the XXH3 digest of the value stored in key, as an hex
string

### SET

```
SET key value [NX | XX | IFEQ match-value | IFNE match-value | IFDEQ match-digest | IFDNE match-digest] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]
```

`IFEQ match-value` - Set the key’s value and expiration only if its
current value is equal to match-value. If key doesn’t exist - it won’t
be created.
`IFNE match-value` - Set the key’s value and expiration only if its
current value is not equal to match-value. If key doesn’t exist - it
will be created.
`IFDEQ match-digest` - Set the key’s value and expiration only if the
digest of its current value is equal to match-digest. If key doesn’t
exist - it won’t be created.
`IFDNE match-digest` - Set the key’s value and expiration only if the
digest of its current value is not equal to match-digest. If key doesn’t
exist - it will be created.

Reply update:
- If GET was not specified:
   - Nil reply if either
- the key doesn’t exist and XX/IFEQ/IFDEQ was specified. The key was not
created.
- the key exists, and NX was specified or a specified
IFEQ/IFNE/IFDEQ/IFDNE condition is false. The key was not set.
   - Simple string reply: OK: The key was set.
- If GET was specified, any of the following:
- Nil reply: The key didn't exist before this command (whether the key
was created or not).
- Bulk string reply: The previous value of the key (whether the key was
set or not).

### DELEX

```
DELEX key [IFEQ match-value | IFNE match-value | IFDEQ match-digest | IFDNE match-digest]
```

Conditionally removes the specified key. A key is ignored if it does not
exist.

`IFEQ match-value` - Delete the key only if its value is equal to
match-value
`IFNE match-value` - Delete the key only if its value is not equal to
match-value
`IFDEQ match-digest` - Delete the key only if the digest of its value is
equal to match-digest
`IFDNE match-digest` - Delete the key only if the digest of its value is
not equal to match-digest

Reply: 
- error if key exists but holds a value that is not a string and
IFEQ/IFNE/IFDEQ/IFDNE is specified.
- (integer) 0 if not deleted (the key does not exist or a specified
IFEQ/IFNE/IFDEQ/IFDNE condition is false), or 1 if deleted.

### Notes

Added copy of xxhash repo to deps -
[version](https://github.com/Cyan4973/xxHash/commit/c961fbe61ad1ee1e430b9c304735a0534fda1c6d)

---------

Co-authored-by: debing.sun <[email protected]>
Co-authored-by: Yuan Wang <[email protected]>
2025-10-21 10:32:49 +03:00

112 lines
7.1 KiB
Plaintext

# Test suite for redis-cli command-line hinting mechanism.
# Each test case consists of two strings: a (partial) input command line, and the expected hint string.
# Command with one arg: GET key
"GET " "key"
"GET abc " ""
# Command with two args: DECRBY key decrement
"DECRBY xyz 2 " ""
"DECRBY xyz " "decrement"
"DECRBY " "key decrement"
# Command with optional arg: LPOP key [count]
"LPOP key " "[count]"
"LPOP key 3 " ""
# Command with optional token arg: XRANGE key start end [COUNT count]
"XRANGE " "key start end [COUNT count]"
"XRANGE k 4 2 " "[COUNT count]"
"XRANGE k 4 2 COU" "[COUNT count]"
"XRANGE k 4 2 COUNT" "[COUNT count]"
"XRANGE k 4 2 COUNT " "count"
# Command with optional token block arg: BITFIELD_RO key [GET encoding offset [GET encoding offset ...]]
"BITFIELD_RO k " "[GET encoding offset [GET encoding offset ...]]"
"BITFIELD_RO k GE" "[GET encoding offset [GET encoding offset ...]]"
"BITFIELD_RO k GET" "[GET encoding offset [GET encoding offset ...]]"
# TODO: The following hints end with an unbalanced "]" which shouldn't be there.
"BITFIELD_RO k GET " "encoding offset [GET encoding offset ...]]"
"BITFIELD_RO k GET xyz " "offset [GET encoding offset ...]]"
"BITFIELD_RO k GET xyz 12 " "[GET encoding offset ...]]"
"BITFIELD_RO k GET xyz 12 GET " "encoding offset [GET encoding offset ...]]"
"BITFIELD_RO k GET enc1 12 GET enc2 " "offset [GET encoding offset ...]]"
"BITFIELD_RO k GET enc1 12 GET enc2 34 " "[GET encoding offset ...]]"
# Two-word command with multiple non-token block args: CONFIG SET parameter value [parameter value ...]
"CONFIG SET param " "value [parameter value ...]"
"CONFIG SET param val " "[parameter value ...]"
"CONFIG SET param val parm2 val2 " "[parameter value ...]"
# Command with nested optional args: ZRANDMEMBER key [count [WITHSCORES]]
"ZRANDMEMBER k " "[count [WITHSCORES]]"
"ZRANDMEMBER k 3 " "[WITHSCORES]"
"ZRANDMEMBER k 3 WI" "[WITHSCORES]"
"ZRANDMEMBER k 3 WITHSCORES " ""
# Wrong data type: count must be an integer. Hinting fails.
"ZRANDMEMBER k cnt " ""
# Command ends with repeated arg: MGET key [key ...]
"MGET " "key [key ...]"
"MGET k " "[key ...]"
"MGET k k " "[key ...]"
# Optional args can be in any order: SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
"SCAN 2 MATCH " "pattern [COUNT count] [TYPE type]"
"SCAN 2 COUNT " "count [MATCH pattern] [TYPE type]"
# One-of choices: BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout
"BLMOVE src dst LEFT " "LEFT|RIGHT timeout"
# Optional args can be in any order: ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
"ZRANGE k 1 2 " "[BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]"
"ZRANGE k 1 2 bylex " "[REV] [LIMIT offset count] [WITHSCORES]"
"ZRANGE k 1 2 bylex rev " "[LIMIT offset count] [WITHSCORES]"
"ZRANGE k 1 2 limit 2 4 " "[BYSCORE|BYLEX] [REV] [WITHSCORES]"
"ZRANGE k 1 2 bylex rev limit 2 4 WITHSCORES " ""
"ZRANGE k 1 2 rev " "[BYSCORE|BYLEX] [LIMIT offset count] [WITHSCORES]"
"ZRANGE k 1 2 WITHSCORES " "[BYSCORE|BYLEX] [REV] [LIMIT offset count]"
# Optional one-of args with parameters: SET key value [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]
"SET key value " "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
"SET key value EX" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
"SET key value EX " "seconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
"SET key value EX 23 " "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
"SET key value EXAT" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
"SET key value EXAT " "unix-time-seconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
"SET key value PX" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
"SET key value PX " "milliseconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
"SET key value PXAT" "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
"SET key value PXAT " "unix-time-milliseconds [NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
"SET key value KEEPTTL " "[NX|XX|IFEQ ifeq-value|IFNE ifne-value|IFDEQ ifdeq-digest|IFDNE ifdne-digest] [GET]"
"SET key value XX " "[GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"
# If an input word can't be matched, stop hinting.
"SET key value FOOBAR " ""
# Incorrect type for EX 'seconds' parameter - stop hinting.
"SET key value EX sec " ""
# Reordering partially-matched optional argument: GEORADIUS key longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key|STOREDIST key]
"GEORADIUS key " "longitude latitude radius M|KM|FT|MI [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key|STOREDIST key]"
"GEORADIUS key 1 2 3 M " "[WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key|STOREDIST key]"
"GEORADIUS key 1 2 3 M COUNT " "count [ANY] [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [STORE key|STOREDIST key]"
"GEORADIUS key 1 2 3 M COUNT 12 " "[ANY] [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [STORE key|STOREDIST key]"
"GEORADIUS key 1 2 3 M COUNT 12 " "[ANY] [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [STORE key|STOREDIST key]"
"GEORADIUS key 1 -2.345 3 M COUNT 12 " "[ANY] [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [STORE key|STOREDIST key]"" ""
# Wrong data type: latitude must be a double. Hinting fails.
"GEORADIUS key 1 X " ""
# Once the next optional argument is started, the [ANY] hint completing the COUNT argument disappears.
"GEORADIUS key 1 2 3 M COUNT 12 ASC " "[WITHCOORD] [WITHDIST] [WITHHASH] [STORE key|STOREDIST key]"
# Incorrect argument type for double-valued token parameter.
"GEOSEARCH k FROMLONLAT " "longitude latitude BYRADIUS radius M|KM|FT|MI|BYBOX width height M|KM|FT|MI [ASC|DESC] [COUNT count [ANY]] [WITHCOORD] [WITHDIST] [WITHHASH]"
"GEOSEARCH k FROMLONLAT 2.34 4.45 BYRADIUS badvalue " ""
# Optional parameters followed by mandatory params: ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]
"ZADD key " "[NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]"
"ZADD key CH LT " "[NX|XX] [INCR] score member [score member ...]"
"ZADD key 0 " "member [score member ...]"
# Empty-valued token argument represented as a pair of double-quotes.
"MIGRATE " "host port key|\"\" destination-db timeout [COPY] [REPLACE] [AUTH password|AUTH2 username password] [KEYS key [key ...]]"