Fix (P)TTL issue with non-existing keys. Improve logging for binary values. Fix RENAME bugs

This commit is contained in:
Roman Gershman 2022-03-28 17:02:03 +03:00
parent 3e2929dfb6
commit c98bc934f2
4 changed files with 61 additions and 31 deletions

View File

@ -2,10 +2,10 @@
// See LICENSE for licensing terms.
//
#include <absl/strings/escaping.h>
#include <absl/strings/str_cat.h>
#include "base/logging.h"
#include "facade/conn_context.h"
#include "facade/dragonfly_connection.h"
#include "facade/error.h"
@ -112,16 +112,17 @@ RedisReplyBuilder* ConnectionContext::operator->() {
} // namespace facade
namespace std {
using facade::ArgS;
ostream& operator<<(ostream& os, facade::CmdArgList ras) {
os << "[";
if (!ras.empty()) {
for (size_t i = 0; i < ras.size() - 1; ++i) {
os << facade::ArgS(ras, i) << ",";
os << absl::CHexEscape(ArgS(ras, i)) << ",";
}
os << facade::ArgS(ras, ras.size() - 1);
os << absl::CHexEscape(ArgS(ras, ras.size() - 1));
}
os << "]";

View File

@ -321,14 +321,18 @@ void GenericFamily::TtlGeneric(CmdArgList args, ConnectionContext* cntx, TimeUni
if (result) {
long ttl = (unit == TimeUnit::SEC) ? (result.value() + 500) / 1000 : result.value();
(*cntx)->SendLong(ttl);
} else {
switch (result.status()) {
case OpStatus::KEY_NOTFOUND:
(*cntx)->SendLong(-1);
break;
default:
(*cntx)->SendLong(-2);
}
return;
}
switch (result.status()) {
case OpStatus::KEY_NOTFOUND:
(*cntx)->SendLong(-2);
break;
default:
LOG_IF(ERROR, result.status() != OpStatus::SKIPPED)
<< "Unexpected status " << result.status();
(*cntx)->SendLong(-1);
break;
}
}
@ -528,7 +532,7 @@ OpResult<uint32_t> GenericFamily::OpExists(const OpArgs& op_args, ArgSlice keys)
OpResult<void> GenericFamily::OpRen(const OpArgs& op_args, string_view from, string_view to,
bool skip_exists) {
auto& db_slice = op_args.shard->db_slice();
auto [from_it, expire_it] = db_slice.FindExt(op_args.db_ind, from);
auto [from_it, from_expire] = db_slice.FindExt(op_args.db_ind, from);
if (!IsValid(from_it))
return OpStatus::KEY_NOTFOUND;
@ -538,25 +542,32 @@ OpResult<void> GenericFamily::OpRen(const OpArgs& op_args, string_view from, str
return OpStatus::KEY_EXISTS;
}
uint64_t exp_ts = IsValid(expire_it) ? expire_it->second.duration() : 0;
if (IsValid(to_it)) {
to_it->second = std::move(from_it->second);
from_it->second.SetExpire(IsValid(expire_it));
uint64_t exp_ts =
IsValid(from_expire) ? db_slice.expire_base() + from_expire->second.duration() : 0;
if (IsValid(to_expire)) {
to_it->second.SetExpire(true);
to_expire->second.Set(exp_ts);
} else {
to_it->second.SetExpire(false);
db_slice.Expire(op_args.db_ind, to_it, exp_ts + db_slice.expire_base());
}
// we keep the value we want to move.
PrimeValue from_obj = std::move(from_it->second);
// Restore the expire flag on 'from'.
from_it->second.SetExpire(IsValid(from_expire));
if (IsValid(to_it)) {
to_it->second = std::move(from_obj);
to_it->second.SetExpire(IsValid(to_expire)); // keep the expire flag on 'to'.
// It is guaranteed that Expire() call does not erase the element because then
// from_it would be invalid. Therefore, it Expire does not invalidate any iterators and
// we can delete via from_it.
db_slice.Expire(op_args.db_ind, to_it, exp_ts);
CHECK(db_slice.Del(op_args.db_ind, from_it));
} else {
db_slice.AddNew(op_args.db_ind, to, std::move(from_it->second),
exp_ts + db_slice.expire_base());
// Need search again since the container might invalidate the iterators.
from_it = db_slice.FindExt(op_args.db_ind, from).first;
// Here we first delete from_it because AddNew below could invalidate from_it.
// On the other hand, AddNew does not rely on the iterators - this is why we keep
// the value in `from_obj`.
CHECK(db_slice.Del(op_args.db_ind, from_it));
db_slice.AddNew(op_args.db_ind, to, std::move(from_obj), exp_ts);
}
CHECK(db_slice.Del(op_args.db_ind, from_it));
return OpStatus::OK;
}

View File

@ -1,4 +1,4 @@
// Copyright 2021, Roman Gershman. All rights reserved.
// Copyright 2022, Roman Gershman. All rights reserved.
// See LICENSE for licensing terms.
//
@ -79,6 +79,14 @@ TEST_F(GenericFamilyTest, Del) {
del_fb.join();
}
TEST_F(GenericFamilyTest, TTL) {
EXPECT_EQ(-2, CheckedInt({"ttl", "foo"}));
EXPECT_EQ(-2, CheckedInt({"pttl", "foo"}));
Run({"set", "foo", "bar"});
EXPECT_EQ(-1, CheckedInt({"ttl", "foo"}));
EXPECT_EQ(-1, CheckedInt({"pttl", "foo"}));
}
TEST_F(GenericFamilyTest, Exists) {
Run({"mset", "x", "0", "y", "1"});
auto resp = Run({"exists", "x", "y", "x"});
@ -128,4 +136,14 @@ TEST_F(GenericFamilyTest, Rename) {
}
TEST_F(GenericFamilyTest, RenameBinary) {
const char kKey1[] = "\x01\x02\x03\x04";
const char kKey2[] = "\x05\x06\x07\x08";
Run({"set", kKey1, "bar"});
Run({"rename", kKey1, kKey2});
EXPECT_THAT(Run({"get", kKey1}), ElementsAre(ArgType(RespExpr::NIL)));
EXPECT_THAT(Run({"get", kKey2}), RespEq("bar"));
}
} // namespace dfly

View File

@ -393,7 +393,7 @@ void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx)
string_view cmd_name{cid->name()};
if (cntx->req_auth && !cntx->authenticated) {
if (cmd_name != "AUTH") {
if (cmd_name != "AUTH" && cmd_name != "QUIT") {
return (*cntx)->SendError("-NOAUTH Authentication required.");
}
}