Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the GETEX command #961

Merged
merged 26 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c1cc1f
impl getex
Oct 8, 2022
b0b4ec6
put the ttl argument parse into a single function
Oct 9, 2022
f7a4876
fix parst ttl function
Oct 9, 2022
11b2946
Release comment && fix argument parse error
Oct 9, 2022
5ccbdbe
Merge branch 'unstable' into patch-1
maochongxin Oct 9, 2022
c220c7d
add white_list argument to decouple ttl parsing and redis functions
Oct 10, 2022
af62eaf
Merge branch 'patch-1' of https://github.com/maochongxin/incubator-kv…
Oct 10, 2022
dc82f57
fix
Oct 10, 2022
d8af300
Merge branch 'unstable' into patch-1
maochongxin Oct 10, 2022
c5d94fe
optimization impl
maochongxin Oct 10, 2022
40164c1
Update src/util.cc
maochongxin Oct 10, 2022
bac282e
optimization impl && fix indent
maochongxin Oct 10, 2022
5cdcdab
fix indent
maochongxin Oct 10, 2022
300c6d7
fix cpplint
maochongxin Oct 11, 2022
67198fb
Merge branch 'unstable' into patch-1
maochongxin Oct 11, 2022
2653db4
Update src/util.cc
maochongxin Oct 11, 2022
6fc1ccd
Update src/redis_cmd.cc
maochongxin Oct 11, 2022
9eaa930
Update src/redis_cmd.cc
maochongxin Oct 11, 2022
f95a9af
Update src/redis_cmd.cc
maochongxin Oct 11, 2022
cb4aeb2
Update src/redis_cmd.cc
maochongxin Oct 11, 2022
e46415e
Merge branch 'unstable' into patch-1
maochongxin Oct 11, 2022
07e5a96
Merge branch 'unstable' into patch-1
PragmaTwice Oct 11, 2022
af4316d
merge unstable
maochongxin Oct 11, 2022
0b71a31
Remove the command number check test case
git-hulk Oct 11, 2022
2ba89f3
strings test add getex case
maochongxin Oct 12, 2022
fcf9c5c
Merge branch 'unstable' into patch-1
maochongxin Oct 12, 2022
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
181 changes: 121 additions & 60 deletions src/redis_cmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,68 @@ AuthResult AuthenticateUser(Connection *conn, Config* config, const std::string&
return AuthResult::OK;
}

