| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- /* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
- *
- * Distributed under the Boost Software License, Version 1.0. (See
- * accompanying file LICENSE.txt)
- */
- #ifndef BOOST_REDIS_REQUEST_HPP
- #define BOOST_REDIS_REQUEST_HPP
- #include <boost/redis/resp3/serialization.hpp>
- #include <boost/redis/resp3/type.hpp>
- #include <algorithm>
- #include <string>
- #include <tuple>
- // NOTE: For some commands like hset it would be a good idea to assert
- // the value type is a pair.
- namespace boost::redis {
- namespace detail {
- auto has_response(std::string_view cmd) -> bool;
- struct request_access;
- } // namespace detail
- /** @brief Represents a Redis request.
- *
- * A request is composed of one or more Redis commands and is
- * referred to in the redis documentation as a pipeline. See
- * <a href="https://redis.io/topics/pipelining"></a> for more info.
- *
- * For example:
- *
- * @code
- * request r;
- * r.push("HELLO", 3);
- * r.push("FLUSHALL");
- * r.push("PING");
- * r.push("PING", "key");
- * r.push("QUIT");
- * @endcode
- *
- * Uses a `std::string` for internal storage.
- */
- class request {
- public:
- /// Request configuration options.
- struct config {
- /** @brief (Deprecated) If `true`, calls to @ref basic_connection::async_exec will
- * complete with error if the connection is lost while the
- * request hasn't been sent yet.
- *
- * @par Deprecated
- * This setting is deprecated and should be always left out as the default
- * (waiting for a connection to be established again).
- * If you need to limit how much time a @ref basic_connection::async_exec
- * operation is allowed to take, use `asio::cancel_after`, instead.
- */
- bool cancel_on_connection_lost = false;
- /** @brief (Deprecated) If `true`, @ref basic_connection::async_exec will complete with
- * @ref boost::redis::error::not_connected if the call happens
- * before the connection with Redis was established.
- *
- * @par Deprecated
- * This setting is deprecated and should be always left out as the default
- * (waiting for a connection to be established).
- * If you need to limit how much time a @ref basic_connection::async_exec
- * operation is allowed to take, use `asio::cancel_after`, instead.
- */
- bool cancel_if_not_connected = false;
- /** @brief If `false`, @ref basic_connection::async_exec will not
- * automatically cancel this request if the connection is lost.
- * Affects only requests that have been written to the server
- * but have not been responded when
- * the connection is lost.
- */
- bool cancel_if_unresponded = true;
- /** @brief (Deprecated) If this request has a `HELLO` command and this flag
- * is `true`, it will be moved to the
- * front of the queue of awaiting requests. This makes it
- * possible to send `HELLO` commands and authenticate before other
- * commands are sent.
- *
- * @par Deprecated
- * This field has been superseded by @ref config::setup.
- * This setup request will always be run first on connection establishment.
- * Please use it to run any required setup commands.
- * This field will be removed in subsequent releases.
- */
- bool hello_with_priority = true;
- };
- /** @brief Constructor
- *
- * @param cfg Configuration options.
- */
- explicit request(config cfg = config{false, false, true, true})
- : cfg_{cfg}
- { }
- /// Returns the number of responses expected for this request.
- [[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
- {
- return expected_responses_;
- };
- /// Returns the number of commands contained in this request.
- [[nodiscard]] auto get_commands() const noexcept -> std::size_t { return commands_; };
- [[nodiscard]] auto payload() const noexcept -> std::string_view { return payload_; }
- [[nodiscard]]
- BOOST_DEPRECATED(
- "The hello_with_priority attribute and related functions are deprecated. "
- "Use config::setup to run setup commands, instead.") auto has_hello_priority() const noexcept
- -> auto const&
- {
- return has_hello_priority_;
- }
- /// Clears the request preserving allocated memory.
- void clear()
- {
- payload_.clear();
- commands_ = 0;
- expected_responses_ = 0;
- has_hello_priority_ = false;
- }
- /// Calls `std::string::reserve` on the internal storage.
- void reserve(std::size_t new_cap = 0) { payload_.reserve(new_cap); }
- /// Returns a reference to the config object.
- [[nodiscard]] auto get_config() const noexcept -> config const& { return cfg_; }
- /// Returns a reference to the config object.
- [[nodiscard]] auto get_config() noexcept -> config& { return cfg_; }
- /** @brief Appends a new command to the end of the request.
- *
- * For example:
- *
- * @code
- * request req;
- * req.push("SET", "key", "some string", "EX", "2");
- * @endcode
- *
- * This will add a `SET` command with value `"some string"` and an
- * expiration of 2 seconds.
- *
- * Command arguments should either be convertible to `std::string_view`
- * or support the `boost_redis_to_bulk` function.
- * This function is a customization point that must be made available
- * using ADL and must have the following signature:
- *
- * @code
- * void boost_redis_to_bulk(std::string& to, T const& t);
- * @endcode
- *
- *
- * See cpp20_serialization.cpp
- *
- * @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
- * @param args Command arguments. Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument.
- * @tparam Ts Types of the command arguments.
- *
- */
- template <class... Ts>
- void push(std::string_view cmd, Ts const&... args)
- {
- auto constexpr pack_size = sizeof...(Ts);
- resp3::add_header(payload_, resp3::type::array, 1 + pack_size);
- resp3::add_bulk(payload_, cmd);
- resp3::add_bulk(payload_, std::tie(std::forward<Ts const&>(args)...));
- check_cmd(cmd);
- }
- /** @brief Appends a new command to the end of the request.
- *
- * This overload is useful for commands that have a key and have a
- * dynamic range of arguments. For example:
- *
- * @code
- * std::map<std::string, std::string> map
- * { {"key1", "value1"}
- * , {"key2", "value2"}
- * , {"key3", "value3"}
- * };
- *
- * request req;
- * req.push_range("HSET", "key", map.cbegin(), map.cend());
- * @endcode
- *
- * Command arguments should either be convertible to `std::string_view`
- * or support the `boost_redis_to_bulk` function.
- * This function is a customization point that must be made available
- * using ADL and must have the following signature:
- *
- * @code
- * void boost_redis_to_bulk(std::string& to, T const& t);
- * @endcode
- *
- * @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
- * @param key The command key. It will be added as the first argument to the command.
- * @param begin Iterator to the begin of the range.
- * @param end Iterator to the end of the range.
- * @tparam ForwardIterator A forward iterator with an element type that is convertible to `std::string_view`
- * or supports `boost_redis_to_bulk`.
- *
- * See cpp20_serialization.cpp
- */
- template <class ForwardIterator>
- void push_range(
- std::string_view cmd,
- std::string_view key,
- ForwardIterator begin,
- ForwardIterator end,
- typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
- {
- using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
- if (begin == end)
- return;
- auto constexpr size = resp3::bulk_counter<value_type>::size;
- auto const distance = std::distance(begin, end);
- resp3::add_header(payload_, resp3::type::array, 2 + size * distance);
- resp3::add_bulk(payload_, cmd);
- resp3::add_bulk(payload_, key);
- for (; begin != end; ++begin)
- resp3::add_bulk(payload_, *begin);
- check_cmd(cmd);
- }
- /** @brief Appends a new command to the end of the request.
- *
- * This overload is useful for commands that have a dynamic number
- * of arguments and don't have a key. For example:
- *
- * @code
- * std::set<std::string> channels
- * { "channel1" , "channel2" , "channel3" };
- *
- * request req;
- * req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
- * @endcode
- *
- * Command arguments should either be convertible to `std::string_view`
- * or support the `boost_redis_to_bulk` function.
- * This function is a customization point that must be made available
- * using ADL and must have the following signature:
- *
- * @code
- * void boost_redis_to_bulk(std::string& to, T const& t);
- * @endcode
- *
- * @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
- * @param begin Iterator to the begin of the range.
- * @param end Iterator to the end of the range.
- * @tparam ForwardIterator A forward iterator with an element type that is convertible to `std::string_view`
- * or supports `boost_redis_to_bulk`.
- *
- * See cpp20_serialization.cpp
- */
- template <class ForwardIterator>
- void push_range(
- std::string_view cmd,
- ForwardIterator begin,
- ForwardIterator end,
- typename std::iterator_traits<ForwardIterator>::value_type* = nullptr)
- {
- using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
- if (begin == end)
- return;
- auto constexpr size = resp3::bulk_counter<value_type>::size;
- auto const distance = std::distance(begin, end);
- resp3::add_header(payload_, resp3::type::array, 1 + size * distance);
- resp3::add_bulk(payload_, cmd);
- for (; begin != end; ++begin)
- resp3::add_bulk(payload_, *begin);
- check_cmd(cmd);
- }
- /** @brief Appends a new command to the end of the request.
- *
- * Equivalent to the overload taking a range of begin and end
- * iterators.
- *
- * @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
- * @param key The command key. It will be added as the first argument to the command.
- * @param range Range containing the command arguments.
- * @tparam Range A type that can be passed to `std::begin()` and `std::end()` to obtain
- * iterators. The range elements should be convertible to `std::string_view`
- * or support `boost_redis_to_bulk`.
- */
- template <class Range>
- void push_range(
- std::string_view cmd,
- std::string_view key,
- Range const& range,
- decltype(std::begin(range))* = nullptr)
- {
- using std::begin;
- using std::end;
- push_range(cmd, key, begin(range), end(range));
- }
- /** @brief Appends a new command to the end of the request.
- *
- * Equivalent to the overload taking a range of begin and end
- * iterators.
- *
- * @param cmd The command to execute. It should be a redis or sentinel command, like `"SET"`.
- * @param range Range containing the command arguments.
- * @tparam Range A type that can be passed to `std::begin()` and `std::end()` to obtain
- * iterators. The range elements should be convertible to `std::string_view`
- * or support `boost_redis_to_bulk`.
- */
- template <class Range>
- void push_range(
- std::string_view cmd,
- Range const& range,
- decltype(std::cbegin(range))* = nullptr)
- {
- using std::cbegin;
- using std::cend;
- push_range(cmd, cbegin(range), cend(range));
- }
- /** @brief Appends the commands in another request to the end of the request.
- *
- * Appends all the commands contained in `other` to the end of
- * this request. Configuration flags in `*this`,
- * like @ref config::cancel_if_unresponded, are *not* modified,
- * even if `other` has a different config than `*this`.
- *
- * @param other The request containing the commands to append.
- */
- void append(const request& other);
- private:
- void check_cmd(std::string_view cmd)
- {
- ++commands_;
- if (!detail::has_response(cmd))
- ++expected_responses_;
- if (cmd == "HELLO")
- has_hello_priority_ = cfg_.hello_with_priority;
- }
- config cfg_;
- std::string payload_;
- std::size_t commands_ = 0;
- std::size_t expected_responses_ = 0;
- bool has_hello_priority_ = false;
- friend struct detail::request_access;
- };
- namespace detail {
- struct request_access {
- inline static void set_priority(request& r, bool value) { r.has_hello_priority_ = value; }
- inline static bool has_priority(const request& r) { return r.has_hello_priority_; }
- };
- // Creates a HELLO 3 request
- request make_hello_request();
- } // namespace detail
- } // namespace boost::redis
- #endif // BOOST_REDIS_REQUEST_HPP
|