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

hulk 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 eb94fbbe Support TYPE flag in the SCAN command (#2255)
eb94fbbe is described below

commit eb94fbbe53504a7a0e2d80df33a5e4235cc34cb0
Author: lizhenglei <[email protected]>
AuthorDate: Sun Apr 21 09:12:44 2024 +0800

    Support TYPE flag in the SCAN command (#2255)
---
 src/commands/cmd_server.cc          |  2 +-
 src/commands/scan_base.h            |  9 ++++-
 src/storage/redis_db.cc             |  4 ++-
 src/storage/redis_db.h              |  3 +-
 tests/gocase/unit/scan/scan_test.go | 67 +++++++++++++++++++++++++++++++++++++
 5 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/src/commands/cmd_server.cc b/src/commands/cmd_server.cc
index 442908ff..28c31603 100644
--- a/src/commands/cmd_server.cc
+++ b/src/commands/cmd_server.cc
@@ -840,7 +840,7 @@ class CommandScan : public CommandScanBase {
 
     std::vector<std::string> keys;
     std::string end_key;
-    auto s = redis_db.Scan(key_name, limit_, prefix_, &keys, &end_key);
+    auto s = redis_db.Scan(key_name, limit_, prefix_, &keys, &end_key, type_);
     if (!s.ok()) {
       return {Status::RedisExecErr, s.ToString()};
     }
diff --git a/src/commands/scan_base.h b/src/commands/scan_base.h
index da3c81ac..3a6438c2 100644
--- a/src/commands/scan_base.h
+++ b/src/commands/scan_base.h
@@ -56,7 +56,13 @@ class CommandScanBase : public Commander {
           return {Status::RedisParseErr, "limit should be a positive integer"};
         }
       } else if (IsScan && parser.EatEqICase("type")) {
-        return {Status::RedisParseErr, "TYPE flag is currently not supported"};
+        std::string type_str = GET_OR_RET(parser.TakeStr());
+        if (auto iter = std::find(RedisTypeNames.begin(), 
RedisTypeNames.end(), type_str);
+            iter != RedisTypeNames.end()) {
+          type_ = static_cast<RedisType>(iter - RedisTypeNames.begin());
+        } else {
+          return {Status::RedisExecErr, "Invalid type"};
+        }
       } else {
         return parser.InvalidSyntax();
       }
@@ -93,6 +99,7 @@ class CommandScanBase : public Commander {
   std::string cursor_;
   std::string prefix_;
   int limit_ = 20;
+  RedisType type_ = kRedisNone;
 };
 
 class CommandSubkeyScanBase : public CommandScanBase {
diff --git a/src/storage/redis_db.cc b/src/storage/redis_db.cc
index fab6562b..a0e06615 100644
--- a/src/storage/redis_db.cc
+++ b/src/storage/redis_db.cc
@@ -310,7 +310,7 @@ rocksdb::Status Database::Keys(const std::string &prefix, 
std::vector<std::strin
 }
 
 rocksdb::Status Database::Scan(const std::string &cursor, uint64_t limit, 
const std::string &prefix,
-                               std::vector<std::string> *keys, std::string 
*end_cursor) {
+                               std::vector<std::string> *keys, std::string 
*end_cursor, RedisType type) {
   end_cursor->clear();
   uint64_t cnt = 0;
   uint16_t slot_start = 0;
@@ -355,6 +355,8 @@ rocksdb::Status Database::Scan(const std::string &cursor, 
uint64_t limit, const
       auto s = metadata.Decode(iter->value());
       if (!s.ok()) continue;
 
+      if (type != kRedisNone && type != metadata.Type()) continue;
+
       if (metadata.Expired()) continue;
       std::tie(std::ignore, user_key) = 
ExtractNamespaceKey<std::string>(iter->key(), storage_->IsSlotIdEncoded());
       keys->emplace_back(user_key);
diff --git a/src/storage/redis_db.h b/src/storage/redis_db.h
index 31de41dc..73a5a654 100644
--- a/src/storage/redis_db.h
+++ b/src/storage/redis_db.h
@@ -93,7 +93,8 @@ class Database {
   [[nodiscard]] rocksdb::Status Keys(const std::string &prefix, 
std::vector<std::string> *keys = nullptr,
                                      KeyNumStats *stats = nullptr);
   [[nodiscard]] rocksdb::Status Scan(const std::string &cursor, uint64_t 
limit, const std::string &prefix,
-                                     std::vector<std::string> *keys, 
std::string *end_cursor = nullptr);
+                                     std::vector<std::string> *keys, 
std::string *end_cursor = nullptr,
+                                     RedisType type = kRedisNone);
   [[nodiscard]] rocksdb::Status RandomKey(const std::string &cursor, 
std::string *key);
   std::string AppendNamespacePrefix(const Slice &user_key);
   [[nodiscard]] rocksdb::Status FindKeyRangeWithPrefix(const std::string 
&prefix, const std::string &prefix_end,
diff --git a/tests/gocase/unit/scan/scan_test.go 
b/tests/gocase/unit/scan/scan_test.go
index 81a2ff64..62b91aff 100644
--- a/tests/gocase/unit/scan/scan_test.go
+++ b/tests/gocase/unit/scan/scan_test.go
@@ -301,6 +301,73 @@ func ScanTest(t *testing.T, rdb *redis.Client, ctx 
context.Context) {
                util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "count", "1", 
"match", "a*", "hello").Err(), ".*syntax error.*")
                util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "count", "1", 
"match", "a*", "hello", "hi").Err(), ".*syntax error.*")
        })
+
+       t.Run("SCAN with type args ", func(t *testing.T) {
+               //string type
+               require.NoError(t, rdb.Set(ctx, "stringtype1", "fee1", 0).Err())
+               require.NoError(t, rdb.Set(ctx, "stringtype2", "fee1", 0).Err())
+               require.NoError(t, rdb.Set(ctx, "stringtype3", "fee1", 0).Err())
+               require.Equal(t, []string{"stringtype1", "stringtype2", 
"stringtype3"}, scanAll(t, rdb, "match", "stringtype*", "type", "string"))
+               require.Equal(t, []string{"stringtype1", "stringtype2", 
"stringtype3"}, scanAll(t, rdb, "match", "stringtype*", "count", "3", "type", 
"string"))
+               //hash type
+               require.NoError(t, rdb.HSet(ctx, "hashtype1", "key1", "val1", 
"key2", "val2").Err())
+               require.NoError(t, rdb.HSet(ctx, "hashtype2", "key1", "val1", 
"key2", "val2").Err())
+               require.NoError(t, rdb.HSet(ctx, "hashtype3", "key1", "val1", 
"key2", "val2").Err())
+               require.Equal(t, []string{"hashtype1", "hashtype2", 
"hashtype3"}, scanAll(t, rdb, "match", "hashtype*", "type", "hash"))
+               require.Equal(t, []string{"hashtype1", "hashtype2", 
"hashtype3"}, scanAll(t, rdb, "match", "hashtype*", "count", "3", "type", 
"hash"))
+               //list type
+               require.NoError(t, rdb.RPush(ctx, "listtype1", "1").Err())
+               require.NoError(t, rdb.RPush(ctx, "listtype2", "2").Err())
+               require.NoError(t, rdb.RPush(ctx, "listtype3", "3").Err())
+               require.Equal(t, []string{"listtype1", "listtype2", 
"listtype3"}, scanAll(t, rdb, "match", "listtype*", "type", "list"))
+               require.Equal(t, []string{"listtype1", "listtype2", 
"listtype3"}, scanAll(t, rdb, "match", "listtype*", "count", "3", "type", 
"list"))
+               //set type
+               require.NoError(t, rdb.SAdd(ctx, "settype1", "1").Err())
+               require.NoError(t, rdb.SAdd(ctx, "settype2", "1").Err())
+               require.NoError(t, rdb.SAdd(ctx, "settype3", "1").Err())
+               require.Equal(t, []string{"settype1", "settype2", "settype3"}, 
scanAll(t, rdb, "match", "settype*", "type", "set"))
+               require.Equal(t, []string{"settype1", "settype2", "settype3"}, 
scanAll(t, rdb, "match", "settype*", "count", "3", "type", "set"))
+               //zet type
+               members := []redis.Z{
+                       {Score: 1, Member: "1"},
+                       {Score: 2, Member: "2"},
+                       {Score: 3, Member: "3"},
+                       {Score: 10, Member: "4"},
+               }
+               require.NoError(t, rdb.ZAdd(ctx, "zsettype1", members...).Err())
+               require.NoError(t, rdb.ZAdd(ctx, "zsettype2", members...).Err())
+               require.NoError(t, rdb.ZAdd(ctx, "zsettype3", members...).Err())
+               require.Equal(t, []string{"zsettype1", "zsettype2", 
"zsettype3"}, scanAll(t, rdb, "match", "zsettype*", "type", "zset"))
+               require.Equal(t, []string{"zsettype1", "zsettype2", 
"zsettype3"}, scanAll(t, rdb, "match", "zsettype*", "count", "3", "type", 
"zset"))
+               //bitmap type
+               require.NoError(t, rdb.SetBit(ctx, "bitmaptype1", 0, 0).Err())
+               require.NoError(t, rdb.SetBit(ctx, "bitmaptype2", 0, 0).Err())
+               require.NoError(t, rdb.SetBit(ctx, "bitmaptype3", 0, 0).Err())
+               require.Equal(t, []string{"bitmaptype1", "bitmaptype2", 
"bitmaptype3"}, scanAll(t, rdb, "match", "bitmaptype*", "type", "bitmap"))
+               require.Equal(t, []string{"bitmaptype1", "bitmaptype2", 
"bitmaptype3"}, scanAll(t, rdb, "match", "bitmaptype*", "count", "3", "type", 
"bitmap"))
+               //stream type
+               require.NoError(t, rdb.XAdd(ctx, &redis.XAddArgs{Stream: 
"streamtype1", Values: []string{"item", "1", "value", "a"}}).Err())
+               require.NoError(t, rdb.XAdd(ctx, &redis.XAddArgs{Stream: 
"streamtype2", Values: []string{"item", "1", "value", "a"}}).Err())
+               require.NoError(t, rdb.XAdd(ctx, &redis.XAddArgs{Stream: 
"streamtype3", Values: []string{"item", "1", "value", "a"}}).Err())
+               require.Equal(t, []string{"streamtype1", "streamtype2", 
"streamtype3"}, scanAll(t, rdb, "match", "streamtype*", "type", "stream"))
+               require.Equal(t, []string{"streamtype1", "streamtype2", 
"streamtype3"}, scanAll(t, rdb, "match", "streamtype*", "count", "3", "type", 
"stream"))
+               //MBbloom type
+               require.NoError(t, rdb.Do(ctx, "bf.reserve", "MBbloomtype1", 
"0.02", "1000").Err())
+               require.NoError(t, rdb.Do(ctx, "bf.reserve", "MBbloomtype2", 
"0.02", "1000").Err())
+               require.NoError(t, rdb.Do(ctx, "bf.reserve", "MBbloomtype3", 
"0.02", "1000").Err())
+               require.Equal(t, []string{"MBbloomtype1", "MBbloomtype2", 
"MBbloomtype3"}, scanAll(t, rdb, "match", "MBbloomtype*", "type", "MBbloom--"))
+               require.Equal(t, []string{"MBbloomtype1", "MBbloomtype2", 
"MBbloomtype3"}, scanAll(t, rdb, "match", "MBbloomtype*", "count", "3", "type", 
"MBbloom--"))
+               //ReJSON-RL type
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "ReJSONtype1", "$", 
` {"x":1, "y":2} `).Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "ReJSONtype2", "$", 
` {"x":1, "y":2} `).Err())
+               require.NoError(t, rdb.Do(ctx, "JSON.SET", "ReJSONtype3", "$", 
` {"x":1, "y":2} `).Err())
+               require.Equal(t, []string{"ReJSONtype1", "ReJSONtype2", 
"ReJSONtype3"}, scanAll(t, rdb, "match", "ReJSONtype*", "type", "ReJSON-RL"))
+               require.Equal(t, []string{"ReJSONtype1", "ReJSONtype2", 
"ReJSONtype3"}, scanAll(t, rdb, "match", "ReJSONtype*", "count", "3", "type", 
"ReJSON-RL"))
+               //invalid type
+               util.ErrorRegexp(t, rdb.Do(ctx, "SCAN", "0", "count", "1", 
"match", "a*", "type", "hi").Err(), "Invalid type")
+
+       })
+
 }
 
 // SCAN of Kvrocks returns _cursor instead of cursor. Thus, redis.Client Scan 
can fail with

Reply via email to