Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion src/sw/redis++/async_redis.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ class AsyncRedis {
Future<void> mset(std::initializer_list<T> il) {
return mset(il.begin(), il.end());
}

template <typename Input, typename Callback>
auto mset(Input first, Input last, Callback &&cb)
-> typename std::enable_if<IsInvocable<typename std::decay<Callback>::type, Future<void> &&>::value, void>::type {
Expand Down Expand Up @@ -507,6 +507,71 @@ class AsyncRedis {
fmt::set_keepttl, key, val, keepttl, type);
}

Future<OptionalString> set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
UpdateType type = UpdateType::ALWAYS) {
return _command<OptionalString>(fmt::set_with_get_option, key, val, ttl, type);
}

template <typename Callback>
auto set_with_get_option(const StringView &key,
const StringView &val,
Callback &&cb)
-> typename std::enable_if<IsInvocable<typename std::decay<Callback>::type, Future<OptionalString> &&>::value, void>::type {
_callback_fmt_command<OptionalString>(std::forward<Callback>(cb),
fmt::set_with_get_option, key, val, std::chrono::milliseconds(0), UpdateType::ALWAYS);
}

template <typename Callback>
auto set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
Callback &&cb)
-> typename std::enable_if<IsInvocable<typename std::decay<Callback>::type, Future<OptionalString> &&>::value, void>::type {
_callback_fmt_command<OptionalString>(std::forward<Callback>(cb),
fmt::set_with_get_option, key, val, ttl, UpdateType::ALWAYS);
}

template <typename Callback>
auto set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type,
Callback &&cb)
-> typename std::enable_if<IsInvocable<typename std::decay<Callback>::type, Future<OptionalString> &&>::value, void>::type {
_callback_fmt_command<OptionalString>(std::forward<Callback>(cb),
fmt::set_with_get_option, key, val, ttl, type);
}

Future<OptionalString> set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type = UpdateType::ALWAYS) {
return _command<OptionalString>(fmt::set_with_get_keepttl_option, key, val, keepttl, type);
}

template <typename Callback>
auto set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
Callback &&cb)
-> typename std::enable_if<IsInvocable<typename std::decay<Callback>::type, Future<OptionalString> &&>::value, void>::type {
_callback_fmt_command<OptionalString>(std::forward<Callback>(cb),
fmt::set_with_get_keepttl_option, key, val, keepttl, UpdateType::ALWAYS);
}

template <typename Callback>
auto set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type,
Callback &&cb)
-> typename std::enable_if<IsInvocable<typename std::decay<Callback>::type, Future<OptionalString> &&>::value, void>::type {
_callback_fmt_command<OptionalString>(std::forward<Callback>(cb),
fmt::set_with_get_keepttl_option, key, val, keepttl, type);
}

Future<long long> strlen(const StringView &key) {
return _command<long long>(fmt::strlen, key);
}
Expand Down
32 changes: 32 additions & 0 deletions src/sw/redis++/cmd_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,38 @@ inline FormattedCommand set_keepttl(const StringView &key,
return format_cmd(args);
}

inline FormattedCommand set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type) {
CmdArgs args;
args << "SET" << key << val << "GET";

if (ttl > std::chrono::milliseconds(0)) {
args << "PX" << ttl.count();
}

cmd::detail::set_update_type(args, type);

return format_cmd(args);
}

inline FormattedCommand set_with_get_keepttl_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type) {
CmdArgs args;
args << "SET" << key << val << "GET";

if (keepttl) {
args << "KEEPTTL";
}

cmd::detail::set_update_type(args, type);

return format_cmd(args);
}

inline FormattedCommand strlen(const StringView &key) {
return format_cmd("STRLEN %b", key.data(), key.size());
}
Expand Down
34 changes: 34 additions & 0 deletions src/sw/redis++/command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,40 @@ void set_keepttl(Connection &connection,
connection.send(args);
}

void set_with_get_option(Connection &connection,
const StringView &key,
const StringView &val,
long long ttl,
UpdateType type) {
CmdArgs args;
args << "SET" << key << val << "GET";

if (ttl > 0) {
args << "PX" << ttl;
}

detail::set_update_type(args, type);

connection.send(args);
}

void set_with_get_keepttl_option(Connection &connection,
const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type) {
CmdArgs args;
args << "SET" << key << val << "GET";

if (keepttl) {
args << "KEEPTTL";
}

detail::set_update_type(args, type);

connection.send(args);
}

// LIST commands.

void linsert(Connection &connection,
Expand Down
12 changes: 12 additions & 0 deletions src/sw/redis++/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,18 @@ void set_keepttl(Connection &connection,
bool keepttl,
UpdateType type);

void set_with_get_option(Connection &connection,
const StringView &key,
const StringView &val,
long long ttl,
UpdateType type);

void set_with_get_keepttl_option(Connection &connection,
const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type);

inline void setex(Connection &connection,
const StringView &key,
long long ttl,
Expand Down
18 changes: 18 additions & 0 deletions src/sw/redis++/queued_redis.h
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,24 @@ class QueuedRedis {
return command(cmd::set_keepttl, key, val, keepttl, type);
}

QueuedRedis& set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
UpdateType type = UpdateType::ALWAYS) {
_set_cmd_indexes.insert(_cmd_num);

return command(cmd::set_with_get_option, key, val, ttl.count(), type);
}

QueuedRedis& set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type = UpdateType::ALWAYS) {
_set_cmd_indexes.insert(_cmd_num);

return command(cmd::set_with_get_keepttl_option, key, val, keepttl, type);
}

