summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorC Charles <chentianjie.ctj@alibaba-inc.com>2022-11-28 17:57:11 +0800
committerGitHub <noreply@github.com>2022-11-28 11:57:11 +0200
commiteeca7f29117a5e376da3e6f1a419fdf5a6e78e8b (patch)
tree0aaebf1dc9b63aa5c8ead129c99565ed0b37728e
parent376b689b03e22889b62e60013d7622cbf98d3dc7 (diff)
downloadredis-eeca7f29117a5e376da3e6f1a419fdf5a6e78e8b.tar.gz
Add withscore option to ZRANK and ZREVRANK. (#11235)
Add an option "withscores" to ZRANK and ZREVRANK. Add `[withscore]` option to both `zrank` and `zrevrank`, like this: ``` z[rev]rank key member [withscore] ```
-rw-r--r--src/commands.c16
-rw-r--r--src/commands/zrank.json16
-rw-r--r--src/commands/zrevrank.json14
-rw-r--r--src/server.h2
-rw-r--r--src/t_zset.c46
-rw-r--r--tests/unit/type/zset.tcl20
6 files changed, 96 insertions, 18 deletions
diff --git a/src/commands.c b/src/commands.c
index 8277e6018..71e3e5abf 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -5902,7 +5902,10 @@ struct redisCommandArg ZRANGESTORE_Args[] = {
/********** ZRANK ********************/
/* ZRANK history */
-#define ZRANK_History NULL
+commandHistory ZRANK_History[] = {
+{"7.2.0","Added the optional `WITHSCORE` argument."},
+{0}
+};
/* ZRANK tips */
#define ZRANK_tips NULL
@@ -5911,6 +5914,7 @@ struct redisCommandArg ZRANGESTORE_Args[] = {
struct redisCommandArg ZRANK_Args[] = {
{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE},
{"member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
+{"withscore",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORE",NULL,NULL,CMD_ARG_OPTIONAL},
{0}
};
@@ -6052,7 +6056,10 @@ struct redisCommandArg ZREVRANGEBYSCORE_Args[] = {
/********** ZREVRANK ********************/
/* ZREVRANK history */
-#define ZREVRANK_History NULL
+commandHistory ZREVRANK_History[] = {
+{"7.2.0","Added the optional `WITHSCORE` argument."},
+{0}
+};
/* ZREVRANK tips */
#define ZREVRANK_tips NULL
@@ -6061,6 +6068,7 @@ struct redisCommandArg ZREVRANGEBYSCORE_Args[] = {
struct redisCommandArg ZREVRANK_Args[] = {
{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE},
{"member",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
+{"withscore",ARG_TYPE_PURE_TOKEN,-1,"WITHSCORE",NULL,NULL,CMD_ARG_OPTIONAL},
{0}
};
@@ -7357,7 +7365,7 @@ struct redisCommand redisCommandTable[] = {
{"zrangebylex","Return a range of members in a sorted set, by lexicographical range","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.8.9",CMD_DOC_DEPRECATED,"`ZRANGE` with the `BYLEX` argument","6.2.0",COMMAND_GROUP_SORTED_SET,ZRANGEBYLEX_History,ZRANGEBYLEX_tips,zrangebylexCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZRANGEBYLEX_Args},
{"zrangebyscore","Return a range of members in a sorted set, by score","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","1.0.5",CMD_DOC_DEPRECATED,"`ZRANGE` with the `BYSCORE` argument","6.2.0",COMMAND_GROUP_SORTED_SET,ZRANGEBYSCORE_History,ZRANGEBYSCORE_tips,zrangebyscoreCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZRANGEBYSCORE_Args},
{"zrangestore","Store a range of members from sorted set into another key","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements stored into the destination key.","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZRANGESTORE_History,ZRANGESTORE_tips,zrangestoreCommand,-5,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZRANGESTORE_Args},
-{"zrank","Determine the index of a member in a sorted set","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZRANK_History,ZRANK_tips,zrankCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZRANK_Args},
+{"zrank","Determine the index of a member in a sorted set","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZRANK_History,ZRANK_tips,zrankCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZRANK_Args},
{"zrem","Remove one or more members from a sorted set","O(M*log(N)) with N being the number of elements in the sorted set and M the number of elements to be removed.","1.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZREM_History,ZREM_tips,zremCommand,-3,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREM_Args},
{"zremrangebylex","Remove all members in a sorted set between the given lexicographical range","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.","2.8.9",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZREMRANGEBYLEX_History,ZREMRANGEBYLEX_tips,zremrangebylexCommand,4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREMRANGEBYLEX_Args},
{"zremrangebyrank","Remove all members in a sorted set within the given indexes","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements removed by the operation.","2.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZREMRANGEBYRANK_History,ZREMRANGEBYRANK_tips,zremrangebyrankCommand,4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREMRANGEBYRANK_Args},
@@ -7365,7 +7373,7 @@ struct redisCommand redisCommandTable[] = {
{"zrevrange","Return a range of members in a sorted set, by index, with scores ordered from high to low","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements returned.","1.2.0",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` argument","6.2.0",COMMAND_GROUP_SORTED_SET,ZREVRANGE_History,ZREVRANGE_tips,zrevrangeCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREVRANGE_Args},
{"zrevrangebylex","Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.8.9",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` and `BYLEX` arguments","6.2.0",COMMAND_GROUP_SORTED_SET,ZREVRANGEBYLEX_History,ZREVRANGEBYLEX_tips,zrevrangebylexCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREVRANGEBYLEX_Args},
{"zrevrangebyscore","Return a range of members in a sorted set, by score, with scores ordered from high to low","O(log(N)+M) with N being the number of elements in the sorted set and M the number of elements being returned. If M is constant (e.g. always asking for the first 10 elements with LIMIT), you can consider it O(log(N)).","2.2.0",CMD_DOC_DEPRECATED,"`ZRANGE` with the `REV` and `BYSCORE` arguments","6.2.0",COMMAND_GROUP_SORTED_SET,ZREVRANGEBYSCORE_History,ZREVRANGEBYSCORE_tips,zrevrangebyscoreCommand,-4,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREVRANGEBYSCORE_Args},
-{"zrevrank","Determine the index of a member in a sorted set, with scores ordered from high to low","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZREVRANK_History,ZREVRANK_tips,zrevrankCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREVRANK_Args},
+{"zrevrank","Determine the index of a member in a sorted set, with scores ordered from high to low","O(log(N))","2.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZREVRANK_History,ZREVRANK_tips,zrevrankCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZREVRANK_Args},
{"zscan","Incrementally iterate sorted sets elements and associated scores","O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.","2.8.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZSCAN_History,ZSCAN_tips,zscanCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZSCAN_Args},
{"zscore","Get the score associated with the given member in a sorted set","O(1)","1.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZSCORE_History,ZSCORE_tips,zscoreCommand,3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZSCORE_Args},
{"zunion","Add multiple sorted sets","O(N)+O(M*log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set.","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZUNION_History,ZUNION_tips,zunionCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},zunionInterDiffGetKeys,.args=ZUNION_Args},
diff --git a/src/commands/zrank.json b/src/commands/zrank.json
index 6d5154715..a15d96c59 100644
--- a/src/commands/zrank.json
+++ b/src/commands/zrank.json
@@ -4,8 +4,14 @@
"complexity": "O(log(N))",
"group": "sorted_set",
"since": "2.0.0",
- "arity": 3,
+ "arity": -3,
"function": "zrankCommand",
+ "history": [
+ [
+ "7.2.0",
+ "Added the optional `WITHSCORE` argument."
+ ]
+ ],
"command_flags": [
"READONLY",
"FAST"
@@ -42,7 +48,13 @@
{
"name": "member",
"type": "string"
+ },
+ {
+ "name": "withscore",
+ "token": "WITHSCORE",
+ "type": "pure-token",
+ "optional": true
}
]
}
-}
+} \ No newline at end of file
diff --git a/src/commands/zrevrank.json b/src/commands/zrevrank.json
index bcd487695..7d5aa795b 100644
--- a/src/commands/zrevrank.json
+++ b/src/commands/zrevrank.json
@@ -4,8 +4,14 @@
"complexity": "O(log(N))",
"group": "sorted_set",
"since": "2.0.0",
- "arity": 3,
+ "arity": -3,
"function": "zrevrankCommand",
+ "history": [
+ [
+ "7.2.0",
+ "Added the optional `WITHSCORE` argument."
+ ]
+ ],
"command_flags": [
"READONLY",
"FAST"
@@ -42,6 +48,12 @@
{
"name": "member",
"type": "string"
+ },
+ {
+ "name": "withscore",
+ "token": "WITHSCORE",
+ "type": "pure-token",
+ "optional": true
}
]
}
diff --git a/src/server.h b/src/server.h
index 2ce5dcb11..ce47d232f 100644
--- a/src/server.h
+++ b/src/server.h
@@ -2903,7 +2903,7 @@ void zsetConvertToListpackIfNeeded(robj *zobj, size_t maxelelen, size_t totelele
int zsetScore(robj *zobj, sds member, double *score);
unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore);
-long zsetRank(robj *zobj, sds ele, int reverse);
+long zsetRank(robj *zobj, sds ele, int reverse, double *score);
int zsetDel(robj *zobj, sds ele);
robj *zsetDup(robj *o);
void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey, long count, int use_nested_array, int reply_nil_when_empty, int *deleted);
diff --git a/src/t_zset.c b/src/t_zset.c
index ed1830175..590652073 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -1511,7 +1511,7 @@ int zsetDel(robj *zobj, sds ele) {
* the one with the lowest score. Otherwise if 'reverse' is non-zero
* the rank is computed considering as element with rank 0 the one with
* the highest score. */
-long zsetRank(robj *zobj, sds ele, int reverse) {
+long zsetRank(robj *zobj, sds ele, int reverse, double *output_score) {
unsigned long llen;
unsigned long rank;
@@ -1535,6 +1535,8 @@ long zsetRank(robj *zobj, sds ele, int reverse) {
}
if (eptr != NULL) {
+ if (output_score)
+ *output_score = zzlGetScore(sptr);
if (reverse)
return llen-rank;
else
@@ -1554,6 +1556,8 @@ long zsetRank(robj *zobj, sds ele, int reverse) {
rank = zslGetRank(zsl,score,ele);
/* Existing elements always have a rank. */
serverAssert(rank != 0);
+ if (output_score)
+ *output_score = score;
if (reverse)
return llen-rank;
else
@@ -3773,17 +3777,43 @@ void zrankGenericCommand(client *c, int reverse) {
robj *key = c->argv[1];
robj *ele = c->argv[2];
robj *zobj;
+ robj* reply;
long rank;
+ int opt_withscore = 0;
+ double score;
- if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
- checkType(c,zobj,OBJ_ZSET)) return;
-
- serverAssertWithInfo(c,ele,sdsEncodedObject(ele));
- rank = zsetRank(zobj,ele->ptr,reverse);
+ if (c->argc > 4) {
+ addReplyErrorArity(c);
+ return;
+ }
+ if (c->argc > 3) {
+ if (!strcasecmp(c->argv[3]->ptr, "withscore")) {
+ opt_withscore = 1;
+ } else {
+ addReplyErrorObject(c, shared.syntaxerr);
+ return;
+ }
+ }
+ reply = opt_withscore ? shared.nullarray[c->resp] : shared.null[c->resp];
+ if ((zobj = lookupKeyReadOrReply(c, key, reply)) == NULL || checkType(c, zobj, OBJ_ZSET)) {
+ return;
+ }
+ serverAssertWithInfo(c, ele, sdsEncodedObject(ele));
+ rank = zsetRank(zobj, ele->ptr, reverse, opt_withscore ? &score : NULL);
if (rank >= 0) {
- addReplyLongLong(c,rank);
+ if (opt_withscore) {
+ addReplyArrayLen(c, 2);
+ }
+ addReplyLongLong(c, rank);
+ if (opt_withscore) {
+ addReplyDouble(c, score);
+ }
} else {
- addReplyNull(c);
+ if (opt_withscore) {
+ addReplyNullArray(c);
+ } else {
+ addReplyNull(c);
+ }
}
}
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 66de682db..73f835e10 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -438,17 +438,33 @@ start_server {tags {"zset"}} {
assert_equal 0 [r zrank zranktmp x]
assert_equal 1 [r zrank zranktmp y]
assert_equal 2 [r zrank zranktmp z]
- assert_equal "" [r zrank zranktmp foo]
assert_equal 2 [r zrevrank zranktmp x]
assert_equal 1 [r zrevrank zranktmp y]
assert_equal 0 [r zrevrank zranktmp z]
- assert_equal "" [r zrevrank zranktmp foo]
+ r readraw 1
+ assert_equal {$-1} [r zrank zranktmp foo]
+ assert_equal {$-1} [r zrevrank zranktmp foo]
+ r readraw 0
+
+ # withscores
+ assert_equal {0 10} [r zrank zranktmp x withscore]
+ assert_equal {1 20} [r zrank zranktmp y withscore]
+ assert_equal {2 30} [r zrank zranktmp z withscore]
+ assert_equal {2 10} [r zrevrank zranktmp x withscore]
+ assert_equal {1 20} [r zrevrank zranktmp y withscore]
+ assert_equal {0 30} [r zrevrank zranktmp z withscore]
+ r readraw 1
+ assert_equal {*-1} [r zrank zranktmp foo withscore]
+ assert_equal {*-1} [r zrevrank zranktmp foo withscore]
+ r readraw 0
}
test "ZRANK - after deletion - $encoding" {
r zrem zranktmp y
assert_equal 0 [r zrank zranktmp x]
assert_equal 1 [r zrank zranktmp z]
+ assert_equal {0 10} [r zrank zranktmp x withscore]
+ assert_equal {1 30} [r zrank zranktmp z withscore]
}
test "ZINCRBY - can create a new sorted set - $encoding" {