Skip to content

Commit 6148f9b

Browse files
Valay17git-hulkPragmaTwice
authored
feat(string): Implement DELEX Command (#3317)
Fixes #3310 Implement DELEX Command Digest function commented out --------- Co-authored-by: hulk <hulk.website@gmail.com> Co-authored-by: Twice <twice@apache.org>
1 parent 576d575 commit 6148f9b

5 files changed

Lines changed: 312 additions & 0 deletions

File tree

src/commands/cmd_string.cc

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,51 @@ class CommandGetEx : public Commander {
108108
std::optional<uint64_t> expire_;
109109
};
110110

111+
class CommandDelEX : public Commander {
112+
public:
113+
Status Parse(const std::vector<std::string> &args) override {
114+
if (args.size() > 4) {
115+
return {Status::RedisParseErr, errWrongNumOfArguments};
116+
}
117+
118+
CommandParser parser(args, 2);
119+
while (parser.Good()) {
120+
if (parser.EatEqICase("ifdeq")) {
121+
option_ = {DelExOption::IFDEQ, GET_OR_RET(parser.TakeStr())};
122+
} else if (parser.EatEqICase("ifdne")) {
123+
option_ = {DelExOption::IFDNE, GET_OR_RET(parser.TakeStr())};
124+
} else if (parser.EatEqICase("ifeq")) {
125+
option_ = {DelExOption::IFEQ, GET_OR_RET(parser.TakeStr())};
126+
} else if (parser.EatEqICase("ifne")) {
127+
option_ = {DelExOption::IFNE, GET_OR_RET(parser.TakeStr())};
128+
} else {
129+
return {Status::RedisParseErr, errInvalidSyntax};
130+
}
131+
}
132+
return Status::OK();
133+
}
134+
135+
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
136+
redis::String string_db(srv->storage, conn->GetNamespace());
137+
bool deleted = false;
138+
auto s = string_db.DelEX(ctx, args_[1], option_, deleted);
139+
140+
if (!s.ok() && !s.IsNotFound()) {
141+
return {Status::RedisExecErr, s.ToString()};
142+
}
143+
144+
if (s.IsNotFound() || !deleted) {
145+
*output = redis::Integer(0);
146+
} else {
147+
*output = redis::Integer(1);
148+
}
149+
return Status::OK();
150+
}
151+
152+
private:
153+
DelExOption option_;
154+
};
155+
111156
class CommandStrlen : public Commander {
112157
public:
113158
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
@@ -812,6 +857,7 @@ REDIS_REGISTER_COMMANDS(
812857
MakeCmdAttr<CommandGetRange>("getrange", 4, "read-only", 1, 1, 1),
813858
MakeCmdAttr<CommandSubStr>("substr", 4, "read-only", 1, 1, 1),
814859
MakeCmdAttr<CommandGetDel>("getdel", 2, "write no-dbsize-check", 1, 1, 1),
860+
MakeCmdAttr<CommandDelEX>("delex", -2, "write", 1, 1, 1),
815861
MakeCmdAttr<CommandSetRange>("setrange", 4, "write", 1, 1, 1),
816862
MakeCmdAttr<CommandMGet>("mget", -2, "read-only", 1, -1, 1),
817863
MakeCmdAttr<CommandAppend>("append", 3, "write", 1, 1, 1), MakeCmdAttr<CommandSet>("set", -3, "write", 1, 1, 1),

src/types/redis_string.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "common/string_util.h"
3030
#include "parse_util.h"
3131
#include "storage/redis_metadata.h"
32+
#include "string_util.h"
3233
#include "time_util.h"
3334

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

186+
rocksdb::Status String::DelEX(engine::Context &ctx, const std::string &user_key, const DelExOption &option,
187+
bool &deleted) {
188+
deleted = false;
189+
std::string val;
190+
std::string ns_key = AppendNamespacePrefix(user_key);
191+
rocksdb::Status s = getValue(ctx, ns_key, &val);
192+
if (!s.ok()) return s;
193+
194+
bool matched = false;
195+
switch (option.type) {
196+
case DelExOption::NONE:
197+
matched = true;
198+
break;
199+
case DelExOption::IFDEQ:
200+
matched = option.value == util::StringDigest(val);
201+
break;
202+
case DelExOption::IFDNE:
203+
matched = option.value != util::StringDigest(val);
204+
break;
205+
case DelExOption::IFEQ:
206+
matched = option.value == val;
207+
break;
208+
case DelExOption::IFNE:
209+
matched = option.value != val;
210+
break;
211+
default:
212+
return rocksdb::Status::InvalidArgument();
213+
}
214+
if (matched) {
215+
s = storage_->Delete(ctx, storage_->DefaultWriteOptions(), metadata_cf_handle_, ns_key);
216+
deleted = s.ok();
217+
}
218+
return s;
219+
}
220+
185221
rocksdb::Status String::GetSet(engine::Context &ctx, const std::string &user_key, const std::string &new_value,
186222
std::optional<std::string> &old_value) {
187223
auto s =

src/types/redis_string.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ struct StringPair {
3434
Slice value;
3535
};
3636

37+
struct DelExOption {
38+
enum Type { NONE, IFDEQ, IFDNE, IFEQ, IFNE };
39+
Type type;
40+
std::string value;
41+
42+
DelExOption() : type(NONE) {}
43+
DelExOption(Type type, std::string value) : type(type), value(std::move(value)) {}
44+
};
45+
3746
enum class StringSetType { NONE, NX, XX };
3847

3948
struct StringSetArgs {
@@ -89,6 +98,7 @@ class String : public Database {
8998
rocksdb::Status Get(engine::Context &ctx, const std::string &user_key, std::string *value);
9099
rocksdb::Status GetEx(engine::Context &ctx, const std::string &user_key, std::string *value,
91100
std::optional<uint64_t> expire);
101+
rocksdb::Status DelEX(engine::Context &ctx, const std::string &user_key, const DelExOption &option, bool &deleted);
92102
rocksdb::Status GetSet(engine::Context &ctx, const std::string &user_key, const std::string &new_value,
93103
std::optional<std::string> &old_value);
94104
rocksdb::Status GetDel(engine::Context &ctx, const std::string &user_key, std::string *value);

tests/cppunit/types/string_test.cc

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include <memory>
2424

25+
#include "string_util.h"
2526
#include "test_base.h"
2627
#include "time_util.h"
2728
#include "types/redis_string.h"
@@ -156,6 +157,152 @@ TEST_F(RedisStringTest, GetSet) {
156157
}
157158
auto s = string_->Del(*ctx_, key_);
158159
}
160+
161+
TEST_F(RedisStringTest, DelEX) {
162+
DelExOption option = {DelExOption::NONE, ""};
163+
bool deleted = false;
164+
165+
std::string key = "test-string-key69";
166+
std::string value = "test-strings-value69";
167+
auto status = string_->Set(*ctx_, key, value);
168+
ASSERT_TRUE(status.ok());
169+
status = string_->Get(*ctx_, key, &value);
170+
ASSERT_TRUE(status.ok() && !status.IsNotFound());
171+
EXPECT_EQ("test-strings-value69", value);
172+
173+
// Check no args delete works
174+
auto s = string_->DelEX(*ctx_, key, option, deleted);
175+
EXPECT_TRUE(s.ok());
176+
EXPECT_FALSE(s.IsNotFound());
177+
EXPECT_TRUE(deleted);
178+
EXPECT_EQ(option.type, DelExOption::NONE);
179+
status = string_->Get(*ctx_, key, &value);
180+
EXPECT_TRUE(!status.ok() && status.IsNotFound());
181+
EXPECT_NE("test-strings-value69", value);
182+
183+
// Check no args delete on same key
184+
s = string_->DelEX(*ctx_, key, option, deleted);
185+
EXPECT_TRUE(s.IsNotFound());
186+
EXPECT_FALSE(deleted);
187+
188+
// Check no args delete on invalid/notfound key
189+
key = "random";
190+
s = string_->DelEX(*ctx_, key, option, deleted);
191+
EXPECT_TRUE(s.IsNotFound());
192+
EXPECT_FALSE(deleted);
193+
status = string_->Get(*ctx_, key, &value);
194+
EXPECT_TRUE(!status.ok() && status.IsNotFound());
195+
196+
// Checking true false cases for all args
197+
key = "test-string-key69";
198+
value = "test-strings-value69";
199+
status = string_->Set(*ctx_, key, value);
200+
EXPECT_TRUE(status.ok());
201+
option.type = DelExOption::IFDEQ;
202+
option.value = "xxxxxxxxxxxxxxxx";
203+
deleted = false;
204+
s = string_->DelEX(*ctx_, key, option, deleted);
205+
EXPECT_TRUE(s.ok());
206+
EXPECT_FALSE(s.IsNotFound());
207+
EXPECT_FALSE(deleted);
208+
status = string_->Get(*ctx_, key, &value);
209+
EXPECT_TRUE(status.ok() && !status.IsNotFound());
210+
EXPECT_EQ("test-strings-value69", value);
211+
212+
option.type = DelExOption::IFDEQ;
213+
option.value = util::StringDigest(value);
214+
deleted = false;
215+
s = string_->DelEX(*ctx_, key, option, deleted);
216+
EXPECT_TRUE(s.ok());
217+
EXPECT_FALSE(s.IsNotFound());
218+
EXPECT_TRUE(deleted);
219+
status = string_->Get(*ctx_, key, &value);
220+
EXPECT_TRUE(!status.ok());
221+
EXPECT_TRUE(status.IsNotFound());
222+
EXPECT_NE("test-strings-value69", value);
223+
224+
key = "test-string-key69";
225+
value = "test-strings-value69";
226+
status = string_->Set(*ctx_, key, value);
227+
EXPECT_TRUE(status.ok());
228+
option.type = DelExOption::IFDNE;
229+
option.value = util::StringDigest(value);
230+
deleted = false;
231+
s = string_->DelEX(*ctx_, key, option, deleted);
232+
EXPECT_TRUE(s.ok());
233+
EXPECT_FALSE(s.IsNotFound());
234+
EXPECT_FALSE(deleted);
235+
status = string_->Get(*ctx_, key, &value);
236+
EXPECT_TRUE(status.ok() && !status.IsNotFound());
237+
EXPECT_EQ("test-strings-value69", value);
238+
239+
option.type = DelExOption::IFDNE;
240+
option.value = "xxxxxxxxxxxxxxxx";
241+
deleted = false;
242+
s = string_->DelEX(*ctx_, key, option, deleted);
243+
EXPECT_TRUE(s.ok());
244+
EXPECT_FALSE(s.IsNotFound());
245+
EXPECT_TRUE(deleted);
246+
status = string_->Get(*ctx_, key, &value);
247+
EXPECT_TRUE(!status.ok());
248+
EXPECT_TRUE(status.IsNotFound());
249+
EXPECT_NE("test-strings-value69", value);
250+
251+
key = "test-string-key69";
252+
value = "test-strings-value69";
253+
status = string_->Set(*ctx_, key, value);
254+
EXPECT_TRUE(status.ok());
255+
option.type = DelExOption::IFEQ;
256+
option.value = "random";
257+
deleted = false;
258+
s = string_->DelEX(*ctx_, key, option, deleted);
259+
EXPECT_TRUE(s.ok());
260+
EXPECT_FALSE(s.IsNotFound());
261+
EXPECT_FALSE(deleted);
262+
status = string_->Get(*ctx_, key, &value);
263+
EXPECT_TRUE(status.ok() && !status.IsNotFound());
264+
EXPECT_EQ("test-strings-value69", value);
265+
266+
option.type = DelExOption::IFEQ;
267+
option.value = "test-strings-value69";
268+
deleted = false;
269+
s = string_->DelEX(*ctx_, key, option, deleted);
270+
EXPECT_TRUE(s.ok());
271+
EXPECT_FALSE(s.IsNotFound());
272+
EXPECT_TRUE(deleted);
273+
status = string_->Get(*ctx_, key, &value);
274+
EXPECT_TRUE(!status.ok());
275+
EXPECT_TRUE(status.IsNotFound());
276+
EXPECT_NE("test-strings-value69", value);
277+
278+
key = "test-string-key69";
279+
value = "test-strings-value69";
280+
status = string_->Set(*ctx_, key, value);
281+
EXPECT_TRUE(status.ok());
282+
option.type = DelExOption::IFNE;
283+
option.value = "test-strings-value69";
284+
deleted = false;
285+
s = string_->DelEX(*ctx_, key, option, deleted);
286+
EXPECT_TRUE(s.ok());
287+
EXPECT_FALSE(s.IsNotFound());
288+
EXPECT_FALSE(deleted);
289+
status = string_->Get(*ctx_, key, &value);
290+
EXPECT_TRUE(status.ok() && !status.IsNotFound());
291+
EXPECT_EQ("test-strings-value69", value);
292+
293+
option.type = DelExOption::IFNE;
294+
option.value = "random";
295+
deleted = false;
296+
s = string_->DelEX(*ctx_, key, option, deleted);
297+
EXPECT_TRUE(s.ok());
298+
EXPECT_FALSE(s.IsNotFound());
299+
EXPECT_TRUE(deleted);
300+
status = string_->Get(*ctx_, key, &value);
301+
EXPECT_TRUE(!status.ok());
302+
EXPECT_TRUE(status.IsNotFound());
303+
EXPECT_NE("test-strings-value69", value);
304+
}
305+
159306
TEST_F(RedisStringTest, GetDel) {
160307
for (auto &pair : pairs_) {
161308
string_->Set(*ctx_, pair.key.ToString(), pair.value.ToString());

tests/gocase/unit/type/strings/strings_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,79 @@ func testString(t *testing.T, configs util.KvrocksServerConfigs) {
277277
require.Equal(t, "", rdb.GetDel(ctx, "foo").Val())
278278
})
279279

280+
t.Run("DelEX command no args", func(t *testing.T) {
281+
key := "test-string-key69"
282+
value := "test-strings-value69"
283+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
284+
require.Equal(t, value, rdb.Get(ctx, key).Val())
285+
286+
require.Equal(t, int64(1), rdb.Do(ctx, "DELEX", key).Val())
287+
require.Equal(t, "", rdb.Get(ctx, key).Val())
288+
289+
require.NoError(t, rdb.Do(ctx, "DelEX", key).Err())
290+
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key).Val())
291+
292+
require.Equal(t, "", rdb.Get(ctx, "random").Val())
293+
require.NoError(t, rdb.Do(ctx, "DelEX", "random").Err())
294+
require.Equal(t, int64(0), rdb.Do(ctx, "DELEX", "random").Val())
295+
})
296+
297+
t.Run("DelEX command with args", func(t *testing.T) {
298+
key := "test-string-key69"
299+
value := "Hello world"
300+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
301+
require.Equal(t, value, rdb.Get(ctx, key).Val())
302+
303+
r := rdb.Do(ctx, "DelEX", key, "random", "random", "random").Err()
304+
require.ErrorContains(t, r, "wrong number")
305+
306+
r = rdb.Do(ctx, "DelEX", key, "random", "random").Err()
307+
require.ErrorContains(t, r, "syntax error")
308+
309+
digest := "b6acb9d84a38ff74"
310+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdeq", "xxxxxxxxxxxxxxxx").Err())
311+
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifdeq", "xxxxxxxxxxxxxxxx").Val())
312+
require.Equal(t, value, rdb.Get(ctx, key).Val())
313+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdeq", digest).Err())
314+
require.Equal(t, "", rdb.Get(ctx, value).Val())
315+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
316+
require.Equal(t, int64(1), rdb.Do(ctx, "DELEX", key, "ifdeq", digest).Val())
317+
require.Equal(t, "", rdb.Get(ctx, value).Val())
318+
319+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
320+
require.Equal(t, value, rdb.Get(ctx, key).Val())
321+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdne", digest).Err())
322+
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifdne", digest).Val())
323+
require.Equal(t, value, rdb.Get(ctx, key).Val())
324+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifdne", "xxxxxxxxxxxxxxxx").Err())
325+
require.Equal(t, "", rdb.Get(ctx, value).Val())
326+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
327+
require.Equal(t, int64(1), rdb.Do(ctx, "DelEX", key, "ifdne", "xxxxxxxxxxxxxxxx").Val())
328+
require.Equal(t, "", rdb.Get(ctx, value).Val())
329+
330+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
331+
require.Equal(t, value, rdb.Get(ctx, key).Val())
332+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifeq", "random").Err())
333+
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifeq", "random").Val())
334+
require.Equal(t, value, rdb.Get(ctx, key).Val())
335+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifeq", value).Err())
336+
require.Equal(t, "", rdb.Get(ctx, value).Val())
337+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
338+
require.Equal(t, int64(1), rdb.Do(ctx, "DelEX", key, "ifeq", value).Val())
339+
require.Equal(t, "", rdb.Get(ctx, value).Val())
340+
341+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
342+
require.Equal(t, value, rdb.Get(ctx, key).Val())
343+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifne", value).Err())
344+
require.Equal(t, int64(0), rdb.Do(ctx, "DelEX", key, "ifne", value).Val())
345+
require.Equal(t, value, rdb.Get(ctx, key).Val())
346+
require.NoError(t, rdb.Do(ctx, "DelEX", key, "ifne", "random").Err())
347+
require.Equal(t, "", rdb.Get(ctx, value).Val())
348+
require.NoError(t, rdb.Set(ctx, key, value, 0).Err())
349+
require.Equal(t, int64(1), rdb.Do(ctx, "DelEX", key, "ifne", "random").Val())
350+
require.Equal(t, "", rdb.Get(ctx, value).Val())
351+
})
352+
280353
t.Run("MGET command", func(t *testing.T) {
281354
require.NoError(t, rdb.FlushDB(ctx).Err())
282355
require.NoError(t, rdb.Set(ctx, "foo", "BAR", 0).Err())

0 commit comments

Comments
 (0)