diff options
author | C Charles <chentianjie.ctj@alibaba-inc.com> | 2022-11-28 17:57:11 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-28 11:57:11 +0200 |
commit | eeca7f29117a5e376da3e6f1a419fdf5a6e78e8b (patch) | |
tree | 0aaebf1dc9b63aa5c8ead129c99565ed0b37728e | |
parent | 376b689b03e22889b62e60013d7622cbf98d3dc7 (diff) | |
download | redis-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.c | 16 | ||||
-rw-r--r-- | src/commands/zrank.json | 16 | ||||
-rw-r--r-- | src/commands/zrevrank.json | 14 | ||||
-rw-r--r-- | src/server.h | 2 | ||||
-rw-r--r-- | src/t_zset.c | 46 | ||||
-rw-r--r-- | tests/unit/type/zset.tcl | 20 |
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" { |