Skip to content

Commit 00f3ec9

Browse files
authored
Merge pull request #340 from boostorg/263-marcelo
Concludes the work on the generic_flat_response started by Nikolai Vladimirov
2 parents c9375a4 + b365b96 commit 00f3ec9

23 files changed

Lines changed: 1255 additions & 136 deletions

README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ them are:
8787
* [Client-side caching](https://redis.io/docs/manual/client-side-caching/).
8888

8989
The connection class supports server pushes by means of the
90-
`connection::async_receive` function, which can be
90+
`connection::async_receive2` function, which can be
9191
called in the same connection that is being used to execute commands.
92-
The coroutine below shows how to use it:
92+
The coroutine below shows how to use it
9393

9494

9595
```cpp
@@ -99,31 +99,30 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
9999
request req;
100100
req.push("SUBSCRIBE", "channel");
101101

102-
generic_response resp;
102+
flat_tree resp;
103103
conn->set_receive_response(resp);
104104

105105
// Loop while reconnection is enabled
106106
while (conn->will_reconnect()) {
107107

108108
// Reconnect to channels.
109-
co_await conn->async_exec(req, ignore);
109+
co_await conn->async_exec(req);
110110

111111
// Loop reading Redis pushes.
112-
for (;;) {
113-
error_code ec;
114-
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
112+
for (error_code ec;;) {
113+
co_await conn->async_receive2(resp, redirect_error(ec));
115114
if (ec)
116115
break; // Connection lost, break so we can reconnect to channels.
117116

118117
// Use the response resp in some way and then clear it.
119118
...
120119

121-
consume_one(resp);
120+
resp.clear();
122121
}
123122
}
124123
}
125124
```
126125
127126
## Further reading
128127
129-
Full documentation is [here](https://www.boost.org/doc/libs/master/libs/redis/index.html).
128+
Full documentation is [here](https://www.boost.org/doc/libs/master/libs/redis/index.html).

doc/modules/ROOT/pages/index.adoc

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ them are:
9797
* https://redis.io/docs/manual/client-side-caching/[Client-side caching].
9898

9999
The connection class supports server pushes by means of the
100-
xref:reference:boost/redis/basic_connection/async_receive.adoc[`connection::async_receive`] function, which can be
100+
xref:reference:boost/redis/basic_connection/async_receive.adoc[`connection::async_receive2`] function, which can be
101101
called in the same connection that is being used to execute commands.
102-
The coroutine below shows how to use it:
102+
The coroutine below shows how to use it
103103

104104

105105
[source,cpp]
@@ -110,26 +110,25 @@ receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
110110
request req;
111111
req.push("SUBSCRIBE", "channel");
112112
113-
generic_response resp;
113+
flat_tree resp;
114114
conn->set_receive_response(resp);
115115
116116
// Loop while reconnection is enabled
117117
while (conn->will_reconnect()) {
118118
119119
// Reconnect to channels.
120-
co_await conn->async_exec(req, ignore);
120+
co_await conn->async_exec(req);
121121
122122
// Loop reading Redis pushes.
123-
for (;;) {
124-
error_code ec;
125-
co_await conn->async_receive(resp, net::redirect_error(net::use_awaitable, ec));
123+
for (error_code ec;;) {
124+
co_await conn->async_receive2(resp, redirect_error(ec));
126125
if (ec)
127126
break; // Connection lost, break so we can reconnect to channels.
128127
129-
// Use the response resp in some way and then clear it.
128+
// Use the response here and then clear it.
130129
...
131130
132-
consume_one(resp);
131+
resp.clear();
133132
}
134133
}
135134
}

example/cpp20_chat_room.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
1+
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
22
*
33
* Distributed under the Boost Software License, Version 1.0. (See
44
* accompanying file LICENSE.txt)
@@ -30,11 +30,9 @@ using boost::asio::consign;
3030
using boost::asio::detached;
3131
using boost::asio::dynamic_buffer;
3232
using boost::asio::redirect_error;
33-
using boost::asio::use_awaitable;
3433
using boost::redis::config;
3534
using boost::redis::connection;
36-
using boost::redis::generic_response;
37-
using boost::redis::ignore;
35+
using boost::redis::generic_flat_response;
3836
using boost::redis::request;
3937
using boost::system::error_code;
4038
using namespace std::chrono_literals;
@@ -47,20 +45,24 @@ auto receiver(std::shared_ptr<connection> conn) -> awaitable<void>
4745
request req;
4846
req.push("SUBSCRIBE", "channel");
4947

50-
generic_response resp;
48+
generic_flat_response resp;
5149
conn->set_receive_response(resp);
5250

5351
while (conn->will_reconnect()) {
5452
// Subscribe to channels.
55-
co_await conn->async_exec(req, ignore);
53+
co_await conn->async_exec(req);
5654

5755
// Loop reading Redis push messages.
5856
for (error_code ec;;) {
59-
co_await conn->async_receive(redirect_error(use_awaitable, ec));
57+
co_await conn->async_receive2(redirect_error(ec));
6058
if (ec)
6159
break; // Connection lost, break so we can reconnect to channels.
62-
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " "
63-
<< resp.value().at(3).value << std::endl;
60+
61+
for (auto const& elem: resp.value().get_view())
62+
std::cout << elem.value << "\n";
63+
64+
std::cout << std::endl;
65+
6466
resp.value().clear();
6567
}
6668
}
@@ -74,7 +76,7 @@ auto publisher(std::shared_ptr<stream_descriptor> in, std::shared_ptr<connection
7476
auto n = co_await async_read_until(*in, dynamic_buffer(msg, 1024), "\n");
7577
request req;
7678
req.push("PUBLISH", "channel", msg);
77-
co_await conn->async_exec(req, ignore);
79+
co_await conn->async_exec(req);
7880
msg.erase(0, n);
7981
}
8082
}

example/cpp20_subscriber.cpp

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
1+
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
22
*
33
* Distributed under the Boost Software License, Version 1.0. (See
44
* accompanying file LICENSE.txt)
55
*/
66

77
#include <boost/redis/connection.hpp>
8-
#include <boost/redis/logger.hpp>
98

109
#include <boost/asio/awaitable.hpp>
1110
#include <boost/asio/co_spawn.hpp>
@@ -22,12 +21,8 @@
2221
namespace asio = boost::asio;
2322
using namespace std::chrono_literals;
2423
using boost::redis::request;
25-
using boost::redis::generic_response;
26-
using boost::redis::consume_one;
27-
using boost::redis::logger;
24+
using boost::redis::generic_flat_response;
2825
using boost::redis::config;
29-
using boost::redis::ignore;
30-
using boost::redis::error;
3126
using boost::system::error_code;
3227
using boost::redis::connection;
3328
using asio::signal_set;
@@ -54,30 +49,29 @@ auto receiver(std::shared_ptr<connection> conn) -> asio::awaitable<void>
5449
request req;
5550
req.push("SUBSCRIBE", "channel");
5651

57-
generic_response resp;
52+
generic_flat_response resp;
5853
conn->set_receive_response(resp);
5954

6055
// Loop while reconnection is enabled
6156
while (conn->will_reconnect()) {
6257
// Reconnect to the channels.
63-
co_await conn->async_exec(req, ignore);
58+
co_await conn->async_exec(req);
6459

65-
// Loop reading Redis pushs messages.
60+
// Loop to read Redis push messages.
6661
for (error_code ec;;) {
67-
// First tries to read any buffered pushes.
68-
conn->receive(ec);
69-
if (ec == error::sync_receive_push_failed) {
70-
ec = {};
71-
co_await conn->async_receive(asio::redirect_error(asio::use_awaitable, ec));
72-
}
73-
62+
// Wait for pushes
63+
co_await conn->async_receive2(asio::redirect_error(ec));
7464
if (ec)
7565
break; // Connection lost, break so we can reconnect to channels.
7666

77-
std::cout << resp.value().at(1).value << " " << resp.value().at(2).value << " "
78-
<< resp.value().at(3).value << std::endl;
67+
// The response must be consumed without suspending the
68+
// coroutine i.e. without the use of async operations.
69+
for (auto const& elem: resp.value().get_view())
70+
std::cout << elem.value << "\n";
71+
72+
std::cout << std::endl;
7973

80-
consume_one(resp);
74+
resp.value().clear();
8175
}
8276
}
8377
}

include/boost/redis/adapter/detail/adapters.hpp

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
1+
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
22
*
33
* Distributed under the Boost Software License, Version 1.0. (See
44
* accompanying file LICENSE.txt)
@@ -12,6 +12,8 @@
1212
#include <boost/redis/resp3/node.hpp>
1313
#include <boost/redis/resp3/serialization.hpp>
1414
#include <boost/redis/resp3/type.hpp>
15+
#include <boost/redis/resp3/flat_tree.hpp>
16+
#include <boost/redis/response.hpp>
1517

1618
#include <boost/assert.hpp>
1719

@@ -176,6 +178,97 @@ class general_aggregate {
176178
}
177179
};
178180