QueuedRedis& setex(const StringView &key,
long long ttl,
const StringView &val) {
Expand Down
18 changes: 18 additions & 0 deletions src/sw/redis++/redis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,24 @@ bool Redis::set(const StringView &key,
return reply::parse_set_reply(*reply);
}

OptionalString Redis::set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type) {
auto reply = command(cmd::set_with_get_option, key, val, ttl.count(), type);

return reply::parse<OptionalString>(*reply);
}

OptionalString Redis::set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type) {
auto reply = command(cmd::set_with_get_keepttl_option, key, val, keepttl, type);

return reply::parse<OptionalString>(*reply);
}

void Redis::setex(const StringView &key,
long long ttl,
const StringView &val) {
Expand Down
31 changes: 31 additions & 0 deletions src/sw/redis++/redis.h
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,37 @@ class Redis {
bool keepttl,
UpdateType type = UpdateType::ALWAYS);

/// @brief Atomically set the string stored at `key` to `val`, and return the old value.
///
/// Example:
/// @code{.cpp}
/// // Set a key-value pair, and expire it after 10 seconds and get the previous value.
/// auto val = redis.set_with_get_option("key", "value", std::chrono::seconds(10));
/// if (val)
/// std::cout << *val << std::endl;
/// else
/// std::cout << "key not exist" << std::endl;
/// @endcode
/// @param key Key.
/// @param val Value.
/// @param ttl Timeout on the key. If `ttl` is 0ms, do not set timeout.
/// @param type Options for set command:
/// - UpdateType::EXIST: Set the key only if it already exists.
/// - UpdateType::NOT_EXIST: Set the key only if it does not exist.
/// - UpdateType::ALWAYS: Always set the key no matter whether it exists.
/// @return The old value stored at key.
/// @note If key does not exist, `getset` returns `OptionalString{}` (`std::nullopt`).
/// @see https://redis.io/commands/set
OptionalString set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
UpdateType type = UpdateType::ALWAYS);

OptionalString set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type = UpdateType::ALWAYS);

// TODO: add SETBIT command.

/// @brief Set key-value pair with the given timeout in seconds.
Expand Down
18 changes: 18 additions & 0 deletions src/sw/redis++/redis_cluster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,24 @@ bool RedisCluster::set(const StringView &key,
return reply::parse_set_reply(*reply);
}

OptionalString RedisCluster::set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type) {
auto reply = command(cmd::set_with_get_option, key, val, ttl.count(), type);

return reply::parse<OptionalString>(*reply);
}

OptionalString RedisCluster::set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type) {
auto reply = command(cmd::set_with_get_keepttl_option, key, val, keepttl, type);

return reply::parse<OptionalString>(*reply);
}

void RedisCluster::setex(const StringView &key,
long long ttl,
const StringView &val) {
Expand Down
10 changes: 10 additions & 0 deletions src/sw/redis++/redis_cluster.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,16 @@ class RedisCluster {
bool keepttl,
UpdateType type = UpdateType::ALWAYS);

OptionalString set_with_get_option(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
UpdateType type = UpdateType::ALWAYS);

OptionalString set_with_get_option(const StringView &key,
const StringView &val,
bool keepttl,
UpdateType type = UpdateType::ALWAYS);

void setex(const StringView &key,
long long ttl,
const StringView &val);
Expand Down
2 changes: 2 additions & 0 deletions test/src/sw/redis++/string_cmds_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class StringCmdTest {

void _test_getset();

void _test_set_with_get_option();

void _test_mgetset();

RedisInstance &_redis;
Expand Down
30 changes: 30 additions & 0 deletions test/src/sw/redis++/string_cmds_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ void StringCmdTest<RedisInstance>::run() {

_test_getset();

_test_set_with_get_option();

_test_mgetset();
}

Expand Down Expand Up @@ -204,6 +206,34 @@ void StringCmdTest<RedisInstance>::_test_getset() {
REDIS_ASSERT(_redis.pttl(key) <= pttl.count(), "failed to test psetex");
}

template <typename RedisInstance>
void StringCmdTest<RedisInstance>::_test_set_with_get_option() {
auto key = test_key("set_with_get_option");
auto non_exist_key = test_key("non-existent");

KeyDeleter<RedisInstance> deleter(_redis, {key, non_exist_key});

std::string val("value");
REDIS_ASSERT(!_redis.set_with_get_option(key, val), "failed to test set_with_get_option");

auto v = _redis.set_with_get_option(key, val + val, std::chrono::milliseconds(0), UpdateType::NOT_EXIST);
REDIS_ASSERT(v && *v == val, "failed to test set_with_get_option (GET)");
v = _redis.get(key);
REDIS_ASSERT(v && *v == val, "failed to test set_with_get_option (NOT_EXIST)");

auto ttl = std::chrono::seconds(10);
_redis.set_with_get_option(non_exist_key, val, ttl, UpdateType::EXIST);
REDIS_ASSERT(!_redis.get(non_exist_key), "failed to test set_with_get_option (EXIST)");

_redis.set_with_get_option(key, val, ttl);
auto ttl_v = _redis.ttl(key);
REDIS_ASSERT(ttl_v >= 0 && ttl_v <= ttl.count(), "failed to test set_with_get_option (TTL)");

_redis.set_with_get_option(key, val, true);
ttl_v = _redis.ttl(key);
REDIS_ASSERT(ttl_v >= 0 && ttl_v <= ttl.count(), "failed to test set_with_get_option (KEEPTTL)");
}

template <typename RedisInstance>
void StringCmdTest<RedisInstance>::_test_mgetset() {
auto kvs = {std::make_pair(test_key("k1"), "v1"),
Expand Down