mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 02:15:45 +02:00
Prepare transactional code to cope with irregular multi-key commands like ZUNIONSTORE
This commit is contained in:
parent
b1f993377b
commit
5c191e31d4
13 changed files with 205 additions and 72 deletions
|
@ -19,6 +19,8 @@ enum class OpStatus : uint16_t {
|
||||||
TIMED_OUT,
|
TIMED_OUT,
|
||||||
OUT_OF_MEMORY,
|
OUT_OF_MEMORY,
|
||||||
INVALID_FLOAT,
|
INVALID_FLOAT,
|
||||||
|
INVALID_INT,
|
||||||
|
SYNTAX_ERR,
|
||||||
};
|
};
|
||||||
|
|
||||||
class OpResultBase {
|
class OpResultBase {
|
||||||
|
|
|
@ -153,7 +153,6 @@ void MCReplyBuilder::SendNotFound() {
|
||||||
SendSimpleString("NOT_FOUND");
|
SendSimpleString("NOT_FOUND");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
char* RedisReplyBuilder::FormatDouble(double val, char* dest, unsigned dest_len) {
|
char* RedisReplyBuilder::FormatDouble(double val, char* dest, unsigned dest_len) {
|
||||||
StringBuilder sb(dest, dest_len);
|
StringBuilder sb(dest, dest_len);
|
||||||
CHECK(dfly_conv.ToShortest(val, &sb));
|
CHECK(dfly_conv.ToShortest(val, &sb));
|
||||||
|
@ -229,6 +228,12 @@ void RedisReplyBuilder::SendError(OpStatus status) {
|
||||||
case OpStatus::INVALID_FLOAT:
|
case OpStatus::INVALID_FLOAT:
|
||||||
SendError(kInvalidFloatErr);
|
SendError(kInvalidFloatErr);
|
||||||
break;
|
break;
|
||||||
|
case OpStatus::INVALID_INT:
|
||||||
|
SendError(kInvalidIntErr);
|
||||||
|
break;
|
||||||
|
case OpStatus::SYNTAX_ERR:
|
||||||
|
SendError(kSyntaxErr);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG(ERROR) << "Unsupported status " << status;
|
LOG(ERROR) << "Unsupported status " << status;
|
||||||
SendError("Internal error");
|
SendError("Internal error");
|
||||||
|
|
|
@ -21,9 +21,11 @@ CommandId::CommandId(const char* name, uint32_t mask, int8_t arity, int8_t first
|
||||||
int8_t last_key, int8_t step)
|
int8_t last_key, int8_t step)
|
||||||
: name_(name), opt_mask_(mask), arity_(arity), first_key_(first_key), last_key_(last_key),
|
: name_(name), opt_mask_(mask), arity_(arity), first_key_(first_key), last_key_(last_key),
|
||||||
step_key_(step) {
|
step_key_(step) {
|
||||||
if (mask & CO::ADMIN) {
|
if (mask & CO::ADMIN)
|
||||||
opt_mask_ |= CO::NOSCRIPT;
|
opt_mask_ |= CO::NOSCRIPT;
|
||||||
}
|
|
||||||
|
if (mask & CO::BLOCKING)
|
||||||
|
opt_mask_ |= CO::REVERSE_MAPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t CommandId::OptCount(uint32_t mask) {
|
uint32_t CommandId::OptCount(uint32_t mask) {
|
||||||
|
@ -81,38 +83,6 @@ CommandRegistry& CommandRegistry::operator<<(CommandId cmd) {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyIndex DetermineKeys(const CommandId* cid, const CmdArgList& args) {
|
|
||||||
DCHECK_EQ(0u, cid->opt_mask() & CO::GLOBAL_TRANS);
|
|
||||||
|
|
||||||
KeyIndex key_index;
|
|
||||||
|
|
||||||
if (cid->first_key_pos() > 0) {
|
|
||||||
key_index.start = cid->first_key_pos();
|
|
||||||
int last = cid->last_key_pos();
|
|
||||||
key_index.end = last > 0 ? last + 1 : (int(args.size()) + 1 + last);
|
|
||||||
key_index.step = cid->key_arg_step();
|
|
||||||
|
|
||||||
return key_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
string_view name{cid->name()};
|
|
||||||
if (name == "EVAL" || name == "EVALSHA") {
|
|
||||||
DCHECK_GE(args.size(), 3u);
|
|
||||||
uint32_t num_keys;
|
|
||||||
|
|
||||||
CHECK(absl::SimpleAtoi(ArgS(args, 2), &num_keys));
|
|
||||||
key_index.start = 3;
|
|
||||||
key_index.end = 3 + num_keys;
|
|
||||||
key_index.step = 1;
|
|
||||||
|
|
||||||
return key_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG(FATAL) << "TBD: Not supported";
|
|
||||||
|
|
||||||
return key_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace CO {
|
namespace CO {
|
||||||
|
|
||||||
const char* OptName(CO::CommandOpt fl) {
|
const char* OptName(CO::CommandOpt fl) {
|
||||||
|
@ -125,6 +95,8 @@ const char* OptName(CO::CommandOpt fl) {
|
||||||
return "readonly";
|
return "readonly";
|
||||||
case DENYOOM:
|
case DENYOOM:
|
||||||
return "denyoom";
|
return "denyoom";
|
||||||
|
case REVERSE_MAPPING:
|
||||||
|
return "reverse-mapping";
|
||||||
case FAST:
|
case FAST:
|
||||||
return "fast";
|
return "fast";
|
||||||
case LOADING:
|
case LOADING:
|
||||||
|
@ -139,6 +111,8 @@ const char* OptName(CO::CommandOpt fl) {
|
||||||
return "blocking";
|
return "blocking";
|
||||||
case GLOBAL_TRANS:
|
case GLOBAL_TRANS:
|
||||||
return "global-trans";
|
return "global-trans";
|
||||||
|
case DESTINATION_KEY:
|
||||||
|
return "dest-key";
|
||||||
}
|
}
|
||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,11 +24,13 @@ enum CommandOpt : uint32_t {
|
||||||
WRITE = 4,
|
WRITE = 4,
|
||||||
LOADING = 8,
|
LOADING = 8,
|
||||||
DENYOOM = 0x10, // use-memory in redis.
|
DENYOOM = 0x10, // use-memory in redis.
|
||||||
|
REVERSE_MAPPING = 0x20,
|
||||||
RANDOM = 0x40,
|
RANDOM = 0x40,
|
||||||
ADMIN = 0x80, // implies NOSCRIPT,
|
ADMIN = 0x80, // implies NOSCRIPT,
|
||||||
NOSCRIPT = 0x100,
|
NOSCRIPT = 0x100,
|
||||||
BLOCKING = 0x200,
|
BLOCKING = 0x200, // implies REVERSE_MAPPING
|
||||||
GLOBAL_TRANS = 0x1000,
|
GLOBAL_TRANS = 0x1000,
|
||||||
|
DESTINATION_KEY = 0x2000,
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* OptName(CommandOpt fl);
|
const char* OptName(CommandOpt fl);
|
||||||
|
@ -83,7 +85,7 @@ class CommandId {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_multi_key() const {
|
bool is_multi_key() const {
|
||||||
return last_key_ != first_key_;
|
return (last_key_ != first_key_) || (opt_mask_ & CO::DESTINATION_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t key_arg_step() const {
|
int8_t key_arg_step() const {
|
||||||
|
@ -157,8 +159,4 @@ class CommandRegistry {
|
||||||
void Command(CmdArgList args, ConnectionContext* cntx);
|
void Command(CmdArgList args, ConnectionContext* cntx);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Given the command and the arguments determines the keys range (index).
|
|
||||||
KeyIndex DetermineKeys(const CommandId* cid, const CmdArgList& args);
|
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -48,9 +48,20 @@ struct KeyLockArgs {
|
||||||
|
|
||||||
// Describes key indices.
|
// Describes key indices.
|
||||||
struct KeyIndex {
|
struct KeyIndex {
|
||||||
|
// if index is non-zero then adds another key index (usually 1).
|
||||||
|
// relevant for for commands like ZUNIONSTORE/ZINTERSTORE for destination key.
|
||||||
|
unsigned bonus = 0;
|
||||||
unsigned start;
|
unsigned start;
|
||||||
unsigned end; // does not include this index (open limit).
|
unsigned end; // does not include this index (open limit).
|
||||||
unsigned step; // 1 for commands like mget. 2 for commands like mset.
|
unsigned step; // 1 for commands like mget. 2 for commands like mset.
|
||||||
|
|
||||||
|
bool HasSingleKey() const {
|
||||||
|
return bonus == 0 && (start + step >= end);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned num_args() const {
|
||||||
|
return end - start + (bonus > 0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct OpArgs {
|
struct OpArgs {
|
||||||
|
|
|
@ -354,6 +354,12 @@ void Service::Shutdown() {
|
||||||
boost::this_fiber::sleep_for(10ms);
|
boost::this_fiber::sleep_for(10ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void MultiSetError(ConnectionContext* cntx) {
|
||||||
|
if (cntx->conn_state.exec_state != ConnectionState::EXEC_INACTIVE) {
|
||||||
|
cntx->conn_state.exec_state = ConnectionState::EXEC_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx) {
|
void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx) {
|
||||||
CHECK(!args.empty());
|
CHECK(!args.empty());
|
||||||
DCHECK_NE(0u, shard_set_.size()) << "Init was not called";
|
DCHECK_NE(0u, shard_set_.size()) << "Init was not called";
|
||||||
|
@ -370,11 +376,7 @@ void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx)
|
||||||
etl.RecordCmd();
|
etl.RecordCmd();
|
||||||
|
|
||||||
ConnectionContext* dfly_cntx = static_cast<ConnectionContext*>(cntx);
|
ConnectionContext* dfly_cntx = static_cast<ConnectionContext*>(cntx);
|
||||||
absl::Cleanup multi_error = [dfly_cntx] {
|
absl::Cleanup multi_error([dfly_cntx] { MultiSetError(dfly_cntx); });
|
||||||
if (dfly_cntx->conn_state.exec_state != ConnectionState::EXEC_INACTIVE) {
|
|
||||||
dfly_cntx->conn_state.exec_state = ConnectionState::EXEC_ERROR;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cid == nullptr) {
|
if (cid == nullptr) {
|
||||||
(*cntx)->SendError(absl::StrCat("unknown command `", cmd_str, "`"), "unknown_cmd");
|
(*cntx)->SendError(absl::StrCat("unknown command `", cmd_str, "`"), "unknown_cmd");
|
||||||
|
@ -464,7 +466,11 @@ void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx)
|
||||||
|
|
||||||
if (under_script) {
|
if (under_script) {
|
||||||
DCHECK(dfly_cntx->transaction);
|
DCHECK(dfly_cntx->transaction);
|
||||||
KeyIndex key_index = DetermineKeys(cid, args);
|
OpResult<KeyIndex> key_index_res = DetermineKeys(cid, args);
|
||||||
|
if (!key_index_res)
|
||||||
|
return (*cntx)->SendError(key_index_res.status());
|
||||||
|
|
||||||
|
const auto& key_index = *key_index_res;
|
||||||
for (unsigned i = key_index.start; i < key_index.end; ++i) {
|
for (unsigned i = key_index.start; i < key_index.end; ++i) {
|
||||||
string_view key = ArgS(args, i);
|
string_view key = ArgS(args, i);
|
||||||
if (!dfly_cntx->conn_state.script_info->keys.contains(key)) {
|
if (!dfly_cntx->conn_state.script_info->keys.contains(key)) {
|
||||||
|
@ -472,7 +478,9 @@ void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dfly_cntx->transaction->SetExecCmd(cid);
|
dfly_cntx->transaction->SetExecCmd(cid);
|
||||||
dfly_cntx->transaction->InitByArgs(dfly_cntx->conn_state.db_index, args);
|
OpStatus st = dfly_cntx->transaction->InitByArgs(dfly_cntx->conn_state.db_index, args);
|
||||||
|
if (st != OpStatus::OK)
|
||||||
|
return (*cntx)->SendError(st);
|
||||||
} else {
|
} else {
|
||||||
DCHECK(dfly_cntx->transaction == nullptr);
|
DCHECK(dfly_cntx->transaction == nullptr);
|
||||||
|
|
||||||
|
@ -480,7 +488,10 @@ void Service::DispatchCommand(CmdArgList args, facade::ConnectionContext* cntx)
|
||||||
dist_trans.reset(new Transaction{cid, &shard_set_});
|
dist_trans.reset(new Transaction{cid, &shard_set_});
|
||||||
dfly_cntx->transaction = dist_trans.get();
|
dfly_cntx->transaction = dist_trans.get();
|
||||||
|
|
||||||
dist_trans->InitByArgs(dfly_cntx->conn_state.db_index, args);
|
OpStatus st = dist_trans->InitByArgs(dfly_cntx->conn_state.db_index, args);
|
||||||
|
if (st != OpStatus::OK)
|
||||||
|
return (*cntx)->SendError(st);
|
||||||
|
|
||||||
dfly_cntx->last_command_debug.shards_count = dfly_cntx->transaction->unique_shard_cnt();
|
dfly_cntx->last_command_debug.shards_count = dfly_cntx->transaction->unique_shard_cnt();
|
||||||
} else {
|
} else {
|
||||||
dfly_cntx->transaction = nullptr;
|
dfly_cntx->transaction = nullptr;
|
||||||
|
@ -856,7 +867,11 @@ void Service::Exec(CmdArgList args, ConnectionContext* cntx) {
|
||||||
cntx->transaction->SetExecCmd(scmd.descr);
|
cntx->transaction->SetExecCmd(scmd.descr);
|
||||||
CmdArgList cmd_arg_list{str_list.data(), str_list.size()};
|
CmdArgList cmd_arg_list{str_list.data(), str_list.size()};
|
||||||
if (IsTransactional(scmd.descr)) {
|
if (IsTransactional(scmd.descr)) {
|
||||||
cntx->transaction->InitByArgs(cntx->conn_state.db_index, cmd_arg_list);
|
OpStatus st = cntx->transaction->InitByArgs(cntx->conn_state.db_index, cmd_arg_list);
|
||||||
|
if (st != OpStatus::OK) {
|
||||||
|
(*cntx)->SendError(st);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
scmd.descr->Invoke(cmd_arg_list, cntx);
|
scmd.descr->Invoke(cmd_arg_list, cntx);
|
||||||
if (rb->GetError())
|
if (rb->GetError())
|
||||||
|
|
|
@ -332,6 +332,18 @@ void ServerFamily::Auth(CmdArgList args, ConnectionContext* cntx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerFamily::Client(CmdArgList args, ConnectionContext* cntx) {
|
||||||
|
ToUpper(&args[1]);
|
||||||
|
string_view sub_cmd = ArgS(args, 1);
|
||||||
|
|
||||||
|
if (sub_cmd == "SETNAME") {
|
||||||
|
return (*cntx)->SendOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_FIRST_N(ERROR, 10) << "Subcommand " << sub_cmd << " not supported";
|
||||||
|
(*cntx)->SendError(kSyntaxErr);
|
||||||
|
}
|
||||||
|
|
||||||
void ServerFamily::Config(CmdArgList args, ConnectionContext* cntx) {
|
void ServerFamily::Config(CmdArgList args, ConnectionContext* cntx) {
|
||||||
ToUpper(&args[1]);
|
ToUpper(&args[1]);
|
||||||
string_view sub_cmd = ArgS(args, 1);
|
string_view sub_cmd = ArgS(args, 1);
|
||||||
|
@ -678,6 +690,18 @@ void ServerFamily::LastSave(CmdArgList args, ConnectionContext* cntx) {
|
||||||
(*cntx)->SendLong(last_save_);
|
(*cntx)->SendLong(last_save_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerFamily::Latency(CmdArgList args, ConnectionContext* cntx) {
|
||||||
|
ToUpper(&args[1]);
|
||||||
|
string_view sub_cmd = ArgS(args, 1);
|
||||||
|
|
||||||
|
if (sub_cmd == "LATEST") {
|
||||||
|
return (*cntx)->StartArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_FIRST_N(ERROR, 10) << "Subcommand " << sub_cmd << " not supported";
|
||||||
|
(*cntx)->SendError(kSyntaxErr);
|
||||||
|
}
|
||||||
|
|
||||||
void ServerFamily::_Shutdown(CmdArgList args, ConnectionContext* cntx) {
|
void ServerFamily::_Shutdown(CmdArgList args, ConnectionContext* cntx) {
|
||||||
CHECK_NOTNULL(acceptor_)->Stop();
|
CHECK_NOTNULL(acceptor_)->Stop();
|
||||||
(*cntx)->SendOk();
|
(*cntx)->SendOk();
|
||||||
|
@ -705,6 +729,7 @@ void ServerFamily::Register(CommandRegistry* registry) {
|
||||||
|
|
||||||
*registry << CI{"AUTH", CO::NOSCRIPT | CO::FAST | CO::LOADING, -2, 0, 0, 0}.HFUNC(Auth)
|
*registry << CI{"AUTH", CO::NOSCRIPT | CO::FAST | CO::LOADING, -2, 0, 0, 0}.HFUNC(Auth)
|
||||||
<< CI{"BGSAVE", CO::ADMIN | CO::GLOBAL_TRANS, 1, 0, 0, 0}.HFUNC(Save)
|
<< CI{"BGSAVE", CO::ADMIN | CO::GLOBAL_TRANS, 1, 0, 0, 0}.HFUNC(Save)
|
||||||
|
<< CI{"CLIENT", CO::NOSCRIPT | CO::LOADING, -2, 0, 0, 0}.HFUNC(Client)
|
||||||
<< CI{"CONFIG", CO::ADMIN, -2, 0, 0, 0}.HFUNC(Config)
|
<< CI{"CONFIG", CO::ADMIN, -2, 0, 0, 0}.HFUNC(Config)
|
||||||
<< CI{"DBSIZE", CO::READONLY | CO::FAST | CO::LOADING, 1, 0, 0, 0}.HFUNC(DbSize)
|
<< CI{"DBSIZE", CO::READONLY | CO::FAST | CO::LOADING, 1, 0, 0, 0}.HFUNC(DbSize)
|
||||||
<< CI{"DEBUG", CO::RANDOM | CO::ADMIN | CO::LOADING, -2, 0, 0, 0}.HFUNC(Debug)
|
<< CI{"DEBUG", CO::RANDOM | CO::ADMIN | CO::LOADING, -2, 0, 0, 0}.HFUNC(Debug)
|
||||||
|
@ -713,6 +738,8 @@ void ServerFamily::Register(CommandRegistry* registry) {
|
||||||
<< CI{"INFO", CO::LOADING, -1, 0, 0, 0}.HFUNC(Info)
|
<< CI{"INFO", CO::LOADING, -1, 0, 0, 0}.HFUNC(Info)
|
||||||
<< CI{"HELLO", CO::LOADING, -1, 0, 0, 0}.HFUNC(Hello)
|
<< CI{"HELLO", CO::LOADING, -1, 0, 0, 0}.HFUNC(Hello)
|
||||||
<< CI{"LASTSAVE", CO::LOADING | CO::RANDOM | CO::FAST, 1, 0, 0, 0}.HFUNC(LastSave)
|
<< CI{"LASTSAVE", CO::LOADING | CO::RANDOM | CO::FAST, 1, 0, 0, 0}.HFUNC(LastSave)
|
||||||
|
<< CI{"LATENCY", CO::NOSCRIPT | CO::LOADING | CO::RANDOM | CO::FAST, -2, 0, 0, 0}.HFUNC(
|
||||||
|
Latency)
|
||||||
<< CI{"MEMORY", kMemOpts, -2, 0, 0, 0}.HFUNC(Memory)
|
<< CI{"MEMORY", kMemOpts, -2, 0, 0, 0}.HFUNC(Memory)
|
||||||
<< CI{"SAVE", CO::ADMIN | CO::GLOBAL_TRANS, 1, 0, 0, 0}.HFUNC(Save)
|
<< CI{"SAVE", CO::ADMIN | CO::GLOBAL_TRANS, 1, 0, 0, 0}.HFUNC(Save)
|
||||||
<< CI{"SHUTDOWN", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, 1, 0, 0, 0}.HFUNC(_Shutdown)
|
<< CI{"SHUTDOWN", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, 1, 0, 0, 0}.HFUNC(_Shutdown)
|
||||||
|
|
|
@ -68,6 +68,7 @@ class ServerFamily {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Auth(CmdArgList args, ConnectionContext* cntx);
|
void Auth(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
void Client(CmdArgList args, ConnectionContext* cntx);
|
||||||
void Config(CmdArgList args, ConnectionContext* cntx);
|
void Config(CmdArgList args, ConnectionContext* cntx);
|
||||||
void DbSize(CmdArgList args, ConnectionContext* cntx);
|
void DbSize(CmdArgList args, ConnectionContext* cntx);
|
||||||
void Debug(CmdArgList args, ConnectionContext* cntx);
|
void Debug(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
@ -77,6 +78,7 @@ class ServerFamily {
|
||||||
void Info(CmdArgList args, ConnectionContext* cntx);
|
void Info(CmdArgList args, ConnectionContext* cntx);
|
||||||
void Hello(CmdArgList args, ConnectionContext* cntx);
|
void Hello(CmdArgList args, ConnectionContext* cntx);
|
||||||
void LastSave(CmdArgList args, ConnectionContext* cntx);
|
void LastSave(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
void Latency(CmdArgList args, ConnectionContext* cntx);
|
||||||
void Psync(CmdArgList args, ConnectionContext* cntx);
|
void Psync(CmdArgList args, ConnectionContext* cntx);
|
||||||
void ReplicaOf(CmdArgList args, ConnectionContext* cntx);
|
void ReplicaOf(CmdArgList args, ConnectionContext* cntx);
|
||||||
void Role(CmdArgList args, ConnectionContext* cntx);
|
void Role(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
@ -84,6 +86,7 @@ class ServerFamily {
|
||||||
void Script(CmdArgList args, ConnectionContext* cntx);
|
void Script(CmdArgList args, ConnectionContext* cntx);
|
||||||
void Sync(CmdArgList args, ConnectionContext* cntx);
|
void Sync(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
|
||||||
|
|
||||||
void _Shutdown(CmdArgList args, ConnectionContext* cntx);
|
void _Shutdown(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
|
||||||
void SyncGeneric(std::string_view repl_master_id, uint64_t offs, ConnectionContext* cntx);
|
void SyncGeneric(std::string_view repl_master_id, uint64_t offs, ConnectionContext* cntx);
|
||||||
|
|
|
@ -904,7 +904,7 @@ void StringFamily::Register(CommandRegistry* registry) {
|
||||||
<< CI{"DECRBY", CO::WRITE | CO::DENYOOM | CO::FAST, 3, 1, 1, 1}.HFUNC(DecrBy)
|
<< CI{"DECRBY", CO::WRITE | CO::DENYOOM | CO::FAST, 3, 1, 1, 1}.HFUNC(DecrBy)
|
||||||
<< CI{"GET", CO::READONLY | CO::FAST, 2, 1, 1, 1}.HFUNC(Get)
|
<< CI{"GET", CO::READONLY | CO::FAST, 2, 1, 1, 1}.HFUNC(Get)
|
||||||
<< CI{"GETSET", CO::WRITE | CO::DENYOOM | CO::FAST, 3, 1, 1, 1}.HFUNC(GetSet)
|
<< CI{"GETSET", CO::WRITE | CO::DENYOOM | CO::FAST, 3, 1, 1, 1}.HFUNC(GetSet)
|
||||||
<< CI{"MGET", CO::READONLY | CO::FAST, -2, 1, -1, 1}.HFUNC(MGet)
|
<< CI{"MGET", CO::READONLY | CO::FAST | CO::REVERSE_MAPPING, -2, 1, -1, 1}.HFUNC(MGet)
|
||||||
<< CI{"MSET", CO::WRITE | CO::DENYOOM, -3, 1, -1, 2}.HFUNC(MSet)
|
<< CI{"MSET", CO::WRITE | CO::DENYOOM, -3, 1, -1, 2}.HFUNC(MSet)
|
||||||
<< CI{"MSETNX", CO::WRITE | CO::DENYOOM, -3, 1, -1, 2}.HFUNC(MSetNx)
|
<< CI{"MSETNX", CO::WRITE | CO::DENYOOM, -3, 1, -1, 2}.HFUNC(MSetNx)
|
||||||
<< CI{"STRLEN", CO::READONLY | CO::FAST, 2, 1, 1, 1}.HFUNC(StrLen)
|
<< CI{"STRLEN", CO::READONLY | CO::FAST, 2, 1, 1, 1}.HFUNC(StrLen)
|
||||||
|
|
|
@ -74,36 +74,42 @@ Transaction::~Transaction() {
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
|
|
||||||
void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
OpStatus Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
db_index_ = index;
|
db_index_ = index;
|
||||||
|
|
||||||
if (IsGlobal()) {
|
if (IsGlobal()) {
|
||||||
unique_shard_cnt_ = ess_->size();
|
unique_shard_cnt_ = ess_->size();
|
||||||
shard_data_.resize(unique_shard_cnt_);
|
shard_data_.resize(unique_shard_cnt_);
|
||||||
return;
|
return OpStatus::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK_GT(args.size(), 1U); // first entry is the command name.
|
CHECK_GT(args.size(), 1U); // first entry is the command name.
|
||||||
DCHECK_EQ(unique_shard_cnt_, 0u);
|
DCHECK_EQ(unique_shard_cnt_, 0u);
|
||||||
DCHECK(args_.empty());
|
DCHECK(args_.empty());
|
||||||
|
|
||||||
KeyIndex key_index = DetermineKeys(cid_, args);
|
OpResult<KeyIndex> key_index_res = DetermineKeys(cid_, args);
|
||||||
|
if (!key_index_res)
|
||||||
|
return key_index_res.status();
|
||||||
|
|
||||||
|
const auto& key_index = *key_index_res;
|
||||||
|
|
||||||
if (key_index.start == args.size()) { // eval with 0 keys.
|
if (key_index.start == args.size()) { // eval with 0 keys.
|
||||||
CHECK(absl::StartsWith(cid_->name(), "EVAL"));
|
CHECK(absl::StartsWith(cid_->name(), "EVAL"));
|
||||||
return;
|
return OpStatus::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
DCHECK_LT(key_index.start, args.size());
|
DCHECK_LT(key_index.start, args.size());
|
||||||
DCHECK_GT(key_index.start, 0u);
|
DCHECK_GT(key_index.start, 0u);
|
||||||
|
|
||||||
bool incremental_locking = multi_ && multi_->incremental;
|
bool incremental_locking = multi_ && multi_->incremental;
|
||||||
bool single_key = !multi_ && (key_index.start + key_index.step) >= key_index.end;
|
bool single_key = !multi_ && key_index.HasSingleKey();
|
||||||
|
|
||||||
if (single_key) {
|
if (single_key) {
|
||||||
DCHECK_GT(key_index.step, 0u);
|
DCHECK_GT(key_index.step, 0u);
|
||||||
|
|
||||||
shard_data_.resize(1); // Single key optimization
|
shard_data_.resize(1); // Single key optimization
|
||||||
|
|
||||||
|
// even for a single key we may have multiple arguments per key (MSET).
|
||||||
for (unsigned j = key_index.start; j < key_index.start + key_index.step; ++j) {
|
for (unsigned j = key_index.start; j < key_index.start + key_index.step; ++j) {
|
||||||
args_.push_back(ArgS(args, j));
|
args_.push_back(ArgS(args, j));
|
||||||
}
|
}
|
||||||
|
@ -112,7 +118,7 @@ void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
unique_shard_cnt_ = 1;
|
unique_shard_cnt_ = 1;
|
||||||
unique_shard_id_ = Shard(key, ess_->size());
|
unique_shard_id_ = Shard(key, ess_->size());
|
||||||
|
|
||||||
return;
|
return OpStatus::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our shard_data is not sparse, so we must allocate for all threads :(
|
// Our shard_data is not sparse, so we must allocate for all threads :(
|
||||||
|
@ -131,6 +137,8 @@ void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
// and regular commands.
|
// and regular commands.
|
||||||
IntentLock::Mode mode = IntentLock::EXCLUSIVE;
|
IntentLock::Mode mode = IntentLock::EXCLUSIVE;
|
||||||
bool should_record_locks = false;
|
bool should_record_locks = false;
|
||||||
|
bool needs_reverse_mapping = cid_->opt_mask() & CO::REVERSE_MAPPING;
|
||||||
|
|
||||||
if (multi_) {
|
if (multi_) {
|
||||||
mode = Mode();
|
mode = Mode();
|
||||||
tmp_space.uniq_keys.clear();
|
tmp_space.uniq_keys.clear();
|
||||||
|
@ -138,10 +146,21 @@ void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
should_record_locks = incremental_locking || !multi_->locks_recorded;
|
should_record_locks = incremental_locking || !multi_->locks_recorded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (key_index.bonus) { // additional one-of key.
|
||||||
|
DCHECK(key_index.step == 1);
|
||||||
|
DCHECK(!needs_reverse_mapping);
|
||||||
|
|
||||||
|
string_view key = ArgS(args, key_index.bonus);
|
||||||
|
uint32_t sid = Shard(key, shard_data_.size());
|
||||||
|
shard_index[sid].args.push_back(key);
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned i = key_index.start; i < key_index.end; ++i) {
|
for (unsigned i = key_index.start; i < key_index.end; ++i) {
|
||||||
string_view key = ArgS(args, i);
|
string_view key = ArgS(args, i);
|
||||||
uint32_t sid = Shard(key, shard_data_.size());
|
uint32_t sid = Shard(key, shard_data_.size());
|
||||||
|
|
||||||
shard_index[sid].args.push_back(key);
|
shard_index[sid].args.push_back(key);
|
||||||
|
if (needs_reverse_mapping)
|
||||||
shard_index[sid].original_index.push_back(i - 1);
|
shard_index[sid].original_index.push_back(i - 1);
|
||||||
|
|
||||||
if (should_record_locks && tmp_space.uniq_keys.insert(key).second) {
|
if (should_record_locks && tmp_space.uniq_keys.insert(key).second) {
|
||||||
|
@ -150,8 +169,10 @@ void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
|
|
||||||
if (key_index.step == 2) { // value
|
if (key_index.step == 2) { // value
|
||||||
++i;
|
++i;
|
||||||
auto val = ArgS(args, i);
|
|
||||||
|
string_view val = ArgS(args, i);
|
||||||
shard_index[sid].args.push_back(val);
|
shard_index[sid].args.push_back(val);
|
||||||
|
if (needs_reverse_mapping)
|
||||||
shard_index[sid].original_index.push_back(i - 1);
|
shard_index[sid].original_index.push_back(i - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +181,10 @@ void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
multi_->locks_recorded = true;
|
multi_->locks_recorded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
args_.resize(key_index.end - key_index.start);
|
args_.resize(key_index.num_args());
|
||||||
|
|
||||||
|
// we need reverse index only for blocking commands or commands like MSET.
|
||||||
|
if (needs_reverse_mapping)
|
||||||
reverse_index_.resize(args_.size());
|
reverse_index_.resize(args_.size());
|
||||||
|
|
||||||
auto next_arg = args_.begin();
|
auto next_arg = args_.begin();
|
||||||
|
@ -192,11 +216,11 @@ void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
uint32_t orig_indx = 0;
|
uint32_t orig_indx = 0;
|
||||||
for (size_t j = 0; j < si.args.size(); ++j) {
|
for (size_t j = 0; j < si.args.size(); ++j) {
|
||||||
*next_arg = si.args[j];
|
*next_arg = si.args[j];
|
||||||
*rev_indx_it = si.original_index[orig_indx];
|
if (needs_reverse_mapping) {
|
||||||
|
*rev_indx_it++ = si.original_index[orig_indx];
|
||||||
|
}
|
||||||
++next_arg;
|
++next_arg;
|
||||||
++orig_indx;
|
++orig_indx;
|
||||||
++rev_indx_it;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +248,8 @@ void Transaction::InitByArgs(DbIndex index, CmdArgList args) {
|
||||||
DCHECK_EQ(TxQueue::kEnd, sd.pq_pos);
|
DCHECK_EQ(TxQueue::kEnd, sd.pq_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return OpStatus::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Transaction::SetExecCmd(const CommandId* cid) {
|
void Transaction::SetExecCmd(const CommandId* cid) {
|
||||||
|
@ -1126,4 +1152,53 @@ void Transaction::BreakOnClose() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OpResult<KeyIndex> DetermineKeys(const CommandId* cid, CmdArgList args) {
|
||||||
|
DCHECK_EQ(0u, cid->opt_mask() & CO::GLOBAL_TRANS);
|
||||||
|
|
||||||
|
KeyIndex key_index;
|
||||||
|
int num_custom_keys = -1;
|
||||||
|
|
||||||
|
if (cid->opt_mask() & CO::DESTINATION_KEY) {
|
||||||
|
key_index.bonus = 1;
|
||||||
|
if (args.size() < 3) {
|
||||||
|
return OpStatus::SYNTAX_ERR;
|
||||||
|
}
|
||||||
|
string_view num(ArgS(args, 2));
|
||||||
|
if (!absl::SimpleAtoi(num, &num_custom_keys) || num_custom_keys < 0 ||
|
||||||
|
size_t(num_custom_keys) + 3 > args.size())
|
||||||
|
return OpStatus::INVALID_INT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cid->first_key_pos() > 0) {
|
||||||
|
key_index.start = cid->first_key_pos();
|
||||||
|
int last = cid->last_key_pos();
|
||||||
|
if (num_custom_keys >= 0) {
|
||||||
|
key_index.end = key_index.start + num_custom_keys;
|
||||||
|
} else {
|
||||||
|
key_index.end = last > 0 ? last + 1 : (int(args.size()) + 1 + last);
|
||||||
|
}
|
||||||
|
key_index.step = cid->key_arg_step();
|
||||||
|
|
||||||
|
return key_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
string_view name{cid->name()};
|
||||||
|
if (name == "EVAL" || name == "EVALSHA") {
|
||||||
|
DCHECK_GE(args.size(), 3u);
|
||||||
|
uint32_t num_keys;
|
||||||
|
|
||||||
|
CHECK(absl::SimpleAtoi(ArgS(args, 2), &num_keys));
|
||||||
|
key_index.start = 3;
|
||||||
|
key_index.end = 3 + num_keys;
|
||||||
|
key_index.step = 1;
|
||||||
|
|
||||||
|
return key_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(FATAL) << "TBD: Not supported";
|
||||||
|
|
||||||
|
return key_index;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -61,7 +61,7 @@ class Transaction {
|
||||||
|
|
||||||
Transaction(const CommandId* cid, EngineShardSet* ess);
|
Transaction(const CommandId* cid, EngineShardSet* ess);
|
||||||
|
|
||||||
void InitByArgs(DbIndex index, CmdArgList args);
|
OpStatus InitByArgs(DbIndex index, CmdArgList args);
|
||||||
|
|
||||||
void SetExecCmd(const CommandId* cid);
|
void SetExecCmd(const CommandId* cid);
|
||||||
|
|
||||||
|
@ -342,4 +342,6 @@ inline uint16_t trans_id(const Transaction* ptr) {
|
||||||
return intptr_t(ptr) & 0xFFFF;
|
return intptr_t(ptr) & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpResult<KeyIndex> DetermineKeys(const CommandId* cid, CmdArgList args);
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -422,10 +422,10 @@ void IntervalVisitor::ExtractSkipList(const zrangespec& range) {
|
||||||
|
|
||||||
void IntervalVisitor::ExtractListPack(const zlexrangespec& range) {
|
void IntervalVisitor::ExtractListPack(const zlexrangespec& range) {
|
||||||
uint8_t* zl = (uint8_t*)zobj_->ptr;
|
uint8_t* zl = (uint8_t*)zobj_->ptr;
|
||||||
uint8_t *eptr, *sptr;
|
uint8_t *eptr, *sptr = nullptr;
|
||||||
uint8_t* vstr;
|
uint8_t* vstr = nullptr;
|
||||||
unsigned int vlen;
|
unsigned int vlen = 0;
|
||||||
long long vlong;
|
long long vlong = 0;
|
||||||
unsigned offset = params_.offset;
|
unsigned offset = params_.offset;
|
||||||
unsigned limit = params_.limit;
|
unsigned limit = params_.limit;
|
||||||
|
|
||||||
|
@ -748,6 +748,10 @@ void ZSetFamily::ZIncrBy(CmdArgList args, ConnectionContext* cntx) {
|
||||||
(*cntx)->SendDouble(add_result.new_score);
|
(*cntx)->SendDouble(add_result.new_score);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZSetFamily::ZInterStore(CmdArgList args, ConnectionContext* cntx) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void ZSetFamily::ZLexCount(CmdArgList args, ConnectionContext* cntx) {
|
void ZSetFamily::ZLexCount(CmdArgList args, ConnectionContext* cntx) {
|
||||||
string_view key = ArgS(args, 1);
|
string_view key = ArgS(args, 1);
|
||||||
|
|
||||||
|
@ -979,6 +983,19 @@ void ZSetFamily::ZScan(CmdArgList args, ConnectionContext* cntx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZSetFamily::ZUnionStore(CmdArgList args, ConnectionContext* cntx) {
|
||||||
|
auto cb = [&](Transaction* t, EngineShard* es) {
|
||||||
|
auto args = t->ShardArgsInShard(es->shard_id());
|
||||||
|
for (auto x : args) {
|
||||||
|
LOG(INFO) << "arg " << x;
|
||||||
|
}
|
||||||
|
return OpStatus::OK;
|
||||||
|
};
|
||||||
|
|
||||||
|
OpStatus result = cntx->transaction->ScheduleSingleHop(std::move(cb));
|
||||||
|
(*cntx)->SendOk();
|
||||||
|
}
|
||||||
|
|
||||||
void ZSetFamily::ZRangeByScoreInternal(string_view key, string_view min_s, string_view max_s,
|
void ZSetFamily::ZRangeByScoreInternal(string_view key, string_view min_s, string_view max_s,
|
||||||
const RangeParams& params, ConnectionContext* cntx) {
|
const RangeParams& params, ConnectionContext* cntx) {
|
||||||
ZRangeSpec range_spec;
|
ZRangeSpec range_spec;
|
||||||
|
@ -1201,7 +1218,7 @@ OpStatus ZSetFamily::OpAdd(const ZParams& zparams, const OpArgs& op_args, string
|
||||||
unsigned processed = 0;
|
unsigned processed = 0;
|
||||||
|
|
||||||
sds& tmp_str = op_args.shard->tmp_str1;
|
sds& tmp_str = op_args.shard->tmp_str1;
|
||||||
double new_score;
|
double new_score = 0;
|
||||||
int retflags = 0;
|
int retflags = 0;
|
||||||
|
|
||||||
OpStatus res = OpStatus::OK;
|
OpStatus res = OpStatus::OK;
|
||||||
|
@ -1483,6 +1500,7 @@ void ZSetFamily::Register(CommandRegistry* registry) {
|
||||||
<< CI{"ZCARD", CO::FAST | CO::READONLY, 2, 1, 1, 1}.HFUNC(ZCard)
|
<< CI{"ZCARD", CO::FAST | CO::READONLY, 2, 1, 1, 1}.HFUNC(ZCard)
|
||||||
<< CI{"ZCOUNT", CO::FAST | CO::READONLY, 4, 1, 1, 1}.HFUNC(ZCount)
|
<< CI{"ZCOUNT", CO::FAST | CO::READONLY, 4, 1, 1, 1}.HFUNC(ZCount)
|
||||||
<< CI{"ZINCRBY", CO::FAST | CO::WRITE | CO::DENYOOM, 4, 1, 1, 1}.HFUNC(ZIncrBy)
|
<< CI{"ZINCRBY", CO::FAST | CO::WRITE | CO::DENYOOM, 4, 1, 1, 1}.HFUNC(ZIncrBy)
|
||||||
|
<< CI{"ZINTERSTORE", CO::WRITE | CO::DESTINATION_KEY, -4, 1, 1, 1}.HFUNC(ZInterStore)
|
||||||
<< CI{"ZLEXCOUNT", CO::READONLY, 4, 1, 1, 1}.HFUNC(ZLexCount)
|
<< CI{"ZLEXCOUNT", CO::READONLY, 4, 1, 1, 1}.HFUNC(ZLexCount)
|
||||||
<< CI{"ZREM", CO::FAST | CO::WRITE, -3, 1, 1, 1}.HFUNC(ZRem)
|
<< CI{"ZREM", CO::FAST | CO::WRITE, -3, 1, 1, 1}.HFUNC(ZRem)
|
||||||
<< CI{"ZRANGE", CO::READONLY, -4, 1, 1, 1}.HFUNC(ZRange)
|
<< CI{"ZRANGE", CO::READONLY, -4, 1, 1, 1}.HFUNC(ZRange)
|
||||||
|
@ -1496,7 +1514,8 @@ void ZSetFamily::Register(CommandRegistry* registry) {
|
||||||
<< CI{"ZREVRANGE", CO::READONLY, 4, 1, 1, 1}.HFUNC(ZRevRange)
|
<< CI{"ZREVRANGE", CO::READONLY, 4, 1, 1, 1}.HFUNC(ZRevRange)
|
||||||
<< CI{"ZREVRANGEBYSCORE", CO::READONLY, -4, 1, 1, 1}.HFUNC(ZRevRangeByScore)
|
<< CI{"ZREVRANGEBYSCORE", CO::READONLY, -4, 1, 1, 1}.HFUNC(ZRevRangeByScore)
|
||||||
<< CI{"ZREVRANK", CO::READONLY | CO::FAST, 3, 1, 1, 1}.HFUNC(ZRevRank)
|
<< CI{"ZREVRANK", CO::READONLY | CO::FAST, 3, 1, 1, 1}.HFUNC(ZRevRank)
|
||||||
<< CI{"ZSCAN", CO::READONLY | CO::RANDOM, -3, 1, 1, 1}.HFUNC(ZScan);
|
<< CI{"ZSCAN", CO::READONLY | CO::RANDOM, -3, 1, 1, 1}.HFUNC(ZScan)
|
||||||
|
<< CI{"ZUNIONSTORE", CO::WRITE | CO::DESTINATION_KEY, -4, 3, 3, 1}.HFUNC(ZUnionStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace dfly
|
} // namespace dfly
|
||||||
|
|
|
@ -56,6 +56,7 @@ class ZSetFamily {
|
||||||
static void ZCard(CmdArgList args, ConnectionContext* cntx);
|
static void ZCard(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ZCount(CmdArgList args, ConnectionContext* cntx);
|
static void ZCount(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ZIncrBy(CmdArgList args, ConnectionContext* cntx);
|
static void ZIncrBy(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
static void ZInterStore(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ZLexCount(CmdArgList args, ConnectionContext* cntx);
|
static void ZLexCount(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ZRange(CmdArgList args, ConnectionContext* cntx);
|
static void ZRange(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ZRank(CmdArgList args, ConnectionContext* cntx);
|
static void ZRank(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
@ -70,6 +71,7 @@ class ZSetFamily {
|
||||||
static void ZRevRangeByScore(CmdArgList args, ConnectionContext* cntx);
|
static void ZRevRangeByScore(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ZRevRank(CmdArgList args, ConnectionContext* cntx);
|
static void ZRevRank(CmdArgList args, ConnectionContext* cntx);
|
||||||
static void ZScan(CmdArgList args, ConnectionContext* cntx);
|
static void ZScan(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
static void ZUnionStore(CmdArgList args, ConnectionContext* cntx);
|
||||||
|
|
||||||
static void ZRangeByScoreInternal(std::string_view key, std::string_view min_s,
|
static void ZRangeByScoreInternal(std::string_view key, std::string_view min_s,
|
||||||
std::string_view max_s, const RangeParams& params,
|
std::string_view max_s, const RangeParams& params,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue