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())
+ })
+ }
}