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

View File

@ -321,14 +321,18 @@ void GenericFamily::TtlGeneric(CmdArgList args, ConnectionContext* cntx, TimeUni
if (result) { if (result) {
long ttl = (unit == TimeUnit::SEC) ? (result.value() + 500) / 1000 : result.value(); long ttl = (unit == TimeUnit::SEC) ? (result.value() + 500) / 1000 : result.value();
(*cntx)->SendLong(ttl); (*cntx)->SendLong(ttl);
} else { return;
switch (result.status()) { }
case OpStatus::KEY_NOTFOUND:
(*cntx)->SendLong(-1); switch (result.status()) {
break; case OpStatus::KEY_NOTFOUND:
default: (*cntx)->SendLong(-2);
(*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, OpResult<void> GenericFamily::OpRen(const OpArgs& op_args, string_view from, string_view to,
bool skip_exists) { bool skip_exists) {
auto& db_slice = op_args.shard->db_slice(); 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)) if (!IsValid(from_it))
return OpStatus::KEY_NOTFOUND; return OpStatus::KEY_NOTFOUND;
@ -538,25 +542,32 @@ OpResult<void> GenericFamily::OpRen(const OpArgs& op_args, string_view from, str
return OpStatus::KEY_EXISTS; return OpStatus::KEY_EXISTS;
} }
uint64_t exp_ts = IsValid(expire_it) ? expire_it->second.duration() : 0; uint64_t exp_ts =
if (IsValid(to_it)) { IsValid(from_expire) ? db_slice.expire_base() + from_expire->second.duration() : 0;
to_it->second = std::move(from_it->second);
from_it->second.SetExpire(IsValid(expire_it));
if (IsValid(to_expire)) { // we keep the value we want to move.
to_it->second.SetExpire(true); PrimeValue from_obj = std::move(from_it->second);
to_expire->second.Set(exp_ts);
} else { // Restore the expire flag on 'from'.
to_it->second.SetExpire(false); from_it->second.SetExpire(IsValid(from_expire));
db_slice.Expire(op_args.db_ind, to_it, exp_ts + db_slice.expire_base());
} 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 { } else {
db_slice.AddNew(op_args.db_ind, to, std::move(from_it->second), // Here we first delete from_it because AddNew below could invalidate from_it.
exp_ts + db_slice.expire_base()); // On the other hand, AddNew does not rely on the iterators - this is why we keep
// Need search again since the container might invalidate the iterators. // the value in `from_obj`.
from_it = db_slice.FindExt(op_args.db_ind, from).first; 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; 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. // See LICENSE for licensing terms.
// //
@ -79,6 +79,14 @@ TEST_F(GenericFamilyTest, Del) {
del_fb.join(); 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) { TEST_F(GenericFamilyTest, Exists) {
Run({"mset", "x", "0", "y", "1"}); Run({"mset", "x", "0", "y", "1"});
auto resp = Run({"exists", "x", "y", "x"}); 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 } // namespace dfly

View File

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