Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
46 changes: 46 additions & 0 deletions src/commands/cmd_string.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,51 @@ class CommandGetEx : public Commander {
std::optional<uint64_t> expire_;
};

class CommandDelEX : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (args.size() > 4) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}

CommandParser parser(args, 2);
while (parser.Good()) {
if (parser.EatEqICase("ifdeq")) {
option_ = {DelExOption::IFDEQ, GET_OR_RET(parser.TakeStr())};
} else if (parser.EatEqICase("ifdne")) {
option_ = {DelExOption::IFDNE, GET_OR_RET(parser.TakeStr())};
} else if (parser.EatEqICase("ifeq")) {
option_ = {DelExOption::IFEQ, GET_OR_RET(parser.TakeStr())};
} else if (parser.EatEqICase("ifne")) {
option_ = {DelExOption::IFNE, GET_OR_RET(parser.TakeStr())};
} else {
return {Status::RedisParseErr, errInvalidSyntax};
}
}
return Status::OK();
}

Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::String string_db(srv->storage, conn->GetNamespace());
bool deleted = false;
auto s = string_db.DelEX(ctx, args_[1], option_, deleted);

if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}

if (s.IsNotFound() || !deleted) {
*output = redis::Integer(0);
} else {
*output = redis::Integer(1);
}
return Status::OK();
}

private:
DelExOption option_;
};

