From 359f0f490f08cc68303cd8c651fe9b903f3b149e Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Thu, 21 Nov 2019 21:34:39 +0100 Subject: [PATCH] Commit of the following - Progresses with aedis. - Improvements in the documentation. --- Makefile | 1 - README.md | 104 ++++++++++++++++++++++- aedis.hpp | 186 +++++++++++++++++++++++------------------ examples.cpp | 227 ++++++++------------------------------------------- 4 files changed, 243 insertions(+), 275 deletions(-) diff --git a/Makefile b/Makefile index e259da08..8c611f89 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,6 @@ CPPFLAGS += -std=c++17 -g CPPFLAGS += -I/opt/boost_1_71_0/include -CPPFLAGS += -I/opt/aedis-1.0.0 CPPFLAGS += -DBOOST_ASIO_CONCURRENCY_HINT_1=BOOST_ASIO_CONCURRENCY_HINT_UNSAFE examples: % : %.o diff --git a/README.md b/README.md index 6fcd3b8b..4a48184a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,102 @@ -# aedis -A redis client designed for simplicity and reliability +# Aedis +Aedis is a redis client designed with the following in mind + +* Simplicity and Minimalist +* No overhead abstractions +* Optimal use of Asio +* Async + +# Example + +Talking to a redis server is as simple as + +``` +void foo() +{ + net::io_context ioc; + session ss {ioc}; + + ss.send(ping()); + + ss.run(); + ioc.run(); +} +``` + +Composition of commands is trivial and there is support for some stl +containers + +``` +void foo() +{ + std::list b + {"one" ,"two", "three"}; + + std::set c + {"a" ,"b", "c"}; + + std::map d + { {{"Name"}, {"Marcelo"}} + , {{"Education"}, {"Physics"}} + , {{"Job"}, {"Programmer"}}}; + + std::map e + { {1, {"foo"}} + , {2, {"bar"}} + , {3, {"foobar"}} + }; + + auto s = ping() + + rpush("b", b) + + lrange("b") + + del("b") + + multi() + + rpush("c", c) + + lrange("c") + + del("c") + + hset("d", d) + + hvals("d") + + zadd({"e"}, e) + + zrange("e") + + zrangebyscore("foo", 2, -1) + + set("f", {"39"}) + + incr("f") + + get("f") + + expire("f", 10) + + publish("g", "A message") + + exec(); + + net::io_context ioc; + session ss {ioc}; + + ss.send(std::move(s)); + + ss.run(); + ioc.run(); +} +``` + +NOTE: Not all commands are implemented yet. Since this client was +writen for my own use I implement new functionality on demand. + +# Features + +* Pubsub +* Pipeline +* Reconnection on connection lost. + +The main missing features at the moment are + +* Sentinel +* Cluster + +I will implement those on demand. + +# Intallation + +Aedis is header only. You only have to include `aedis.hpp` in your +project. Further dependencies are + +* Boost.Asio +* libfmt + diff --git a/aedis.hpp b/aedis.hpp index 48a9b285..3404d7a6 100644 --- a/aedis.hpp +++ b/aedis.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,7 @@ std::string make_bulky_item(std::string const& param) } inline -std::string make_cmd_header(int size) +std::string make_header(int size) { return "*" + std::to_string(size) + "\r\n"; } @@ -66,37 +67,67 @@ struct accumulator { return a; } - template - auto operator()(std::string a, T b) const + auto operator()(std::string a, int b) const { a += make_bulky_item(std::to_string(b)); return a; } + + auto operator()(std::string a, std::pair b) const + { + a += make_bulky_item(b.first); + a += make_bulky_item(b.second); + return a; + } + + auto operator()(std::string a, std::pair b) const + { + a += make_bulky_item(std::to_string(b.first)); + a += make_bulky_item(b.second); + return a; + } }; -template -auto assemble(char const* cmd, Iter begin, Iter end) -{ - auto const d = std::distance(begin, end); - - auto payload = make_cmd_header(d + 1) - + make_bulky_item(cmd); - - return std::accumulate(begin , end, std::move(payload), accumulator{}); -} - inline auto assemble(char const* cmd) { - std::initializer_list arg; - return assemble(cmd, std::begin(arg), std::end(arg)); + return make_header(1) + make_bulky_item(cmd); +} + +template +auto assemble( char const* cmd + , std::initializer_list key + , Iter begin + , Iter end + , int size = 1) +{ + auto const d1 = + std::distance( std::cbegin(key) + , std::cend(key)); + + auto const d2 = std::distance(begin, end); + + auto a = make_header(1 + d1 + size * d2) + + make_bulky_item(cmd); + + auto b = + std::accumulate( std::cbegin(key) + , std::cend(key) + , std::move(a) + , accumulator{}); + + return + std::accumulate( begin + , end + , std::move(b) + , accumulator{}); } inline -auto assemble(char const* cmd, std::string const& str) +auto assemble(char const* cmd, std::string const& key) { - auto arg = {str}; - return assemble(cmd, std::begin(arg), std::end(arg)); + std::initializer_list dummy; + return assemble(cmd, {key}, std::cbegin(dummy), std::cend(dummy)); } // Converts a decimal number in ascii format to integer. @@ -235,17 +266,7 @@ auto async_read_resp( AsyncStream& s template auto rpush(std::string const& key, Iter begin, Iter end) { - auto const d = std::distance(begin, end); - - auto payload = resp::make_cmd_header(2 + d) - + resp::make_bulky_item("RPUSH") - + resp::make_bulky_item(key); - - auto cmd_str = std::accumulate( begin - , end - , std::move(payload) - , resp::accumulator{}); - return cmd_str; + return resp::assemble("RPUSH", {key}, begin, end); } template @@ -272,17 +293,7 @@ auto rpush( std::string const& key template auto lpush(std::string const& key, Iter begin, Iter end) { - auto const d = std::distance(begin, end); - - auto payload = resp::make_cmd_header(2 + d) - + resp::make_bulky_item("LPUSH") - + resp::make_bulky_item(key); - - auto cmd_str = std::accumulate( begin - , end - , std::move(payload) - , resp::accumulator{}); - return cmd_str; + return resp::assemble("LPUSH", {key}, begin, end); } inline @@ -291,6 +302,12 @@ auto multi() return resp::assemble("MULTI"); } +inline +auto ping() +{ + return resp::assemble("PING"); +} + inline auto exec() { @@ -330,27 +347,42 @@ auto get(std::string const& key) inline auto publish(std::string const& key, std::string const& msg) { - auto par = {key, msg}; - return resp::assemble("PUBLISH", std::begin(par), std::end(par)); + auto par = {msg}; + return resp::assemble("PUBLISH", {key}, std::begin(par), std::end(par)); } inline -auto set(std::initializer_list const& args) +auto set( std::string const& key + , std::initializer_list const& args) { - return resp::assemble("SET", std::begin(args), std::end(args)); + return resp::assemble("SET", {key}, std::begin(args), std::end(args)); } inline -auto hset(std::initializer_list const& l) +auto hset( std::string const& key + , std::initializer_list const& l) { - return resp::assemble("HSET", std::begin(l), std::end(l)); + return resp::assemble("HSET", {key}, std::begin(l), std::end(l)); +} + +template +auto hset( std::string const& key + , std::map const& m) +{ + return resp::assemble("HSET", {key}, std::begin(m), std::end(m), 2); +} + +inline +auto hvals(std::string const& key) +{ + return resp::assemble("HVALS", {key}); } inline auto hget(std::string const& key, std::string const& field) { - auto par = {key, field}; - return resp::assemble("HGET", std::begin(par), std::end(par)); + auto par = {field}; + return resp::assemble("HGET", {key}, std::begin(par), std::end(par)); } inline @@ -358,33 +390,36 @@ auto hmget( std::string const& key , std::string const& field1 , std::string const& field2) { - auto par = {key, field1, field2}; - return resp::assemble("HMGET", std::begin(par), std::end(par)); + auto par = {field1, field2}; + return resp::assemble("HMGET", {key}, std::cbegin(par), std::cend(par)); } inline auto expire(std::string const& key, int secs) { - auto par = {key, std::to_string(secs)}; - return resp::assemble("EXPIRE", std::begin(par), std::end(par)); + auto par = {std::to_string(secs)}; + return resp::assemble("EXPIRE", {key}, std::begin(par), std::end(par)); } inline auto zadd(std::string const& key, int score, std::string const& value) { - auto par = {key, std::to_string(score), value}; - return resp::assemble("ZADD", std::begin(par), std::end(par)); + auto par = {std::to_string(score), value}; + return resp::assemble("ZADD", {key}, std::cbegin(par), std::cend(par)); +} + +template +auto zadd( std::initializer_list key + , std::map const& m) +{ + return resp::assemble("ZADD", key, std::cbegin(m), std::cend(m), 2); } inline -auto zrange(std::string const& key, int min, int max) +auto zrange(std::string const& key, int min = 0, int max = -1) { - auto par = { key - , std::to_string(min) - , std::to_string(max) - }; - - return resp::assemble("zrange", std::begin(par), std::end(par)); + auto par = { std::to_string(min), std::to_string(max) }; + return resp::assemble("ZRANGE", {key}, std::begin(par), std::end(par)); } inline @@ -394,46 +429,35 @@ auto zrangebyscore(std::string const& key, int min, int max) if (max != -1) max_str = std::to_string(max); - auto par = { key - , std::to_string(min) - , max_str - //, std::string {"withscores"} - }; - - return resp::assemble("zrangebyscore", std::begin(par), std::end(par)); + auto par = { std::to_string(min) , max_str }; + return resp::assemble("zrangebyscore", {key}, std::begin(par), std::end(par)); } inline auto zremrangebyscore(std::string const& key, int score) { auto const s = std::to_string(score); - auto par = {key, s, s}; - return resp::assemble("ZREMRANGEBYSCORE", std::begin(par), std::end(par)); + auto par = {s, s}; + return resp::assemble("ZREMRANGEBYSCORE", {key}, std::begin(par), std::end(par)); } inline auto lrange(std::string const& key, int min = 0, int max = -1) { - auto par = { key - , std::to_string(min) - , std::to_string(max) - }; - - return resp::assemble("lrange", std::begin(par), std::end(par)); + auto par = { std::to_string(min) , std::to_string(max) }; + return resp::assemble("lrange", {key}, std::begin(par), std::end(par)); } inline auto del(std::string const& key) { - auto par = {key}; - return resp::assemble("del", std::begin(par), std::end(par)); + return resp::assemble("del", key); } inline auto llen(std::string const& key) { - auto par = {key}; - return resp::assemble("llen", std::begin(par), std::end(par)); + return resp::assemble("llen", key); } namespace log diff --git a/examples.cpp b/examples.cpp index 96037dd2..30d9b016 100644 --- a/examples.cpp +++ b/examples.cpp @@ -5,175 +5,16 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -#include -#include -#include -#include -#include -#include -#include - -#include +#include "aedis.hpp" using namespace aedis; -using args_type = std::initializer_list; - -void set_get() +void send(std::string cmd) { net::io_context ioc; session ss {ioc}; - auto c = set({"Name", "Marcelo"}) - + get("Name"); - - ss.send(std::move(c)); - - ss.run(); - ioc.run(); -} - -void transaction() -{ - net::io_context ioc; - session ss {ioc}; - - auto o = multi() - + set({"Age", "39"}) - + incr("Age") - + get("Age") - + expire("Age", 10) - + exec(); - - ss.send(std::move(o)); - ss.run(); - ioc.run(); -} - -void rpush_vector() -{ - net::io_context ioc; - session ss {ioc}; - - std::vector v1 {1 , 2, 3, 4, 5, 6, 7}; - ss.send(rpush("a", v1) + lrange("a") + del("a")); - - std::list v2 {"one" ,"two", "three"}; - ss.send(rpush("b", v2) + lrange("b") + del("b")); - - std::set v3 {"a" ,"b", "c"}; - ss.send(rpush("c", v3) + lrange("c") + del("c")); - - ss.run(); - ioc.run(); -} - -void pub(int count, char const* channel) -{ - net::io_context ioc; - session pub_session(ioc); - for (auto i = 0; i < count; ++i) - pub_session.send(publish(channel, std::to_string(i))); - - pub_session.run(); - - ioc.run(); -} - -auto msg_handler2 = [i = 0](auto ec, auto const& res) mutable -{ - if (ec) - throw std::runtime_error(ec.message()); - - auto const n = std::stoi(res.back()); - if (n != i + 1) - std::cout << "===============> Error." << std::endl; - std::cout << "Counter: " << n << std::endl; - - i = n; - - //for (auto const& o : res) - // std::cout << o << " "; - - //std::cout << std::endl; -}; - -struct sub_arena { - session s; - - sub_arena( net::io_context& ioc - , std::string channel) - : s(ioc) - { - s.set_msg_handler(msg_handler2); - - auto const on_conn_handler = [this, channel]() - { s.send(subscribe(channel)); }; - - s.set_on_conn_handler(on_conn_handler); - s.run(); - } -}; - -void sub(char const* channel) -{ - net::io_context ioc; - sub_arena arena(ioc, channel); - ioc.run(); -} - -void zadd() -{ - net::io_context ioc; - session ss {ioc}; - - auto c1 = zadd("foo", 1, "bar1") - + zadd("foo", 2, "bar2") - + zadd("foo", 3, "bar3") - + zadd("foo", 4, "bar4") - + zadd("foo", 5, "bar5"); - - ss.send(c1); - - ss.run(); - ioc.run(); -} - -void zrangebyscore() -{ - net::io_context ioc; - session ss(ioc); - - auto c1 = zrangebyscore("foo", 2, -1); - - ss.send(c1); - - ss.run(); - ioc.run(); -} - -void zrange() -{ - net::io_context ioc; - session ss(ioc); - - ss.send(zrange("foo", 2, -1)); - - ss.run(); - ioc.run(); -} - -void read_msg_op() -{ - net::io_context ioc; - session ss(ioc); - - auto c1 = multi() - + lrange("foo") - + del("foo") - + exec(); - - ss.send(c1); + ss.send(std::move(cmd)); ss.run(); ioc.run(); @@ -181,39 +22,43 @@ void read_msg_op() int main(int argc, char* argv[]) { - try { - if (argc == 1) { - std::cerr << "Usage: " << argv[0] << " n host port" << std::endl; - return 1; - } + std::list b + {"one" ,"two", "three"}; - session::config cfg; - auto const n = std::stoi(argv[1]); + std::set c + {"a" ,"b", "c"}; - if (argc == 3) { - cfg.host = argv[2]; - cfg.port = argv[3]; - } + std::map d + { {{"Name"}, {"Marcelo"}} + , {{"Education"}, {"Physics"}} + , {{"Job"}, {"Programmer"}}}; - char const* channel = "foo"; + std::map e + { {1, {"foo"}} + , {2, {"bar"}} + , {3, {"foobar"}} + }; - switch (n) { - case 0: set_get(); break; - case 1: transaction(); break; - case 2: pub(10, channel); break; - case 3: sub(channel); break; - case 4: zadd(); break; - case 5: zrangebyscore(); break; - case 6: zrange(); break; - case 7: read_msg_op(); break; - case 8: rpush_vector(); break; - default: - std::cerr << "Option not available." << std::endl; - } - } catch (std::exception const& e) { - std::cerr << e.what() << "\n"; - } + auto s = ping() + + rpush("b", b) + + lrange("b") + + del("b") + + multi() + + rpush("c", c) + + lrange("c") + + del("c") + + hset("d", d) + + hvals("d") + + zadd({"e"}, e) + + zrange("e") + + zrangebyscore("foo", 2, -1) + + set("f", {"39"}) + + incr("f") + + get("f") + + expire("f", 10) + + publish("g", "A message") + + exec(); - return 0; + send(std::move(s)); }