From 87d7690c8303e64fb466401bd277d698f616326c Mon Sep 17 00:00:00 2001 From: sewenew Date: Sun, 6 Oct 2024 19:52:51 +0800 Subject: [PATCH] support LMPOP, LMOVE and BLMOVE commands --- README.md | 4 +- src/sw/redis++/async_redis.h | 54 +++++++++++++++++++ src/sw/redis++/async_redis_cluster.h | 54 +++++++++++++++++++ src/sw/redis++/cmd_formatter.h | 33 ++++++++++++ src/sw/redis++/command.cpp | 2 +- src/sw/redis++/command.h | 33 ++++++++++++ src/sw/redis++/command_options.cpp | 16 ++++++ src/sw/redis++/command_options.h | 7 +++ src/sw/redis++/redis.cpp | 14 +++++ src/sw/redis++/redis.h | 75 ++++++++++++++++++++++++++ src/sw/redis++/redis.hpp | 10 ++++ src/sw/redis++/redis_cluster.cpp | 14 +++++ src/sw/redis++/redis_cluster.h | 15 ++++++ src/sw/redis++/redis_cluster.hpp | 9 ++++ src/sw/redis++/reply.h | 12 +++++ test/src/sw/redis++/async_test.h | 65 ++++++++++++++++++++++ test/src/sw/redis++/list_cmds_test.h | 6 +++ test/src/sw/redis++/list_cmds_test.hpp | 63 ++++++++++++++++++++++ test/src/sw/redis++/utils.h | 7 ++- 19 files changed, 489 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 74d63e5c..033c5c05 100644 --- a/README.md +++ b/README.md @@ -318,9 +318,9 @@ Visual Studio 2019 (Win 10) If you build *redis-plus-plus* with `-DREDIS_PLUS_PLUS_BUILD_TEST=ON` (the default behavior, and you can disable building test with `-DREDIS_PLUS_PLUS_BUILD_TEST=OFF`), you'll get a test program in *build/test* directory: *build/test/test_redis++*. -In order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version (by now, it's 5.0). Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future. +In order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version. Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future. -**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, e.g. Redis 2.0, Redis 3.0, Redis 4.0, Redis 5.0. +**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, i.e. Redis 2.0 and above. **NEVER** run the test program in production envronment, since the keys, which the test program reads or writes, might conflict with your application. diff --git a/src/sw/redis++/async_redis.h b/src/sw/redis++/async_redis.h index 8621f492..e53f3850 100644 --- a/src/sw/redis++/async_redis.h +++ b/src/sw/redis++/async_redis.h @@ -811,6 +811,60 @@ class AsyncRedis { return rpush(key, il.begin(), il.end(), std::forward(cb)); } + template + Future>> lmpop(Input first, Input last, ListWhence whence, long long count = 1) { + range_check("LMPOP", first, last); + + return _command>>(fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, long long count, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + range_check("LMPOP", first, last); + + _callback_fmt_command>>(std::forward(cb), + fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + return lmpop(first, last, whence, 1, std::forward(cb)); + } + + Future lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + return _command(fmt::lmove, src, dest, src_whence, dest_whence); + } + + template + void lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::lmove, src, dest, src_whence, dest_whence); + } + + Future blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return _command(fmt::blmove, src, dest, src_whence, dest_whence, timeout.count()); + } + + template + void blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::blmove, src, dest, + src_whence, dest_whence, timeout.count()); + } + + template + auto blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) + -> typename std::enable_if::type, Future &&>::value, void>::type { + blmove(src, dest, src_whence, dest_whence, std::chrono::seconds{0}, std::forward(cb)); + } + // HASH commands. Future hdel(const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/async_redis_cluster.h b/src/sw/redis++/async_redis_cluster.h index 8c2a41fd..285aaa83 100644 --- a/src/sw/redis++/async_redis_cluster.h +++ b/src/sw/redis++/async_redis_cluster.h @@ -493,6 +493,60 @@ class AsyncRedisCluster { return rpush(key, il.begin(), il.end()); } + template + Future>> lmpop(Input first, Input last, ListWhence whence, long long count = 1) { + range_check("LMPOP", first, last); + + return _command>>(fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, long long count, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + range_check("LMPOP", first, last); + + _callback_fmt_command>>(std::forward(cb), + fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + return lmpop(first, last, whence, 1, std::forward(cb)); + } + + Future lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + return _command(fmt::lmove, src, dest, src_whence, dest_whence); + } + + template + void lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::lmove, src, dest, src_whence, dest_whence); + } + + Future blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return _command(fmt::blmove, src, dest, src_whence, dest_whence, timeout.count()); + } + + template + void blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::blmove, src, dest, + src_whence, dest_whence, timeout.count()); + } + + template + auto blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) + -> typename std::enable_if::type, Future &&>::value, void>::type { + blmove(src, dest, src_whence, dest_whence, std::chrono::seconds{0}, std::forward(cb)); + } + // HASH commands. Future hdel(const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/cmd_formatter.h b/src/sw/redis++/cmd_formatter.h index 2467c866..01b69d74 100644 --- a/src/sw/redis++/cmd_formatter.h +++ b/src/sw/redis++/cmd_formatter.h @@ -377,6 +377,39 @@ FormattedCommand rpush_range(const StringView &key, Input first, Input last) { return format_cmd(args); } +template +FormattedCommand lmpop(Input first, Input last, ListWhence whence, long long count) { + assert(first != last); + + CmdArgs args; + + auto keys_num = std::distance(first, last); + + args << "LMPOP" << keys_num << std::make_pair(first, last) << to_string(whence) << "COUNT" << count; + + return format_cmd(args); +} + +inline FormattedCommand lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + return format_cmd("LMOVE %b %b %s %s", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data()); +} + +inline FormattedCommand blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, long long timeout) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + return format_cmd("BLMOVE %b %b %s %s %lld", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data(), timeout); +} + // HASH commands. inline FormattedCommand hdel(const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/command.cpp b/src/sw/redis++/command.cpp index 68baa15d..f5fa0179 100644 --- a/src/sw/redis++/command.cpp +++ b/src/sw/redis++/command.cpp @@ -107,7 +107,7 @@ void linsert(Connection &connection, break; default: - assert(false); + throw Error("unknown insert position"); } connection.send("LINSERT %b %s %b %b", diff --git a/src/sw/redis++/command.h b/src/sw/redis++/command.h index 5ee80e64..76e96fb7 100644 --- a/src/sw/redis++/command.h +++ b/src/sw/redis++/command.h @@ -636,6 +636,39 @@ inline void rpushx(Connection &connection, const StringView &key, const StringVi val.data(), val.size()); } +template +inline void lmpop(Connection &connection, Input first, Input last, ListWhence whence, long long count) { + assert(first != last); + + CmdArgs args; + + auto keys_num = std::distance(first, last); + + args << "LMPOP" << keys_num << std::make_pair(first, last) << to_string(whence) << "COUNT" << count; + + connection.send(args); +} + +inline void lmove(Connection &connection, const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + connection.send("LMOVE %b %b %s %s", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data()); +} + +inline void blmove(Connection &connection, const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, long long timeout) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + connection.send("BLMOVE %b %b %s %s %lld", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data(), timeout); +} + // HASH commands. inline void hdel(Connection &connection, const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/command_options.cpp b/src/sw/redis++/command_options.cpp index eda34d0e..df236bd4 100644 --- a/src/sw/redis++/command_options.cpp +++ b/src/sw/redis++/command_options.cpp @@ -184,6 +184,22 @@ const std::string& RightBoundedInterval::lower() const { return NEGATIVE_INFINITY_STRING; } +std::string to_string(ListWhence whence) { + std::string str; + switch (whence) { + case ListWhence::LEFT: + str = "LEFT"; + break; + case ListWhence::RIGHT: + str = "RIGHT"; + break; + default: + throw Error("unknown list whence"); + } + + return str; +} + } } diff --git a/src/sw/redis++/command_options.h b/src/sw/redis++/command_options.h index aa779fd7..cc524be0 100644 --- a/src/sw/redis++/command_options.h +++ b/src/sw/redis++/command_options.h @@ -35,6 +35,11 @@ enum class InsertPosition { AFTER }; +enum class ListWhence { + LEFT, + RIGHT +}; + enum class BoundType { CLOSED, OPEN, @@ -209,6 +214,8 @@ struct WithDist : TupleWithType {}; template struct WithHash : TupleWithType {}; +std::string to_string(ListWhence whence); + } } diff --git a/src/sw/redis++/redis.cpp b/src/sw/redis++/redis.cpp index 761103f2..8496b676 100644 --- a/src/sw/redis++/redis.cpp +++ b/src/sw/redis++/redis.cpp @@ -515,6 +515,20 @@ long long Redis::rpushx(const StringView &key, const StringView &val) { return reply::parse(*reply); } +OptionalString Redis::lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto reply = command(cmd::lmove, src, dest, src_whence, dest_whence); + + return reply::parse(*reply); +} + +OptionalString Redis::blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, const std::chrono::seconds &timeout) { + auto reply = command(cmd::blmove, src, dest, src_whence, dest_whence, timeout.count()); + + return reply::parse(*reply); +} + long long Redis::hdel(const StringView &key, const StringView &field) { auto reply = command(cmd::hdel, key, field); diff --git a/src/sw/redis++/redis.h b/src/sw/redis++/redis.h index 15070db1..9311e42a 100644 --- a/src/sw/redis++/redis.h +++ b/src/sw/redis++/redis.h @@ -1314,6 +1314,81 @@ class Redis { /// @see https://redis.io/commands/rpushx long long rpushx(const StringView &key, const StringView &val); + /// @brief Pop one or more elements from the first non-empty list. + /// + /// Example: + /// @code{.cpp} + /// auto lists = {"l1", "l2"}; + /// auto val = redis.lmpop>(lists.begin(), lists.end(), ListWhence::LEFT, 2); + /// if (val) + /// std::cout << "list: " << val->first << ", size: " << val->second.size() << std::endl; + /// else + /// std::cout << "all lists are empty" << std::endl; + /// @endcode + /// @param first Iterator to the first list. + /// @param last Off-the-end iterator to the given list range. + /// @param whence ListWhence::LEFT or ListWhence::RIGHT. + /// @param count Number of elements to be popped. + /// @return Elements popped from list. + /// @note If key does not exist, `lmpop` returns `Optional>{}` (`std::nullopt`). + /// @see https://redis.io/commands/lmpop + template + Optional> lmpop(Input first, Input last, ListWhence whence, long long count = 1); + + /// @brief Pop one or more elements from the first non-empty list. + /// @param il Initializer list of Redis lists. + /// @param pos ListWhence::LEFT or ListWhence::RIGHT. + /// @param count Number of elements to be popped. + /// @return Elements popped from list. + /// @note If key does not exist, `lmpop` returns `Optional>{}` (`std::nullopt`). + /// @see https://redis.io/commands/lmpop + template + Optional> lmpop(std::initializer_list il, ListWhence pos, long long count = 1) { + return lmpop(il.begin(), il.end(), pos, count); + } + + /// @brief Move element from src list to dest list. + /// + /// Example: + /// @code{.cpp} + /// auto val = redis.lmove("src", "dest", ListWhence::LEFT, ListWhence::RIGHT); + /// if (val) + /// std::cout << "moved " << *val << " from src to dest" << std::endl; + /// else + /// std::cout << "src list does not exist" << std::endl; + /// @endcode + /// @param src Source list. + /// @param dest Destination list. + /// @param src_whence From where of the source list. + /// @param dest_whence To where of the dest list. + /// @return The moved element. + /// @note If source list does not exist, `lmove` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/lmove + OptionalString lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence); + + /// @brief The block version of lmove. + /// + /// Example: + /// @code{.cpp} + /// auto val = redis.blmove("src", "dest", ListWhence::LEFT, ListWhence::RIGHT, std::chrono::seonds(2)); + /// if (val) + /// std::cout << "moved " << *val << " from src to dest" << std::endl; + /// else + /// std::cout << "src list does not exist" << std::endl; + /// @endcode + /// @param src Source list. + /// @param dest Destination list. + /// @param src_whence From where of the source list. + /// @param dest_whence To where of the dest list. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return The moved element. + /// @note If source list does not exist, `blmove` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/blmove + OptionalString blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + // HASH commands. /// @brief Remove the given field from hash. diff --git a/src/sw/redis++/redis.hpp b/src/sw/redis++/redis.hpp index 5d40f503..9714ff6b 100644 --- a/src/sw/redis++/redis.hpp +++ b/src/sw/redis++/redis.hpp @@ -339,6 +339,16 @@ inline long long Redis::rpush(const StringView &key, Input first, Input last) { return reply::parse(*reply); } +template +Optional> Redis::lmpop(Input first, Input last, ListWhence whence, long long count) { + range_check("LMPOP", first, last); + + auto reply = command(cmd::lmpop, first, last, whence, count); + + return reply::parse>>(*reply); +} + + // HASH commands. template diff --git a/src/sw/redis++/redis_cluster.cpp b/src/sw/redis++/redis_cluster.cpp index bd1d3bdb..7eebc1a7 100644 --- a/src/sw/redis++/redis_cluster.cpp +++ b/src/sw/redis++/redis_cluster.cpp @@ -426,6 +426,20 @@ long long RedisCluster::rpushx(const StringView &key, const StringView &val) { return reply::parse(*reply); } +OptionalString RedisCluster::lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto reply = command(cmd::lmove, src, dest, src_whence, dest_whence); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, const std::chrono::seconds &timeout) { + auto reply = command(cmd::blmove, src, dest, src_whence, dest_whence, timeout.count()); + + return reply::parse(*reply); +} + long long RedisCluster::hdel(const StringView &key, const StringView &field) { auto reply = command(cmd::hdel, key, field); diff --git a/src/sw/redis++/redis_cluster.h b/src/sw/redis++/redis_cluster.h index cef76656..644bf93d 100644 --- a/src/sw/redis++/redis_cluster.h +++ b/src/sw/redis++/redis_cluster.h @@ -403,6 +403,21 @@ class RedisCluster { long long rpushx(const StringView &key, const StringView &val); + template + Optional> lmpop(Input first, Input last, ListWhence whence, long long count = 1); + + template + Optional> lmpop(std::initializer_list il, ListWhence whence, long long count = 1) { + return lmpop(il.begin(), il.end(), whence, count); + } + + OptionalString lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence); + + OptionalString blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + // HASH commands. long long hdel(const StringView &key, const StringView &field); diff --git a/src/sw/redis++/redis_cluster.hpp b/src/sw/redis++/redis_cluster.hpp index c0e8188b..cff89e16 100644 --- a/src/sw/redis++/redis_cluster.hpp +++ b/src/sw/redis++/redis_cluster.hpp @@ -310,6 +310,15 @@ inline long long RedisCluster::rpush(const StringView &key, Input first, Input l return reply::parse(*reply); } +template +Optional> RedisCluster::lmpop(Input first, Input last, ListWhence whence, long long count) { + range_check("LMPOP", first, last); + + auto reply = command(cmd::lmpop, first, last, whence, count); + + return reply::parse>>(*reply); +} + // HASH commands. template diff --git a/src/sw/redis++/reply.h b/src/sw/redis++/reply.h index fe4dafe3..32ea9a97 100644 --- a/src/sw/redis++/reply.h +++ b/src/sw/redis++/reply.h @@ -184,6 +184,9 @@ std::string to_status(redisReply &reply); template void to_array(redisReply &reply, Output output); +template +void to_optional_array(redisReply &reply, Output output); + // Parse set reply to bool type bool parse_set_reply(redisReply &reply); @@ -494,6 +497,15 @@ void to_array(redisReply &reply, Output output) { detail::to_array(typename IsKvPairIter::type(), reply, output); } +template +void to_optional_array(redisReply &reply, Output output) { + if (is_nil(reply)) { + return; + } + + to_array(reply, output); +} + template auto parse_xpending_reply(redisReply &reply, Output output) -> std::tuple { diff --git a/test/src/sw/redis++/async_test.h b/test/src/sw/redis++/async_test.h index 2932a9fe..7d2bbb7a 100644 --- a/test/src/sw/redis++/async_test.h +++ b/test/src/sw/redis++/async_test.h @@ -33,6 +33,16 @@ namespace redis { namespace test { +template <> +inline void delete_keys(AsyncRedis &r, const std::vector &keys) { + r.del(keys.begin(), keys.end()).get(); +} + +template <> +inline void delete_keys(AsyncRedisCluster &r, const std::vector &keys) { + r.del(keys.begin(), keys.end()).get(); +} + template class AsyncTest { public: @@ -47,6 +57,8 @@ class AsyncTest { private: void _test_str(); + void _test_list(); + void _test_hash(); void _test_set(); @@ -73,6 +85,8 @@ template void AsyncTest::run() { _test_str(); + _test_list(); + _test_hash(); _test_set(); @@ -150,6 +164,57 @@ void AsyncTest::_test_str() { _wait(); } +template +void AsyncTest::_test_list() { + auto src = test_key("src"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {src, dest}); + + auto num = _redis.lpush(src, {"a", "b", "c"}).get(); + REDIS_ASSERT(num == 3, "failed to test async list: lpush"); + + num = _redis.lpush(dest, {"e", "f", "g"}).get(); + REDIS_ASSERT(num == 3, "failed to test async list: lpush"); + + auto val = _redis.lmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT).get(); + REDIS_ASSERT(val && *val == "c", "failed to test async list: lmove"); + + set_ready(false); + _redis.blmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT, + [this](Future &&fut) { + auto val = fut.get(); + REDIS_ASSERT(val && *val == "b", "failed to test async list: blmove"); + + this->set_ready(); + }); + _wait(); + + set_ready(false); + _redis.blmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT, std::chrono::seconds{1}, + [this](Future &&fut) { + auto val = fut.get(); + REDIS_ASSERT(val && *val == "a", "failed to test async list: blmove"); + + this->set_ready(); + }); + _wait(); + + auto keys = std::initializer_list{src, dest}; + auto lmpop_res = _redis.template lmpop>(keys.begin(), keys.end(), ListWhence::LEFT).get(); + REDIS_ASSERT(lmpop_res && lmpop_res->first == dest && lmpop_res->second.size() == 1, "failed to test async list: lmpop"); + + set_ready(false); + _redis.template lmpop>(keys.begin(), keys.end(), ListWhence::LEFT, 2, + [this, dest](Future>>> && fut) { + auto val = fut.get(); + REDIS_ASSERT(val && val->first == dest && val->second.size() == 2, "failed to test async list: lmpop"); + + this->set_ready(); + }); + _wait(); +} + template void AsyncTest::_test_hash() { auto key = test_key("hash"); diff --git a/test/src/sw/redis++/list_cmds_test.h b/test/src/sw/redis++/list_cmds_test.h index 2092fe9e..458fefed 100644 --- a/test/src/sw/redis++/list_cmds_test.h +++ b/test/src/sw/redis++/list_cmds_test.h @@ -41,6 +41,12 @@ class ListCmdTest { void _test_blocking(); + void _test_lmove(); + + void _test_blmove(); + + void _test_lmpop(); + RedisInstance &_redis; }; diff --git a/test/src/sw/redis++/list_cmds_test.hpp b/test/src/sw/redis++/list_cmds_test.hpp index fd26d8fd..67406775 100644 --- a/test/src/sw/redis++/list_cmds_test.hpp +++ b/test/src/sw/redis++/list_cmds_test.hpp @@ -34,6 +34,12 @@ void ListCmdTest::run() { _test_list(); _test_blocking(); + + _test_lmove(); + + _test_blmove(); + + _test_lmpop(); } template @@ -145,6 +151,63 @@ void ListCmdTest::_test_blocking() { REDIS_ASSERT(str && *str == val, "failed to test rpoplpush"); } +template +void ListCmdTest::_test_lmove() { + auto src = test_key("src"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {src, dest}); + + _redis.lpush(src, {"a", "b", "c"}); + _redis.lpush(dest, {"e", "f", "d"}); + + auto val = _redis.lmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT); + REDIS_ASSERT(val && *val == "c", "failed to test lmove"); + auto src_len = _redis.llen(src); + auto dest_len = _redis.llen(dest); + REDIS_ASSERT(src_len == 2 && dest_len == 4, "failed to test lmove"); + + val = _redis.lmove(test_key("not_exist_list"), dest, ListWhence::LEFT, ListWhence::RIGHT); + REDIS_ASSERT(!val, "failed to test lmove"); +} + +template +void ListCmdTest::_test_blmove() { + auto src = test_key("src"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {src, dest}); + + _redis.lpush(src, {"a", "b", "c"}); + _redis.lpush(dest, {"e", "f", "d"}); + + auto val = _redis.blmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT); + REDIS_ASSERT(val && *val == "c", "failed to test lmove"); + auto src_len = _redis.llen(src); + auto dest_len = _redis.llen(dest); + REDIS_ASSERT(src_len == 2 && dest_len == 4, "failed to test lmove"); + + val = _redis.blmove(test_key("not_exist_list"), dest, ListWhence::LEFT, ListWhence::RIGHT, std::chrono::seconds(1)); + REDIS_ASSERT(!val, "failed to test lmove"); +} + +template +void ListCmdTest::_test_lmpop() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + + KeyDeleter deleter(_redis, {k1, k2}); + + _redis.lpush(k1, {"a", "b"}); + _redis.lpush(k2, {"c"}); + + auto res = _redis.template lmpop>({k1, k2}, ListWhence::LEFT, 2); + REDIS_ASSERT(res && res->first == k1 && res->second.size() == 2, "failed to test lmpop"); + + res = _redis.template lmpop>({k1, k2}, ListWhence::LEFT, 2); + REDIS_ASSERT(res && res->first == k2 && res->second.size() == 1, "failed to test lmpop"); +} + } } diff --git a/test/src/sw/redis++/utils.h b/test/src/sw/redis++/utils.h index da4e1fdb..8c909093 100644 --- a/test/src/sw/redis++/utils.h +++ b/test/src/sw/redis++/utils.h @@ -68,6 +68,11 @@ void cluster_specializing_test(Test &test, (test.*func)(instance); } +template +void delete_keys(RedisInstance &r, const std::vector &keys) { + r.del(keys.begin(), keys.end()); +} + template class KeyDeleter { public: @@ -88,7 +93,7 @@ class KeyDeleter { private: void _delete() { if (!_keys.empty()) { - _redis.del(_keys.begin(), _keys.end()); + delete_keys(_redis, _keys); } }