Status ParseTtlHelper(const std::vector<std::string> &args, int *result) {
maochongxin marked this conversation as resolved.
Show resolved Hide resolved
int ttl = 0;
int64_t expire = 0;
bool last_arg = false;
for (size_t i = 0; i < args.size(); i++) {
last_arg = (i == args.size() - 1);
std::string opt = Util::ToLower(args[i]);
if (opt == "ex" && !ttl && !last_arg) {
auto parse_result = ParseInt<int>(args[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
ttl = *parse_result;
if (ttl <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
} else if (opt == "exat" && !ttl && !expire && !last_arg) {
auto parse_result = ParseInt<int64_t>(args[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
expire = *parse_result;
if (expire <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
} else if (opt == "pxat" && !ttl && !expire && !last_arg) {
auto parse_result = ParseInt<uint64_t>(args[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
uint64_t expire_ms = *parse_result;
if (expire_ms <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
if (expire_ms < 1000) {
expire = 1;
} else {
expire = static_cast<int64_t>(expire_ms / 1000);
}
} else if (opt == "px" && !ttl && !last_arg) {
int64_t ttl_ms = 0;
auto parse_result = ParseInt<int64_t>(args[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
ttl_ms = *parse_result;
if (ttl_ms <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
if (ttl_ms > 0 && ttl_ms < 1000) {
ttl = 1; // round up the pttl to second
} else {
ttl = static_cast<int>(ttl_ms / 1000);
}
} else if (opt == "persist" || opt == "nx"|| opt == "xx") {
// pass
PragmaTwice marked this conversation as resolved.
Show resolved Hide resolved
} else {
return Status(Status::NotOK, errInvalidSyntax);
}
}
if (!ttl && expire) {
int64_t now;
rocksdb::Env::Default()->GetCurrentTime(&now);
*result = expire - now;
} else {
*result = ttl;
}
return Status::OK();
}

class CommandAuth : public Commander {
public:
Status Execute(Server *svr, Connection *conn, std::string *output) override {
Expand Down Expand Up @@ -318,6 +380,48 @@ class CommandGet : public Commander {
}
};

class CommandGetEx : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (std::find_if(args.begin(), args.end(), [](const std::string& str) -> bool {
PragmaTwice marked this conversation as resolved.
Show resolved Hide resolved
return Util::ToLower(str) == "persist";
PragmaTwice marked this conversation as resolved.
Show resolved Hide resolved
}) != std::end(args)) {
if (args.size() > 3) {
return Status(Status::NotOK, errInvalidSyntax);
}
} else {
auto s = ParseTtlHelper(std::vector<std::string>(args.begin() + 2, args.end()), &ttl_);
if (!s.IsOK()) {
return s;
}
}
return Commander::Parse(args);
}
Status Execute(Server *svr, Connection *conn, std::string *output) override {

std::string value;
Redis::String string_db(svr->storage_, conn->GetNamespace());
rocksdb::Status s = string_db.GetEx(args_[1], &value, ttl_);

// The IsInvalidArgument error means the key type maybe a bitmap
// which we need to fall back to the bitmap's GetString according
// to the `max-bitmap-to-string-mb` configuration.
if (s.IsInvalidArgument()) {
Config *config = svr->GetConfig();
uint32_t max_btos_size = static_cast<uint32_t>(config->max_bitmap_to_string_mb) * MiB;
Redis::Bitmap bitmap_db(svr->storage_, conn->GetNamespace());
s = bitmap_db.GetString(args_[1], max_btos_size, &value);
}
if (!s.ok() && !s.IsNotFound()) {
return Status(Status::RedisExecErr, s.ToString());
}
*output = s.IsNotFound() ? Redis::NilString() : Redis::BulkString(value);
return Status::OK();
}
private:
int ttl_ = 0;
};

class CommandStrlen: public Commander {
public:
Status Execute(Server *svr, Connection *conn, std::string *output) override {
Expand Down Expand Up @@ -471,82 +575,39 @@ class CommandAppend: public Commander {
class CommandSet : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
bool last_arg;
for (size_t i = 3; i < args.size(); i++) {
last_arg = (i == args.size()-1);
std::string opt = Util::ToLower(args[i]);
if (opt == "nx" && !xx_) {
nx_ = true;
} else if (opt == "xx" && !nx_) {
xx_ = true;
} else if (opt == "ex" && !ttl_ && !last_arg) {
auto parse_result = ParseInt<int>(args_[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
ttl_ = *parse_result;
if (ttl_ <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
} else if (opt == "exat" && !ttl_ && !expire_ && !last_arg) {
auto parse_result = ParseInt<int64_t>(args_[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
expire_ = *parse_result;
if (expire_ <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
} else if (opt == "pxat" && !ttl_ && !expire_ && !last_arg) {
auto parse_result = ParseInt<uint64_t>(args[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
uint64_t expire_ms = *parse_result;
if (expire_ms <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
if (expire_ms < 1000) {
expire_ = 1;
} else {
expire_ = static_cast<int64_t>(expire_ms/1000);
}
} else if (opt == "px" && !ttl_ && !last_arg) {
int64_t ttl_ms = 0;
auto parse_result = ParseInt<int64_t>(args_[++i], 10);
if (!parse_result) {
return Status(Status::RedisParseErr, errValueNotInteger);
}
ttl_ms = *parse_result;
if (ttl_ms <= 0) return Status(Status::RedisParseErr, errInvalidExpireTime);
if (ttl_ms > 0 && ttl_ms < 1000) {
ttl_ = 1; // round up the pttl to second
} else {
ttl_ = static_cast<int>(ttl_ms/1000);
std::for_each(args.begin(), args.end(), [this] (const std::string& str) {
if (Util::ToLower(str) == "nx") {
nx_ = true;
} else if (Util::ToLower(str) == "xx") {
xx_ = true;
}
} else {
});
if (xx_ && nx_) {
return Status(Status::NotOK, errInvalidSyntax);
}
}
auto s = ParseTtlHelper(std::vector<std::string>(args.begin() + 3, args.end()), &ttl_);
if (!s.IsOK()) { return s; }
return Commander::Parse(args);
}
Status Execute(Server *svr, Connection *conn, std::string *output) override {
int ret;
Redis::String string_db(svr->storage_, conn->GetNamespace());
rocksdb::Status s;

if (!ttl_ && expire_) {
int64_t now;
rocksdb::Env::Default()->GetCurrentTime(&now);
ttl_ = expire_ - now;
if (ttl_ <= 0) {
string_db.Del(args_[1]);
*output = Redis::SimpleString("OK");
return Status::OK();
}
if (ttl_ < 0) {
string_db.Del(args_[1]);
*output = Redis::SimpleString("OK");
return Status::OK();
}

if (nx_) {
s = string_db.SetNX(args_[1], args_[2], ttl_, &ret);
s = string_db.SetNX(args_[1], args_[2], ttl_, &ret);
} else if (xx_) {
s = string_db.SetXX(args_[1], args_[2], ttl_, &ret);
} else {
s = string_db.SetEX(args_[1], args_[2], ttl_);
s = string_db.SetEX(args_[1], args_[2], ttl_);
}

if (!s.ok()) {
return Status(Status::RedisExecErr, s.ToString());
}
Expand All @@ -562,7 +623,6 @@ class CommandSet : public Commander {
bool xx_ = false;
bool nx_ = false;
int ttl_ = 0;
int64_t expire_ = 0;
};

class CommandSetEX : public Commander {
Expand Down Expand Up @@ -5890,6 +5950,7 @@ CommandAttributes redisCommandTable[] = {
ADD_CMD("unlink", -2, "write", 1, -1, 1, CommandDel),

ADD_CMD("get", 2, "read-only", 1, 1, 1, CommandGet),
ADD_CMD("getex", -2, "write", 1,1,1, CommandGetEx),
ADD_CMD("strlen", 2, "read-only", 1, 1, 1, CommandStrlen),
ADD_CMD("getset", 3, "write", 1, 1, 1, CommandGetSet),
ADD_CMD("getrange", 4, "read-only", 1, 1, 1, CommandGetRange),
Expand Down
28 changes: 28 additions & 0 deletions src/redis_string.cc
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,34 @@ rocksdb::Status String::Get(const std::string &user_key, std::string *value) {
return getValue(ns_key, value);
}

rocksdb::Status String::GetEx(const std::string &user_key, std::string *value, int ttl) {
uint32_t expire = 0;
if (ttl > 0) {
int64_t now;
rocksdb::Env::Default()->GetCurrentTime(&now);
expire = uint32_t(now) + ttl;
}
std::string ns_key;
AppendNamespacePrefix(user_key, &ns_key);

LockGuard guard(storage_->GetLockManager(), ns_key);
rocksdb::Status s = getValue(ns_key, value);
if (!s.ok() && s.IsNotFound()) return s;

std::string raw_data;
Metadata metadata(kRedisString, false);
metadata.expire = expire;
metadata.Encode(&raw_data);
raw_data.append(value->data(), value->size());
rocksdb::WriteBatch batch;
WriteBatchLogData log_data(kRedisString);
batch.PutLogData(log_data.Encode());
batch.Put(metadata_cf_handle_, ns_key, raw_data);
s = storage_->Write(storage_->DefaultWriteOptions(), &batch);
if (!s.ok()) return s;
return rocksdb::Status::OK();
}

rocksdb::Status String::GetSet(const std::string &user_key, const std::string &new_value, std::string *old_value) {
std::string ns_key;
AppendNamespacePrefix(user_key, &ns_key);
Expand Down
1 change: 1 addition & 0 deletions src/redis_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class String : public Database {
explicit String(Engine::Storage *storage, const std::string &ns) : Database(storage, ns) {}
rocksdb::Status Append(const std::string &user_key, const std::string &value, int *ret);
rocksdb::Status Get(const std::string &user_key, std::string *value);
rocksdb::Status GetEx(const std::string &user_key, std::string *value, int ttl);
rocksdb::Status GetSet(const std::string &user_key, const std::string &new_value, std::string *old_value);
rocksdb::Status GetDel(const std::string &user_key, std::string *value);
rocksdb::Status Set(const std::string &user_key, const std::string &value);
Expand Down
96 changes: 48 additions & 48 deletions tests/tcl/tests/unit/type/string.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -125,60 +125,60 @@ start_server {tags {"string"}} {
assert_equal 20 [r get x]
}

# test "GETEX EX option" {
# r del foo
# r set foo bar
# r getex foo ex 10
# assert_range [r ttl foo] 5 10
# }
test "GETEX EX option" {
r del foo
r set foo bar
r getex foo ex 10
assert_range [r ttl foo] 5 10
}

# test "GETEX PX option" {
# r del foo
# r set foo bar
# r getex foo px 10000
# assert_range [r pttl foo] 5000 10000
# }
test "GETEX PX option" {
r del foo
r set foo bar
r getex foo px 10000
assert_range [r pttl foo] 5000 10000
}

# test "GETEX EXAT option" {
# r del foo
# r set foo bar
# r getex foo exat [expr [clock seconds] + 10]
# assert_range [r ttl foo] 5 10
# }
test "GETEX EXAT option" {
r del foo
r set foo bar
r getex foo exat [expr [clock seconds] + 10]
assert_range [r ttl foo] 5 10
}

# test "GETEX PXAT option" {
# r del foo
# r set foo bar
# r getex foo pxat [expr [clock milliseconds] + 10000]
# assert_range [r pttl foo] 5000 10000
# }
test "GETEX PXAT option" {
r del foo
r set foo bar
r getex foo pxat [expr [clock milliseconds] + 10000]
assert_range [r pttl foo] 5000 10000
}

# test "GETEX PERSIST option" {
# r del foo
# r set foo bar ex 10
# assert_range [r ttl foo] 5 10
# r getex foo persist
# assert_equal -1 [r ttl foo]
# }
test "GETEX PERSIST option" {
r del foo
r set foo bar ex 10
assert_range [r ttl foo] 5 10
r getex foo persist
assert_equal -1 [r ttl foo]
}

# test "GETEX no option" {
# r del foo
# r set foo bar
# r getex foo
# assert_equal bar [r getex foo]
# }
test "GETEX no option" {
r del foo
r set foo bar
r getex foo
assert_equal bar [r getex foo]
}

test "GETEX syntax errors" {
set ex {}
catch {r getex foo non-existent-option} ex
set ex
} {*syntax*}

# test "GETEX syntax errors" {
# set ex {}
# catch {r getex foo non-existent-option} ex
# set ex
# } {*syntax*}

# test "GETEX no arguments" {
# set ex {}
# catch {r getex} ex
# set ex
# } {*wrong number of arguments*}
test "GETEX no arguments" {
set ex {}
catch {r getex} ex
set ex
} {*wrong number of arguments*}

test "GETDEL command" {
r del foo
Expand Down