Skip to content

Generic flat response implementation #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions example/cpp20_chat_room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ using boost::asio::redirect_error;
using boost::asio::use_awaitable;
using boost::redis::config;
using boost::redis::connection;
using boost::redis::generic_response;
using boost::redis::generic_flat_response;
using boost::redis::ignore;
using boost::redis::request;
using boost::system::error_code;
Expand All @@ -45,7 +45,7 @@ auto receiver(std::shared_ptr<connection> conn) -> awaitable<void>
request req;
req.push("SUBSCRIBE", "channel");

generic_response resp;
generic_flat_response resp;
conn->set_receive_response(resp);

while (conn->will_reconnect()) {
Expand Down
6 changes: 3 additions & 3 deletions example/cpp20_streams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

namespace net = boost::asio;
using boost::redis::config;
using boost::redis::generic_response;
using boost::redis::generic_flat_response;
using boost::redis::operation;
using boost::redis::request;
using boost::redis::connection;
Expand All @@ -33,7 +33,7 @@ auto stream_reader(std::shared_ptr<connection> conn) -> net::awaitable<void>
{
std::string redisStreamKey_;
request req;
generic_response resp;
generic_flat_response resp;

std::string stream_id{"$"};
std::string const field = "myfield";
Expand All @@ -51,7 +51,7 @@ auto stream_reader(std::shared_ptr<connection> conn) -> net::awaitable<void>
// The following approach was taken in order to be able to
// deal with the responses, as generated by redis in the case
// that there are multiple stream 'records' within a single
// generic_response. The nesting and number of values in
// generic_flat_response. The nesting and number of values in
// resp.value() are different, depending on the contents
// of the stream in redis. Uncomment the above commented-out
// code for examples while running the XADD command.
Expand Down
4 changes: 2 additions & 2 deletions example/cpp20_subscriber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::redis::request;
using boost::redis::generic_response;
using boost::redis::generic_flat_response;
using boost::redis::consume_one;
using boost::redis::logger;
using boost::redis::config;
Expand Down Expand Up @@ -54,7 +54,7 @@ auto receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
request req;
req.push("SUBSCRIBE", "channel");

generic_response resp;
generic_flat_response resp;
conn->set_receive_response(resp);

// Loop while reconnection is enabled
Expand Down
39 changes: 37 additions & 2 deletions include/boost/redis/adapter/detail/adapters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
#include <boost/redis/resp3/node.hpp>
#include <boost/redis/resp3/serialization.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/response.hpp>

#include <boost/assert.hpp>

#include <array>
#include <charconv>
#include <deque>
#include <forward_list>
#include <iostream>
#include <list>
#include <map>
#include <optional>
Expand Down Expand Up @@ -136,8 +138,6 @@ void boost_redis_from_bulk(T& t, resp3::basic_node<String> const& node, system::
from_bulk_impl<T>::apply(t, node, ec);
}

//================================================

template <class Result>
class general_aggregate {
private:
Expand Down Expand Up @@ -174,6 +174,41 @@ class general_aggregate {
}
};

template <>
class general_aggregate<result<flat_response_value>> {
private:
result<flat_response_value>* result_;

public:
explicit general_aggregate(result<flat_response_value>* c = nullptr)
: result_(c)
{ }

void on_init() { }
void on_done()
{
if (result_->has_value()) {
result_->value().set_view();
}
}

template <class String>
void on_node(resp3::basic_node<String> const& nd, system::error_code&)
{
BOOST_ASSERT_MSG(!!result_, "Unexpected null pointer");
switch (nd.data_type) {
case resp3::type::blob_error:
case resp3::type::simple_error:
*result_ = error{
nd.data_type,
std::string{std::cbegin(nd.value), std::cend(nd.value)}
};
break;
default: result_->value().add_node(nd);
}
}
};

template <class Node>
class general_simple {
private:
Expand Down
8 changes: 8 additions & 0 deletions include/boost/redis/adapter/detail/response_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ struct response_traits<result<std::vector<resp3::basic_node<String>, Allocator>>
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};

template <>
struct response_traits<generic_flat_response> {
using response_type = generic_flat_response;
using adapter_type = general_aggregate<response_type>;

static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};

template <class... Ts>
struct response_traits<response<Ts...>> {
using response_type = response<Ts...>;
Expand Down
8 changes: 8 additions & 0 deletions include/boost/redis/adapter/detail/result_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <boost/redis/error.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/redis/resp3/type.hpp>
#include <boost/redis/response.hpp>

#include <boost/mp11.hpp>

Expand Down Expand Up @@ -62,6 +63,13 @@ struct result_traits<result<std::vector<resp3::basic_node<String>, Allocator>>>
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};

template <>
struct result_traits<generic_flat_response> {
using response_type = generic_flat_response;
using adapter_type = adapter::detail::general_aggregate<response_type>;
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
};

template <class T>
using adapter_t = typename result_traits<std::decay_t<T>>::adapter_type;

Expand Down
39 changes: 34 additions & 5 deletions include/boost/redis/impl/response.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,30 @@

namespace boost::redis {

void consume_one(generic_response& r, system::error_code& ec)
namespace {
template <typename Container>
auto& get_value(Container& c)
{
return c;
}

template <>
auto& get_value(flat_response_value& c)
{
return c.view();
}

template <typename Response>
void consume_one_impl(Response& r, system::error_code& ec)
{
if (r.has_error())
return; // Nothing to consume.

if (std::empty(r.value()))
auto& value = get_value(r.value());
if (std::empty(value))
return; // Nothing to consume.

auto const depth = r.value().front().depth;
auto const depth = value.front().depth;

// To simplify we will refuse to consume any data-type that is not
// a root node. I think there is no use for that and it is complex
Expand All @@ -33,11 +48,17 @@ void consume_one(generic_response& r, system::error_code& ec)
return e.depth == depth;
};

auto match = std::find_if(std::next(std::cbegin(r.value())), std::cend(r.value()), f);
auto match = std::find_if(std::next(std::cbegin(value)), std::cend(value), f);

r.value().erase(std::cbegin(r.value()), match);
value.erase(std::cbegin(value), match);
}

} // namespace

void consume_one(generic_response& r, system::error_code& ec) { consume_one_impl(r, ec); }

void consume_one(generic_flat_response& r, system::error_code& ec) { consume_one_impl(r, ec); }

void consume_one(generic_response& r)
{
system::error_code ec;
Expand All @@ -46,4 +67,12 @@ void consume_one(generic_response& r)
throw system::system_error(ec);
}

void consume_one(generic_flat_response& r)
{
system::error_code ec;
consume_one(r, ec);
if (ec)
throw system::system_error(ec);
}

} // namespace boost::redis
15 changes: 15 additions & 0 deletions include/boost/redis/resp3/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ using node = basic_node<std::string>;
/// A node in the response tree that does not own its data.
using node_view = basic_node<std::string_view>;

struct offset_string {
std::string_view data;
std::size_t offset{};
std::size_t size{};

operator std::string() const { return std::string{data}; }

friend std::ostream& operator<<(std::ostream& os, offset_string const& s)
{
return os << s.data;
}
};

using offset_node = basic_node<offset_string>;

} // namespace boost::redis::resp3

