Skip to content

Commit 412f553

Browse files
committed
Adds support for optional fields
1 parent 31ceed9 commit 412f553

File tree

8 files changed

+287
-112
lines changed

8 files changed

+287
-112
lines changed

Diff for: README.md

+61-51
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,39 @@ Boost.Redis is a high-level [Redis](https://redis.io/) client library built on t
44
[Boost.Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
55
that implements the Redis protocol
66
[RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
7-
The requirements for using Boost.Redis are:
7+
The requirements for using Boost.Redis are
88

9-
* Boost. The library is included in Boost distributions starting with 1.84.
9+
* Boost 1.84 or higher.
1010
* C++17 or higher.
1111
* Redis 6 or higher (must support RESP3).
12-
* Gcc (11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022).
12+
* GCC (11, 12), Clang (11, 13, 14) and Visual Studio (16 2019, 17 2022).
1313
* Have basic-level knowledge about [Redis](https://redis.io/docs/)
1414
and [Boost.Asio](https://www.boost.org/doc/libs/1_82_0/doc/html/boost_asio/overview.html).
1515

16-
The latest release can be downloaded on
17-
https://github.com/boostorg/redis/releases. The library headers can be
18-
found in the `include` subdirectory and a compilation of the source
16+
To use the library it is necessary to include
1917

2018
```cpp
2119
#include <boost/redis/src.hpp>
2220
```
2321

24-
is required. The simplest way to do it is to included this header in
25-
no more than one source file in your applications. To build the
26-
examples and tests cmake is supported, for example
22+
in no more than one source file in your applications. To build the
23+
examples and tests with cmake run
2724

2825
```cpp
2926
# Linux
30-
$ BOOST_ROOT=/opt/boost_1_84_0 cmake --preset g++-11
27+
$ BOOST_ROOT=/opt/boost_1_84_0 cmake -S <source-dir> -B <binary-dir>
3128

3229
# Windows
3330
$ cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake
3431
```
3532

33+
For more details see https://github.com/boostorg/cmake.
34+
3635
<a name="connection"></a>
3736
## Connection
3837

39-
Let us start with a simple application that uses a short-lived
40-
connection to send a [ping](https://redis.io/commands/ping/) command
41-
to Redis
38+
The code below uses a short-lived connection to
39+
[ping](https://redis.io/commands/ping/) the Redis server
4240

4341
```cpp
4442
auto co_main(config const& cfg) -> net::awaitable<void>
@@ -50,7 +48,7 @@ auto co_main(config const& cfg) -> net::awaitable<void>
5048
request req;
5149
req.push("PING", "Hello world");
5250

53-
// Response where the PONG response will be stored.
51+
// Response object.
5452
response<std::string> resp;
5553

5654
// Executes the request.
@@ -145,21 +143,18 @@ req.push_range("SUBSCRIBE", std::cbegin(list), std::cend(list));
145143
req.push_range("HSET", "key", map);
146144
```
147145
148-
Sending a request to Redis is performed with `boost::redis::connection::async_exec` as already stated.
149-
150-
### Config flags
151-
152-
The `boost::redis::request::config` object inside the request dictates how the
153-
`boost::redis::connection` should handle the request in some important situations. The
154-
reader is advised to read it carefully.
146+
Sending a request to Redis is performed with `boost::redis::connection::async_exec` as already stated. The
147+
`boost::redis::request::config` object inside the request dictates how
148+
the `boost::redis::connection` the request is handled in some
149+
situations. The reader is advised to read it carefully.
155150
156151
<a name="responses"></a>
157152
## Responses
158153
159-
Boost.Redis uses the following strategy to support Redis responses
154+
Boost.Redis uses the following strategy to deal with Redis responses
160155
161-
* `boost::redis::request` is used for requests whose number of commands are not dynamic.
162-
* **Dynamic**: Otherwise use `boost::redis::generic_response`.
156+
* `boost::redis::request` used for requests whose number of commands are not dynamic.
157+
* `boost::redis::generic_response` used when the size is dynamic.
163158
164159
For example, the request below has three commands
165160
@@ -170,8 +165,8 @@ req.push("INCR", "key");
170165
req.push("QUIT");
171166
```
172167

173-
and its response also has three commands and can be read in the
174-
following response object
168+
and therefore its response will also contain three elements which can
169+
be read in the following reponse object
175170

176171
```cpp
177172
response<std::string, int, std::string>
@@ -186,7 +181,7 @@ To ignore responses to individual commands in the request use the tag
186181

187182
```cpp
188183
// Ignore the second and last responses.
189-
response<std::string, boost::redis::ignore_t, std::string, boost::redis::ignore_t>
184+
response<std::string, ignore_t, std::string, ignore_t>
190185
```
191186

192187
The following table provides the resp3-types returned by some Redis
@@ -230,7 +225,7 @@ req.push("QUIT");
230225

231226
```
232227

233-
can be read in the tuple below
228+
can be read in the response object below
234229

235230
```cpp
236231
response<
@@ -243,7 +238,8 @@ response<
243238
> resp;
244239
```
245240

246-
Where both are passed to `async_exec` as showed elsewhere
241+
Then, to execute the request and read the response use `async_exec` as
242+
shown below
247243

248244
```cpp
249245
co_await conn->async_exec(req, resp);
@@ -279,27 +275,23 @@ req.push("SUBSCRIBE", "channel");
279275
req.push("QUIT");
280276
```
281277

282-
must be read in this tuple `response<std::string, std::string>`,
283-
that has static size two.
278+
must be read in the response object `response<std::string, std::string>`.
284279

285280
### Null
286281

287-
It is not uncommon for apps to access keys that do not exist or
288-
that have already expired in the Redis server, to deal with these
289-
cases Boost.Redis provides support for `std::optional`. To use it,
290-
wrap your type around `std::optional` like this
282+
It is not uncommon for apps to access keys that do not exist or that
283+
have already expired in the Redis server, to deal with these usecases
284+
wrap the type with an `std::optional` as shown below
291285

292286
```cpp
293287
response<
294288
std::optional<A>,
295289
std::optional<B>,
296290
...
297291
> resp;
298-
299-
co_await conn->async_exec(req, resp);
300292
```
301293

302-
Everything else stays pretty much the same.
294+
Everything else stays the same.
303295

304296
### Transactions
305297

@@ -321,22 +313,18 @@ use the following response type
321313
```cpp
322314
using boost::redis::ignore;
323315

324-
using exec_resp_type =
316+
317+
response<
318+
ignore_t, // multi
319+
ignore_t, // QUEUED
320+
ignore_t, // QUEUED
321+
ignore_t, // QUEUED
325322
response<
326323
std::optional<std::string>, // get
327324
std::optional<std::vector<std::string>>, // lrange
328325
std::optional<std::map<std::string, std::string>> // hgetall
329-
>;
330-
331-
response<
332-
boost::redis::ignore_t, // multi
333-
boost::redis::ignore_t, // get
334-
boost::redis::ignore_t, // lrange
335-
boost::redis::ignore_t, // hgetall
336-
exec_resp_type, // exec
326+
> // exec
337327
> resp;
338-
339-
co_await conn->async_exec(req, resp);
340328
```
341329

342330
For a complete example see cpp20_containers.cpp.
@@ -350,7 +338,7 @@ commands won't fit in the model presented above, some examples are
350338

351339
* Commands (like `set`) whose responses don't have a fixed
352340
RESP3 type. Expecting an `int` and receiving a blob-string
353-
will result in error.
341+
results in an error.
354342
* RESP3 aggregates that contain nested aggregates can't be read in STL containers.
355343
* Transactions with a dynamic number of commands can't be read in a `response`.
356344

@@ -411,7 +399,7 @@ the following customization points
411399
void boost_redis_to_bulk(std::string& to, mystruct const& obj);
412400

413401
// Deserialize
414-
void boost_redis_from_bulk(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
402+
void boost_redis_from_bulk(mystruct& u, node_view const& node, boost::system::error_code&)
415403
```
416404
417405
These functions are accessed over ADL and therefore they must be
@@ -676,6 +664,28 @@ https://lists.boost.org/Archives/boost/2023/01/253944.php.
676664

677665
## Changelog
678666

667+
### Boost 1.88
668+
669+
* (Issue [233](https://github.com/boostorg/redis/issues/233))
670+
To deal with keys that might not exits in the Redis server, the
671+
library supports `std::optional`, for example
672+
`response<std::optional<std::vector<std::string>>>`. In some cases
673+
however, such as the [MGET](https://redis.io/docs/latest/commands/mget/) command,
674+
each element in the vector might be non exiting, now it is possible
675+
to specify a response as `response<std::optional<std::vector<std::optional<std::string>>>>`.
676+
677+
* (Issue [225](https://github.com/boostorg/redis/issues/225))
678+
Use `deferred` as the connection default completion token.
679+
680+
* (Issue [128](https://github.com/boostorg/redis/issues/128))
681+
Adds a new `async_exec` overload that allows passing response
682+
adapters. This makes it possible to receive Redis responses directly
683+
in custom data structures thereby avoiding uncessary data copying.
684+
Thanks to Ruben Perez (@anarthal) for implementing this feature.
685+
686+
* There are also other multiple small improvements in this release,
687+
users can refer to the git history for more details.
688+
679689
### Boost 1.87
680690

681691
* (Issue [205](https://github.com/boostorg/redis/issues/205))

Diff for: example/cpp20_containers.cpp

+41-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva ([email protected])
1+
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva ([email protected])
22
*
33
* Distributed under the Boost Software License, Version 1.0. (See
44
* accompanying file LICENSE.txt)
@@ -24,13 +24,25 @@ using boost::asio::awaitable;
2424
using boost::asio::detached;
2525
using boost::asio::consign;
2626

27+
template<class T>
28+
std::ostream& operator<<(std::ostream& os, std::optional<T> const& opt)
29+
{
30+
if (opt.has_value())
31+
std::cout << opt.value();
32+
else
33+
std::cout << "null";
34+
35+
return os;
36+
}
37+
2738
void print(std::map<std::string, std::string> const& cont)
2839
{
2940
for (auto const& e: cont)
3041
std::cout << e.first << ": " << e.second << "\n";
3142
}
3243

33-
void print(std::vector<int> const& cont)
44+
template <class T>
45+
void print(std::vector<T> const& cont)
3446
{
3547
for (auto const& e: cont) std::cout << e << " ";
3648
std::cout << "\n";
@@ -48,6 +60,7 @@ auto store(std::shared_ptr<connection> conn) -> awaitable<void>
4860
request req;
4961
req.push_range("RPUSH", "rpush-key", vec);
5062
req.push_range("HSET", "hset-key", map);
63+
req.push("SET", "key", "value");
5164

5265
co_await conn->async_exec(req, ignore);
5366
}
@@ -67,26 +80,48 @@ auto hgetall(std::shared_ptr<connection> conn) -> awaitable<void>
6780
print(std::get<0>(resp).value());
6881
}
6982

83+
auto mget(std::shared_ptr<connection> conn) -> awaitable<void>
84+
{
85+
// A request contains multiple commands.
86+
request req;
87+
req.push("MGET", "key", "non-existing-key");
88+
89+
// Responses as tuple elements.
90+
response<std::vector<std::optional<std::string>>> resp;
91+
92+
// Executes the request and reads the response.
93+
co_await conn->async_exec(req, resp);
94+
95+
print(std::get<0>(resp).value());
96+
}
97+
7098
// Retrieves in a transaction.
7199
auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
72100
{
73101
request req;
74102
req.push("MULTI");
75103
req.push("LRANGE", "rpush-key", 0, -1); // Retrieves
76104
req.push("HGETALL", "hset-key"); // Retrieves
105+
req.push("MGET", "key", "non-existing-key");
77106
req.push("EXEC");
78107

79108
response<
80109
ignore_t, // multi
81110
ignore_t, // lrange
82111
ignore_t, // hgetall
83-
response<std::optional<std::vector<int>>, std::optional<std::map<std::string, std::string>>> // exec
112+
ignore_t, // mget
113+
response<
114+
std::optional<std::vector<int>>,
115+
std::optional<std::map<std::string, std::string>>,
116+
std::optional<std::vector<std::optional<std::string>>>
117+
> // exec
84118
> resp;
85119

86120
co_await conn->async_exec(req, resp);
87121

88-
print(std::get<0>(std::get<3>(resp).value()).value().value());
89-
print(std::get<1>(std::get<3>(resp).value()).value().value());
122+
print(std::get<0>(std::get<4>(resp).value()).value().value());
123+
print(std::get<1>(std::get<4>(resp).value()).value().value());
124+
print(std::get<2>(std::get<4>(resp).value()).value().value());
90125
}
91126

92127
// Called from the main function (see main.cpp)
@@ -98,6 +133,7 @@ awaitable<void> co_main(config cfg)
98133
co_await store(conn);
99134
co_await transaction(conn);
100135
co_await hgetall(conn);
136+
co_await mget(conn);
101137
conn->cancel();
102138
}
103139

Diff for: example/cpp20_json.cpp

+13-3
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
#include <boost/redis/resp3/serialization.hpp>
2222

2323
namespace asio = boost::asio;
24+
namespace resp3 = boost::redis::resp3;
2425
using namespace boost::describe;
2526
using boost::redis::request;
2627
using boost::redis::response;
2728
using boost::redis::ignore_t;
2829
using boost::redis::config;
2930
using boost::redis::connection;
31+
using boost::redis::resp3::node_view;
3032

3133
// Struct that will be stored in Redis using json serialization.
3234
struct user {
@@ -40,10 +42,18 @@ BOOST_DESCRIBE_STRUCT(user, (), (name, age, country))
4042

4143
// Boost.Redis customization points (example/json.hpp)
4244
void boost_redis_to_bulk(std::string& to, user const& u)
43-
{ boost::redis::resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u))); }
45+
{
46+
resp3::boost_redis_to_bulk(to, boost::json::serialize(boost::json::value_from(u)));
47+
}
4448

45-
void boost_redis_from_bulk(user& u, std::string_view sv, boost::system::error_code&)
46-
{ u = boost::json::value_to<user>(boost::json::parse(sv)); }
49+
void
50+
boost_redis_from_bulk(
51+
user& u,
52+
node_view const& node,
53+
boost::system::error_code&)
54+
{
55+
u = boost::json::value_to<user>(boost::json::parse(node.value));
56+
}
4757

4858
auto co_main(config cfg) -> asio::awaitable<void>
4959
{

0 commit comments

Comments
 (0)