Skip to content

Commit 4733aed

Browse files
youngmore1024murphyjacob4ranshid
authored
Add XX option to ZRANGE commands for null/empty distinction (valkey-io#2978)
Fixes: valkey-io#2977 Add WITHSTATUS option to ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE, ZRANGEBYLEX, ZREVRANGEBYLEX commands to distinguish between: Key does not exist (nullarray response) Key exists but no matching elements (emptyarray response) In RESP3 mode: Key not exists: returns _ (nil) Key exists but empty result: returns *0 (empty array) In RESP2 mode: Key not exists: returns *-1 (nil) Key exists but empty result: returns *0 (empty array) Maintains backward compatibility This addresses the need to differentiate between "key not found" vs "no results found" scenarios in client applications. --------- Signed-off-by: youngmore1024 <youngmore1024@outlook.com> Signed-off-by: Jacob Murphy <jkmurphy@google.com> Co-authored-by: Jacob Murphy <jkmurphy@google.com> Co-authored-by: Ran Shidlansik <ranshid@amazon.com>
1 parent dd30872 commit 4733aed

10 files changed

Lines changed: 356 additions & 24 deletions

File tree

src/commands.def

Lines changed: 24 additions & 9 deletions
Large diffs are not rendered by default.

src/commands/zrange.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
[
1111
"6.2.0",
1212
"Added the `REV`, `BYSCORE`, `BYLEX` and `LIMIT` options."
13+
],
14+
[
15+
"9.2.0",
16+
"Added the `XX` option."
1317
]
1418
],
1519
"command_flags": [
@@ -133,6 +137,13 @@
133137
"token": "WITHSCORES",
134138
"type": "pure-token",
135139
"optional": true
140+
},
141+
{
142+
"name": "xx",
143+
"token": "XX",
144+
"type": "pure-token",
145+
"optional": true,
146+
"since": "9.2.0"
136147
}
137148
]
138149
}

src/commands/zrangebylex.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"since": "2.8.9",
77
"arity": -4,
88
"function": "zrangebylexCommand",
9+
"history": [
10+
[
11+
"9.2.0",
12+
"Added the `XX` option."
13+
]
14+
],
915
"command_flags": [
1016
"READONLY"
1117
],
@@ -71,6 +77,13 @@
7177
"type": "integer"
7278
}
7379
]
80+
},
81+
{
82+
"name": "xx",
83+
"token": "XX",
84+
"type": "pure-token",
85+
"optional": true,
86+
"since": "9.2.0"
7487
}
7588
]
7689
}

src/commands/zrangebyscore.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
[
1111
"2.0.0",
1212
"Added the `WITHSCORES` modifier."
13+
],
14+
[
15+
"9.2.0",
16+
"Added the `XX` option."
1317
]
1418
],
1519
"command_flags": [
@@ -95,6 +99,13 @@
9599
"optional": true,
96100
"since": "2.0.0"
97101
},
102+
{
103+
"name": "xx",
104+
"token": "XX",
105+
"type": "pure-token",
106+
"optional": true,
107+
"since": "9.2.0"
108+
},
98109
{
99110
"token": "LIMIT",
100111
"name": "limit",

src/commands/zrevrange.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"since": "1.2.0",
77
"arity": -4,
88
"function": "zrevrangeCommand",
9+
"history": [
10+
[
11+
"9.2.0",
12+
"Added the `XX` option."
13+
]
14+
],
915
"command_flags": [
1016
"READONLY"
1117
],
@@ -85,6 +91,13 @@
8591
"token": "WITHSCORES",
8692
"type": "pure-token",
8793
"optional": true
94+
},
95+
{
96+
"name": "xx",
97+
"token": "XX",
98+
"type": "pure-token",
99+
"optional": true,
100+
"since": "9.2.0"
88101
}
89102
]
90103
}