181+
template <>
182+
class general_aggregate<resp3::tree> {
183+
private:
184+
resp3::tree* tree_ = nullptr;
185+
186+
public:
187+
explicit general_aggregate(resp3::tree* c = nullptr)
188+
: tree_(c)
189+
{ }
190+
191+
void on_init() { }
192+
void on_done() { }
193+
194+
template <class String>
195+
void on_node(resp3::basic_node<String> const& nd, system::error_code&)
196+
{
197+
BOOST_ASSERT_MSG(!!tree_, "Unexpected null pointer");
198+
199+
resp3::node tmp;
200+
tmp.data_type = nd.data_type;
201+
tmp.aggregate_size = nd.aggregate_size;
202+
tmp.depth = nd.depth;
203+
tmp.value = std::string{std::cbegin(nd.value), std::cend(nd.value)};
204+
205+
tree_->push_back(std::move(tmp));
206+
}
207+
};
208+
209+
template <>
210+
class general_aggregate<generic_flat_response> {
211+
private:
212+
generic_flat_response* tree_ = nullptr;
213+
214+
public:
215+
explicit general_aggregate(generic_flat_response* c = nullptr)
216+
: tree_(c)
217+
{ }
218+
219+
void on_init() { }
220+
void on_done()
221+
{
222+
BOOST_ASSERT_MSG(!!tree_, "Unexpected null pointer");
223+
if (tree_->has_value()) {
224+
tree_->value().notify_done();
225+
}
226+
}
227+
228+
template <class String>
229+
void on_node(resp3::basic_node<String> const& nd, system::error_code&)
230+
{
231+
BOOST_ASSERT_MSG(!!tree_, "Unexpected null pointer");
232+
switch (nd.data_type) {
233+
case resp3::type::blob_error:
234+
case resp3::type::simple_error:
235+
*tree_ = error{
236+
nd.data_type,
237+
std::string{std::cbegin(nd.value), std::cend(nd.value)}
238+
};
239+
break;
240+
default:
241+
if (tree_->has_value()) {
242+
(**tree_).push(nd);
243+
}
244+
}
245+
}
246+
};
247+
248+
template <>
249+
class general_aggregate<resp3::flat_tree> {
250+
private:
251+
resp3::flat_tree* tree_ = nullptr;
252+
253+
public:
254+
explicit general_aggregate(resp3::flat_tree* c = nullptr)
255+
: tree_(c)
256+
{ }
257+
258+
void on_init() { }
259+
void on_done()
260+
{
261+
tree_->notify_done();
262+
}
263+
264+
template <class String>
265+
void on_node(resp3::basic_node<String> const& nd, system::error_code&)
266+
{
267+
BOOST_ASSERT_MSG(!!tree_, "Unexpected null pointer");
268+
tree_->push(nd);
269+
}
270+
};
271+
179272
template <class Node>
180273
class general_simple {
181274
private:

include/boost/redis/adapter/detail/response_traits.hpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,32 @@ struct response_traits<result<ignore_t>> {
9292
};
9393

9494
template <class String, class Allocator>
95-
struct response_traits<result<std::vector<resp3::basic_node<String>, Allocator>>> {
96-
using response_type = result<std::vector<resp3::basic_node<String>, Allocator>>;
95+
struct response_traits<result<resp3::basic_tree<String, Allocator>>> {
96+
using response_type = result<resp3::basic_tree<String, Allocator>>;
97+
using adapter_type = general_aggregate<response_type>;
98+
99+
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
100+
};
101+
102+
template <class String>
103+
struct response_traits<resp3::basic_tree<String>> {
104+
using response_type = resp3::basic_tree<String>;
105+
using adapter_type = general_aggregate<response_type>;
106+
107+
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
108+
};
109+
110+
template <>
111+
struct response_traits<resp3::flat_tree> {
112+
using response_type = resp3::flat_tree;
113+
using adapter_type = general_aggregate<response_type>;
114+
115+
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
116+
};
117+
118+
template <>
119+
struct response_traits<generic_flat_response> {
120+
using response_type = generic_flat_response;
97121
using adapter_type = general_aggregate<response_type>;
98122

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

0 commit comments

Comments
 (0)