mirror of
https://github.com/boostorg/redis.git
synced 2026-02-02 21:12:16 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19f03e2f41 | ||
|
|
0fff6496fb | ||
|
|
57ba376544 | ||
|
|
46b86c20e5 | ||
|
|
577c6d35fb | ||
|
|
42ef8a3b06 | ||
|
|
f9af8e585b | ||
|
|
350ae19936 | ||
|
|
57f2644903 | ||
|
|
0ac0c3cf23 | ||
|
|
5c23299a8a | ||
|
|
379da7a340 | ||
|
|
bcf13c03c0 | ||
|
|
be79f58808 | ||
|
|
2c96b07623 | ||
|
|
577d32f0e2 | ||
|
|
5f93257502 | ||
|
|
409ee61522 | ||
|
|
5aa034d6a5 | ||
|
|
0ff0e537ce | ||
|
|
320ee6b3cc | ||
|
|
5061e5a7a6 | ||
|
|
0bda78dd9c | ||
|
|
8704e7756f | ||
|
|
8207ef7e8a | ||
|
|
4712872daf | ||
|
|
06752ac664 | ||
|
|
b3be596f77 | ||
|
|
f92290be16 | ||
|
|
331010c240 | ||
|
|
11b697c572 | ||
|
|
1218b2ef01 | ||
|
|
8cc142d55b | ||
|
|
0723922ac1 | ||
|
|
54ba34c70c | ||
|
|
efe44fbd45 | ||
|
|
268b59afbc | ||
|
|
6b6272694c | ||
|
|
f36f3b7b5e | ||
|
|
b4fef73b87 | ||
|
|
c27b3b6b85 | ||
|
|
feb383d7e2 | ||
|
|
ee71ff885d | ||
|
|
80a80f44ff | ||
|
|
053ce3aea9 | ||
|
|
db27e3cc60 | ||
|
|
449ba0dd73 | ||
|
|
8728f58981 | ||
|
|
2573f39aa1 | ||
|
|
f460bf43a5 | ||
|
|
85d0a30dad | ||
|
|
b7f22d8c61 | ||
|
|
b04cc12d64 | ||
|
|
317690ee91 | ||
|
|
fe3cc2efb0 |
91
Makefile.am
91
Makefile.am
@@ -6,30 +6,32 @@ DISTCHECK_CONFIGURE_FLAGS = CPPFLAGS="$(BOOST_CPPFLAGS) $(CPPFLAGS)" LDFLAGS="$(
|
||||
|
||||
AM_CPPFLAGS =
|
||||
AM_CPPFLAGS += $(BOOST_CPPFLAGS)
|
||||
#AM_CPPFLAGS += -I$(top_srcdir)/include
|
||||
|
||||
AM_LDFLAGS =
|
||||
AM_LDFLAGS += -pthread
|
||||
|
||||
check_PROGRAMS =
|
||||
check_PROGRAMS += intro
|
||||
check_PROGRAMS += sets
|
||||
check_PROGRAMS += hashes
|
||||
check_PROGRAMS += serialization
|
||||
check_PROGRAMS += multipurpose_response
|
||||
check_PROGRAMS += lists
|
||||
check_PROGRAMS += key_expiration
|
||||
check_PROGRAMS += response_adapter
|
||||
check_PROGRAMS += sync
|
||||
check_PROGRAMS += multipurpose_client
|
||||
check_PROGRAMS += test_offline
|
||||
check_PROGRAMS += low_level_sync_intro
|
||||
check_PROGRAMS += high_level_intro
|
||||
check_PROGRAMS += high_level_aggregates
|
||||
check_PROGRAMS += high_level_stl_containers
|
||||
check_PROGRAMS += high_level_serialization
|
||||
check_PROGRAMS += test_low_level
|
||||
if HAVE_CXX20
|
||||
check_PROGRAMS += low_level_async_intro
|
||||
check_PROGRAMS += low_level_adapter
|
||||
check_PROGRAMS += test_online
|
||||
check_PROGRAMS += transaction
|
||||
endif
|
||||
|
||||
EXTRA_PROGRAMS =
|
||||
EXTRA_PROGRAMS += subscriber
|
||||
EXTRA_PROGRAMS += echo_server
|
||||
EXTRA_PROGRAMS += chat_room
|
||||
EXTRA_PROGRAMS += high_level_subscriber
|
||||
EXTRA_PROGRAMS += commands
|
||||
if HAVE_CXX20
|
||||
EXTRA_PROGRAMS += low_level_subscriber
|
||||
EXTRA_PROGRAMS += high_level_echo_server
|
||||
EXTRA_PROGRAMS += high_level_chat_room
|
||||
endif
|
||||
|
||||
CLEANFILES =
|
||||
CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
@@ -37,55 +39,52 @@ CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
.PHONY: all
|
||||
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
|
||||
|
||||
intro_SOURCES = $(top_srcdir)/examples/intro.cpp
|
||||
sets_SOURCES = $(top_srcdir)/examples/sets.cpp
|
||||
hashes_SOURCES = $(top_srcdir)/examples/hashes.cpp
|
||||
serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp
|
||||
multipurpose_response_SOURCES = $(top_srcdir)/examples/multipurpose_response.cpp
|
||||
lists_SOURCES = $(top_srcdir)/examples/lists.cpp
|
||||
key_expiration_SOURCES = $(top_srcdir)/examples/key_expiration.cpp
|
||||
response_adapter_SOURCES = $(top_srcdir)/examples/response_adapter.cpp
|
||||
sync_SOURCES = $(top_srcdir)/examples/sync.cpp
|
||||
multipurpose_client_SOURCES = $(top_srcdir)/examples/multipurpose_client.cpp
|
||||
high_level_intro_SOURCES = $(top_srcdir)/examples/high_level/intro.cpp
|
||||
high_level_aggregates_SOURCES = $(top_srcdir)/examples/high_level/aggregates.cpp
|
||||
high_level_stl_containers_SOURCES = $(top_srcdir)/examples/high_level/stl_containers.cpp
|
||||
high_level_serialization_SOURCES = $(top_srcdir)/examples/high_level/serialization.cpp
|
||||
low_level_sync_intro_SOURCES = $(top_srcdir)/examples/low_level/sync_intro.cpp
|
||||
commands_SOURCES = $(top_srcdir)/tools/commands.cpp
|
||||
test_offline_SOURCES = $(top_srcdir)/tests/offline.cpp
|
||||
high_level_subscriber_SOURCES = $(top_srcdir)/examples/high_level/subscriber.cpp
|
||||
test_low_level_SOURCES = $(top_srcdir)/tests/low_level.cpp
|
||||
if HAVE_CXX20
|
||||
test_online_SOURCES = $(top_srcdir)/tests/online.cpp
|
||||
subscriber_SOURCES = $(top_srcdir)/examples/subscriber.cpp
|
||||
echo_server_SOURCES = $(top_srcdir)/examples/echo_server.cpp
|
||||
chat_room_SOURCES = $(top_srcdir)/examples/chat_room.cpp
|
||||
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
|
||||
low_level_async_intro_SOURCES = $(top_srcdir)/examples/low_level/async_intro.cpp
|
||||
low_level_subscriber_SOURCES = $(top_srcdir)/examples/low_level/subscriber.cpp
|
||||
low_level_adapter_SOURCES = $(top_srcdir)/examples/low_level/adapter.cpp
|
||||
high_level_echo_server_SOURCES = $(top_srcdir)/examples/high_level/echo_server.cpp
|
||||
high_level_chat_room_SOURCES = $(top_srcdir)/examples/high_level/chat_room.cpp
|
||||
endif
|
||||
|
||||
nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/config.hpp\
|
||||
$(top_srcdir)/aedis/src.hpp\
|
||||
$(top_srcdir)/aedis/redis/command.hpp\
|
||||
$(top_srcdir)/aedis/generic/client.hpp\
|
||||
$(top_srcdir)/aedis/generic/serializer.hpp\
|
||||
$(top_srcdir)/aedis/generic/detail/client_ops.hpp\
|
||||
$(top_srcdir)/aedis/sentinel/command.hpp\
|
||||
$(top_srcdir)/aedis/aedis.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapter/detail/adapters.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapter/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapt.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/composer.hpp\
|
||||
$(top_srcdir)/aedis/adapter/detail/adapters.hpp\
|
||||
$(top_srcdir)/aedis/adapter/error.hpp\
|
||||
$(top_srcdir)/aedis/adapter/impl/error.ipp\
|
||||
$(top_srcdir)/aedis/adapter/adapt.hpp\
|
||||
$(top_srcdir)/aedis/adapter/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/resp3/node.hpp\
|
||||
$(top_srcdir)/aedis/resp3/compose.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/read_ops.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/parser.hpp\
|
||||
$(top_srcdir)/aedis/resp3/serializer.hpp\
|
||||
$(top_srcdir)/aedis/resp3/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/resp3/node.hpp\
|
||||
$(top_srcdir)/aedis/resp3/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/impl/error.ipp\
|
||||
$(top_srcdir)/aedis/resp3/type.hpp\
|
||||
$(top_srcdir)/aedis/resp3/read.hpp\
|
||||
$(top_srcdir)/aedis/redis/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/sentinel/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/resp3/detail/impl/parser.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/node.ipp\
|
||||
$(top_srcdir)/aedis/redis/experimental/client.hpp\
|
||||
$(top_srcdir)/aedis/redis/experimental/impl/client.ipp
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp
|
||||
|
||||
nobase_noinst_HEADERS =\
|
||||
$(top_srcdir)/examples/lib/net_utils.hpp\
|
||||
$(top_srcdir)/examples/lib/user_session.hpp\
|
||||
$(top_srcdir)/tests/check.hpp\
|
||||
$(top_srcdir)/tests/test_stream.hpp
|
||||
$(top_srcdir)/examples/high_level/user_session.hpp\
|
||||
$(top_srcdir)/tests/check.hpp
|
||||
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
|
||||
68
aedis/adapter/adapt.hpp
Normal file
68
aedis/adapter/adapt.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/adapter/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Creates a void response adapter.
|
||||
\ingroup any
|
||||
|
||||
The adapter returned by this function ignores responses and is
|
||||
useful to avoid wasting time with responses on which the user is
|
||||
not insterested in.
|
||||
|
||||
Example usage:
|
||||
|
||||
@code
|
||||
co_await async_read(socket, buffer, adapt());
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt() noexcept
|
||||
{ return response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
*
|
||||
* All STL containers, \c std::tuple and built-in types are supported and
|
||||
* can be used in conjunction with \c boost::optional<T>.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
* sr.push(command::multi);
|
||||
* sr.push(command::ping, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push_range(command::rpush, ...);
|
||||
* sr.push(command::lrange, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push(command::exec);
|
||||
*
|
||||
* co_await async_write(socket, buffer(request));
|
||||
*
|
||||
* // Reads the response to a transaction
|
||||
* std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
401
aedis/adapter/detail/adapters.hpp
Normal file
401
aedis/adapter/detail/adapters.hpp
Normal file
@@ -0,0 +1,401 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <forward_list>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/generic/serializer.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
// Serialization.
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_string(
|
||||
T& i,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
|
||||
}
|
||||
|
||||
void from_string(
|
||||
bool& t,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
t = *sv.data() == 't';
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_string(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code&)
|
||||
{
|
||||
s.append(sv.data(), sv.size());
|
||||
}
|
||||
|
||||
//================================================
|
||||
|
||||
void set_on_resp3_error(resp3::type t, boost::system::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case resp3::type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case resp3::type::blob_error: ec = adapter::error::blob_error; return;
|
||||
case resp3::type::null: ec = adapter::error::null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
class general_aggregate {
|
||||
private:
|
||||
Result* result_;
|
||||
|
||||
public:
|
||||
general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
{
|
||||
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node>
|
||||
class general_simple {
|
||||
private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
general_simple(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
{
|
||||
result_->data_type = n.data_type;
|
||||
result_->aggregate_size = n.aggregate_size;
|
||||
result_->depth = n.depth;
|
||||
result_->value.assign(n.value.data(), n.value.size());
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class simple_impl {
|
||||
public:
|
||||
void on_value_available(Result&) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& n,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(n.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(n.data_type)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
from_string(result, n.value, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class set_impl {
|
||||
private:
|
||||
typename Result::iterator hint_;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& result)
|
||||
{ hint_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (nd.data_type != resp3::type::set)
|
||||
ec = error::expects_set_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_set_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
typename Result::key_type obj;
|
||||
from_string(obj, nd.value, ec);
|
||||
hint_ = result.insert(hint_, std::move(obj));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class map_impl {
|
||||
private:
|
||||
typename Result::iterator current_;
|
||||
bool on_key_ = true;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& result)
|
||||
{ current_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (element_multiplicity(nd.data_type) != 2)
|
||||
ec = error::expects_map_like_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_map_like_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
if (on_key_) {
|
||||
typename Result::key_type obj;
|
||||
from_string(obj, nd.value, ec);
|
||||
current_ = result.insert(current_, {std::move(obj), {}});
|
||||
} else {
|
||||
typename Result::mapped_type obj;
|
||||
from_string(obj, nd.value, ec);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
on_key_ = !on_key_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class vector_impl {
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
auto const m = element_multiplicity(nd.data_type);
|
||||
result.reserve(result.size() + m * nd.aggregate_size);
|
||||
} else {
|
||||
result.push_back({});
|
||||
from_string(result.back(), nd.value, ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class array_impl {
|
||||
private:
|
||||
int i_ = -1;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (i_ != -1) {
|
||||
ec = adapter::error::nested_aggregate_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
|
||||
ec = error::incompatible_size;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (i_ == -1) {
|
||||
ec = adapter::error::expects_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(nd.aggregate_size == 1);
|
||||
from_string(result.at(i_), nd.value, ec);
|
||||
}
|
||||
|
||||
++i_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
struct list_impl {
|
||||
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (!is_aggregate(nd.data_type)) {
|
||||
assert(nd.aggregate_size == 1);
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_aggregate;
|
||||
return;
|
||||
}
|
||||
|
||||
result.push_back({});
|
||||
from_string(result.back(), nd.value, ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
struct impl_map { using type = simple_impl<T>; };
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct impl_map<std::set<Key, Compare, Allocator>> { using type = set_impl<std::set<Key, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct impl_map<std::multiset<Key, Compare, Allocator>> { using type = set_impl<std::multiset<Key, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct impl_map<std::map<Key, T, Compare, Allocator>> { using type = map_impl<std::map<Key, T, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct impl_map<std::multimap<Key, T, Compare, Allocator>> { using type = map_impl<std::multimap<Key, T, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::vector<T, Allocator>> { using type = vector_impl<std::vector<T, Allocator>>; };
|
||||
|
||||
template <class T, std::size_t N>
|
||||
struct impl_map<std::array<T, N>> { using type = array_impl<std::array<T, N>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::list<T, Allocator>> { using type = list_impl<std::list<T, Allocator>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T, Allocator>>; };
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
template <class Result>
|
||||
class wrapper {
|
||||
private:
|
||||
Result* result_;
|
||||
typename impl_map<Result>::type impl_;
|
||||
|
||||
public:
|
||||
wrapper(Result* t = nullptr) : result_(t)
|
||||
{ impl_.on_value_available(*result_); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
assert(result_);
|
||||
impl_(*result_, nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class wrapper<boost::optional<T>> {
|
||||
private:
|
||||
boost::optional<T>* result_;
|
||||
typename impl_map<T>::type impl_;
|
||||
|
||||
public:
|
||||
wrapper(boost::optional<T>* o = nullptr) : result_(o), impl_{} {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
if (nd.data_type == resp3::type::null)
|
||||
return;
|
||||
|
||||
if (!result_->has_value()) {
|
||||
*result_ = T{};
|
||||
impl_.on_value_available(result_->value());
|
||||
}
|
||||
|
||||
impl_(result_->value(), nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // aedis
|
||||
66
aedis/adapter/error.hpp
Normal file
66
aedis/adapter/error.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Errors that may occurr when reading a response.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_simple_type = 1,
|
||||
|
||||
/// Expects aggregate type.
|
||||
expects_aggregate,
|
||||
|
||||
/// Expects a map but got other aggregate.
|
||||
expects_map_like_aggregate,
|
||||
|
||||
/// Expects a set aggregate but got something else.
|
||||
expects_set_aggregate,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_aggregate_unsupported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
blob_error,
|
||||
|
||||
/// Aggregate container has incompatible size.
|
||||
incompatible_size,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
};
|
||||
|
||||
/** \brief todo
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_code make_error_code(error e);
|
||||
|
||||
/** \brief todo
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_condition make_error_condition(error e);
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::adapter::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
59
aedis/adapter/impl/error.ipp
Normal file
59
aedis/adapter/impl/error.ipp
Normal file
@@ -0,0 +1,59 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{
|
||||
return "aedis.adapter";
|
||||
}
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::expects_simple_type: return "Expects a simple RESP3 type";
|
||||
case error::expects_aggregate: return "Expects aggregate type";
|
||||
case error::expects_map_like_aggregate: return "Expects map aggregate";
|
||||
case error::expects_set_aggregate: return "Expects set aggregate";
|
||||
case error::nested_aggregate_unsupported: return "Nested aggregate unsupported.";
|
||||
case error::simple_error: return "Got RESP3 simple-error.";
|
||||
case error::blob_error: return "Got RESP3 blob-error.";
|
||||
case error::incompatible_size: return "Aggregate container has incompatible size.";
|
||||
case error::null: return "Got RESP3 null.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
boost::system::error_category const& category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
boost::system::error_code make_error_code(error e)
|
||||
{
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
boost::system::error_condition make_error_condition(error e)
|
||||
{
|
||||
return boost::system::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
234
aedis/adapter/response_traits.hpp
Normal file
234
aedis/adapter/response_traits.hpp
Normal file
@@ -0,0 +1,234 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
#include <boost/variant2.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/adapter/detail/adapters.hpp>
|
||||
#include <aedis/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
|
||||
/** @brief Traits class for response objects.
|
||||
* @ingroup any
|
||||
*
|
||||
* Provides traits for all supported response types i.e. all STL containers
|
||||
* and C++ buil-in types.
|
||||
*/
|
||||
template <class ResponseType>
|
||||
struct response_traits
|
||||
{
|
||||
/// The adapter type.
|
||||
using adapter_type = adapter::detail::wrapper<ResponseType>;
|
||||
|
||||
/** @brief Returns an adapter for the reponse r
|
||||
*
|
||||
* @param r The response object e.g a C++ container.
|
||||
* @return An adapter suitable for use in resp3::read or resp3::async_read.
|
||||
* @remark Users can also use the free adapt function for type deduction.
|
||||
*/
|
||||
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
/// Template typedef for response_traits.
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <class T>
|
||||
struct response_traits<resp3::node<T>>
|
||||
{
|
||||
using response_type = resp3::node<T>;
|
||||
using adapter_type = adapter::detail::general_simple<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class String, class Allocator>
|
||||
struct response_traits<std::vector<resp3::node<String>, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<resp3::node<String>, Allocator>;
|
||||
using adapter_type = adapter::detail::general_aggregate<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<void>
|
||||
{
|
||||
using response_type = void;
|
||||
using adapter_type = resp3::detail::ignore_response;
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[N] = internal_adapt(std::get<N>(from));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner2 {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
std::get<N>(dest) = internal_adapt(std::get<N>(from));
|
||||
assigner2<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner2<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
std::get<0>(dest) = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** @brief Return a specific adapter from the tuple.
|
||||
*
|
||||
* \param t A tuple of response adapters.
|
||||
* \return The adapter that corresponds to type T.
|
||||
*/
|
||||
template <class T, class Tuple>
|
||||
auto& get(Tuple& t)
|
||||
{
|
||||
return std::get<typename response_traits<T>::adapter_type>(t);
|
||||
}
|
||||
|
||||
template <class Tuple>
|
||||
using adapters_array_t =
|
||||
std::array<
|
||||
boost::mp11::mp_unique<
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
boost::variant2::variant>>,
|
||||
std::tuple_size<Tuple>::value>;
|
||||
|
||||
template <class Tuple>
|
||||
adapters_array_t<Tuple> make_adapters_array(Tuple& t)
|
||||
{
|
||||
adapters_array_t<Tuple> ret;
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(ret, t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** @brief Transaforms a tuple of responses.
|
||||
*
|
||||
* @return Transaforms a tuple of responses into a tuple of adapters.
|
||||
*/
|
||||
template <class Tuple>
|
||||
using adapters_tuple_t =
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
std::tuple>;
|
||||
|
||||
/** @brief Make a tuple of adapters.
|
||||
*
|
||||
* \param t Tuple of responses.
|
||||
* \return Tuple of adapters.
|
||||
*/
|
||||
template <class Tuple>
|
||||
auto
|
||||
make_adapters_tuple(Tuple& t)
|
||||
{
|
||||
adapters_tuple_t<Tuple> ret;
|
||||
detail::assigner2<std::tuple_size<Tuple>::value - 1>::assign(ret, t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Tuple>
|
||||
class static_aggregate_adapter {
|
||||
private:
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
adapters_array_t<Tuple> adapters_;
|
||||
|
||||
public:
|
||||
static_aggregate_adapter(Tuple* r = nullptr)
|
||||
: adapters_(make_adapters_array(*r))
|
||||
{}
|
||||
|
||||
void count(resp3::node<boost::string_view> const& nd)
|
||||
{
|
||||
if (nd.depth == 1) {
|
||||
if (is_aggregate(nd.data_type))
|
||||
aggregate_size_ = element_multiplicity(nd.data_type) * nd.aggregate_size;
|
||||
else
|
||||
++i_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (--aggregate_size_ == 0)
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
using boost::variant2::visit;
|
||||
|
||||
if (nd.depth == 0) {
|
||||
auto const real_aggr_size = nd.aggregate_size * element_multiplicity(nd.data_type);
|
||||
if (real_aggr_size != std::tuple_size<Tuple>::value)
|
||||
ec = error::incompatible_size;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
visit([&](auto& arg){arg(nd, ec);}, adapters_[i_]);
|
||||
count(nd);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
{
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = detail::static_aggregate_adapter<response_type>;
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
693
aedis/aedis.hpp
693
aedis/aedis.hpp
@@ -7,91 +7,569 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/adapter/adapt.hpp>
|
||||
#include <aedis/adapter/error.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
#include <aedis/sentinel/command.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/error.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
#include <aedis/generic/client.hpp>
|
||||
#include <aedis/generic/serializer.hpp>
|
||||
|
||||
/** \mainpage Documentation
|
||||
\tableofcontents
|
||||
|
||||
\section Overview
|
||||
|
||||
Aedis is low-level redis client library built on top of Boost.Asio
|
||||
that implements communication with a Redis server over its native
|
||||
protocol RESP3. It has first-class support for STL containers and
|
||||
C++ built in types among other things. You will be able to
|
||||
implement your own redis client or use a general purpose provided
|
||||
by the library. For more information about Redis see
|
||||
https://redis.io/
|
||||
|
||||
Aedis is a [Redis](https://redis.io/) client library built on top
|
||||
of [Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
|
||||
that provides simple and efficient communication with a Redis
|
||||
server. Some of its distinctive features are
|
||||
|
||||
@li Support for the latest version of the Redis communication protocol [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md).
|
||||
@li First class support for STL containers and C++ built-in types.
|
||||
@li Serialization and deserialization of your own data types that avoid unnecessary copies.
|
||||
@li Support for Redis [sentinel](https://redis.io/docs/manual/sentinel).
|
||||
@li Sync and async API.
|
||||
|
||||
In addition to that, Aedis provides a high level client that offers the following functionality
|
||||
|
||||
@li Management of message queues.
|
||||
@li Simplified handling of server pushes.
|
||||
@li Zero asymptotic allocations by means of memory reuse.
|
||||
|
||||
If you never heard about Redis the best place to start is on
|
||||
https://redis.io. Now let us have a look at the low-level API.
|
||||
|
||||
\section low-level-api Low-level API
|
||||
|
||||
The low-level API is very useful for simple tasks, for example,
|
||||
assume we want to perform the following steps
|
||||
|
||||
@li Set the value of a Redis key.
|
||||
@li Set the expiration of that key to two seconds.
|
||||
@li Get and return its old value.
|
||||
@li Quit
|
||||
|
||||
The async coroutine-based implementation of the steps above look like
|
||||
|
||||
@code
|
||||
net::awaitable<std::string> set(net::ip::tcp::endpoint ep)
|
||||
{
|
||||
// To make code less verbose
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
|
||||
tcp_socket socket{co_await net::this_coro::executor};
|
||||
co_await socket.async_connect(ep);
|
||||
|
||||
std::string request, read_buffer, response;
|
||||
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::set, "key", "Value", "EX", "2", "get");
|
||||
sr.push(command::quit);
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(read_buffer)); // Hello (ignored).
|
||||
co_await resp3::async_read(socket, dynamic_buffer(read_buffer), adapt(response)); // Set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(read_buffer)); // Quit (ignored)
|
||||
|
||||
co_return response;
|
||||
}
|
||||
@endcode
|
||||
|
||||
The simplicity of the code above makes it self explanatory
|
||||
|
||||
@li Connect to the Redis server.
|
||||
@li Declare a \c std::string to hold the request and add some commands in it with a serializer.
|
||||
@li Write the payload to the socket and read the responses in the same order they were sent.
|
||||
@li Return the response to the user.
|
||||
|
||||
The @c hello command above is always required and must be sent
|
||||
first as it informs we want to communicate over RESP3.
|
||||
|
||||
\subsection requests Requests
|
||||
|
||||
As stated above, request are created by defining a storage object
|
||||
and a serializer that knowns how to convert user data into valid
|
||||
RESP3 wire-format. Redis request are composed of one or more
|
||||
commands (in Redis documentation they are called [pipelines](https://redis.io/topics/pipelining)),
|
||||
which means users can add
|
||||
as many commands to the request as they like, a feature that aids
|
||||
performance.
|
||||
|
||||
The individual commands in a request assume many
|
||||
different forms: with and without keys, variable length arguments,
|
||||
ranges etc. To account for all these variations, the \c
|
||||
serializer class offers some member functions, each of
|
||||
them with a couple of overloads, for example
|
||||
|
||||
@code
|
||||
// Some data to send to Redis.
|
||||
std::string value = "some value";
|
||||
|
||||
std::list<std::string> list {"channel1", "channel2", "channel3"};
|
||||
|
||||
std::map<std::string, mystruct> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}};
|
||||
|
||||
// Command with no arguments
|
||||
sr.push(command::quit);
|
||||
|
||||
// Command with variable lenght arguments.
|
||||
sr.push(command::set, "key", value, "EX", "2");
|
||||
|
||||
// Sends a container, no key.
|
||||
sr.push_range(command::subscribe, list);
|
||||
|
||||
// Same as above but an iterator range.
|
||||
sr.push_range2(command::subscribe, std::cbegin(list), std::cend(list));
|
||||
|
||||
// Sends a container, with key.
|
||||
sr.push_range(command::hset, "key", map);
|
||||
|
||||
// Same as above but as iterator range.
|
||||
sr.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
@endcode
|
||||
|
||||
Once all commands we want to send have been added to the
|
||||
request, we can write it as usual to the socket.
|
||||
|
||||
@code
|
||||
co_await net::async_write(socket, buffer(request));
|
||||
@endcode
|
||||
|
||||
\subsubsection requests-serialization Serialization
|
||||
|
||||
The \c send and \c send_range functions above work with integers
|
||||
e.g. \c int and \c std::string out of the box. To send your own
|
||||
data type defined the \c to_bulk function like this
|
||||
|
||||
@code
|
||||
// Example struct.
|
||||
struct mystruct {
|
||||
// ...
|
||||
};
|
||||
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
// Convert to obj string and call
|
||||
aedis::resp3::to_bulk(to, "Dummy serializaiton string.");
|
||||
}
|
||||
|
||||
std::map<std::string, mystruct> map
|
||||
{ {"key1", {...}}
|
||||
, {"key2", {...}}
|
||||
, {"key3", {...}}};
|
||||
|
||||
db.send_range(command::hset, "key", map);
|
||||
@endcode
|
||||
|
||||
It is quite common to store json string in Redis for example.
|
||||
|
||||
\subsection responses Responses
|
||||
|
||||
To read responses effectively, users must know their RESP3 type,
|
||||
this can be found in the Redis documentation of each command
|
||||
(https://redis.io/commands). For example
|
||||
|
||||
Command | RESP3 type | Documentation
|
||||
---------|-------------------------------------|--------------
|
||||
lpush | Number | https://redis.io/commands/lpush
|
||||
lrange | Array | https://redis.io/commands/lrange
|
||||
set | Simple string, null or blob string | https://redis.io/commands/set
|
||||
get | Blob string | https://redis.io/commands/get
|
||||
smembers | Set | https://redis.io/commands/smembers
|
||||
hgetall | Map | https://redis.io/commands/hgetall
|
||||
|
||||
Once the RESP3 type of a given response is known we can choose a
|
||||
proper C++ data structure to receive it in. Fortunately,
|
||||
this is a simple task for most types, for example
|
||||
|
||||
RESP3 type | C++ | Type
|
||||
---------------|--------------------------------------------------------------|------------------
|
||||
Simple string | \c std::string | Simple
|
||||
Simple error | \c std::string | Simple
|
||||
Blob string | \c std::string, \c std::vector | Simple
|
||||
Blob error | \c std::string, \c std::vector | Simple
|
||||
Number | `long long`, `int`, \c std::string | Simple
|
||||
Null | `boost::optional<T>` | Simple
|
||||
Array | \c std::vector, \c std::list, \c std::array, \c std::deque | Aggregate
|
||||
Map | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
Set | \c std::vector, \c std::set, \c std::unordered_set | Aggregate
|
||||
Push | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
|
||||
Exceptions to this rule are responses that contain nested
|
||||
aggregates or heterogeneous data types, those will be treated
|
||||
later. As of this writing, not all RESP3 types are used by the
|
||||
Redis server, which means in practice users will be concerned with
|
||||
a reduced subset of the RESP3 specification. Now let us see some
|
||||
examples
|
||||
|
||||
@code
|
||||
// To ignore the response.
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt());
|
||||
|
||||
// Read in a std::string e.g. get.
|
||||
std::string str;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(str));
|
||||
|
||||
// Read in a long long e.g. rpush.
|
||||
long long number;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(number));
|
||||
|
||||
// Read in a std::set e.g. smembers.
|
||||
std::set<T, U> set;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(set));
|
||||
|
||||
// Read in a std::map e.g. hgetall.
|
||||
std::map<T, U> set;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(map));
|
||||
|
||||
// Read in a std::unordered_map e.g. hgetall.
|
||||
std::unordered_map<T, U> umap;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
|
||||
|
||||
// Read in a std::vector e.g. lrange.
|
||||
std::vector<T> vec;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(vec));
|
||||
@endcode
|
||||
|
||||
In other words, it is pretty straightforward, just pass the result
|
||||
of \c adapt to the read function and make sure the response RESP3
|
||||
type fits in the type you are calling @c adapter(...) with. All
|
||||
standard C++ containers are supported by aedis.
|
||||
|
||||
\subsubsection Optional
|
||||
|
||||
It is not uncommon for apps to access keys that do not exist or
|
||||
that have already expired in the Redis server, to deal with these
|
||||
cases Aedis provides support for \c boost::optional. To use it,
|
||||
wrap your type around \c boost::optional like this
|
||||
|
||||
@code
|
||||
boost::optional<std::unordered_map<T, U>> umap;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
|
||||
@endcode
|
||||
|
||||
Everything else stays pretty much the same, before accessing data
|
||||
users will have to check (or assert) the optional contains a
|
||||
value.
|
||||
|
||||
\subsubsection heterogeneous_aggregates Heterogeneous aggregates
|
||||
|
||||
There are cases where Redis returns aggregates that
|
||||
contain heterogeneous data, for example, an array that contains
|
||||
integers, strings nested sets etc. Aedis supports reading such
|
||||
aggregates in a \c std::tuple efficiently as long as the they
|
||||
don't contain 2-order nested aggregates e.g. an array that
|
||||
contains an array of arrays. For example, to read the response to
|
||||
a \c hello command we can use the following response type.
|
||||
|
||||
@code
|
||||
using hello_type = std::tuple<
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, int,
|
||||
std::string, int,
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, std::vector<std::string>>;
|
||||
@endcode
|
||||
|
||||
Transactions are another example where this feature is useful, for
|
||||
example, the response to the transaction below
|
||||
|
||||
@code
|
||||
db.send(command::multi);
|
||||
db.send(command::get, "key1");
|
||||
db.send(command::lrange, "key2", 0, -1);
|
||||
db.send(command::hgetall, "key3");
|
||||
db.send(command::exec);
|
||||
@endcode
|
||||
|
||||
can be read in the following way
|
||||
|
||||
@code
|
||||
std::tuple<
|
||||
boost::optional<std::string>, // Response to get
|
||||
boost::optional<std::vector<std::string>>, // Response to lrange
|
||||
boost::optional<std::map<std::string, std::string>> // Response to hgetall
|
||||
> trans;
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore multi
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore get
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore hgetall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(trans));
|
||||
@endcode
|
||||
|
||||
Note that we are not ignoring the response to the commands
|
||||
themselves above but whether they have been successfully queued.
|
||||
Only after @c exec is received Redis will execute them in
|
||||
sequence. The response will then be sent in a single chunk to the
|
||||
client.
|
||||
|
||||
\subsubsection Serialization
|
||||
|
||||
As mentioned in \ref requests-serialization, it is common for
|
||||
users to serialized data before sending it to Redis e.g. json
|
||||
strings, for example
|
||||
|
||||
@code
|
||||
sr.push(command::set, "key", "json-string")
|
||||
sr.push(command::get, "key")
|
||||
@endcode
|
||||
|
||||
For performance and convenience reasons, we may want to avoid
|
||||
receiving the response to the \c get command above as a string
|
||||
just to convert it later to a e.g. deserialized json. To support
|
||||
this, Aedis calls a user defined \c from_string function while
|
||||
parsing the response. In simple terms, define your type
|
||||
|
||||
@code
|
||||
struct mystruct {
|
||||
// struct fields.
|
||||
};
|
||||
@endcode
|
||||
|
||||
and deserialize it from a string in a function \c from_string with
|
||||
the following signature
|
||||
|
||||
@code
|
||||
void from_string(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
|
||||
{
|
||||
// Deserializes p into obj.
|
||||
}
|
||||
@endcode
|
||||
|
||||
After that, you can start receiving data efficiently in the desired
|
||||
types e.g. \c mystruct, \c std::map<std::string, mystruct> etc.
|
||||
|
||||
\subsubsection gen-case The general case
|
||||
|
||||
As already mentioned, there are cases where the response to Redis
|
||||
commands won't fit in the model presented above, some examples are
|
||||
|
||||
@li Commands (like \c set) whose response don't have a fixed
|
||||
RESP3 type. Expecting an \c int and receiving a blob string
|
||||
will result in error.
|
||||
@li RESP3 responses that contain three levels of (nested) aggregates can't be
|
||||
read in STL containers.
|
||||
@li Transactions with a dynamic number of commands can't be read in a \c std::tuple.
|
||||
|
||||
To deal with these cases Aedis provides the \c resp3::node
|
||||
type, that is the most general form of an element in a response,
|
||||
be it a simple RESP3 type or an aggregate. It is defined like this
|
||||
|
||||
@code
|
||||
template <class String>
|
||||
struct node {
|
||||
// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
// The number of elements of an aggregate (or 1 for simple data).
|
||||
std::size_t aggregate_size;
|
||||
|
||||
// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
// The actual data. For aggregate types this is always empty.
|
||||
String value;
|
||||
};
|
||||
@endcode
|
||||
|
||||
Any response to a Redis command can be received in a \c
|
||||
std::vector<node<std::string>>. The vector can be seen as a
|
||||
pre-order view of the response tree
|
||||
(https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
Using it is no different that using other types
|
||||
|
||||
@code
|
||||
// Receives any RESP3 simple data type.
|
||||
node<std::string> resp;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
|
||||
// Receives any RESP3 simple or aggregate data type.
|
||||
std::vector<node<std::string>> resp;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
@endcode
|
||||
|
||||
For example, suppose we want to retrieve a hash data structure
|
||||
from Redis with \c hgetall, some of the options are
|
||||
|
||||
@li \c std::vector<node<std::string>: Works always.
|
||||
@li \c std::vector<std::string>: Efficient and flat, all elements as string.
|
||||
@li \c std::map<std::string, std::string>: Efficient if you need the data as a \c std::map
|
||||
@li \c std::map<U, V>: Efficient if you are storing serialized data. Avoids temporaries and requires \c from_string for \c U and \c V.
|
||||
|
||||
In addition to the above users can also use unordered versions of the containers. The same reasoning also applies to sets e.g. \c smembers.
|
||||
|
||||
\subsubsection low-level-adapters Adapters
|
||||
|
||||
Users that are not satisfied with any of the options above can
|
||||
write their own adapters very easily. For example, the adapter below
|
||||
can be used to print incoming data to the screen.
|
||||
|
||||
@code
|
||||
auto adapter = [](resp3::node<boost::string_view> const& nd, boost::system::error_code&)
|
||||
{
|
||||
std::cout << nd << std::endl;
|
||||
};
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapter);
|
||||
@endcode
|
||||
|
||||
See more in the \ref examples section.
|
||||
|
||||
\section high-level-api High-level API
|
||||
|
||||
It requires a lot of further work to make use of many important features
|
||||
of the Redis server while using the low-level API, for example
|
||||
|
||||
@li \b Server \b pushes: Short lived connections can't handle server pushes (e.g. https://redis.io/topics/client-side-caching and https://redis.io/topics/notifications).
|
||||
@li \b Pubsub: Just like server pushes, to use Redis pubsub users need long lasting connections (https://redis.io/topics/pubsub).
|
||||
@li \b Performance: Keep opening and closing connections impact performance.
|
||||
@li \b Pipeline: Code such as shown in \ref low-level-api don't
|
||||
support pipelines well since it can only send a fixed number of
|
||||
commands at time. It misses important optimization opportunities
|
||||
(https://redis.io/topics/pipelining).
|
||||
|
||||
To avoid these drawbacks users will address the points above
|
||||
reinventing the high-level API here and there over and over again,
|
||||
to prevent that from happening Aedis provides its own. The general
|
||||
form of a program that uses the high-level api looks like this
|
||||
|
||||
@code
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client<net::ip::tcp::socket> db{ioc.get_executor()};
|
||||
receiver recv;
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ ... });
|
||||
|
||||
// Pass db around to other objects so we can send commands.
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@endcode
|
||||
|
||||
The only thing users have to care about is with the implementation
|
||||
of the \c receiver class, everything else will be performed
|
||||
automatically by the client class. The general form of a receiver
|
||||
looks like this
|
||||
|
||||
@code
|
||||
class receiver {
|
||||
public:
|
||||
// Called when a new chunck of user data becomes available.
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec);
|
||||
|
||||
// Called when a response becomes available.
|
||||
void on_read(command cmd);
|
||||
|
||||
// Called when a request has been writen to the socket.
|
||||
void on_write(std::size_t n);
|
||||
|
||||
// Called when a server push is received.
|
||||
void on_push();
|
||||
};
|
||||
@endcode
|
||||
|
||||
Sending commands is also similar to what has been discussed before.
|
||||
|
||||
@code
|
||||
void foo(client<net::ip::tcp::socket>& db)
|
||||
{
|
||||
db.send(command::ping, "O rato roeu a roupa do rei de Roma");
|
||||
db.send(command::incr, "counter");
|
||||
db.send(command::set, "key", "Três pratos de trigo para três tigres");
|
||||
db.send(command::get, "key");
|
||||
...
|
||||
}
|
||||
@endcode
|
||||
|
||||
The \c send functions in this case will add commands to the output
|
||||
queue and send them only if there is no pending response of a
|
||||
previously sent command. This is so because RESP3 is a
|
||||
request/response protocol, which means clients must wait for the
|
||||
response to a command before proceeding with the next one.
|
||||
|
||||
\subsection high-level-responses Responses
|
||||
|
||||
Aedis also provides some facilities to use use custom responses with the
|
||||
high-level API. Assume for example you have many different custom
|
||||
response types \c T1, \c T2 etc, a receiver that makes use of this looks
|
||||
like
|
||||
|
||||
@code
|
||||
using responses_tuple_type = std::tuple<T1, T2, T3>;
|
||||
using adapters_tuple_type = adapters_t<responses_tuple_type>;
|
||||
|
||||
class myreceiver {
|
||||
public:
|
||||
myreceiver(...) : adapters_(make_adapters_tuple(resps_)) , {}
|
||||
|
||||
void
|
||||
on_resp3( command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
// Direct the responses to the desired adapter.
|
||||
switch (cmd) {
|
||||
case cmd1: adapter::get<T1>(adapters_)(nd, ec);
|
||||
case cmd2: adapter::get<T2>(adapters_)(nd, ec);
|
||||
case cmd3: adapter::get<T2>(adapters_)(nd, ec);
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case cmd1: // Data on std::get<T1>(resps_); break;
|
||||
case cmd2: // Data on std::get<T2>(resps_); break;
|
||||
case cmd3: // Data on std::get<T3>(resps_); break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n) { ... }
|
||||
|
||||
void on_push() { ... }
|
||||
|
||||
private:
|
||||
responses_tuple_type resps_;
|
||||
adapters_tuple_type adapters_;
|
||||
};
|
||||
@endcode
|
||||
|
||||
\section examples Examples
|
||||
|
||||
\b Basics: Focuses on small examples that show basic usage of
|
||||
the library.
|
||||
To better fix what has been said above, users should have a look at some simple examples.
|
||||
|
||||
- intro.cpp: A good starting point. Some commands are sent to the
|
||||
Redis server and the responses are printed to screen.
|
||||
\b Low \b level \b API
|
||||
|
||||
- transaction.cpp: Shows how to read the responses to a trasaction
|
||||
efficiently. See also https://redis.io/topics/transactions.
|
||||
@li low_level/sync_intro.cpp: Shows how to use the Aedis synchronous api.
|
||||
@li low_level/async_intro.cpp: Show how to use the low level async api.
|
||||
@li low_level/subscriber.cpp: Shows how channel subscription works at the low level.
|
||||
@li low_level/adapter.cpp: Shows how to write a response adapter that prints to the screen, see \ref low-level-adapters.
|
||||
|
||||
- multipurpose_response.cpp: Shows how to read any responses to
|
||||
Redis commands, including nested aggegagtes.
|
||||
\b High \b level \b API
|
||||
|
||||
- subscriber.cpp: Shows how channel subscription works at a low
|
||||
level. See also https://redis.io/topics/pubsub.
|
||||
@li high_level/intro.cpp: Some commands are sent to the Redis server and the responses are printed to screen.
|
||||
@li high_level/aggregates.cpp: Shows how receive RESP3 aggregate data types in a general way.
|
||||
@li high_level/stl_containers.cpp: Shows how to read responses in STL containers.
|
||||
@li high_level/serialization.cpp: Shows how to de/serialize your own data types.
|
||||
@li high_level/subscriber.cpp: Shows how channel subscription works at a high level. See also https://redis.io/topics/pubsub.
|
||||
|
||||
- sync.cpp: Shows hot to use the Aedis synchronous api.
|
||||
\b Asynchronous \b Servers
|
||||
|
||||
- key_expiration.cpp: Shows how to use \c std::optional to deal
|
||||
with keys that may have expired or do not exist.
|
||||
|
||||
\b STL \b Containers: Many of the Redis data structures can be
|
||||
directly translated in to STL containers, below you will find some
|
||||
example code. For a list of Redis data types see
|
||||
https://redis.io/topics/data-types.
|
||||
|
||||
- hashes.cpp: Shows how to read Redis hashes in a \c std::map, \c
|
||||
std::unordered_map and \c std::vector.
|
||||
|
||||
- lists.cpp: Shows how to read Redis lists in \c std::list,
|
||||
\c std::deque, \c std::vector. It also illustrates basic serialization.
|
||||
|
||||
- sets.cpp: Shows how to read Redis sets in a \c std::set, \c
|
||||
std::unordered_set and \c std::vector.
|
||||
|
||||
\b Customization \b points: Shows how de/serialize user types
|
||||
avoiding copies. This is particularly useful for low latency
|
||||
applications that want to avoid unneeded copies, for examples when
|
||||
storing json strings in Redis keys.
|
||||
|
||||
- serialization.cpp: Shows how to de/serialize your own
|
||||
non-aggregate data-structures.
|
||||
|
||||
- response_adapter.cpp: Customization point for users that want to
|
||||
de/serialize their own data-structures like containers for example.
|
||||
|
||||
\b Asynchronous \b servers: Contains some non-trivial examples
|
||||
servers that interact with users and Redis asynchronously over
|
||||
long lasting connections using a higher level API.
|
||||
|
||||
- multipurpose_client.cpp: Shows how to use and experimental high
|
||||
level redis client that keeps a long lasting connections to a
|
||||
redis server. This is the starting point for the next examples.
|
||||
|
||||
- echo_server.cpp: Shows the basic principles behind asynchronous
|
||||
communication with a database in an asynchronous server. In this
|
||||
case, the server is a proxy between the user and Redis.
|
||||
|
||||
- chat_room.cpp: Shows how to build a scalable chat room that
|
||||
scales to millions of users.
|
||||
@li high_level/echo_server.cpp: Shows the basic principles behind asynchronous communication with a database in an asynchronous server.
|
||||
@li high_level/chat_room.cpp: Shows how to build a scalable chat room that scales to millions of users.
|
||||
|
||||
\section using-aedis Using Aedis
|
||||
|
||||
@@ -99,43 +577,53 @@
|
||||
|
||||
- Boost 1.78 or greater.
|
||||
- Unix Shell and Make.
|
||||
- Compiler with C++20 coroutine support e.g. GCC 10 or greater.
|
||||
- C++14. Some examples require C++20 with coroutine support.
|
||||
- Redis server.
|
||||
|
||||
|
||||
Some examples will also require interaction with
|
||||
|
||||
- redis-cli: used in one example.
|
||||
- redis-cli: Used in one example.
|
||||
- Redis Sentinel Server: used in some examples.
|
||||
|
||||
Aedis has been tested with the following compilers
|
||||
|
||||
- Tested with gcc: 7.5.0, 8.4.0, 9.3.0, 10.3.0.
|
||||
- Tested with clang: 11.0.0, 10.0.0, 9.0.1, 8.0.1, 7.0.1.
|
||||
|
||||
\subsection Installation
|
||||
|
||||
Start by downloading and configuring the library
|
||||
|
||||
|
||||
The first thing to do is to download and unpack Aedis
|
||||
|
||||
```
|
||||
# Download the libray on github.
|
||||
$ wget https://github.com/mzimbres/aedis/release-path # TODO
|
||||
# Download the latest release on github
|
||||
$ wget https://github.com/mzimbres/aedis/releases
|
||||
|
||||
# Uncompress the tarball and cd into the dir
|
||||
$ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0
|
||||
|
||||
# Run configure with appropriate C++ flags and your boost
|
||||
# installation, for example # You may also have to use
|
||||
# -Wno-subobject-linkage on gcc.
|
||||
$ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall"\
|
||||
./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib
|
||||
|
||||
```
|
||||
|
||||
To install the library run
|
||||
$ tar -xzvf aedis-version.tar.gz
|
||||
```
|
||||
|
||||
If you can't use \c configure and \c make (e.g. Windows users)
|
||||
you can already add the directory where you unpacked aedis to the
|
||||
include directories in your project, otherwise run
|
||||
|
||||
```
|
||||
# See configure --help for all options.
|
||||
$ ./configure --prefix=/opt/aedis-version --with-boost=/opt/boost_1_78_0
|
||||
|
||||
# Install Aedis in the path specified in --prefix
|
||||
$ sudo make install
|
||||
|
||||
```
|
||||
|
||||
At this point you can start using Aedis. To build the examples and
|
||||
test you can also run
|
||||
and include the following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
```
|
||||
|
||||
in exactly one source file in your applications. At this point you
|
||||
can start using Aedis. To build the examples and run the tests run
|
||||
|
||||
```
|
||||
# Build aedis examples.
|
||||
@@ -144,18 +632,7 @@
|
||||
# Test aedis in your machine.
|
||||
$ make check
|
||||
```
|
||||
|
||||
Finally you will have to include the following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
```
|
||||
in exactly one source file in your applications.
|
||||
|
||||
Windows users can use aedis by either adding the project root
|
||||
directory to their include path or manually copying to another
|
||||
location.
|
||||
|
||||
|
||||
\subsection Developers
|
||||
|
||||
To generate the build system run
|
||||
@@ -164,15 +641,15 @@
|
||||
$ autoreconf -i
|
||||
```
|
||||
|
||||
After that you will have a config in the project dir that you can
|
||||
run as explained above, for example, to use a compiler other that
|
||||
the system compiler use
|
||||
After that you will have a configure script
|
||||
that you can run as explained above, for example, to use a
|
||||
compiler other that the system compiler run
|
||||
|
||||
```
|
||||
CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\
|
||||
CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\
|
||||
CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ...
|
||||
$ CC=/opt/gcc-10.2.0/bin/gcc-10.2.0 CXX=/opt/gcc-10.2.0/bin/g++-10.2.0 CXXFLAGS="-g -Wall -Werror" ./configure ...
|
||||
$ make distcheck
|
||||
```
|
||||
|
||||
\section Referece
|
||||
|
||||
See \subpage any.
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// TODO: Remove this.
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace net = boost::asio;
|
||||
}
|
||||
|
||||
374
aedis/generic/client.hpp
Normal file
374
aedis/generic/client.hpp
Normal file
@@ -0,0 +1,374 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/generic/detail/client_ops.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
// TODO: What to do if users send a discard command not contained in a
|
||||
// transaction. The client object will try to pop the queue until a
|
||||
// multi is found.
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
|
||||
/** \brief A high level Redis client.
|
||||
* \ingroup any
|
||||
*
|
||||
* This class represents a connection to the Redis server. Some of
|
||||
* its most important features are
|
||||
*
|
||||
* 1. Automatic management of commands. The implementation will send
|
||||
* commands and read responses automatically for the user.
|
||||
* 2. Memory reuse. Dynamic memory allocations will decrease with time.
|
||||
*
|
||||
* For more details, please see the documentation of each individual
|
||||
* function.
|
||||
*/
|
||||
template <class AsyncReadWriteStream, class Command>
|
||||
class client {
|
||||
public:
|
||||
using stream_type = AsyncReadWriteStream;
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
using default_completion_token_type = boost::asio::default_completion_token_t<executor_type>;
|
||||
|
||||
/** \brief Constructor.
|
||||
*
|
||||
* \param ex The executor.
|
||||
*/
|
||||
client(boost::asio::any_io_executor ex)
|
||||
: socket_{ex}
|
||||
, timer_{ex}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
send(Command::hello, 3);
|
||||
}
|
||||
|
||||
/// Returns the executor.
|
||||
auto get_executor() {return socket_.get_executor();}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command to send.
|
||||
* @param args Arguments to commands.
|
||||
*/
|
||||
template <class... Ts>
|
||||
void send(Command cmd, Ts const&... args)
|
||||
{
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
serializer<std::string> sr(requests_);
|
||||
auto const before = requests_.size();
|
||||
sr.push(cmd, args...);
|
||||
auto const after = requests_.size();
|
||||
assert(after - before != 0);
|
||||
req_info_.front().size += after - before;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.push_back(cmd);
|
||||
++req_info_.front().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param key The key the commands refers to
|
||||
* @param begin Begin of the range.
|
||||
* @param end End of the range.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void send_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
serializer<std::string> sr(requests_);
|
||||
auto const before = requests_.size();
|
||||
sr.push_range2(cmd, key, begin, end);
|
||||
auto const after = requests_.size();
|
||||
assert(after - before != 0);
|
||||
req_info_.front().size += after - before;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.push_back(cmd);
|
||||
++req_info_.front().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param begin Begin of the range.
|
||||
* @param end End of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void send_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
serializer<std::string> sr(requests_);
|
||||
auto const before = requests_.size();
|
||||
sr.push_range2(cmd, begin, end);
|
||||
auto const after = requests_.size();
|
||||
assert(after - before != 0);
|
||||
req_info_.front().size += after - before;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.push_back(cmd);
|
||||
++req_info_.front().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param key The key the commands refers to.
|
||||
* @param range Range of elements to send.
|
||||
*/
|
||||
template <class Key, class Range>
|
||||
void send_range(Command cmd, Key const& key, Range const& range)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
send_range2(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param range End of the range.
|
||||
*/
|
||||
template <class Range>
|
||||
void send_range(Command cmd, Range const& range)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
send_range2(cmd, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Starts communication with the Redis server asynchronously.
|
||||
*
|
||||
* This class performs the following steps
|
||||
*
|
||||
* @li Connect to the endpoint passed in the function parameter.
|
||||
* @li Start the async read operation that keeps reading responses to commands and server pushes.
|
||||
* @li Start the async write operation that keeps sending commands to Redis.
|
||||
*
|
||||
* \param recv The receiver (see below)
|
||||
* \param ep The address of the Redis server.
|
||||
* \param token The completion token (ASIO jargon)
|
||||
*
|
||||
* The receiver is a class that privides the following member functions
|
||||
*
|
||||
* @code
|
||||
* class receiver {
|
||||
* public:
|
||||
* // Called when a new chunck of user data becomes available.
|
||||
* void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec);
|
||||
*
|
||||
* // Called when a response becomes available.
|
||||
* void on_read(command cmd);
|
||||
*
|
||||
* // Called when a request has been writen to the socket.
|
||||
* void on_write(std::size_t n);
|
||||
*
|
||||
* // Called when a server push is received.
|
||||
* void on_push();
|
||||
* };
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type
|
||||
>
|
||||
auto
|
||||
async_run(
|
||||
Receiver& recv,
|
||||
boost::asio::ip::tcp::endpoint ep = {boost::asio::ip::make_address("127.0.0.1"), 6379},
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
endpoint_ = ep;
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(run_op<client, Receiver>{this, &recv}, token, socket_, timer_);
|
||||
}
|
||||
|
||||
private:
|
||||
template <class T, class U, class V> friend struct read_op;
|
||||
template <class T, class U> friend struct writer_op;
|
||||
template <class T, class U> friend struct read_write_op;
|
||||
template <class T, class U> friend struct run_op;
|
||||
|
||||
struct request_info {
|
||||
// Request size in bytes.
|
||||
std::size_t size = 0;
|
||||
|
||||
// The number of commands it contains excluding commands that
|
||||
// have push types as responses, see has_push_response.
|
||||
std::size_t cmds = 0;
|
||||
};
|
||||
|
||||
// Buffer used in the read operations.
|
||||
std::string read_buffer_;
|
||||
|
||||
// Requests payload.
|
||||
std::string requests_;
|
||||
|
||||
// The commands contained in the requests.
|
||||
std::vector<Command> commands_;
|
||||
|
||||
// Info about the requests.
|
||||
std::vector<request_info> req_info_;
|
||||
|
||||
// The stream.
|
||||
stream_type socket_;
|
||||
|
||||
// Timer used to inform the write coroutine that it can write the
|
||||
// next message in the output queue.
|
||||
boost::asio::steady_timer timer_;
|
||||
|
||||
// Redis endpoint.
|
||||
boost::asio::ip::tcp::endpoint endpoint_;
|
||||
|
||||
bool stop_writer_ = false;
|
||||
|
||||
/* Prepares the back of the queue to receive further commands.
|
||||
*
|
||||
* If true is returned the request in the front of the queue can be
|
||||
* sent to the server. See async_write_some.
|
||||
*/
|
||||
bool prepare_next()
|
||||
{
|
||||
if (req_info_.empty()) {
|
||||
req_info_.push_back({});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req_info_.front().size == 0) {
|
||||
// It has already been written and we are waiting for the
|
||||
// responses.
|
||||
req_info_.push_back({});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true when the next request can be writen.
|
||||
bool on_cmd(Command)
|
||||
{
|
||||
// TODO: If the response to a discard is received we have to
|
||||
// remove all commands up until multi.
|
||||
|
||||
assert(!req_info_.empty());
|
||||
assert(!commands_.empty());
|
||||
|
||||
commands_.erase(std::begin(commands_));
|
||||
|
||||
if (--req_info_.front().cmds != 0)
|
||||
return false;
|
||||
|
||||
req_info_.erase(std::begin(req_info_));
|
||||
|
||||
return !req_info_.empty();
|
||||
}
|
||||
|
||||
// Reads messages asynchronously.
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_reader(
|
||||
Receiver* recv,
|
||||
CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(read_op<client, Receiver, Command>{this, recv}, token, socket_);
|
||||
}
|
||||
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_writer(
|
||||
Receiver* recv,
|
||||
CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(writer_op<client, Receiver>{this, recv}, token, socket_, timer_);
|
||||
}
|
||||
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_read_write(
|
||||
Receiver* recv,
|
||||
CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(read_write_op<client, Receiver>{this, recv}, token, socket_, timer_);
|
||||
}
|
||||
};
|
||||
|
||||
} // generic
|
||||
} // aedis
|
||||
205
aedis/generic/detail/client_ops.hpp
Normal file
205
aedis/generic/detail/client_ops.hpp
Normal file
@@ -0,0 +1,205 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
template <class Client, class Receiver>
|
||||
struct run_op {
|
||||
Client* cli;
|
||||
Receiver* recv_;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) {
|
||||
yield cli->socket_.async_connect(cli->endpoint_, std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
yield cli->async_read_write(recv_, std::move(self));
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client, class Receiver>
|
||||
struct read_write_op {
|
||||
Client* cli;
|
||||
Receiver* recv;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
)
|
||||
{
|
||||
reenter (coro) {
|
||||
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return cli->async_writer(recv, token);},
|
||||
[this](auto token) { return cli->async_reader(recv, token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one_error(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1); break;
|
||||
case 1: self.complete(ec2); break;
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Consider limiting the size of the pipelines by spliting that last
|
||||
// one in two if needed.
|
||||
template <class Client, class Receiver>
|
||||
struct writer_op {
|
||||
Client* cli;
|
||||
Receiver* recv;
|
||||
std::size_t size;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro) for (;;) {
|
||||
|
||||
boost::ignore_unused(n);
|
||||
|
||||
assert(!cli->req_info_.empty());
|
||||
assert(cli->req_info_.front().size != 0);
|
||||
assert(!cli->requests_.empty());
|
||||
|
||||
yield
|
||||
boost::asio::async_write(
|
||||
cli->socket_,
|
||||
boost::asio::buffer(cli->requests_.data(), cli->req_info_.front().size),
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
cli->socket_.close();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
size = cli->req_info_.front().size;
|
||||
|
||||
cli->requests_.erase(0, cli->req_info_.front().size);
|
||||
cli->req_info_.front().size = 0;
|
||||
|
||||
if (cli->req_info_.front().cmds == 0)
|
||||
cli->req_info_.erase(std::begin(cli->req_info_));
|
||||
|
||||
recv->on_write(size);
|
||||
|
||||
yield cli->timer_.async_wait(std::move(self));
|
||||
|
||||
if (cli->stop_writer_) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client, class Receiver, class Command>
|
||||
struct read_op {
|
||||
Client* cli;
|
||||
Receiver* recv;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
// Consider moving this variables to the client to spare some
|
||||
// memory in the competion handler.
|
||||
resp3::type t = resp3::type::invalid;
|
||||
Command cmd = Command::invalid;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro) for (;;) {
|
||||
|
||||
boost::ignore_unused(n);
|
||||
|
||||
if (cli->read_buffer_.empty()) {
|
||||
yield
|
||||
boost::asio::async_read_until(
|
||||
cli->socket_,
|
||||
boost::asio::dynamic_buffer(cli->read_buffer_),
|
||||
"\r\n",
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
cli->stop_writer_ = true;
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!cli->read_buffer_.empty());
|
||||
t = resp3::detail::to_type(cli->read_buffer_.front());
|
||||
cmd = Command::invalid;
|
||||
if (t != resp3::type::push) {
|
||||
assert(!cli->commands_.empty());
|
||||
cmd = cli->commands_.front();
|
||||
}
|
||||
|
||||
yield
|
||||
resp3::async_read(
|
||||
cli->socket_,
|
||||
boost::asio::dynamic_buffer(cli->read_buffer_),
|
||||
[p = recv, c = cmd](resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable {p->on_resp3(c, nd, ec);},
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
cli->stop_writer_ = true;
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (t == resp3::type::push) {
|
||||
recv->on_push();
|
||||
} else {
|
||||
if (cli->on_cmd(cmd))
|
||||
cli->timer_.cancel_one();
|
||||
|
||||
recv->on_read(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // generic
|
||||
} // aedis
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/detail/composer.hpp>
|
||||
#include <boost/hana.hpp>
|
||||
#include <aedis/resp3/compose.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace generic {
|
||||
|
||||
/** @brief Creates a Redis request from user data.
|
||||
* \ingroup any
|
||||
@@ -32,21 +33,19 @@ namespace resp3 {
|
||||
* co_await async_write(socket, buffer(request));
|
||||
* @endcode
|
||||
*
|
||||
* \tparam Storage The storage type. This is currently a \c std::string.
|
||||
* \tparam Storage The storage type e.g \c std::string.
|
||||
* \tparam Command The command to serialize.
|
||||
*
|
||||
* \remarks Non-string types will be converted to string by using \c
|
||||
* to_string, which must be made available over ADL.
|
||||
*/
|
||||
|
||||
// NOTE: Consider adding an overload for containers.
|
||||
//
|
||||
// TODO: Should we detect any std::pair or tuple in the type in the parameter
|
||||
// pack to calculate the header size correctly?
|
||||
// Consider detecting tuples in the type in the parameter pack to
|
||||
// calculate the header size correctly.
|
||||
//
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
template <class Storage, class Command>
|
||||
template <class Storage>
|
||||
class serializer {
|
||||
private:
|
||||
Storage* request_;
|
||||
@@ -76,14 +75,17 @@ public:
|
||||
* \param args The arguments of the Redis command.
|
||||
*
|
||||
*/
|
||||
template <class... Ts>
|
||||
template <class Command, class... Ts>
|
||||
void push(Command cmd, Ts const&... args)
|
||||
{
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
detail::add_header(*request_, 1 + pack_size);
|
||||
using boost::hana::for_each;
|
||||
using boost::hana::make_tuple;
|
||||
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
(detail::add_bulk(*request_, args), ...);
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
resp3::add_header(*request_, 1 + pack_size);
|
||||
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
resp3::add_bulk(*request_, make_tuple(args...));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
@@ -98,7 +100,7 @@ public:
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
* req.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
@@ -106,19 +108,22 @@ public:
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void push_range(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
template <class Command, class Key, class ForwardIterator>
|
||||
void push_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 2 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
detail::add_bulk(*request_, key);
|
||||
resp3::add_header(*request_, 2 + size * distance);
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
resp3::add_bulk(*request_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
resp3::add_bulk(*request_, *begin);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
@@ -138,20 +143,58 @@ public:
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
template <class Command, class ForwardIterator>
|
||||
void push_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 1 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
resp3::add_header(*request_, 1 + size * distance);
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
resp3::add_bulk(*request_, *begin);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Similar to the range version.
|
||||
*/
|
||||
template <class Command, class Key, class Range>
|
||||
void push_range(Command cmd, Key const& key, Range const& range)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Similar to the range version.
|
||||
*/
|
||||
template <class Command, class Range>
|
||||
void push_range(Command cmd, Range const& range)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, begin(range), end(range));
|
||||
}
|
||||
};
|
||||
|
||||
} // resp3
|
||||
/** \brief Creates a serializer.
|
||||
* \ingroup any
|
||||
* \param storage The string.
|
||||
*/
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
serializer<std::basic_string<CharT, Traits, Allocator>>
|
||||
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
|
||||
{
|
||||
return serializer<std::basic_string<CharT, Traits, Allocator>>(storage);
|
||||
}
|
||||
|
||||
} // generic
|
||||
} // aedis
|
||||
@@ -10,8 +10,6 @@
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace redis {
|
||||
|
||||
@@ -74,7 +72,7 @@ enum class command {
|
||||
decrby,
|
||||
/// https://redis.io/commands/del
|
||||
del,
|
||||
/// https://redis.io/commands/discard
|
||||
/// https://redis.io/commands/discard (not supported yet)
|
||||
discard,
|
||||
/// https://redis.io/commands/dump
|
||||
dump,
|
||||
@@ -432,8 +430,8 @@ enum class command {
|
||||
zscore,
|
||||
/// https://redis.io/commands/zunionstore
|
||||
zunionstore,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
/// Invalid command.
|
||||
invalid
|
||||
};
|
||||
|
||||
/** \brief Converts a command to a string
|
||||
@@ -456,16 +454,5 @@ std::ostream& operator<<(std::ostream& os, command c);
|
||||
*/
|
||||
bool has_push_response(command cmd);
|
||||
|
||||
/** \brief Creates a serializer for a \c std::string.
|
||||
* \ingroup any
|
||||
* \param storage The string.
|
||||
*/
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
resp3::serializer<std::string, command>
|
||||
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
|
||||
{
|
||||
return resp3::serializer<std::basic_string<CharT, Traits, Allocator>, command>(storage);
|
||||
}
|
||||
|
||||
} // redis
|
||||
} // aedis
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
/** \brief A high level redis client.
|
||||
* \ingroup any
|
||||
*
|
||||
* This Redis client keeps a connection to the database open and
|
||||
* uses it for all communication with Redis. For examples on how to
|
||||
* use see the examples chat_room.cpp, echo_server.cpp and redis_client.cpp.
|
||||
*
|
||||
* \remarks This class reuses its internal buffers for requests and
|
||||
* for reading Redis responses. With time it will allocate less and
|
||||
* less.
|
||||
*/
|
||||
class client : public std::enable_shared_from_this<client> {
|
||||
public:
|
||||
/** \brief The extended response adapter type.
|
||||
*
|
||||
* The difference between the adapter and extended_adapter
|
||||
* concepts is that the extended get a command redis::parameter.
|
||||
*/
|
||||
using extented_adapter_type = std::function<void(redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&)>;
|
||||
|
||||
/// The type of the message callback.
|
||||
using on_message_type = std::function<void(std::error_code ec, redis::command)>;
|
||||
|
||||
/// The type of the socket used by the client.
|
||||
//using socket_type = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using socket_type = net::ip::tcp::socket;
|
||||
|
||||
private:
|
||||
struct request_info {
|
||||
// Request size in bytes.
|
||||
std::size_t size = 0;
|
||||
|
||||
// The number of commands it contains excluding commands that
|
||||
// have push types as responses, see has_push_response.
|
||||
std::size_t cmds = 0;
|
||||
};
|
||||
|
||||
// Requests payload.
|
||||
std::string requests_;
|
||||
|
||||
// The commands contained in the requests.
|
||||
std::queue<redis::command> commands_;
|
||||
|
||||
// Info about the requests.
|
||||
std::queue<request_info> req_info_;
|
||||
|
||||
// The stream.
|
||||
socket_type socket_;
|
||||
|
||||
// Timer used to inform the write coroutine that it can write the
|
||||
// next message in the output queue.
|
||||
net::steady_timer timer_;
|
||||
|
||||
// Response adapter.
|
||||
extented_adapter_type extended_adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {};
|
||||
|
||||
// Message callback.
|
||||
on_message_type on_msg_ = [](std::error_code ec, redis::command) {};
|
||||
|
||||
// Set when the writer coroutine should stop.
|
||||
bool stop_writer_ = false;
|
||||
|
||||
// A coroutine that keeps reading the socket. When a message
|
||||
// arrives it calls on_message.
|
||||
net::awaitable<void> reader();
|
||||
|
||||
// Write coroutine. It is kept suspended until there are messages
|
||||
// to be sent.
|
||||
net::awaitable<void> writer();
|
||||
|
||||
/* Prepares the back of the queue to receive further commands.
|
||||
*
|
||||
* If true is returned the request in the front of the queue can be
|
||||
* sent to the server. See async_write_some.
|
||||
*/
|
||||
bool prepare_next();
|
||||
|
||||
public:
|
||||
/** \brief Client constructor.
|
||||
*
|
||||
* Constructos the client from an executor.
|
||||
*
|
||||
* \param ex The executor.
|
||||
*/
|
||||
client(net::any_io_executor ex);
|
||||
|
||||
/// Returns the executor used for I/O with Redis.
|
||||
auto get_executor() {return socket_.get_executor();}
|
||||
|
||||
/** \brief Starts communication with Redis.
|
||||
*
|
||||
* This functions will send the hello command to Redis and spawn
|
||||
* the read and write coroutines.
|
||||
*
|
||||
* \param socket A socket that is connected to redis.
|
||||
*
|
||||
* \returns This function returns an awaitable on which users should \c
|
||||
* co_await. When the communication with Redis is lost the
|
||||
* coroutine will finally co_return.
|
||||
*/
|
||||
net::awaitable<void> engage(socket_type socket);
|
||||
|
||||
/** \brief Adds a command to the command queue.
|
||||
*
|
||||
* \sa serializer.hpp
|
||||
*/
|
||||
template <class... Ts>
|
||||
void send(redis::command cmd, Ts const&... args);
|
||||
|
||||
/// Sets an extended response adapter.
|
||||
void set_extended_adapter(extented_adapter_type adapter);
|
||||
|
||||
/// Sets the message callback;
|
||||
void set_msg_callback(on_message_type on_msg);
|
||||
};
|
||||
|
||||
template <class... Ts>
|
||||
void client::send(redis::command cmd, Ts const&... args)
|
||||
{
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
auto sr = redis::make_serializer(requests_);
|
||||
auto const before = std::size(requests_);
|
||||
sr.push(cmd, args...);
|
||||
auto const after = std::size(requests_);
|
||||
req_info_.front().size += after - before;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.emplace(cmd);
|
||||
++req_info_.front().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,200 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
client::client(net::any_io_executor ex)
|
||||
: socket_{ex}
|
||||
, timer_{ex}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
net::awaitable<void> client::reader()
|
||||
{
|
||||
// Writes and reads continuosly from the socket.
|
||||
for (std::string buffer;;) {
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the writer is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
boost::system::error_code ec;
|
||||
co_await
|
||||
net::async_write(
|
||||
socket_,
|
||||
net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break; // We must await the responses.
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
|
||||
do { // Keeps reading while there are no messages queued waiting to be sent.
|
||||
do { // Consumes the responses to all commands in the request.
|
||||
boost::system::error_code ec;
|
||||
auto const t =
|
||||
co_await async_read_type(socket_, net::dynamic_buffer(buffer),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
if (t == type::push) {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, redis::command::unknown);
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
} else {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, commands_.front());
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
commands_.pop();
|
||||
--req_info_.front().cmds;
|
||||
}
|
||||
|
||||
} while (!std::empty(req_info_) && req_info_.front().cmds != 0);
|
||||
|
||||
// We may exit the loop above either because we are done
|
||||
// with the response or because we received a server push
|
||||
// while the queue was empty so we have to check before
|
||||
// poping..
|
||||
if (!std::empty(req_info_))
|
||||
req_info_.pop();
|
||||
|
||||
} while (std::empty(req_info_));
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> client::writer()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
while (socket_.is_open()) {
|
||||
ec = {};
|
||||
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
if (stop_writer_)
|
||||
co_return;
|
||||
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the reader is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
ec = {};
|
||||
co_await net::async_write(
|
||||
socket_, net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
// What should we do here exactly? Closing the socket will
|
||||
// cause the reader coroutine to return so that the engage
|
||||
// coroutine returns to the user.
|
||||
socket_.close();
|
||||
co_return;
|
||||
}
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break;
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool client::prepare_next()
|
||||
{
|
||||
if (std::empty(req_info_)) {
|
||||
req_info_.push({});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req_info_.front().size == 0) {
|
||||
// It has already been written and we are waiting for the
|
||||
// responses.
|
||||
req_info_.push({});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void client::set_extended_adapter(extented_adapter_type adapter)
|
||||
{
|
||||
extended_adapter_ = adapter;
|
||||
}
|
||||
|
||||
void client::set_msg_callback(on_message_type on_msg)
|
||||
{
|
||||
on_msg_ = on_msg;
|
||||
}
|
||||
|
||||
net::awaitable<void> client::engage(socket_type socket)
|
||||
{
|
||||
using namespace aedis::net::experimental::awaitable_operators;
|
||||
|
||||
socket_ = std::move(socket);
|
||||
|
||||
std::string request;
|
||||
auto sr = redis::make_serializer(request);
|
||||
sr.push(redis::command::hello, 3);
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await net::async_write(socket_, net::buffer(request), net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
std::string buffer;
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await
|
||||
resp3::async_read(
|
||||
socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
on_msg_(ec, redis::command::hello);
|
||||
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
co_await (reader() && writer());
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
@@ -217,6 +218,7 @@ char const* to_string(command c)
|
||||
"ZSCAN",
|
||||
"ZSCORE",
|
||||
"ZUNIONSTORE",
|
||||
"INVALID",
|
||||
};
|
||||
|
||||
return table[static_cast<int>(c)];
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Creates a void response adapter.
|
||||
\ingroup any
|
||||
|
||||
The adapter returned by this function ignores responses and is
|
||||
useful to avoid wasting time with responses which the user is
|
||||
insterested in.
|
||||
|
||||
Example usage:
|
||||
|
||||
@code
|
||||
co_await async_read(socket, buffer, adapt());
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt() noexcept
|
||||
{ return response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
*
|
||||
* For example
|
||||
* The following types are supported.
|
||||
*
|
||||
* - Integer data types e.g. `int`, `unsigned`, etc.
|
||||
*
|
||||
* - `std::string`
|
||||
*
|
||||
* We also support the following C++ containers
|
||||
*
|
||||
* - `std::vector<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::deque<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::list<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::unordered_set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* - `std::unordered_map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* All these types can be wrapped in an `std::optional<T>`. This
|
||||
* function also support \c std::tuple to read the response to
|
||||
* tuples. At the moment this feature supports only transactions that
|
||||
* contain simple types or aggregates that don't contain aggregates
|
||||
* themselves (as in most cases).
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
sr.push(command::multi);
|
||||
sr.push(command::ping, ...);
|
||||
sr.push(command::incr, ...);
|
||||
sr.push_range(command::rpush, ...);
|
||||
sr.push(command::lrange, ...);
|
||||
sr.push(command::incr, ...);
|
||||
sr.push(command::exec);
|
||||
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the response to a transaction
|
||||
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,382 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_string(
|
||||
T& i,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
auto const res = std::from_chars(data, data + data_size, i);
|
||||
if (res.ec != std::errc())
|
||||
ec = std::make_error_code(res.ec);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_string(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
s.assign(data, data_size);
|
||||
}
|
||||
|
||||
void set_on_resp3_error(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
case type::null: ec = adapter::error::null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// For optional responses.
|
||||
void set_on_resp3_error2(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapter that ignores responses.
|
||||
struct ignore {
|
||||
void
|
||||
operator()(
|
||||
type, std::size_t, std::size_t, char const*, std::size_t,
|
||||
std::error_code&) { }
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class general {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
general(Container* c = nullptr): result_(c) {}
|
||||
|
||||
/** @brief Function called by the parser when new data has been processed.
|
||||
*
|
||||
* Users who what to customize their response types are required to derive
|
||||
* from this class and override this function, see examples.
|
||||
*
|
||||
* \param t The RESP3 type of the data.
|
||||
*
|
||||
* \param n When t is an aggregate data type this will contain its size
|
||||
* (see also element_multiplicity) for simple data types this is always 1.
|
||||
*
|
||||
* \param depth The element depth in the tree.
|
||||
*
|
||||
* \param data A pointer to the data.
|
||||
*
|
||||
* \param size The size of data.
|
||||
*/
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->emplace_back(t, aggregate_size, depth, std::string{data, size});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node>
|
||||
class adapter_node {
|
||||
private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
adapter_node(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->data_type = t;
|
||||
result_->aggregate_size = aggregate_size;
|
||||
result_->depth = depth;
|
||||
result_->data.assign(data, data_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Adapter for RESP3 simple data types.
|
||||
template <class T>
|
||||
class simple {
|
||||
private:
|
||||
T* result_;
|
||||
|
||||
public:
|
||||
simple(T* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
from_string(*result_, data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class simple_optional {
|
||||
private:
|
||||
std::optional<T>* result_;
|
||||
|
||||
public:
|
||||
simple_optional(std::optional<T>* o = nullptr) : result_(o) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error2(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
if (t == type::null)
|
||||
return;
|
||||
|
||||
if (!result_->has_value())
|
||||
*result_ = T{};
|
||||
|
||||
from_string(result_->value(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
/* A std::vector adapter.
|
||||
*/
|
||||
template <class Container>
|
||||
class vector {
|
||||
private:
|
||||
int i_ = -1;
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
vector(Container* v = nullptr) : result_{v} {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (i_ != -1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const m = element_multiplicity(t);
|
||||
result_->resize(m * aggregate_size);
|
||||
++i_;
|
||||
} else {
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
from_string(result_->at(i_), data, data_size, ec);
|
||||
++i_;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class list {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
list(Container* ref = nullptr): result_(ref) {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
result_->push_back({});
|
||||
from_string(result_->back(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class set {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator hint_;
|
||||
|
||||
public:
|
||||
set(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, hint_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::set) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
if (hint_ == std::end(*result_)) {
|
||||
auto const ret = result_->insert(std::move(obj));
|
||||
hint_ = ret.first;
|
||||
} else {
|
||||
hint_ = result_->insert(hint_, std::move(obj));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class map {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator current_;
|
||||
bool on_key_ = true;
|
||||
|
||||
public:
|
||||
map(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, current_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::map) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (on_key_) {
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_ = result_->insert(current_, {std::move(obj), {}});
|
||||
} else {
|
||||
typename Container::mapped_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
on_key_ = !on_key_;
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
# include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Errors that may occurr when reading a response.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_simple_type = 1,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_unsupported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
blob_error,
|
||||
|
||||
/// The tuple used as response has incompatible size.
|
||||
incompatible_tuple_size,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
/// \todo Fix string lifetime.
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.response_adapter"; }
|
||||
|
||||
// TODO: Move to .ipp
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::expects_simple_type: return "Expects a simple RESP3 type";
|
||||
case error::nested_unsupported: return "Nested responses unsupported.";
|
||||
case error::simple_error: return "Got RESP3 simple-error.";
|
||||
case error::blob_error: return "Got RESP3 blob-error.";
|
||||
case error::incompatible_tuple_size: return "The tuple used as response has incompatible size.";
|
||||
case error::null: return "Got RESP3 null.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& adapter_category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::adapter_category()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::adapter_category());
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::resp3::adapter::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
121
aedis/resp3/compose.hpp
Normal file
121
aedis/resp3/compose.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/hana.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** @brief Adds data to the request.
|
||||
* @ingroup any
|
||||
*/
|
||||
template <class Request>
|
||||
void to_bulk(Request& to, boost::string_view data)
|
||||
{
|
||||
auto const str = std::to_string(data.size());
|
||||
|
||||
to += "$";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
to.append(std::cbegin(data), std::cend(data));
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
|
||||
void to_bulk(Request& to, T n)
|
||||
{
|
||||
auto const s = std::to_string(n);
|
||||
to_bulk(to, boost::string_view{s});
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
struct add_bulk_impl {
|
||||
template <class Request>
|
||||
static void add(Request& to, T const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <class U, class V>
|
||||
struct add_bulk_impl<std::pair<U, V>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, std::pair<U, V> const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from.first);
|
||||
to_bulk(to, from.second);
|
||||
}
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct add_bulk_impl<boost::hana::tuple<Ts...>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, boost::hana::tuple<Ts...> const& from)
|
||||
{
|
||||
using boost::hana::for_each;
|
||||
|
||||
// Fold expressions is C++17 so we use hana.
|
||||
//(resp3::add_bulk(*request_, args), ...);
|
||||
|
||||
for_each(from, [&](auto const& e) {
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** @brief Adds a resp3 header to the store to.
|
||||
* @ingroup any
|
||||
*/
|
||||
template <class Request>
|
||||
void add_header(Request& to, std::size_t size)
|
||||
{
|
||||
auto const str = std::to_string(size);
|
||||
|
||||
to += "*";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
/** @brief Adds a rep3 bulk to the request.
|
||||
* @ingroup any
|
||||
*
|
||||
* This function adds \c data as a bulk string to the request \c to.
|
||||
*/
|
||||
template <class Request, class T>
|
||||
void add_bulk(Request& to, T const& data)
|
||||
{
|
||||
detail::add_bulk_impl<T>::add(to, data);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct bulk_counter;
|
||||
|
||||
template <class>
|
||||
struct bulk_counter {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct bulk_counter<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,87 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
template <class>
|
||||
struct needs_to_string : std::true_type {};
|
||||
|
||||
template <> struct needs_to_string<std::string> : std::false_type {};
|
||||
template <> struct needs_to_string<std::string_view> : std::false_type {};
|
||||
template <> struct needs_to_string<char const*> : std::false_type {};
|
||||
template <> struct needs_to_string<char*> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char[N]> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char const[N]> : std::false_type {};
|
||||
|
||||
template <class Storage>
|
||||
void add_header(Storage& to, int size)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(size);
|
||||
|
||||
to += "*";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage>
|
||||
void add_bulk(Storage& to, std::string_view data)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(std::size(data));
|
||||
|
||||
to += "$";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
to += data;
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage, class T>
|
||||
void add_bulk(Storage& to, T const& data, typename std::enable_if<needs_to_string<T>::value, bool>::type = false)
|
||||
{
|
||||
using std::to_string;
|
||||
auto const s = to_string(data);
|
||||
add_bulk(to, s);
|
||||
}
|
||||
|
||||
// Overload for pairs.
|
||||
// TODO: Overload for tuples.
|
||||
template <class Storage, class T1, class T2>
|
||||
void add_bulk(Storage& to, std::pair<T1, T2> const& pair)
|
||||
{
|
||||
add_bulk(to, pair.first);
|
||||
add_bulk(to, pair.second);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct value_type_size {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct value_type_size<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -12,6 +12,16 @@ namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec)
|
||||
{
|
||||
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
|
||||
std::size_t ret;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_number;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
type to_type(char c)
|
||||
{
|
||||
switch (c) {
|
||||
|
||||
@@ -8,22 +8,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <charconv>
|
||||
#include <system_error>
|
||||
#include <limits>
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/error.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec);
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
type to_type(char c);
|
||||
|
||||
template <class ResponseAdapter>
|
||||
class parser {
|
||||
private:
|
||||
using node_type = node<boost::string_view>;
|
||||
static constexpr std::size_t max_embedded_depth = 5;
|
||||
|
||||
ResponseAdapter adapter_;
|
||||
@@ -54,24 +61,21 @@ public:
|
||||
|
||||
// Returns the number of bytes that have been consumed.
|
||||
std::size_t
|
||||
advance(char const* data, std::size_t n, std::error_code& ec)
|
||||
consume(char const* data, std::size_t n, boost::system::error_code& ec)
|
||||
{
|
||||
if (bulk_ != type::invalid) {
|
||||
n = bulk_length_ + 2;
|
||||
switch (bulk_) {
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
if (bulk_length_ == 0) {
|
||||
sizes_[depth_] = 1;
|
||||
} else {
|
||||
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
assert(bulk_length_ != 0);
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
@@ -83,46 +87,75 @@ public:
|
||||
} else if (sizes_[depth_] != 0) {
|
||||
auto const t = to_type(*data);
|
||||
switch (t) {
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bulk_ = t;
|
||||
if (bulk_length_ == 0) {
|
||||
adapter_({type::streamed_string_part, 1, depth_, {}}, ec);
|
||||
sizes_[depth_] = 0; // We are done.
|
||||
} else {
|
||||
bulk_ = type::streamed_string_part;
|
||||
}
|
||||
} break;
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::blob_string:
|
||||
{
|
||||
if (*(data + 1) == '?') {
|
||||
// NOTE: This can only be triggered with blob_string.
|
||||
// Trick: A streamed string is read as an aggregate
|
||||
// of infinite lenght. When the streaming is done
|
||||
// the server is supposed to send a part with lenght
|
||||
// the server is supposed to send a part with length
|
||||
// 0.
|
||||
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
|
||||
} else {
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
bulk_ = type::blob_string;
|
||||
bulk_ = t;
|
||||
}
|
||||
} break;
|
||||
case type::simple_error:
|
||||
case type::number:
|
||||
case type::doublean:
|
||||
case type::boolean:
|
||||
{
|
||||
if (n == 3) {
|
||||
ec = error::empty_field;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*(data + 1) != 'f' && *(data + 1) != 't') {
|
||||
ec = error::unexpected_bool_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::doublean:
|
||||
case type::big_number:
|
||||
case type::number:
|
||||
{
|
||||
if (n == 3) {
|
||||
ec = error::empty_field;
|
||||
return 0;
|
||||
}
|
||||
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::simple_error:
|
||||
case type::simple_string:
|
||||
{
|
||||
adapter_(t, 1, depth_, data + 1, n - 3, ec);
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -130,7 +163,7 @@ public:
|
||||
} break;
|
||||
case type::null:
|
||||
{
|
||||
adapter_(type::null, 1, depth_, nullptr, 0, ec);
|
||||
adapter_({type::null, 1, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -142,14 +175,11 @@ public:
|
||||
case type::attribute:
|
||||
case type::map:
|
||||
{
|
||||
std::size_t l;
|
||||
auto const r = std::from_chars(data + 1, data + n - 2, l);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
auto const l = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
adapter_(t, l, depth_, nullptr, 0, ec);
|
||||
adapter_({t, l, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
@@ -9,15 +9,24 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/read_until.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
struct ignore_response {
|
||||
void operator()(node<boost::string_view>, boost::system::error_code&) { }
|
||||
};
|
||||
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
@@ -28,7 +37,8 @@ private:
|
||||
DynamicBuffer buf_;
|
||||
parser<ResponseAdapter> parser_;
|
||||
std::size_t consumed_;
|
||||
int start_;
|
||||
std::size_t buffer_size_;
|
||||
boost::asio::coroutine coro_;
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
@@ -36,7 +46,6 @@ public:
|
||||
, buf_ {buf}
|
||||
, parser_ {adapter}
|
||||
, consumed_{0}
|
||||
, start_{1}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
@@ -44,15 +53,16 @@ public:
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
switch (start_) {
|
||||
for (;;) {
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
case 1:
|
||||
start_ = 0;
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
reenter (coro_) for (;;) {
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
yield
|
||||
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// On a bulk read we can't read until delimiter since the
|
||||
// payload may contain the delimiter itself so we have to
|
||||
// read the whole chunk. However if the bulk blob is small
|
||||
@@ -60,47 +70,49 @@ public:
|
||||
// read), in which case there is no need of initiating
|
||||
// another async op, otherwise we have to read the missing
|
||||
// bytes.
|
||||
if (std::size(buf_) < (parser_.bulk_length() + 2)) {
|
||||
start_ = 0;
|
||||
auto const s = std::size(buf_);
|
||||
auto const l = parser_.bulk_length();
|
||||
auto const to_read = l + 2 - s;
|
||||
buf_.grow(to_read);
|
||||
net::async_read(stream_, buf_.data(s, to_read), net::transfer_all(), std::move(self));
|
||||
return;
|
||||
if (buf_.size() < (parser_.bulk_length() + 2)) {
|
||||
buffer_size_ = buf_.size();
|
||||
buf_.grow(parser_.bulk_length() + 2 - buffer_size_);
|
||||
|
||||
yield
|
||||
boost::asio::async_read(
|
||||
stream_,
|
||||
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
|
||||
boost::asio::transfer_all(),
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
n = parser_.bulk_length() + 2;
|
||||
assert(buf_.size() >= n);
|
||||
}
|
||||
|
||||
n = parser_.advance((char const*)buf_.data(0, n).data(), n, ec);
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
n = parser_.consume((char const*)buf_.data(0, n).data(), n, ec);
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
buf_.consume(n);
|
||||
consumed_ += n;
|
||||
if (parser_.done()) {
|
||||
self.complete({}, consumed_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
buf_.consume(n);
|
||||
consumed_ += n;
|
||||
if (parser_.done()) {
|
||||
self.complete({}, consumed_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
template <class AsyncReadStream, class DynamicBuffer>
|
||||
class type_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
boost::asio::coroutine coro_;
|
||||
|
||||
public:
|
||||
type_op(AsyncReadStream& stream, DynamicBuffer buf)
|
||||
@@ -113,25 +125,26 @@ public:
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
reenter (coro_) {
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, type::invalid);
|
||||
return;
|
||||
boost::ignore_unused(n);
|
||||
if (buf_.size() == 0) {
|
||||
yield boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, type::invalid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto const* data = (char const*)buf_.data(0, n).data();
|
||||
auto const type = to_type(*data);
|
||||
self.complete(ec, type);
|
||||
}
|
||||
|
||||
if (std::size(buf_) == 0) {
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
auto const* data = (char const*)buf_.data(0, n).data();
|
||||
auto const type = to_type(*data);
|
||||
self.complete(ec, type);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
# include <system_error>
|
||||
|
||||
/// \file error.hpp
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
@@ -22,52 +27,24 @@ enum class error
|
||||
unexpected_read_size,
|
||||
|
||||
/// The maximum depth of a nested response was exceeded.
|
||||
exceeeds_max_nested_depth
|
||||
exceeeds_max_nested_depth,
|
||||
|
||||
/// Unexpected bool value
|
||||
unexpected_bool_value,
|
||||
|
||||
/// Expected field value is empty.
|
||||
empty_field
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.resp3"; }
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::invalid_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::unexpected_read_size: return "Unexpected read size.";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
/** \brief Converts an error in an std::error_code object.
|
||||
/** \brief Converts an error in an boost::system::error_code object.
|
||||
* \ingroup any
|
||||
*/
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
boost::system::error_code make_error_code(error e);
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
/** \brief todo
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_condition make_error_condition(error e);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
54
aedis/resp3/impl/error.ipp
Normal file
54
aedis/resp3/impl/error.ipp
Normal file
@@ -0,0 +1,54 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/resp3/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{
|
||||
return "aedis.resp3";
|
||||
}
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::invalid_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::unexpected_read_size: return "Unexpected read size.";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
case error::unexpected_bool_value: return "Unexpected bool value.";
|
||||
case error::empty_field: return "Expected field value is empty.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
boost::system::error_category const& category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
boost::system::error_code make_error_code(error e)
|
||||
{
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
boost::system::error_condition make_error_condition(error e)
|
||||
{
|
||||
return boost::system::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,67 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
std::string to_string(node const& in)
|
||||
{
|
||||
std::string out;
|
||||
out += std::to_string(in.depth);
|
||||
out += '\t';
|
||||
out += to_string(in.data_type);
|
||||
out += '\t';
|
||||
out += std::to_string(in.aggregate_size);
|
||||
out += '\t';
|
||||
if (!is_aggregate(in.data_type))
|
||||
out += in.data;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool operator==(node const& a, node const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& a.data == b.data;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, node const& o)
|
||||
{
|
||||
os << to_string(o);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r)
|
||||
{
|
||||
os << to_string(r);
|
||||
return os;
|
||||
}
|
||||
|
||||
// TODO: Output like in redis-cli.
|
||||
std::string to_string(std::vector<node> const& vec)
|
||||
{
|
||||
if (std::empty(vec))
|
||||
return {};
|
||||
|
||||
auto begin = std::cbegin(vec);
|
||||
std::string res;
|
||||
for (; begin != std::prev(std::cend(vec)); ++begin) {
|
||||
res += to_string(*begin);
|
||||
res += '\n';
|
||||
}
|
||||
|
||||
res += to_string(*begin);
|
||||
return res;
|
||||
}
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
||||
@@ -20,10 +20,21 @@ namespace resp3 {
|
||||
*
|
||||
* Redis responses are the pre-order view of the response tree (see
|
||||
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
*
|
||||
* The node class represent one element in the response tree. The string type
|
||||
* is a template give more flexibility, for example
|
||||
*
|
||||
* @li @c boost::string_view
|
||||
* @li @c std::string
|
||||
* @li @c boost::static_string
|
||||
*
|
||||
* \remark Any Redis response can be received in an array of nodes, for
|
||||
* example \c std::vector<node<std::string>>.
|
||||
*/
|
||||
template <class String>
|
||||
struct node {
|
||||
/// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
resp3::type data_type;
|
||||
|
||||
/// The number of elements of an aggregate.
|
||||
std::size_t aggregate_size;
|
||||
@@ -31,38 +42,54 @@ struct node {
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
/// The actual data. For aggregate data types this is always empty.
|
||||
std::string data;
|
||||
/// The actual data. For aggregate types this is always empty.
|
||||
String value;
|
||||
};
|
||||
|
||||
/** \brief Converts the node to a string.
|
||||
* \ingroup any
|
||||
*
|
||||
* \param obj The node object.
|
||||
* \param in The node object.
|
||||
*/
|
||||
std::string to_string(node const& obj);
|
||||
template <class String>
|
||||
std::string to_string(node<String> const& in)
|
||||
{
|
||||
std::string out;
|
||||
out += std::to_string(in.depth);
|
||||
out += '\t';
|
||||
out += to_string(in.data_type);
|
||||
out += '\t';
|
||||
out += std::to_string(in.aggregate_size);
|
||||
out += '\t';
|
||||
if (!is_aggregate(in.data_type))
|
||||
out.append(in.value.data(), in.value.size());
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/** \brief Compares a node for equality.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool operator==(node const& a, node const& b);
|
||||
template <class String>
|
||||
bool operator==(node<String> const& a, node<String> const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& a.value == b.value;
|
||||
};
|
||||
|
||||
/** \brief Writes the node to the stream.
|
||||
* \ingroup any
|
||||
*
|
||||
* NOTE: Binary data is not converted to text.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, node const& o);
|
||||
template <class String>
|
||||
std::ostream& operator<<(std::ostream& os, node<String> const& o)
|
||||
{
|
||||
os << to_string(o);
|
||||
return os;
|
||||
}
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::string to_string(std::vector<node> const& vec);
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r);
|
||||
|
||||
} // resp3
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/detail/read_ops.hpp>
|
||||
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
namespace aedis {
|
||||
@@ -44,12 +44,12 @@ read(
|
||||
ResponseAdapter adapter,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
detail::parser p {adapter};
|
||||
detail::parser<ResponseAdapter> p {adapter};
|
||||
std::size_t n = 0;
|
||||
std::size_t consumed = 0;
|
||||
do {
|
||||
if (p.bulk() == type::invalid) {
|
||||
n = net::read_until(stream, buf, "\r\n", ec);
|
||||
n = boost::asio::read_until(stream, buf, "\r\n", ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -58,12 +58,12 @@ read(
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
auto const s = std::size(buf);
|
||||
auto const s = buf.size();
|
||||
auto const l = p.bulk_length();
|
||||
if (s < (l + 2)) {
|
||||
auto const to_read = l + 2 - s;
|
||||
buf.grow(to_read);
|
||||
n = net::read(stream, buf.data(s, to_read), ec);
|
||||
n = boost::asio::read(stream, buf.data(s, to_read), ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -74,9 +74,8 @@ read(
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
auto const* data = (char const*) buf.data(0, n).data();
|
||||
n = p.advance(data, n, ec);
|
||||
n = p.consume(data, n, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -102,12 +101,12 @@ read(
|
||||
template<
|
||||
class SyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type>
|
||||
class ResponseAdapter = detail::ignore_response>
|
||||
std::size_t
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter = adapt())
|
||||
ResponseAdapter adapter = ResponseAdapter{})
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
auto const n = resp3::read(stream, buf, adapter, ec);
|
||||
@@ -141,17 +140,17 @@ read(
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type,
|
||||
class CompletionToken = net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
class ResponseAdapter = detail::ignore_response,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
ResponseAdapter adapter = adapt(),
|
||||
ResponseAdapter adapter = ResponseAdapter{},
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::async_compose
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
|
||||
@@ -177,15 +176,15 @@ template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read_type(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::async_compose
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, type)
|
||||
>(detail::type_op<AsyncReadStream, DynamicBuffer> {stream, buffer}, token, stream);
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapter/detail/adapters.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Traits class for response objects.
|
||||
* \ingroup any
|
||||
*/
|
||||
template <class T>
|
||||
struct response_traits
|
||||
{
|
||||
/// The response type.
|
||||
using response_type = T;
|
||||
|
||||
/// The adapter type.
|
||||
using adapter_type = adapter::detail::simple<response_type>;
|
||||
|
||||
/// Returns an adapter for the reponse r
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
/// Template typedef for response_traits.
|
||||
template <class T>
|
||||
using response_traits_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <class T>
|
||||
struct response_traits<std::optional<T>>
|
||||
{
|
||||
using response_type = std::optional<T>;
|
||||
using adapter_type = adapter::detail::simple_optional<typename response_type::value_type>;
|
||||
static auto adapt(response_type& i) noexcept { return adapter_type{&i}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::vector<T, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<T, Allocator>;
|
||||
using adapter_type = adapter::detail::vector<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<node>
|
||||
{
|
||||
using response_type = node;
|
||||
using adapter_type = adapter::detail::adapter_node<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
struct response_traits<std::vector<node, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<node, Allocator>;
|
||||
using adapter_type = adapter::detail::general<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::list<T, Allocator>>
|
||||
{
|
||||
using response_type = std::list<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::deque<T, Allocator>>
|
||||
{
|
||||
using response_type = std::deque<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct response_traits<std::set<Key, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::set<Key, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_set<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_set<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct response_traits<std::map<Key, T, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::map<Key, T, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_map<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_map<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<void>
|
||||
{
|
||||
using response_type = void;
|
||||
using adapter_type = adapter::detail::ignore;
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
class flat_transaction_adapter {
|
||||
private:
|
||||
using variant_type =
|
||||
boost::mp11::mp_rename<boost::mp11::mp_transform<response_traits_t, Tuple>, std::variant>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
std::array<variant_type, std::tuple_size<Tuple>::value> adapters_;
|
||||
|
||||
public:
|
||||
flat_transaction_adapter(Tuple* r)
|
||||
{ assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r); }
|
||||
|
||||
void count(type t, std::size_t aggregate_size, std::size_t depth)
|
||||
{
|
||||
if (depth == 1) {
|
||||
if (is_aggregate(t))
|
||||
aggregate_size_ = aggregate_size;
|
||||
else
|
||||
++i_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (--aggregate_size_ == 0)
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
if (depth == 0) {
|
||||
if (aggregate_size != std::tuple_size<Tuple>::value) {
|
||||
ec = adapter::error::incompatible_tuple_size;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::visit([&](auto& arg){arg(t, aggregate_size, depth, data, size, ec);}, adapters_[i_]);
|
||||
count(t, aggregate_size, depth);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
|
||||
// The adapter of responses to transactions, move it to its own header?
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
{
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = adapter::detail::flat_transaction_adapter<response_type>;
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -52,7 +52,13 @@ enum class command {
|
||||
/// https://redis.io/commands/unsubscribe
|
||||
unsubscribe,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
invalid,
|
||||
// For internal usege only, users should ignore this.
|
||||
multi,
|
||||
// For internal usege only, users should ignore this.
|
||||
discard,
|
||||
// For internal usege only, users should ignore this.
|
||||
exec,
|
||||
};
|
||||
|
||||
/** \brief Converts a sentinel command to a string
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
#include <aedis/redis/impl/command.ipp>
|
||||
#include <aedis/sentinel/impl/command.ipp>
|
||||
#include <aedis/resp3/impl/type.ipp>
|
||||
#include <aedis/resp3/impl/node.ipp>
|
||||
#include <aedis/redis/experimental/impl/client.ipp>
|
||||
#include <aedis/resp3/detail/impl/parser.ipp>
|
||||
#include <aedis/resp3/impl/error.ipp>
|
||||
#include <aedis/adapter/impl/error.ipp>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([Aedis], [0.0.1], [mzimbres@gmail.com])
|
||||
AC_INIT([Aedis], [0.1.0], [mzimbres@gmail.com])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
#AC_CONFIG_SRCDIR([src/aedis.cpp])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
@@ -18,5 +18,10 @@ AC_CHECK_HEADER_STDBOOL
|
||||
AC_TYPE_UINT64_T
|
||||
AC_CHECK_TYPES([ptrdiff_t])
|
||||
|
||||
AX_CXX_COMPILE_STDCXX(14, , mandatory)
|
||||
AX_CXX_COMPILE_STDCXX(20, , optional)
|
||||
|
||||
AM_CONDITIONAL(HAVE_CXX20,[test x$HAVE_CXX20 == x1])
|
||||
|
||||
AC_CONFIG_FILES([Makefile doc/Doxyfile])
|
||||
AC_OUTPUT
|
||||
|
||||
@@ -32,13 +32,13 @@ DOXYFILE_ENCODING = UTF-8
|
||||
# title of most generated pages and in a few other places.
|
||||
# The default value is: My Project.
|
||||
|
||||
PROJECT_NAME = "Aedis"
|
||||
PROJECT_NAME = "@PACKAGE_NAME@"
|
||||
|
||||
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "0.0.1"
|
||||
PROJECT_NUMBER = "@PACKAGE_VERSION@"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
@@ -179,7 +179,7 @@ STRIP_FROM_PATH =
|
||||
# specify the list of include paths that are normally passed to the compiler
|
||||
# using the -I flag.
|
||||
|
||||
STRIP_FROM_INC_PATH =
|
||||
STRIP_FROM_INC_PATH = .
|
||||
|
||||
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
|
||||
# less readable) file names. This can be useful is your file systems doesn't
|
||||
@@ -850,52 +850,7 @@ INPUT_ENCODING = UTF-8
|
||||
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.c \
|
||||
*.cc \
|
||||
*.cxx \
|
||||
*.cpp \
|
||||
*.c++ \
|
||||
*.java \
|
||||
*.ii \
|
||||
*.ixx \
|
||||
*.ipp \
|
||||
*.i++ \
|
||||
*.inl \
|
||||
*.ddl \
|
||||
*.odl \
|
||||
*.h \
|
||||
*.hh \
|
||||
*.hxx \
|
||||
*.hpp \
|
||||
*.h++ \
|
||||
*.cs \
|
||||
*.d \
|
||||
*.php \
|
||||
*.php4 \
|
||||
*.php5 \
|
||||
*.phtml \
|
||||
*.inc \
|
||||
*.m \
|
||||
*.markdown \
|
||||
*.md \
|
||||
*.mm \
|
||||
*.dox \
|
||||
*.doc \
|
||||
*.txt \
|
||||
*.py \
|
||||
*.pyw \
|
||||
*.f90 \
|
||||
*.f95 \
|
||||
*.f03 \
|
||||
*.f08 \
|
||||
*.f \
|
||||
*.for \
|
||||
*.tcl \
|
||||
*.vhd \
|
||||
*.vhdl \
|
||||
*.ucf \
|
||||
*.qsf \
|
||||
*.ice
|
||||
FILE_PATTERNS = *.hpp *.cpp
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
@@ -910,7 +865,7 @@ RECURSIVE = YES
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE = include/aedis/impl include/aedis/resp3/impl include/aedis/resp3/detail
|
||||
EXCLUDE =
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
@@ -943,7 +898,7 @@ EXCLUDE_SYMBOLS = std
|
||||
# that contain example code fragments that are included (see the \include
|
||||
# command).
|
||||
|
||||
EXAMPLE_PATH = examples
|
||||
EXAMPLE_PATH =
|
||||
|
||||
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
|
||||
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
|
||||
@@ -24,9 +24,7 @@ div.contents {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/*
|
||||
code
|
||||
{
|
||||
background-color:#EFD25E;
|
||||
background-color:#f0e9ce;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/user_session.hpp"
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::resp3::adapt;
|
||||
using aedis::resp3::experimental::client;
|
||||
using aedis::resp3::node;
|
||||
using aedis::resp3::type;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
|
||||
// TODO: Delete sessions that have expired.
|
||||
class receiver : public std::enable_shared_from_this<receiver> {
|
||||
public:
|
||||
private:
|
||||
std::vector<node> resps_;
|
||||
std::vector<std::weak_ptr<user_session_base>> sessions_;
|
||||
|
||||
public:
|
||||
void on_message(std::error_code ec, command cmd)
|
||||
{
|
||||
if (ec) {
|
||||
std::cerr << "Error: " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case command::incr:
|
||||
{
|
||||
std::cout << "Message so far: " << resps_.front().data << std::endl;
|
||||
} break;
|
||||
case command::unknown: // Push
|
||||
{
|
||||
for (auto& weak: sessions_) {
|
||||
if (auto session = weak.lock()) {
|
||||
session->deliver(resps_.at(3).data);
|
||||
} else {
|
||||
std::cout << "Session expired." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
default: { /* Ignore */ }
|
||||
}
|
||||
|
||||
resps_.clear();
|
||||
}
|
||||
|
||||
auto get_extended_adapter()
|
||||
{
|
||||
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
|
||||
{ return adapter(t, aggregate_size, depth, data, size, ec); };
|
||||
}
|
||||
|
||||
auto add(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push_back(session); }
|
||||
};
|
||||
|
||||
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
co_await db->engage(std::move(socket));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> listener()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
|
||||
|
||||
auto recv = std::make_shared<receiver>();
|
||||
auto on_db_msg = [recv](std::error_code ec, command cmd)
|
||||
{ recv->on_message(ec, cmd); };
|
||||
|
||||
auto db = std::make_shared<client>(ex);
|
||||
db->set_extended_adapter(recv->get_extended_adapter());
|
||||
db->set_msg_callback(on_db_msg);
|
||||
net::co_spawn(ex, connection_manager(db), net::detached);
|
||||
db->send(command::subscribe, "channel");
|
||||
|
||||
auto on_user_msg = [db](std::string const& msg)
|
||||
{
|
||||
db->send(command::publish, "channel", msg);
|
||||
db->send(command::incr, "message-counter");
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
auto socket = co_await acceptor.async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
recv->add(session);
|
||||
session->start(on_user_msg);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
co_spawn(ioc, listener(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/user_session.hpp"
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
using aedis::resp3::node;
|
||||
using aedis::resp3::adapt;
|
||||
using aedis::resp3::experimental::client;
|
||||
using aedis::resp3::type;
|
||||
|
||||
class receiver : public std::enable_shared_from_this<receiver> {
|
||||
private:
|
||||
std::vector<node> resps_;
|
||||
std::queue<std::weak_ptr<user_session_base>> sessions_;
|
||||
|
||||
public:
|
||||
void on_message(std::error_code ec, command cmd)
|
||||
{
|
||||
if (ec) {
|
||||
std::cerr << "Error: " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case command::ping:
|
||||
{
|
||||
if (auto session = sessions_.front().lock()) {
|
||||
session->deliver(resps_.front().data);
|
||||
} else {
|
||||
std::cout << "Session expired." << std::endl;
|
||||
}
|
||||
|
||||
sessions_.pop();
|
||||
} break;
|
||||
case command::incr:
|
||||
{
|
||||
std::cout << "Echos so far: " << resps_.front().data << std::endl;
|
||||
} break;
|
||||
default: { /* Ignore */; }
|
||||
}
|
||||
|
||||
resps_.clear();
|
||||
}
|
||||
|
||||
auto get_extended_adapter()
|
||||
{
|
||||
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
|
||||
{ return adapter(t, aggregate_size, depth, data, size, ec); };
|
||||
}
|
||||
|
||||
void add_user_session(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push(session); }
|
||||
};
|
||||
|
||||
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
co_await db->engage(std::move(socket));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> listener()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
|
||||
|
||||
auto recv = std::make_shared<receiver>();
|
||||
auto on_db_msg = [recv](std::error_code ec, command cmd)
|
||||
{ recv->on_message(ec, cmd); };
|
||||
|
||||
auto db = std::make_shared<client>(ex);
|
||||
db->set_extended_adapter(recv->get_extended_adapter());
|
||||
db->set_msg_callback(on_db_msg);
|
||||
net::co_spawn(ex, connection_manager(db), net::detached);
|
||||
|
||||
for (;;) {
|
||||
auto socket = co_await acceptor.async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
|
||||
auto on_user_msg = [db, recv, session](std::string const& msg)
|
||||
{
|
||||
db->send(command::ping, msg);
|
||||
db->send(command::incr, "echo-counter");
|
||||
recv->add_user_session(session);
|
||||
};
|
||||
|
||||
session->start(on_user_msg);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
co_spawn(ioc, listener(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> containers()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// The expected responses
|
||||
int hset;
|
||||
std::vector<std::string> hgetall1;
|
||||
std::map<std::string, std::string> hgetall2;
|
||||
std::unordered_map<std::string, std::string> hgetall3;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hset));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall1));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall2));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall3));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "hset: " << hset;
|
||||
std::cout << "\nhgetall (as vector): ";
|
||||
for (auto const& e: hgetall1) std::cout << e << ", ";
|
||||
std::cout << "\nhgetall (as map): ";
|
||||
for (auto const& e: hgetall2) std::cout << e.first << " ==> " << e.second << "; ";
|
||||
std::cout << "\nhgetall (as unordered_map): ";
|
||||
for (auto const& e: hgetall3) std::cout << e.first << " ==> " << e.second << "; ";
|
||||
std::cout << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, containers(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
110
examples/high_level/aggregates.cpp
Normal file
110
examples/high_level/aggregates.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::redis::command;
|
||||
using client_type = aedis::generic::client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
// Prints aggregates that don't contain any nested aggregates.
|
||||
void print_aggregate(response_type const& v)
|
||||
{
|
||||
auto const m = element_multiplicity(v.front().data_type);
|
||||
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
|
||||
std::cout << v[i + 1].value << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
struct receiver {
|
||||
public:
|
||||
receiver(client_type& db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{&db} {}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
{
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
// Sends the stl containers.
|
||||
db_->send_range(command::hset, "hset-key", map);
|
||||
db_->send_range(command::rpush, "rpush-key", vec);
|
||||
db_->send_range(command::sadd, "sadd-key", set);
|
||||
|
||||
// Retrieves the containers.
|
||||
db_->send(command::hgetall, "hset-key");
|
||||
db_->send(command::lrange, "rpush-key", 0, -1);
|
||||
db_->send(command::smembers, "sadd-key");
|
||||
db_->send(command::quit);
|
||||
} break;
|
||||
|
||||
case command::lrange:
|
||||
case command::smembers:
|
||||
case command::hgetall:
|
||||
print_aggregate(resp_);
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db{ioc.get_executor()};
|
||||
receiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
125
examples/high_level/chat_room.cpp
Normal file
125
examples/high_level/chat_room.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "user_session.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
class receiver {
|
||||
public:
|
||||
receiver(std::shared_ptr<client_type> db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{db}
|
||||
{}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
db_->send(command::subscribe, "channel");
|
||||
break;
|
||||
|
||||
case command::incr:
|
||||
std::cout << "Messages so far: " << resp_.front().value << std::endl;
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push()
|
||||
{
|
||||
for (auto& session: sessions_)
|
||||
session->deliver(resp_.at(3).value);
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
auto add(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push_back(session); }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
std::shared_ptr<client_type> db_;
|
||||
std::vector<std::shared_ptr<user_session_base>> sessions_;
|
||||
};
|
||||
|
||||
net::awaitable<void>
|
||||
listener(
|
||||
std::shared_ptr<net::ip::tcp::acceptor> acc,
|
||||
std::shared_ptr<client_type> db,
|
||||
std::shared_ptr<receiver> recv)
|
||||
{
|
||||
auto on_user_msg = [db](std::string const& msg)
|
||||
{
|
||||
db->send(command::publish, "channel", msg);
|
||||
db->send(command::incr, "message-counter");
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
auto socket = co_await acc->async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
session->start(on_user_msg);
|
||||
recv->add(session);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
|
||||
auto db = std::make_shared<client_type>(ioc.get_executor());
|
||||
auto recv = std::make_shared<receiver>(db);
|
||||
|
||||
db->async_run(
|
||||
*recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
|
||||
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
|
||||
co_spawn(ioc, listener(acc, db, recv), net::detached);
|
||||
|
||||
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
|
||||
signals.async_wait([&] (auto, int) { ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
121
examples/high_level/echo_server.cpp
Normal file
121
examples/high_level/echo_server.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "user_session.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
class myreceiver {
|
||||
public:
|
||||
myreceiver()
|
||||
: adapter_{adapt(resp_)}
|
||||
{}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::ping:
|
||||
sessions_.front()->deliver(resp_.front().value);
|
||||
sessions_.pop();
|
||||
break;
|
||||
|
||||
case command::incr:
|
||||
std::cout << "Echos so far: " << resp_.front().value << std::endl;
|
||||
break;
|
||||
|
||||
default: /* Ignore */;
|
||||
}
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void add_user_session(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push(session); }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
std::queue<std::shared_ptr<user_session_base>> sessions_;
|
||||
};
|
||||
|
||||
net::awaitable<void>
|
||||
listener(
|
||||
std::shared_ptr<net::ip::tcp::acceptor> acc,
|
||||
std::shared_ptr<client_type> db,
|
||||
std::shared_ptr<myreceiver> recv)
|
||||
{
|
||||
for (;;) {
|
||||
auto socket = co_await acc->async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
|
||||
auto on_user_msg = [db, recv, session](std::string const& msg)
|
||||
{
|
||||
db->send(command::ping, msg);
|
||||
db->send(command::incr, "echo-counter");
|
||||
recv->add_user_session(session);
|
||||
};
|
||||
|
||||
session->start(on_user_msg);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
|
||||
auto db = std::make_shared<client_type>(ioc.get_executor());
|
||||
auto recv = std::make_shared<myreceiver>();
|
||||
|
||||
db->async_run(
|
||||
*recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
|
||||
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
|
||||
co_spawn(ioc, listener(acc, db, recv), net::detached);
|
||||
|
||||
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
|
||||
signals.async_wait([&] (auto, int) { ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
76
examples/high_level/intro.cpp
Normal file
76
examples/high_level/intro.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::adapter::adapt;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = node<std::string>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
struct myreceiver {
|
||||
public:
|
||||
myreceiver(client_type& db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{&db} {}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
db_->send(command::ping, "O rato roeu a roupa do rei de Roma");
|
||||
db_->send(command::incr, "intro-counter");
|
||||
db_->send(command::set, "intro-key", "Três pratos de trigo para três tigres");
|
||||
db_->send(command::get, "intro-key");
|
||||
db_->send(command::quit);
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cout << resp_.value << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
myreceiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
183
examples/high_level/serialization.cpp
Normal file
183
examples/high_level/serialization.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
// Arbitrary struct to de/serialize.
|
||||
struct mystruct {
|
||||
int a;
|
||||
int b;
|
||||
};
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace adapter = aedis::adapter;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapters_tuple_t;
|
||||
using aedis::adapter::make_adapters_tuple;
|
||||
using aedis::adapter::get;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using responses_tuple_type =
|
||||
std::tuple<
|
||||
boost::optional<mystruct>,
|
||||
std::list<mystruct>,
|
||||
std::set<mystruct>,
|
||||
std::map<std::string, mystruct>
|
||||
>;
|
||||
using adapters_tuple_type = adapters_tuple_t<responses_tuple_type>;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, mystruct const& obj)
|
||||
{
|
||||
os << "a: " << obj.a << ", b: " << obj.b;
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator<(mystruct const& a, mystruct const& b)
|
||||
{
|
||||
return std::tie(a.a, a.b) < std::tie(b.a, b.b);
|
||||
}
|
||||
|
||||
// Dumy serialization.
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
aedis::resp3::to_bulk(to, "Dummy serializaiton string.");
|
||||
}
|
||||
|
||||
// Dummy deserialization.
|
||||
void from_string(mystruct& obj, boost::string_view sv, boost::system::error_code& ec)
|
||||
{
|
||||
obj.a = 1;
|
||||
obj.b = 2;
|
||||
}
|
||||
|
||||
class receiver {
|
||||
public:
|
||||
receiver(client_type& db)
|
||||
: adapters_(make_adapters_tuple(resps_))
|
||||
, db_{&db} {}
|
||||
|
||||
void
|
||||
on_resp3(
|
||||
command cmd,
|
||||
node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::get: adapter::get<boost::optional<mystruct>>(adapters_)(nd, ec); break;
|
||||
case command::lrange: adapter::get<std::list<mystruct>>(adapters_)(nd, ec); break;
|
||||
case command::smembers: adapter::get<std::set<mystruct>>(adapters_)(nd, ec); break;
|
||||
case command::hgetall: adapter::get<std::map<std::string, mystruct>>(adapters_)(nd, ec); break;
|
||||
default:; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
std::cout << cmd << "\n";
|
||||
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
{
|
||||
mystruct var{1, 2};
|
||||
|
||||
std::map<std::string, mystruct> map
|
||||
{ {"key1", {1, 2}}
|
||||
, {"key2", {3, 4}}
|
||||
, {"key3", {5, 6}}};
|
||||
|
||||
std::vector<mystruct> vec
|
||||
{{1, 2}, {3, 4}, {5, 6}};
|
||||
|
||||
std::set<std::string> set
|
||||
{{1, 2}, {3, 4}, {5, 6}};
|
||||
|
||||
// Sends
|
||||
db_->send(command::set, "serialization-var-key", var, "EX", "2");
|
||||
db_->send_range(command::hset, "serialization-hset-key", map);
|
||||
db_->send_range(command::rpush, "serialization-rpush-key", vec);
|
||||
db_->send_range(command::sadd, "serialization-sadd-key", set);
|
||||
|
||||
// Retrieves
|
||||
db_->send(command::get, "serialization-var-key");
|
||||
db_->send(command::hgetall, "serialization-hset-key");
|
||||
db_->send(command::lrange, "serialization-rpush-key", 0, -1);
|
||||
db_->send(command::smembers, "serialization-sadd-key");
|
||||
} break;
|
||||
|
||||
case command::get:
|
||||
{
|
||||
if (std::get<boost::optional<mystruct>>(resps_).has_value()) {
|
||||
std::cout << std::get<boost::optional<mystruct>>(resps_).value() << "\n\n";
|
||||
std::get<boost::optional<mystruct>>(resps_).reset();
|
||||
} else {
|
||||
std::cout << "Expired." << "\n";
|
||||
}
|
||||
} break;
|
||||
|
||||
case command::lrange:
|
||||
for (auto const& e: std::get<std::list<mystruct>>(resps_))
|
||||
std::cout << e << "\n";
|
||||
std::cout << "\n";
|
||||
std::get<std::list<mystruct>>(resps_).clear();
|
||||
break;
|
||||
|
||||
case command::smembers:
|
||||
for (auto const& e: std::get<std::set<mystruct>>(resps_))
|
||||
std::cout << e << "\n";
|
||||
std::cout << "\n";
|
||||
std::get<std::set<mystruct>>(resps_).clear();
|
||||
break;
|
||||
|
||||
case command::hgetall:
|
||||
for (auto const& e: std::get<std::map<std::string, mystruct>>(resps_))
|
||||
std::cout << e.first << ", " << e.second << std::endl;
|
||||
std::cout << "\n";
|
||||
std::get<std::map<std::string, mystruct>>(resps_).clear();
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
private:
|
||||
responses_tuple_type resps_;
|
||||
adapters_tuple_type adapters_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
receiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
net::steady_timer tm{ioc, std::chrono::seconds{3}};
|
||||
|
||||
tm.async_wait([&db](auto ec){
|
||||
db.send(command::get, "serialization-var-key");
|
||||
db.send(command::quit);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
127
examples/high_level/stl_containers.cpp
Normal file
127
examples/high_level/stl_containers.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace adapter = aedis::adapter;
|
||||
using aedis::resp3::node;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapters_tuple_t;
|
||||
using aedis::adapter::make_adapters_tuple;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using responses_tuple_type =
|
||||
std::tuple<
|
||||
std::list<int>,
|
||||
boost::optional<std::set<std::string>>,
|
||||
std::vector<node<std::string>>
|
||||
>;
|
||||
using adapters_tuple_type = adapters_tuple_t<responses_tuple_type>;
|
||||
|
||||
template <class Container>
|
||||
void print_and_clear(Container& cont)
|
||||
{
|
||||
std::cout << "\n";
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
cont.clear();
|
||||
}
|
||||
|
||||
class myreceiver {
|
||||
public:
|
||||
myreceiver(client_type& db)
|
||||
: adapters_(make_adapters_tuple(resps_))
|
||||
, db_{&db} {}
|
||||
|
||||
void
|
||||
on_resp3(
|
||||
command cmd,
|
||||
node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::lrange: adapter::get<std::list<int>>(adapters_)(nd, ec); break;
|
||||
case command::smembers: adapter::get<boost::optional<std::set<std::string>>>(adapters_)(nd, ec); break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
{
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
// Sends the stl containers.
|
||||
db_->send_range(command::hset, "hset-key", map);
|
||||
db_->send_range(command::rpush, "rpush-key", vec);
|
||||
db_->send_range(command::sadd, "sadd-key", set);
|
||||
|
||||
//_ Retrieves the containers.
|
||||
db_->send(command::hgetall, "hset-key");
|
||||
db_->send(command::lrange, "rpush-key", 0, -1);
|
||||
db_->send(command::smembers, "sadd-key");
|
||||
db_->send(command::quit);
|
||||
} break;
|
||||
|
||||
case command::lrange:
|
||||
print_and_clear(std::get<std::list<int>>(resps_));
|
||||
break;
|
||||
|
||||
case command::smembers:
|
||||
print_and_clear(std::get<boost::optional<std::set<std::string>>>(resps_).value());
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
private:
|
||||
responses_tuple_type resps_;
|
||||
adapters_tuple_type adapters_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db{ioc.get_executor()};
|
||||
myreceiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
95
examples/high_level/subscriber.cpp
Normal file
95
examples/high_level/subscriber.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::node;
|
||||
using aedis::sentinel::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::adapter::adapt;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
/* In this example we send a subscription to a channel and start
|
||||
* reading server side messages indefinitely.
|
||||
*
|
||||
* After starting the example you can test it by sending messages with
|
||||
* redis-cli like this
|
||||
*
|
||||
* $ redis-cli -3
|
||||
* 127.0.0.1:6379> PUBLISH channel1 some-message
|
||||
* (integer) 3
|
||||
* 127.0.0.1:6379>
|
||||
*
|
||||
* The messages will then appear on the terminal you are running the
|
||||
* example.
|
||||
*/
|
||||
|
||||
class myreceiver {
|
||||
public:
|
||||
myreceiver(client_type& db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{&db} {}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
db_->send(command::subscribe, "channel1", "channel2");
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push()
|
||||
{
|
||||
std::cout
|
||||
<< "Event: " << resp_.at(1).value << "\n"
|
||||
<< "Channel: " << resp_.at(2).value << "\n"
|
||||
<< "Message: " << resp_.at(3).value << "\n"
|
||||
<< std::endl;
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db{ioc.get_executor()};
|
||||
myreceiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
|
||||
// An example user session.
|
||||
|
||||
@@ -26,7 +29,7 @@ class user_session:
|
||||
public user_session_base,
|
||||
public std::enable_shared_from_this<user_session> {
|
||||
public:
|
||||
user_session(net::ip::tcp::socket socket)
|
||||
user_session(boost::asio::ip::tcp::socket socket)
|
||||
: socket_(std::move(socket))
|
||||
, timer_(socket_.get_executor())
|
||||
{ timer_.expires_at(std::chrono::steady_clock::time_point::max()); }
|
||||
@@ -35,11 +38,11 @@ public:
|
||||
{
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this(), on_msg]{ return self->reader(on_msg); },
|
||||
net::detached);
|
||||
boost::asio::detached);
|
||||
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this()]{ return self->writer(); },
|
||||
net::detached);
|
||||
boost::asio::detached);
|
||||
}
|
||||
|
||||
void deliver(std::string const& msg)
|
||||
@@ -49,12 +52,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
net::awaitable<void>
|
||||
boost::asio::awaitable<void>
|
||||
reader(std::function<void(std::string const&)> on_msg)
|
||||
{
|
||||
try {
|
||||
for (std::string msg;;) {
|
||||
auto const n = co_await net::async_read_until(socket_, net::dynamic_buffer(msg, 1024), "\n", net::use_awaitable);
|
||||
auto const n = co_await boost::asio::async_read_until(socket_, boost::asio::dynamic_buffer(msg, 1024), "\n", boost::asio::use_awaitable);
|
||||
on_msg(msg);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
@@ -63,15 +66,15 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> writer()
|
||||
boost::asio::awaitable<void> writer()
|
||||
{
|
||||
try {
|
||||
while (socket_.is_open()) {
|
||||
if (write_msgs_.empty()) {
|
||||
boost::system::error_code ec;
|
||||
co_await timer_.async_wait(redirect_error(net::use_awaitable, ec));
|
||||
co_await timer_.async_wait(boost::asio::redirect_error(boost::asio::use_awaitable, ec));
|
||||
} else {
|
||||
co_await net::async_write(socket_, net::buffer(write_msgs_.front()), net::use_awaitable);
|
||||
co_await boost::asio::async_write(socket_, boost::asio::buffer(write_msgs_.front()), boost::asio::use_awaitable);
|
||||
write_msgs_.pop_front();
|
||||
}
|
||||
}
|
||||
@@ -86,8 +89,8 @@ private:
|
||||
timer_.cancel();
|
||||
}
|
||||
|
||||
net::ip::tcp::socket socket_;
|
||||
net::steady_timer timer_;
|
||||
boost::asio::ip::tcp::socket socket_;
|
||||
boost::asio::steady_timer timer_;
|
||||
std::deque<std::string> write_msgs_;
|
||||
};
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> ping()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect(); // See lib/net_utils.hpp
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::incr, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Responses we are interested in.
|
||||
int incr;
|
||||
std::string ping;
|
||||
|
||||
// Reads the responses to ping and incr, ignore the others.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(ping));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(incr));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Print the responses.
|
||||
std::cout
|
||||
<< "ping: " << ping << "\n"
|
||||
<< "incr: " << incr << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, ping(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include <aedis/src.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> key_expiration()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
// Creates and sends the first request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "key", "Some payload", "EX", "2");
|
||||
sr.push(command::get, "key");
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Will hold the response to get.
|
||||
std::optional<std::string> get;
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
|
||||
|
||||
std::cout
|
||||
<< "Before expiration: " << get.has_value() << ", "
|
||||
<< *get << std::endl;
|
||||
|
||||
// Waits some seconds for the key to expire.
|
||||
timer tm{socket.get_executor(), std::chrono::seconds{3}};
|
||||
co_await tm.async_wait();
|
||||
|
||||
// Creates a request to get after expiration.
|
||||
get.reset(); request.clear();
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the response to the second request.
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
|
||||
|
||||
// Reading without an optional will result in an error.
|
||||
std::string str;
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer),
|
||||
adapt(str), net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
// Quit
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer));
|
||||
|
||||
std::cout << "After expiration (optional): " << get.has_value() << "\n";
|
||||
std::cout << "After expiration (non-optional): " << ec.message() << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, key_expiration(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
|
||||
using tcp_socket = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::socket>;
|
||||
using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
|
||||
using timer = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::steady_timer>;
|
||||
|
||||
aedis::net::awaitable<tcp_socket>
|
||||
connect(
|
||||
std::string host = "127.0.0.1",
|
||||
std::string port = "6379")
|
||||
{
|
||||
auto ex = co_await aedis::net::this_coro::executor;
|
||||
tcp_resolver resolver{ex};
|
||||
auto const res = co_await resolver.async_resolve(host, port);
|
||||
tcp_socket socket{ex};
|
||||
co_await aedis::net::async_connect(socket, res);
|
||||
co_return std::move(socket);
|
||||
}
|
||||
|
||||
//net::awaitable<void>
|
||||
//client::connection_manager()
|
||||
//{
|
||||
// using namespace aedis::net::experimental::awaitable_operators;
|
||||
// using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
|
||||
//
|
||||
// for (;;) {
|
||||
// tcp_resolver resolver{socket_.get_executor()};
|
||||
// auto const res = co_await resolver.async_resolve("127.0.0.1", "6379");
|
||||
// co_await net::async_connect(socket_, res);
|
||||
//
|
||||
// co_await say_hello();
|
||||
//
|
||||
// timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
// co_await (reader() && writer());
|
||||
//
|
||||
// socket_.close();
|
||||
// timer_.cancel();
|
||||
//
|
||||
// timer_.expires_after(std::chrono::seconds{1});
|
||||
// boost::system::error_code ec;
|
||||
// co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> ping()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
// Creates and sends the request.
|
||||
auto vec = {1, 2, 3, 4, 5, 6};
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "key", std::cbegin(vec), std::cend(vec));
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
int rpush;
|
||||
std::vector<std::string> svec;
|
||||
std::list<std::string> slist;
|
||||
std::deque<std::string> sdeq;
|
||||
std::vector<int> ivec;
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(rpush)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(svec));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(slist));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(sdeq));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(ivec));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "rpush: " << rpush;
|
||||
std::cout << "\nlrange (as vector): ";
|
||||
for (auto e: svec) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as list): ";
|
||||
for (auto e: slist) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as deque): ";
|
||||
for (auto e: sdeq) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as vector<int>): ";
|
||||
for (auto e: ivec) std::cout << e << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, ping(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
65
examples/low_level/adapter.cpp
Normal file
65
examples/low_level/adapter.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
tcp::resolver resv{ex};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(*std::begin(res));
|
||||
|
||||
std::string request, buffer;
|
||||
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::ping, "Some message.");
|
||||
sr.push(command::quit);
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
auto adapter = [](resp3::node<boost::string_view> const& nd, boost::system::error_code&)
|
||||
{
|
||||
std::cout << nd << std::endl;
|
||||
};
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapter);
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, example(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
78
examples/low_level/async_intro.cpp
Normal file
78
examples/low_level/async_intro.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::redis::command;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::generic::make_serializer;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using response_type = std::tuple<std::string, boost::optional<std::string>>;
|
||||
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
tcp::resolver resv{ex};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(*std::begin(res));
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::multi);
|
||||
sr.push(command::ping, "Some message.");
|
||||
sr.push(command::set, "low-level-key", "some content", "EX", "2");
|
||||
sr.push(command::exec);
|
||||
sr.push(command::quit);
|
||||
co_await net::async_write(socket, buffer(request));
|
||||
|
||||
std::tuple<std::string, boost::optional<std::string>> response;
|
||||
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(response));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
|
||||
std::cout
|
||||
<< "Ping: " << std::get<0>(response) << "\n"
|
||||
<< "Get (has_value): " << std::get<1>(response).has_value()
|
||||
<< std::endl;
|
||||
|
||||
if (std::get<1>(response).has_value())
|
||||
std::cout << "Get (value): " << std::get<1>(response).value() << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, example(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
68
examples/low_level/subscriber.cpp
Normal file
68
examples/low_level/subscriber.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::node;
|
||||
using aedis::redis::command;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::generic::make_serializer;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using response_type = std::tuple<std::string, boost::optional<std::string>>;
|
||||
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
tcp::resolver resv{ex};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(*std::begin(res));
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::subscribe, "channel1", "channel2");
|
||||
co_await net::async_write(socket, buffer(request));
|
||||
|
||||
// Ignores the response to hello.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
for (auto const& e: resp)
|
||||
std::cout << e << std::endl;
|
||||
resp.clear();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, example(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,29 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using aedis::resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::adapter::adapt;
|
||||
using net::dynamic_buffer;
|
||||
using net::ip::tcp;
|
||||
|
||||
using hello_type = std::tuple<
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, int,
|
||||
std::string, int,
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, std::vector<std::string>>;
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
@@ -27,9 +38,9 @@ int main()
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp::socket socket{ioc};
|
||||
connect(socket, res);
|
||||
net::connect(socket, res);
|
||||
|
||||
// Creates and sends the request to redis.
|
||||
// Creates and sends a request to redis.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
@@ -37,15 +48,17 @@ int main()
|
||||
sr.push(command::quit);
|
||||
net::write(socket, net::buffer(request));
|
||||
|
||||
// Will store the response to ping.
|
||||
// Responses
|
||||
std::string resp;
|
||||
hello_type hello;
|
||||
|
||||
// Reads the responses to all commands in the request.
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer)); // hello (ignored)
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt(hello)); // hello
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
resp3::read(socket, dynamic_buffer(buffer)); // quit (ignored)
|
||||
|
||||
std::cout << std::get<0>(hello) << ": " << std::get<1>(hello) << std::endl;
|
||||
std::cout << "Ping: " << resp << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
@@ -1,82 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using resp3::type;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
// An adapter that prints the data it receives in the screen.
|
||||
struct myadapter {
|
||||
void operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code&)
|
||||
{
|
||||
std::cout
|
||||
<< "Type: " << t << "\n"
|
||||
<< "Aggregate_size: " << aggregate_size << "\n"
|
||||
<< "Depth: " << depth << "\n"
|
||||
<< "Data: " << std::string_view(data, size) << "\n"
|
||||
<< "----------------------" << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
net::awaitable<void> adapter_example()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
auto list = {"one", "two", "three"};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "key", std::cbegin(list), std::cend(list));
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), myadapter{}); // lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, adapter_example(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <charconv>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::make_serializer;
|
||||
using aedis::redis::command;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
// Define the to_string and from_string functions for your own data
|
||||
// types.
|
||||
|
||||
// The struct we would like to store in redis using our own
|
||||
// serialization.
|
||||
struct mydata {
|
||||
int a;
|
||||
int b;
|
||||
};
|
||||
|
||||
// Serializes to Tab Separated Value (TSV).
|
||||
std::string to_string(mydata const& obj)
|
||||
{
|
||||
return std::to_string(obj.a) + '\t' + std::to_string(obj.b);
|
||||
}
|
||||
|
||||
// Deserializes TSV.
|
||||
void
|
||||
from_string(
|
||||
mydata& obj,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
auto const* end = data + size;
|
||||
auto const* pos = std::find(data, end, '\t');
|
||||
assert(pos != end); // Or use your own error code.
|
||||
|
||||
auto const res1 = std::from_chars(data, pos, obj.a);
|
||||
if (res1.ec != std::errc()) {
|
||||
ec = std::make_error_code(res1.ec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const res2 = std::from_chars(pos + 1, end, obj.b);
|
||||
if (res2.ec != std::errc()) {
|
||||
ec = std::make_error_code(res2.ec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> serialization()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
mydata obj{21, 22};
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "key", obj);
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// The response.
|
||||
mydata get;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(get));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Print the responses.
|
||||
std::cout << "get: a = " << get.a << ", b = " << get.b << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, serialization(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> containers()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::sadd, "key", std::cbegin(set), std::cend(set));
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
int sadd;
|
||||
std::vector<std::string> smembers1;
|
||||
std::set<std::string> smembers2;
|
||||
std::unordered_set<std::string> smembers3;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(sadd));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers1));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers2));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers3));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "sadd: " << sadd;
|
||||
std::cout << "\nsmembers (as vector): ";
|
||||
for (auto const& e: smembers1) std::cout << e << " ";
|
||||
std::cout << "\nsmembers (as set): ";
|
||||
for (auto const& e: smembers2) std::cout << e << " ";
|
||||
std::cout << "\nsmembers (as unordered_set): ";
|
||||
for (auto const& e: smembers3) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, containers(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
/* In this example we send a subscription to a channel and start
|
||||
reading server side messages indefinitely.
|
||||
|
||||
Notice we store the id of the connection (attributed by the redis
|
||||
server) to be able to identify it (in the logs for example).
|
||||
|
||||
After starting the example you can test it by sending messages with
|
||||
redis-cli like this
|
||||
|
||||
$ redis-cli -3
|
||||
127.0.0.1:6379> PUBLISH channel1 some-message
|
||||
(integer) 3
|
||||
127.0.0.1:6379>
|
||||
|
||||
The messages will then appear on the terminal you are running the
|
||||
example.
|
||||
*/
|
||||
net::awaitable<void> subscriber()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, "3");
|
||||
sr.push(command::subscribe, "channel1", "channel2");
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
std::vector<node> resp;
|
||||
|
||||
// Reads the response to the hello command.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
|
||||
// Saves the id of this connection.
|
||||
auto const id = resp.at(8).data;
|
||||
|
||||
// Loops to receive server pushes.
|
||||
for (;;) {
|
||||
resp.clear();
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
|
||||
std::cout
|
||||
<< "Subscriber " << id << ":\n"
|
||||
<< resp << std::endl;
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> transaction()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
auto list = {"one", "two", "three"};
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::multi); // Starts a transaction
|
||||
sr.push(command::ping, "Some message");
|
||||
sr.push(command::incr, "incr1-key");
|
||||
sr.push_range(command::rpush, "list-key", std::cbegin(list), std::cend(list));
|
||||
sr.push(command::lrange, "list-key", 0, -1);
|
||||
sr.push(command::incr, "incr2-key");
|
||||
sr.push(command::exec); // Ends the transaction.
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
|
||||
// Reads the response.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the response to the transaction.
|
||||
std::cout << "ping: " << std::get<0>(execs) << "\n";
|
||||
std::cout << "incr1: " << std::get<1>(execs) << "\n";
|
||||
std::cout << "rpush: " << std::get<2>(execs) << "\n";
|
||||
std::cout << "lrange: ";
|
||||
for (auto const& e: std::get<3>(execs)) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
std::cout << "incr2: " << std::get<4>(execs) << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, transaction(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
1005
m4/ax_cxx_compile_stdcxx.m4
Normal file
1005
m4/ax_cxx_compile_stdcxx.m4
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,38 @@
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <stdlib.h>
|
||||
|
||||
template <class T>
|
||||
void check_equal(T const& a, T const& b, std::string const& msg = "")
|
||||
void expect_eq(T const& a, T const& b, std::string const& msg = "")
|
||||
{
|
||||
if (a == b)
|
||||
if (a == b) {
|
||||
std::cout << "Success: " << msg << std::endl;
|
||||
else
|
||||
} else {
|
||||
std::cout << "Error: " << msg << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void expect_error(boost::system::error_code a, boost::system::error_condition expected = {})
|
||||
{
|
||||
if (a == expected) {
|
||||
if (a)
|
||||
std::cout << "Success: " << a.message() << " (" << a.category().name() << ")" << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: " << a.message() << " (" << a.category().name() << ")" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void check_empty(T const& t)
|
||||
{
|
||||
if (t.empty()) {
|
||||
//std::cout << "Success: " << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: Not empty" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
714
tests/low_level.cpp
Normal file
714
tests/low_level.cpp
Normal file
@@ -0,0 +1,714 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/beast/_experimental/test/stream.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "config.h"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using test_stream = boost::beast::test::stream;
|
||||
using aedis::adapter::adapt;
|
||||
using node_type = aedis::resp3::node<std::string>;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
template <class Result>
|
||||
struct expect {
|
||||
std::string in;
|
||||
Result expected;
|
||||
std::string name;
|
||||
boost::system::error_condition ec;
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
void test_sync(net::any_io_executor ex, expect<Result> e)
|
||||
{
|
||||
std::string rbuffer;
|
||||
test_stream ts {ex};
|
||||
ts.append(e.in);
|
||||
Result result;
|
||||
boost::system::error_code ec;
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt(result), ec);
|
||||
expect_error(ec, e.ec);
|
||||
if (e.ec)
|
||||
return;
|
||||
check_empty(rbuffer);
|
||||
expect_eq(result, e.expected, e.name);
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
class async_test: public std::enable_shared_from_this<async_test<Result>> {
|
||||
private:
|
||||
std::string rbuffer_;
|
||||
test_stream ts_;
|
||||
expect<Result> data_;
|
||||
Result result_;
|
||||
|
||||
public:
|
||||
async_test(net::any_io_executor ex, expect<Result> e)
|
||||
: ts_{ex}
|
||||
, data_{e}
|
||||
{
|
||||
ts_.append(e.in);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
auto self = this->shared_from_this();
|
||||
auto f = [self](auto ec, auto n)
|
||||
{
|
||||
expect_error(ec, self->data_.ec);
|
||||
if (self->data_.ec)
|
||||
return;
|
||||
check_empty(self->rbuffer_);
|
||||
expect_eq(self->result_, self->data_.expected, self->data_.name);
|
||||
};
|
||||
|
||||
resp3::async_read(
|
||||
ts_,
|
||||
net::dynamic_buffer(rbuffer_),
|
||||
adapt(result_),
|
||||
f);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
void test_async(net::any_io_executor ex, expect<Result> e)
|
||||
{
|
||||
std::make_shared<async_test<Result>>(ex, e)->run();
|
||||
}
|
||||
|
||||
void test_number(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<int> ok;
|
||||
ok = 11;
|
||||
|
||||
// Success
|
||||
auto const in01 = expect<node_type>{":3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)"};
|
||||
auto const in02 = expect<node_type>{":-3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)"};
|
||||
auto const in03 = expect<int>{":11\r\n", int{11}, "number.int"};
|
||||
auto const in04 = expect<boost::optional<int>>{":11\r\n", ok, "number.optional.int"};
|
||||
auto const in05 = expect<std::tuple<int>>{"*1\r\n:11\r\n", std::tuple<int>{11}, "number.tuple.int"};
|
||||
auto const in06 = expect<boost::optional<int>>{"%11\r\n", boost::optional<int>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_simple_type)};
|
||||
auto const in07 = expect<std::set<std::string>>{":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in08 = expect<std::unordered_set<std::string>>{":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in09 = expect<std::map<std::string, std::string>>{":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
auto const in10 = expect<std::unordered_map<std::string, std::string>>{":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
auto const in11 = expect<std::list<std::string>>{":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_aggregate)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_bool(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<bool> ok;
|
||||
ok = true;
|
||||
|
||||
// Success.
|
||||
auto const in08 = expect<node_type>{"#f\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)"};
|
||||
auto const in09 = expect<node_type>{"#t\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)"};
|
||||
auto const in10 = expect<bool>{"#t\r\n", bool{true}, "bool.bool (true)"};
|
||||
auto const in11 = expect<bool>{"#f\r\n", bool{false}, "bool.bool (true)"};
|
||||
auto const in13 = expect<boost::optional<bool>>{"#t\r\n", ok, "optional.int"};
|
||||
|
||||
// Error
|
||||
auto const in01 = expect<boost::optional<bool>>{"#11\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_condition(aedis::resp3::error::unexpected_bool_value)};
|
||||
auto const in03 = expect<std::set<int>>{"#t\r\n", std::set<int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in04 = expect<std::unordered_set<int>>{"#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in05 = expect<std::map<int, int>>{"#t\r\n", std::map<int, int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
auto const in06 = expect<std::unordered_map<int, int>>{"#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_streamed_string(net::io_context& ioc)
|
||||
{
|
||||
std::string const wire = "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {aedis::resp3::type::streamed_string_part, 1, 1, "Hell"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "o wor"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "d"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, ""}
|
||||
};
|
||||
|
||||
std::vector<node_type> e1b { {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "streamed_string.node"};
|
||||
auto const in03 = expect<std::string>{wire, std::string{"Hello word"}, "streamed_string.string"};
|
||||
auto const in02 = expect<std::vector<node_type>>{"$?\r\n;0\r\n", e1b, "streamed_string.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
}
|
||||
|
||||
void test_push(net::io_context& ioc)
|
||||
{
|
||||
std::string const wire = ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::push, 4UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "pubsub"}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "message"}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "some-channel"}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "some message"}
|
||||
};
|
||||
|
||||
std::vector<node_type> e1b { {resp3::type::push, 0UL, 0UL, {}} };
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "push.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{">0\r\n", e1b, "push.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_map(net::io_context& ioc)
|
||||
{
|
||||
using map_type = std::map<std::string, std::string>;
|
||||
using mmap_type = std::multimap<std::string, std::string>;
|
||||
using umap_type = std::unordered_map<std::string, std::string>;
|
||||
using mumap_type = std::unordered_multimap<std::string, std::string>;
|
||||
using vec_type = std::vector<std::string>;
|
||||
using op_map_type = boost::optional<std::map<std::string, std::string>>;
|
||||
using op_vec_type = boost::optional<std::vector<std::string>>;
|
||||
using tuple_type = std::tuple<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>;
|
||||
|
||||
std::string const wire = "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n";
|
||||
|
||||
std::vector<node_type> expected_1a
|
||||
{ {resp3::type::map, 4UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key1"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key2"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
|
||||
};
|
||||
|
||||
map_type expected_1b
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
umap_type e1g
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
mmap_type e1k
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
mumap_type e1l
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
std::vector<std::string> expected_1c
|
||||
{ "key1", "value1"
|
||||
, "key2", "value2"
|
||||
, "key3", "value3"
|
||||
, "key3", "value3"
|
||||
};
|
||||
|
||||
op_map_type expected_1d;
|
||||
expected_1d = expected_1b;
|
||||
|
||||
op_vec_type expected_1e;
|
||||
expected_1e = expected_1c;
|
||||
|
||||
tuple_type e1f
|
||||
{ std::string{"key1"}, std::string{"value1"}
|
||||
, std::string{"key2"}, std::string{"value2"}
|
||||
, std::string{"key3"}, std::string{"value3"}
|
||||
, std::string{"key3"}, std::string{"value3"}
|
||||
};
|
||||
|
||||
auto const in00 = expect<std::vector<node_type>>{wire, expected_1a, "map.node"};
|
||||
auto const in01 = expect<map_type>{"%0\r\n", map_type{}, "map.map.empty"};
|
||||
auto const in02 = expect<map_type>{wire, expected_1b, "map.map"};
|
||||
auto const in03 = expect<mmap_type>{wire, e1k, "map.multimap"};
|
||||
auto const in04 = expect<umap_type>{wire, e1g, "map.unordered_map"};
|
||||
auto const in05 = expect<mumap_type>{wire, e1l, "map.unordered_multimap"};
|
||||
auto const in06 = expect<vec_type>{wire, expected_1c, "map.vector"};
|
||||
auto const in07 = expect<op_map_type>{wire, expected_1d, "map.optional.map"};
|
||||
auto const in08 = expect<op_vec_type>{wire, expected_1e, "map.optional.vector"};
|
||||
auto const in09 = expect<std::tuple<op_map_type>>{"*1\r\n" + wire, std::tuple<op_map_type>{expected_1d}, "map.transaction.optional.map"};
|
||||
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_simple_type)};
|
||||
auto const in11 = expect<tuple_type>{wire, e1f, "map.tuple"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_attribute(net::io_context& ioc)
|
||||
{
|
||||
char const* wire = "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::attribute, 1UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "key-popularity"}
|
||||
, {resp3::type::map, 2UL, 1UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 2UL, "a"}
|
||||
, {resp3::type::doublean, 1UL, 2UL, "0.1923"}
|
||||
, {resp3::type::blob_string, 1UL, 2UL, "b"}
|
||||
, {resp3::type::doublean, 1UL, 2UL, "0.0012"}
|
||||
};
|
||||
|
||||
std::vector<node_type> e1b;
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "attribute.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{"|0\r\n", e1b, "attribute.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_array(net::io_context& ioc)
|
||||
{
|
||||
char const* wire = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::array, 3UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
};
|
||||
|
||||
std::vector<int> const e1b{11, 22, 3};
|
||||
std::vector<std::string> const e1c{"11", "22", "3"};
|
||||
std::vector<std::string> const e1d{};
|
||||
std::vector<node_type> const e1e{{resp3::type::array, 0UL, 0UL, {}}};
|
||||
std::array<int, 3> const e1f{11, 22, 3};
|
||||
std::list<int> const e1g{11, 22, 3};
|
||||
std::deque<int> const e1h{11, 22, 3};
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "array.node"};
|
||||
auto const in02 = expect<std::vector<int>>{wire, e1b, "array.int"};
|
||||
auto const in03 = expect<std::vector<node_type>>{"*0\r\n", e1e, "array.node.empty"};
|
||||
auto const in04 = expect<std::vector<std::string>>{"*0\r\n", e1d, "array.string.empty"};
|
||||
auto const in05 = expect<std::vector<std::string>>{wire, e1c, "array.string"};
|
||||
auto const in06 = expect<std::array<int, 3>>{wire, e1f, "array.array"};
|
||||
auto const in07 = expect<std::list<int>>{wire, e1g, "array.list"};
|
||||
auto const in08 = expect<std::deque<int>>{wire, e1h, "array.deque"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
}
|
||||
|
||||
void test_set(net::io_context& ioc)
|
||||
{
|
||||
using set_type = std::set<std::string>;
|
||||
using mset_type = std::multiset<std::string>;
|
||||
using uset_type = std::unordered_set<std::string>;
|
||||
using muset_type = std::unordered_multiset<std::string>;
|
||||
using vec_type = std::vector<std::string>;
|
||||
using op_vec_type = boost::optional<std::vector<std::string>>;
|
||||
|
||||
std::string const wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
|
||||
std::vector<node_type> const expected1a
|
||||
{ {resp3::type::set, 6UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
};
|
||||
|
||||
mset_type const e1f{"apple", "one", "orange", "orange", "three", "two"};
|
||||
uset_type const e1c{"apple", "one", "orange", "three", "two"};
|
||||
muset_type const e1g{"apple", "one", "orange", "orange", "three", "two"};
|
||||
vec_type const e1d = {"orange", "apple", "one", "two", "three", "orange"};
|
||||
op_vec_type expected_1e;
|
||||
expected_1e = e1d;
|
||||
|
||||
auto const in00 = expect<std::vector<node_type>>{wire, expected1a, "set.node"};
|
||||
auto const in01 = expect<std::vector<node_type>>{"~0\r\n", std::vector<node_type>{ {resp3::type::set, 0UL, 0UL, {}} }, "set.node (empty)"};
|
||||
auto const in02 = expect<set_type>{wire, set_type{"apple", "one", "orange", "three", "two"}, "set.set"};
|
||||
auto const in03 = expect<mset_type>{wire, e1f, "set.multiset"};
|
||||
auto const in04 = expect<vec_type>{wire, e1d, "set.vector "};
|
||||
auto const in05 = expect<op_vec_type>{wire, expected_1e, "set.vector "};
|
||||
auto const in06 = expect<uset_type>{wire, e1c, "set.unordered_set"};
|
||||
auto const in07 = expect<muset_type>{wire, e1g, "set.unordered_multiset"};
|
||||
auto const in08 = expect<std::tuple<uset_type>>{"*1\r\n" + wire, std::tuple<uset_type>{e1c}, "set.tuple"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
}
|
||||
|
||||
void test_simple_error(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"-Error\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node"};
|
||||
auto const in02 = expect<node_type>{"-\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_blob_string(net::io_context& ioc)
|
||||
{
|
||||
std::string str(100000, 'a');
|
||||
str[1000] = '\r';
|
||||
str[1001] = '\n';
|
||||
|
||||
std::string wire;
|
||||
wire += '$';
|
||||
wire += std::to_string(str.size());
|
||||
wire += "\r\n";
|
||||
wire += str;
|
||||
wire += "\r\n";
|
||||
|
||||
auto const in01 = expect<node_type>{"$2\r\nhh\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hh"}}, "blob_string.node"};
|
||||
auto const in02 = expect<node_type>{"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}, "blob_string.node (with separator)"};
|
||||
auto const in03 = expect<node_type>{"$0\r\n\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {}}, "blob_string.node.empty"};
|
||||
auto const in04 = expect<node_type>{wire, node_type{resp3::type::blob_string, 1UL, 0UL, {str}}, "blob_string.node (long string)"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
}
|
||||
|
||||
void test_double(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{",1.23\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}, "double.node"};
|
||||
auto const in02 = expect<node_type>{",inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}, "double.node (inf)"};
|
||||
auto const in03 = expect<node_type>{",-inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}, "double.node (-inf)"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
}
|
||||
|
||||
void test_blob_error(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"!21\r\nSYNTAX invalid syntax\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, "blob_error"};
|
||||
auto const in02 = expect<node_type>{"!0\r\n\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {}}, "blob_error.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_verbatim_string(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"=15\r\ntxt:Some string\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}, "verbatim_string"};
|
||||
auto const in02 = expect<node_type>{"=0\r\n\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}, "verbatim_string.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_big_number(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"(3492890328409238509324850943850943825024385\r\n", node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}, "big_number.node"};
|
||||
auto const in02 = expect<int>{"(\r\n", int{}, "big_number.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_simple_string(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<std::string> ok1, ok2;
|
||||
ok1 = "OK";
|
||||
ok2 = "";
|
||||
|
||||
auto in00 = expect<std::string>{"+OK\r\n", std::string{"OK"}, "simple_string.string"};
|
||||
auto in01 = expect<std::string>{"+\r\n", std::string{""}, "simple_string.string.empty"};
|
||||
auto in02 = expect<boost::optional<std::string>>{"+OK\r\n", boost::optional<std::string>{"OK"}, "simple_string.optional"};
|
||||
auto in03 = expect<boost::optional<std::string>>{"+\r\n", boost::optional<std::string>{""}, "simple_string.optional.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
}
|
||||
|
||||
void test_resp3(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<int>{"s11\r\n", int{}, "number.error", aedis::resp3::make_error_condition(aedis::resp3::error::invalid_type)};
|
||||
auto const in02 = expect<int>{":adf\r\n", int{11}, "number.int", aedis::resp3::make_error_condition(aedis::resp3::error::not_a_number)};
|
||||
auto const in03 = expect<int>{":\r\n", int{}, "number.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
auto const in04 = expect<boost::optional<bool>>{"#\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
auto const in05 = expect<std::string>{",\r\n", std::string{}, "double.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
}
|
||||
|
||||
void test_null(net::io_context& ioc)
|
||||
{
|
||||
using op_type_01 = boost::optional<bool>;
|
||||
using op_type_02 = boost::optional<int>;
|
||||
using op_type_03 = boost::optional<std::string>;
|
||||
using op_type_04 = boost::optional<std::vector<std::string>>;
|
||||
using op_type_05 = boost::optional<std::list<std::string>>;
|
||||
using op_type_06 = boost::optional<std::map<std::string, std::string>>;
|
||||
using op_type_07 = boost::optional<std::unordered_map<std::string, std::string>>;
|
||||
using op_type_08 = boost::optional<std::set<std::string>>;
|
||||
using op_type_09 = boost::optional<std::unordered_set<std::string>>;
|
||||
|
||||
auto const in01 = expect<op_type_01>{"_\r\n", op_type_01{}, "null.optional.bool"};
|
||||
auto const in02 = expect<op_type_02>{"_\r\n", op_type_02{}, "null.optional.int"};
|
||||
auto const in03 = expect<op_type_03>{"_\r\n", op_type_03{}, "null.optional.string"};
|
||||
auto const in04 = expect<op_type_04>{"_\r\n", op_type_04{}, "null.optional.vector"};
|
||||
auto const in05 = expect<op_type_05>{"_\r\n", op_type_05{}, "null.optional.list"};
|
||||
auto const in06 = expect<op_type_06>{"_\r\n", op_type_06{}, "null.optional.map"};
|
||||
auto const in07 = expect<op_type_07>{"_\r\n", op_type_07{}, "null.optional.unordered_map"};
|
||||
auto const in08 = expect<op_type_08>{"_\r\n", op_type_08{}, "null.optional.set"};
|
||||
auto const in09 = expect<op_type_09>{"_\r\n", op_type_09{}, "null.optional.unordered_set"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc {1};
|
||||
|
||||
// Simple types.
|
||||
test_simple_string(ioc);
|
||||
test_simple_error(ioc);
|
||||
test_blob_string(ioc);
|
||||
test_blob_error(ioc);
|
||||
test_number(ioc);
|
||||
test_double(ioc);
|
||||
test_bool(ioc);
|
||||
test_null(ioc);
|
||||
test_big_number(ioc);
|
||||
test_verbatim_string(ioc);
|
||||
|
||||
// Aggregates.
|
||||
test_array(ioc);
|
||||
test_set(ioc);
|
||||
test_map(ioc);
|
||||
test_push(ioc);
|
||||
test_streamed_string(ioc);
|
||||
|
||||
// RESP3
|
||||
test_resp3(ioc);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -1,648 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include <aedis/src.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
#include "test_stream.hpp"
|
||||
#include "check.hpp"
|
||||
|
||||
// Consider using Beast test_stream and instantiate the test socket
|
||||
// only once.
|
||||
|
||||
namespace net = aedis::net;
|
||||
using tcp = net::ip::tcp;
|
||||
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
|
||||
|
||||
namespace this_coro = net::this_coro;
|
||||
|
||||
using namespace aedis;
|
||||
using namespace aedis::resp3;
|
||||
|
||||
std::vector<node> gresp;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
void simple_string_sync()
|
||||
{
|
||||
test_tcp_socket ts {"+OK\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
|
||||
check_equal(result, expected, "simple_string (node-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::string expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (string-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (optional-string-sync)");
|
||||
}
|
||||
}
|
||||
|
||||
void simple_string_sync_empty()
|
||||
{
|
||||
test_tcp_socket ts {"+\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
|
||||
check_equal(result, expected, "simple_string (empty-node-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::string expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-string-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-optional-string-sync)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> simple_string_async()
|
||||
{
|
||||
test_tcp_socket ts {"+OK\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
|
||||
check_equal(result, expected, "simple_string (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::string expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (string-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (optional-string-async)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> simple_string_async_empty()
|
||||
{
|
||||
test_tcp_socket ts {"+\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
|
||||
check_equal(result, expected, "simple_string (empty-node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::string expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-string-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-optional-string-async)");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Test a large simple string. For example
|
||||
// std::string str(10000, 'a');
|
||||
// std::string cmd;
|
||||
// cmd += '+';
|
||||
// cmd += str;
|
||||
// cmd += "\r\n";
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> test_simple_error_async()
|
||||
{
|
||||
std::string buf;
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"-Error\r\n"};
|
||||
node result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
node expected {resp3::type::simple_error, 1UL, 0UL, {"Error"}};
|
||||
check_equal(result, expected, "simple_error (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"-Error\r\n"};
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
std::string expected {"Error"};
|
||||
check_equal(result, expected, "simple_error (string-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"-\r\n"};
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
std::string expected {""};
|
||||
check_equal(result, expected, "simple_error (empty-string-async)");
|
||||
}
|
||||
|
||||
// TODO: Test with optional.
|
||||
// TODO: Test with a very long string?
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> test_number()
|
||||
{
|
||||
std::string buf;
|
||||
|
||||
{
|
||||
test_tcp_socket ts {":-3\r\n"};
|
||||
node result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
node expected {resp3::type::number, 1UL, 0UL, {"-3"}};
|
||||
check_equal(result, expected, "number (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {":-3\r\n"};
|
||||
long long result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
check_equal(result, -3LL, "number (signed-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {":3\r\n"};
|
||||
std::size_t result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
check_equal(result, 3UL, "number (unsigned-async)");
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> array_async()
|
||||
{
|
||||
test_tcp_socket ts {"*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n"};
|
||||
std::string buf;
|
||||
auto dbuf = net::dynamic_buffer(buf);
|
||||
|
||||
{
|
||||
std::vector<node> result;
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::array, 3UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
};
|
||||
|
||||
check_equal(result, expected, "array (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<int> result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
|
||||
std::vector<int> expected {11, 22, 3};
|
||||
check_equal(result, expected, "array (int-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"*0\r\n"};
|
||||
|
||||
std::vector<int> result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
|
||||
std::vector<int> expected;
|
||||
check_equal(result, expected, "array (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> test_blob_string()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"$2\r\nhh\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {"hh"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_string");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_string (with separator)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"$0\r\n\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_string (size 0)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_floating_point()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {",1.23\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::doublean, 1UL, 0UL, {"1.23"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
check_equal(resp, expected, "double");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {",inf\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::doublean, 1UL, 0UL, {"inf"}} };
|
||||
check_equal(resp, expected, "double (inf)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {",-inf\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::doublean, 1UL, 0UL, {"-inf"}} };
|
||||
check_equal(resp, expected, "double (-inf)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
net::awaitable<void> test_boolean()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"#f\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::boolean, 1UL, 0UL, {"f"}} };
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
check_equal(resp, expected, "bool (false)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"#t\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::boolean, 1UL, 0UL, {"t"}} };
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
check_equal(resp, expected, "bool (true)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_blob_error()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"!21\r\nSYNTAX invalid syntax\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_error (message)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"!0\r\n\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_error, 1UL, 0UL, {}} };
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_error (empty message)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_verbatim_string()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"=15\r\ntxt:Some string\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "verbatim_string");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"=0\r\n\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::verbatim_string, 1UL, 0UL, {}} };
|
||||
check_equal(gresp, expected, "verbatim_string (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_set2()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"~5\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::set, 5UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
|
||||
};
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "test set (1)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"~0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::set, 0UL, 0UL, {}}
|
||||
};
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "test set (2)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_flat_map_async()
|
||||
{
|
||||
std::string buf;
|
||||
auto dbuf = net::dynamic_buffer(buf);
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"%3\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n"};
|
||||
std::map<std::string, std::string> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
|
||||
std::map<std::string, std::string> expected
|
||||
{ {"server", "redis"}
|
||||
, {"version", "6.0.9"}
|
||||
, {"proto", "3"}
|
||||
};
|
||||
|
||||
check_equal(result, expected, "map (flat-map-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"%0\r\n"};
|
||||
std::map<std::string, std::string> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
std::map<std::string, std::string> expected;
|
||||
check_equal(result, expected, "map (flat-empty-map-async)");
|
||||
}
|
||||
|
||||
{
|
||||
// TODO: Why do we get a crash when %3. It should produce an
|
||||
// error instead.
|
||||
test_tcp_socket ts {"%2\r\n$6\r\nserver\r\n$2\r\n10\r\n$7\r\nversion\r\n$2\r\n30\r\n"};
|
||||
std::map<std::string, int> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
|
||||
std::map<std::string, int> expected
|
||||
{ {"server", 10}
|
||||
, {"version", 30}
|
||||
};
|
||||
|
||||
check_equal(result, expected, "map (flat-map-string-int-async)");
|
||||
}
|
||||
|
||||
// TODO: Test optional map.
|
||||
// TODO: Test serializaition with different key and value types.
|
||||
}
|
||||
|
||||
net::awaitable<void> test_map()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"%7\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n$2\r\nid\r\n:203\r\n$4\r\nmode\r\n$10\r\nstandalone\r\n$4\r\nrole\r\n$6\r\nmaster\r\n$7\r\nmodules\r\n*0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::map, 7UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"server"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"redis"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"version"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"6.0.9"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"proto"}}
|
||||
, {resp3::type::number, 1UL, 1UL, {"3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"id"}}
|
||||
, {resp3::type::number, 1UL, 1UL, {"203"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"mode"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"standalone"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"role"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"master"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"modules"}}
|
||||
, {resp3::type::array, 0UL, 1UL, {}}
|
||||
};
|
||||
check_equal(gresp, expected, "test map");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"%0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::map, 0UL, 0UL, {}} };
|
||||
check_equal(gresp, expected, "test map (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_streamed_string()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::streamed_string_part, 1UL, 0UL, {"Hello world"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "streamed string");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"$?\r\n;0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::streamed_string_part, 1UL, 0UL, {}} };
|
||||
check_equal(resp, expected, "streamed string (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
//net::awaitable<void> offline()
|
||||
//{
|
||||
// std::string buf;
|
||||
// //{
|
||||
// // std::string cmd {"|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n"};
|
||||
// // test_tcp_socket ts {cmd};
|
||||
// // resp3::flat_radapter res;
|
||||
// // co_await async_read(ts, buf, res);
|
||||
// // check_equal(res.result, {"key-popularity", "a", "0.1923", "b", "0.0012"}, "attribute");
|
||||
// //}
|
||||
//
|
||||
// //{
|
||||
// // std::string cmd {">4\r\n+pubsub\r\n+message\r\n+foo\r\n+bar\r\n"};
|
||||
// // test_tcp_socket ts {cmd};
|
||||
// // resp3::flat_radapter res;
|
||||
// // co_await async_read(ts, buf, res);
|
||||
// // check_equal(res.result, {"pubsub", "message", "foo", "bar"}, "push type");
|
||||
// //}
|
||||
//
|
||||
// //{
|
||||
// // std::string cmd {">0\r\n"};
|
||||
// // test_tcp_socket ts {cmd};
|
||||
// // resp3::flat_radapter res;
|
||||
// // co_await async_read(ts, buf, res);
|
||||
// // check_equal(res.result, {}, "push type (empty)");
|
||||
// //}
|
||||
//}
|
||||
|
||||
net::awaitable<void> optional_async()
|
||||
{
|
||||
test_tcp_socket ts {"_\r\n"};
|
||||
std::string buf;
|
||||
auto dbuf = net::dynamic_buffer(buf);
|
||||
|
||||
{
|
||||
node result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
node expected {resp3::type::null, 1UL, 0UL, {""}};
|
||||
check_equal(result, expected, "optional (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
int result;
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
//auto const expected = make_error_code(adapter::error::null);
|
||||
//std::cout << expected.message() << std::endl;
|
||||
// TODO: Convert to std::error_code.
|
||||
//check_equal(ec, expected, "optional (int-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<int> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
std::optional<int> expected;
|
||||
check_equal(result, expected, "optional (optional-int-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
std::optional<std::string> expected;
|
||||
check_equal(result, expected, "optional (optional-int-async)");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
simple_string_sync();
|
||||
simple_string_sync_empty();
|
||||
|
||||
net::io_context ioc {1};
|
||||
|
||||
co_spawn(ioc, simple_string_async(), net::detached);
|
||||
co_spawn(ioc, simple_string_async_empty(), net::detached);
|
||||
co_spawn(ioc, test_simple_error_async(), net::detached);
|
||||
co_spawn(ioc, test_number(), net::detached);
|
||||
co_spawn(ioc, test_map(), net::detached);
|
||||
co_spawn(ioc, test_flat_map_async(), net::detached);
|
||||
co_spawn(ioc, optional_async(), net::detached);
|
||||
|
||||
co_spawn(ioc, array_async(), net::detached);
|
||||
co_spawn(ioc, test_blob_string(), net::detached);
|
||||
co_spawn(ioc, test_floating_point(), net::detached);
|
||||
co_spawn(ioc, test_boolean(), net::detached);
|
||||
co_spawn(ioc, test_blob_error(), net::detached);
|
||||
co_spawn(ioc, test_verbatim_string(), net::detached);
|
||||
co_spawn(ioc, test_set2(), net::detached);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
356
tests/online.cpp
356
tests/online.cpp
@@ -8,31 +8,35 @@
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "test_stream.hpp"
|
||||
|
||||
#include "check.hpp"
|
||||
|
||||
namespace net = aedis::net;
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::adapter::adapt;
|
||||
using node_type = aedis::resp3::node<std::string>;
|
||||
using tcp = net::ip::tcp;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<tcp::socket>;
|
||||
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
|
||||
|
||||
namespace this_coro = net::this_coro;
|
||||
|
||||
using namespace aedis;
|
||||
using namespace aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
|
||||
std::vector<node> gresp;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
std::vector<node_type> gresp;
|
||||
|
||||
net::awaitable<void>
|
||||
test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
std::vector<int> list_ {1 ,2, 3, 4, 5, 6};
|
||||
std::string set_ {"aaa"};
|
||||
@@ -42,7 +46,7 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "a", std::cbegin(list_), std::cend(list_));
|
||||
sr.push_range(command::rpush, "a", list_);
|
||||
sr.push(command::llen, "a");
|
||||
sr.push(command::lrange, "a", 0, -1);
|
||||
sr.push(command::ltrim, "a", 2, -2);
|
||||
@@ -71,7 +75,7 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
{ {"field1", "value1"}
|
||||
, {"field2", "value2"}};
|
||||
|
||||
sr.push_range(command::hset, "d", std::cbegin(m1), std::cend(m1));
|
||||
sr.push_range(command::hset, "d", m1);
|
||||
sr.push(command::hget, "d", "field2");
|
||||
sr.push(command::hgetall, "d");
|
||||
sr.push(command::hdel, "d", "field1", "field2"); // TODO: Test as range too.
|
||||
@@ -83,7 +87,7 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
sr.push(command::zremrangebyscore, "f", "-inf", "+inf");
|
||||
|
||||
auto const v = std::vector<int>{1, 2, 3};
|
||||
sr.push_range(command::sadd, "g", std::cbegin(v), std::cend(v));
|
||||
sr.push_range(command::sadd, "g", v);
|
||||
sr.push(command::smembers, "g");
|
||||
sr.push(command::quit);
|
||||
//----------------------------------
|
||||
@@ -102,28 +106,28 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(), net::use_awaitable);
|
||||
|
||||
{ // rpush:
|
||||
std::vector<node> resp;
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
auto const n = std::to_string(std::size(list_));
|
||||
std::vector<node> expected
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::number, 1UL, 0UL, n} };
|
||||
|
||||
check_equal(resp, expected, "rpush (value)");
|
||||
expect_eq(resp, expected, "rpush (value)");
|
||||
}
|
||||
|
||||
{ // llen
|
||||
std::vector<node> resp;
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
std::vector<node> expected
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::number, 1UL, 0UL, {"6"}} };
|
||||
check_equal(resp, expected, "llen");
|
||||
expect_eq(resp, expected, "llen");
|
||||
}
|
||||
|
||||
{ // lrange
|
||||
std::vector<node> resp;
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node> expected
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::array, 6UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"1"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"2"}}
|
||||
@@ -133,47 +137,47 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"6"}}
|
||||
};
|
||||
|
||||
check_equal(resp, expected, "lrange ");
|
||||
expect_eq(resp, expected, "lrange ");
|
||||
}
|
||||
|
||||
{ // ltrim
|
||||
std::vector<node> resp;
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node> expected
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
check_equal(resp, expected, "ltrim");
|
||||
expect_eq(resp, expected, "ltrim");
|
||||
}
|
||||
|
||||
{ // lpop
|
||||
std::vector<node> resp;
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node> expected
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
|
||||
|
||||
check_equal(resp, expected, "lpop");
|
||||
expect_eq(resp, expected, "lpop");
|
||||
}
|
||||
|
||||
//{ // lpop
|
||||
// std::vector<node> resp;
|
||||
// std::vector<node_type> resp;
|
||||
// co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 2UL, 0UL, {}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"4"}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"5"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "lpop");
|
||||
// expect_eq(resp, expected, "lpop");
|
||||
//}
|
||||
|
||||
//{ // lrange
|
||||
// static int c = 0;
|
||||
|
||||
// if (c == 0) {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 6UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
|
||||
@@ -183,12 +187,12 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"6"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "lrange ");
|
||||
// expect_eq(resp, expected, "lrange ");
|
||||
// } else {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
|
||||
|
||||
// check_equal(resp, expected, "lrange (inside transaction)");
|
||||
// expect_eq(resp, expected, "lrange (inside transaction)");
|
||||
// }
|
||||
//
|
||||
// ++c;
|
||||
@@ -198,154 +202,154 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
// switch (cmd) {
|
||||
// case command::multi:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "multi");
|
||||
// expect_eq(resp, expected, "multi");
|
||||
// } break;
|
||||
// case command::ping:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
|
||||
|
||||
// check_equal(resp, expected, "ping");
|
||||
// expect_eq(resp, expected, "ping");
|
||||
// } break;
|
||||
// case command::set:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "set");
|
||||
// expect_eq(resp, expected, "set");
|
||||
// } break;
|
||||
// case command::quit:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "quit");
|
||||
// expect_eq(resp, expected, "quit");
|
||||
// } break;
|
||||
// case command::flushall:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "flushall");
|
||||
// expect_eq(resp, expected, "flushall");
|
||||
// } break;
|
||||
// case command::append:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"4"}} };
|
||||
|
||||
// check_equal(resp, expected, "append");
|
||||
// expect_eq(resp, expected, "append");
|
||||
// } break;
|
||||
// case command::hset:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
|
||||
|
||||
// check_equal(resp, expected, "hset");
|
||||
// expect_eq(resp, expected, "hset");
|
||||
// } break;
|
||||
// case command::del:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "del");
|
||||
// expect_eq(resp, expected, "del");
|
||||
// } break;
|
||||
// case command::incr:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "incr");
|
||||
// expect_eq(resp, expected, "incr");
|
||||
// } break;
|
||||
// case command::publish:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "publish");
|
||||
// expect_eq(resp, expected, "publish");
|
||||
// } break;
|
||||
// case command::hincrby:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"10"}} };
|
||||
|
||||
// check_equal(resp, expected, "hincrby");
|
||||
// expect_eq(resp, expected, "hincrby");
|
||||
// } break;
|
||||
// case command::zadd:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "zadd");
|
||||
// expect_eq(resp, expected, "zadd");
|
||||
// } break;
|
||||
// case command::sadd:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"3"}} };
|
||||
|
||||
// check_equal(resp, expected, "sadd");
|
||||
// expect_eq(resp, expected, "sadd");
|
||||
// } break;
|
||||
// case command::hdel:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
|
||||
|
||||
// check_equal(resp, expected, "hdel");
|
||||
// expect_eq(resp, expected, "hdel");
|
||||
// } break;
|
||||
// case command::zremrangebyscore:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "zremrangebyscore");
|
||||
// expect_eq(resp, expected, "zremrangebyscore");
|
||||
// } break;
|
||||
// case command::get:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, test.set_} };
|
||||
|
||||
// check_equal(resp, expected, "get");
|
||||
// expect_eq(resp, expected, "get");
|
||||
// } break;
|
||||
// case command::hget:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, std::string{"value2"}} };
|
||||
|
||||
// check_equal(resp, expected, "hget");
|
||||
// expect_eq(resp, expected, "hget");
|
||||
// } break;
|
||||
// case command::hvals:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 2UL, 0UL, {}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"value1"}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"value2"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "hvals");
|
||||
// expect_eq(resp, expected, "hvals");
|
||||
// } break;
|
||||
// case command::zrange:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 1UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "hvals");
|
||||
// expect_eq(resp, expected, "hvals");
|
||||
// } break;
|
||||
// case command::zrangebyscore:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 1UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "zrangebyscore");
|
||||
// expect_eq(resp, expected, "zrangebyscore");
|
||||
// } break;
|
||||
// case command::exec:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 3UL, 0UL, {}}
|
||||
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
|
||||
// , {resp3::type::array, 2UL, 1UL, {}}
|
||||
@@ -354,29 +358,29 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "transaction");
|
||||
// expect_eq(resp, expected, "transaction");
|
||||
|
||||
// } break;
|
||||
// case command::hgetall:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::map, 2UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"field1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"field2"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
|
||||
// };
|
||||
// check_equal(resp, expected, "hgetall (value)");
|
||||
// expect_eq(resp, expected, "hgetall (value)");
|
||||
// } break;
|
||||
// case command::smembers:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::set, 3UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
// };
|
||||
// check_equal(resp, expected, "smembers (value)");
|
||||
// expect_eq(resp, expected, "smembers (value)");
|
||||
// } break;
|
||||
// default: { std::cout << "Error: " << resp.front().data_type << " " << cmd << std::endl; }
|
||||
// }
|
||||
@@ -387,194 +391,12 @@ test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
//net::awaitable<void>
|
||||
//test_list(net::ip::tcp::resolver::results_type const& results)
|
||||
//{
|
||||
// std::vector<int> list {1 ,2, 3, 4, 5, 6};
|
||||
//
|
||||
// resp3::serializer p;
|
||||
// p.push(command::hello, 3);
|
||||
// p.push(command::flushall);
|
||||
// p.push_range(command::rpush, "a", std::cbegin(list), std::cend(list));
|
||||
// p.push(command::lrange, "a", 0, -1);
|
||||
// p.push(command::lrange, "a", 2, -2);
|
||||
// p.push(command::ltrim, "a", 2, -2);
|
||||
// p.push(command::lpop, "a");
|
||||
// p.push(command::quit);
|
||||
//
|
||||
// auto ex = co_await this_coro::executor;
|
||||
// tcp_socket socket {ex};
|
||||
// co_await async_connect(socket, results);
|
||||
// co_await async_write(socket, net::buffer(p.payload));
|
||||
// std::string buf;
|
||||
//
|
||||
// { // hello
|
||||
// gresp.clear();
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// }
|
||||
//
|
||||
// { // flushall
|
||||
// gresp.clear();
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
// check_equal(gresp, expected, "flushall");
|
||||
// }
|
||||
//
|
||||
// { // rpush
|
||||
// gresp.clear();
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"6"}} };
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// check_equal(gresp, expected, "rpush");
|
||||
// }
|
||||
//
|
||||
// { // lrange
|
||||
// resp3::flat_array_int_type buffer;
|
||||
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
|
||||
// co_await async_read(socket, buf, res);
|
||||
// check_equal(buffer, list, "lrange-1");
|
||||
// }
|
||||
//
|
||||
// { // lrange
|
||||
// resp3::flat_array_int_type buffer;
|
||||
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
|
||||
// co_await async_read(socket, buf, res);
|
||||
// check_equal(buffer, std::vector<int>{3, 4, 5}, "lrange-2");
|
||||
// }
|
||||
//
|
||||
// { // ltrim
|
||||
// gresp.clear();
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
//
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// check_equal(gresp, expected, "ltrim");
|
||||
// }
|
||||
//
|
||||
// { // lpop. Why a blob string instead of a number?
|
||||
// gresp.clear();
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
|
||||
//
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// check_equal(gresp, expected, "lpop");
|
||||
// }
|
||||
//
|
||||
// { // quit
|
||||
// gresp.clear();
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
// check_equal(gresp, expected, "ltrim");
|
||||
// }
|
||||
//}
|
||||
|
||||
std::string test_bulk1(10000, 'a');
|
||||
|
||||
net::awaitable<void>
|
||||
test_set(net::ip::tcp::resolver::results_type const& results)
|
||||
{
|
||||
using namespace aedis;
|
||||
|
||||
// Tests whether the parser can handle payloads that contain the separator.
|
||||
test_bulk1[30] = '\r';
|
||||
test_bulk1[31] = '\n';
|
||||
|
||||
std::string test_bulk2 = "aaaaa";
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "s", test_bulk1);
|
||||
sr.push(command::get, "s");
|
||||
sr.push(command::set, "s", test_bulk2);
|
||||
sr.push(command::get, "s");
|
||||
sr.push(command::set, "s", "");
|
||||
sr.push(command::get, "s");
|
||||
sr.push(command::quit);
|
||||
|
||||
auto ex = co_await this_coro::executor;
|
||||
tcp_socket socket {ex};
|
||||
co_await async_connect(socket, results);
|
||||
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
std::string buf;
|
||||
{ // hello, flushall
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
}
|
||||
|
||||
{ // set
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "set1");
|
||||
}
|
||||
|
||||
{ // get
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk1} };
|
||||
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "get1");
|
||||
}
|
||||
|
||||
{ // set
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "ltrim");
|
||||
}
|
||||
|
||||
{ // get
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk2} };
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "get2");
|
||||
}
|
||||
|
||||
{ // set
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "set3");
|
||||
}
|
||||
|
||||
{ // get
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
|
||||
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "get3");
|
||||
}
|
||||
|
||||
{ // quit
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "quit");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc {1};
|
||||
tcp::resolver resv(ioc);
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
|
||||
//co_spawn(ioc, test_list(res), net::detached);
|
||||
co_spawn(ioc, test_set(res), net::detached);
|
||||
co_spawn(ioc, test_general(res), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
struct initiate_async_receive {
|
||||
using executor_type = aedis::net::system_executor;
|
||||
|
||||
std::string::const_iterator pbegin;
|
||||
std::string::const_iterator pend;
|
||||
|
||||
executor_type get_executor() noexcept
|
||||
{ return aedis::net::system_executor(); }
|
||||
|
||||
template <
|
||||
class ReadHandler,
|
||||
class MutableBufferSequence>
|
||||
void operator()(
|
||||
ReadHandler&& handler,
|
||||
MutableBufferSequence const& buffers)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if (std::size(buffers) == 0) {
|
||||
handler(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto begin = boost::asio::buffer_sequence_begin(buffers);
|
||||
auto end = boost::asio::buffer_sequence_end(buffers);
|
||||
//std::cout << "Buffers size: " << std::size(buffers) << std::endl;
|
||||
|
||||
std::size_t transferred = 0;
|
||||
while (begin != end) {
|
||||
//std::cout << "Buffer size: " << std::ssize(*begin) << std::endl;
|
||||
auto const min = std::min(std::ssize(*begin), pend - pbegin);
|
||||
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
|
||||
std::advance(pbegin, min);
|
||||
transferred += min;
|
||||
++begin;
|
||||
}
|
||||
|
||||
handler(ec, transferred);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Executor>
|
||||
struct test_stream {
|
||||
std::string const payload;
|
||||
|
||||
using executor_type = Executor;
|
||||
|
||||
template<
|
||||
class MutableBufferSequence,
|
||||
class ReadHandler =
|
||||
aedis::net::default_completion_token_t<executor_type>
|
||||
>
|
||||
auto async_read_some(
|
||||
MutableBufferSequence const& buffers,
|
||||
ReadHandler&& handler = net::default_completion_token_t<executor_type>{})
|
||||
{
|
||||
return aedis::net::async_initiate<ReadHandler,
|
||||
void (boost::system::error_code, std::size_t)>(
|
||||
initiate_async_receive
|
||||
{std::cbegin(payload), std::cend(payload)},
|
||||
handler, buffers);
|
||||
}
|
||||
|
||||
template<class MutableBufferSequence>
|
||||
std::size_t read_some(
|
||||
MutableBufferSequence const& buffers,
|
||||
boost::system::error_code&)
|
||||
{
|
||||
if (std::size(buffers) == 0)
|
||||
return 0;
|
||||
|
||||
boost::system::error_code ec;
|
||||
auto pbegin = std::cbegin(payload);
|
||||
auto pend = std::cend(payload);
|
||||
|
||||
auto begin = boost::asio::buffer_sequence_begin(buffers);
|
||||
auto end = boost::asio::buffer_sequence_end(buffers);
|
||||
|
||||
std::size_t transferred = 0;
|
||||
while (begin != end) {
|
||||
auto const min = std::min(std::ssize(*begin), pend - pbegin);
|
||||
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
|
||||
std::advance(pbegin, min);
|
||||
transferred += min;
|
||||
++begin;
|
||||
}
|
||||
|
||||
return transferred;
|
||||
}
|
||||
|
||||
executor_type get_executor() noexcept
|
||||
{ return aedis::net::system_executor(); }
|
||||
|
||||
template<class Executor1>
|
||||
struct rebind_executor {
|
||||
using other = test_stream<Executor1>;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -9,16 +9,18 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
@@ -32,17 +34,17 @@ std::string toupper(std::string s)
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
get_cmd_names(std::vector<node> const& resp)
|
||||
get_cmd_names(std::vector<node<std::string>> const& resp)
|
||||
{
|
||||
if (std::empty(resp)) {
|
||||
if (resp.empty()) {
|
||||
std::cerr << "Response is empty." << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> ret;
|
||||
for (auto i = 0ULL; i < std::size(resp); ++i) {
|
||||
for (auto i = 0ULL; i < resp.size(); ++i) {
|
||||
if (resp.at(i).depth == 1)
|
||||
ret.push_back(resp.at(i + 1).data);
|
||||
ret.push_back(resp.at(i + 1).value);
|
||||
}
|
||||
|
||||
std::sort(std::begin(ret), std::end(ret));
|
||||
@@ -58,7 +60,7 @@ void print_cmds_enum(std::vector<std::string> const& cmds)
|
||||
<< " " << cmd << ",\n";
|
||||
}
|
||||
|
||||
std::cout << " unknown\n};\n";
|
||||
std::cout << " invalid\n};\n";
|
||||
}
|
||||
|
||||
void print_cmds_strs(std::vector<std::string> const& cmds)
|
||||
@@ -78,7 +80,7 @@ int main()
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp::socket socket{ioc};
|
||||
connect(socket, res);
|
||||
net::connect(socket, res);
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
@@ -87,7 +89,7 @@ int main()
|
||||
sr.push(command::quit);
|
||||
write(socket, buffer(request));
|
||||
|
||||
std::vector<node> resp;
|
||||
std::vector<node<std::string>> resp;
|
||||
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer));
|
||||
|
||||
Reference in New Issue
Block a user