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 c791511c Add the support of LMPOP command (#1737)
c791511c is described below

commit c791511c77c0e2bf659150dccfd76eefb6d64415
Author: HolyLow <[email protected]>
AuthorDate: Wed Sep 6 22:44:30 2023 +0800

    Add the support of LMPOP command (#1737)
    
    Co-authored-by: mwish <[email protected]>
    Co-authored-by: Binbin <[email protected]>
---
 src/commands/cmd_list.cc                 |  79 ++++++++++++++
 tests/gocase/unit/type/list/list_test.go | 177 +++++++++++++++++++++++++++++++
 2 files changed, 256 insertions(+)

diff --git a/src/commands/cmd_list.cc b/src/commands/cmd_list.cc
index 1ad581a6..8ccf382c 100644
--- a/src/commands/cmd_list.cc
+++ b/src/commands/cmd_list.cc
@@ -154,6 +154,84 @@ class CommandRPop : public CommandPop {
   CommandRPop() : CommandPop(false) {}
 };
 
+class CommandLMPop : public Commander {
+ public:
+  // format: LMPOP #numkeys key0 [key1 ...] <LEFT | RIGHT> [COUNT count]
+  Status Parse(const std::vector<std::string> &args) override {
+    CommandParser parser(args, 1);
+
+    auto num_keys = GET_OR_RET(parser.TakeInt<uint32_t>());
+    keys_.clear();
+    keys_.reserve(num_keys);
+    for (uint32_t i = 0; i < num_keys; ++i) {
+      keys_.emplace_back(GET_OR_RET(parser.TakeStr()));
+    }
+
+    auto left_or_right = util::ToLower(GET_OR_RET(parser.TakeStr()));
+    if (left_or_right == "left") {
+      left_ = true;
+    } else if (left_or_right == "right") {
+      left_ = false;
+    } else {
+      return {Status::RedisParseErr, errInvalidSyntax};
+    }
+
+    while (parser.Good()) {
+      if (parser.EatEqICase("count") && count_ == -1) {
+        count_ = GET_OR_RET(parser.TakeInt<uint32_t>());
+      } else {
+        return parser.InvalidSyntax();
+      }
+    }
+    if (count_ == -1) {
+      count_ = 1;
+    }
+
+    return Status::OK();
+  }
+
+  Status Execute(Server *svr, Connection *conn, std::string *output) override {
+    redis::List list_db(svr->storage, conn->GetNamespace());
+
+    std::vector<std::string> elems;
+    std::string chosen_key;
+    for (auto &key : keys_) {
+      auto s = list_db.PopMulti(key, left_, count_, &elems);
+      if (!s.ok() && !s.IsNotFound()) {
+        return {Status::RedisExecErr, s.ToString()};
+      }
+      if (!elems.empty()) {
+        chosen_key = key;
+        break;
+      }
+    }
+
+    if (elems.empty()) {
+      *output = redis::NilString();
+    } else {
+      std::string elems_bulk = redis::MultiBulkString(elems);
+      *output = redis::Array({redis::BulkString(chosen_key), 
std::move(elems_bulk)});
+    }
+
+    return Status::OK();
+  }
+
+  static const inline CommandKeyRangeGen keyRangeGen = [](const 
std::vector<std::string> &args) {
+    CommandKeyRange range;
+    range.first_key = 2;
+    range.key_step = 1;
+    // This parsing would always succeed as this cmd has been parsed before.
+    auto num_key = *ParseInt<int32_t>(args[1], 10);
+    range.last_key = range.first_key + num_key - 1;
+    return range;
+  };
+
+ private:
+  bool left_;
+  uint32_t count_ = -1;
+  std::vector<std::string> keys_;
+};
+
 class CommandBPop : public Commander,
                     private EvbufCallbackBase<CommandBPop, false>,
                     private EventCallbackBase<CommandBPop> {
@@ -766,6 +844,7 @@ REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandBLPop>("blpop", 
-3, "write no-script"
                         MakeCmdAttr<CommandLRem>("lrem", 4, "write", 1, 1, 1),
                         MakeCmdAttr<CommandLSet>("lset", 4, "write", 1, 1, 1),
                         MakeCmdAttr<CommandLTrim>("ltrim", 4, "write", 1, 1, 
1),
+                        MakeCmdAttr<CommandLMPop>("lmpop", -4, "write", 
CommandLMPop::keyRangeGen),
                         MakeCmdAttr<CommandRPop>("rpop", -2, "write", 1, 1, 1),
                         MakeCmdAttr<CommandRPopLPUSH>("rpoplpush", 3, "write", 
1, 2, 1),
                         MakeCmdAttr<CommandRPush>("rpush", -3, "write", 1, 1, 
1),
diff --git a/tests/gocase/unit/type/list/list_test.go 
b/tests/gocase/unit/type/list/list_test.go
index b7db17e7..0522e783 100644
--- a/tests/gocase/unit/type/list/list_test.go
+++ b/tests/gocase/unit/type/list/list_test.go
@@ -1000,4 +1000,181 @@ func TestList(t *testing.T) {
                        require.Empty(t, rdb.LPosCount(ctx, "mylist", "b", 10, 
redis.LPosArgs{Rank: 5}).Val())
                })
        }
+
+       for _, direction := range []string{"LEFT", "RIGHT"} {
+               key1 := "lmpop-list1"
+               key2 := "lmpop-list2"
+               rdb.Del(ctx, key1, key2)
+               require.EqualValues(t, 5, rdb.LPush(ctx, key1, "one", "two", 
"three", "four", "five").Val())
+               require.EqualValues(t, 5, rdb.LPush(ctx, key2, "ONE", "TWO", 
"THREE", "FOUR", "FIVE").Val())
+
+               t.Run(fmt.Sprintf("LMPOP test oneKey countSingle %s", 
direction), func(t *testing.T) {
+                       result := rdb.LMPop(ctx, direction, 1, key1)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key1, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"five"}, resultVal)
+                       } else {
+                               require.Equal(t, []string{"one"}, resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test oneKey countMulti %s", 
direction), func(t *testing.T) {
+                       result := rdb.LMPop(ctx, direction, 2, key1)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key1, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"four", "three"}, 
resultVal)
+                       } else {
+                               require.Equal(t, []string{"two", "three"}, 
resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test oneKey countTooMuch %s", 
direction), func(t *testing.T) {
+                       result := rdb.LMPop(ctx, direction, 10, key1)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key1, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"two", "one"}, 
resultVal)
+                       } else {
+                               require.Equal(t, []string{"four", "five"}, 
resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test oneKey empty %s", direction), 
func(t *testing.T) {
+                       require.EqualError(t, rdb.LMPop(ctx, direction, 1, 
key1).Err(), redis.Nil.Error())
+               })
+
+               require.EqualValues(t, 2, rdb.LPush(ctx, key1, "six", 
"seven").Val())
+               t.Run(fmt.Sprintf("LMPOP test firstKey countOver %s", 
direction), func(t *testing.T) {
+                       result := rdb.LMPop(ctx, direction, 10, key1, key2)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key1, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"seven", "six"}, 
resultVal)
+                       } else {
+                               require.Equal(t, []string{"six", "seven"}, 
resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test secondKey countSingle %s", 
direction), func(t *testing.T) {
+                       result := rdb.LMPop(ctx, direction, 1, key1, key2)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key2, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"FIVE"}, resultVal)
+                       } else {
+                               require.Equal(t, []string{"ONE"}, resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test secondKey countMulti %s", 
direction), func(t *testing.T) {
+                       result := rdb.LMPop(ctx, direction, 2, key1, key2)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key2, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"FOUR", "THREE"}, 
resultVal)
+                       } else {
+                               require.Equal(t, []string{"TWO", "THREE"}, 
resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test secondKey countOver %s", 
direction), func(t *testing.T) {
+                       result := rdb.LMPop(ctx, direction, 10, key1, key2)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key2, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"TWO", "ONE"}, 
resultVal)
+                       } else {
+                               require.Equal(t, []string{"FOUR", "FIVE"}, 
resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test bothKey empty %s", direction), 
func(t *testing.T) {
+                       require.EqualError(t, rdb.LMPop(ctx, direction, 1, 
key1, key2).Err(), redis.Nil.Error())
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test dummyKey empty %s", direction), 
func(t *testing.T) {
+                       require.EqualError(t, rdb.LMPop(ctx, direction, 1, 
"dummy1", "dummy2").Err(), redis.Nil.Error())
+               })
+
+               lmpopNoCount := func(c *redis.Client, ctx context.Context, 
direction string, keys ...string) *redis.KeyValuesCmd {
+                       args := make([]interface{}, 2+len(keys), 5+len(keys))
+                       args[0] = "lmpop"
+                       args[1] = len(keys)
+                       for i, key := range keys {
+                               args[2+i] = key
+                       }
+                       args = append(args, strings.ToLower(direction))
+                       cmd := redis.NewKeyValuesCmd(ctx, args...)
+                       _ = c.Process(ctx, cmd)
+                       return cmd
+               }
+               rdb.Del(ctx, key1, key2)
+               require.EqualValues(t, 2, rdb.LPush(ctx, key1, "one", 
"two").Val())
+               require.EqualValues(t, 2, rdb.LPush(ctx, key2, "ONE", 
"TWO").Val())
+
+               t.Run(fmt.Sprintf("LMPOP test oneKey noCount one %s", 
direction), func(t *testing.T) {
+                       result := lmpopNoCount(rdb, ctx, direction, key1)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key1, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"two"}, resultVal)
+                       } else {
+                               require.Equal(t, []string{"one"}, resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test firstKey noCount one %s", 
direction), func(t *testing.T) {
+                       result := lmpopNoCount(rdb, ctx, direction, key1, key2)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key1, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"one"}, resultVal)
+                       } else {
+                               require.Equal(t, []string{"two"}, resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test oneKey noCount empty %s", 
direction), func(t *testing.T) {
+                       require.EqualError(t, lmpopNoCount(rdb, ctx, direction, 
key1).Err(), redis.Nil.Error())
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test secondKey noCount one %s", 
direction), func(t *testing.T) {
+                       result := lmpopNoCount(rdb, ctx, direction, key1, key2)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key2, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"TWO"}, resultVal)
+                       } else {
+                               require.Equal(t, []string{"ONE"}, resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test secondKey noCount one %s", 
direction), func(t *testing.T) {
+                       result := lmpopNoCount(rdb, ctx, direction, key1, key2)
+                       resultKey, resultVal := result.Val()
+                       require.NoError(t, result.Err())
+                       require.EqualValues(t, key2, resultKey)
+                       if direction == "LEFT" {
+                               require.Equal(t, []string{"ONE"}, resultVal)
+                       } else {
+                               require.Equal(t, []string{"TWO"}, resultVal)
+                       }
+               })
+
+               t.Run(fmt.Sprintf("LMPOP test bothKey noCount empty %s", 
direction), func(t *testing.T) {
+                       require.EqualError(t, lmpopNoCount(rdb, ctx, direction, 
key1, key2).Err(), redis.Nil.Error())
+               })
+       }
 }

Reply via email to