mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-10 18:05:44 +02:00
fix: LMPOP implementation was added (#4504)
This commit is contained in:
parent
44506ad2a9
commit
efb7861cee
3 changed files with 376 additions and 0 deletions
|
@ -69,6 +69,7 @@ using namespace std;
|
||||||
using namespace facade;
|
using namespace facade;
|
||||||
using absl::GetFlag;
|
using absl::GetFlag;
|
||||||
using time_point = Transaction::time_point;
|
using time_point = Transaction::time_point;
|
||||||
|
using absl::SimpleAtoi;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -1173,8 +1174,159 @@ void BPopGeneric(ListDir dir, CmdArgList args, Transaction* tx, SinkReplyBuilder
|
||||||
return rb->SendNullArray();
|
return rb->SendNullArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LMPopParams {
|
||||||
|
uint32_t num_keys;
|
||||||
|
ListDir dir;
|
||||||
|
int pop_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
facade::ErrorReply ParseLMPop(CmdArgList args, LMPopParams* params) {
|
||||||
|
CmdArgParser parser{args};
|
||||||
|
|
||||||
|
if (!SimpleAtoi(parser.Next(), ¶ms->num_keys)) {
|
||||||
|
return facade::ErrorReply(kUintErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params->num_keys <= 0 || !parser.HasAtLeast(params->num_keys + 1)) {
|
||||||
|
return facade::ErrorReply(kSyntaxErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.Skip(params->num_keys);
|
||||||
|
|
||||||
|
if (parser.Check("LEFT")) {
|
||||||
|
params->dir = ListDir::LEFT;
|
||||||
|
} else if (parser.Check("RIGHT")) {
|
||||||
|
params->dir = ListDir::RIGHT;
|
||||||
|
} else {
|
||||||
|
return facade::ErrorReply(kSyntaxErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
params->pop_count = 1;
|
||||||
|
if (parser.HasNext()) {
|
||||||
|
if (!parser.Check("COUNT", ¶ms->pop_count)) {
|
||||||
|
return facade::ErrorReply(kSyntaxErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parser.Finalize()) {
|
||||||
|
return facade::ErrorReply(parser.Error()->MakeReply());
|
||||||
|
}
|
||||||
|
|
||||||
|
return facade::ErrorReply(OpStatus::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the first non-empty key found in the shard arguments along with its type validity.
|
||||||
|
// Returns a pair of (key, is_valid_type) where is_valid_type is true if the key exists
|
||||||
|
// and has the correct type (LIST). If a wrong type is found, returns that key with false.
|
||||||
|
// Returns nullopt if no suitable key is found.
|
||||||
|
optional<pair<string_view, bool>> GetFirstNonEmptyKeyFound(EngineShard* shard, Transaction* t) {
|
||||||
|
ShardArgs keys = t->GetShardArgs(shard->shard_id());
|
||||||
|
DCHECK(!keys.Empty());
|
||||||
|
|
||||||
|
auto& db_slice = t->GetDbSlice(shard->shard_id());
|
||||||
|
optional<pair<string_view, bool>> result;
|
||||||
|
|
||||||
|
for (string_view key : keys) {
|
||||||
|
auto res = db_slice.FindReadOnly(t->GetDbContext(), key, OBJ_LIST);
|
||||||
|
if (res) {
|
||||||
|
result = {key, true};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the key is not found, check if it's a wrong type error
|
||||||
|
if (res.status() == OpStatus::WRONG_TYPE) {
|
||||||
|
result = {key, false};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void ListFamily::LMPop(CmdArgList args, const CommandContext& cmd_cntx) {
|
||||||
|
auto* response_builder = static_cast<RedisReplyBuilder*>(cmd_cntx.rb);
|
||||||
|
|
||||||
|
LMPopParams params;
|
||||||
|
facade::ErrorReply parse_result = ParseLMPop(args, ¶ms);
|
||||||
|
|
||||||
|
if (parse_result.status != OpStatus::OK) {
|
||||||
|
response_builder->SendError(parse_result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a vector to store first found key for each shard
|
||||||
|
vector<optional<pair<string_view, bool>>> found_keys_per_shard(shard_set->size());
|
||||||
|
|
||||||
|
auto cb = [&](Transaction* t, EngineShard* shard) {
|
||||||
|
// Each shard writes results to its own space
|
||||||
|
found_keys_per_shard[shard->shard_id()] = GetFirstNonEmptyKeyFound(shard, t);
|
||||||
|
return OpStatus::OK;
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd_cntx.tx->Execute(std::move(cb), false /* followed by another hop */);
|
||||||
|
|
||||||
|
// Find the first existing key from command arguments
|
||||||
|
optional<string_view> key_to_pop;
|
||||||
|
bool found_wrong_type = false;
|
||||||
|
size_t min_index = numeric_limits<size_t>::max();
|
||||||
|
|
||||||
|
// Iterate over each shard to find the key with the smallest index
|
||||||
|
for (ShardId sid = 0; sid < found_keys_per_shard.size(); ++sid) {
|
||||||
|
if (!found_keys_per_shard[sid])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const auto& [found_key, is_valid_type] = *found_keys_per_shard[sid];
|
||||||
|
ShardArgs shard_args = cmd_cntx.tx->GetShardArgs(sid);
|
||||||
|
|
||||||
|
for (auto it = shard_args.begin(); it != shard_args.end(); ++it) {
|
||||||
|
if (found_key == *it && it.index() < min_index) {
|
||||||
|
min_index = it.index();
|
||||||
|
key_to_pop = found_key;
|
||||||
|
found_wrong_type = !is_valid_type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors and empty cases first
|
||||||
|
if (!key_to_pop || found_wrong_type) {
|
||||||
|
cmd_cntx.tx->Conclude();
|
||||||
|
if (found_wrong_type) {
|
||||||
|
response_builder->SendError(kWrongTypeErr);
|
||||||
|
} else {
|
||||||
|
response_builder->SendNull();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop values from the found key
|
||||||
|
optional<ShardId> key_shard = Shard(*key_to_pop, shard_set->size());
|
||||||
|
OpResult<StringVec> result;
|
||||||
|
|
||||||
|
auto cb_pop = [¶ms, key_shard, &result, key = *key_to_pop](Transaction* t,
|
||||||
|
EngineShard* shard) {
|
||||||
|
if (*key_shard == shard->shard_id()) {
|
||||||
|
result = OpPop(t->GetOpArgs(shard), key, params.dir, params.pop_count, true, false);
|
||||||
|
}
|
||||||
|
return OpStatus::OK;
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd_cntx.tx->Execute(std::move(cb_pop), true);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
response_builder->StartArray(2);
|
||||||
|
response_builder->SendBulkString(*key_to_pop);
|
||||||
|
response_builder->StartArray(result->size());
|
||||||
|
for (const auto& val : *result) {
|
||||||
|
response_builder->SendBulkString(val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response_builder->SendNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ListFamily::LPush(CmdArgList args, const CommandContext& cmd_cntx) {
|
void ListFamily::LPush(CmdArgList args, const CommandContext& cmd_cntx) {
|
||||||
return PushGeneric(ListDir::LEFT, false, std::move(args), cmd_cntx.tx, cmd_cntx.rb);
|
return PushGeneric(ListDir::LEFT, false, std::move(args), cmd_cntx.tx, cmd_cntx.rb);
|
||||||
}
|
}
|
||||||
|
@ -1442,6 +1594,7 @@ namespace acl {
|
||||||
constexpr uint32_t kLPush = WRITE | LIST | FAST;
|
constexpr uint32_t kLPush = WRITE | LIST | FAST;
|
||||||
constexpr uint32_t kLPushX = WRITE | LIST | FAST;
|
constexpr uint32_t kLPushX = WRITE | LIST | FAST;
|
||||||
constexpr uint32_t kLPop = WRITE | LIST | FAST;
|
constexpr uint32_t kLPop = WRITE | LIST | FAST;
|
||||||
|
constexpr uint32_t kLMPop = WRITE | LIST | FAST;
|
||||||
constexpr uint32_t kRPush = WRITE | LIST | FAST;
|
constexpr uint32_t kRPush = WRITE | LIST | FAST;
|
||||||
constexpr uint32_t kRPushX = WRITE | LIST | FAST;
|
constexpr uint32_t kRPushX = WRITE | LIST | FAST;
|
||||||
constexpr uint32_t kRPop = WRITE | LIST | FAST;
|
constexpr uint32_t kRPop = WRITE | LIST | FAST;
|
||||||
|
@ -1467,6 +1620,7 @@ void ListFamily::Register(CommandRegistry* registry) {
|
||||||
<< CI{"LPUSH", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kLPush}.HFUNC(LPush)
|
<< CI{"LPUSH", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kLPush}.HFUNC(LPush)
|
||||||
<< CI{"LPUSHX", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kLPushX}.HFUNC(LPushX)
|
<< CI{"LPUSHX", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kLPushX}.HFUNC(LPushX)
|
||||||
<< CI{"LPOP", CO::WRITE | CO::FAST, -2, 1, 1, acl::kLPop}.HFUNC(LPop)
|
<< CI{"LPOP", CO::WRITE | CO::FAST, -2, 1, 1, acl::kLPop}.HFUNC(LPop)
|
||||||
|
<< CI{"LMPOP", CO::WRITE | CO::SLOW | CO::VARIADIC_KEYS, -4, 2, 2, acl::kLMPop}.HFUNC(LMPop)
|
||||||
<< CI{"RPUSH", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kRPush}.HFUNC(RPush)
|
<< CI{"RPUSH", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kRPush}.HFUNC(RPush)
|
||||||
<< CI{"RPUSHX", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kRPushX}.HFUNC(RPushX)
|
<< CI{"RPUSHX", CO::WRITE | CO::FAST | CO::DENYOOM, -3, 1, 1, acl::kRPushX}.HFUNC(RPushX)
|
||||||
<< CI{"RPOP", CO::WRITE | CO::FAST, -2, 1, 1, acl::kRPop}.HFUNC(RPop)
|
<< CI{"RPOP", CO::WRITE | CO::FAST, -2, 1, 1, acl::kRPop}.HFUNC(RPop)
|
||||||
|
|
|
@ -33,6 +33,7 @@ class ListFamily {
|
||||||
static void RPop(CmdArgList args, const CommandContext& cmd_cntx);
|
static void RPop(CmdArgList args, const CommandContext& cmd_cntx);
|
||||||
static void BLPop(CmdArgList args, const CommandContext& cmd_cntx);
|
static void BLPop(CmdArgList args, const CommandContext& cmd_cntx);
|
||||||
static void BRPop(CmdArgList args, const CommandContext& cmd_cntx);
|
static void BRPop(CmdArgList args, const CommandContext& cmd_cntx);
|
||||||
|
static void LMPop(CmdArgList args, const CommandContext& cmd_cntx);
|
||||||
static void LLen(CmdArgList args, const CommandContext& cmd_cntx);
|
static void LLen(CmdArgList args, const CommandContext& cmd_cntx);
|
||||||
static void LPos(CmdArgList args, const CommandContext& cmd_cntx);
|
static void LPos(CmdArgList args, const CommandContext& cmd_cntx);
|
||||||
static void LIndex(CmdArgList args, const CommandContext& cmd_cntx);
|
static void LIndex(CmdArgList args, const CommandContext& cmd_cntx);
|
||||||
|
|
|
@ -1086,5 +1086,226 @@ TEST_F(ListFamilyTest, ContendExpire) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ListFamilyTest, LMPopInvalidSyntax) {
|
||||||
|
// Not enough arguments
|
||||||
|
auto resp = Run({"lmpop", "1", "a"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("wrong number of arguments"));
|
||||||
|
|
||||||
|
// Zero keys
|
||||||
|
resp = Run({"lmpop", "0", "LEFT", "COUNT", "1"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("syntax error"));
|
||||||
|
|
||||||
|
// Number of keys is not uint
|
||||||
|
resp = Run({"lmpop", "aa", "a", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("value is not an integer or out of range"));
|
||||||
|
|
||||||
|
// Missing LEFT/RIGHT
|
||||||
|
resp = Run({"lmpop", "1", "a", "COUNT", "1"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("syntax error"));
|
||||||
|
|
||||||
|
// Wrong number of keys
|
||||||
|
resp = Run({"lmpop", "1", "a", "b", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("syntax error"));
|
||||||
|
|
||||||
|
// COUNT without number
|
||||||
|
resp = Run({"lmpop", "1", "a", "LEFT", "COUNT"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("syntax error"));
|
||||||
|
|
||||||
|
// COUNT is not uint
|
||||||
|
resp = Run({"lmpop", "1", "a", "LEFT", "COUNT", "boo"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("value is not an integer or out of range"));
|
||||||
|
|
||||||
|
// Too many arguments
|
||||||
|
resp = Run({"lmpop", "1", "c", "LEFT", "COUNT", "2", "foo"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("syntax error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ListFamilyTest, LMPop) {
|
||||||
|
// All lists are empty
|
||||||
|
auto resp = Run({"lmpop", "1", "e", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
|
||||||
|
|
||||||
|
// LEFT operation
|
||||||
|
resp = Run({"lpush", "a", "a1", "a2"});
|
||||||
|
EXPECT_THAT(resp, IntArg(2));
|
||||||
|
|
||||||
|
resp = Run({"lmpop", "1", "a", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("a", RespArray(ElementsAre("a2")))));
|
||||||
|
|
||||||
|
// RIGHT operation
|
||||||
|
resp = Run({"lpush", "b", "b1", "b2"});
|
||||||
|
EXPECT_THAT(resp, IntArg(2));
|
||||||
|
|
||||||
|
resp = Run({"lmpop", "1", "b", "RIGHT"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("b", RespArray(ElementsAre("b1")))));
|
||||||
|
|
||||||
|
// COUNT > 1
|
||||||
|
resp = Run({"lpush", "c", "c1", "c2"});
|
||||||
|
EXPECT_THAT(resp, IntArg(2));
|
||||||
|
|
||||||
|
resp = Run({"lmpop", "1", "c", "RIGHT", "COUNT", "2"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("c", RespArray(ElementsAre("c1", "c2")))));
|
||||||
|
|
||||||
|
resp = Run({"llen", "c"});
|
||||||
|
EXPECT_THAT(resp, IntArg(0));
|
||||||
|
|
||||||
|
// COUNT > number of elements in list
|
||||||
|
resp = Run({"lpush", "d", "d1", "d2"});
|
||||||
|
EXPECT_THAT(resp, IntArg(2));
|
||||||
|
|
||||||
|
resp = Run({"lmpop", "1", "d", "RIGHT", "COUNT", "3"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("d", RespArray(ElementsAre("d1", "d2")))));
|
||||||
|
|
||||||
|
resp = Run({"llen", "d"});
|
||||||
|
EXPECT_THAT(resp, IntArg(0));
|
||||||
|
|
||||||
|
// First non-empty list is not the first list
|
||||||
|
resp = Run({"lpush", "x", "x1"});
|
||||||
|
EXPECT_THAT(resp, IntArg(1));
|
||||||
|
|
||||||
|
resp = Run({"lpush", "y", "y1"});
|
||||||
|
EXPECT_THAT(resp, IntArg(1));
|
||||||
|
|
||||||
|
resp = Run({"lmpop", "3", "empty", "x", "y", "RIGHT"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("x", RespArray(ElementsAre("x1")))));
|
||||||
|
|
||||||
|
resp = Run({"llen", "x"});
|
||||||
|
EXPECT_THAT(resp, IntArg(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ListFamilyTest, LMPopMultipleElements) {
|
||||||
|
// Test removing multiple elements from left end
|
||||||
|
Run({"rpush", "list1", "a", "b", "c", "d", "e"});
|
||||||
|
auto resp = Run({"lmpop", "1", "list1", "LEFT", "COUNT", "3"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("list1", RespArray(ElementsAre("a", "b", "c")))));
|
||||||
|
|
||||||
|
resp = Run({"lrange", "list1", "0", "-1"});
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre("d", "e"));
|
||||||
|
|
||||||
|
// Test removing multiple elements from right end
|
||||||
|
Run({"rpush", "list2", "v", "w", "x", "y", "z"});
|
||||||
|
resp = Run({"lmpop", "1", "list2", "RIGHT", "COUNT", "2"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("list2", RespArray(ElementsAre("z", "y")))));
|
||||||
|
|
||||||
|
resp = Run({"lrange", "list2", "0", "-1"});
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre("v", "w", "x"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ListFamilyTest, LMPopMultipleLists) {
|
||||||
|
// Test finding first non-empty list
|
||||||
|
Run({"rpush", "list1", "a", "b"});
|
||||||
|
Run({"rpush", "list2", "c", "d"});
|
||||||
|
Run({"rpush", "list3", "e", "f"});
|
||||||
|
|
||||||
|
// Pop from first non-empty list
|
||||||
|
auto resp = Run({"lmpop", "3", "list1", "list2", "list3", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("list1", RespArray(ElementsAre("a")))));
|
||||||
|
|
||||||
|
// Pop from second list after first becomes empty
|
||||||
|
Run({"lmpop", "1", "list1", "LEFT"}); // Empty list1
|
||||||
|
resp = Run({"lmpop", "3", "list1", "list2", "list3", "RIGHT", "COUNT", "2"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("list2", RespArray(ElementsAre("d", "c")))));
|
||||||
|
|
||||||
|
// Verify third list remains untouched
|
||||||
|
resp = Run({"lrange", "list3", "0", "-1"});
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre("e", "f"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ListFamilyTest, LMPopEdgeCases) {
|
||||||
|
// Test with empty list
|
||||||
|
Run({"rpush", "empty_list", "a"});
|
||||||
|
Run({"lpop", "empty_list"});
|
||||||
|
auto resp = Run({"lmpop", "1", "empty_list", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
|
||||||
|
|
||||||
|
// Test with non-existent list
|
||||||
|
resp = Run({"lmpop", "1", "nonexistent", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
|
||||||
|
|
||||||
|
// Test with wrong type key
|
||||||
|
Run({"set", "string_key", "value"});
|
||||||
|
resp = Run({"lmpop", "1", "string_key", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("WRONGTYPE Operation against a key holding the wrong kind of value"));
|
||||||
|
|
||||||
|
// Test without COUNT parameter - should return 1 element by default
|
||||||
|
Run({"rpush", "list", "a", "b"});
|
||||||
|
resp = Run({"lmpop", "1", "list", "LEFT"});
|
||||||
|
EXPECT_THAT(resp,
|
||||||
|
RespArray(ElementsAre(
|
||||||
|
"list", RespArray(ElementsAre("a"))))); // Should return 1 element by default
|
||||||
|
|
||||||
|
// Test with COUNT = 0 - should return error
|
||||||
|
resp = Run({"lmpop", "1", "list", "LEFT", "COUNT", "0"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("list", RespArray(ElementsAre()))));
|
||||||
|
|
||||||
|
// Test with negative COUNT - should return error
|
||||||
|
resp = Run({"lmpop", "1", "list", "LEFT", "COUNT", "-1"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("list", RespArray(ElementsAre("b")))));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ListFamilyTest, LMPopDocExample) {
|
||||||
|
// Try to pop from non-existing lists
|
||||||
|
auto resp = Run({"LMPOP", "2", "non1", "non2", "LEFT", "COUNT", "10"});
|
||||||
|
EXPECT_THAT(resp, ArgType(RespExpr::NIL));
|
||||||
|
|
||||||
|
// Create first list and test basic pop
|
||||||
|
resp = Run({"LPUSH", "mylist", "one", "two", "three", "four", "five"});
|
||||||
|
EXPECT_THAT(resp, IntArg(5));
|
||||||
|
|
||||||
|
resp = Run({"LMPOP", "1", "mylist", "LEFT"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("mylist", RespArray(ElementsAre("five")))));
|
||||||
|
|
||||||
|
resp = Run({"LRANGE", "mylist", "0", "-1"});
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre("four", "three", "two", "one"));
|
||||||
|
|
||||||
|
// Test RIGHT pop with COUNT
|
||||||
|
resp = Run({"LMPOP", "1", "mylist", "RIGHT", "COUNT", "10"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("mylist",
|
||||||
|
RespArray(ElementsAre("one", "two", "three", "four")))));
|
||||||
|
|
||||||
|
// Create two lists and test multi-key pop
|
||||||
|
resp = Run({"LPUSH", "mylist", "one", "two", "three", "four", "five"});
|
||||||
|
EXPECT_THAT(resp, IntArg(5));
|
||||||
|
|
||||||
|
resp = Run({"LPUSH", "mylist2", "a", "b", "c", "d", "e"});
|
||||||
|
EXPECT_THAT(resp, IntArg(5));
|
||||||
|
|
||||||
|
resp = Run({"LMPOP", "2", "mylist", "mylist2", "RIGHT", "COUNT", "3"});
|
||||||
|
EXPECT_THAT(resp,
|
||||||
|
RespArray(ElementsAre("mylist", RespArray(ElementsAre("one", "two", "three")))));
|
||||||
|
|
||||||
|
resp = Run({"LRANGE", "mylist", "0", "-1"});
|
||||||
|
EXPECT_THAT(resp.GetVec(), ElementsAre("five", "four"));
|
||||||
|
|
||||||
|
resp = Run({"LMPOP", "2", "mylist", "mylist2", "RIGHT", "COUNT", "5"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("mylist", RespArray(ElementsAre("four", "five")))));
|
||||||
|
|
||||||
|
resp = Run({"LMPOP", "2", "mylist", "mylist2", "RIGHT", "COUNT", "10"});
|
||||||
|
EXPECT_THAT(resp,
|
||||||
|
RespArray(ElementsAre("mylist2", RespArray(ElementsAre("a", "b", "c", "d", "e")))));
|
||||||
|
|
||||||
|
// Verify both lists are now empty
|
||||||
|
resp = Run({"EXISTS", "mylist", "mylist2"});
|
||||||
|
EXPECT_THAT(resp, IntArg(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ListFamilyTest, LMPopWrongType) {
|
||||||
|
// Setup: create a list and a hash
|
||||||
|
Run({"lpush", "l1", "e1"});
|
||||||
|
Run({"hset", "foo", "k1", "v1"});
|
||||||
|
|
||||||
|
// Test: first key is wrong type
|
||||||
|
auto resp = Run({"lmpop", "2", "foo", "l1", "left"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("WRONGTYPE Operation against a key holding the wrong kind of value"));
|
||||||
|
|
||||||
|
// Test: second key is wrong type but first doesn't exist
|
||||||
|
resp = Run({"lmpop", "2", "nonexistent", "foo", "left"});
|
||||||
|
EXPECT_THAT(resp, ErrArg("WRONGTYPE Operation against a key holding the wrong kind of value"));
|
||||||
|
|
||||||
|
// Test: second key is wrong type but first is a valid list
|
||||||
|
resp = Run({"lmpop", "2", "l1", "foo", "left"});
|
||||||
|
EXPECT_THAT(resp, RespArray(ElementsAre("l1", RespArray(ElementsAre("e1")))));
|
||||||
|
}
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue