diff --git a/aedis/aedis.hpp b/aedis/aedis.hpp index ea789d3a..cd6e24c2 100644 --- a/aedis/aedis.hpp +++ b/aedis/aedis.hpp @@ -25,8 +25,8 @@ \section Overview Aedis is a low-level redis client library that provides simple and - efficient communication with a Redis server. It is built on top of - Boost.Asio and some of its distinctive features are + efficient communication with a Redis server (see https://redis.io/). + It is built on top of Boost.Asio and some of its distinctive features are 1. Support for the latest version of the Redis communication protocol RESP3. 2. Asynchronous interface that handles servers pushs optimally. @@ -57,53 +57,134 @@ implementation of the \c receiver class, to make that simpler, Aedis provides a base receiver class that abstracts most of the complexity away from the user. In a typical implementation the - following two function will have to be implemented + following functions will be overriden by the user @code class myreceiver : receiver { public: - void on_read_impl(command cmd) override { ... } - void on_push_impl() override { ... } + void on_read_impl(command cmd) override + { + // ... + } + + void on_write_impl(std::size_t n) override + { + // ... + } + + void on_push_impl() override + { + // ... + } }; @endcode - Sending commands to Redis is also simple, for example + The \c client object (\c db above) can be passed + around in the program to send commands to Redis, for example @code - 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"); - db.>send(command::quit); + void foo(client& 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 - See Tutorial for more details on how to use the library. - For more information about Redis see https://redis.io/ - - \section tutorial Tutorial - - In the last section we have seen the general structure of an Aedis - program. Here we will go in depth. + Now let us see how to make requests and receive responses with more detail. \subsection Requests - A request is composed of one or more commands (in Redis + Request are composed of one or more commands (in Redis documentation they are called pipeline, see https://redis.io/topics/pipelining). The individual commands in a - request assume different forms, for example + request assume many different forms + + 1. With key: \c set, \c get, \c expire etc. + 2. Without key: \c hello, \c quit etc. + 3. Ranges with a key: \c rpush, \c hgtall etc. + 4. Ranges without a key: \c subscribe, etc. + 5. etc. + + To account for all these possibilities, the \c client class offer + some member functions, for example @code + // Some data to send to Redis. + std::string value = "some value"; + + std::list list {"channel1", "channel2", "channel3"}; + + std::map map + { {"key1", "value1"} + , {"key2", "value2"} + , {"key3", "value3"}}; + + // No key or arguments. db.>send(command::quit); - db.>send(command::subscribe, "channel1", "channel2"); - db_->send_range(command::hset, "key", std::cbegin(map), std::cend(map)); + + // With key and arguments. + db.>send(command::set, "key", value, "EX", "2"); + + // Sends a container, no key + db.>send_range(command::subscribe, list); + + // As above but an iterator range. + db->send_range2(command::subscribe, std::cbegin(list), std::cend(list)); + + // Sends a container, with key. + db->send_range(command::hset, "key", map); + + // As above but as iterator range. + db->send_range2(command::hset, "key", std::cbegin(map), std::cend(map)); @endcode + + The \c send functions above adds commands to the output queue and + send only if there is no pending response of a previously sent + command. Users can also easily send their own data types to Redis + by defining a to_string function, for example + + @code + struct mystruct { + int a; + int b; + }; + std::string to_string(mystruct const& obj) + { + // Convert to string + } + + std::map map + { {"key1", {1, 2}} + , {"key2", {3, 4}} + , {"key3", {5, 6}}}; + + db.send_range(command::hset, "serialization-hset-key", map); + @endcode + + See https://redis.io/commands/hset for \c hset documentation. \subsection Responses + + The Redis protocol RESP3 defines two types of data, they are. + + 1. Simple data types: simple string, simple error, \c blob string, \c blob error, number, double, etc. + 2. Aggregates: array, push, set, map, etc. + + Most of these types can be translated into C++ containers and bilt-in types, for example + + 1. \c std::string: simple and blob string and error. + 2. `long long int`: number + 3. \c double: double. + https://redis.io/topics/data-types. + \subsubsection Optional - \subsubsection Serializaiton \subsubsection Transactions + \subsubsection Serializaiton See also https://redis.io/topics/transactions. diff --git a/aedis/redis/client.hpp b/aedis/redis/client.hpp index dc01de38..4c07d38d 100644 --- a/aedis/redis/client.hpp +++ b/aedis/redis/client.hpp @@ -12,7 +12,7 @@ #include #include -// TODO: What to do if a users send a discard not contained in a +// 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. @@ -203,8 +203,10 @@ public: timer_.cancel_one(); } + /** \brief Sends and iterator range (overload with key). + */ template - void send_range(command cmd, Key const& key, ForwardIterator begin, ForwardIterator end) + void send_range2(command cmd, Key const& key, ForwardIterator begin, ForwardIterator end) { if (begin == end) return; @@ -227,6 +229,54 @@ public: timer_.cancel_one(); } + /** \brief Sends and iterator range (overload without key). + */ + template + void send_range2(command cmd, ForwardIterator begin, ForwardIterator end) + { + if (begin == end) + return; + + auto const can_write = prepare_next(); + + auto sr = redis::make_serializer(requests_); + auto const before = std::size(requests_); + sr.push_range(cmd, begin, end); + auto const after = std::size(requests_); + 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 Sends a range. + */ + template + 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 Sends a range. + */ + template + 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. + */ template < class Receiver, class CompletionToken = default_completion_token_type diff --git a/aedis/resp3/detail/composer.hpp b/aedis/resp3/detail/composer.hpp index 15426ad1..2c83d992 100644 --- a/aedis/resp3/detail/composer.hpp +++ b/aedis/resp3/detail/composer.hpp @@ -11,6 +11,14 @@ #include #include +// TODO: The signature from to_string should be changed from +// +// std::string to_string(T const&) +// to +// +// void to_string(std::string& s, T const&) +// + namespace aedis { namespace resp3 { namespace detail { @@ -63,8 +71,8 @@ void add_bulk(Storage& to, T const& data, typename std::enable_if void add_bulk(Storage& to, std::pair const& pair) { diff --git a/doc/aedis.css b/doc/aedis.css index 3c6054e2..235c3b62 100644 --- a/doc/aedis.css +++ b/doc/aedis.css @@ -24,9 +24,7 @@ div.contents { padding: 15px; } -/* code { - background-color:#EFD25E; + background-color:#f0e9ce; } -*/ diff --git a/examples/aggregates.cpp b/examples/aggregates.cpp index 0c76a565..421ba8fe 100644 --- a/examples/aggregates.cpp +++ b/examples/aggregates.cpp @@ -54,9 +54,9 @@ private: {"one", "two", "three", "four"}; // Sends the stl containers. - db_->send_range(command::hset, "hset-key", std::cbegin(map), std::cend(map)); - db_->send_range(command::rpush, "rpush-key", std::cbegin(vec), std::cend(vec)); - db_->send_range(command::sadd, "sadd-key", std::cbegin(set), std::cend(set)); + 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"); diff --git a/examples/intro.cpp b/examples/intro.cpp index f5f9704a..2b7bd0bf 100644 --- a/examples/intro.cpp +++ b/examples/intro.cpp @@ -31,8 +31,8 @@ private: 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-set-key", "Três pratos de trigo para três tigres"); - db_->send(command::get, "intro-set-key"); + 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; diff --git a/examples/serialization.cpp b/examples/serialization.cpp index 7714e611..c552f037 100644 --- a/examples/serialization.cpp +++ b/examples/serialization.cpp @@ -104,9 +104,9 @@ private: // Sends db_->send(command::set, "serialization-var-key", var, "EX", "2"); - db_->send_range(command::hset, "serialization-hset-key", std::cbegin(map), std::cend(map)); - db_->send_range(command::rpush, "serialization-rpush-key", std::cbegin(vec), std::cend(vec)); - db_->send_range(command::sadd, "serialization-sadd-key", std::cbegin(set), std::cend(set)); + 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"); diff --git a/examples/stl_containers.cpp b/examples/stl_containers.cpp index 94a65236..233c0154 100644 --- a/examples/stl_containers.cpp +++ b/examples/stl_containers.cpp @@ -70,9 +70,9 @@ private: {"one", "two", "three", "four"}; // Sends the stl containers. - db_->send_range(command::hset, "hset-key", std::cbegin(map), std::cend(map)); - db_->send_range(command::rpush, "rpush-key", std::cbegin(vec), std::cend(vec)); - db_->send_range(command::sadd, "sadd-key", std::cbegin(set), std::cend(set)); + 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");