blob: 6b97eb1557a23554d3f0a62d1de5f8bd1c368aa4 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
#include "commander.h"
#include "commands/blocking_commander.h"
#include "commands/command_parser.h"
#include "error_constants.h"
#include "event_util.h"
#include "server/redis_reply.h"
#include "server/server.h"
#include "types/redis_list.h"
namespace redis {
class CommandPush : public Commander {
public:
CommandPush(bool create_if_missing, bool left) : left_(left), create_if_missing_(create_if_missing) {}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
std::vector<Slice> elems;
for (size_t i = 2; i < args_.size(); i++) {
elems.emplace_back(args_[i]);
}
uint64_t ret = 0;
rocksdb::Status s;
redis::List list_db(srv->storage, conn->GetNamespace());
if (create_if_missing_) {
s = list_db.Push(ctx, args_[1], elems, left_, &ret);
} else {
s = list_db.PushX(ctx, args_[1], elems, left_, &ret);
}
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
srv->WakeupBlockingConns(args_[1], elems.size());
*output = redis::Integer(ret);
return Status::OK();
}
private:
bool left_;
bool create_if_missing_;
};
class CommandLPush : public CommandPush {
public:
CommandLPush() : CommandPush(true, true) {}
};
class CommandRPush : public CommandPush {
public:
CommandRPush() : CommandPush(true, false) {}
};
class CommandLPushX : public CommandPush {
public:
CommandLPushX() : CommandPush(false, true) {}
};
class CommandRPushX : public CommandPush {
public:
CommandRPushX() : CommandPush(false, false) {}
};
class CommandPop : public Commander {
public:
explicit CommandPop(bool left) : left_(left) {}
Status Parse(const std::vector<std::string> &args) override {
if (args.size() > 3) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
if (args.size() == 2) {
return Status::OK();
}
auto v = ParseInt<int32_t>(args[2], 10);
if (!v) {
return {Status::RedisParseErr, errValueNotInteger};
}
if (*v < 0) {
return {Status::RedisParseErr, errValueMustBePositive};
}
count_ = *v;
with_count_ = true;
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
if (with_count_) {
std::vector<std::string> elems;
auto s = list_db.PopMulti(ctx, args_[1], left_, count_, &elems);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
if (s.IsNotFound()) {
*output = conn->NilArray();
} else {
*output = conn->MultiBulkString(elems);
}
} else {
std::string elem;
auto s = list_db.Pop(ctx, args_[1], left_, &elem);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
if (s.IsNotFound()) {
*output = conn->NilString();
} else {
*output = redis::BulkString(elem);
}
}
return Status::OK();
}
private:
bool left_;
bool with_count_ = false;
uint32_t count_ = 1;
};
class CommandLPop : public CommandPop {
public:
CommandLPop() : CommandPop(true) {}
};
class CommandRPop : public CommandPop {
public:
CommandRPop() : CommandPop(false) {}
};
class CommandLMPop : public Commander {
public:
// format: LMPOP #numkeys key0 [key1 ...] <LEFT | RIGHT> [COUNT count]
Status Parse(const std::vector<std::string> &args) override {
CommandParser parser(args, 1);
auto num_keys = GET_OR_RET(parser.TakeInt<uint32_t>());
keys_.clear();
keys_.reserve(num_keys);
for (uint32_t i = 0; i < num_keys; ++i) {
keys_.emplace_back(GET_OR_RET(parser.TakeStr()));
}
auto left_or_right = util::ToLower(GET_OR_RET(parser.TakeStr()));
if (left_or_right == "left") {
left_ = true;
} else if (left_or_right == "right") {
left_ = false;
} else {
return {Status::RedisParseErr, errInvalidSyntax};
}
while (parser.Good()) {
if (parser.EatEqICase("count") && count_ == static_cast<uint32_t>(-1)) {
count_ = GET_OR_RET(parser.TakeInt<uint32_t>());
} else {
return parser.InvalidSyntax();
}
}
if (count_ == static_cast<uint32_t>(-1)) {
count_ = 1;
}
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
std::vector<std::string> elems;
std::string chosen_key;
for (auto &key : keys_) {
auto s = list_db.PopMulti(ctx, key, left_, count_, &elems);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
if (!elems.empty()) {
chosen_key = key;
break;
}
}
if (elems.empty()) {
*output = conn->NilString();
} else {
std::string elems_bulk = conn->MultiBulkString(elems);
*output = redis::Array({redis::BulkString(chosen_key), std::move(elems_bulk)});
}
return Status::OK();
}
static const inline CommandKeyRangeGen keyRangeGen = [](const std::vector<std::string> &args) {
CommandKeyRange range;
range.first_key = 2;
range.key_step = 1;
// This parsing would always succeed as this cmd has been parsed before.
auto num_key = *ParseInt<int32_t>(args[1], 10);
range.last_key = range.first_key + num_key - 1;
return range;
};
private:
bool left_;
uint32_t count_ = -1;
std::vector<std::string> keys_;
};
class CommandBPop : public BlockingCommander {
public:
explicit CommandBPop(bool left) : left_(left) {}
CommandBPop(const CommandBPop &) = delete;
CommandBPop &operator=(const CommandBPop &) = delete;
~CommandBPop() override = default;
Status Parse(const std::vector<std::string> &args) override {
auto parse_result = ParseFloat(args[args.size() - 1]);
if (!parse_result) {
return {Status::RedisParseErr, errTimeoutIsNotFloat};
}
if (*parse_result < 0) {
return {Status::RedisParseErr, "timeout should not be negative"};
}
timeout_ = static_cast<int64_t>(*parse_result * 1000 * 1000);
keys_ = std::vector<std::string>(args.begin() + 1, args.end() - 1);
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
srv_ = srv;
InitConnection(conn);
auto s = TryPopFromList(ctx);
if (s.ok() || !s.IsNotFound()) {
return Status::OK(); // error has already output in TryPopFromList
}
return StartBlocking(timeout_, output);
}
void BlockKeys() override {
for (const auto &key : keys_) {
srv_->BlockOnKey(key, conn_);
}
}
void UnblockKeys() override {
for (const auto &key : keys_) {
srv_->UnblockOnKey(key, conn_);
}
}
rocksdb::Status TryPopFromList(engine::Context &ctx) {
redis::List list_db(srv_->storage, conn_->GetNamespace());
std::string elem;
const std::string *last_key_ptr = nullptr;
rocksdb::Status s;
for (const auto &key : keys_) {
last_key_ptr = &key;
s = list_db.Pop(ctx, key, left_, &elem);
if (s.ok() || !s.IsNotFound()) {
break;
}
}
if (s.ok()) {
if (!last_key_ptr) {
conn_->Reply(conn_->MultiBulkString({"", ""}));
} else {
conn_->GetServer()->UpdateWatchedKeysManually({*last_key_ptr});
conn_->Reply(conn_->MultiBulkString({*last_key_ptr, std::move(elem)}));
}
} else if (!s.IsNotFound()) {
conn_->Reply(redis::Error({Status::NotOK, s.ToString()}));
}
return s;
}
MultiLockGuard GetLocks() override {
std::vector<std::string> lock_keys;
lock_keys.reserve(keys_.size());
for (const auto &key : keys_) {
auto ns_key = ComposeNamespaceKey(conn_->GetNamespace(), key, srv_->storage->IsSlotIdEncoded());
lock_keys.emplace_back(std::move(ns_key));
}
return MultiLockGuard(srv_->storage->GetLockManager(), lock_keys);
}
bool OnBlockingWrite() override {
engine::Context ctx(srv_->storage);
auto s = TryPopFromList(ctx);
return !s.IsNotFound();
}
std::string NoopReply(const Connection *conn) override { return conn->NilString(); }
private:
bool left_ = false;
int64_t timeout_ = 0; // microseconds
std::vector<std::string> keys_;
Server *srv_ = nullptr;
};
class CommandBLPop : public CommandBPop {
public:
CommandBLPop() : CommandBPop(true) {}
};
class CommandBRPop : public CommandBPop {
public:
CommandBRPop() : CommandBPop(false) {}
};
class CommandBLMPop : public BlockingCommander {
public:
CommandBLMPop() = default;
CommandBLMPop(const CommandBLMPop &) = delete;
CommandBLMPop &operator=(const CommandBLMPop &) = delete;
~CommandBLMPop() override = default;
// format: BLMPOP timeout #numkeys key0 [key1 ...] <LEFT | RIGHT> [COUNT count]
Status Parse(const std::vector<std::string> &args) override {
CommandParser parser(args, 1);
auto timeout = GET_OR_RET(parser.TakeFloat());
timeout_ = static_cast<int64_t>(timeout * 1000 * 1000);
auto num_keys = GET_OR_RET(parser.TakeInt<uint32_t>());
keys_.clear();
keys_.reserve(num_keys);
for (uint32_t i = 0; i < num_keys; ++i) {
keys_.emplace_back(GET_OR_RET(parser.TakeStr()));
}
auto left_or_right = util::ToLower(GET_OR_RET(parser.TakeStr()));
if (left_or_right == "left") {
left_ = true;
} else if (left_or_right == "right") {
left_ = false;
} else {
return {Status::RedisParseErr, errInvalidSyntax};
}
while (parser.Good()) {
if (parser.EatEqICase("count") && count_ == static_cast<uint32_t>(-1)) {
count_ = GET_OR_RET(parser.TakeInt<uint32_t>());
} else {
return parser.InvalidSyntax();
}
}
if (count_ == static_cast<uint32_t>(-1)) {
count_ = 1;
}
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
srv_ = srv;
InitConnection(conn);
auto s = ExecuteUnblocked(ctx);
if (s.ok() || !s.IsNotFound()) {
return Status::OK(); // error has already been output
}
return StartBlocking(timeout_, output);
}
rocksdb::Status ExecuteUnblocked(engine::Context &ctx) {
redis::List list_db(srv_->storage, conn_->GetNamespace());
std::vector<std::string> elems;
std::string chosen_key;
rocksdb::Status s;
for (const auto &key : keys_) {
s = list_db.PopMulti(ctx, key, left_, count_, &elems);
if (s.ok() && !elems.empty()) {
chosen_key = key;
break;
}
if (!s.IsNotFound()) {
break;
}
}
if (s.ok()) {
if (!elems.empty()) {
conn_->GetServer()->UpdateWatchedKeysManually({chosen_key});
std::string elems_bulk = conn_->MultiBulkString(elems);
conn_->Reply(redis::Array({redis::BulkString(chosen_key), std::move(elems_bulk)}));
}
} else if (!s.IsNotFound()) {
conn_->Reply(redis::Error({Status::NotOK, s.ToString()}));
}
return s;
}
void BlockKeys() override {
for (const auto &key : keys_) {
srv_->BlockOnKey(key, conn_);
}
}
void UnblockKeys() override {
for (const auto &key : keys_) {
srv_->UnblockOnKey(key, conn_);
}
}
MultiLockGuard GetLocks() override {
std::vector<std::string> lock_keys;
lock_keys.reserve(keys_.size());
for (const auto &key : keys_) {
auto ns_key = ComposeNamespaceKey(conn_->GetNamespace(), key, srv_->storage->IsSlotIdEncoded());
lock_keys.emplace_back(std::move(ns_key));
}
return MultiLockGuard(srv_->storage->GetLockManager(), lock_keys);
}
bool OnBlockingWrite() override {
engine::Context ctx(srv_->storage);
auto s = ExecuteUnblocked(ctx);
return !s.IsNotFound();
}
std::string NoopReply(const Connection *conn) override { return conn->NilString(); }
static const inline CommandKeyRangeGen keyRangeGen = [](const std::vector<std::string> &args) {
CommandKeyRange range;
range.first_key = 3;
range.key_step = 1;
// This parsing would always succeed as this cmd has been parsed before.
auto num_key = *ParseInt<int32_t>(args[2], 10);
range.last_key = range.first_key + num_key - 1;
return range;
};
private:
bool left_;
uint32_t count_ = -1;
int64_t timeout_ = 0; // microseconds
std::vector<std::string> keys_;
Server *srv_ = nullptr;
};
class CommandLRem : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto parse_result = ParseInt<int>(args[2], 10);
if (!parse_result) {
return {Status::RedisParseErr, errValueNotInteger};
}
count_ = *parse_result;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
uint64_t ret = 0;
redis::List list_db(srv->storage, conn->GetNamespace());
auto s = list_db.Rem(ctx, args_[1], count_, args_[3], &ret);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::Integer(ret);
return Status::OK();
}
private:
int count_ = 0;
};
class CommandLInsert : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if ((util::ToLower(args[2]) == "before")) {
before_ = true;
} else if ((util::ToLower(args[2]) == "after")) {
before_ = false;
} else {
return {Status::RedisParseErr, errInvalidSyntax};
}
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
int ret = 0;
redis::List list_db(srv->storage, conn->GetNamespace());
auto s = list_db.Insert(ctx, args_[1], args_[3], args_[4], before_, &ret);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::Integer(ret);
return Status::OK();
}
private:
bool before_ = false;
};
class CommandLRange : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto parse_start = ParseInt<int>(args[2], 10);
auto parse_stop = ParseInt<int>(args[3], 10);
if (!parse_start || !parse_stop) {
return {Status::RedisParseErr, errValueNotInteger};
}
start_ = *parse_start;
stop_ = *parse_stop;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
std::vector<std::string> elems;
auto s = list_db.Range(ctx, args_[1], start_, stop_, &elems);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = ArrayOfBulkStrings(elems);
return Status::OK();
}
private:
int start_ = 0, stop_ = 0;
};
class CommandLLen : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
uint64_t count = 0;
auto s = list_db.Size(ctx, args_[1], &count);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::Integer(count);
return Status::OK();
}
};
class CommandLIndex : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto parse_result = ParseInt<int>(args[2], 10);
if (!parse_result) {
return {Status::RedisParseErr, errValueNotInteger};
}
index_ = *parse_result;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
std::string elem;
auto s = list_db.Index(ctx, args_[1], index_, &elem);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
if (s.IsNotFound()) {
*output = conn->NilString();
} else {
*output = redis::BulkString(elem);
}
return Status::OK();
}
private:
int index_ = 0;
};
class CommandLSet : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto parse_result = ParseInt<int>(args[2], 10);
if (!parse_result) {
return {Status::RedisParseErr, errValueNotInteger};
}
index_ = *parse_result;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
auto s = list_db.Set(ctx, args_[1], index_, args_[3]);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
if (s.IsNotFound()) {
return {Status::RedisExecErr, errNoSuchKey};
}
*output = redis::RESP_OK;
return Status::OK();
}
private:
int index_ = 0;
};
class CommandLTrim : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto parse_start = ParseInt<int>(args[2], 10);
auto parse_stop = ParseInt<int>(args[3], 10);
if (!parse_start || !parse_stop) {
return {Status::RedisParseErr, errValueNotInteger};
}
start_ = *parse_start;
stop_ = *parse_stop;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
auto s = list_db.Trim(ctx, args_[1], start_, stop_);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::RESP_OK;
return Status::OK();
}
private:
int start_ = 0;
int stop_ = 0;
};
class CommandRPopLPUSH : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
std::string elem;
auto s = list_db.LMove(ctx, args_[1], args_[2], /*src_left=*/false, /*dst_left=*/true, &elem);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = s.IsNotFound() ? conn->NilString() : redis::BulkString(elem);
return Status::OK();
}
};
class CommandLMove : public Commander {
public:
Status Parse([[maybe_unused]] const std::vector<std::string> &args) override {
auto arg_val = util::ToLower(args_[3]);
if (arg_val != "left" && arg_val != "right") {
return {Status::RedisParseErr, errInvalidSyntax};
}
src_left_ = arg_val == "left";
arg_val = util::ToLower(args_[4]);
if (arg_val != "left" && arg_val != "right") {
return {Status::RedisParseErr, errInvalidSyntax};
}
dst_left_ = arg_val == "left";
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
std::string elem;
auto s = list_db.LMove(ctx, args_[1], args_[2], src_left_, dst_left_, &elem);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = s.IsNotFound() ? conn->NilString() : redis::BulkString(elem);
return Status::OK();
}
private:
bool src_left_;
bool dst_left_;
};
class CommandBLMove : public BlockingCommander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto arg_val = util::ToLower(args_[3]);
if (arg_val != "left" && arg_val != "right") {
return {Status::RedisParseErr, errInvalidSyntax};
}
src_left_ = arg_val == "left";
arg_val = util::ToLower(args_[4]);
if (arg_val != "left" && arg_val != "right") {
return {Status::RedisParseErr, errInvalidSyntax};
}
dst_left_ = arg_val == "left";
auto parse_result = ParseFloat(args[args.size() - 1]);
if (!parse_result) {
return {Status::RedisParseErr, errTimeoutIsNotFloat};
}
if (*parse_result < 0) {
return {Status::RedisParseErr, errTimeoutIsNegative};
}
timeout_ = static_cast<int64_t>(*parse_result * 1000 * 1000);
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
srv_ = srv;
InitConnection(conn);
redis::List list_db(srv->storage, conn->GetNamespace());
std::string elem;
auto s = list_db.LMove(ctx, args_[1], args_[2], src_left_, dst_left_, &elem);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
if (!elem.empty()) {
*output = redis::BulkString(elem);
return Status::OK();
}
return StartBlocking(timeout_, output);
}
void BlockKeys() override { srv_->BlockOnKey(args_[1], conn_); }
void UnblockKeys() override { srv_->UnblockOnKey(args_[1], conn_); }
MultiLockGuard GetLocks() override {
std::vector<std::string> lock_keys{
ComposeNamespaceKey(conn_->GetNamespace(), args_[1], srv_->storage->IsSlotIdEncoded())};
if (args_[1] != args_[2]) {
lock_keys.emplace_back(ComposeNamespaceKey(conn_->GetNamespace(), args_[2], srv_->storage->IsSlotIdEncoded()));
}
return MultiLockGuard(srv_->storage->GetLockManager(), lock_keys);
}
bool OnBlockingWrite() override {
redis::List list_db(srv_->storage, conn_->GetNamespace());
std::string elem;
engine::Context ctx(srv_->storage);
auto s = list_db.LMove(ctx, args_[1], args_[2], src_left_, dst_left_, &elem);
if (!s.ok() && !s.IsNotFound()) {
conn_->Reply(redis::Error({Status::NotOK, s.ToString()}));
return true;
}
bool empty = elem.empty();
if (!empty) {
conn_->Reply(redis::BulkString(elem));
}
return !empty;
}
std::string NoopReply(const Connection *conn) override { return conn->NilArray(); }
private:
bool src_left_;
bool dst_left_;
int64_t timeout_ = 0; // microseconds
Server *srv_ = nullptr;
};
class CommandLPos : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (args.size() > 9) {
return {Status::RedisParseErr, errInvalidSyntax};
}
CommandParser parser(args, 3);
while (parser.Good()) {
if (parser.EatEqICase("rank")) {
spec_.rank = GET_OR_RET(parser.TakeInt());
if (spec_.rank == 0) {
return {Status::RedisParseErr,
"RANK can't be zero: use 1 to start from "
"the first match, 2 from the second ... "
"or use negative to start from the end of the list"};
} else if (spec_.rank == LLONG_MIN) {
// Negating LLONG_MIN will cause an overflow, and is effectively be the same as passing -1.
return {Status::RedisParseErr, "rank would overflow"};
}
} else if (parser.EatEqICase("count")) {
spec_.count = GET_OR_RET(parser.TakeInt());
if (spec_.count < 0) {
return {Status::RedisParseErr, "COUNT can't be negative"};
}
} else if (parser.EatEqICase("maxlen")) {
spec_.max_len = GET_OR_RET(parser.TakeInt());
if (spec_.max_len < 0) {
return {Status::RedisParseErr, "MAXLEN can't be negative"};
}
} else {
return {Status::RedisParseErr, errInvalidSyntax};
}
}
return Status::OK();
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::List list_db(srv->storage, conn->GetNamespace());
std::vector<int64_t> indexes;
auto s = list_db.Pos(ctx, args_[1], args_[2], spec_, &indexes);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
// We return nil or a single value if `COUNT` option is not given.
if (!spec_.count.has_value()) {
if (s.IsNotFound() || indexes.empty()) {
*output = conn->NilString();
} else {
assert(indexes.size() == 1);
*output = redis::Integer(indexes[0]);
}
}
// Otherwise we return an array.
else {
std::vector<std::string> values;
values.reserve(indexes.size());
for (const auto &index : indexes) {
values.emplace_back(std::to_string(index));
}
*output = ArrayOfBulkStrings(values);
}
return Status::OK();
}
private:
PosSpec spec_;
};
REDIS_REGISTER_COMMANDS(List, MakeCmdAttr<CommandBLPop>("blpop", -3, "write blocking", 1, -2, 1),
MakeCmdAttr<CommandBRPop>("brpop", -3, "write blocking", 1, -2, 1),
MakeCmdAttr<CommandBLMPop>("blmpop", -5, "write blocking", CommandBLMPop::keyRangeGen),
MakeCmdAttr<CommandLIndex>("lindex", 3, "read-only", 1, 1, 1),
MakeCmdAttr<CommandLInsert>("linsert", 5, "write slow", 1, 1, 1),
MakeCmdAttr<CommandLLen>("llen", 2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandLMove>("lmove", 5, "write", 1, 2, 1),
MakeCmdAttr<CommandBLMove>("blmove", 6, "write blocking", 1, 2, 1),
MakeCmdAttr<CommandLPop>("lpop", -2, "write", 1, 1, 1), //
MakeCmdAttr<CommandLPos>("lpos", -3, "read-only", 1, 1, 1),
MakeCmdAttr<CommandLPush>("lpush", -3, "write", 1, 1, 1),
MakeCmdAttr<CommandLPushX>("lpushx", -3, "write", 1, 1, 1),
MakeCmdAttr<CommandLRange>("lrange", 4, "read-only", 1, 1, 1),
MakeCmdAttr<CommandLRem>("lrem", 4, "write no-dbsize-check slow", 1, 1, 1),
MakeCmdAttr<CommandLSet>("lset", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandLTrim>("ltrim", 4, "write no-dbsize-check", 1, 1, 1),
MakeCmdAttr<CommandLMPop>("lmpop", -4, "write", CommandLMPop::keyRangeGen),
MakeCmdAttr<CommandRPop>("rpop", -2, "write", 1, 1, 1),
MakeCmdAttr<CommandRPopLPUSH>("rpoplpush", 3, "write", 1, 2, 1),
MakeCmdAttr<CommandRPush>("rpush", -3, "write", 1, 1, 1),
MakeCmdAttr<CommandRPushX>("rpushx", -3, "write", 1, 1, 1), )
} // namespace redis