This is an automated email from the ASF dual-hosted git repository.

twice pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 6521198f ZRANGE / ZREVRANK add withscore option support (#1607)
6521198f is described below

commit 6521198f9eb46deb2919306181781be5b3a57437
Author: Binbin <[email protected]>
AuthorDate: Sat Jul 29 21:16:28 2023 +0800

    ZRANGE / ZREVRANK add withscore option support (#1607)
    
    Add an option "withscores" to ZRANK and ZREVRANK.
    The syntax are:
    ```
    zrank key member [WITHSCORE]
    zrevrank key member [WITHSCORE]
    ```
    
    With this option, will additionally return the member's score,
    in the form of an array.
    ```
    127.0.0.1:6666> zadd zset 1 a 2 b 3 c
    (integer) 3
    127.0.0.1:6666> zrank zset b withscore
    1) (integer) 1
    2) "2"
    127.0.0.1:6666> zrevrank zset c withscore
    1) (integer) 0
    2) "3"
    ```
    
    This option was added in Redis 7.2
---
 src/commands/cmd_zset.cc                 | 41 ++++++++++++++++++++++++++++----
 src/types/redis_zset.cc                  |  5 +++-
 src/types/redis_zset.h                   |  3 ++-
 tests/cppunit/types/zset_test.cc         | 12 +++++++---
 tests/gocase/unit/type/zset/zset_test.go | 28 +++++++++++++++++++---
 5 files changed, 76 insertions(+), 13 deletions(-)

diff --git a/src/commands/cmd_zset.cc b/src/commands/cmd_zset.cc
index e97ead7a..79c18862 100644
--- a/src/commands/cmd_zset.cc
+++ b/src/commands/cmd_zset.cc
@@ -966,24 +966,55 @@ class CommandZRevRangeByScore : public 
CommandZRangeGeneric {
 class CommandZRank : public Commander {
  public:
   explicit CommandZRank(bool reversed = false) : reversed_(reversed) {}
+
+  Status Parse(const std::vector<std::string> &args) override {
+    if (args.size() > 4) {
+      return {Status::RedisParseErr, errWrongNumOfArguments};
+    }
+
+    // skip the <CMD> <key> <member> and parse remaining optional arguments
+    CommandParser parser(args, 3);
+    while (parser.Good()) {
+      if (parser.EatEqICase("withscore") && !with_score_) {
+        with_score_ = true;
+      } else {
+        return parser.InvalidSyntax();
+      }
+    }
+
+    return Commander::Parse(args);
+  }
+
   Status Execute(Server *svr, Connection *conn, std::string *output) override {
     int rank = 0;
+    double score = 0.0;
     redis::ZSet zset_db(svr->storage, conn->GetNamespace());
-    auto s = zset_db.Rank(args_[1], args_[2], reversed_, &rank);
+    auto s = zset_db.Rank(args_[1], args_[2], reversed_, &rank, &score);
     if (!s.ok()) {
       return {Status::RedisExecErr, s.ToString()};
     }
 
     if (rank == -1) {
-      *output = redis::NilString();
+      if (with_score_) {
+        output->append(redis::MultiLen(-1));
+      } else {
+        *output = redis::NilString();
+      }
     } else {
-      *output = redis::Integer(rank);
+      if (with_score_) {
+        output->append(redis::MultiLen(2));
+        output->append(redis::Integer(rank));
+        output->append(redis::BulkString(util::Float2String(score)));
+      } else {
+        *output = redis::Integer(rank);
+      }
     }
     return Status::OK();
   }
 
  private:
   bool reversed_;
+  bool with_score_ = false;
 };
 
 class CommandZRevRank : public CommandZRank {
@@ -1361,13 +1392,13 @@ 
REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandZAdd>("zadd", -4, "write", 1, 1, 1),
                         MakeCmdAttr<CommandZRangeByLex>("zrangebylex", -4, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandZRevRangeByLex>("zrevrangebylex", 
-4, "read-only", 1, 1, 1),
                         MakeCmdAttr<CommandZRangeByScore>("zrangebyscore", -4, 
"read-only", 1, 1, 1),
-                        MakeCmdAttr<CommandZRank>("zrank", 3, "read-only", 1, 
1, 1),
+                        MakeCmdAttr<CommandZRank>("zrank", -3, "read-only", 1, 
1, 1),
                         MakeCmdAttr<CommandZRem>("zrem", -3, "write", 1, 1, 1),
                         MakeCmdAttr<CommandZRemRangeByRank>("zremrangebyrank", 
4, "write", 1, 1, 1),
                         
MakeCmdAttr<CommandZRemRangeByScore>("zremrangebyscore", 4, "write", 1, 1, 1),
                         MakeCmdAttr<CommandZRemRangeByLex>("zremrangebylex", 
4, "write", 1, 1, 1),
                         
MakeCmdAttr<CommandZRevRangeByScore>("zrevrangebyscore", -4, "read-only", 1, 1, 
1),
-                        MakeCmdAttr<CommandZRevRank>("zrevrank", 3, 
"read-only", 1, 1, 1),
+                        MakeCmdAttr<CommandZRevRank>("zrevrank", -3, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandZScore>("zscore", 3, "read-only", 
1, 1, 1),
                         MakeCmdAttr<CommandZMScore>("zmscore", -3, 
"read-only", 1, 1, 1),
                         MakeCmdAttr<CommandZScan>("zscan", -3, "read-only", 1, 
1, 1),
diff --git a/src/types/redis_zset.cc b/src/types/redis_zset.cc
index bc0515ef..e7a1585d 100644
--- a/src/types/redis_zset.cc
+++ b/src/types/redis_zset.cc
@@ -566,8 +566,10 @@ rocksdb::Status ZSet::Remove(const Slice &user_key, const 
std::vector<Slice> &me
   return storage_->Write(storage_->DefaultWriteOptions(), 
batch->GetWriteBatch());
 }
 
-rocksdb::Status ZSet::Rank(const Slice &user_key, const Slice &member, bool 
reversed, int *member_rank) {
+rocksdb::Status ZSet::Rank(const Slice &user_key, const Slice &member, bool 
reversed, int *member_rank,
+                           double *member_score) {
   *member_rank = -1;
+  *member_score = 0.0;
 
   std::string ns_key;
   AppendNamespacePrefix(user_key, &ns_key);
@@ -613,6 +615,7 @@ rocksdb::Status ZSet::Rank(const Slice &user_key, const 
Slice &member, bool reve
   }
 
   *member_rank = rank;
+  *member_score = target_score;
   return rocksdb::Status::OK();
 }
 
diff --git a/src/types/redis_zset.h b/src/types/redis_zset.h
index 8722b351..1ea0b192 100644
--- a/src/types/redis_zset.h
+++ b/src/types/redis_zset.h
@@ -98,7 +98,8 @@ class ZSet : public SubKeyScanner {
   rocksdb::Status Add(const Slice &user_key, ZAddFlags flags, MemberScores 
*mscores, uint64_t *added_cnt);
   rocksdb::Status Card(const Slice &user_key, uint64_t *size);
   rocksdb::Status IncrBy(const Slice &user_key, const Slice &member, double 
increment, double *score);
-  rocksdb::Status Rank(const Slice &user_key, const Slice &member, bool 
reversed, int *member_rank);
+  rocksdb::Status Rank(const Slice &user_key, const Slice &member, bool 
reversed, int *member_rank,
+                       double *member_score);
   rocksdb::Status Remove(const Slice &user_key, const std::vector<Slice> 
&members, uint64_t *removed_cnt);
   rocksdb::Status Pop(const Slice &user_key, int count, bool min, MemberScores 
*mscores);
   rocksdb::Status Score(const Slice &user_key, const Slice &member, double 
*score);
diff --git a/tests/cppunit/types/zset_test.cc b/tests/cppunit/types/zset_test.cc
index f7e6e27a..1db9aec6 100644
--- a/tests/cppunit/types/zset_test.cc
+++ b/tests/cppunit/types/zset_test.cc
@@ -382,19 +382,25 @@ TEST_F(RedisZSetTest, Rank) {
 
   for (size_t i = 0; i < fields_.size(); i++) {
     int rank = 0;
-    zset_->Rank(key_, fields_[i], false, &rank);
+    double score = 0.0;
+    zset_->Rank(key_, fields_[i], false, &rank, &score);
     EXPECT_EQ(i, rank);
+    EXPECT_EQ(scores_[i], score);
   }
   for (size_t i = 0; i < fields_.size(); i++) {
     int rank = 0;
-    zset_->Rank(key_, fields_[i], true, &rank);
+    double score = 0.0;
+    zset_->Rank(key_, fields_[i], true, &rank, &score);
     EXPECT_EQ(i, static_cast<int>(fields_.size() - rank - 1));
+    EXPECT_EQ(scores_[i], score);
   }
   std::vector<std::string> no_exist_members = {"a", "b"};
   for (const auto &member : no_exist_members) {
     int rank = 0;
-    zset_->Rank(key_, member, true, &rank);
+    double score = 0.0;
+    zset_->Rank(key_, member, true, &rank, &score);
     EXPECT_EQ(-1, rank);
+    EXPECT_EQ(0.0, score);
   }
   zset_->Del(key_);
 }
diff --git a/tests/gocase/unit/type/zset/zset_test.go 
b/tests/gocase/unit/type/zset/zset_test.go
index 1d741364..d5822221 100644
--- a/tests/gocase/unit/type/zset/zset_test.go
+++ b/tests/gocase/unit/type/zset/zset_test.go
@@ -674,20 +674,42 @@ func basicTests(t *testing.T, rdb *redis.Client, ctx 
context.Context, encoding s
                rdb.ZAdd(ctx, "zranktmp", redis.Z{Score: 10, Member: "x"})
                rdb.ZAdd(ctx, "zranktmp", redis.Z{Score: 20, Member: "y"})
                rdb.ZAdd(ctx, "zranktmp", redis.Z{Score: 30, Member: "z"})
+
                require.Equal(t, int64(0), rdb.ZRank(ctx, "zranktmp", 
"x").Val())
                require.Equal(t, int64(1), rdb.ZRank(ctx, "zranktmp", 
"y").Val())
                require.Equal(t, int64(2), rdb.ZRank(ctx, "zranktmp", 
"z").Val())
-               require.Equal(t, int64(0), rdb.ZRank(ctx, "zranktmp", 
"foo").Val())
+               require.Equal(t, redis.Nil, rdb.ZRank(ctx, "zranktmp", 
"foo").Err())
                require.Equal(t, int64(2), rdb.ZRevRank(ctx, "zranktmp", 
"x").Val())
                require.Equal(t, int64(1), rdb.ZRevRank(ctx, "zranktmp", 
"y").Val())
                require.Equal(t, int64(0), rdb.ZRevRank(ctx, "zranktmp", 
"z").Val())
-               require.Equal(t, int64(0), rdb.ZRevRank(ctx, "zranktmp", 
"foo").Val())
+               require.Equal(t, redis.Nil, rdb.ZRevRank(ctx, "zranktmp", 
"foo").Err())
+
+               require.Equal(t, []interface{}{int64(0), "10"}, rdb.Do(ctx, 
"zrank", "zranktmp", "x", "withscore").Val())
+               require.Equal(t, []interface{}{int64(1), "20"}, rdb.Do(ctx, 
"zrank", "zranktmp", "y", "withscore").Val())
+               require.Equal(t, []interface{}{int64(2), "30"}, rdb.Do(ctx, 
"zrank", "zranktmp", "z", "withscore").Val())
+               require.Equal(t, redis.Nil, rdb.Do(ctx, "zrank", "zranktmp", 
"foo", "withscore").Err())
+               require.Equal(t, []interface{}{int64(2), "10"}, rdb.Do(ctx, 
"zrevrank", "zranktmp", "x", "withscore").Val())
+               require.Equal(t, []interface{}{int64(1), "20"}, rdb.Do(ctx, 
"zrevrank", "zranktmp", "y", "withscore").Val())
+               require.Equal(t, []interface{}{int64(0), "30"}, rdb.Do(ctx, 
"zrevrank", "zranktmp", "z", "withscore").Val())
+               require.Equal(t, redis.Nil, rdb.Do(ctx, "zrevrank", "zranktmp", 
"foo", "withscore").Err())
        })
 
-       t.Run(fmt.Sprintf("ZRANK - after deletion -%s", encoding), func(t 
*testing.T) {
+       t.Run(fmt.Sprintf("ZRANK/ZREVRANK - after deletion -%s", encoding), 
func(t *testing.T) {
                rdb.ZRem(ctx, "zranktmp", "y")
+
                require.Equal(t, int64(0), rdb.ZRank(ctx, "zranktmp", 
"x").Val())
                require.Equal(t, int64(1), rdb.ZRank(ctx, "zranktmp", 
"z").Val())
+               require.Equal(t, redis.Nil, rdb.ZRank(ctx, "zranktmp", 
"foo").Err())
+               require.Equal(t, int64(1), rdb.ZRevRank(ctx, "zranktmp", 
"x").Val())
+               require.Equal(t, int64(0), rdb.ZRevRank(ctx, "zranktmp", 
"z").Val())
+               require.Equal(t, redis.Nil, rdb.ZRevRank(ctx, "zranktmp", 
"foo").Err())
+
+               require.Equal(t, []interface{}{int64(0), "10"}, rdb.Do(ctx, 
"zrank", "zranktmp", "x", "withscore").Val())
+               require.Equal(t, []interface{}{int64(1), "30"}, rdb.Do(ctx, 
"zrank", "zranktmp", "z", "withscore").Val())
+               require.Equal(t, redis.Nil, rdb.Do(ctx, "zrank", "zranktmp", 
"foo", "withscore").Err())
+               require.Equal(t, []interface{}{int64(1), "10"}, rdb.Do(ctx, 
"zrevrank", "zranktmp", "x", "withscore").Val())
+               require.Equal(t, []interface{}{int64(0), "30"}, rdb.Do(ctx, 
"zrevrank", "zranktmp", "z", "withscore").Val())
+               require.Equal(t, redis.Nil, rdb.Do(ctx, "zrevrank", "zranktmp", 
"foo", "withscore").Err())
        })
 
        t.Run(fmt.Sprintf("ZINCRBY - can create a new sorted set - %s", 
encoding), func(t *testing.T) {

Reply via email to