src/commands/zrevrangebylex.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"since": "2.8.9",
77
"arity": -4,
88
"function": "zrevrangebylexCommand",
9+
"history": [
10+
[
11+
"9.2.0",
12+
"Added the `XX` option."
13+
]
14+
],
915
"command_flags": [
1016
"READONLY"
1117
],
@@ -71,6 +77,13 @@
7177
"type": "integer"
7278
}
7379
]
80+
},
81+
{
82+
"name": "xx",
83+
"token": "XX",
84+
"type": "pure-token",
85+
"optional": true,
86+
"since": "9.2.0"
7487
}
7588
]
7689
}

src/commands/zrevrangebyscore.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
[
1111
"2.1.6",
1212
"`min` and `max` can be exclusive."
13+
],
14+
[
15+
"9.2.0",
16+
"Added the `XX` option."
1317
]
1418
],
1519
"command_flags": [
@@ -94,6 +98,13 @@
9498
"type": "pure-token",
9599
"optional": true
96100
},
101+
{
102+
"name": "xx",
103+
"token": "XX",
104+
"type": "pure-token",
105+
"optional": true,
106+
"since": "9.2.0"
107+
},
97108
{
98109
"token": "LIMIT",
99110
"name": "limit",

src/t_zset.c

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3199,14 +3199,14 @@ void zrangestoreCommand(client *c) {
31993199
zrangeGenericCommand(&handler, 2, 1, ZRANGE_AUTO, ZRANGE_DIRECTION_AUTO);
32003200
}
32013201

3202-
/* ZRANGE <key> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [LIMIT offset count] */
3202+
/* ZRANGE <key> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [XX] [LIMIT offset count] */
32033203
void zrangeCommand(client *c) {
32043204
zrange_result_handler handler;
32053205
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
32063206
zrangeGenericCommand(&handler, 1, 0, ZRANGE_AUTO, ZRANGE_DIRECTION_AUTO);
32073207
}
32083208

3209-
/* ZREVRANGE <key> <start> <stop> [WITHSCORES] */
3209+
/* ZREVRANGE <key> <start> <stop> [WITHSCORES] [XX] */
32103210
void zrevrangeCommand(client *c) {
32113211
zrange_result_handler handler;
32123212
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
@@ -3320,14 +3320,14 @@ void genericZrangebyscoreCommand(zrange_result_handler *handler,
33203320
handler->finalizeResultEmission(handler, rangelen);
33213321
}
33223322

3323-
/* ZRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count] */
3323+
/* ZRANGEBYSCORE <key> <min> <max> [WITHSCORES] [XX] [LIMIT offset count] */
33243324
void zrangebyscoreCommand(client *c) {
33253325
zrange_result_handler handler;
33263326
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
33273327
zrangeGenericCommand(&handler, 1, 0, ZRANGE_SCORE, ZRANGE_DIRECTION_FORWARD);
33283328
}
33293329

3330-
/* ZREVRANGEBYSCORE <key> <max> <min> [WITHSCORES] [LIMIT offset count] */
3330+
/* ZREVRANGEBYSCORE <key> <max> <min> [WITHSCORES] [XX] [LIMIT offset count] */
33313331
void zrevrangebyscoreCommand(client *c) {
33323332
zrange_result_handler handler;
33333333
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
@@ -3589,14 +3589,14 @@ void genericZrangebylexCommand(zrange_result_handler *handler,
35893589
handler->finalizeResultEmission(handler, rangelen);
35903590
}
35913591

3592-
/* ZRANGEBYLEX <key> <min> <max> [LIMIT offset count] */
3592+
/* ZRANGEBYLEX <key> <min> <max> [LIMIT offset count] [XX] */
35933593
void zrangebylexCommand(client *c) {
35943594
zrange_result_handler handler;
35953595
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
35963596
zrangeGenericCommand(&handler, 1, 0, ZRANGE_LEX, ZRANGE_DIRECTION_FORWARD);
35973597
}
35983598

3599-
/* ZREVRANGEBYLEX <key> <max> <min> [LIMIT offset count] */
3599+
/* ZREVRANGEBYLEX <key> <max> <min> [LIMIT offset count] [XX] */
36003600
void zrevrangebylexCommand(client *c) {
36013601
zrange_result_handler handler;
36023602
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
@@ -3611,7 +3611,9 @@ void zrevrangebylexCommand(client *c) {
36113611
* other command pass explicit value.
36123612
*
36133613
* The argc_start points to the src key argument, so following syntax is like:
3614-
* <src> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [LIMIT offset count]
3614+
* <src> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [XX] [LIMIT offset count]
3615+
*
3616+
* Note: XX is not supported by ZRANGESTORE.
36153617
*/
36163618
void zrangeGenericCommand(zrange_result_handler *handler,
36173619
int argc_start,
@@ -3630,6 +3632,7 @@ void zrangeGenericCommand(zrange_result_handler *handler,
36303632
long opt_start = 0;
36313633
long opt_end = 0;
36323634
int opt_withscores = 0;
3635+
int opt_keyexist = 0;
36333636
long opt_offset = 0;
36343637
long opt_limit = -1;
36353638

@@ -3638,6 +3641,8 @@ void zrangeGenericCommand(zrange_result_handler *handler,
36383641
int leftargs = c->argc - j - 1;
36393642
if (!store && !strcasecmp(objectGetVal(c->argv[j]), "withscores")) {
36403643
opt_withscores = 1;
3644+
} else if (!store && !strcasecmp(objectGetVal(c->argv[j]), "xx")) {
3645+
opt_keyexist = 1;
36413646
} else if (!strcasecmp(objectGetVal(c->argv[j]), "limit") && leftargs >= 2) {
36423647
if ((getLongFromObjectOrReply(c, c->argv[j + 1], &opt_offset, NULL) != C_OK) ||
36433648
(getLongFromObjectOrReply(c, c->argv[j + 2], &opt_limit, NULL) != C_OK)) {
@@ -3715,6 +3720,8 @@ void zrangeGenericCommand(zrange_result_handler *handler,
37153720
if (store) {
37163721
handler->beginResultEmission(handler, -1);
37173722
handler->finalizeResultEmission(handler, 0);
3723+
} else if (opt_keyexist) {
3724+
addReplyNullArray(c);
37183725
} else {
37193726
addReply(c, shared.emptyarray);
37203727
}

tests/assets/test_cli_hint_suite.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@
5858
# One-of choices: BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout
5959
"BLMOVE src dst LEFT " "LEFT|RIGHT timeout"
6060

61-
# Optional args can be in any order: ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
62-
"ZRANGE k 1 2 " "[BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]"
63-
"ZRANGE k 1 2 bylex " "[REV] [LIMIT offset count] [WITHSCORES]"
64-
"ZRANGE k 1 2 bylex rev " "[LIMIT offset count] [WITHSCORES]"
65-
"ZRANGE k 1 2 limit 2 4 " "[BYSCORE|BYLEX] [REV] [WITHSCORES]"
66-
"ZRANGE k 1 2 bylex rev limit 2 4 WITHSCORES " ""
67-
"ZRANGE k 1 2 rev " "[BYSCORE|BYLEX] [LIMIT offset count] [WITHSCORES]"
68-
"ZRANGE k 1 2 WITHSCORES " "[BYSCORE|BYLEX] [REV] [LIMIT offset count]"
61+
# Optional args can be in any order: ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] [XX]
62+
"ZRANGE k 1 2 " "[BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] [XX]"
63+
"ZRANGE k 1 2 bylex " "[REV] [LIMIT offset count] [WITHSCORES] [XX]"
64+
"ZRANGE k 1 2 bylex rev " "[LIMIT offset count] [WITHSCORES] [XX]"
65+
"ZRANGE k 1 2 limit 2 4 " "[BYSCORE|BYLEX] [REV] [WITHSCORES] [XX]"
66+
"ZRANGE k 1 2 bylex rev limit 2 4 WITHSCORES XX " ""
67+
"ZRANGE k 1 2 rev " "[BYSCORE|BYLEX] [LIMIT offset count] [WITHSCORES] [XX]"
68+
"ZRANGE k 1 2 WITHSCORES " "[BYSCORE|BYLEX] [REV] [LIMIT offset count] [XX]"
6969

7070
# Optional one-of args with parameters: SET key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]
7171
"SET key value " "[NX|XX|IFEQ comparison-value] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]"

0 commit comments

Comments
 (0)