#endif // BOOST_REDIS_RESP3_NODE_HPP
82 changes: 81 additions & 1 deletion include/boost/redis/response.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,82 @@ using response = std::tuple<adapter::result<Ts>...>;
*/
using generic_response = adapter::result<std::vector<resp3::node>>;

/**
* Forward declaration to allow friendship with the template class
* that manages filling of flat_response_value.
*/
namespace adapter::detail {
template <class Result>
class general_aggregate;
}

struct flat_response_value {
public:
/// Reserve capacity for nodes and data storage.
void reserve(std::size_t num_nodes, std::size_t string_size)
{
data_.reserve(num_nodes * string_size);
view_.reserve(num_nodes);
}

void clear()
{
data_.clear();
view_.clear();
}

std::size_t size() const noexcept { return view_.size(); }
bool empty() noexcept { return view_.empty(); }

resp3::offset_node& at(std::size_t index) { return view_.at(index); }
resp3::offset_node const& at(std::size_t index) const { return view_.at(index); }

std::vector<resp3::offset_node> const& view() const { return view_; }
std::vector<resp3::offset_node>& view() { return view_; }

private:
void set_view()
{
for (auto& node : view_) {
auto& offset_string = node.value;
offset_string.data = std::string_view{
data_.data() + offset_string.offset,
offset_string.size};
}
}

template <class String>
void add_node(resp3::basic_node<String> const& nd)
{
resp3::offset_string offset_string;
offset_string.offset = data_.size();
offset_string.size = nd.value.size();

data_.append(nd.value.data(), nd.value.size());

resp3::offset_node new_node;
new_node.data_type = nd.data_type;
new_node.aggregate_size = nd.aggregate_size;
new_node.depth = nd.depth;
new_node.value = std::move(offset_string);

view_.push_back(std::move(new_node));
}

template <class T>
friend class adapter::detail::general_aggregate;

std::string data_;
std::vector<resp3::offset_node> view_;
};

/** @brief A memory-efficient generic response to a request.
* @ingroup high-level-api
*
* Uses a compact buffer to store RESP3 data with reduced allocations.
*/
using generic_flat_response = adapter::result<flat_response_value>;

/** @brief Consume on response from a generic response
*
* This function rotates the elements so that the start of the next
Expand Down Expand Up @@ -72,12 +148,16 @@ using generic_response = adapter::result<std::vector<resp3::node>>;
*/
void consume_one(generic_response& r, system::error_code& ec);

/// Consume on response from a generic flat response
void consume_one(generic_flat_response& r, system::error_code& ec);

/**
* @brief Throwing overload of `consume_one`.
* @brief Throwing overloads of `consume_one`.
*
* @param r The response to modify.
*/
void consume_one(generic_response& r);
void consume_one(generic_flat_response& r);

} // namespace boost::redis

Expand Down
3 changes: 3 additions & 0 deletions test/test_any_adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <boost/test/included/unit_test.hpp>

using boost::redis::generic_response;
using boost::redis::generic_flat_response;
using boost::redis::response;
using boost::redis::ignore;
using boost::redis::any_adapter;
Expand All @@ -24,10 +25,12 @@ BOOST_AUTO_TEST_CASE(any_adapter_response_types)
response<int> r1;
response<int, std::string> r2;
generic_response r3;
generic_flat_response r4;

BOOST_CHECK_NO_THROW(any_adapter{r1});
BOOST_CHECK_NO_THROW(any_adapter{r2});
BOOST_CHECK_NO_THROW(any_adapter{r3});
BOOST_CHECK_NO_THROW(any_adapter{r4});
BOOST_CHECK_NO_THROW(any_adapter{ignore});
}

Expand Down
Loading