Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 10:08:25

0001 /* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
0002  *
0003  * Distributed under the Boost Software License, Version 1.0. (See
0004  * accompanying file LICENSE.txt)
0005  */
0006 
0007 #ifndef BOOST_REDIS_REQUEST_HPP
0008 #define BOOST_REDIS_REQUEST_HPP
0009 
0010 #include <boost/redis/resp3/type.hpp>
0011 #include <boost/redis/resp3/serialization.hpp>
0012 
0013 #include <string>
0014 #include <tuple>
0015 #include <algorithm>
0016 
0017 // NOTE: For some commands like hset it would be a good idea to assert
0018 // the value type is a pair.
0019 
0020 namespace boost::redis {
0021 
0022 namespace detail{
0023 auto has_response(std::string_view cmd) -> bool;
0024 }
0025 
0026 /** \brief Creates Redis requests.
0027  *  \ingroup high-level-api
0028  *  
0029  *  A request is composed of one or more Redis commands and is
0030  *  referred to in the redis documentation as a pipeline, see
0031  *  https://redis.io/topics/pipelining. For example
0032  *
0033  *  @code
0034  *  request r;
0035  *  r.push("HELLO", 3);
0036  *  r.push("FLUSHALL");
0037  *  r.push("PING");
0038  *  r.push("PING", "key");
0039  *  r.push("QUIT");
0040  *  @endcode
0041  *
0042  *  \remarks
0043  *
0044  *  Uses a std::string for internal storage.
0045  */
0046 class request {
0047 public:
0048    /// Request configuration options.
0049    struct config {
0050       /** \brief If `true` calls to `connection::async_exec` will
0051        * complete with error if the connection is lost while the
0052        * request hasn't been sent yet.
0053        */
0054       bool cancel_on_connection_lost = true;
0055 
0056       /** \brief If `true` `connection::async_exec` will complete with
0057        * `boost::redis::error::not_connected` if the call happens
0058        * before the connection with Redis was established.
0059        */
0060       bool cancel_if_not_connected = false;
0061 
0062       /** \brief If `false` `connection::async_exec` will not
0063        * automatically cancel this request if the connection is lost.
0064        * Affects only requests that have been written to the socket
0065        * but remained unresponded when
0066        * `boost::redis::connection::async_run` completed.
0067        */
0068       bool cancel_if_unresponded = true;
0069 
0070       /** \brief If this request has a `HELLO` command and this flag
0071        * is `true`, the `boost::redis::connection` will move it to the
0072        * front of the queue of awaiting requests. This makes it
0073        * possible to send `HELLO` and authenticate before other
0074        * commands are sent.
0075        */
0076       bool hello_with_priority = true;
0077    };
0078 
0079    /** \brief Constructor
0080     *  
0081     *  \param cfg Configuration options.
0082     */
0083     explicit
0084     request(config cfg = config{true, false, true, true})
0085     : cfg_{cfg} {}
0086 
0087     //// Returns the number of responses expected for this request.
0088    [[nodiscard]] auto get_expected_responses() const noexcept -> std::size_t
0089       { return expected_responses_;};
0090 
0091     //// Returns the number of commands contained in this request.
0092    [[nodiscard]] auto get_commands() const noexcept -> std::size_t
0093       { return commands_;};
0094 
0095    [[nodiscard]] auto payload() const noexcept -> std::string_view
0096       { return payload_;}
0097 
0098    [[nodiscard]] auto has_hello_priority() const noexcept -> auto const&
0099       { return has_hello_priority_;}
0100 
0101    /// Clears the request preserving allocated memory.
0102    void clear()
0103    {
0104       payload_.clear();
0105       commands_ = 0;
0106       expected_responses_ = 0;
0107       has_hello_priority_ = false;
0108    }
0109 
0110    /// Calls std::string::reserve on the internal storage.
0111    void reserve(std::size_t new_cap = 0)
0112       { payload_.reserve(new_cap); }
0113 
0114    /// Returns a const reference to the config object.
0115    [[nodiscard]] auto get_config() const noexcept -> auto const& {return cfg_; }
0116 
0117    /// Returns a reference to the config object.
0118    [[nodiscard]] auto get_config() noexcept -> auto& {return cfg_; }
0119 
0120    /** @brief Appends a new command to the end of the request.
0121     *
0122     *  For example
0123     *
0124     *  \code
0125     *  request req;
0126     *  req.push("SET", "key", "some string", "EX", "2");
0127     *  \endcode
0128     *
0129     *  will add the `set` command with value "some string" and an
0130     *  expiration of 2 seconds.
0131     *
0132     *  \param cmd The command e.g redis or sentinel command.
0133     *  \param args Command arguments.
0134     *  \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
0135     *
0136     *  @code
0137     *  void boost_redis_to_bulk(std::string& to, T const& t);
0138     *  {
0139     *     boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
0140     *  }
0141     *  @endcode
0142     *
0143     *  See cpp20_serialization.cpp
0144     */
0145    template <class... Ts>
0146    void push(std::string_view cmd, Ts const&... args)
0147    {
0148       auto constexpr pack_size = sizeof...(Ts);
0149       resp3::add_header(payload_, resp3::type::array, 1 + pack_size);
0150       resp3::add_bulk(payload_, cmd);
0151       resp3::add_bulk(payload_, std::tie(std::forward<Ts const&>(args)...));
0152 
0153       check_cmd(cmd);
0154    }
0155 
0156    /** @brief Appends a new command to the end of the request.
0157     *  
0158     *  This overload is useful for commands that have a key and have a
0159     *  dynamic range of arguments. For example
0160     *
0161     *  @code
0162     *  std::map<std::string, std::string> map
0163     *     { {"key1", "value1"}
0164     *     , {"key2", "value2"}
0165     *     , {"key3", "value3"}
0166     *     };
0167     *
0168     *  request req;
0169     *  req.push_range("HSET", "key", std::cbegin(map), std::cend(map));
0170     *  @endcode
0171     *  
0172     *  \param cmd The command e.g. Redis or Sentinel command.
0173     *  \param key The command key.
0174     *  \param begin Iterator to the begin of the range.
0175     *  \param end Iterator to the end of the range.
0176     *  \tparam Ts Non-string types will be converted to string by calling `boost_redis_to_bulk` on each argument. This function must be made available over ADL and must have the following signature
0177     *
0178     *  @code
0179     *  void boost_redis_to_bulk(std::string& to, T const& t);
0180     *  {
0181     *     boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
0182     *  }
0183     *  @endcode
0184     *
0185     *  See cpp20_serialization.cpp
0186     */
0187    template <class ForwardIterator>
0188    void
0189    push_range(
0190       std::string_view const& cmd,
0191       std::string_view const& key,
0192       ForwardIterator begin,
0193       ForwardIterator end,
0194       typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
0195    {
0196       using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
0197 
0198       if (begin == end)
0199          return;
0200 
0201       auto constexpr size = resp3::bulk_counter<value_type>::size;
0202       auto const distance = std::distance(begin, end);
0203       resp3::add_header(payload_, resp3::type::array, 2 + size * distance);
0204       resp3::add_bulk(payload_, cmd);
0205       resp3::add_bulk(payload_, key);
0206 
0207       for (; begin != end; ++begin)
0208      resp3::add_bulk(payload_, *begin);
0209 
0210       check_cmd(cmd);
0211    }
0212 
0213    /** @brief Appends a new command to the end of the request.
0214     *
0215     *  This overload is useful for commands that have a dynamic number
0216     *  of arguments and don't have a key. For example
0217     *
0218     *  \code
0219     *  std::set<std::string> channels
0220     *     { "channel1" , "channel2" , "channel3" }
0221     *
0222     *  request req;
0223     *  req.push("SUBSCRIBE", std::cbegin(channels), std::cend(channels));
0224     *  \endcode
0225     *
0226     *  \param cmd The Redis command
0227     *  \param begin Iterator to the begin of the range.
0228     *  \param end Iterator to the end of the range.
0229     *  \tparam ForwardIterator If the value type is not a std::string it will be converted to a string by calling `boost_redis_to_bulk`. This function must be made available over ADL and must have the following signature
0230     *
0231     *  @code
0232     *  void boost_redis_to_bulk(std::string& to, T const& t);
0233     *  {
0234     *     boost::redis::resp3::boost_redis_to_bulk(to, serialize(t));
0235     *  }
0236     *  @endcode
0237     *
0238     *  See cpp20_serialization.cpp
0239     */
0240    template <class ForwardIterator>
0241    void
0242    push_range(
0243       std::string_view const& cmd,
0244       ForwardIterator begin,
0245       ForwardIterator end,
0246       typename std::iterator_traits<ForwardIterator>::value_type * = nullptr)
0247    {
0248       using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
0249 
0250       if (begin == end)
0251          return;
0252 
0253       auto constexpr size = resp3::bulk_counter<value_type>::size;
0254       auto const distance = std::distance(begin, end);
0255       resp3::add_header(payload_, resp3::type::array, 1 + size * distance);
0256       resp3::add_bulk(payload_, cmd);
0257 
0258       for (; begin != end; ++begin)
0259      resp3::add_bulk(payload_, *begin);
0260 
0261       check_cmd(cmd);
0262    }
0263 
0264    /** @brief Appends a new command to the end of the request.
0265     *  
0266     *  Equivalent to the overload taking a range of begin and end
0267     *  iterators.
0268     *
0269     *  \param cmd Redis command.
0270     *  \param key Redis key.
0271     *  \param range Range to send e.g. `std::map`.
0272     *  \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
0273     */
0274    template <class Range>
0275    void
0276    push_range(
0277       std::string_view const& cmd,
0278       std::string_view const& key,
0279       Range const& range,
0280       decltype(std::begin(range)) * = nullptr)
0281    {
0282       using std::begin;
0283       using std::end;
0284       push_range(cmd, key, begin(range), end(range));
0285    }
0286 
0287    /** @brief Appends a new command to the end of the request.
0288     *
0289     *  Equivalent to the overload taking a range of begin and end
0290     *  iterators.
0291     *
0292     *  \param cmd Redis command.
0293     *  \param range Range to send e.g. `std::map`.
0294     *  \tparam A type that can be passed to `std::cbegin()` and `std::cend()`.
0295     */
0296    template <class Range>
0297    void
0298    push_range(
0299       std::string_view cmd,
0300       Range const& range,
0301       decltype(std::cbegin(range)) * = nullptr)
0302    {
0303       using std::cbegin;
0304       using std::cend;
0305       push_range(cmd, cbegin(range), cend(range));
0306    }
0307 
0308 private:
0309    void check_cmd(std::string_view cmd)
0310    {
0311       ++commands_;
0312 
0313       if (!detail::has_response(cmd))
0314          ++expected_responses_;
0315 
0316       if (cmd == "HELLO")
0317          has_hello_priority_ = cfg_.hello_with_priority;
0318    }
0319 
0320    config cfg_;
0321    std::string payload_;
0322    std::size_t commands_ = 0;
0323    std::size_t expected_responses_ = 0;
0324    bool has_hello_priority_ = false;
0325 };
0326 
0327 } // boost::redis::resp3
0328 
0329 #endif // BOOST_REDIS_REQUEST_HPP