src/commands/cmd_bit.cc (325 lines of code) (raw):

/* * 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/command_parser.h" #include "error_constants.h" #include "server/server.h" #include "status.h" #include "types/redis_bitmap.h" namespace redis { Status GetBitOffsetFromArgument(const std::string &arg, uint32_t *offset) { auto parse_result = ParseInt<uint32_t>(arg, 10); if (!parse_result) { return parse_result.ToStatus(); } *offset = *parse_result; return Status::OK(); } class CommandGetBit : public Commander { public: Status Parse(const std::vector<std::string> &args) override { Status s = GetBitOffsetFromArgument(args[2], &offset_); if (!s.IsOK()) return {Status::RedisParseErr, "bit offset is not an integer or out of range"}; return Commander::Parse(args); } Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { bool bit = false; redis::Bitmap bitmap_db(srv->storage, conn->GetNamespace()); auto s = bitmap_db.GetBit(ctx, args_[1], offset_, &bit); if (!s.ok()) return {Status::RedisExecErr, s.ToString()}; *output = redis::Integer(bit ? 1 : 0); return Status::OK(); } private: uint32_t offset_ = 0; }; class CommandSetBit : public Commander { public: Status Parse(const std::vector<std::string> &args) override { Status s = GetBitOffsetFromArgument(args[2], &offset_); if (!s.IsOK()) return s; if (args[3] == "0") { bit_ = false; } else if (args[3] == "1") { bit_ = true; } else { return {Status::RedisParseErr, "bit is not an integer or out of range"}; } return Commander::Parse(args); } Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { bool old_bit = false; redis::Bitmap bitmap_db(srv->storage, conn->GetNamespace()); auto s = bitmap_db.SetBit(ctx, args_[1], offset_, bit_, &old_bit); if (!s.ok()) return {Status::RedisExecErr, s.ToString()}; *output = redis::Integer(old_bit ? 1 : 0); return Status::OK(); } private: uint32_t offset_ = 0; bool bit_ = false; }; // BITCOUNT key [start end [BYTE | BIT]] class CommandBitCount : public Commander { public: Status Parse(const std::vector<std::string> &args) override { if (args.size() == 3) { return {Status::RedisParseErr, errInvalidSyntax}; } if (args.size() > 5) { return {Status::RedisParseErr, errInvalidSyntax}; } if (args.size() >= 4) { auto parse_start = ParseInt<int64_t>(args[2], 10); if (!parse_start) { return {Status::RedisParseErr, errValueNotInteger}; } start_ = *parse_start; auto parse_stop = ParseInt<int64_t>(args[3], 10); if (!parse_stop) { return {Status::RedisParseErr, errValueNotInteger}; } stop_ = *parse_stop; } if (args.size() == 5) { if (util::EqualICase(args[4], "BYTE")) { is_bit_index_ = false; } else if (util::EqualICase(args[4], "BIT")) { is_bit_index_ = true; } else { return {Status::RedisParseErr, errInvalidSyntax}; } } return Commander::Parse(args); } Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { uint32_t cnt = 0; redis::Bitmap bitmap_db(srv->storage, conn->GetNamespace()); auto s = bitmap_db.BitCount(ctx, args_[1], start_, stop_, is_bit_index_, &cnt); if (!s.ok()) return {Status::RedisExecErr, s.ToString()}; *output = redis::Integer(cnt); return Status::OK(); } private: int64_t start_ = 0; int64_t stop_ = -1; bool is_bit_index_ = false; }; class CommandBitPos : public Commander { public: using Commander::Parse; Status Parse(const std::vector<std::string> &args) override { if (args.size() >= 4) { auto parse_start = ParseInt<int64_t>(args[3], 10); if (!parse_start) { return {Status::RedisParseErr, errValueNotInteger}; } start_ = *parse_start; } if (args.size() >= 5) { auto parse_stop = ParseInt<int64_t>(args[4], 10); if (!parse_stop) { return {Status::RedisParseErr, errValueNotInteger}; } stop_given_ = true; stop_ = *parse_stop; } if (args.size() >= 6 && util::EqualICase(args[5], "BIT")) { is_bit_index_ = true; } auto parse_arg = ParseInt<int64_t>(args[2], 10); if (!parse_arg) { return {Status::RedisParseErr, errValueNotInteger}; } if (*parse_arg == 0) { bit_ = false; } else if (*parse_arg == 1) { bit_ = true; } else { return {Status::RedisParseErr, "The bit argument must be 1 or 0."}; } return Commander::Parse(args); } Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { int64_t pos = 0; redis::Bitmap bitmap_db(srv->storage, conn->GetNamespace()); auto s = bitmap_db.BitPos(ctx, args_[1], bit_, start_, stop_, stop_given_, &pos, is_bit_index_); if (!s.ok()) return {Status::RedisExecErr, s.ToString()}; *output = redis::Integer(pos); return Status::OK(); } private: int64_t start_ = 0; int64_t stop_ = -1; bool bit_ = false; bool stop_given_ = false; bool is_bit_index_ = false; }; class CommandBitOp : public Commander { public: Status Parse(const std::vector<std::string> &args) override { std::string opname = util::ToLower(args[1]); if (opname == "and") op_flag_ = kBitOpAnd; else if (opname == "or") op_flag_ = kBitOpOr; else if (opname == "xor") op_flag_ = kBitOpXor; else if (opname == "not") op_flag_ = kBitOpNot; else return {Status::RedisInvalidCmd, errInvalidSyntax}; if (op_flag_ == kBitOpNot && args.size() != 4) { return {Status::RedisInvalidCmd, "BITOP NOT must be called with a single source key."}; } return Commander::Parse(args); } Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { std::vector<Slice> op_keys; op_keys.reserve(args_.size() - 2); for (uint64_t i = 3; i < args_.size(); i++) { op_keys.emplace_back(args_[i]); } int64_t dest_key_len = 0; redis::Bitmap bitmap_db(srv->storage, conn->GetNamespace()); auto s = bitmap_db.BitOp(ctx, op_flag_, args_[1], args_[2], op_keys, &dest_key_len); if (!s.ok()) return {Status::RedisExecErr, s.ToString()}; *output = redis::Integer(dest_key_len); return Status::OK(); } private: BitOpFlags op_flag_; }; template <bool ReadOnly> class CommandBitfield : public Commander { public: Status Parse(const std::vector<std::string> &args) override { BitfieldOperation cmd; read_only_ = true; // BITFIELD <key> [commands...] for (CommandParser group(args, 2); group.Good();) { auto remains = group.Remains(); std::string opcode = util::ToLower(group[0]); if (opcode == "get") { cmd.type = BitfieldOperation::Type::kGet; } else if (opcode == "set") { cmd.type = BitfieldOperation::Type::kSet; read_only_ = false; } else if (opcode == "incrby") { cmd.type = BitfieldOperation::Type::kIncrBy; read_only_ = false; } else if (opcode == "overflow") { constexpr auto kOverflowCmdSize = 2; if (remains < kOverflowCmdSize) { return {Status::RedisParseErr, errWrongNumOfArguments}; } auto s = parseOverflowSubCommand(group[1], &cmd); if (!s.IsOK()) { return s; } group.Skip(kOverflowCmdSize); continue; } else { return {Status::RedisParseErr, errUnknownSubcommandOrWrongArguments}; } if (remains < 3) { return {Status::RedisParseErr, errWrongNumOfArguments}; } // parse encoding auto encoding = parseBitfieldEncoding(group[1]); if (!encoding.IsOK()) { return encoding.ToStatus(); } cmd.encoding = encoding.GetValue(); // parse offset if (!GetBitOffsetFromArgument(group[2], &cmd.offset).IsOK()) { return {Status::RedisParseErr, "bit offset is not an integer or out of range"}; } if (cmd.type != BitfieldOperation::Type::kGet) { if (remains < 4) { return {Status::RedisParseErr, errWrongNumOfArguments}; } auto value = ParseInt<int64_t>(group[3], 10); if (!value.IsOK()) { return value.ToStatus(); } cmd.value = value.GetValue(); // SET|INCRBY <encoding> <offset> <value> group.Skip(4); } else { // GET <encoding> <offset> group.Skip(3); } cmds_.push_back(cmd); } if constexpr (ReadOnly) { if (!read_only_) { return {Status::RedisParseErr, "BITFIELD_RO only supports the GET subcommand"}; } } return Commander::Parse(args); } Status Execute(engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { redis::Bitmap bitmap_db(srv->storage, conn->GetNamespace()); std::vector<std::optional<BitfieldValue>> rets; rocksdb::Status s; if (read_only_) { s = bitmap_db.BitfieldReadOnly(ctx, args_[1], cmds_, &rets); } else { s = bitmap_db.Bitfield(ctx, args_[1], cmds_, &rets); } std::vector<std::string> str_rets(rets.size()); for (size_t i = 0; i != rets.size(); ++i) { if (rets[i].has_value()) { if (rets[i]->Encoding().IsSigned()) { str_rets[i] = redis::Integer(CastToSignedWithoutBitChanges(rets[i]->Value())); } else { str_rets[i] = redis::Integer(rets[i]->Value()); } } else { str_rets[i] = conn->NilString(); } } *output = redis::Array(str_rets); return Status::OK(); } private: static Status parseOverflowSubCommand(const std::string &overflow, BitfieldOperation *cmd) { std::string lower = util::ToLower(overflow); if (lower == "wrap") { cmd->overflow = BitfieldOverflowBehavior::kWrap; } else if (lower == "sat") { cmd->overflow = BitfieldOverflowBehavior::kSat; } else if (lower == "fail") { cmd->overflow = BitfieldOverflowBehavior::kFail; } else { return {Status::RedisParseErr, errUnknownSubcommandOrWrongArguments}; } return Status::OK(); } static StatusOr<BitfieldEncoding> parseBitfieldEncoding(const std::string &token) { if (token.empty()) { return {Status::RedisParseErr, errUnknownSubcommandOrWrongArguments}; } auto sign = std::tolower(token[0]); if (sign != 'u' && sign != 'i') { return {Status::RedisParseErr, errUnknownSubcommandOrWrongArguments}; } auto type = BitfieldEncoding::Type::kUnsigned; if (sign == 'i') { type = BitfieldEncoding::Type::kSigned; } auto bits_parse = ParseInt<uint8_t>(token.substr(1), 10); if (!bits_parse.IsOK()) { return bits_parse.ToStatus(); } uint8_t bits = bits_parse.GetValue(); auto encoding = BitfieldEncoding::Create(type, bits); if (!encoding.IsOK()) { return {Status::RedisParseErr, errUnknownSubcommandOrWrongArguments}; } return encoding.GetValue(); } std::vector<BitfieldOperation> cmds_; bool read_only_; }; REDIS_REGISTER_COMMANDS(Bit, MakeCmdAttr<CommandGetBit>("getbit", 3, "read-only", 1, 1, 1), MakeCmdAttr<CommandSetBit>("setbit", 4, "write", 1, 1, 1), MakeCmdAttr<CommandBitCount>("bitcount", -2, "read-only", 1, 1, 1), MakeCmdAttr<CommandBitPos>("bitpos", -3, "read-only", 1, 1, 1), MakeCmdAttr<CommandBitOp>("bitop", -4, "write", 2, -1, 1), MakeCmdAttr<CommandBitfield<false>>("bitfield", -2, "write", 1, 1, 1), MakeCmdAttr<CommandBitfield<true>>("bitfield_ro", -2, "read-only", 1, 1, 1), ) } // namespace redis