Skip to content

Commit

Permalink
feat: add fieldttl command that returns the ttl of the member (dragon…
Browse files Browse the repository at this point in the history
…flydb#2026)

Currently implemented only for saddex.

Signed-off-by: Roman Gershman <[email protected]>
  • Loading branch information
romange authored and azuredream committed Oct 17, 2023
1 parent 03e2771 commit d364a6d
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 50 deletions.
1 change: 1 addition & 0 deletions src/core/dense_set.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ void DenseSet::IteratorBase::Advance() {
++curr_list_;
if (curr_list_ == owner_->entries_.end()) {
curr_entry_ = nullptr;
owner_ = nullptr;
return;
}
owner_->ExpireIfNeeded(nullptr, &(*curr_list_));
Expand Down
54 changes: 7 additions & 47 deletions src/core/string_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class StringSet : public DenseSet {
iterator() : IteratorBase() {
}

iterator(DenseSet* set, bool is_end) : IteratorBase(set, is_end) {
iterator(DenseSet* set) : IteratorBase(set, false) {
}

iterator& operator++() {
Expand All @@ -67,7 +67,10 @@ class StringSet : public DenseSet {
}

bool operator==(const iterator& b) const {
return curr_list_ == b.curr_list_;
if (owner_ == nullptr && b.owner_ == nullptr) { // to allow comparison with end()
return true;
}
return owner_ == b.owner_ && curr_entry_ == b.curr_entry_;
}

bool operator!=(const iterator& b) const {
Expand All @@ -86,57 +89,14 @@ class StringSet : public DenseSet {
using IteratorBase::HasExpiry;
};

class const_iterator : private IteratorBase {
public:
using iterator_category = std::input_iterator_tag;
using value_type = const char*;
using pointer = value_type*;
using reference = value_type&;

const_iterator() : IteratorBase() {
}

const_iterator(const DenseSet* set, bool is_end) : IteratorBase(set, is_end) {
}

const_iterator& operator++() {
Advance();
return *this;
}

bool operator==(const const_iterator& b) const {
return curr_list_ == b.curr_list_;
}

bool operator!=(const const_iterator& b) const {
return !(*this == b);
}

value_type operator*() const {
return (value_type)curr_entry_->GetObject();
}

value_type operator->() const {
return (value_type)curr_entry_->GetObject();
}
};

iterator begin() {
return iterator{this, false};
return iterator{this};
}

iterator end() {
return iterator{this, true};
return iterator{};
}
/*
const_iterator cbegin() const {
return const_iterator{this, false};
}

const_iterator cend() const {
return const_iterator{this, true};
}
*/
uint32_t Scan(uint32_t, const std::function<void(sds)>&) const;
iterator Find(std::string_view member) {
return iterator{FindIt(&member, 1)};
Expand Down
42 changes: 42 additions & 0 deletions src/server/generic_family.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extern "C" {
#include "server/rdb_extensions.h"
#include "server/rdb_load.h"
#include "server/rdb_save.h"
#include "server/set_family.h"
#include "server/transaction.h"
#include "util/varz.h"

Expand Down Expand Up @@ -618,6 +619,27 @@ OpStatus OpExpire(const OpArgs& op_args, string_view key, const DbSlice::ExpireP
return res.status();
}

// returns -2 if the key was not found, -3 if the field was not found,
// -1 if ttl on the field was not found.
OpResult<long> OpFieldTtl(Transaction* t, EngineShard* shard, string_view key, string_view field) {
auto& db_slice = shard->db_slice();
const DbContext& db_cntx = t->GetDbContext();
auto [it, expire_it] = db_slice.FindExt(db_cntx, key);
if (!IsValid(it))
return -2;

if (it->second.ObjType() != OBJ_SET) // TODO: to finish for hashes.
return OpStatus::WRONG_TYPE;

if (it->second.ObjType() == OBJ_SET) {
int32_t res = SetFamily::FieldExpireTime(db_cntx, it->second, field);
return res <= 0 ? res : int32_t(res - MemberTimeSeconds(db_cntx.time_now_ms));
}

// TODO: to finish with hash family.
return OpStatus::INVALID_VALUE;
}

} // namespace

void GenericFamily::Init(util::ProactorPool* pp) {
Expand Down Expand Up @@ -1060,6 +1082,24 @@ void GenericFamily::Restore(CmdArgList args, ConnectionContext* cntx) {
}
}

// Returns -2 if key not found, WRONG_TYPE if key is not a set or hash
// -1 if the field does not have associated TTL on it, and -3 if field is not found.
void GenericFamily::FieldTtl(CmdArgList args, ConnectionContext* cntx) {
string_view key = ArgS(args, 0);
string_view field = ArgS(args, 1);

auto cb = [&](Transaction* t, EngineShard* shard) { return OpFieldTtl(t, shard, key, field); };

OpResult<long> result = cntx->transaction->ScheduleSingleHopT(std::move(cb));

if (result) {
(*cntx)->SendLong(*result);
return;
}

(*cntx)->SendError(result.status());
}

void GenericFamily::Move(CmdArgList args, ConnectionContext* cntx) {
string_view key = ArgS(args, 0);
string_view target_db_sv = ArgS(args, 1);
Expand Down Expand Up @@ -1455,6 +1495,7 @@ constexpr uint32_t kSelect = FAST | CONNECTION;
constexpr uint32_t kScan = KEYSPACE | READ | SLOW;
constexpr uint32_t kTTL = KEYSPACE | READ | FAST;
constexpr uint32_t kPTTL = KEYSPACE | READ | FAST;
constexpr uint32_t kFieldTtl = KEYSPACE | READ | FAST;
constexpr uint32_t kTime = FAST;
constexpr uint32_t kType = KEYSPACE | READ | FAST;
constexpr uint32_t kDump = KEYSPACE | READ | SLOW;
Expand Down Expand Up @@ -1495,6 +1536,7 @@ void GenericFamily::Register(CommandRegistry* registry) {
<< CI{"SCAN", CO::READONLY | CO::FAST | CO::LOADING, -2, 0, 0, 0, acl::kScan}.HFUNC(Scan)
<< CI{"TTL", CO::READONLY | CO::FAST, 2, 1, 1, 1, acl::kTTL}.HFUNC(Ttl)
<< CI{"PTTL", CO::READONLY | CO::FAST, 2, 1, 1, 1, acl::kPTTL}.HFUNC(Pttl)
<< CI{"FIELDTTL", CO::READONLY | CO::FAST, 3, 1, 1, 1, acl::kFieldTtl}.HFUNC(FieldTtl)
<< CI{"TIME", CO::LOADING | CO::FAST, 1, 0, 0, 0, acl::kTime}.HFUNC(Time)
<< CI{"TYPE", CO::READONLY | CO::FAST | CO::LOADING, 2, 1, 1, 1, acl::kType}.HFUNC(Type)
<< CI{"DUMP", CO::READONLY, 2, 1, 1, 1, acl::kDump}.HFUNC(Dump)
Expand Down
1 change: 1 addition & 0 deletions src/server/generic_family.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class GenericFamily {
static void Type(CmdArgList args, ConnectionContext* cntx);
static void Dump(CmdArgList args, ConnectionContext* cntx);
static void Restore(CmdArgList args, ConnectionContext* cntx);
static void FieldTtl(CmdArgList args, ConnectionContext* cntx);

static OpResult<void> RenameGeneric(CmdArgList args, bool skip_exist_dest,
ConnectionContext* cntx);
Expand Down
17 changes: 17 additions & 0 deletions src/server/generic_family_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -602,4 +602,21 @@ TEST_F(GenericFamilyTest, Info) {
EXPECT_EQ(1, get_rdb_changes_since_last_save(resp.GetString()));
}

TEST_F(GenericFamilyTest, FieldTtl) {
TEST_current_time_ms = kMemberExpiryBase * 1000; // to reset to test time.
EXPECT_THAT(Run({"saddex", "key", "1", "val1"}), IntArg(1));
EXPECT_THAT(Run({"saddex", "key", "2", "val2"}), IntArg(1));
EXPECT_EQ(-2, CheckedInt({"fieldttl", "nokey", "val1"})); // key not found
EXPECT_EQ(-3, CheckedInt({"fieldttl", "key", "bar"})); // field not found
EXPECT_EQ(1, CheckedInt({"fieldttl", "key", "val1"}));
EXPECT_EQ(2, CheckedInt({"fieldttl", "key", "val2"}));

AdvanceTime(1100);
EXPECT_EQ(-3, CheckedInt({"fieldttl", "key", "val1"}));
EXPECT_EQ(1, CheckedInt({"fieldttl", "key", "val2"}));

Run({"set", "str", "val"});
EXPECT_THAT(Run({"fieldttl", "str", "bar"}), ErrArg("wrong"));
}

} // namespace dfly
39 changes: 36 additions & 3 deletions src/server/set_family.cc
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,31 @@ bool IsInSet(const DbContext& db_context, const SetType& st, string_view member)
}
}

// returns -3 if member is not found, -1 if no ttl is associated with this member.
int32_t GetExpiry(const DbContext& db_context, const SetType& st, string_view member) {
if (st.second == kEncodingIntSet) {
long long llval;
if (!string2ll(member.data(), member.size(), &llval))
return -3;

return -1;
}

if (IsDenseEncoding(st)) {
StringSet* ss = (StringSet*)st.first;
ss->set_time(MemberTimeSeconds(db_context.time_now_ms));

auto it = ss->Find(member);
if (it == ss->end())
return -3;

return it.ExpiryTime();
} else {
// Old encoding, does not support expiry.
return -1;
}
}

void FindInSet(StringVec& memberships, const DbContext& db_context, const SetType& st,
const vector<string_view>& members) {
for (const auto& member : members) {
Expand Down Expand Up @@ -1494,9 +1519,9 @@ void SAddEx(CmdArgList args, ConnectionContext* cntx) {
return (*cntx)->SendError(kInvalidIntErr);
}

vector<string_view> vals(args.size() - 3);
for (size_t i = 3; i < args.size(); ++i) {
vals[i - 3] = ArgS(args, i);
vector<string_view> vals(args.size() - 2);
for (size_t i = 2; i < args.size(); ++i) {
vals[i - 2] = ArgS(args, i);
}

ArgSlice arg_slice{vals.data(), vals.size()};
Expand Down Expand Up @@ -1628,4 +1653,12 @@ void SetFamily::ConvertTo(const intset* src, dict* dest) {
}
}

int32_t SetFamily::FieldExpireTime(const DbContext& db_context, const PrimeValue& pv,
std::string_view field) {
DCHECK_EQ(OBJ_SET, pv.ObjType());

SetType st{pv.RObjPtr(), pv.Encoding()};
return GetExpiry(db_context, st, field);
}

} // namespace dfly
6 changes: 6 additions & 0 deletions src/server/set_family.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "facade/op_status.h"
#include "server/common.h"
#include "server/table.h"

typedef struct intset intset;
typedef struct redisObject robj;
Expand All @@ -30,6 +31,11 @@ class SetFamily {
// Returns true if succeeded, false on OOM.
static bool ConvertToStrSet(const intset* is, size_t expected_len, robj* dest);

// returns expiry time in seconds since kMemberExpiryBase date.
// returns -3 if field was not found, -1 if no ttl is associated with the item.
static int32_t FieldExpireTime(const DbContext& db_context, const PrimeValue& pv,
std::string_view field);

private:
};

Expand Down

0 comments on commit d364a6d

Please sign in to comment.