-
Notifications
You must be signed in to change notification settings - Fork 991
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
feat: add HEXPIRE and FIELDEXPIRE #3842
Changes from 6 commits
2814994
34ebd95
9f77722
e75db1a
e239803
bd88ad0
1a49875
3933f8b
0538772
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
#include <boost/operators.hpp> | ||
#include <optional> | ||
|
||
#include "facade/cmd_arg_parser.h" | ||
#include "facade/reply_builder.h" | ||
|
||
extern "C" { | ||
|
@@ -44,6 +45,7 @@ using namespace facade; | |
|
||
namespace { | ||
|
||
constexpr uint32_t kMaxTtl = (1UL << 26); | ||
constexpr size_t DUMP_FOOTER_SIZE = sizeof(uint64_t) + sizeof(uint16_t); // version number and crc | ||
|
||
std::optional<RdbVersion> GetRdbVersion(std::string_view msg) { | ||
|
@@ -672,6 +674,24 @@ OpStatus OpExpire(const OpArgs& op_args, string_view key, const DbSlice::ExpireP | |
return res.status(); | ||
} | ||
|
||
OpResult<vector<long>> OpFieldExpire(const OpArgs& op_args, string_view key, uint32_t ttl_sec, | ||
CmdArgList values) { | ||
auto& db_slice = op_args.GetDbSlice(); | ||
auto [it, expire_it, auto_updater] = db_slice.FindMutable(op_args.db_cntx, key); | ||
|
||
if (!IsValid(it) || (it->second.ObjType() != OBJ_SET && it->second.ObjType() != OBJ_HASH)) { | ||
std::vector<long> res(values.size(), -2); | ||
return res; | ||
} | ||
|
||
PrimeValue& pv = it->second; | ||
if (pv.ObjType() == OBJ_SET) { | ||
return SetFamily::SetFieldsExpireTime(op_args, ttl_sec, values, pv); | ||
} else { | ||
return HSetFamily::SetFieldsExpireTime(op_args, ttl_sec, key, values, pv); | ||
} | ||
} | ||
|
||
// 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) { | ||
|
@@ -1261,6 +1281,33 @@ void GenericFamily::Restore(CmdArgList args, ConnectionContext* cntx) { | |
} | ||
} | ||
|
||
void GenericFamily::FieldExpire(CmdArgList args, ConnectionContext* cntx) { | ||
CmdArgParser parser{args}; | ||
string_view key = parser.Next(); | ||
string_view ttl_str = parser.Next(); | ||
uint32_t ttl_sec; | ||
if (!absl::SimpleAtoi(ttl_str, &ttl_sec) || ttl_sec == 0 || ttl_sec > kMaxTtl) { | ||
return cntx->SendError(kInvalidIntErr); | ||
} | ||
CmdArgList fields = parser.Tail(); | ||
NegatioN marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
auto cb = [&](Transaction* t, EngineShard* shard) { | ||
return OpFieldExpire(t->GetOpArgs(shard), key, ttl_sec, fields); | ||
}; | ||
|
||
OpResult<vector<long>> result = cntx->transaction->ScheduleSingleHopT(std::move(cb)); | ||
auto* rb = static_cast<RedisReplyBuilder*>(cntx->reply_builder()); | ||
if (result) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a function but its fine :) |
||
rb->StartArray(result->size()); | ||
const auto& array = result.value(); | ||
for (const auto& v : array) { | ||
rb->SendLong(v); | ||
} | ||
} else { | ||
cntx->SendError(result.status()); | ||
} | ||
} | ||
|
||
// 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) { | ||
|
@@ -1763,6 +1810,7 @@ constexpr uint32_t kMove = KEYSPACE | WRITE | FAST; | |
constexpr uint32_t kRestore = KEYSPACE | WRITE | SLOW | DANGEROUS; | ||
constexpr uint32_t kExpireTime = KEYSPACE | READ | FAST; | ||
constexpr uint32_t kPExpireTime = KEYSPACE | READ | FAST; | ||
constexpr uint32_t kFieldExpire = WRITE | HASH | SET | FAST; | ||
} // namespace acl | ||
|
||
void GenericFamily::Register(CommandRegistry* registry) { | ||
|
@@ -1788,6 +1836,8 @@ void GenericFamily::Register(CommandRegistry* registry) { | |
PexpireAt) | ||
<< CI{"PEXPIRE", CO::WRITE | CO::FAST | CO::NO_AUTOJOURNAL, 3, 1, 1, acl::kPExpire}.HFUNC( | ||
Pexpire) | ||
<< CI{"FIELDEXPIRE", CO::WRITE | CO::FAST | CO::DENYOOM, -4, 1, 1, acl::kFieldExpire}.HFUNC( | ||
FieldExpire) | ||
<< CI{"RENAME", CO::WRITE | CO::NO_AUTOJOURNAL, 3, 1, 2, acl::kRename}.HFUNC(Rename) | ||
<< CI{"RENAMENX", CO::WRITE | CO::NO_AUTOJOURNAL, 3, 1, 2, acl::kRenamNX}.HFUNC(RenameNx) | ||
<< CI{"SELECT", kSelectOpts, 2, 0, 0, acl::kSelect}.HFUNC(Select) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ | |
|
||
#include "server/hset_family.h" | ||
|
||
#include "server/family_utils.h" | ||
|
||
extern "C" { | ||
#include "redis/listpack.h" | ||
#include "redis/redis_aux.h" | ||
|
@@ -725,6 +727,23 @@ void HGetGeneric(CmdArgList args, ConnectionContext* cntx, uint8_t getall_mask) | |
} | ||
} | ||
|
||
OpResult<vector<long>> OpHExpire(const OpArgs& op_args, string_view key, uint32_t ttl_sec, | ||
CmdArgList values) { | ||
auto& db_slice = op_args.GetDbSlice(); | ||
auto op_res = db_slice.FindMutable(op_args.db_cntx, key, OBJ_HASH); | ||
|
||
if (!op_res) { | ||
if (op_res.status() == OpStatus::KEY_NOTFOUND) { | ||
std::vector<long> res(values.size(), -2); | ||
return res; | ||
} | ||
return op_res.status(); | ||
} | ||
NegatioN marked this conversation as resolved.
Show resolved
Hide resolved
kostasrim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
PrimeValue& pv = (*op_res).it->second; | ||
return HSetFamily::SetFieldsExpireTime(op_args, ttl_sec, key, values, pv); | ||
} | ||
|
||
// HSETEX key [NX] tll_sec field value field value ... | ||
void HSetEx(CmdArgList args, ConnectionContext* cntx) { | ||
CmdArgParser parser{args}; | ||
|
@@ -808,6 +827,49 @@ void HSetFamily::HExists(CmdArgList args, ConnectionContext* cntx) { | |
} | ||
} | ||
|
||
void HSetFamily::HExpire(CmdArgList args, ConnectionContext* cntx) { | ||
CmdArgParser parser{args}; | ||
string_view key = parser.Next(); | ||
string_view ttl_str = parser.Next(); | ||
uint32_t ttl_sec; | ||
constexpr uint32_t kMaxTtl = (1UL << 26); | ||
if (!absl::SimpleAtoi(ttl_str, &ttl_sec) || ttl_sec == 0 || ttl_sec > kMaxTtl) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep we reject It's fine to do so. Generally speaking
NegatioN marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return cntx->SendError(kInvalidIntErr); | ||
} | ||
if (!static_cast<bool>(parser.Check("FIELDS"sv))) { | ||
return cntx->SendError("Mandatory argument FIELDS is missing or not at the right position", | ||
kSyntaxErrType); | ||
} | ||
|
||
string_view numFieldsStr = parser.Next(); | ||
uint32_t numFields; | ||
if (!absl::SimpleAtoi(numFieldsStr, &numFields) || numFields == 0) { | ||
return cntx->SendError(kInvalidIntErr); | ||
} | ||
|
||
CmdArgList fields = parser.Tail(); | ||
if (fields.size() != numFields) { | ||
return cntx->SendError("The `numfields` parameter must match the number of arguments", | ||
kSyntaxErrType); | ||
} | ||
|
||
auto cb = [&](Transaction* t, EngineShard* shard) { | ||
return OpHExpire(t->GetOpArgs(shard), key, ttl_sec, fields); | ||
}; | ||
|
||
OpResult<vector<long>> result = cntx->transaction->ScheduleSingleHopT(std::move(cb)); | ||
auto* rb = static_cast<RedisReplyBuilder*>(cntx->reply_builder()); | ||
if (result) { | ||
rb->StartArray(result->size()); | ||
const auto& array = result.value(); | ||
for (const auto& v : array) { | ||
rb->SendLong(v); | ||
} | ||
} else { | ||
cntx->SendError(result.status()); | ||
} | ||
} | ||
|
||
void HSetFamily::HMGet(CmdArgList args, ConnectionContext* cntx) { | ||
string_view key = ArgS(args, 0); | ||
|
||
|
@@ -1189,6 +1251,7 @@ constexpr uint32_t kHSet = WRITE | HASH | FAST; | |
constexpr uint32_t kHSetEx = WRITE | HASH | FAST; | ||
constexpr uint32_t kHSetNx = WRITE | HASH | FAST; | ||
constexpr uint32_t kHStrLen = READ | HASH | FAST; | ||
constexpr uint32_t kHExpire = WRITE | HASH | FAST; | ||
constexpr uint32_t kHVals = READ | HASH | SLOW; | ||
} // namespace acl | ||
|
||
|
@@ -1206,6 +1269,7 @@ void HSetFamily::Register(CommandRegistry* registry) { | |
<< CI{"HINCRBYFLOAT", CO::WRITE | CO::DENYOOM | CO::FAST, 4, 1, 1, acl::kHIncrByFloat}.HFUNC( | ||
HIncrByFloat) | ||
<< CI{"HKEYS", CO::READONLY, 2, 1, 1, acl::kHKeys}.HFUNC(HKeys) | ||
<< CI{"HEXPIRE", CO::WRITE | CO::FAST | CO::DENYOOM, -5, 1, 1, acl::kHExpire}.HFUNC(HExpire) | ||
<< CI{"HRANDFIELD", CO::READONLY, -2, 1, 1, acl::kHRandField}.HFUNC(HRandField) | ||
<< CI{"HSCAN", CO::READONLY, -3, 1, 1, acl::kHScan}.HFUNC(HScan) | ||
<< CI{"HSET", CO::WRITE | CO::FAST | CO::DENYOOM, -4, 1, 1, acl::kHSet}.HFUNC(HSet) | ||
|
@@ -1276,4 +1340,27 @@ int32_t HSetFamily::FieldExpireTime(const DbContext& db_context, const PrimeValu | |
} | ||
} | ||
|
||
vector<long> HSetFamily::SetFieldsExpireTime(const OpArgs& op_args, uint32_t ttl_sec, | ||
string_view key, CmdArgList values, PrimeValue& pv) { | ||
DCHECK_EQ(OBJ_HASH, pv.ObjType()); | ||
op_args.shard->search_indices()->RemoveDoc(key, op_args.db_cntx, pv); | ||
NegatioN marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (pv.Encoding() == kEncodingListPack) { | ||
// a valid result can never be a listpack, since it doesnt keep ttl | ||
uint8_t* lp = (uint8_t*)pv.RObjPtr(); | ||
auto& db_slice = op_args.GetDbSlice(); | ||
DbTableStats* stats = db_slice.MutableStats(op_args.db_cntx.db_index); | ||
stats->listpack_bytes -= lpBytes(lp); | ||
stats->listpack_blob_cnt--; | ||
StringMap* sm = HSetFamily::ConvertToStrMap(lp); | ||
pv.InitRobj(OBJ_HASH, kEncodingStrMap2, sm); | ||
} | ||
|
||
// This needs to be explicitly fetched again since the pv might have changed. | ||
StringMap* sm = container_utils::GetStringMap(pv, op_args.db_cntx); | ||
vector<long> res = ExpireElements<StringMap>(sm, values, ttl_sec); | ||
op_args.shard->search_indices()->AddDoc(key, op_args.db_cntx, pv); | ||
return res; | ||
} | ||
|
||
} // namespace dfly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that way you don't need:
void*
to the respective typeExpireElements(arg1, arg2)
without the<>
syntax. (that is, let the compiler automatically deduce this)