class CommandStrlen : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
Expand Down Expand Up @@ -812,6 +857,7 @@ REDIS_REGISTER_COMMANDS(
MakeCmdAttr<CommandGetRange>("getrange", 4, "read-only", 1, 1, 1),
MakeCmdAttr<CommandSubStr>("substr", 4, "read-only", 1, 1, 1),
MakeCmdAttr<CommandGetDel>("getdel", 2, "write no-dbsize-check", 1, 1, 1),
MakeCmdAttr<CommandDelEX>("delex", -2, "write", 1, 1, 1),
MakeCmdAttr<CommandSetRange>("setrange", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandMGet>("mget", -2, "read-only", 1, -1, 1),
MakeCmdAttr<CommandAppend>("append", 3, "write", 1, 1, 1), MakeCmdAttr<CommandSet>("set", -3, "write", 1, 1, 1),
Expand Down
36 changes: 36 additions & 0 deletions src/types/redis_string.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "common/string_util.h"
#include "parse_util.h"
#include "storage/redis_metadata.h"
#include "string_util.h"
#include "time_util.h"

namespace redis {
Expand Down Expand Up @@ -182,6 +183,41 @@ rocksdb::Status String::GetEx(engine::Context &ctx, const std::string &user_key,
return rocksdb::Status::OK();
}

rocksdb::Status String::DelEX(engine::Context &ctx, const std::string &user_key, const DelExOption &option,
bool &deleted) {
deleted = false;
std::string val;
std::string ns_key = AppendNamespacePrefix(user_key);
rocksdb::Status s = getValue(ctx, ns_key, &val);
if (!s.ok()) return s;

bool matched = false;
switch (option.type) {
case DelExOption::NONE:
matched = true;
break;
case DelExOption::IFDEQ:
matched = option.value == util::StringDigest(val);
break;
case DelExOption::IFDNE:
matched = option.value != util::StringDigest(val);
break;
case DelExOption::IFEQ:
matched = option.value == val;
break;
case DelExOption::IFNE:
matched = option.value != val;
break;
default:
return rocksdb::Status::InvalidArgument();
}
if (matched) {
s = storage_->Delete(ctx, storage_->DefaultWriteOptions(), metadata_cf_handle_, ns_key);
deleted = s.ok();
}
return s;
}

rocksdb::Status String::GetSet(engine::Context &ctx, const std::string &user_key, const std::string &new_value,
std::optional<std::string> &old_value) {
auto s =
Expand Down
10 changes: 10 additions & 0 deletions src/types/redis_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ struct StringPair {
Slice value;
};

struct DelExOption {
enum Type { NONE, IFDEQ, IFDNE, IFEQ, IFNE };
Type type;
std::string value;

DelExOption() : type(NONE) {}
DelExOption(Type type, std::string value) : type(type), value(std::move(value)) {}
};

enum class StringSetType { NONE, NX, XX };

struct StringSetArgs {
Expand Down Expand Up @@ -89,6 +98,7 @@ class String : public Database {
rocksdb::Status Get(engine::Context &ctx, const std::string &user_key, std::string *value);
rocksdb::Status GetEx(engine::Context &ctx, const std::string &user_key, std::string *value,
std::optional<uint64_t> expire);
rocksdb::Status DelEX(engine::Context &ctx, const std::string &user_key, const DelExOption &option, bool &deleted);
rocksdb::Status GetSet(engine::Context &ctx, const std::string &user_key, const std::string &new_value,
std::optional<std::string> &old_value);
rocksdb::Status GetDel(engine::Context &ctx, const std::string &user_key, std::string *value);
Expand Down
147 changes: 147 additions & 0 deletions tests/cppunit/types/string_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <memory>

#include "string_util.h"
#include "test_base.h"
#include "time_util.h"
#include "types/redis_string.h"
Expand Down Expand Up @@ -156,6 +157,152 @@ TEST_F(RedisStringTest, GetSet) {
}
auto s = string_->Del(*ctx_, key_);
}

TEST_F(RedisStringTest, DelEX) {
DelExOption option = {DelExOption::NONE, ""};
bool deleted = false;

std::string key = "test-string-key69";
std::string value = "test-strings-value69";
auto status = string_->Set(*ctx_, key, value);
ASSERT_TRUE(status.ok());
status = string_->Get(*ctx_, key, &value);
ASSERT_TRUE(status.ok() && !status.IsNotFound());
EXPECT_EQ("test-strings-value69", value);

// Check no args delete works
auto s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_TRUE(deleted);
EXPECT_EQ(option.type, DelExOption::NONE);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(!status.ok() && status.IsNotFound());
EXPECT_NE("test-strings-value69", value);

// Check no args delete on same key
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.IsNotFound());
EXPECT_FALSE(deleted);

// Check no args delete on invalid/notfound key
key = "random";
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.IsNotFound());
EXPECT_FALSE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(!status.ok() && status.IsNotFound());

// Checking true false cases for all args
key = "test-string-key69";
value = "test-strings-value69";
status = string_->Set(*ctx_, key, value);
EXPECT_TRUE(status.ok());
option.type = DelExOption::IFDEQ;
option.value = "xxxxxxxxxxxxxxxx";
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_FALSE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(status.ok() && !status.IsNotFound());
EXPECT_EQ("test-strings-value69", value);

option.type = DelExOption::IFDEQ;
option.value = util::StringDigest(value);
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_TRUE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(!status.ok());
EXPECT_TRUE(status.IsNotFound());
EXPECT_NE("test-strings-value69", value);

key = "test-string-key69";
value = "test-strings-value69";
status = string_->Set(*ctx_, key, value);
EXPECT_TRUE(status.ok());
option.type = DelExOption::IFDNE;
option.value = util::StringDigest(value);
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_FALSE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(status.ok() && !status.IsNotFound());
EXPECT_EQ("test-strings-value69", value);

option.type = DelExOption::IFDNE;
option.value = "xxxxxxxxxxxxxxxx";
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_TRUE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(!status.ok());
EXPECT_TRUE(status.IsNotFound());
EXPECT_NE("test-strings-value69", value);

key = "test-string-key69";
value = "test-strings-value69";
status = string_->Set(*ctx_, key, value);
EXPECT_TRUE(status.ok());
option.type = DelExOption::IFEQ;
option.value = "random";
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_FALSE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(status.ok() && !status.IsNotFound());
EXPECT_EQ("test-strings-value69", value);

option.type = DelExOption::IFEQ;
option.value = "test-strings-value69";
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_TRUE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(!status.ok());
EXPECT_TRUE(status.IsNotFound());
EXPECT_NE("test-strings-value69", value);

key = "test-string-key69";
value = "test-strings-value69";
status = string_->Set(*ctx_, key, value);
EXPECT_TRUE(status.ok());
option.type = DelExOption::IFNE;
option.value = "test-strings-value69";
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_FALSE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(status.ok() && !status.IsNotFound());
EXPECT_EQ("test-strings-value69", value);

option.type = DelExOption::IFNE;
option.value = "random";
deleted = false;
s = string_->DelEX(*ctx_, key, option, deleted);
EXPECT_TRUE(s.ok());
EXPECT_FALSE(s.IsNotFound());
EXPECT_TRUE(deleted);
status = string_->Get(*ctx_, key, &value);
EXPECT_TRUE(!status.ok());
EXPECT_TRUE(status.IsNotFound());
EXPECT_NE("test-strings-value69", value);
}

TEST_F(RedisStringTest, GetDel) {
for (auto &pair : pairs_) {
string_->Set(*ctx_, pair.key.ToString(), pair.value.ToString());
Expand Down
73 changes: 73 additions & 0 deletions tests/gocase/unit/type/strings/strings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,79 @@ func testString(t *testing.T, configs util.KvrocksServerConfigs) {
require.Equal(t, "", rdb.GetDel(ctx, "foo").Val())
})

t.Run("DelEX command no args", func(t *testing.T) {
key := "test-string-key69"
value := "test-strings-value69"
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, value, rdb.Get(ctx, key).Val())

require.Equal(t, int64(1), rdb.Do(ctx, "DELEX", key).Val())
require.Equal(t, "", rdb.Get(ctx, key).Val())

require.NoError(t, rdb.Do(ctx, "DelEX", key).Err())
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key).Val())

require.Equal(t, "", rdb.Get(ctx, "random").Val())
require.NoError(t, rdb.Do(ctx, "DelEX", "random").Err())
require.Equal(t, int64(0), rdb.Do(ctx, "DELEX", "random").Val())
})

t.Run("DelEX command with args", func(t *testing.T) {
key := "test-string-key69"
value := "Hello world"
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, value, rdb.Get(ctx, key).Val())

r := rdb.Do(ctx, "DelEX", key, "random", "random", "random").Err()
require.ErrorContains(t, r, "wrong number")

r = rdb.Do(ctx, "DelEX", key, "random", "random").Err()
require.ErrorContains(t, r, "syntax error")

digest := "b6acb9d84a38ff74"
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdeq", "xxxxxxxxxxxxxxxx").Err())
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifdeq", "xxxxxxxxxxxxxxxx").Val())
require.Equal(t, value, rdb.Get(ctx, key).Val())
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdeq", digest).Err())
require.Equal(t, "", rdb.Get(ctx, value).Val())
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, int64(1), rdb.Do(ctx, "DELEX", key, "ifdeq", digest).Val())
require.Equal(t, "", rdb.Get(ctx, value).Val())

require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, value, rdb.Get(ctx, key).Val())
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdne", digest).Err())
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifdne", digest).Val())
require.Equal(t, value, rdb.Get(ctx, key).Val())
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdne", "xxxxxxxxxxxxxxxx").Err())
require.Equal(t, "", rdb.Get(ctx, value).Val())
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, int64(1), rdb.Do(ctx, "DelEX", key, "ifdne", "xxxxxxxxxxxxxxxx").Val())
require.Equal(t, "", rdb.Get(ctx, value).Val())

require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, value, rdb.Get(ctx, key).Val())
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifeq", "random").Err())
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifeq", "random").Val())
require.Equal(t, value, rdb.Get(ctx, key).Val())
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifeq", value).Err())
require.Equal(t, "", rdb.Get(ctx, value).Val())
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, int64(1), rdb.Do(ctx, "DelEX", key, "ifeq", value).Val())
require.Equal(t, "", rdb.Get(ctx, value).Val())

require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, value, rdb.Get(ctx, key).Val())
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifne", value).Err())
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifne", value).Val())
require.Equal(t, value, rdb.Get(ctx, key).Val())
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifne", "random").Err())
require.Equal(t, "", rdb.Get(ctx, value).Val())
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
require.Equal(t, int64(1), rdb.Do(ctx, "DelEX", key, "ifne", "random").Val())
require.Equal(t, "", rdb.Get(ctx, value).Val())
})

t.Run("MGET command", func(t *testing.T) {
require.NoError(t, rdb.FlushDB(ctx).Err())
require.NoError(t, rdb.Set(ctx, "foo", "BAR", 0).Err())
Expand Down
Loading