blob: dbbcfdc6b84b52dc2a396847698e6659179b5d6b [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 <limits>
#include <optional>
#include <string_view>
#include "commander.h"
#include "commands/command_parser.h"
#include "error_constants.h"
#include "scan_base.h"
#include "server/server.h"
#include "time_util.h"
#include "types/redis_hash.h"
namespace redis {
namespace {
constexpr uint64_t kMaxHashFieldExpireAtMs = (static_cast<uint64_t>(1) << 46) - 1;
enum class HashFieldExpireTimeMode {
kRelativeSeconds,
kRelativeMilliseconds,
kAbsoluteSeconds,
kAbsoluteMilliseconds,
};
std::vector<Slice> ToSlices(const std::vector<std::string> &values) {
std::vector<Slice> slices;
slices.reserve(values.size());
for (const auto &value : values) {
slices.emplace_back(value);
}
return slices;
}
std::string IntegerArray(const std::vector<int64_t> &results) {
std::vector<std::string> entries;
entries.reserve(results.size());
for (auto result : results) {
entries.emplace_back(redis::Integer(result));
}
return redis::Array(entries);
}
uint64_t CeilDiv1000(uint64_t value) { return value / 1000 + (value % 1000 != 0 ? 1 : 0); }
Status ParseHashFieldExpireArgument(std::string_view raw, int64_t *expire_arg) {
auto parsed = ParseInt<int64_t>(raw, 10);
if (!parsed) {
return {Status::RedisParseErr, errValueNotInteger};
}
if (*parsed < 0) {
return {Status::RedisParseErr, "invalid expire time, must be >= 0"};
}
*expire_arg = *parsed;
return Status::OK();
}
Status ConvertHashFieldExpireAtMs(int64_t expire_arg, HashFieldExpireTimeMode time_mode, uint64_t now_ms,
uint64_t *expire_at_ms) {
auto value = static_cast<uint64_t>(expire_arg);
bool seconds =
time_mode == HashFieldExpireTimeMode::kRelativeSeconds || time_mode == HashFieldExpireTimeMode::kAbsoluteSeconds;
bool relative = time_mode == HashFieldExpireTimeMode::kRelativeSeconds ||
time_mode == HashFieldExpireTimeMode::kRelativeMilliseconds;
if (seconds) {
if (value > kMaxHashFieldExpireAtMs / 1000) {
return {Status::RedisExecErr, "expire time overflow"};
}
value *= 1000;
}
if (value > kMaxHashFieldExpireAtMs) {
return {Status::RedisExecErr, "expire time overflow"};
}
if (relative) {
if (value > kMaxHashFieldExpireAtMs - now_ms) {
return {Status::RedisExecErr, "expire time overflow"};
}
value += now_ms;
}
*expire_at_ms = value;
return Status::OK();
}
std::optional<HashFieldExpireCondition> ParseHashExpireCondition(std::string_view token) {
if (util::EqualICase(token, "NX")) return HashFieldExpireCondition::kNX;
if (util::EqualICase(token, "XX")) return HashFieldExpireCondition::kXX;
if (util::EqualICase(token, "GT")) return HashFieldExpireCondition::kGT;
if (util::EqualICase(token, "LT")) return HashFieldExpireCondition::kLT;
return std::nullopt;
}
template <typename Parser>
Status ParseHashFieldListTail(Parser &parser, std::vector<std::string> *fields) {
if (!parser.Good()) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
auto num_fields = parser.template TakeInt<int64_t>(NumericRange<int64_t>{1, std::numeric_limits<int64_t>::max()}, 10);
if (!num_fields) {
return {Status::RedisParseErr, errValueNotInteger};
}
if (static_cast<size_t>(*num_fields) != parser.Remains()) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
fields->clear();
fields->reserve(static_cast<size_t>(*num_fields));
while (parser.Good()) {
fields->emplace_back(GET_OR_RET(parser.TakeStr()));
}
return Status::OK();
}
Status ParseHashFixedFields(const std::vector<std::string> &args, std::vector<std::string> *fields) {
CommandParser parser(args, 2);
if (!parser.EatEqICase("FIELDS")) {
return {Status::RedisParseErr, errInvalidSyntax};
}
return ParseHashFieldListTail(parser, fields);
}
Status ParseHashExpireFields(const std::vector<std::string> &args, size_t start,
HashFieldExpireCondition *condition_out, std::vector<std::string> *fields) {
*condition_out = HashFieldExpireCondition::kNone;
fields->clear();
bool fields_seen = false;
for (size_t i = start; i < args.size();) {
if (util::EqualICase(args[i], "FIELDS")) {
if (fields_seen) {
return {Status::RedisParseErr, errInvalidSyntax};
}
fields_seen = true;
if (i + 1 >= args.size()) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
auto num_fields = ParseInt<int64_t>(args[i + 1], 10);
if (!num_fields || *num_fields < 1) {
return {Status::RedisParseErr, errValueNotInteger};
}
size_t first_field = i + 2;
auto field_count = static_cast<size_t>(*num_fields);
if (field_count > args.size() - first_field) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
fields->clear();
fields->reserve(field_count);
for (size_t j = 0; j < field_count; j++) {
fields->emplace_back(args[first_field + j]);
}
i = first_field + field_count;
continue;
}
auto condition = ParseHashExpireCondition(args[i]);
if (!condition) {
return {Status::RedisParseErr, errInvalidSyntax};
}
if (*condition_out != HashFieldExpireCondition::kNone && *condition_out != *condition) {
return {Status::RedisParseErr, errInvalidSyntax};
}
*condition_out = *condition;
i++;
}
if (!fields_seen) {
return {Status::RedisParseErr, errInvalidSyntax};
}
return Status::OK();
}
int64_t FormatHashFieldExpireResult(int64_t expire_at, uint64_t now, HashFieldExpireTimeMode time_mode) {
if (expire_at < 0) {
return expire_at;
}
auto expire = static_cast<uint64_t>(expire_at);
switch (time_mode) {
case HashFieldExpireTimeMode::kRelativeSeconds:
return static_cast<int64_t>(CeilDiv1000(expire > now ? expire - now : 0));
case HashFieldExpireTimeMode::kRelativeMilliseconds:
return static_cast<int64_t>(expire > now ? expire - now : 0);
case HashFieldExpireTimeMode::kAbsoluteSeconds:
return static_cast<int64_t>(CeilDiv1000(expire));
case HashFieldExpireTimeMode::kAbsoluteMilliseconds:
return expire_at;
}
return -2;
}
uint64_t GenerateHLenFlags(uint64_t flags, const std::vector<std::string> &args, const Config &config) {
bool needs_repair = false;
if (args.size() == 2) {
needs_repair = config.hash_encoding_mode == HashSubkeyEncodingMode::kFieldExpiration &&
config.hash_length_mode == HashLengthMode::kAccurate;
} else if (args.size() == 3) {
needs_repair = util::EqualICase(args[2], "REPAIR");
}
if (!needs_repair) {
return flags;
}
return (flags | kCmdWrite | kCmdNoDBSizeCheck) & ~kCmdReadOnly;
}
} // namespace
class CommandHGet : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::string value;
auto s = hash_db.Get(ctx, args_[1], args_[2], &value);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = s.IsNotFound() ? conn->NilString() : redis::BulkString(value);
return Status::OK();
}
};
class CommandHSetNX : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (args.size() % 2 != 0) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
for (size_t i = 2; i < args_.size(); i += 2) {
field_values_.emplace_back(args_[i], args_[i + 1]);
}
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
uint64_t ret = 0;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.MSet(ctx, args_[1], field_values_, true, &ret);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::Integer(ret);
return Status::OK();
}
private:
std::vector<FieldValue> field_values_;
};
class CommandHStrlen : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::string value;
auto s = hash_db.Get(ctx, args_[1], args_[2], &value);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::Integer(static_cast<int>(value.size()));
return Status::OK();
}
};
class CommandHDel : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
std::vector<Slice> fields;
for (size_t i = 2; i < args_.size(); i++) {
fields.emplace_back(args_[i]);
}
uint64_t ret = 0;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.Delete(ctx, args_[1], fields, &ret);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::Integer(ret);
return Status::OK();
}
};
class CommandHExists : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::string value;
auto s = hash_db.Get(ctx, args_[1], args_[2], &value);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = s.IsNotFound() ? redis::Integer(0) : redis::Integer(1);
return Status::OK();
}
};
class CommandHLen : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (args.size() == 2) {
length_mode_ = std::nullopt;
return Commander::Parse(args);
}
if (args.size() != 3) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
if (util::EqualICase(args[2], "APPROX")) {
length_mode_ = HashLengthMode::kApproximate;
} else if (util::EqualICase(args[2], "REPAIR")) {
length_mode_ = HashLengthMode::kAccurate;
} else {
return {Status::RedisParseErr, errInvalidSyntax};
}
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
uint64_t count = 0;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = length_mode_ ? hash_db.Size(ctx, args_[1], &count, *length_mode_) : hash_db.Size(ctx, args_[1], &count);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = s.IsNotFound() ? redis::Integer(0) : redis::Integer(count);
return Status::OK();
}
private:
std::optional<HashLengthMode> length_mode_;
};
class CommandHIncrBy : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto parse_result = ParseInt<int64_t>(args[3], 10);
if (!parse_result) {
return {Status::RedisParseErr, errValueNotInteger};
}
increment_ = *parse_result;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
int64_t ret = 0;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.IncrBy(ctx, args_[1], args_[2], increment_, &ret);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::Integer(ret);
return Status::OK();
}
private:
int64_t increment_ = 0;
};
class CommandHIncrByFloat : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
auto increment = ParseFloat(args[3]);
if (!increment) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
increment_ = *increment;
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
double ret = 0;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.IncrByFloat(ctx, args_[1], args_[2], increment_, &ret);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::BulkString(util::Float2String(ret));
return Status::OK();
}
private:
double increment_ = 0;
};
class CommandHMGet : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
std::vector<Slice> fields;
for (size_t i = 2; i < args_.size(); i++) {
fields.emplace_back(args_[i]);
}
std::vector<std::string> values;
std::vector<rocksdb::Status> statuses;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.MGet(ctx, args_[1], fields, &values, &statuses);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
if (s.IsNotFound()) {
values.resize(fields.size(), "");
*output = conn->MultiBulkString(values);
} else {
*output = conn->MultiBulkString(values, statuses);
}
return Status::OK();
}
};
class CommandHMSet : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (args.size() % 2 != 0) {
return {Status::RedisParseErr, errWrongNumOfArguments};
}
for (size_t i = 2; i < args_.size(); i += 2) {
field_values_.emplace_back(args_[i], args_[i + 1]);
}
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
uint64_t ret = 0;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.MSet(ctx, args_[1], field_values_, false, &ret);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
if (GetAttributes()->name == "hset") {
*output = redis::Integer(ret);
} else {
*output = redis::RESP_OK;
}
return Status::OK();
}
private:
std::vector<FieldValue> field_values_;
};
class CommandHSetExpire : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
ttl_ = GET_OR_RET(ParseInt<uint64_t>(args[2], 10));
if ((args.size() - 3) % 2 != 0) {
return {Status::RedisParseErr, "Invalid number of arguments: field-value pairs must be complete"};
}
for (size_t i = 3; i < args_.size(); i += 2) {
field_values_.emplace_back(args_[i], args_[i + 1]);
}
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
uint64_t ret = 0;
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.MSet(ctx, args_[1], field_values_, false, &ret, ttl_ * 1000 + util::GetTimeStampMS());
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = redis::RESP_OK;
return Status::OK();
}
private:
std::vector<FieldValue> field_values_;
uint64_t ttl_ = 0;
};
class CommandHKeys : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::vector<FieldValue> field_values;
auto s = hash_db.GetAll(ctx, args_[1], &field_values, HashFetchType::kOnlyKey);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
std::vector<std::string> keys;
keys.reserve(field_values.size());
for (const auto &fv : field_values) {
keys.emplace_back(fv.field);
}
*output = conn->MultiBulkString(keys);
return Status::OK();
}
};
class CommandHVals : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::vector<FieldValue> field_values;
auto s = hash_db.GetAll(ctx, args_[1], &field_values, HashFetchType::kOnlyValue);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
std::vector<std::string> values;
values.reserve(field_values.size());
for (const auto &p : field_values) {
values.emplace_back(p.value);
}
*output = ArrayOfBulkStrings(values);
return Status::OK();
}
};
class CommandHGetAll : public Commander {
public:
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::vector<FieldValue> field_values;
auto s = hash_db.GetAll(ctx, args_[1], &field_values);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
std::vector<std::string> kv_pairs;
kv_pairs.reserve(field_values.size());
for (const auto &p : field_values) {
kv_pairs.emplace_back(p.field);
kv_pairs.emplace_back(p.value);
}
*output = conn->MapOfBulkStrings(kv_pairs);
return Status::OK();
}
};
class CommandHRangeByLex : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
CommandParser parser(args, 4);
while (parser.Good()) {
if (parser.EatEqICase("REV")) {
spec_.reversed = true;
} else if (parser.EatEqICase("LIMIT")) {
spec_.offset = GET_OR_RET(parser.TakeInt());
spec_.count = GET_OR_RET(parser.TakeInt());
} else {
return parser.InvalidSyntax();
}
}
if (spec_.reversed) {
return ParseRangeLexSpec(args[3], args[2], &spec_);
} else {
return ParseRangeLexSpec(args[2], args[3], &spec_);
}
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::vector<FieldValue> field_values;
rocksdb::Status s = hash_db.RangeByLex(ctx, args_[1], spec_, &field_values);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
std::vector<std::string> kv_pairs;
for (const auto &p : field_values) {
kv_pairs.emplace_back(p.field);
kv_pairs.emplace_back(p.value);
}
*output = ArrayOfBulkStrings(kv_pairs);
return Status::OK();
}
private:
RangeLexSpec spec_;
};
class CommandHScan : public CommandSubkeyScanBase {
public:
CommandHScan() = default;
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::vector<std::string> fields;
std::vector<std::string> values;
auto key_name = srv->GetKeyNameFromCursor(cursor_, CursorType::kTypeHash);
auto s = hash_db.Scan(ctx, key_, key_name, limit_, prefix_, &fields, no_values_ ? nullptr : &values);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
auto cursor = GetNextCursor(srv, fields, CursorType::kTypeHash);
std::vector<std::string> entries;
if (no_values_) {
entries.reserve(fields.size());
} else {
entries.reserve(2 * fields.size());
}
for (size_t i = 0; i < fields.size(); i++) {
entries.emplace_back(redis::BulkString(fields[i]));
if (!no_values_) {
entries.emplace_back(redis::BulkString(values[i]));
}
}
*output = redis::Array({redis::BulkString(cursor), redis::Array(entries)});
return Status::OK();
}
};
class CommandHRandField : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
if (args.size() >= 3) {
no_parameters_ = false;
auto parse_result = ParseInt<int64_t>(args[2], 10);
if (!parse_result) {
return {Status::RedisParseErr, errValueNotInteger};
}
command_count_ = *parse_result;
if (args.size() > 4 || (args.size() == 4 && !util::EqualICase(args[3], "withvalues"))) {
return {Status::RedisParseErr, errInvalidSyntax};
} else if (args.size() == 4) {
withvalues_ = true;
}
}
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
redis::Hash hash_db(srv->storage, conn->GetNamespace());
std::vector<FieldValue> field_values;
auto s = hash_db.RandField(ctx, args_[1], command_count_, &field_values,
withvalues_ ? HashFetchType::kAll : HashFetchType::kOnlyKey);
if (!s.ok() && !s.IsNotFound()) {
return {Status::RedisExecErr, s.ToString()};
}
std::vector<std::string> result_entries;
result_entries.reserve(field_values.size());
for (const auto &p : field_values) {
result_entries.emplace_back(p.field);
if (withvalues_) result_entries.emplace_back(p.value);
}
if (no_parameters_)
*output = s.IsNotFound() ? conn->NilString() : redis::BulkString(result_entries[0]);
else
*output = ArrayOfBulkStrings(result_entries);
return Status::OK();
}
private:
bool withvalues_ = false;
int64_t command_count_ = 1;
bool no_parameters_ = true;
};
template <HashFieldExpireTimeMode kTimeMode>
class CommandHExpireGeneric : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
GET_OR_RET(ParseHashFieldExpireArgument(args[2], &expire_arg_));
GET_OR_RET(ParseHashExpireFields(args, 3, &condition_, &fields_));
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
uint64_t now = util::GetTimeStampMS();
uint64_t expire_at = 0;
GET_OR_RET(ConvertHashFieldExpireAtMs(expire_arg_, kTimeMode, now, &expire_at));
std::vector<int64_t> results;
std::vector<Slice> fields = ToSlices(fields_);
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.ExpireFields(ctx, args_[1], fields, expire_at, condition_, &results, now);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = IntegerArray(results);
return Status::OK();
}
private:
int64_t expire_arg_ = 0;
HashFieldExpireCondition condition_ = HashFieldExpireCondition::kNone;
std::vector<std::string> fields_;
};
class CommandHPersist : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
GET_OR_RET(ParseHashFixedFields(args, &fields_));
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
std::vector<int64_t> results;
std::vector<Slice> fields = ToSlices(fields_);
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.PersistFields(ctx, args_[1], fields, &results);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
*output = IntegerArray(results);
return Status::OK();
}
private:
std::vector<std::string> fields_;
};
template <HashFieldExpireTimeMode kTimeMode>
class CommandHExpireInfo : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
GET_OR_RET(ParseHashFixedFields(args, &fields_));
return Commander::Parse(args);
}
Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override {
uint64_t now = util::GetTimeStampMS();
std::vector<int64_t> results;
std::vector<Slice> fields = ToSlices(fields_);
redis::Hash hash_db(srv->storage, conn->GetNamespace());
auto s = hash_db.GetFieldsExpireTime(ctx, args_[1], fields, &results, now);
if (!s.ok()) {
return {Status::RedisExecErr, s.ToString()};
}
for (auto &result : results) {
result = FormatHashFieldExpireResult(result, now, kTimeMode);
}
*output = IntegerArray(results);
return Status::OK();
}
private:
std::vector<std::string> fields_;
};
REDIS_REGISTER_COMMANDS(Hash, MakeCmdAttr<CommandHGet>("hget", 3, "read-only", 1, 1, 1),
MakeCmdAttr<CommandHIncrBy>("hincrby", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandHIncrByFloat>("hincrbyfloat", 4, "write", 1, 1, 1),
MakeCmdAttr<CommandHMSet>("hset", -4, "write", 1, 1, 1),
MakeCmdAttr<CommandHSetExpire>("hsetexpire", -5, "write", 1, 1, 1),
MakeCmdAttr<CommandHSetNX>("hsetnx", -4, "write", 1, 1, 1),
MakeCmdAttr<CommandHDel>("hdel", -3, "write no-dbsize-check", 1, 1, 1),
MakeCmdAttr<CommandHStrlen>("hstrlen", 3, "read-only", 1, 1, 1),
MakeCmdAttr<CommandHExists>("hexists", 3, "read-only", 1, 1, 1),
MakeCmdAttr<CommandHLen>("hlen", -2, "read-only", 1, 1, 1, GenerateHLenFlags),
MakeCmdAttr<CommandHMGet>("hmget", -3, "read-only", 1, 1, 1),
MakeCmdAttr<CommandHMSet>("hmset", -4, "write", 1, 1, 1),
MakeCmdAttr<CommandHKeys>("hkeys", 2, "read-only slow", 1, 1, 1),
MakeCmdAttr<CommandHVals>("hvals", 2, "read-only slow", 1, 1, 1),
MakeCmdAttr<CommandHGetAll>("hgetall", 2, "read-only slow", 1, 1, 1),
MakeCmdAttr<CommandHScan>("hscan", -3, "read-only", 1, 1, 1),
MakeCmdAttr<CommandHRangeByLex>("hrangebylex", -4, "read-only", 1, 1, 1),
MakeCmdAttr<CommandHRandField>("hrandfield", -2, "read-only slow", 1, 1, 1), )
} // namespace redis