| /* |
| * 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 "error_constants.h" |
| #include "server/server.h" |
| #include "storage/redis_pubsub.h" |
| |
| namespace redis { |
| |
| class CommandPublish : public Commander { |
| public: |
| Status Execute([[maybe_unused]] engine::Context &ctx, Server *srv, [[maybe_unused]] Connection *conn, |
| std::string *output) override { |
| if (!srv->IsSlave()) { |
| // Compromise: can't replicate a message to sub-replicas in a cascading-like structure. |
| // Replication relies on WAL seq; increasing the seq on a replica will break the replication process, |
| // hence the compromise solution |
| redis::PubSub pubsub_db(srv->storage); |
| |
| auto s = pubsub_db.Publish(ctx, args_[1], args_[2]); |
| if (!s.ok()) { |
| return {Status::RedisExecErr, s.ToString()}; |
| } |
| } |
| |
| int receivers = srv->PublishMessage(args_[1], args_[2]); |
| |
| *output = redis::Integer(receivers); |
| |
| return Status::OK(); |
| } |
| }; |
| |
| class CommandMPublish : public Commander { |
| public: |
| Status Execute(engine::Context &ctx, Server *srv, [[maybe_unused]] Connection *conn, std::string *output) override { |
| int total_receivers = 0; |
| |
| for (size_t i = 2; i < args_.size(); i++) { |
| if (!srv->IsSlave()) { |
| redis::PubSub pubsub_db(srv->storage); |
| |
| auto s = pubsub_db.Publish(ctx, args_[1], args_[i]); |
| if (!s.ok()) { |
| return {Status::RedisExecErr, s.ToString()}; |
| } |
| } |
| |
| int receivers = srv->PublishMessage(args_[1], args_[i]); |
| total_receivers += receivers; |
| } |
| |
| *output = redis::Integer(total_receivers); |
| |
| return Status::OK(); |
| } |
| }; |
| |
| void SubscribeCommandReply(const Connection *conn, std::string *output, const std::string &name, |
| const std::string &sub_name, int num) { |
| output->append(conn->HeaderOfPush(3)); |
| output->append(redis::BulkString(name)); |
| output->append(sub_name.empty() ? conn->NilString() : BulkString(sub_name)); |
| output->append(redis::Integer(num)); |
| } |
| |
| class CommandSubscribe : public Commander { |
| public: |
| Status Execute([[maybe_unused]] engine::Context &ctx, [[maybe_unused]] Server *srv, Connection *conn, |
| std::string *output) override { |
| for (unsigned i = 1; i < args_.size(); i++) { |
| conn->SubscribeChannel(args_[i]); |
| SubscribeCommandReply(conn, output, "subscribe", args_[i], |
| conn->SubscriptionsCount() + conn->PSubscriptionsCount()); |
| } |
| return Status::OK(); |
| } |
| }; |
| |
| class CommandUnSubscribe : public Commander { |
| public: |
| Status Execute([[maybe_unused]] engine::Context &ctx, [[maybe_unused]] Server *srv, Connection *conn, |
| std::string *output) override { |
| if (args_.size() == 1) { |
| conn->UnsubscribeAll([conn, output](const std::string &sub_name, int num) { |
| SubscribeCommandReply(conn, output, "unsubscribe", sub_name, num); |
| }); |
| } else { |
| for (size_t i = 1; i < args_.size(); i++) { |
| conn->UnsubscribeChannel(args_[i]); |
| SubscribeCommandReply(conn, output, "unsubscribe", args_[i], |
| conn->SubscriptionsCount() + conn->PSubscriptionsCount()); |
| } |
| } |
| return Status::OK(); |
| } |
| }; |
| |
| class CommandPSubscribe : public Commander { |
| public: |
| Status Execute([[maybe_unused]] engine::Context &ctx, [[maybe_unused]] Server *srv, Connection *conn, |
| std::string *output) override { |
| for (size_t i = 1; i < args_.size(); i++) { |
| conn->PSubscribeChannel(args_[i]); |
| SubscribeCommandReply(conn, output, "psubscribe", args_[i], |
| conn->SubscriptionsCount() + conn->PSubscriptionsCount()); |
| } |
| return Status::OK(); |
| } |
| }; |
| |
| class CommandPUnSubscribe : public Commander { |
| public: |
| Status Execute([[maybe_unused]] engine::Context &ctx, [[maybe_unused]] Server *srv, Connection *conn, |
| std::string *output) override { |
| if (args_.size() == 1) { |
| conn->PUnsubscribeAll([conn, output](const std::string &sub_name, int num) { |
| SubscribeCommandReply(conn, output, "punsubscribe", sub_name, num); |
| }); |
| } else { |
| for (size_t i = 1; i < args_.size(); i++) { |
| conn->PUnsubscribeChannel(args_[i]); |
| SubscribeCommandReply(conn, output, "punsubscribe", args_[i], |
| conn->SubscriptionsCount() + conn->PSubscriptionsCount()); |
| } |
| } |
| return Status::OK(); |
| } |
| }; |
| |
| class CommandSSubscribe : public Commander { |
| public: |
| Status Execute([[maybe_unused]] engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { |
| uint16_t slot = 0; |
| if (srv->GetConfig()->cluster_enabled) { |
| slot = GetSlotIdFromKey(args_[1]); |
| for (unsigned int i = 2; i < args_.size(); i++) { |
| if (GetSlotIdFromKey(args_[i]) != slot) { |
| return {Status::RedisCrossSlot, "Keys in request don't hash to the same slot"}; |
| } |
| } |
| } |
| |
| for (unsigned int i = 1; i < args_.size(); i++) { |
| conn->SSubscribeChannel(args_[i], slot); |
| SubscribeCommandReply(conn, output, "ssubscribe", args_[i], conn->SSubscriptionsCount()); |
| } |
| return Status::OK(); |
| } |
| }; |
| |
| class CommandSUnSubscribe : public Commander { |
| public: |
| Status Execute([[maybe_unused]] engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { |
| if (args_.size() == 1) { |
| conn->SUnsubscribeAll([conn, output](const std::string &sub_name, int num) { |
| SubscribeCommandReply(conn, output, "sunsubscribe", sub_name, num); |
| }); |
| } else { |
| for (size_t i = 1; i < args_.size(); i++) { |
| conn->SUnsubscribeChannel(args_[i], srv->GetConfig()->cluster_enabled ? GetSlotIdFromKey(args_[i]) : 0); |
| SubscribeCommandReply(conn, output, "sunsubscribe", args_[i], conn->SSubscriptionsCount()); |
| } |
| } |
| return Status::OK(); |
| } |
| }; |
| |
| class CommandPubSub : public Commander { |
| public: |
| Status Parse(const std::vector<std::string> &args) override { |
| subcommand_ = util::ToLower(args[1]); |
| if (subcommand_ == "numpat" && args.size() == 2) { |
| return Status::OK(); |
| } |
| |
| if ((subcommand_ == "numsub" || subcommand_ == "shardnumsub") && args.size() >= 2) { |
| if (args.size() > 2) { |
| channels_ = std::vector<std::string>(args.begin() + 2, args.end()); |
| } |
| return Status::OK(); |
| } |
| |
| if ((subcommand_ == "channels" || subcommand_ == "shardchannels") && args.size() <= 3) { |
| if (args.size() == 3) { |
| pattern_ = args[2]; |
| } |
| return Status::OK(); |
| } |
| |
| return {Status::RedisInvalidCmd, errUnknownSubcommandOrWrongArguments}; |
| } |
| |
| Status Execute([[maybe_unused]] engine::Context &ctx, Server *srv, Connection *conn, std::string *output) override { |
| if (subcommand_ == "numpat") { |
| *output = redis::Integer(srv->GetPubSubPatternSize()); |
| return Status::OK(); |
| } |
| |
| if (subcommand_ == "numsub" || subcommand_ == "shardnumsub") { |
| std::vector<ChannelSubscribeNum> channel_subscribe_nums; |
| if (subcommand_ == "numsub") { |
| srv->ListChannelSubscribeNum(channels_, &channel_subscribe_nums); |
| } else { |
| srv->ListSChannelSubscribeNum(channels_, &channel_subscribe_nums); |
| } |
| |
| output->append(redis::MultiLen(channel_subscribe_nums.size() * 2)); |
| for (const auto &chan_subscribe_num : channel_subscribe_nums) { |
| output->append(redis::BulkString(chan_subscribe_num.channel)); |
| output->append(redis::Integer(chan_subscribe_num.subscribe_num)); |
| } |
| |
| return Status::OK(); |
| } |
| |
| if (subcommand_ == "channels" || subcommand_ == "shardchannels") { |
| std::vector<std::string> channels; |
| if (subcommand_ == "channels") { |
| srv->GetChannelsByPattern(pattern_, &channels); |
| } else { |
| srv->GetSChannelsByPattern(pattern_, &channels); |
| } |
| *output = conn->MultiBulkString(channels); |
| return Status::OK(); |
| } |
| |
| return {Status::RedisInvalidCmd, errUnknownSubcommandOrWrongArguments}; |
| } |
| |
| private: |
| std::string pattern_; |
| std::vector<std::string> channels_; |
| std::string subcommand_; |
| }; |
| |
| REDIS_REGISTER_COMMANDS(Pubsub, MakeCmdAttr<CommandPublish>("publish", 3, "read-only", NO_KEY), |
| MakeCmdAttr<CommandMPublish>("mpublish", -3, "read-only", NO_KEY), |
| MakeCmdAttr<CommandSubscribe>("subscribe", -2, "read-only no-multi no-script", NO_KEY), |
| MakeCmdAttr<CommandUnSubscribe>("unsubscribe", -1, "read-only no-multi no-script", NO_KEY), |
| MakeCmdAttr<CommandPSubscribe>("psubscribe", -2, "read-only no-multi no-script", NO_KEY), |
| MakeCmdAttr<CommandPUnSubscribe>("punsubscribe", -1, "read-only no-multi no-script", NO_KEY), |
| MakeCmdAttr<CommandSSubscribe>("ssubscribe", -2, "read-only no-multi no-script", NO_KEY), |
| MakeCmdAttr<CommandSUnSubscribe>("sunsubscribe", -1, "read-only no-multi no-script", NO_KEY), |
| MakeCmdAttr<CommandPubSub>("pubsub", -2, "read-only no-script", NO_KEY), ) |
| |
| } // namespace redis |