浏览代码

Merge remote-tracking branch 'origin/dev' into multipath

Joseph Henry 5 年之前
父节点
当前提交
4465952d11
共有 100 个文件被更改,包括 17536 次插入9267 次删除
  1. 2 0
      .dockerignore
  2. 4 3
      controller/EmbeddedNetworkController.cpp
  3. 4 1
      controller/EmbeddedNetworkController.hpp
  4. 262 34
      controller/PostgreSQL.cpp
  5. 15 2
      controller/PostgreSQL.hpp
  6. 15 0
      controller/Redis.hpp
  7. 5 5
      ext/central-controller-docker/Dockerfile
  8. 22 20
      ext/central-controller-docker/main.sh
  9. 0 1
      ext/hiredis-0.14.1/.gitignore
  10. 45 0
      ext/hiredis-0.14.1/.travis.yml
  11. 190 0
      ext/hiredis-0.14.1/CHANGELOG.md
  12. 0 0
      ext/hiredis-0.14.1/COPYING
  13. 51 42
      ext/hiredis-0.14.1/Makefile
  14. 410 0
      ext/hiredis-0.14.1/README.md
  15. 1 28
      ext/hiredis-0.14.1/adapters/ae.h
  16. 10 10
      ext/hiredis-0.14.1/adapters/glib.h
  17. 81 0
      ext/hiredis-0.14.1/adapters/ivykis.h
  18. 1 1
      ext/hiredis-0.14.1/adapters/libev.h
  19. 13 40
      ext/hiredis-0.14.1/adapters/libevent.h
  20. 3 3
      ext/hiredis-0.14.1/adapters/libuv.h
  21. 114 0
      ext/hiredis-0.14.1/adapters/macosx.h
  22. 135 0
      ext/hiredis-0.14.1/adapters/qt.h
  23. 65 0
      ext/hiredis-0.14.1/alloc.c
  24. 53 0
      ext/hiredis-0.14.1/alloc.h
  25. 23 0
      ext/hiredis-0.14.1/appveyor.yml
  26. 41 15
      ext/hiredis-0.14.1/async.c
  27. 1 1
      ext/hiredis-0.14.1/async.h
  28. 6 5
      ext/hiredis-0.14.1/dict.c
  29. 0 0
      ext/hiredis-0.14.1/dict.h
  30. 0 0
      ext/hiredis-0.14.1/examples/example-ae.c
  31. 0 0
      ext/hiredis-0.14.1/examples/example-glib.c
  32. 58 0
      ext/hiredis-0.14.1/examples/example-ivykis.c
  33. 0 0
      ext/hiredis-0.14.1/examples/example-libev.c
  34. 0 0
      ext/hiredis-0.14.1/examples/example-libevent.c
  35. 0 0
      ext/hiredis-0.14.1/examples/example-libuv.c
  36. 66 0
      ext/hiredis-0.14.1/examples/example-macosx.c
  37. 46 0
      ext/hiredis-0.14.1/examples/example-qt.cpp
  38. 32 0
      ext/hiredis-0.14.1/examples/example-qt.h
  39. 1 1
      ext/hiredis-0.14.1/examples/example.c
  40. 12 0
      ext/hiredis-0.14.1/fmacros.h
  41. 19 34
      ext/hiredis-0.14.1/hiredis.c
  42. 7 28
      ext/hiredis-0.14.1/hiredis.h
  43. 53 0
      ext/hiredis-0.14.1/include/hiredis/alloc.h
  44. 130 0
      ext/hiredis-0.14.1/include/hiredis/async.h
  45. 126 0
      ext/hiredis-0.14.1/include/hiredis/dict.h
  46. 12 0
      ext/hiredis-0.14.1/include/hiredis/fmacros.h
  47. 200 0
      ext/hiredis-0.14.1/include/hiredis/hiredis.h
  48. 0 4
      ext/hiredis-0.14.1/include/hiredis/net.h
  49. 4 22
      ext/hiredis-0.14.1/include/hiredis/read.h
  50. 273 0
      ext/hiredis-0.14.1/include/hiredis/sds.h
  51. 42 0
      ext/hiredis-0.14.1/include/hiredis/sdsalloc.h
  52. 0 0
      ext/hiredis-0.14.1/include/hiredis/win32.h
  53. 二进制
      ext/hiredis-0.14.1/lib/centos8/libhiredis.a
  54. 二进制
      ext/hiredis-0.14.1/lib/macos/libhiredis.a
  55. 47 28
      ext/hiredis-0.14.1/net.c
  56. 49 0
      ext/hiredis-0.14.1/net.h
  57. 117 44
      ext/hiredis-0.14.1/read.c
  58. 111 0
      ext/hiredis-0.14.1/read.h
  59. 342 165
      ext/hiredis-0.14.1/sds.c
  60. 273 0
      ext/hiredis-0.14.1/sds.h
  61. 42 0
      ext/hiredis-0.14.1/sdsalloc.h
  62. 131 14
      ext/hiredis-0.14.1/test.c
  63. 42 0
      ext/hiredis-0.14.1/win32.h
  64. 0 16
      ext/hiredis-vip-0.3.0/.travis.yml
  65. 0 16
      ext/hiredis-vip-0.3.0/CHANGELOG.md
  66. 0 255
      ext/hiredis-vip-0.3.0/README.md
  67. 0 341
      ext/hiredis-vip-0.3.0/adlist.c
  68. 0 93
      ext/hiredis-vip-0.3.0/adlist.h
  69. 0 1700
      ext/hiredis-vip-0.3.0/command.c
  70. 0 179
      ext/hiredis-vip-0.3.0/command.h
  71. 0 23
      ext/hiredis-vip-0.3.0/fmacros.h
  72. 0 188
      ext/hiredis-vip-0.3.0/hiarray.c
  73. 0 56
      ext/hiredis-vip-0.3.0/hiarray.h
  74. 0 4747
      ext/hiredis-vip-0.3.0/hircluster.c
  75. 0 178
      ext/hiredis-vip-0.3.0/hircluster.h
  76. 0 554
      ext/hiredis-vip-0.3.0/hiutil.c
  77. 0 265
      ext/hiredis-vip-0.3.0/hiutil.h
  78. 0 105
      ext/hiredis-vip-0.3.0/sds.h
  79. 32 0
      ext/redis-plus-plus-1.1.1/.gitignore
  80. 51 0
      ext/redis-plus-plus-1.1.1/CMakeLists.txt
  81. 201 0
      ext/redis-plus-plus-1.1.1/LICENSE
  82. 1776 0
      ext/redis-plus-plus-1.1.1/README.md
  83. 2233 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h
  84. 180 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h
  85. 211 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h
  86. 194 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h
  87. 115 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h
  88. 159 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h
  89. 49 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h
  90. 1844 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h
  91. 208 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp
  92. 25 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h
  93. 1523 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h
  94. 1365 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp
  95. 1395 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h
  96. 1415 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp
  97. 363 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h
  98. 138 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h
  99. 115 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h
  100. 137 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h

+ 2 - 0
.dockerignore

@@ -0,0 +1,2 @@
+.git/
+workspace/

+ 4 - 3
controller/EmbeddedNetworkController.cpp

@@ -456,14 +456,15 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule)
 
 } // anonymous namespace
 
-EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort) :
+EmbeddedNetworkController::EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc) :
 	_startTime(OSUtils::now()),
 	_listenPort(listenPort),
 	_node(node),
 	_ztPath(ztPath),
 	_path(dbPath),
 	_sender((NetworkController::Sender *)0),
-	_db(this)
+	_db(this),
+	_rc(rc)
 {
 }
 
@@ -484,7 +485,7 @@ void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
 
 #ifdef ZT_CONTROLLER_USE_LIBPQ
 	if ((_path.length() > 9)&&(_path.substr(0,9) == "postgres:")) {
-		_db.addDB(std::shared_ptr<DB>(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort)));
+		_db.addDB(std::shared_ptr<DB>(new PostgreSQL(_signingId,_path.substr(9).c_str(), _listenPort, _rc)));
 	} else {
 #endif
 		_db.addDB(std::shared_ptr<DB>(new FileDB(_path.c_str())));

+ 4 - 1
controller/EmbeddedNetworkController.hpp

@@ -43,6 +43,7 @@
 namespace ZeroTier {
 
 class Node;
+struct RedisConfig;
 
 class EmbeddedNetworkController : public NetworkController,public DB::ChangeListener
 {
@@ -51,7 +52,7 @@ public:
 	 * @param node Parent node
 	 * @param dbPath Database path (file path or database credentials)
 	 */
-	EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort);
+	EmbeddedNetworkController(Node *node,const char *ztPath,const char *dbPath, int listenPort, RedisConfig *rc);
 	virtual ~EmbeddedNetworkController();
 
 	virtual void init(const Identity &signingId,Sender *sender);
@@ -148,6 +149,8 @@ private:
 
 	std::unordered_map< _MemberStatusKey,_MemberStatus,_MemberStatusHash > _memberStatus;
 	std::mutex _memberStatus_l;
+
+	RedisConfig *_rc;
 };
 
 } // namespace ZeroTier

+ 262 - 34
controller/PostgreSQL.cpp

@@ -18,7 +18,7 @@
 #include "../node/Constants.hpp"
 #include "EmbeddedNetworkController.hpp"
 #include "../version.h"
-#include "hiredis.h"
+#include "Redis.hpp"
 
 #include <libpq-fe.h>
 #include <sstream>
@@ -68,7 +68,11 @@ std::string join(const std::vector<std::string> &elements, const char * const se
 
 using namespace ZeroTier;
 
-PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort)
+using Attrs = std::vector<std::pair<std::string, std::string>>;
+using Item = std::pair<std::string, Attrs>;
+using ItemStream = std::vector<Item>;
+
+PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc)
 	: DB()
 	, _myId(myId)
 	, _myAddress(myId.address())
@@ -77,6 +81,9 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort)
 	, _run(1)
 	, _waitNoticePrinted(false)
 	, _listenPort(listenPort)
+	, _rc(rc)
+	, _redis(NULL)
+	, _cluster(NULL)
 {
 	char myAddress[64];
 	_myAddressStr = myId.address().toString(myAddress);
@@ -112,6 +119,23 @@ PostgreSQL::PostgreSQL(const Identity &myId, const char *path, int listenPort)
 	PQfinish(conn);
 	conn = NULL;
 
+	if (_rc != NULL) {
+		sw::redis::ConnectionOptions opts;
+		sw::redis::ConnectionPoolOptions poolOpts;
+		opts.host = _rc->hostname;
+		opts.port = _rc->port;
+		opts.password = _rc->password;
+		opts.db = 0;
+		poolOpts.size = 10;
+		if (_rc->clusterMode) {
+			fprintf(stderr, "Using Redis in Cluster Mode\n");
+			_cluster = std::make_shared<sw::redis::RedisCluster>(opts, poolOpts);
+		} else {
+			fprintf(stderr, "Using Redis in Standalone Mode\n");
+			_redis = std::make_shared<sw::redis::Redis>(opts, poolOpts);
+		}
+	}
+
 	_readyLock.lock();
 	_heartbeatThread = std::thread(&PostgreSQL::heartbeat, this);
 	_membersDbWatcher = std::thread(&PostgreSQL::membersDbWatcher, this);
@@ -130,11 +154,11 @@ PostgreSQL::~PostgreSQL()
 	_heartbeatThread.join();
 	_membersDbWatcher.join();
 	_networksDbWatcher.join();
+	_commitQueue.stop();
 	for (int i = 0; i < ZT_CENTRAL_CONTROLLER_COMMIT_THREADS; ++i) {
 		_commitThread[i].join();
 	}
 	_onlineNotificationThread.join();
-
 }
 
 
@@ -205,12 +229,14 @@ void PostgreSQL::eraseNetwork(const uint64_t networkId)
 	tmp.first["objtype"] = "_delete_network";
 	tmp.second = true;
 	_commitQueue.post(tmp);
+	nlohmann::json nullJson;
+	_networkChanged(tmp.first, nullJson, true);
 }
 
 void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId)
 {
 	char tmp2[24];
-	std::pair<nlohmann::json,bool> tmp;
+	std::pair<nlohmann::json,bool> tmp, nw;
 	Utils::hex(networkId, tmp2);
 	tmp.first["nwid"] = tmp2;
 	Utils::hex(memberId, tmp2);
@@ -218,6 +244,8 @@ void PostgreSQL::eraseMember(const uint64_t networkId, const uint64_t memberId)
 	tmp.first["objtype"] = "_delete_member";
 	tmp.second = true;
 	_commitQueue.post(tmp);
+	nlohmann::json nullJson;
+	_memberChanged(tmp.first, nullJson, true);
 }
 
 void PostgreSQL::nodeIsOnline(const uint64_t networkId, const uint64_t memberId, const InetAddress &physicalAddress)
@@ -591,7 +619,7 @@ void PostgreSQL::heartbeat()
 			std::string build = std::to_string(ZEROTIER_ONE_VERSION_BUILD);
 			std::string now = std::to_string(OSUtils::now());
 			std::string host_port = std::to_string(_listenPort);
-			std::string use_rabbitmq = (false) ? "true" : "false";
+			std::string use_redis = (_rc != NULL) ? "true" : "false";
 			const char *values[10] = {
 				controllerId,
 				hostname,
@@ -602,16 +630,16 @@ void PostgreSQL::heartbeat()
 				rev.c_str(),
 				build.c_str(),
 				host_port.c_str(),
-				use_rabbitmq.c_str()
+				use_redis.c_str()
 			};
 
 			PGresult *res = PQexecParams(conn,
-				"INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_rabbitmq) "
+				"INSERT INTO ztc_controller (id, cluster_host, last_alive, public_identity, v_major, v_minor, v_rev, v_build, host_port, use_redis) "
 				"VALUES ($1, $2, TO_TIMESTAMP($3::double precision/1000), $4, $5, $6, $7, $8, $9, $10) "
 				"ON CONFLICT (id) DO UPDATE SET cluster_host = EXCLUDED.cluster_host, last_alive = EXCLUDED.last_alive, "
 				"public_identity = EXCLUDED.public_identity, v_major = EXCLUDED.v_major, v_minor = EXCLUDED.v_minor, "
 				"v_rev = EXCLUDED.v_rev, v_build = EXCLUDED.v_rev, host_port = EXCLUDED.host_port, "
-				"use_rabbitmq = EXCLUDED.use_rabbitmq",
+				"use_redis = EXCLUDED.use_redis",
 				10,	   // number of parameters
 				NULL,	// oid field.   ignore
 				values,  // values for substitution
@@ -630,6 +658,7 @@ void PostgreSQL::heartbeat()
 
 	PQfinish(conn);
 	conn = NULL;
+	fprintf(stderr, "Exited heartbeat thread\n");
 }
 
 void PostgreSQL::membersDbWatcher()
@@ -643,10 +672,10 @@ void PostgreSQL::membersDbWatcher()
 
 	initializeMembers(conn);
 
-	if (false) {
-		// PQfinish(conn);
-		// conn = NULL;
-		// _membersWatcher_RabbitMQ();
+	if (_rc) {
+		PQfinish(conn);
+		conn = NULL;
+		_membersWatcher_Redis();
 	} else {
 		_membersWatcher_Postgres(conn);
 		PQfinish(conn);
@@ -701,9 +730,58 @@ void PostgreSQL::_membersWatcher_Postgres(PGconn *conn) {
 	}
 }
 
-void PostgreSQL::_membersWatcher_Reids() {
-	char buff[11] = {0};
+void PostgreSQL::_membersWatcher_Redis() {
+	char buf[11] = {0};
+	std::string key = "member-stream:{" + std::string(_myAddress.toString(buf)) + "}";
 	
+	while (_run == 1) {
+		json tmp;
+		std::unordered_map<std::string, ItemStream> result;
+		if (_rc->clusterMode) {
+			_cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end()));
+		} else {
+			_redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end()));
+		}
+		if (!result.empty()) {
+			for (auto element : result) {
+#ifdef ZT_TRACE
+				fprintf(stdout, "Received notification from: %s\n", element.first.c_str());
+#endif
+				for (auto rec : element.second) {
+					std::string id = rec.first;
+					auto attrs = rec.second;
+#ifdef ZT_TRACE
+					fprintf(stdout, "Record ID: %s\n", id.c_str());
+					fprintf(stdout, "attrs len: %lu\n", attrs.size());
+#endif
+					for (auto a : attrs) {
+#ifdef ZT_TRACE
+						fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str());
+#endif
+						try {
+							tmp = json::parse(a.second);
+							json &ov = tmp["old_val"];
+							json &nv = tmp["new_val"];
+							json oldConfig, newConfig;
+							if (ov.is_object()) oldConfig = ov;
+							if (nv.is_object()) newConfig = nv;
+							if (oldConfig.is_object()||newConfig.is_object()) {
+								_memberChanged(oldConfig,newConfig,(this->_ready >= 2));
+							}
+						} catch (...) {
+							fprintf(stderr, "json parse error in networkWatcher_Redis\n");
+						}
+					}
+					if (_rc->clusterMode) {
+						_cluster->xdel(key, id);
+					} else {
+						_redis->xdel(key, id);
+					}
+				}
+			}
+		}
+	}
+	fprintf(stderr, "membersWatcher ended\n");
 }
 
 void PostgreSQL::networksDbWatcher()
@@ -717,10 +795,10 @@ void PostgreSQL::networksDbWatcher()
 
 	initializeNetworks(conn);
 
-	if (false) {
-		// PQfinish(conn);
-		// conn = NULL;
-		// _networksWatcher_RabbitMQ();
+	if (_rc) {
+		PQfinish(conn);
+		conn = NULL;
+		_networksWatcher_Redis();
 	} else {
 		_networksWatcher_Postgres(conn);
 		PQfinish(conn);
@@ -774,7 +852,58 @@ void PostgreSQL::_networksWatcher_Postgres(PGconn *conn) {
 }
 
 void PostgreSQL::_networksWatcher_Redis() {
-
+	char buf[11] = {0};
+	std::string key = "network-stream:{" + std::string(_myAddress.toString(buf)) + "}";
+	
+	while (_run == 1) {
+		json tmp;
+		std::unordered_map<std::string, ItemStream> result;
+		if (_rc->clusterMode) {
+			_cluster->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end()));
+		} else {
+			_redis->xread(key, "$", std::chrono::seconds(1), 0, std::inserter(result, result.end()));
+		}
+		
+		if (!result.empty()) {
+			for (auto element : result) {
+#ifdef ZT_TRACE
+				fprintf(stdout, "Received notification from: %s\n", element.first.c_str());
+#endif
+				for (auto rec : element.second) {
+					std::string id = rec.first;
+					auto attrs = rec.second;
+#ifdef ZT_TRACE
+					fprintf(stdout, "Record ID: %s\n", id.c_str());
+					fprintf(stdout, "attrs len: %lu\n", attrs.size());
+#endif
+					for (auto a : attrs) {
+#ifdef ZT_TRACE
+						fprintf(stdout, "key: %s\nvalue: %s\n", a.first.c_str(), a.second.c_str());
+#endif
+						try {
+							tmp = json::parse(a.second);
+							json &ov = tmp["old_val"];
+							json &nv = tmp["new_val"];
+							json oldConfig, newConfig;
+							if (ov.is_object()) oldConfig = ov;
+							if (nv.is_object()) newConfig = nv;
+							if (oldConfig.is_object()||newConfig.is_object()) {
+								_networkChanged(oldConfig,newConfig,(this->_ready >= 2));
+							}
+						} catch (...) {
+							fprintf(stderr, "json parse error in networkWatcher_Redis\n");
+						}
+					}
+					if (_rc->clusterMode) {
+						_cluster->xdel(key, id);
+					} else {
+						_redis->xdel(key, id);
+					}
+				}
+			}
+		}
+	}
+	fprintf(stderr, "networksWatcher ended\n");
 }
 
 void PostgreSQL::commitThread()
@@ -1272,9 +1401,19 @@ void PostgreSQL::commitThread()
 		fprintf(stderr, "ERROR: %s commitThread should still be running! Exiting Controller.\n", _myAddressStr.c_str());
 		exit(7);
 	}
+	fprintf(stderr, "commitThread finished\n");
 }
 
 void PostgreSQL::onlineNotificationThread()
+{
+	if (_rc != NULL) {
+		onlineNotification_Redis();
+	} else {
+		onlineNotification_Postgres();
+	}
+}
+
+void PostgreSQL::onlineNotification_Postgres()
 {
 	PGconn *conn = getPgConn();
 	if (PQstatus(conn) == CONNECTION_BAD) {
@@ -1284,9 +1423,7 @@ void PostgreSQL::onlineNotificationThread()
 	}
 	_connected = 1;
 
-	//int64_t	lastUpdatedNetworkStatus = 0;
-	std::unordered_map< std::pair<uint64_t,uint64_t>,int64_t,_PairHasher > lastOnlineCumulative;
-
+	nlohmann::json jtmp1, jtmp2;
 	while (_run == 1) {
 		if (PQstatus(conn) != CONNECTION_OK) {
 			fprintf(stderr, "ERROR: Online Notification thread lost connection to Postgres.");
@@ -1294,9 +1431,6 @@ void PostgreSQL::onlineNotificationThread()
 			exit(5);
 		}
 
-		// map used to send notifications to front end
-		std::unordered_map<std::string, std::vector<std::string>> updateMap;
-
 		std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > lastOnline;
 		{
 			std::lock_guard<std::mutex> l(_lastOnline_l);
@@ -1317,20 +1451,13 @@ void PostgreSQL::onlineNotificationThread()
 			OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i);
 			OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", i->first.second);
 
-			auto found = _networks.find(nwid_i);
-			if (found == _networks.end()) {
-				continue; // skip members trying to join non-existant networks
+			if(!get(nwid_i, jtmp1, i->first.second, jtmp2)) {
+				continue; // skip non existent networks/members
 			}
 
 			std::string networkId(nwidTmp);
 			std::string memberId(memTmp);
 
-			std::vector<std::string> &members = updateMap[networkId];
-			members.push_back(memberId);
-
-			lastOnlineCumulative[i->first] = i->second.first;
-
-
 			const char *qvals[2] = {
 				networkId.c_str(),
 				memberId.c_str()
@@ -1400,6 +1527,107 @@ void PostgreSQL::onlineNotificationThread()
 	}
 }
 
+void PostgreSQL::onlineNotification_Redis()
+{
+	_connected = 1;
+	
+	char buf[11] = {0};
+	std::string controllerId = std::string(_myAddress.toString(buf));
+
+	while (_run == 1) {
+		std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > lastOnline;
+		{
+			std::lock_guard<std::mutex> l(_lastOnline_l);
+			lastOnline.swap(_lastOnline);
+		}
+
+		if (_rc->clusterMode) {
+			auto tx = _cluster->redis(controllerId).transaction(true);
+			_doRedisUpdate(tx, controllerId, lastOnline);
+		} else {
+			auto tx = _redis->transaction(true);
+			_doRedisUpdate(tx, controllerId, lastOnline);
+		}
+		
+		std::this_thread::sleep_for(std::chrono::milliseconds(10));
+	}
+}
+
+void PostgreSQL::_doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, 
+	std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > &lastOnline) 
+
+{
+	nlohmann::json jtmp1, jtmp2;
+	for (auto i=lastOnline.begin(); i != lastOnline.end(); ++i) {
+		uint64_t nwid_i = i->first.first;
+		uint64_t memberid_i = i->first.second;
+		char nwidTmp[64];
+		char memTmp[64];
+		char ipTmp[64];
+		OSUtils::ztsnprintf(nwidTmp,sizeof(nwidTmp), "%.16llx", nwid_i);
+		OSUtils::ztsnprintf(memTmp,sizeof(memTmp), "%.10llx", memberid_i);
+
+		if (!get(nwid_i, jtmp1, memberid_i, jtmp2)){
+			continue;  // skip non existent members/networks
+		}
+		auto found = _networks.find(nwid_i);
+		if (found == _networks.end()) {
+			continue; // skip members trying to join non-existant networks
+		}
+
+		std::string networkId(nwidTmp);
+		std::string memberId(memTmp);
+
+		int64_t ts = i->second.first;
+		std::string ipAddr = i->second.second.toIpString(ipTmp);
+		std::string timestamp = std::to_string(ts);
+
+		std::unordered_map<std::string, std::string> record = {
+			{"id", memberId},
+			{"address", ipAddr},
+			{"last_updated", std::to_string(ts)}
+		};
+		tx.zadd("nodes-online:{"+controllerId+"}", memberId, ts)
+			.zadd("network-nodes-online:{"+controllerId+"}:"+networkId, memberId, ts)
+			.sadd("network-nodes-all:{"+controllerId+"}:"+networkId, memberId)
+			.hmset("network:{"+controllerId+"}:"+networkId+":"+memberId, record.begin(), record.end());
+	}
+
+	tx.exec();
+
+	// expire records from all-nodes and network-nodes member list
+	uint64_t expireOld = OSUtils::now() - 300000;
+	
+	auto cursor = 0LL;
+	std::unordered_set<std::string> keys;
+	// can't scan for keys in a transaction, so we need to fall back to _cluster or _redis
+	// to get all network-members keys
+	if(_rc->clusterMode) {
+		auto r = _cluster->redis(controllerId);
+		while(true) {
+			cursor = r.scan(cursor, "network-nodes-online:{"+controllerId+"}:*", INT_MAX, std::inserter(keys, keys.begin()));
+			if (cursor == 0) {
+				break;
+			}
+		}
+	} else {
+		while(true) {
+			cursor = _redis->scan(cursor, "network-nodes-online:"+controllerId+":*", INT_MAX, std::inserter(keys, keys.begin()));
+			if (cursor == 0) {
+				break;
+			}
+		}
+	}
+
+	tx.zremrangebyscore("nodes-online:{"+controllerId+"}", sw::redis::RightBoundedInterval<double>(expireOld, sw::redis::BoundType::LEFT_OPEN));
+
+	for(const auto &k : keys) {
+		tx.zremrangebyscore(k, sw::redis::RightBoundedInterval<double>(expireOld, sw::redis::BoundType::LEFT_OPEN));
+	}
+
+	tx.exec();
+}
+
 PGconn *PostgreSQL::getPgConn(OverrideMode m)
 {
 	if (m == ALLOW_PGBOUNCER_OVERRIDE) {

+ 15 - 2
controller/PostgreSQL.hpp

@@ -20,12 +20,17 @@
 
 #define ZT_CENTRAL_CONTROLLER_COMMIT_THREADS 4
 
+#include <memory>
+#include <redis++/redis++.h>
+
 extern "C" {
 typedef struct pg_conn PGconn;
 }
 
 namespace ZeroTier {
 
+struct RedisConfig;
+
 /**
  * A controller database driver that talks to PostgreSQL
  *
@@ -35,7 +40,7 @@ namespace ZeroTier {
 class PostgreSQL : public DB
 {
 public:
-	PostgreSQL(const Identity &myId, const char *path, int listenPort);
+	PostgreSQL(const Identity &myId, const char *path, int listenPort, RedisConfig *rc);
 	virtual ~PostgreSQL();
 
 	virtual bool waitForReady();
@@ -60,11 +65,15 @@ private:
 	void networksDbWatcher();
 	void _networksWatcher_Postgres(PGconn *conn);
 
-	void _membersWatcher_Reids();
+	void _membersWatcher_Redis();
 	void _networksWatcher_Redis();
 
 	void commitThread();
 	void onlineNotificationThread();
+	void onlineNotification_Postgres();
+	void onlineNotification_Redis();
+	void _doRedisUpdate(sw::redis::Transaction &tx, std::string &controllerId, 
+		std::unordered_map< std::pair<uint64_t,uint64_t>,std::pair<int64_t,InetAddress>,_PairHasher > &lastOnline);
 
 	enum OverrideMode {
 		ALLOW_PGBOUNCER_OVERRIDE = 0,
@@ -94,6 +103,10 @@ private:
 	mutable volatile bool _waitNoticePrinted;
 
 	int _listenPort;
+
+	RedisConfig *_rc;
+	std::shared_ptr<sw::redis::Redis> _redis;
+	std::shared_ptr<sw::redis::RedisCluster> _cluster;
 };
 
 } // namespace ZeroTier

+ 15 - 0
controller/Redis.hpp

@@ -0,0 +1,15 @@
+#ifndef ZT_CONTROLLER_REDIS_HPP
+#define ZT_CONTROLLER_REDIS_HPP
+
+#include <string>
+
+namespace ZeroTier {
+struct RedisConfig {
+    std::string hostname;
+    int port;
+    std::string password;
+    bool clusterMode;
+};
+}
+
+#endif

+ 5 - 5
ext/central-controller-docker/Dockerfile

@@ -1,22 +1,22 @@
 # Dockerfile for ZeroTier Central Controllers
-FROM centos:7 as builder
+FROM centos:8 as builder
 MAINTAINER Adam Ierymekno <[email protected]>, Grant Limberg <[email protected]>
 
 ARG git_branch=master
 
 RUN yum update -y
-RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
+RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm && dnf -qy module disable postgresql
 RUN yum -y install epel-release && yum -y update && yum clean all
 RUN yum groupinstall -y "Development Tools"
-RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel glibc-static libstdc++-static clang jemalloc jemalloc-devel
+RUN yum install -y bash postgresql10 postgresql10-devel libpqxx-devel clang jemalloc jemalloc-devel
 
 # RUN git clone http://git.int.zerotier.com/zerotier/ZeroTierOne.git
 # RUN if [ "$git_branch" != "master" ]; then cd ZeroTierOne && git checkout -b $git_branch origin/$git_branch; fi
 ADD . /ZeroTierOne
 RUN cd ZeroTierOne && make clean && make central-controller
 
-FROM centos:7
-RUN yum install -y yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm && yum -y install epel-release && yum -y update && yum clean all
+FROM centos:8
+RUN yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm && dnf -qy module disable postgresql && yum -y install epel-release && yum -y update && yum clean all
 RUN yum install -y jemalloc jemalloc-devel postgresql10
 
 COPY --from=builder /ZeroTierOne/zerotier-one /usr/local/bin/zerotier-one

+ 22 - 20
ext/central-controller-docker/main.sh

@@ -25,30 +25,32 @@ if [ -z "$ZT_DB_PASSWORD" ]; then
     exit 1
 fi
 
-RMQ=""
-if [ "$ZT_USE_RABBITMQ" == "true" ]; then
-    if [ -z "$RABBITMQ_HOST" ]; then
-        echo '*** FAILED: RABBITMQ_HOST environment variable not defined'
+REDIS=""
+if [ "$ZT_USE_REDIS" == "true" ]; then
+    if [ -z "$ZT_REDIS_HOST" ]; then
+        echo '*** FAILED: ZT_REDIS_HOST environment variable not defined'
         exit 1
     fi
-    if [ -z "$RABBITMQ_PORT" ]; then
-        echo '*** FAILED: RABBITMQ_PORT environment variable not defined'
-        exit 1
-    fi
-    if [ -z "$RABBITMQ_USERNAME" ]; then
-        echo '*** FAILED: RABBITMQ_USERNAME environment variable not defined'
+
+    if [ -z "$ZT_REDIS_PORT" ]; then
+        echo '*** FAILED: ZT_REDIS_PORT enivronment variable not defined'
         exit 1
     fi
-    if [ -z "$RABBITMQ_PASSWORD" ]; then
-        echo '*** FAILED: RABBITMQ_PASSWORD environment variable not defined'
+
+    if [ -z "$ZT_REDIS_CLUSTER_MODE" ]; then
+        echo '*** FAILED: ZT_REDIS_CLUSTER_MODE environment variable not defined'
         exit 1
     fi
-    RMQ=", \"rabbitmq\": {
-        \"host\": \"${RABBITMQ_HOST}\",
-        \"port\": ${RABBITMQ_PORT},
-        \"username\": \"${RABBITMQ_USERNAME}\",
-        \"password\": \"${RABBITMQ_PASSWORD}\"
-    }"
+
+    REDIS="\"redis\": {
+            \"hostname\": \"${ZT_REDIS_HOST}\",
+            \"port\": ${ZT_REDIS_PORT},
+            \"clusterMode\": ${ZT_REDIS_CLUSTER_MODE},
+            \"password\": \"${ZT_REDIS_PASSWORD}\"
+        }
+    "
+else
+    REDIS="\"redis\": {}"
 fi
 
 mkdir -p /var/lib/zerotier-one
@@ -62,14 +64,14 @@ DEFAULT_PORT=9993
 
 echo "{
     \"settings\": {
+        \"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\",
         \"portMappingEnabled\": true,
         \"softwareUpdate\": \"disable\",
         \"interfacePrefixBlacklist\": [
             \"inot\",
             \"nat64\"
         ],
-        \"controllerDbPath\": \"postgres:host=${ZT_DB_HOST} port=${ZT_DB_PORT} dbname=${ZT_DB_NAME} user=${ZT_DB_USER} password=${ZT_DB_PASSWORD} sslmode=prefer sslcert=${DB_CLIENT_CERT} sslkey=${DB_CLIENT_KEY} sslrootcert=${DB_SERVER_CA}\"
-        ${RMQ}
+        ${REDIS}
     }
 }    
 " > /var/lib/zerotier-one/local.conf

+ 0 - 1
ext/hiredis-vip-0.3.0/.gitignore → ext/hiredis-0.14.1/.gitignore

@@ -3,5 +3,4 @@
 /*.o
 /*.so
 /*.dylib
-/*.a
 /*.pc

+ 45 - 0
ext/hiredis-0.14.1/.travis.yml

@@ -0,0 +1,45 @@
+language: c
+sudo: false
+compiler:
+  - gcc
+  - clang
+
+os:
+  - linux
+  - osx
+
+branches:
+  only:
+    - staging
+    - trying
+    - master
+
+before_script:
+    - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
+
+addons:
+  apt:
+    packages:
+    - libc6-dbg
+    - libc6-dev
+    - libc6:i386
+    - libc6-dev-i386
+    - libc6-dbg:i386
+    - gcc-multilib
+    - valgrind
+
+env:
+    - CFLAGS="-Werror"
+    - PRE="valgrind --track-origins=yes --leak-check=full"
+    - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
+    - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
+
+matrix:
+  exclude:
+    - os: osx
+      env: PRE="valgrind --track-origins=yes --leak-check=full"
+
+    - os: osx
+      env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
+
+script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example

+ 190 - 0
ext/hiredis-0.14.1/CHANGELOG.md

@@ -0,0 +1,190 @@
+**NOTE: BREAKING CHANGES upgrading from 0.13.x to 0.14.x **:
+
+* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
+  protocol errors. This is consistent with the RESP specification. On 32-bit
+  platforms, the upper bound is lowered to `SIZE_MAX`.
+
+* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
+
+  User code should compare this to `size_t` values as well.  If it was used to
+  compare to other values, casting might be necessary or can be removed, if
+  casting was applied before.
+
+### 0.14.1 (2020-03-13)
+
+* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder)
+
+### 0.14.0 (2018-09-25)
+
+* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
+* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
+* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
+* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
+* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
+* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
+* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
+* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
+* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
+* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
+* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
+* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
+* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
+* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
+* Fix libevent leak (zfz [515228])
+* Clean up GCC warning (Ichito Nagata [2ec774])
+* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
+* Solaris compilation fix (Donald Whyte [41b07d])
+* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
+* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
+* libuv use after free fix (Paul Scott [cbb956])
+* Properly close socket fd on reconnect attempt (WSL [64d1ec])
+* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
+* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
+* Update libevent (Chris Xin [386802])
+* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
+* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
+* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
+* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
+* Compatibility fix for strerror_r (Tom Lee [bb1747])
+* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
+* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
+* Catch a buffer overflow when formatting the error message
+* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
+* Fix warnings, when compiled with -Wshadow
+* Make hiredis compile in Cygwin on Windows, now CI-tested
+
+**BREAKING CHANGES**:
+
+* Remove backwards compatibility macro's
+
+This removes the following old function aliases, use the new name now:
+
+| Old                         | New                    |
+| --------------------------- | ---------------------- |
+| redisReplyReaderCreate      | redisReaderCreate      |
+| redisReplyReaderCreate      | redisReaderCreate      |
+| redisReplyReaderFree        | redisReaderFree        |
+| redisReplyReaderFeed        | redisReaderFeed        |
+| redisReplyReaderGetReply    | redisReaderGetReply    |
+| redisReplyReaderSetPrivdata | redisReaderSetPrivdata |
+| redisReplyReaderGetObject   | redisReaderGetObject   |
+| redisReplyReaderGetError    | redisReaderGetError    |
+
+* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS`
+
+Previously it broke some builds for people that had `DEBUG` set to some arbitrary value,
+due to debugging other software.
+By renaming we avoid unintentional name clashes.
+
+Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again.
+
+### 0.13.3 (2015-09-16)
+
+* Revert "Clear `REDIS_CONNECTED` flag when connection is closed".
+* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni)
+
+
+If the `REDIS_CONNECTED` flag is cleared,
+the async onDisconnect callback function will never be called.
+This causes problems as the disconnect is never reported back to the user.
+
+### 0.13.2 (2015-08-25)
+
+* Prevent crash on pending replies in async code (Thanks, @switch-st)
+* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs)
+* Add MacOS X addapter (Thanks, @dizzus)
+* Add Qt adapter (Thanks, Pietro Cerutti)
+* Add Ivykis adapter (Thanks, Gergely Nagy)
+
+All adapters are provided as is and are only tested where possible.
+
+### 0.13.1 (2015-05-03)
+
+This is a bug fix release.
+The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code.
+Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects.
+Other non-C99 code can now use hiredis as usual again.
+Sorry for the inconvenience.
+
+* Fix memory leak in async reply handling (Salvatore Sanfilippo)
+* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa)
+
+### 0.13.0 (2015-04-16)
+
+This release adds a minimal Windows compatibility layer.
+The parser, standalone since v0.12.0, can now be compiled on Windows
+(and thus used in other client libraries as well)
+
+* Windows compatibility layer for parser code (tzickel)
+* Properly escape data printed to PKGCONF file (Dan Skorupski)
+* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff)
+* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra)
+
+### 0.12.1 (2015-01-26)
+
+* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location
+* Fix `make test` as 32 bit build on 64 bit platform
+
+### 0.12.0 (2015-01-22)
+
+* Add optional KeepAlive support
+
+* Try again on EINTR errors
+
+* Add libuv adapter
+
+* Add IPv6 support
+
+* Remove possibility of multiple close on same fd
+
+* Add ability to bind source address on connect
+
+* Add redisConnectFd() and redisFreeKeepFd()
+
+* Fix getaddrinfo() memory leak
+
+* Free string if it is unused (fixes memory leak)
+
+* Improve redisAppendCommandArgv performance 2.5x
+
+* Add support for SO_REUSEADDR
+
+* Fix redisvFormatCommand format parsing
+
+* Add GLib 2.0 adapter
+
+* Refactor reading code into read.c
+
+* Fix errno error buffers to not clobber errors
+
+* Generate pkgconf during build
+
+* Silence _BSD_SOURCE warnings
+
+* Improve digit counting for multibulk creation
+
+
+### 0.11.0
+
+* Increase the maximum multi-bulk reply depth to 7.
+
+* Increase the read buffer size from 2k to 16k.
+
+* Use poll(2) instead of select(2) to support large fds (>= 1024).
+
+### 0.10.1
+
+* Makefile overhaul. Important to check out if you override one or more
+  variables using environment variables or via arguments to the "make" tool.
+
+* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements
+  being created by the default reply object functions.
+
+* Issue #43: Don't crash in an asynchronous context when Redis returns an error
+  reply after the connection has been made (this happens when the maximum
+  number of connections is reached).
+
+### 0.10.0
+
+* See commit log.
+

+ 0 - 0
ext/hiredis-vip-0.3.0/COPYING → ext/hiredis-0.14.1/COPYING


+ 51 - 42
ext/hiredis-vip-0.3.0/Makefile → ext/hiredis-0.14.1/Makefile

@@ -3,19 +3,20 @@
 # Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
 # This file is released under the BSD license, see the COPYING file
 
-OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o adlist.o hircluster.o
+OBJ=net.o hiredis.o sds.o async.o read.o alloc.o
 EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
 TESTS=hiredis-test
-LIBNAME=libhiredis_vip
+LIBNAME=libhiredis
 PKGCONFNAME=hiredis.pc
 
-HIREDIS_VIP_MAJOR=$(shell grep HIREDIS_VIP_MAJOR hircluster.h | awk '{print $$3}')
-HIREDIS_VIP_MINOR=$(shell grep HIREDIS_VIP_MINOR hircluster.h | awk '{print $$3}')
-HIREDIS_VIP_PATCH=$(shell grep HIREDIS_VIP_PATCH hircluster.h | awk '{print $$3}')
+HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
+HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
+HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}')
+HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}')
 
 # Installation related variables and target
 PREFIX?=/usr/local
-INCLUDE_PATH?=include/hiredis-vip
+INCLUDE_PATH?=include/hiredis
 LIBRARY_PATH?=lib
 PKGCONF_PATH?=pkgconfig
 INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
@@ -35,17 +36,18 @@ endef
 export REDIS_TEST_CONFIG
 
 # Fallback to gcc when $CC is not in $PATH.
-CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
+CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
+CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
 OPTIMIZATION?=-O3
 WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
-DEBUG?= -g -ggdb
-REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH)
-REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
+DEBUG_FLAGS?= -g -ggdb
+REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
+REAL_LDFLAGS=$(LDFLAGS)
 
 DYLIBSUFFIX=so
 STLIBSUFFIX=a
-DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR)
-DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR)
+DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
+DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
 DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
 DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
 STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
@@ -56,32 +58,24 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
 ifeq ($(uname_S),SunOS)
   REAL_LDFLAGS+= -ldl -lnsl -lsocket
   DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
-  INSTALL= cp -r
 endif
 ifeq ($(uname_S),Darwin)
   DYLIBSUFFIX=dylib
-  DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(DYLIBSUFFIX)
-  DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(DYLIBSUFFIX)
-  DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+  DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
+  DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
 endif
 
 all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
 
 # Deps (use make dep to generate this)
-
-adlist.o: adlist.c adlist.h hiutil.h
-async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
-command.o: command.c command.h hiredis.h read.h sds.h adlist.h hiutil.h hiarray.h
-crc16.o: crc16.c hiutil.h
-dict.o: dict.c fmacros.h dict.h
-hiarray.o: hiarray.c hiarray.h hiutil.h
-hircluster.o: hircluster.c fmacros.h hircluster.h hiredis.h read.h sds.h adlist.h hiarray.h hiutil.h async.h command.h dict.c dict.h
-hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
-hiutil.o: hiutil.c hiutil.h
-net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
+alloc.o: alloc.c fmacros.h alloc.h
+async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
+dict.o: dict.c fmacros.h alloc.h dict.h
+hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h
+net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h
 read.o: read.c fmacros.h read.h sds.h
-sds.o: sds.c sds.h
-test.o: test.c fmacros.h hiredis.h read.h sds.h net.h
+sds.o: sds.c sds.h sdsalloc.h
+test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h
 
 $(DYLIBNAME): $(OBJ)
 	$(DYLIB_MAKE_CMD) $(OBJ)
@@ -100,7 +94,13 @@ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
 	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
 
 hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
-	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
+
+hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
+
+hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
 
 ifndef AE_DIR
 hiredis-example-ae:
@@ -117,7 +117,20 @@ hiredis-example-libuv:
 	@false
 else
 hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
-	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME)
+	$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME)
+endif
+
+ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
+hiredis-example-qt:
+	@echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR"
+	@false
+else
+hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
+	$(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
+	    $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
+	$(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
+	    $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
+	$(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore
 endif
 
 hiredis-example: examples/example.c $(STLIBNAME)
@@ -148,11 +161,7 @@ clean:
 dep:
 	$(CC) -MM *.c
 
-ifeq ($(uname_S),SunOS)
-  INSTALL?= cp -r
-endif
-
-INSTALL?= cp -a
+INSTALL?= cp -pPR
 
 $(PKGCONFNAME): hiredis.h
 	@echo "Generating $@ for pkgconfig..."
@@ -163,16 +172,16 @@ $(PKGCONFNAME): hiredis.h
 	@echo >> $@
 	@echo Name: hiredis >> $@
 	@echo Description: Minimalistic C client library for Redis. >> $@
-	@echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@
+	@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
 	@echo Libs: -L\$${libdir} -lhiredis >> $@
 	@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
 
 install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
-	mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
-	$(INSTALL) hiredis.h async.h read.h sds.h hiutil.h hiarray.h dict.h dict.c adlist.h fmacros.h hircluster.h adapters $(INSTALL_INCLUDE_PATH)
+	mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
+	$(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH)
+	$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
 	$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
-	cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
-	cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME)
+	cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
 	$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
 	mkdir -p $(INSTALL_PKGCONF_PATH)
 	$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
@@ -202,4 +211,4 @@ coverage: gcov
 noopt:
 	$(MAKE) OPTIMIZATION=""
 
-.PHONY: all test check clean dep install 32bit gprof gcov noopt
+.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt

+ 410 - 0
ext/hiredis-0.14.1/README.md

@@ -0,0 +1,410 @@
+[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis)
+
+**This Readme reflects the latest changed in the master branch. See [v0.14.1](https://github.com/redis/hiredis/tree/v0.14.1) for the Readme and documentation for the latest release.**
+
+# HIREDIS
+
+Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database.
+
+It is minimalistic because it just adds minimal support for the protocol, but
+at the same time it uses a high level printf-alike API in order to make it
+much higher level than otherwise suggested by its minimal code base and the
+lack of explicit bindings for every Redis command.
+
+Apart from supporting sending commands and receiving replies, it comes with
+a reply parser that is decoupled from the I/O layer. It
+is a stream parser designed for easy reusability, which can for instance be used
+in higher level language bindings for efficient reply parsing.
+
+Hiredis only supports the binary-safe Redis protocol, so you can use it with any
+Redis version >= 1.2.0.
+
+The library comes with multiple APIs. There is the
+*synchronous API*, the *asynchronous API* and the *reply parsing API*.
+
+## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x
+
+Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
+protocol errors. This is consistent with the RESP specification. On 32-bit
+platforms, the upper bound is lowered to `SIZE_MAX`.
+
+Change `redisReply.len` to `size_t`, as it denotes the the size of a string
+
+User code should compare this to `size_t` values as well.  If it was used to
+compare to other values, casting might be necessary or can be removed, if
+casting was applied before.
+
+For a detailed list of changes please view our [Changelog](CHANGELOG.md).
+
+## Synchronous API
+
+To consume the synchronous API, there are only a few function calls that need to be introduced:
+
+```c
+redisContext *redisConnect(const char *ip, int port);
+void *redisCommand(redisContext *c, const char *format, ...);
+void freeReplyObject(void *reply);
+```
+
+### Connecting
+
+The function `redisConnect` is used to create a so-called `redisContext`. The
+context is where Hiredis holds state for a connection. The `redisContext`
+struct has an integer `err` field that is non-zero when the connection is in
+an error state. The field `errstr` will contain a string with a description of
+the error. More information on errors can be found in the **Errors** section.
+After trying to connect to Redis using `redisConnect` you should
+check the `err` field to see if establishing the connection was successful:
+```c
+redisContext *c = redisConnect("127.0.0.1", 6379);
+if (c == NULL || c->err) {
+    if (c) {
+        printf("Error: %s\n", c->errstr);
+        // handle error
+    } else {
+        printf("Can't allocate redis context\n");
+    }
+}
+```
+
+*Note: A `redisContext` is not thread-safe.*
+
+### Sending commands
+
+There are several ways to issue commands to Redis. The first that will be introduced is
+`redisCommand`. This function takes a format similar to printf. In the simplest form,
+it is used like this:
+```c
+reply = redisCommand(context, "SET foo bar");
+```
+
+The specifier `%s` interpolates a string in the command, and uses `strlen` to
+determine the length of the string:
+```c
+reply = redisCommand(context, "SET foo %s", value);
+```
+When you need to pass binary safe strings in a command, the `%b` specifier can be
+used. Together with a pointer to the string, it requires a `size_t` length argument
+of the string:
+```c
+reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
+```
+Internally, Hiredis splits the command in different arguments and will
+convert it to the protocol used to communicate with Redis.
+One or more spaces separates arguments, so you can use the specifiers
+anywhere in an argument:
+```c
+reply = redisCommand(context, "SET key:%s %s", myid, value);
+```
+
+### Using replies
+
+The return value of `redisCommand` holds a reply when the command was
+successfully executed. When an error occurs, the return value is `NULL` and
+the `err` field in the context will be set (see section on **Errors**).
+Once an error is returned the context cannot be reused and you should set up
+a new connection.
+
+The standard replies that `redisCommand` are of the type `redisReply`. The
+`type` field in the `redisReply` should be used to test what kind of reply
+was received:
+
+* **`REDIS_REPLY_STATUS`**:
+    * The command replied with a status reply. The status string can be accessed using `reply->str`.
+      The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ERROR`**:
+    *  The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
+
+* **`REDIS_REPLY_INTEGER`**:
+    * The command replied with an integer. The integer value can be accessed using the
+      `reply->integer` field of type `long long`.
+
+* **`REDIS_REPLY_NIL`**:
+    * The command replied with a **nil** object. There is no data to access.
+
+* **`REDIS_REPLY_STRING`**:
+    * A bulk (string) reply. The value of the reply can be accessed using `reply->str`.
+      The length of this string can be accessed using `reply->len`.
+
+* **`REDIS_REPLY_ARRAY`**:
+    * A multi bulk reply. The number of elements in the multi bulk reply is stored in
+      `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well
+      and can be accessed via `reply->element[..index..]`.
+      Redis may reply with nested arrays but this is fully supported.
+
+Replies should be freed using the `freeReplyObject()` function.
+Note that this function will take care of freeing sub-reply objects
+contained in arrays and nested arrays, so there is no need for the user to
+free the sub replies (it is actually harmful and will corrupt the memory).
+
+**Important:** the current version of hiredis (0.10.0) frees replies when the
+asynchronous API is used. This means you should not call `freeReplyObject` when
+you use this API. The reply is cleaned up by hiredis _after_ the callback
+returns. This behavior will probably change in future releases, so make sure to
+keep an eye on the changelog when upgrading (see issue #39).
+
+### Cleaning up
+
+To disconnect and free the context the following function can be used:
+```c
+void redisFree(redisContext *c);
+```
+This function immediately closes the socket and then frees the allocations done in
+creating the context.
+
+### Sending commands (cont'd)
+
+Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
+It has the following prototype:
+```c
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+```
+It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
+arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
+use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
+need to be binary safe, the entire array of lengths `argvlen` should be provided.
+
+The return value has the same semantic as `redisCommand`.
+
+### Pipelining
+
+To explain how Hiredis supports pipelining in a blocking connection, there needs to be
+understanding of the internal execution flow.
+
+When any of the functions in the `redisCommand` family is called, Hiredis first formats the
+command according to the Redis protocol. The formatted command is then put in the output buffer
+of the context. This output buffer is dynamic, so it can hold any number of commands.
+After the command is put in the output buffer, `redisGetReply` is called. This function has the
+following two execution paths:
+
+1. The input buffer is non-empty:
+    * Try to parse a single reply from the input buffer and return it
+    * If no reply could be parsed, continue at *2*
+2. The input buffer is empty:
+    * Write the **entire** output buffer to the socket
+    * Read from the socket until a single reply could be parsed
+
+The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
+is expected on the socket. To pipeline commands, the only things that needs to be done is
+filling up the output buffer. For this cause, two commands can be used that are identical
+to the `redisCommand` family, apart from not returning a reply:
+```c
+void redisAppendCommand(redisContext *c, const char *format, ...);
+void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+```
+After calling either function one or more times, `redisGetReply` can be used to receive the
+subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
+the latter means an error occurred while reading a reply. Just as with the other commands,
+the `err` field in the context can be used to find out what the cause of this error is.
+
+The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
+a single call to `read(2)`):
+```c
+redisReply *reply;
+redisAppendCommand(context,"SET foo bar");
+redisAppendCommand(context,"GET foo");
+redisGetReply(context,&reply); // reply for SET
+freeReplyObject(reply);
+redisGetReply(context,&reply); // reply for GET
+freeReplyObject(reply);
+```
+This API can also be used to implement a blocking subscriber:
+```c
+reply = redisCommand(context,"SUBSCRIBE foo");
+freeReplyObject(reply);
+while(redisGetReply(context,&reply) == REDIS_OK) {
+    // consume message
+    freeReplyObject(reply);
+}
+```
+### Errors
+
+When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
+returned. The `err` field inside the context will be non-zero and set to one of the
+following constants:
+
+* **`REDIS_ERR_IO`**:
+    There was an I/O error while creating the connection, trying to write
+    to the socket or read from the socket. If you included `errno.h` in your
+    application, you can use the global `errno` variable to find out what is
+    wrong.
+
+* **`REDIS_ERR_EOF`**:
+    The server closed the connection which resulted in an empty read.
+
+* **`REDIS_ERR_PROTOCOL`**:
+    There was an error while parsing the protocol.
+
+* **`REDIS_ERR_OTHER`**:
+    Any other error. Currently, it is only used when a specified hostname to connect
+    to cannot be resolved.
+
+In every case, the `errstr` field in the context will be set to hold a string representation
+of the error.
+
+## Asynchronous API
+
+Hiredis comes with an asynchronous API that works easily with any event library.
+Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)
+and [libevent](http://monkey.org/~provos/libevent/).
+
+### Connecting
+
+The function `redisAsyncConnect` can be used to establish a non-blocking connection to
+Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
+should be checked after creation to see if there were errors creating the connection.
+Because the connection that will be created is non-blocking, the kernel is not able to
+instantly return if the specified host and port is able to accept a connection.
+
+*Note: A `redisAsyncContext` is not thread-safe.*
+
+```c
+redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+if (c->err) {
+    printf("Error: %s\n", c->errstr);
+    // handle error
+}
+```
+
+The asynchronous context can hold a disconnect callback function that is called when the
+connection is disconnected (either because of an error or per user request). This function should
+have the following prototype:
+```c
+void(const redisAsyncContext *c, int status);
+```
+On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
+user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
+field in the context can be accessed to find out the cause of the error.
+
+The context object is always freed after the disconnect callback fired. When a reconnect is needed,
+the disconnect callback is a good point to do so.
+
+Setting the disconnect callback can only be done once per context. For subsequent calls it will
+return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
+```c
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+```
+### Sending commands and their callbacks
+
+In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
+Therefore, unlike the synchronous API, there is only a single way to send commands.
+Because commands are sent to Redis asynchronously, issuing a command requires a callback function
+that is called when the reply is received. Reply callbacks should have the following prototype:
+```c
+void(redisAsyncContext *c, void *reply, void *privdata);
+```
+The `privdata` argument can be used to curry arbitrary data to the callback from the point where
+the command is initially queued for execution.
+
+The functions that can be used to issue commands in an asynchronous context are:
+```c
+int redisAsyncCommand(
+  redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+  const char *format, ...);
+int redisAsyncCommandArgv(
+  redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
+  int argc, const char **argv, const size_t *argvlen);
+```
+Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
+was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
+is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
+returned on calls to the `redisAsyncCommand` family.
+
+If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback
+for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only
+valid for the duration of the callback.
+
+All pending callbacks are called with a `NULL` reply when the context encountered an error.
+
+### Disconnecting
+
+An asynchronous connection can be terminated using:
+```c
+void redisAsyncDisconnect(redisAsyncContext *ac);
+```
+When this function is called, the connection is **not** immediately terminated. Instead, new
+commands are no longer accepted and the connection is only terminated when all pending commands
+have been written to the socket, their respective replies have been read and their respective
+callbacks have been executed. After this, the disconnection callback is executed with the
+`REDIS_OK` status and the context object is freed.
+
+### Hooking it up to event library *X*
+
+There are a few hooks that need to be set on the context object after it is created.
+See the `adapters/` directory for bindings to *libev* and *libevent*.
+
+## Reply parsing API
+
+Hiredis comes with a reply parsing API that makes it easy for writing higher
+level language bindings.
+
+The reply parsing API consists of the following functions:
+```c
+redisReader *redisReaderCreate(void);
+void redisReaderFree(redisReader *reader);
+int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
+int redisReaderGetReply(redisReader *reader, void **reply);
+```
+The same set of functions are used internally by hiredis when creating a
+normal Redis context, the above API just exposes it to the user for a direct
+usage.
+
+### Usage
+
+The function `redisReaderCreate` creates a `redisReader` structure that holds a
+buffer with unparsed data and state for the protocol parser.
+
+Incoming data -- most likely from a socket -- can be placed in the internal
+buffer of the `redisReader` using `redisReaderFeed`. This function will make a
+copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed
+when `redisReaderGetReply` is called. This function returns an integer status
+and a reply object (as described above) via `void **reply`. The returned status
+can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went
+wrong (either a protocol error, or an out of memory error).
+
+The parser limits the level of nesting for multi bulk payloads to 7. If the
+multi bulk nesting level is higher than this, the parser returns an error.
+
+### Customizing replies
+
+The function `redisReaderGetReply` creates `redisReply` and makes the function
+argument `reply` point to the created `redisReply` variable. For instance, if
+the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply`
+will hold the status as a vanilla C string. However, the functions that are
+responsible for creating instances of the `redisReply` can be customized by
+setting the `fn` field on the `redisReader` struct. This should be done
+immediately after creating the `redisReader`.
+
+For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c)
+uses customized reply object functions to create Ruby objects.
+
+### Reader max buffer
+
+Both when using the Reader API directly or when using it indirectly via a
+normal Redis context, the redisReader structure uses a buffer in order to
+accumulate data from the server.
+Usually this buffer is destroyed when it is empty and is larger than 16
+KiB in order to avoid wasting memory in unused buffers
+
+However when working with very big payloads destroying the buffer may slow
+down performances considerably, so it is possible to modify the max size of
+an idle buffer changing the value of the `maxbuf` field of the reader structure
+to the desired value. The special value of 0 means that there is no maximum
+value for an idle buffer, so the buffer will never get freed.
+
+For instance if you have a normal Redis context you can set the maximum idle
+buffer to zero (unlimited) just with:
+```c
+context->reader->maxbuf = 0;
+```
+This should be done only in order to maximize performances when working with
+large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again
+as soon as possible in order to prevent allocation of useless memory.
+
+## AUTHORS
+
+Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
+Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.  
+Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
+Jan-Erik Rediger (janerik at fnordig dot com)

+ 1 - 28
ext/hiredis-vip-0.3.0/adapters/ae.h → ext/hiredis-0.14.1/adapters/ae.h

@@ -35,10 +35,6 @@
 #include "../hiredis.h"
 #include "../async.h"
 
-#if 1 //shenzheng 2015-11-5 redis cluster
-#include "../hircluster.h"
-#endif //shenzheng 2015-11-5 redis cluster
-
 typedef struct redisAeEvents {
     redisAsyncContext *context;
     aeEventLoop *loop;
@@ -112,7 +108,7 @@ static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
         return REDIS_ERR;
 
     /* Create container for context and r/w events */
-    e = (redisAeEvents*)malloc(sizeof(*e));
+    e = (redisAeEvents*)hi_malloc(sizeof(*e));
     e->context = ac;
     e->loop = loop;
     e->fd = c->fd;
@@ -128,27 +124,4 @@ static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
 
     return REDIS_OK;
 }
-
-#if 1 //shenzheng 2015-11-5 redis cluster
-
-static int redisAeAttach_link(redisAsyncContext *ac, void *base)
-{
-	redisAeAttach((aeEventLoop *)base, ac);
-}
-
-static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) {
-
-	if(acc == NULL || loop == NULL)
-	{
-		return REDIS_ERR;
-	}
-
-	acc->adapter = loop;
-	acc->attach_fn = redisAeAttach_link;
-	
-    return REDIS_OK;
-}
-
-#endif //shenzheng 2015-11-5 redis cluster
-
 #endif

+ 10 - 10
ext/hiredis-vip-0.3.0/adapters/glib.h → ext/hiredis-0.14.1/adapters/glib.h

@@ -16,43 +16,43 @@ typedef struct
 static void
 redis_source_add_read (gpointer data)
 {
-    RedisSource *source = data;
+    RedisSource *source = (RedisSource *)data;
     g_return_if_fail(source);
     source->poll_fd.events |= G_IO_IN;
-    g_main_context_wakeup(g_source_get_context(data));
+    g_main_context_wakeup(g_source_get_context((GSource *)data));
 }
 
 static void
 redis_source_del_read (gpointer data)
 {
-    RedisSource *source = data;
+    RedisSource *source = (RedisSource *)data;
     g_return_if_fail(source);
     source->poll_fd.events &= ~G_IO_IN;
-    g_main_context_wakeup(g_source_get_context(data));
+    g_main_context_wakeup(g_source_get_context((GSource *)data));
 }
 
 static void
 redis_source_add_write (gpointer data)
 {
-    RedisSource *source = data;
+    RedisSource *source = (RedisSource *)data;
     g_return_if_fail(source);
     source->poll_fd.events |= G_IO_OUT;
-    g_main_context_wakeup(g_source_get_context(data));
+    g_main_context_wakeup(g_source_get_context((GSource *)data));
 }
 
 static void
 redis_source_del_write (gpointer data)
 {
-    RedisSource *source = data;
+    RedisSource *source = (RedisSource *)data;
     g_return_if_fail(source);
     source->poll_fd.events &= ~G_IO_OUT;
-    g_main_context_wakeup(g_source_get_context(data));
+    g_main_context_wakeup(g_source_get_context((GSource *)data));
 }
 
 static void
 redis_source_cleanup (gpointer data)
 {
-    RedisSource *source = data;
+    RedisSource *source = (RedisSource *)data;
 
     g_return_if_fail(source);
 
@@ -63,7 +63,7 @@ redis_source_cleanup (gpointer data)
      * current main loop. However, we will remove the GPollFD.
      */
     if (source->poll_fd.fd >= 0) {
-        g_source_remove_poll(data, &source->poll_fd);
+        g_source_remove_poll((GSource *)data, &source->poll_fd);
         source->poll_fd.fd = -1;
     }
 }

+ 81 - 0
ext/hiredis-0.14.1/adapters/ivykis.h

@@ -0,0 +1,81 @@
+#ifndef __HIREDIS_IVYKIS_H__
+#define __HIREDIS_IVYKIS_H__
+#include <iv.h>
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct redisIvykisEvents {
+    redisAsyncContext *context;
+    struct iv_fd fd;
+} redisIvykisEvents;
+
+static void redisIvykisReadEvent(void *arg) {
+    redisAsyncContext *context = (redisAsyncContext *)arg;
+    redisAsyncHandleRead(context);
+}
+
+static void redisIvykisWriteEvent(void *arg) {
+    redisAsyncContext *context = (redisAsyncContext *)arg;
+    redisAsyncHandleWrite(context);
+}
+
+static void redisIvykisAddRead(void *privdata) {
+    redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+    iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent);
+}
+
+static void redisIvykisDelRead(void *privdata) {
+    redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+    iv_fd_set_handler_in(&e->fd, NULL);
+}
+
+static void redisIvykisAddWrite(void *privdata) {
+    redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+    iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent);
+}
+
+static void redisIvykisDelWrite(void *privdata) {
+    redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+    iv_fd_set_handler_out(&e->fd, NULL);
+}
+
+static void redisIvykisCleanup(void *privdata) {
+    redisIvykisEvents *e = (redisIvykisEvents*)privdata;
+
+    iv_fd_unregister(&e->fd);
+    free(e);
+}
+
+static int redisIvykisAttach(redisAsyncContext *ac) {
+    redisContext *c = &(ac->c);
+    redisIvykisEvents *e;
+
+    /* Nothing should be attached when something is already attached */
+    if (ac->ev.data != NULL)
+        return REDIS_ERR;
+
+    /* Create container for context and r/w events */
+    e = (redisIvykisEvents*)hi_malloc(sizeof(*e));
+    e->context = ac;
+
+    /* Register functions to start/stop listening for events */
+    ac->ev.addRead = redisIvykisAddRead;
+    ac->ev.delRead = redisIvykisDelRead;
+    ac->ev.addWrite = redisIvykisAddWrite;
+    ac->ev.delWrite = redisIvykisDelWrite;
+    ac->ev.cleanup = redisIvykisCleanup;
+    ac->ev.data = e;
+
+    /* Initialize and install read/write events */
+    IV_FD_INIT(&e->fd);
+    e->fd.fd = c->fd;
+    e->fd.handler_in = redisIvykisReadEvent;
+    e->fd.handler_out = redisIvykisWriteEvent;
+    e->fd.handler_err = NULL;
+    e->fd.cookie = e->context;
+
+    iv_fd_register(&e->fd);
+
+    return REDIS_OK;
+}
+#endif

+ 1 - 1
ext/hiredis-vip-0.3.0/adapters/libev.h → ext/hiredis-0.14.1/adapters/libev.h

@@ -119,7 +119,7 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
         return REDIS_ERR;
 
     /* Create container for context and r/w events */
-    e = (redisLibevEvents*)malloc(sizeof(*e));
+    e = (redisLibevEvents*)hi_malloc(sizeof(*e));
     e->context = ac;
 #if EV_MULTIPLICITY
     e->loop = loop;

+ 13 - 40
ext/hiredis-vip-0.3.0/adapters/libevent.h → ext/hiredis-0.14.1/adapters/libevent.h

@@ -30,17 +30,13 @@
 
 #ifndef __HIREDIS_LIBEVENT_H__
 #define __HIREDIS_LIBEVENT_H__
-#include <event.h>
+#include <event2/event.h>
 #include "../hiredis.h"
 #include "../async.h"
 
-#if 1 //shenzheng 2015-9-21 redis cluster
-#include "../hircluster.h"
-#endif //shenzheng 2015-9-21 redis cluster
-
 typedef struct redisLibeventEvents {
     redisAsyncContext *context;
-    struct event rev, wev;
+    struct event *rev, *wev;
 } redisLibeventEvents;
 
 static void redisLibeventReadEvent(int fd, short event, void *arg) {
@@ -57,28 +53,28 @@ static void redisLibeventWriteEvent(int fd, short event, void *arg) {
 
 static void redisLibeventAddRead(void *privdata) {
     redisLibeventEvents *e = (redisLibeventEvents*)privdata;
-    event_add(&e->rev,NULL);
+    event_add(e->rev,NULL);
 }
 
 static void redisLibeventDelRead(void *privdata) {
     redisLibeventEvents *e = (redisLibeventEvents*)privdata;
-    event_del(&e->rev);
+    event_del(e->rev);
 }
 
 static void redisLibeventAddWrite(void *privdata) {
     redisLibeventEvents *e = (redisLibeventEvents*)privdata;
-    event_add(&e->wev,NULL);
+    event_add(e->wev,NULL);
 }
 
 static void redisLibeventDelWrite(void *privdata) {
     redisLibeventEvents *e = (redisLibeventEvents*)privdata;
-    event_del(&e->wev);
+    event_del(e->wev);
 }
 
 static void redisLibeventCleanup(void *privdata) {
     redisLibeventEvents *e = (redisLibeventEvents*)privdata;
-    event_del(&e->rev);
-    event_del(&e->wev);
+    event_free(e->rev);
+    event_free(e->wev);
     free(e);
 }
 
@@ -91,7 +87,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
         return REDIS_ERR;
 
     /* Create container for context and r/w events */
-    e = (redisLibeventEvents*)malloc(sizeof(*e));
+    e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
     e->context = ac;
 
     /* Register functions to start/stop listening for events */
@@ -103,33 +99,10 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
     ac->ev.data = e;
 
     /* Initialize and install read/write events */
-    event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
-    event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e);
-    event_base_set(base,&e->rev);
-    event_base_set(base,&e->wev);
+    e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
+    e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
+    event_add(e->rev, NULL);
+    event_add(e->wev, NULL);
     return REDIS_OK;
 }
-
-#if 1 //shenzheng 2015-9-21 redis cluster
-
-static int redisLibeventAttach_link(redisAsyncContext *ac, void *base)
-{
-	redisLibeventAttach(ac, (struct event_base *)base);
-}
-
-static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) {
-
-	if(acc == NULL || base == NULL)
-	{
-		return REDIS_ERR;
-	}
-
-	acc->adapter = base;
-	acc->attach_fn = redisLibeventAttach_link;
-	
-    return REDIS_OK;
-}
-
-#endif //shenzheng 2015-9-21 redis cluster
-
 #endif

+ 3 - 3
ext/hiredis-vip-0.3.0/adapters/libuv.h → ext/hiredis-0.14.1/adapters/libuv.h

@@ -20,10 +20,10 @@ static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
     return;
   }
 
-  if (events & UV_READABLE) {
+  if (p->context != NULL && (events & UV_READABLE)) {
     redisAsyncHandleRead(p->context);
   }
-  if (events & UV_WRITABLE) {
+  if (p->context != NULL && (events & UV_WRITABLE)) {
     redisAsyncHandleWrite(p->context);
   }
 }
@@ -83,6 +83,7 @@ static void on_close(uv_handle_t* handle) {
 static void redisLibuvCleanup(void *privdata) {
   redisLibuvEvents* p = (redisLibuvEvents*)privdata;
 
+  p->context = NULL; // indicate that context might no longer exist
   uv_close((uv_handle_t*)&p->handle, on_close);
 }
 
@@ -118,5 +119,4 @@ static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {
 
   return REDIS_OK;
 }
-
 #endif

+ 114 - 0
ext/hiredis-0.14.1/adapters/macosx.h

@@ -0,0 +1,114 @@
+//
+//  Created by Дмитрий Бахвалов on 13.07.15.
+//  Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
+//
+
+#ifndef __HIREDIS_MACOSX_H__
+#define __HIREDIS_MACOSX_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "../hiredis.h"
+#include "../async.h"
+
+typedef struct {
+    redisAsyncContext *context;
+    CFSocketRef socketRef;
+    CFRunLoopSourceRef sourceRef;
+} RedisRunLoop;
+
+static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
+    if( redisRunLoop != NULL ) {
+        if( redisRunLoop->sourceRef != NULL ) {
+            CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);
+            CFRelease(redisRunLoop->sourceRef);
+        }
+        if( redisRunLoop->socketRef != NULL ) {
+            CFSocketInvalidate(redisRunLoop->socketRef);
+            CFRelease(redisRunLoop->socketRef);
+        }
+        free(redisRunLoop);
+    }
+    return REDIS_ERR;
+}
+
+static void redisMacOSAddRead(void *privdata) {
+    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
+}
+
+static void redisMacOSDelRead(void *privdata) {
+    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
+}
+
+static void redisMacOSAddWrite(void *privdata) {
+    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+    CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
+}
+
+static void redisMacOSDelWrite(void *privdata) {
+    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+    CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
+}
+
+static void redisMacOSCleanup(void *privdata) {
+    RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
+    freeRedisRunLoop(redisRunLoop);
+}
+
+static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) {
+    redisAsyncContext* context = (redisAsyncContext*) info;
+
+    switch (callbackType) {
+        case kCFSocketReadCallBack:
+            redisAsyncHandleRead(context);
+            break;
+
+        case kCFSocketWriteCallBack:
+            redisAsyncHandleWrite(context);
+            break;
+
+        default:
+            break;
+    }
+}
+
+static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) {
+    redisContext *redisCtx = &(redisAsyncCtx->c);
+
+    /* Nothing should be attached when something is already attached */
+    if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
+
+    RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop));
+    if( !redisRunLoop ) return REDIS_ERR;
+
+    /* Setup redis stuff */
+    redisRunLoop->context = redisAsyncCtx;
+
+    redisAsyncCtx->ev.addRead  = redisMacOSAddRead;
+    redisAsyncCtx->ev.delRead  = redisMacOSDelRead;
+    redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;
+    redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;
+    redisAsyncCtx->ev.cleanup  = redisMacOSCleanup;
+    redisAsyncCtx->ev.data     = redisRunLoop;
+
+    /* Initialize and install read/write events */
+    CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL };
+
+    redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd,
+                                                       kCFSocketReadCallBack | kCFSocketWriteCallBack,
+                                                       redisMacOSAsyncCallback,
+                                                       &socketCtx);
+    if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop);
+
+    redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);
+    if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop);
+
+    CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);
+
+    return REDIS_OK;
+}
+
+#endif
+

+ 135 - 0
ext/hiredis-0.14.1/adapters/qt.h

@@ -0,0 +1,135 @@
+/*-
+ * Copyright (C) 2014 Pietro Cerutti <[email protected]>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_QT_H__
+#define __HIREDIS_QT_H__
+#include <QSocketNotifier>
+#include "../async.h"
+
+static void RedisQtAddRead(void *);
+static void RedisQtDelRead(void *);
+static void RedisQtAddWrite(void *);
+static void RedisQtDelWrite(void *);
+static void RedisQtCleanup(void *);
+
+class RedisQtAdapter : public QObject {
+
+    Q_OBJECT
+
+    friend
+    void RedisQtAddRead(void * adapter) {
+        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+        a->addRead();
+    }
+
+    friend
+    void RedisQtDelRead(void * adapter) {
+        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+        a->delRead();
+    }
+
+    friend
+    void RedisQtAddWrite(void * adapter) {
+        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+        a->addWrite();
+    }
+
+    friend
+    void RedisQtDelWrite(void * adapter) {
+        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+        a->delWrite();
+    }
+
+    friend
+    void RedisQtCleanup(void * adapter) {
+        RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
+        a->cleanup();
+    }
+
+    public:
+        RedisQtAdapter(QObject * parent = 0)
+            : QObject(parent), m_ctx(0), m_read(0), m_write(0) { }
+
+        ~RedisQtAdapter() {
+            if (m_ctx != 0) {
+                m_ctx->ev.data = NULL;
+            }
+        }
+
+        int setContext(redisAsyncContext * ac) {
+            if (ac->ev.data != NULL) {
+                return REDIS_ERR;
+            }
+            m_ctx = ac;
+            m_ctx->ev.data = this;
+            m_ctx->ev.addRead = RedisQtAddRead;
+            m_ctx->ev.delRead = RedisQtDelRead;
+            m_ctx->ev.addWrite = RedisQtAddWrite;
+            m_ctx->ev.delWrite = RedisQtDelWrite;
+            m_ctx->ev.cleanup = RedisQtCleanup;
+            return REDIS_OK;
+        }
+
+    private:
+        void addRead() {
+            if (m_read) return;
+            m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);
+            connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));
+        }
+
+        void delRead() {
+            if (!m_read) return;
+            delete m_read;
+            m_read = 0;
+        }
+
+        void addWrite() {
+            if (m_write) return;
+            m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);
+            connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));
+        }
+
+        void delWrite() {
+            if (!m_write) return;
+            delete m_write;
+            m_write = 0;
+        }
+
+        void cleanup() {
+            delRead();
+            delWrite();
+        }
+
+    private slots:
+        void read() { redisAsyncHandleRead(m_ctx); }
+        void write() { redisAsyncHandleWrite(m_ctx); }
+
+    private:
+        redisAsyncContext * m_ctx;
+        QSocketNotifier * m_read;
+        QSocketNotifier * m_write;
+};
+
+#endif /* !__HIREDIS_QT_H__ */

+ 65 - 0
ext/hiredis-0.14.1/alloc.c

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "fmacros.h"
+#include "alloc.h"
+#include <string.h>
+
+void *hi_malloc(size_t size) {
+    void *ptr = malloc(size);
+    if (ptr == NULL)
+        HIREDIS_OOM_HANDLER;
+
+    return ptr;
+}
+
+void *hi_calloc(size_t nmemb, size_t size) {
+    void *ptr = calloc(nmemb, size);
+    if (ptr == NULL)
+        HIREDIS_OOM_HANDLER;
+
+    return ptr;
+}
+
+void *hi_realloc(void *ptr, size_t size) {
+    void *newptr = realloc(ptr, size);
+    if (newptr == NULL)
+        HIREDIS_OOM_HANDLER;
+
+    return newptr;
+}
+
+char *hi_strdup(const char *str) {
+    char *newstr = strdup(str);
+    if (newstr == NULL)
+        HIREDIS_OOM_HANDLER;
+
+    return newstr;
+}

+ 53 - 0
ext/hiredis-0.14.1/alloc.h

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HIREDIS_ALLOC_H
+#define HIREDIS_ALLOC_H
+
+#include <stdlib.h> /* for size_t */
+
+#ifndef HIREDIS_OOM_HANDLER
+#define HIREDIS_OOM_HANDLER abort()
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void *hi_malloc(size_t size);
+void *hi_calloc(size_t nmemb, size_t size);
+void *hi_realloc(void *ptr, size_t size);
+char *hi_strdup(const char *str);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* HIREDIS_ALLOC_H */

+ 23 - 0
ext/hiredis-0.14.1/appveyor.yml

@@ -0,0 +1,23 @@
+# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
+environment:
+    matrix:
+        - CYG_BASH: C:\cygwin64\bin\bash
+          CC: gcc
+        - CYG_BASH: C:\cygwin\bin\bash
+          CC: gcc
+          TARGET: 32bit
+          TARGET_VARS: 32bit-vars
+
+clone_depth: 1
+
+# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
+init:
+    - git config --global core.autocrlf input
+
+# Install needed build dependencies
+install:
+    - '%CYG_BASH% -lc "cygcheck -dc cygwin"'
+
+build_script:
+    - 'echo building...'
+    - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'

+ 41 - 15
ext/hiredis-vip-0.3.0/async.c → ext/hiredis-0.14.1/async.c

@@ -30,6 +30,7 @@
  */
 
 #include "fmacros.h"
+#include "alloc.h"
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
@@ -68,7 +69,7 @@ static unsigned int callbackHash(const void *key) {
 
 static void *callbackValDup(void *privdata, const void *src) {
     ((void) privdata);
-    redisCallback *dup = malloc(sizeof(*dup));
+    redisCallback *dup = hi_malloc(sizeof(*dup));
     memcpy(dup,src,sizeof(*dup));
     return dup;
 }
@@ -119,7 +120,6 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
     ac->err = 0;
     ac->errstr = NULL;
     ac->data = NULL;
-    ac->dataHandler = NULL;
 
     ac->ev.data = NULL;
     ac->ev.addRead = NULL;
@@ -313,10 +313,6 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
         }
     }
 
-    if (ac->dataHandler) {
-        ac->dataHandler(ac);
-    }
-
     /* Cleanup self */
     redisFree(c);
 }
@@ -341,7 +337,8 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
 
     if (ac->err == 0) {
         /* For clean disconnects, there should be no pending callbacks. */
-        assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
+        int ret = __redisShiftCallback(&ac->replies,NULL);
+        assert(ret == REDIS_ERR);
     } else {
         /* Disconnection is caused by an error, make sure that pending
          * callbacks cannot call new commands. */
@@ -369,6 +366,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
 static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
     redisContext *c = &(ac->c);
     dict *callbacks;
+    redisCallback *cb;
     dictEntry *de;
     int pvariant;
     char *stype;
@@ -392,16 +390,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
         sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
         de = dictFind(callbacks,sname);
         if (de != NULL) {
-            memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
+            cb = dictGetEntryVal(de);
+
+            /* If this is an subscribe reply decrease pending counter. */
+            if (strcasecmp(stype+pvariant,"subscribe") == 0) {
+                cb->pending_subs -= 1;
+            }
+
+            memcpy(dstcb,cb,sizeof(*dstcb));
 
             /* If this is an unsubscribe message, remove it. */
             if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
-                dictDelete(callbacks,sname);
+                if (cb->pending_subs == 0)
+                    dictDelete(callbacks,sname);
 
                 /* If this was the last unsubscribe message, revert to
                  * non-subscribe mode. */
                 assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
-                if (reply->element[2]->integer == 0)
+
+                /* Unset subscribed flag only when no pipelined pending subscribe. */
+                if (reply->element[2]->integer == 0 
+                    && dictSize(ac->sub.channels) == 0
+                    && dictSize(ac->sub.patterns) == 0)
                     c->flags &= ~REDIS_SUBSCRIBED;
             }
         }
@@ -415,7 +425,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
 
 void redisProcessCallbacks(redisAsyncContext *ac) {
     redisContext *c = &(ac->c);
-    redisCallback cb = {NULL, NULL, NULL};
+    redisCallback cb = {NULL, NULL, 0, NULL};
     void *reply = NULL;
     int status;
 
@@ -423,7 +433,8 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
         if (reply == NULL) {
             /* When the connection is being disconnected and there are
              * no more replies, this is the cue to really disconnect. */
-            if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
+            if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
+                && ac->replies.head == NULL) {
                 __redisAsyncDisconnect(ac);
                 return;
             }
@@ -493,7 +504,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
 }
 
 /* Internal helper function to detect socket status the first time a read or
- * write event fires. When connecting was not succesful, the connect callback
+ * write event fires. When connecting was not successful, the connect callback
  * is called with a REDIS_ERR status and the context is free'd. */
 static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
     redisContext *c = &(ac->c);
@@ -587,6 +598,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
 static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
     redisContext *c = &(ac->c);
     redisCallback cb;
+    struct dict *cbdict;
+    dictEntry *de;
+    redisCallback *existcb;
     int pvariant, hasnext;
     const char *cstr, *astr;
     size_t clen, alen;
@@ -600,6 +614,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
     /* Setup callback */
     cb.fn = fn;
     cb.privdata = privdata;
+    cb.pending_subs = 1;
 
     /* Find out which command will be appended. */
     p = nextArgument(cmd,&cstr,&clen);
@@ -616,9 +631,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
         while ((p = nextArgument(p,&astr,&alen)) != NULL) {
             sname = sdsnewlen(astr,alen);
             if (pvariant)
-                ret = dictReplace(ac->sub.patterns,sname,&cb);
+                cbdict = ac->sub.patterns;
             else
-                ret = dictReplace(ac->sub.channels,sname,&cb);
+                cbdict = ac->sub.channels;
+
+            de = dictFind(cbdict,sname);
+
+            if (de != NULL) {
+                existcb = dictGetEntryVal(de);
+                cb.pending_subs = existcb->pending_subs + 1;
+            }
+
+            ret = dictReplace(cbdict,sname,&cb);
 
             if (ret == 0) sdsfree(sname);
         }
@@ -680,6 +704,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv
     int len;
     int status;
     len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
+    if (len < 0)
+        return REDIS_ERR;
     status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
     sdsfree(cmd);
     return status;

+ 1 - 1
ext/hiredis-vip-0.3.0/async.h → ext/hiredis-0.14.1/async.h

@@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
 typedef struct redisCallback {
     struct redisCallback *next; /* simple singly linked list */
     redisCallbackFn *fn;
+    int pending_subs;
     void *privdata;
 } redisCallback;
 
@@ -68,7 +69,6 @@ typedef struct redisAsyncContext {
 
     /* Not used by hiredis */
     void *data;
-    void (*dataHandler)(struct redisAsyncContext* ac);
 
     /* Event library data and hooks */
     struct {

+ 6 - 5
ext/hiredis-vip-0.3.0/dict.c → ext/hiredis-0.14.1/dict.c

@@ -34,6 +34,7 @@
  */
 
 #include "fmacros.h"
+#include "alloc.h"
 #include <stdlib.h>
 #include <assert.h>
 #include <limits.h>
@@ -71,7 +72,7 @@ static void _dictReset(dict *ht) {
 
 /* Create a new hash table */
 static dict *dictCreate(dictType *type, void *privDataPtr) {
-    dict *ht = malloc(sizeof(*ht));
+    dict *ht = hi_malloc(sizeof(*ht));
     _dictInit(ht,type,privDataPtr);
     return ht;
 }
@@ -142,7 +143,7 @@ static int dictAdd(dict *ht, void *key, void *val) {
         return DICT_ERR;
 
     /* Allocates the memory and stores key */
-    entry = malloc(sizeof(*entry));
+    entry = hi_malloc(sizeof(*entry));
     entry->next = ht->table[index];
     ht->table[index] = entry;
 
@@ -161,7 +162,7 @@ static int dictReplace(dict *ht, void *key, void *val) {
     dictEntry *entry, auxentry;
 
     /* Try to add the element. If the key
-     * does not exists dictAdd will suceed. */
+     * does not exists dictAdd will succeed. */
     if (dictAdd(ht, key, val) == DICT_OK)
         return 1;
     /* It already exists, get the entry */
@@ -256,7 +257,7 @@ static dictEntry *dictFind(dict *ht, const void *key) {
 }
 
 static dictIterator *dictGetIterator(dict *ht) {
-    dictIterator *iter = malloc(sizeof(*iter));
+    dictIterator *iter = hi_malloc(sizeof(*iter));
 
     iter->ht = ht;
     iter->index = -1;
@@ -293,7 +294,7 @@ static void dictReleaseIterator(dictIterator *iter) {
 
 /* Expand the hash table if needed */
 static int _dictExpandIfNeeded(dict *ht) {
-    /* If the hash table is empty expand it to the intial size,
+    /* If the hash table is empty expand it to the initial size,
      * if the table is "full" dobule its size. */
     if (ht->size == 0)
         return dictExpand(ht, DICT_HT_INITIAL_SIZE);

+ 0 - 0
ext/hiredis-vip-0.3.0/dict.h → ext/hiredis-0.14.1/dict.h


+ 0 - 0
ext/hiredis-vip-0.3.0/examples/example-ae.c → ext/hiredis-0.14.1/examples/example-ae.c


+ 0 - 0
ext/hiredis-vip-0.3.0/examples/example-glib.c → ext/hiredis-0.14.1/examples/example-glib.c


+ 58 - 0
ext/hiredis-0.14.1/examples/example-ivykis.c

@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/ivykis.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+
+    iv_init();
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    redisIvykisAttach(c);
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+
+    iv_main();
+
+    iv_deinit();
+
+    return 0;
+}

+ 0 - 0
ext/hiredis-vip-0.3.0/examples/example-libev.c → ext/hiredis-0.14.1/examples/example-libev.c


+ 0 - 0
ext/hiredis-vip-0.3.0/examples/example-libevent.c → ext/hiredis-0.14.1/examples/example-libevent.c


+ 0 - 0
ext/hiredis-vip-0.3.0/examples/example-libuv.c → ext/hiredis-0.14.1/examples/example-libuv.c


+ 66 - 0
ext/hiredis-0.14.1/examples/example-macosx.c

@@ -0,0 +1,66 @@
+//
+//  Created by Дмитрий Бахвалов on 13.07.15.
+//  Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
+//
+
+#include <stdio.h>
+
+#include <hiredis.h>
+#include <async.h>
+#include <adapters/macosx.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+    redisReply *reply = r;
+    if (reply == NULL) return;
+    printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+    /* Disconnect after receiving the reply to GET */
+    redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+    if (status != REDIS_OK) {
+        printf("Error: %s\n", c->errstr);
+        return;
+    }
+    CFRunLoopStop(CFRunLoopGetCurrent());
+    printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+    signal(SIGPIPE, SIG_IGN);
+
+    CFRunLoopRef loop = CFRunLoopGetCurrent();
+    if( !loop ) {
+        printf("Error: Cannot get current run loop\n");
+        return 1;
+    }
+
+    redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+    if (c->err) {
+        /* Let *c leak for now... */
+        printf("Error: %s\n", c->errstr);
+        return 1;
+    }
+
+    redisMacOSAttach(c, loop);
+
+    redisAsyncSetConnectCallback(c,connectCallback);
+    redisAsyncSetDisconnectCallback(c,disconnectCallback);
+
+    redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
+    redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+
+    CFRunLoopRun();
+
+    return 0;
+}
+

+ 46 - 0
ext/hiredis-0.14.1/examples/example-qt.cpp

@@ -0,0 +1,46 @@
+#include <iostream>
+using namespace std;
+
+#include <QCoreApplication>
+#include <QTimer>
+
+#include "example-qt.h"
+
+void getCallback(redisAsyncContext *, void * r, void * privdata) {
+
+    redisReply * reply = static_cast<redisReply *>(r);
+    ExampleQt * ex = static_cast<ExampleQt *>(privdata);
+    if (reply == nullptr || ex == nullptr) return;
+
+    cout << "key: " << reply->str << endl;
+
+    ex->finish();
+}
+
+void ExampleQt::run() {
+
+    m_ctx = redisAsyncConnect("localhost", 6379);
+
+    if (m_ctx->err) {
+        cerr << "Error: " << m_ctx->errstr << endl;
+        redisAsyncFree(m_ctx);
+        emit finished();
+    }
+
+    m_adapter.setContext(m_ctx);
+
+    redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value);
+    redisAsyncCommand(m_ctx, getCallback, this, "GET key");
+}
+
+int main (int argc, char **argv) {
+
+    QCoreApplication app(argc, argv);
+
+    ExampleQt example(argv[argc-1]);
+
+    QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit()));
+    QTimer::singleShot(0, &example, SLOT(run()));
+
+    return app.exec();
+}

+ 32 - 0
ext/hiredis-0.14.1/examples/example-qt.h

@@ -0,0 +1,32 @@
+#ifndef __HIREDIS_EXAMPLE_QT_H
+#define __HIREDIS_EXAMPLE_QT_H
+
+#include <adapters/qt.h>
+
+class ExampleQt : public QObject {
+
+    Q_OBJECT
+
+    public:
+        ExampleQt(const char * value, QObject * parent = 0)
+            : QObject(parent), m_value(value) {}
+
+    signals:
+        void finished();
+
+    public slots:
+        void run();
+
+    private:
+        void finish() { emit finished(); }
+
+    private:
+        const char * m_value;
+        redisAsyncContext * m_ctx;
+        RedisQtAdapter m_adapter;
+
+    friend
+    void getCallback(redisAsyncContext *, void *, void *);
+};
+
+#endif /* !__HIREDIS_EXAMPLE_QT_H */

+ 1 - 1
ext/hiredis-vip-0.3.0/examples/example.c → ext/hiredis-0.14.1/examples/example.c

@@ -57,7 +57,7 @@ int main(int argc, char **argv) {
     for (j = 0; j < 10; j++) {
         char buf[64];
 
-        snprintf(buf,64,"%d",j);
+        snprintf(buf,64,"%u",j);
         reply = redisCommand(c,"LPUSH mylist element-%s", buf);
         freeReplyObject(reply);
     }

+ 12 - 0
ext/hiredis-0.14.1/fmacros.h

@@ -0,0 +1,12 @@
+#ifndef __HIREDIS_FMACRO_H
+#define __HIREDIS_FMACRO_H
+
+#define _XOPEN_SOURCE 600
+#define _POSIX_C_SOURCE 200112L
+
+#if defined(__APPLE__) && defined(__MACH__)
+/* Enable TCP_KEEPALIVE */
+#define _DARWIN_C_SOURCE
+#endif
+
+#endif

+ 19 - 34
ext/hiredis-vip-0.3.0/hiredis.c → ext/hiredis-0.14.1/hiredis.c

@@ -84,16 +84,14 @@ void freeReplyObject(void *reply) {
     case REDIS_REPLY_ARRAY:
         if (r->element != NULL) {
             for (j = 0; j < r->elements; j++)
-                if (r->element[j] != NULL)
-                    freeReplyObject(r->element[j]);
+                freeReplyObject(r->element[j]);
             free(r->element);
         }
         break;
     case REDIS_REPLY_ERROR:
     case REDIS_REPLY_STATUS:
     case REDIS_REPLY_STRING:
-        if (r->str != NULL)
-            free(r->str);
+        free(r->str);
         break;
     }
     free(r);
@@ -432,11 +430,7 @@ cleanup:
     }
 
     sdsfree(curarg);
-
-    /* No need to check cmd since it is the last statement that can fail,
-     * but do it anyway to be as defensive as possible. */
-    if (cmd != NULL)
-        free(cmd);
+    free(cmd);
 
     return error_type;
 }
@@ -507,7 +501,7 @@ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
     cmd = sdscatfmt(cmd, "*%i\r\n", argc);
     for (j=0; j < argc; j++) {
         len = argvlen ? argvlen[j] : strlen(argv[j]);
-        cmd = sdscatfmt(cmd, "$%T\r\n", len);
+        cmd = sdscatfmt(cmd, "$%u\r\n", len);
         cmd = sdscatlen(cmd, argv[j], len);
         cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
     }
@@ -581,7 +575,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
     } else {
         /* Only REDIS_ERR_IO may lack a description! */
         assert(type == REDIS_ERR_IO);
-        __redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
+        strerror_r(errno, c->errstr, sizeof(c->errstr));
     }
 }
 
@@ -596,14 +590,8 @@ static redisContext *redisContextInit(void) {
     if (c == NULL)
         return NULL;
 
-    c->err = 0;
-    c->errstr[0] = '\0';
     c->obuf = sdsempty();
     c->reader = redisReaderCreate();
-    c->tcp.host = NULL;
-    c->tcp.source_addr = NULL;
-    c->unix_sock.path = NULL;
-    c->timeout = NULL;
 
     if (c->obuf == NULL || c->reader == NULL) {
         redisFree(c);
@@ -618,18 +606,12 @@ void redisFree(redisContext *c) {
         return;
     if (c->fd > 0)
         close(c->fd);
-    if (c->obuf != NULL)
-        sdsfree(c->obuf);
-    if (c->reader != NULL)
-        redisReaderFree(c->reader);
-    if (c->tcp.host)
-        free(c->tcp.host);
-    if (c->tcp.source_addr)
-        free(c->tcp.source_addr);
-    if (c->unix_sock.path)
-        free(c->unix_sock.path);
-    if (c->timeout)
-        free(c->timeout);
+    sdsfree(c->obuf);
+    redisReaderFree(c->reader);
+    free(c->tcp.host);
+    free(c->tcp.source_addr);
+    free(c->unix_sock.path);
+    free(c->timeout);
     free(c);
 }
 
@@ -710,6 +692,8 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
 redisContext *redisConnectBindNonBlock(const char *ip, int port,
                                        const char *source_addr) {
     redisContext *c = redisContextInit();
+    if (c == NULL)
+        return NULL;
     c->flags &= ~REDIS_BLOCK;
     redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
     return c;
@@ -718,6 +702,8 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port,
 redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
                                                 const char *source_addr) {
     redisContext *c = redisContextInit();
+    if (c == NULL)
+        return NULL;
     c->flags &= ~REDIS_BLOCK;
     c->flags |= REDIS_REUSEADDR;
     redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
@@ -822,10 +808,10 @@ int redisBufferRead(redisContext *c) {
 /* Write the output buffer to the socket.
  *
  * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
- * succesfully written to the socket. When the buffer is empty after the
+ * successfully written to the socket. When the buffer is empty after the
  * write operation, "done" is set to 1 (if given).
  *
- * Returns REDIS_ERR if an error occured trying to write and sets
+ * Returns REDIS_ERR if an error occurred trying to write and sets
  * c->errstr to hold the appropriate error string.
  */
 int redisBufferWrite(redisContext *c, int *done) {
@@ -984,7 +970,7 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s
  * context is non-blocking, the "reply" pointer will not be used and the
  * command is simply appended to the write buffer.
  *
- * Returns the reply when a reply was succesfully retrieved. Returns NULL
+ * Returns the reply when a reply was successfully retrieved. Returns NULL
  * otherwise. When NULL is returned in a blocking context, the error field
  * in the context will be set.
  */
@@ -1007,9 +993,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
 
 void *redisCommand(redisContext *c, const char *format, ...) {
     va_list ap;
-    void *reply = NULL;
     va_start(ap,format);
-    reply = redisvCommand(c,format,ap);
+    void *reply = redisvCommand(c,format,ap);
     va_end(ap);
     return reply;
 }

+ 7 - 28
ext/hiredis-vip-0.3.0/hiredis.h → ext/hiredis-0.14.1/hiredis.h

@@ -38,10 +38,12 @@
 #include <sys/time.h> /* for struct timeval */
 #include <stdint.h> /* uintXX_t, etc */
 #include "sds.h" /* for sds */
+#include "alloc.h" /* for allocation wrappers */
 
 #define HIREDIS_MAJOR 0
-#define HIREDIS_MINOR 13
+#define HIREDIS_MINOR 14
 #define HIREDIS_PATCH 1
+#define HIREDIS_SONAME 0.14
 
 /* Connection type can be blocking or non-blocking and is set in the
  * least significant bit of the flags field in redisContext. */
@@ -79,30 +81,6 @@
  * SO_REUSEADDR is being used. */
 #define REDIS_CONNECT_RETRIES  10
 
-/* strerror_r has two completely different prototypes and behaviors
- * depending on system issues, so we need to operate on the error buffer
- * differently depending on which strerror_r we're using. */
-#ifndef _GNU_SOURCE
-/* "regular" POSIX strerror_r that does the right thing. */
-#define __redis_strerror_r(errno, buf, len)                                    \
-    do {                                                                       \
-        strerror_r((errno), (buf), (len));                                     \
-    } while (0)
-#else
-/* "bad" GNU strerror_r we need to clean up after. */
-#define __redis_strerror_r(errno, buf, len)                                    \
-    do {                                                                       \
-        char *err_str = strerror_r((errno), (buf), (len));                     \
-        /* If return value _isn't_ the start of the buffer we passed in,       \
-         * then GNU strerror_r returned an internal static buffer and we       \
-         * need to copy the result into our private buffer. */                 \
-        if (err_str != (buf)) {                                                \
-            buf[(len)] = '\0';                                                 \
-            strncat((buf), err_str, ((len) - 1));                              \
-        }                                                                      \
-    } while (0)
-#endif
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -111,7 +89,7 @@ extern "C" {
 typedef struct redisReply {
     int type; /* REDIS_REPLY_* */
     long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
-    int len; /* Length of string */
+    size_t len; /* Length of string */
     char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
     size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
     struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
@@ -132,7 +110,7 @@ void redisFreeSdsCommand(sds cmd);
 
 enum redisConnectionType {
     REDIS_CONN_TCP,
-    REDIS_CONN_UNIX,
+    REDIS_CONN_UNIX
 };
 
 /* Context for a connection to Redis */
@@ -156,6 +134,7 @@ typedef struct redisContext {
     struct {
         char *path;
     } unix_sock;
+
 } redisContext;
 
 redisContext *redisConnect(const char *ip, int port);
@@ -177,7 +156,7 @@ redisContext *redisConnectFd(int fd);
  * host, ip (or path), timeout and bind address are reused,
  * flags are used unmodified from the existing context.
  *
- * Returns REDIS_OK on successfull connect or REDIS_ERR otherwise.
+ * Returns REDIS_OK on successful connect or REDIS_ERR otherwise.
  */
 int redisReconnect(redisContext *c);
 

+ 53 - 0
ext/hiredis-0.14.1/include/hiredis/alloc.h

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HIREDIS_ALLOC_H
+#define HIREDIS_ALLOC_H
+
+#include <stdlib.h> /* for size_t */
+
+#ifndef HIREDIS_OOM_HANDLER
+#define HIREDIS_OOM_HANDLER abort()
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void *hi_malloc(size_t size);
+void *hi_calloc(size_t nmemb, size_t size);
+void *hi_realloc(void *ptr, size_t size);
+char *hi_strdup(const char *str);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* HIREDIS_ALLOC_H */

+ 130 - 0
ext/hiredis-0.14.1/include/hiredis/async.h

@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_ASYNC_H
+#define __HIREDIS_ASYNC_H
+#include "hiredis.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
+struct dict; /* dictionary header is included in async.c */
+
+/* Reply callback prototype and container */
+typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
+typedef struct redisCallback {
+    struct redisCallback *next; /* simple singly linked list */
+    redisCallbackFn *fn;
+    int pending_subs;
+    void *privdata;
+} redisCallback;
+
+/* List of callbacks for either regular replies or pub/sub */
+typedef struct redisCallbackList {
+    redisCallback *head, *tail;
+} redisCallbackList;
+
+/* Connection callback prototypes */
+typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+
+/* Context for an async connection to Redis */
+typedef struct redisAsyncContext {
+    /* Hold the regular context, so it can be realloc'ed. */
+    redisContext c;
+
+    /* Setup error flags so they can be used directly. */
+    int err;
+    char *errstr;
+
+    /* Not used by hiredis */
+    void *data;
+
+    /* Event library data and hooks */
+    struct {
+        void *data;
+
+        /* Hooks that are called when the library expects to start
+         * reading/writing. These functions should be idempotent. */
+        void (*addRead)(void *privdata);
+        void (*delRead)(void *privdata);
+        void (*addWrite)(void *privdata);
+        void (*delWrite)(void *privdata);
+        void (*cleanup)(void *privdata);
+    } ev;
+
+    /* Called when either the connection is terminated due to an error or per
+     * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
+    redisDisconnectCallback *onDisconnect;
+
+    /* Called when the first write event was received. */
+    redisConnectCallback *onConnect;
+
+    /* Regular command callbacks */
+    redisCallbackList replies;
+
+    /* Subscription callbacks */
+    struct {
+        redisCallbackList invalid;
+        struct dict *channels;
+        struct dict *patterns;
+    } sub;
+} redisAsyncContext;
+
+/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnect(const char *ip, int port);
+redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
+redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
+                                                  const char *source_addr);
+redisAsyncContext *redisAsyncConnectUnix(const char *path);
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
+int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+void redisAsyncDisconnect(redisAsyncContext *ac);
+void redisAsyncFree(redisAsyncContext *ac);
+
+/* Handle read/write events */
+void redisAsyncHandleRead(redisAsyncContext *ac);
+void redisAsyncHandleWrite(redisAsyncContext *ac);
+
+/* Command functions for an async context. Write the command to the
+ * output buffer and register the provided callback. */
+int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
+int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
+int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
+int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 126 - 0
ext/hiredis-0.14.1/include/hiredis/dict.h

@@ -0,0 +1,126 @@
+/* Hash table implementation.
+ *
+ * This file implements in memory hash tables with insert/del/replace/find/
+ * get-random-element operations. Hash tables will auto resize if needed
+ * tables of power of two in size are used, collisions are handled by
+ * chaining. See the source code for more information... :)
+ *
+ * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __DICT_H
+#define __DICT_H
+
+#define DICT_OK 0
+#define DICT_ERR 1
+
+/* Unused arguments generate annoying warnings... */
+#define DICT_NOTUSED(V) ((void) V)
+
+typedef struct dictEntry {
+    void *key;
+    void *val;
+    struct dictEntry *next;
+} dictEntry;
+
+typedef struct dictType {
+    unsigned int (*hashFunction)(const void *key);
+    void *(*keyDup)(void *privdata, const void *key);
+    void *(*valDup)(void *privdata, const void *obj);
+    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
+    void (*keyDestructor)(void *privdata, void *key);
+    void (*valDestructor)(void *privdata, void *obj);
+} dictType;
+
+typedef struct dict {
+    dictEntry **table;
+    dictType *type;
+    unsigned long size;
+    unsigned long sizemask;
+    unsigned long used;
+    void *privdata;
+} dict;
+
+typedef struct dictIterator {
+    dict *ht;
+    int index;
+    dictEntry *entry, *nextEntry;
+} dictIterator;
+
+/* This is the initial size of every hash table */
+#define DICT_HT_INITIAL_SIZE     4
+
+/* ------------------------------- Macros ------------------------------------*/
+#define dictFreeEntryVal(ht, entry) \
+    if ((ht)->type->valDestructor) \
+        (ht)->type->valDestructor((ht)->privdata, (entry)->val)
+
+#define dictSetHashVal(ht, entry, _val_) do { \
+    if ((ht)->type->valDup) \
+        entry->val = (ht)->type->valDup((ht)->privdata, _val_); \
+    else \
+        entry->val = (_val_); \
+} while(0)
+
+#define dictFreeEntryKey(ht, entry) \
+    if ((ht)->type->keyDestructor) \
+        (ht)->type->keyDestructor((ht)->privdata, (entry)->key)
+
+#define dictSetHashKey(ht, entry, _key_) do { \
+    if ((ht)->type->keyDup) \
+        entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \
+    else \
+        entry->key = (_key_); \
+} while(0)
+
+#define dictCompareHashKeys(ht, key1, key2) \
+    (((ht)->type->keyCompare) ? \
+        (ht)->type->keyCompare((ht)->privdata, key1, key2) : \
+        (key1) == (key2))
+
+#define dictHashKey(ht, key) (ht)->type->hashFunction(key)
+
+#define dictGetEntryKey(he) ((he)->key)
+#define dictGetEntryVal(he) ((he)->val)
+#define dictSlots(ht) ((ht)->size)
+#define dictSize(ht) ((ht)->used)
+
+/* API */
+static unsigned int dictGenHashFunction(const unsigned char *buf, int len);
+static dict *dictCreate(dictType *type, void *privDataPtr);
+static int dictExpand(dict *ht, unsigned long size);
+static int dictAdd(dict *ht, void *key, void *val);
+static int dictReplace(dict *ht, void *key, void *val);
+static int dictDelete(dict *ht, const void *key);
+static void dictRelease(dict *ht);
+static dictEntry * dictFind(dict *ht, const void *key);
+static dictIterator *dictGetIterator(dict *ht);
+static dictEntry *dictNext(dictIterator *iter);
+static void dictReleaseIterator(dictIterator *iter);
+
+#endif /* __DICT_H */

+ 12 - 0
ext/hiredis-0.14.1/include/hiredis/fmacros.h

@@ -0,0 +1,12 @@
+#ifndef __HIREDIS_FMACRO_H
+#define __HIREDIS_FMACRO_H
+
+#define _XOPEN_SOURCE 600
+#define _POSIX_C_SOURCE 200112L
+
+#if defined(__APPLE__) && defined(__MACH__)
+/* Enable TCP_KEEPALIVE */
+#define _DARWIN_C_SOURCE
+#endif
+
+#endif

+ 200 - 0
ext/hiredis-0.14.1/include/hiredis/hiredis.h

@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ *                     Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_H
+#define __HIREDIS_H
+#include "read.h"
+#include <stdarg.h> /* for va_list */
+#include <sys/time.h> /* for struct timeval */
+#include <stdint.h> /* uintXX_t, etc */
+#include "sds.h" /* for sds */
+#include "alloc.h" /* for allocation wrappers */
+
+#define HIREDIS_MAJOR 0
+#define HIREDIS_MINOR 14
+#define HIREDIS_PATCH 1
+#define HIREDIS_SONAME 0.14
+
+/* Connection type can be blocking or non-blocking and is set in the
+ * least significant bit of the flags field in redisContext. */
+#define REDIS_BLOCK 0x1
+
+/* Connection may be disconnected before being free'd. The second bit
+ * in the flags field is set when the context is connected. */
+#define REDIS_CONNECTED 0x2
+
+/* The async API might try to disconnect cleanly and flush the output
+ * buffer and read all subsequent replies before disconnecting.
+ * This flag means no new commands can come in and the connection
+ * should be terminated once all replies have been read. */
+#define REDIS_DISCONNECTING 0x4
+
+/* Flag specific to the async API which means that the context should be clean
+ * up as soon as possible. */
+#define REDIS_FREEING 0x8
+
+/* Flag that is set when an async callback is executed. */
+#define REDIS_IN_CALLBACK 0x10
+
+/* Flag that is set when the async context has one or more subscriptions. */
+#define REDIS_SUBSCRIBED 0x20
+
+/* Flag that is set when monitor mode is active */
+#define REDIS_MONITORING 0x40
+
+/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
+#define REDIS_REUSEADDR 0x80
+
+#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
+
+/* number of times we retry to connect in the case of EADDRNOTAVAIL and
+ * SO_REUSEADDR is being used. */
+#define REDIS_CONNECT_RETRIES  10
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is the reply object returned by redisCommand() */
+typedef struct redisReply {
+    int type; /* REDIS_REPLY_* */
+    long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
+    size_t len; /* Length of string */
+    char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
+    size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
+    struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
+} redisReply;
+
+redisReader *redisReaderCreate(void);
+
+/* Function to free the reply objects hiredis returns by default. */
+void freeReplyObject(void *reply);
+
+/* Functions to format a command according to the protocol. */
+int redisvFormatCommand(char **target, const char *format, va_list ap);
+int redisFormatCommand(char **target, const char *format, ...);
+int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
+int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
+void redisFreeCommand(char *cmd);
+void redisFreeSdsCommand(sds cmd);
+
+enum redisConnectionType {
+    REDIS_CONN_TCP,
+    REDIS_CONN_UNIX
+};
+
+/* Context for a connection to Redis */
+typedef struct redisContext {
+    int err; /* Error flags, 0 when there is no error */
+    char errstr[128]; /* String representation of error when applicable */
+    int fd;
+    int flags;
+    char *obuf; /* Write buffer */
+    redisReader *reader; /* Protocol reader */
+
+    enum redisConnectionType connection_type;
+    struct timeval *timeout;
+
+    struct {
+        char *host;
+        char *source_addr;
+        int port;
+    } tcp;
+
+    struct {
+        char *path;
+    } unix_sock;
+
+} redisContext;
+
+redisContext *redisConnect(const char *ip, int port);
+redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
+redisContext *redisConnectNonBlock(const char *ip, int port);
+redisContext *redisConnectBindNonBlock(const char *ip, int port,
+                                       const char *source_addr);
+redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
+                                                const char *source_addr);
+redisContext *redisConnectUnix(const char *path);
+redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
+redisContext *redisConnectUnixNonBlock(const char *path);
+redisContext *redisConnectFd(int fd);
+
+/**
+ * Reconnect the given context using the saved information.
+ *
+ * This re-uses the exact same connect options as in the initial connection.
+ * host, ip (or path), timeout and bind address are reused,
+ * flags are used unmodified from the existing context.
+ *
+ * Returns REDIS_OK on successful connect or REDIS_ERR otherwise.
+ */
+int redisReconnect(redisContext *c);
+
+int redisSetTimeout(redisContext *c, const struct timeval tv);
+int redisEnableKeepAlive(redisContext *c);
+void redisFree(redisContext *c);
+int redisFreeKeepFd(redisContext *c);
+int redisBufferRead(redisContext *c);
+int redisBufferWrite(redisContext *c, int *done);
+
+/* In a blocking context, this function first checks if there are unconsumed
+ * replies to return and returns one if so. Otherwise, it flushes the output
+ * buffer to the socket and reads until it has a reply. In a non-blocking
+ * context, it will return unconsumed replies until there are no more. */
+int redisGetReply(redisContext *c, void **reply);
+int redisGetReplyFromReader(redisContext *c, void **reply);
+
+/* Write a formatted command to the output buffer. Use these functions in blocking mode
+ * to get a pipeline of commands. */
+int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len);
+
+/* Write a command to the output buffer. Use these functions in blocking mode
+ * to get a pipeline of commands. */
+int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
+int redisAppendCommand(redisContext *c, const char *format, ...);
+int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+/* Issue a command to Redis. In a blocking context, it is identical to calling
+ * redisAppendCommand, followed by redisGetReply. The function will return
+ * NULL if there was an error in performing the request, otherwise it will
+ * return the reply. In a non-blocking context, it is identical to calling
+ * only redisAppendCommand and will always return NULL. */
+void *redisvCommand(redisContext *c, const char *format, va_list ap);
+void *redisCommand(redisContext *c, const char *format, ...);
+void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 0 - 4
ext/hiredis-vip-0.3.0/net.h → ext/hiredis-0.14.1/include/hiredis/net.h

@@ -37,10 +37,6 @@
 
 #include "hiredis.h"
 
-#if defined(__sun)
-#define AF_LOCAL AF_UNIX
-#endif
-
 int redisCheckSocketError(redisContext *c);
 int redisContextSetTimeout(redisContext *c, const struct timeval tv);
 int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);

+ 4 - 22
ext/hiredis-vip-0.3.0/read.h → ext/hiredis-0.14.1/include/hiredis/read.h

@@ -38,7 +38,7 @@
 #define REDIS_OK 0
 
 /* When an error occurs, the err flag in a context is set to hold the type of
- * error that occured. REDIS_ERR_IO means there was an I/O error and you
+ * error that occurred. REDIS_ERR_IO means there was an I/O error and you
  * should use the "errno" variable to find out what is wrong.
  * For other values, the "errstr" field will hold a description. */
 #define REDIS_ERR_IO 1 /* Error in read or write */
@@ -46,9 +46,6 @@
 #define REDIS_ERR_PROTOCOL 4 /* Protocol error */
 #define REDIS_ERR_OOM 5 /* Out of memory */
 #define REDIS_ERR_OTHER 2 /* Everything else... */
-#if 1 //shenzheng 2015-8-10 redis cluster
-#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6
-#endif //shenzheng 2015-8-10 redis cluster
 
 #define REDIS_REPLY_STRING 1
 #define REDIS_REPLY_ARRAY 2
@@ -59,16 +56,6 @@
 
 #define REDIS_READER_MAX_BUF (1024*16)  /* Default max unused reader buffer. */
 
-#if 1 //shenzheng 2015-8-22 redis cluster
-#define REDIS_ERROR_MOVED 			"MOVED"
-#define REDIS_ERROR_ASK 			"ASK"
-#define REDIS_ERROR_TRYAGAIN 		"TRYAGAIN"
-#define REDIS_ERROR_CROSSSLOT 		"CROSSSLOT"
-#define REDIS_ERROR_CLUSTERDOWN 	"CLUSTERDOWN"
-
-#define REDIS_STATUS_OK 			"OK"
-#endif //shenzheng 2015-9-24 redis cluster
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -113,14 +100,9 @@ void redisReaderFree(redisReader *r);
 int redisReaderFeed(redisReader *r, const char *buf, size_t len);
 int redisReaderGetReply(redisReader *r, void **reply);
 
-/* Backwards compatibility, can be removed on big version bump. */
-#define redisReplyReaderCreate redisReaderCreate
-#define redisReplyReaderFree redisReaderFree
-#define redisReplyReaderFeed redisReaderFeed
-#define redisReplyReaderGetReply redisReaderGetReply
-#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
-#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
-#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
+#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
+#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
+#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
 
 #ifdef __cplusplus
 }

+ 273 - 0
ext/hiredis-0.14.1/include/hiredis/sds.h

@@ -0,0 +1,273 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SDS_H
+#define __SDS_H
+
+#define SDS_MAX_PREALLOC (1024*1024)
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+typedef char *sds;
+
+/* Note: sdshdr5 is never used, we just access the flags byte directly.
+ * However is here to document the layout of type 5 SDS strings. */
+struct __attribute__ ((__packed__)) sdshdr5 {
+    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr8 {
+    uint8_t len; /* used */
+    uint8_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr16 {
+    uint16_t len; /* used */
+    uint16_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr32 {
+    uint32_t len; /* used */
+    uint32_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr64 {
+    uint64_t len; /* used */
+    uint64_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+
+#define SDS_TYPE_5  0
+#define SDS_TYPE_8  1
+#define SDS_TYPE_16 2
+#define SDS_TYPE_32 3
+#define SDS_TYPE_64 4
+#define SDS_TYPE_MASK 7
+#define SDS_TYPE_BITS 3
+#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
+#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
+#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
+
+static inline size_t sdslen(const sds s) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            return SDS_TYPE_5_LEN(flags);
+        case SDS_TYPE_8:
+            return SDS_HDR(8,s)->len;
+        case SDS_TYPE_16:
+            return SDS_HDR(16,s)->len;
+        case SDS_TYPE_32:
+            return SDS_HDR(32,s)->len;
+        case SDS_TYPE_64:
+            return SDS_HDR(64,s)->len;
+    }
+    return 0;
+}
+
+static inline size_t sdsavail(const sds s) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5: {
+            return 0;
+        }
+        case SDS_TYPE_8: {
+            SDS_HDR_VAR(8,s);
+            return sh->alloc - sh->len;
+        }
+        case SDS_TYPE_16: {
+            SDS_HDR_VAR(16,s);
+            return sh->alloc - sh->len;
+        }
+        case SDS_TYPE_32: {
+            SDS_HDR_VAR(32,s);
+            return sh->alloc - sh->len;
+        }
+        case SDS_TYPE_64: {
+            SDS_HDR_VAR(64,s);
+            return sh->alloc - sh->len;
+        }
+    }
+    return 0;
+}
+
+static inline void sdssetlen(sds s, size_t newlen) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            {
+                unsigned char *fp = ((unsigned char*)s)-1;
+                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+            }
+            break;
+        case SDS_TYPE_8:
+            SDS_HDR(8,s)->len = newlen;
+            break;
+        case SDS_TYPE_16:
+            SDS_HDR(16,s)->len = newlen;
+            break;
+        case SDS_TYPE_32:
+            SDS_HDR(32,s)->len = newlen;
+            break;
+        case SDS_TYPE_64:
+            SDS_HDR(64,s)->len = newlen;
+            break;
+    }
+}
+
+static inline void sdsinclen(sds s, size_t inc) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            {
+                unsigned char *fp = ((unsigned char*)s)-1;
+                unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
+                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+            }
+            break;
+        case SDS_TYPE_8:
+            SDS_HDR(8,s)->len += inc;
+            break;
+        case SDS_TYPE_16:
+            SDS_HDR(16,s)->len += inc;
+            break;
+        case SDS_TYPE_32:
+            SDS_HDR(32,s)->len += inc;
+            break;
+        case SDS_TYPE_64:
+            SDS_HDR(64,s)->len += inc;
+            break;
+    }
+}
+
+/* sdsalloc() = sdsavail() + sdslen() */
+static inline size_t sdsalloc(const sds s) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            return SDS_TYPE_5_LEN(flags);
+        case SDS_TYPE_8:
+            return SDS_HDR(8,s)->alloc;
+        case SDS_TYPE_16:
+            return SDS_HDR(16,s)->alloc;
+        case SDS_TYPE_32:
+            return SDS_HDR(32,s)->alloc;
+        case SDS_TYPE_64:
+            return SDS_HDR(64,s)->alloc;
+    }
+    return 0;
+}
+
+static inline void sdssetalloc(sds s, size_t newlen) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            /* Nothing to do, this type has no total allocation info. */
+            break;
+        case SDS_TYPE_8:
+            SDS_HDR(8,s)->alloc = newlen;
+            break;
+        case SDS_TYPE_16:
+            SDS_HDR(16,s)->alloc = newlen;
+            break;
+        case SDS_TYPE_32:
+            SDS_HDR(32,s)->alloc = newlen;
+            break;
+        case SDS_TYPE_64:
+            SDS_HDR(64,s)->alloc = newlen;
+            break;
+    }
+}
+
+sds sdsnewlen(const void *init, size_t initlen);
+sds sdsnew(const char *init);
+sds sdsempty(void);
+sds sdsdup(const sds s);
+void sdsfree(sds s);
+sds sdsgrowzero(sds s, size_t len);
+sds sdscatlen(sds s, const void *t, size_t len);
+sds sdscat(sds s, const char *t);
+sds sdscatsds(sds s, const sds t);
+sds sdscpylen(sds s, const char *t, size_t len);
+sds sdscpy(sds s, const char *t);
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap);
+#ifdef __GNUC__
+sds sdscatprintf(sds s, const char *fmt, ...)
+    __attribute__((format(printf, 2, 3)));
+#else
+sds sdscatprintf(sds s, const char *fmt, ...);
+#endif
+
+sds sdscatfmt(sds s, char const *fmt, ...);
+sds sdstrim(sds s, const char *cset);
+void sdsrange(sds s, int start, int end);
+void sdsupdatelen(sds s);
+void sdsclear(sds s);
+int sdscmp(const sds s1, const sds s2);
+sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
+void sdsfreesplitres(sds *tokens, int count);
+void sdstolower(sds s);
+void sdstoupper(sds s);
+sds sdsfromlonglong(long long value);
+sds sdscatrepr(sds s, const char *p, size_t len);
+sds *sdssplitargs(const char *line, int *argc);
+sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
+sds sdsjoin(char **argv, int argc, char *sep);
+sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
+
+/* Low level functions exposed to the user API */
+sds sdsMakeRoomFor(sds s, size_t addlen);
+void sdsIncrLen(sds s, int incr);
+sds sdsRemoveFreeSpace(sds s);
+size_t sdsAllocSize(sds s);
+void *sdsAllocPtr(sds s);
+
+/* Export the allocator used by SDS to the program using SDS.
+ * Sometimes the program SDS is linked to, may use a different set of
+ * allocators, but may want to allocate or free things that SDS will
+ * respectively free or allocate. */
+void *sds_malloc(size_t size);
+void *sds_realloc(void *ptr, size_t size);
+void sds_free(void *ptr);
+
+#ifdef REDIS_TEST
+int sdsTest(int argc, char *argv[]);
+#endif
+
+#endif

+ 42 - 0
ext/hiredis-0.14.1/include/hiredis/sdsalloc.h

@@ -0,0 +1,42 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* SDS allocator selection.
+ *
+ * This file is used in order to change the SDS allocator at compile time.
+ * Just define the following defines to what you want to use. Also add
+ * the include of your alternate allocator if needed (not needed in order
+ * to use the default libc allocator). */
+
+#define s_malloc malloc
+#define s_realloc realloc
+#define s_free free

+ 0 - 0
ext/hiredis-vip-0.3.0/win32.h → ext/hiredis-0.14.1/include/hiredis/win32.h


二进制
ext/hiredis-0.14.1/lib/centos8/libhiredis.a


二进制
ext/hiredis-0.14.1/lib/macos/libhiredis.a


+ 47 - 28
ext/hiredis-vip-0.3.0/net.c → ext/hiredis-0.14.1/net.c

@@ -65,12 +65,13 @@ static void redisContextCloseFd(redisContext *c) {
 }
 
 static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
+    int errorno = errno;  /* snprintf() may change errno */
     char buf[128] = { 0 };
     size_t len = 0;
 
     if (prefix != NULL)
         len = snprintf(buf,sizeof(buf),"%s: ",prefix);
-    __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
+    strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
     __redisSetError(c,type,buf);
 }
 
@@ -135,14 +136,13 @@ int redisKeepAlive(redisContext *c, int interval) {
 
     val = interval;
 
-#ifdef _OSX
+#if defined(__APPLE__) && defined(__MACH__)
     if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
         __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
         return REDIS_ERR;
     }
 #else
 #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
-    val = interval;
     if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
         __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
         return REDIS_ERR;
@@ -178,19 +178,15 @@ static int redisSetTcpNoDelay(redisContext *c) {
 
 #define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
 
-static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) {
-    struct pollfd   wfd[1];
-    long msec;
-
-    msec          = -1;
-    wfd[0].fd     = c->fd;
-    wfd[0].events = POLLOUT;
+static int redisContextTimeoutMsec(redisContext *c, long *result)
+{
+    const struct timeval *timeout = c->timeout;
+    long msec = -1;
 
     /* Only use timeout when not NULL. */
     if (timeout != NULL) {
         if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
-            __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
-            redisContextCloseFd(c);
+            *result = msec;
             return REDIS_ERR;
         }
 
@@ -201,6 +197,16 @@ static int redisContextWaitReady(redisContext *c, const struct timeval *timeout)
         }
     }
 
+    *result = msec;
+    return REDIS_OK;
+}
+
+static int redisContextWaitReady(redisContext *c, long msec) {
+    struct pollfd   wfd[1];
+
+    wfd[0].fd     = c->fd;
+    wfd[0].events = POLLOUT;
+
     if (errno == EINPROGRESS) {
         int res;
 
@@ -265,7 +271,9 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
     int blocking = (c->flags & REDIS_BLOCK);
     int reuseaddr = (c->flags & REDIS_REUSEADDR);
     int reuses = 0;
+    long timeout_msec = -1;
 
+    servinfo = NULL;
     c->connection_type = REDIS_CONN_TCP;
     c->tcp.port = port;
 
@@ -277,31 +285,34 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
      * This is a bit ugly, but atleast it works and doesn't leak memory.
      **/
     if (c->tcp.host != addr) {
-        if (c->tcp.host)
-            free(c->tcp.host);
+        free(c->tcp.host);
 
-        c->tcp.host = strdup(addr);
+        c->tcp.host = hi_strdup(addr);
     }
 
     if (timeout) {
         if (c->timeout != timeout) {
             if (c->timeout == NULL)
-                c->timeout = malloc(sizeof(struct timeval));
+                c->timeout = hi_malloc(sizeof(struct timeval));
 
             memcpy(c->timeout, timeout, sizeof(struct timeval));
         }
     } else {
-        if (c->timeout)
-            free(c->timeout);
+        free(c->timeout);
         c->timeout = NULL;
     }
 
+    if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
+        __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
+        goto error;
+    }
+
     if (source_addr == NULL) {
         free(c->tcp.source_addr);
         c->tcp.source_addr = NULL;
     } else if (c->tcp.source_addr != source_addr) {
         free(c->tcp.source_addr);
-        c->tcp.source_addr = strdup(source_addr);
+        c->tcp.source_addr = hi_strdup(source_addr);
     }
 
     snprintf(_port, 6, "%d", port);
@@ -343,6 +354,7 @@ addrretry:
                 n = 1;
                 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
                                sizeof(n)) < 0) {
+                    freeaddrinfo(bservinfo);
                     goto error;
                 }
             }
@@ -371,10 +383,11 @@ addrretry:
                 if (++reuses >= REDIS_CONNECT_RETRIES) {
                     goto error;
                 } else {
+                    redisContextCloseFd(c);
                     goto addrretry;
                 }
             } else {
-                if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
+                if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
                     goto error;
             }
         }
@@ -397,7 +410,10 @@ addrretry:
 error:
     rv = REDIS_ERR;
 end:
-    freeaddrinfo(servinfo);
+    if(servinfo) {
+        freeaddrinfo(servinfo);
+    }
+
     return rv;  // Need to return REDIS_OK if alright
 }
 
@@ -415,36 +431,39 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
 int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
     int blocking = (c->flags & REDIS_BLOCK);
     struct sockaddr_un sa;
+    long timeout_msec = -1;
 
-    if (redisCreateSocket(c,AF_LOCAL) < 0)
+    if (redisCreateSocket(c,AF_UNIX) < 0)
         return REDIS_ERR;
     if (redisSetBlocking(c,0) != REDIS_OK)
         return REDIS_ERR;
 
     c->connection_type = REDIS_CONN_UNIX;
     if (c->unix_sock.path != path)
-        c->unix_sock.path = strdup(path);
+        c->unix_sock.path = hi_strdup(path);
 
     if (timeout) {
         if (c->timeout != timeout) {
             if (c->timeout == NULL)
-                c->timeout = malloc(sizeof(struct timeval));
+                c->timeout = hi_malloc(sizeof(struct timeval));
 
             memcpy(c->timeout, timeout, sizeof(struct timeval));
         }
     } else {
-        if (c->timeout)
-            free(c->timeout);
+        free(c->timeout);
         c->timeout = NULL;
     }
 
-    sa.sun_family = AF_LOCAL;
+    if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
+        return REDIS_ERR;
+
+    sa.sun_family = AF_UNIX;
     strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
     if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
         if (errno == EINPROGRESS && !blocking) {
             /* This is ok. */
         } else {
-            if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
+            if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
                 return REDIS_ERR;
         }
     }

+ 49 - 0
ext/hiredis-0.14.1/net.h

@@ -0,0 +1,49 @@
+/* Extracted from anet.c to work properly with Hiredis error reporting.
+ *
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
+ *                     Jan-Erik Rediger <janerik at fnordig dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __NET_H
+#define __NET_H
+
+#include "hiredis.h"
+
+int redisCheckSocketError(redisContext *c);
+int redisContextSetTimeout(redisContext *c, const struct timeval tv);
+int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
+int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
+                               const struct timeval *timeout,
+                               const char *source_addr);
+int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
+int redisKeepAlive(redisContext *c, int interval);
+
+#endif

+ 117 - 44
ext/hiredis-vip-0.3.0/read.c → ext/hiredis-0.14.1/read.c

@@ -39,6 +39,7 @@
 #include <assert.h>
 #include <errno.h>
 #include <ctype.h>
+#include <limits.h>
 
 #include "read.h"
 #include "sds.h"
@@ -52,11 +53,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
     }
 
     /* Clear input buffer on errors. */
-    if (r->buf != NULL) {
-        sdsfree(r->buf);
-        r->buf = NULL;
-        r->pos = r->len = 0;
-    }
+    sdsfree(r->buf);
+    r->buf = NULL;
+    r->pos = r->len = 0;
 
     /* Reset task stack. */
     r->ridx = -1;
@@ -127,7 +126,7 @@ static char *seekNewline(char *s, size_t len) {
      * might not have a trailing NULL character. */
     while (pos < _len) {
         while(pos < _len && s[pos] != '\r') pos++;
-        if (s[pos] != '\r') {
+        if (pos==_len) {
             /* Not found. */
             return NULL;
         } else {
@@ -143,33 +142,79 @@ static char *seekNewline(char *s, size_t len) {
     return NULL;
 }
 
-/* Read a long long value starting at *s, under the assumption that it will be
- * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
-static long long readLongLong(char *s) {
-    long long v = 0;
-    int dec, mult = 1;
-    char c;
-
-    if (*s == '-') {
-        mult = -1;
-        s++;
-    } else if (*s == '+') {
-        mult = 1;
-        s++;
+/* Convert a string into a long long. Returns REDIS_OK if the string could be
+ * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
+ * will be set to the parsed value when appropriate.
+ *
+ * Note that this function demands that the string strictly represents
+ * a long long: no spaces or other characters before or after the string
+ * representing the number are accepted, nor zeroes at the start if not
+ * for the string "0" representing the zero number.
+ *
+ * Because of its strictness, it is safe to use this function to check if
+ * you can convert a string into a long long, and obtain back the string
+ * from the number without any loss in the string representation. */
+static int string2ll(const char *s, size_t slen, long long *value) {
+    const char *p = s;
+    size_t plen = 0;
+    int negative = 0;
+    unsigned long long v;
+
+    if (plen == slen)
+        return REDIS_ERR;
+
+    /* Special case: first and only digit is 0. */
+    if (slen == 1 && p[0] == '0') {
+        if (value != NULL) *value = 0;
+        return REDIS_OK;
     }
 
-    while ((c = *(s++)) != '\r') {
-        dec = c - '0';
-        if (dec >= 0 && dec < 10) {
-            v *= 10;
-            v += dec;
-        } else {
-            /* Should not happen... */
-            return -1;
-        }
+    if (p[0] == '-') {
+        negative = 1;
+        p++; plen++;
+
+        /* Abort on only a negative sign. */
+        if (plen == slen)
+            return REDIS_ERR;
+    }
+
+    /* First digit should be 1-9, otherwise the string should just be 0. */
+    if (p[0] >= '1' && p[0] <= '9') {
+        v = p[0]-'0';
+        p++; plen++;
+    } else if (p[0] == '0' && slen == 1) {
+        *value = 0;
+        return REDIS_OK;
+    } else {
+        return REDIS_ERR;
     }
 
-    return mult*v;
+    while (plen < slen && p[0] >= '0' && p[0] <= '9') {
+        if (v > (ULLONG_MAX / 10)) /* Overflow. */
+            return REDIS_ERR;
+        v *= 10;
+
+        if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
+            return REDIS_ERR;
+        v += p[0]-'0';
+
+        p++; plen++;
+    }
+
+    /* Return if not all bytes were used. */
+    if (plen < slen)
+        return REDIS_ERR;
+
+    if (negative) {
+        if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
+            return REDIS_ERR;
+        if (value != NULL) *value = -v;
+    } else {
+        if (v > LLONG_MAX) /* Overflow. */
+            return REDIS_ERR;
+        if (value != NULL) *value = v;
+    }
+    return REDIS_OK;
 }
 
 static char *readLine(redisReader *r, int *_len) {
@@ -220,10 +265,17 @@ static int processLineItem(redisReader *r) {
 
     if ((p = readLine(r,&len)) != NULL) {
         if (cur->type == REDIS_REPLY_INTEGER) {
-            if (r->fn && r->fn->createInteger)
-                obj = r->fn->createInteger(cur,readLongLong(p));
-            else
+            if (r->fn && r->fn->createInteger) {
+                long long v;
+                if (string2ll(p, len, &v) == REDIS_ERR) {
+                    __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+                            "Bad integer value");
+                    return REDIS_ERR;
+                }
+                obj = r->fn->createInteger(cur,v);
+            } else {
                 obj = (void*)REDIS_REPLY_INTEGER;
+            }
         } else {
             /* Type will be error or status. */
             if (r->fn && r->fn->createString)
@@ -250,7 +302,7 @@ static int processBulkItem(redisReader *r) {
     redisReadTask *cur = &(r->rstack[r->ridx]);
     void *obj = NULL;
     char *p, *s;
-    long len;
+    long long len;
     unsigned long bytelen;
     int success = 0;
 
@@ -259,9 +311,20 @@ static int processBulkItem(redisReader *r) {
     if (s != NULL) {
         p = r->buf+r->pos;
         bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
-        len = readLongLong(p);
 
-        if (len < 0) {
+        if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
+            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+                    "Bad bulk string length");
+            return REDIS_ERR;
+        }
+
+        if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
+            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+                    "Bulk string length out of range");
+            return REDIS_ERR;
+        }
+
+        if (len == -1) {
             /* The nil object can always be created. */
             if (r->fn && r->fn->createNil)
                 obj = r->fn->createNil(cur);
@@ -303,8 +366,8 @@ static int processMultiBulkItem(redisReader *r) {
     redisReadTask *cur = &(r->rstack[r->ridx]);
     void *obj;
     char *p;
-    long elements;
-    int root = 0;
+    long long elements;
+    int root = 0, len;
 
     /* Set error for nested multi bulks with depth > 7 */
     if (r->ridx == 8) {
@@ -313,10 +376,21 @@ static int processMultiBulkItem(redisReader *r) {
         return REDIS_ERR;
     }
 
-    if ((p = readLine(r,NULL)) != NULL) {
-        elements = readLongLong(p);
+    if ((p = readLine(r,&len)) != NULL) {
+        if (string2ll(p, len, &elements) == REDIS_ERR) {
+            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+                    "Bad multi-bulk length");
+            return REDIS_ERR;
+        }
+
         root = (r->ridx == 0);
 
+        if (elements < -1 || elements > INT_MAX) {
+            __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+                    "Multi-bulk length out of range");
+            return REDIS_ERR;
+        }
+
         if (elements == -1) {
             if (r->fn && r->fn->createNil)
                 obj = r->fn->createNil(cur);
@@ -416,12 +490,10 @@ static int processItem(redisReader *r) {
 redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
     redisReader *r;
 
-    r = calloc(sizeof(redisReader),1);
+    r = calloc(1,sizeof(redisReader));
     if (r == NULL)
         return NULL;
 
-    r->err = 0;
-    r->errstr[0] = '\0';
     r->fn = fn;
     r->buf = sdsempty();
     r->maxbuf = REDIS_READER_MAX_BUF;
@@ -435,10 +507,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
 }
 
 void redisReaderFree(redisReader *r) {
+    if (r == NULL)
+        return;
     if (r->reply != NULL && r->fn && r->fn->freeObject)
         r->fn->freeObject(r->reply);
-    if (r->buf != NULL)
-        sdsfree(r->buf);
+    sdsfree(r->buf);
     free(r);
 }
 

+ 111 - 0
ext/hiredis-0.14.1/read.h

@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef __HIREDIS_READ_H
+#define __HIREDIS_READ_H
+#include <stdio.h> /* for size_t */
+
+#define REDIS_ERR -1
+#define REDIS_OK 0
+
+/* When an error occurs, the err flag in a context is set to hold the type of
+ * error that occurred. REDIS_ERR_IO means there was an I/O error and you
+ * should use the "errno" variable to find out what is wrong.
+ * For other values, the "errstr" field will hold a description. */
+#define REDIS_ERR_IO 1 /* Error in read or write */
+#define REDIS_ERR_EOF 3 /* End of file */
+#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
+#define REDIS_ERR_OOM 5 /* Out of memory */
+#define REDIS_ERR_OTHER 2 /* Everything else... */
+
+#define REDIS_REPLY_STRING 1
+#define REDIS_REPLY_ARRAY 2
+#define REDIS_REPLY_INTEGER 3
+#define REDIS_REPLY_NIL 4
+#define REDIS_REPLY_STATUS 5
+#define REDIS_REPLY_ERROR 6
+
+#define REDIS_READER_MAX_BUF (1024*16)  /* Default max unused reader buffer. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct redisReadTask {
+    int type;
+    int elements; /* number of elements in multibulk container */
+    int idx; /* index in parent (array) object */
+    void *obj; /* holds user-generated value for a read task */
+    struct redisReadTask *parent; /* parent task */
+    void *privdata; /* user-settable arbitrary field */
+} redisReadTask;
+
+typedef struct redisReplyObjectFunctions {
+    void *(*createString)(const redisReadTask*, char*, size_t);
+    void *(*createArray)(const redisReadTask*, int);
+    void *(*createInteger)(const redisReadTask*, long long);
+    void *(*createNil)(const redisReadTask*);
+    void (*freeObject)(void*);
+} redisReplyObjectFunctions;
+
+typedef struct redisReader {
+    int err; /* Error flags, 0 when there is no error */
+    char errstr[128]; /* String representation of error when applicable */
+
+    char *buf; /* Read buffer */
+    size_t pos; /* Buffer cursor */
+    size_t len; /* Buffer length */
+    size_t maxbuf; /* Max length of unused buffer */
+
+    redisReadTask rstack[9];
+    int ridx; /* Index of current read task */
+    void *reply; /* Temporary reply pointer */
+
+    redisReplyObjectFunctions *fn;
+    void *privdata;
+} redisReader;
+
+/* Public API for the protocol parser. */
+redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
+void redisReaderFree(redisReader *r);
+int redisReaderFeed(redisReader *r, const char *buf, size_t len);
+int redisReaderGetReply(redisReader *r, void **reply);
+
+#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
+#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
+#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 342 - 165
ext/hiredis-vip-0.3.0/sds.c → ext/hiredis-0.14.1/sds.c

@@ -1,6 +1,8 @@
-/* SDS (Simple Dynamic Strings), A C dynamic strings library.
+/* SDSLib 2.0 -- A C dynamic strings library
  *
- * Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,8 +35,36 @@
 #include <string.h>
 #include <ctype.h>
 #include <assert.h>
-
 #include "sds.h"
+#include "sdsalloc.h"
+
+static inline int sdsHdrSize(char type) {
+    switch(type&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            return sizeof(struct sdshdr5);
+        case SDS_TYPE_8:
+            return sizeof(struct sdshdr8);
+        case SDS_TYPE_16:
+            return sizeof(struct sdshdr16);
+        case SDS_TYPE_32:
+            return sizeof(struct sdshdr32);
+        case SDS_TYPE_64:
+            return sizeof(struct sdshdr64);
+    }
+    return 0;
+}
+
+static inline char sdsReqType(size_t string_size) {
+    if (string_size < 32)
+        return SDS_TYPE_5;
+    if (string_size < 0xff)
+        return SDS_TYPE_8;
+    if (string_size < 0xffff)
+        return SDS_TYPE_16;
+    if (string_size < 0xffffffff)
+        return SDS_TYPE_32;
+    return SDS_TYPE_64;
+}
 
 /* Create a new sds string with the content specified by the 'init' pointer
  * and 'initlen'.
@@ -43,26 +73,65 @@
  * The string is always null-termined (all the sds strings are, always) so
  * even if you create an sds string with:
  *
- * mystring = sdsnewlen("abc",3");
+ * mystring = sdsnewlen("abc",3);
  *
  * You can print the string with printf() as there is an implicit \0 at the
  * end of the string. However the string is binary safe and can contain
  * \0 characters in the middle, as the length is stored in the sds header. */
 sds sdsnewlen(const void *init, size_t initlen) {
-    struct sdshdr *sh;
-
-    if (init) {
-        sh = malloc(sizeof *sh+initlen+1);
-    } else {
-        sh = calloc(sizeof *sh+initlen+1,1);
-    }
+    void *sh;
+    sds s;
+    char type = sdsReqType(initlen);
+    /* Empty strings are usually created in order to append. Use type 8
+     * since type 5 is not good at this. */
+    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
+    int hdrlen = sdsHdrSize(type);
+    unsigned char *fp; /* flags pointer. */
+
+    sh = s_malloc(hdrlen+initlen+1);
     if (sh == NULL) return NULL;
-    sh->len = initlen;
-    sh->free = 0;
+    if (!init)
+        memset(sh, 0, hdrlen+initlen+1);
+    s = (char*)sh+hdrlen;
+    fp = ((unsigned char*)s)-1;
+    switch(type) {
+        case SDS_TYPE_5: {
+            *fp = type | (initlen << SDS_TYPE_BITS);
+            break;
+        }
+        case SDS_TYPE_8: {
+            SDS_HDR_VAR(8,s);
+            sh->len = initlen;
+            sh->alloc = initlen;
+            *fp = type;
+            break;
+        }
+        case SDS_TYPE_16: {
+            SDS_HDR_VAR(16,s);
+            sh->len = initlen;
+            sh->alloc = initlen;
+            *fp = type;
+            break;
+        }
+        case SDS_TYPE_32: {
+            SDS_HDR_VAR(32,s);
+            sh->len = initlen;
+            sh->alloc = initlen;
+            *fp = type;
+            break;
+        }
+        case SDS_TYPE_64: {
+            SDS_HDR_VAR(64,s);
+            sh->len = initlen;
+            sh->alloc = initlen;
+            *fp = type;
+            break;
+        }
+    }
     if (initlen && init)
-        memcpy(sh->buf, init, initlen);
-    sh->buf[initlen] = '\0';
-    return (char*)sh->buf;
+        memcpy(s, init, initlen);
+    s[initlen] = '\0';
+    return s;
 }
 
 /* Create an empty (zero length) sds string. Even in this case the string
@@ -71,7 +140,7 @@ sds sdsempty(void) {
     return sdsnewlen("",0);
 }
 
-/* Create a new sds string starting from a null termined C string. */
+/* Create a new sds string starting from a null terminated C string. */
 sds sdsnew(const char *init) {
     size_t initlen = (init == NULL) ? 0 : strlen(init);
     return sdsnewlen(init, initlen);
@@ -85,7 +154,7 @@ sds sdsdup(const sds s) {
 /* Free an sds string. No operation is performed if 's' is NULL. */
 void sdsfree(sds s) {
     if (s == NULL) return;
-    free(s-sizeof(struct sdshdr));
+    s_free((char*)s-sdsHdrSize(s[-1]));
 }
 
 /* Set the sds string length to the length as obtained with strlen(), so
@@ -103,21 +172,17 @@ void sdsfree(sds s) {
  * the output will be "6" as the string was modified but the logical length
  * remains 6 bytes. */
 void sdsupdatelen(sds s) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
     int reallen = strlen(s);
-    sh->free += (sh->len-reallen);
-    sh->len = reallen;
+    sdssetlen(s, reallen);
 }
 
-/* Modify an sds string on-place to make it empty (zero length).
+/* Modify an sds string in-place to make it empty (zero length).
  * However all the existing buffer is not discarded but set as free space
  * so that next append operations will not require allocations up to the
  * number of bytes previously available. */
 void sdsclear(sds s) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
-    sh->free += sh->len;
-    sh->len = 0;
-    sh->buf[0] = '\0';
+    sdssetlen(s, 0);
+    s[0] = '\0';
 }
 
 /* Enlarge the free space at the end of the sds string so that the caller
@@ -127,23 +192,48 @@ void sdsclear(sds s) {
  * Note: this does not change the *length* of the sds string as returned
  * by sdslen(), but only the free buffer space we have. */
 sds sdsMakeRoomFor(sds s, size_t addlen) {
-    struct sdshdr *sh, *newsh;
-    size_t free = sdsavail(s);
+    void *sh, *newsh;
+    size_t avail = sdsavail(s);
     size_t len, newlen;
+    char type, oldtype = s[-1] & SDS_TYPE_MASK;
+    int hdrlen;
+
+    /* Return ASAP if there is enough space left. */
+    if (avail >= addlen) return s;
 
-    if (free >= addlen) return s;
     len = sdslen(s);
-    sh = (void*) (s-sizeof *sh);
+    sh = (char*)s-sdsHdrSize(oldtype);
     newlen = (len+addlen);
     if (newlen < SDS_MAX_PREALLOC)
         newlen *= 2;
     else
         newlen += SDS_MAX_PREALLOC;
-    newsh = realloc(sh, sizeof *newsh+newlen+1);
-    if (newsh == NULL) return NULL;
 
-    newsh->free = newlen - len;
-    return newsh->buf;
+    type = sdsReqType(newlen);
+
+    /* Don't use type 5: the user is appending to the string and type 5 is
+     * not able to remember empty space, so sdsMakeRoomFor() must be called
+     * at every appending operation. */
+    if (type == SDS_TYPE_5) type = SDS_TYPE_8;
+
+    hdrlen = sdsHdrSize(type);
+    if (oldtype==type) {
+        newsh = s_realloc(sh, hdrlen+newlen+1);
+        if (newsh == NULL) return NULL;
+        s = (char*)newsh+hdrlen;
+    } else {
+        /* Since the header size changes, need to move the string forward,
+         * and can't use realloc */
+        newsh = s_malloc(hdrlen+newlen+1);
+        if (newsh == NULL) return NULL;
+        memcpy((char*)newsh+hdrlen, s, len+1);
+        s_free(sh);
+        s = (char*)newsh+hdrlen;
+        s[-1] = type;
+        sdssetlen(s, len);
+    }
+    sdssetalloc(s, newlen);
+    return s;
 }
 
 /* Reallocate the sds string so that it has no free space at the end. The
@@ -153,12 +243,29 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
  * After the call, the passed sds string is no longer valid and all the
  * references must be substituted with the new pointer returned by the call. */
 sds sdsRemoveFreeSpace(sds s) {
-    struct sdshdr *sh;
-
-    sh = (void*) (s-sizeof *sh);
-    sh = realloc(sh, sizeof *sh+sh->len+1);
-    sh->free = 0;
-    return sh->buf;
+    void *sh, *newsh;
+    char type, oldtype = s[-1] & SDS_TYPE_MASK;
+    int hdrlen;
+    size_t len = sdslen(s);
+    sh = (char*)s-sdsHdrSize(oldtype);
+
+    type = sdsReqType(len);
+    hdrlen = sdsHdrSize(type);
+    if (oldtype==type) {
+        newsh = s_realloc(sh, hdrlen+len+1);
+        if (newsh == NULL) return NULL;
+        s = (char*)newsh+hdrlen;
+    } else {
+        newsh = s_malloc(hdrlen+len+1);
+        if (newsh == NULL) return NULL;
+        memcpy((char*)newsh+hdrlen, s, len+1);
+        s_free(sh);
+        s = (char*)newsh+hdrlen;
+        s[-1] = type;
+        sdssetlen(s, len);
+    }
+    sdssetalloc(s, len);
+    return s;
 }
 
 /* Return the total size of the allocation of the specifed sds string,
@@ -169,9 +276,14 @@ sds sdsRemoveFreeSpace(sds s) {
  * 4) The implicit null term.
  */
 size_t sdsAllocSize(sds s) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
+    size_t alloc = sdsalloc(s);
+    return sdsHdrSize(s[-1])+alloc+1;
+}
 
-    return sizeof(*sh)+sh->len+sh->free+1;
+/* Return the pointer of the actual SDS allocation (normally SDS strings
+ * are referenced by the start of the string buffer). */
+void *sdsAllocPtr(sds s) {
+    return (void*) (s-sdsHdrSize(s[-1]));
 }
 
 /* Increment the sds length and decrements the left free space at the
@@ -198,13 +310,44 @@ size_t sdsAllocSize(sds s) {
  * sdsIncrLen(s, nread);
  */
 void sdsIncrLen(sds s, int incr) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
-
-    assert(sh->free >= incr);
-    sh->len += incr;
-    sh->free -= incr;
-    assert(sh->free >= 0);
-    s[sh->len] = '\0';
+    unsigned char flags = s[-1];
+    size_t len;
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5: {
+            unsigned char *fp = ((unsigned char*)s)-1;
+            unsigned char oldlen = SDS_TYPE_5_LEN(flags);
+            assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr)));
+            *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS);
+            len = oldlen+incr;
+            break;
+        }
+        case SDS_TYPE_8: {
+            SDS_HDR_VAR(8,s);
+            assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+            len = (sh->len += incr);
+            break;
+        }
+        case SDS_TYPE_16: {
+            SDS_HDR_VAR(16,s);
+            assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+            len = (sh->len += incr);
+            break;
+        }
+        case SDS_TYPE_32: {
+            SDS_HDR_VAR(32,s);
+            assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr)));
+            len = (sh->len += incr);
+            break;
+        }
+        case SDS_TYPE_64: {
+            SDS_HDR_VAR(64,s);
+            assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr)));
+            len = (sh->len += incr);
+            break;
+        }
+        default: len = 0; /* Just to avoid compilation warnings. */
+    }
+    s[len] = '\0';
 }
 
 /* Grow the sds to have the specified length. Bytes that were not part of
@@ -213,19 +356,15 @@ void sdsIncrLen(sds s, int incr) {
  * if the specified length is smaller than the current length, no operation
  * is performed. */
 sds sdsgrowzero(sds s, size_t len) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
-    size_t totlen, curlen = sh->len;
+    size_t curlen = sdslen(s);
 
     if (len <= curlen) return s;
     s = sdsMakeRoomFor(s,len-curlen);
     if (s == NULL) return NULL;
 
     /* Make sure added region doesn't contain garbage */
-    sh = (void*)(s-sizeof *sh);
     memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
-    totlen = sh->len+sh->free;
-    sh->len = len;
-    sh->free = totlen-sh->len;
+    sdssetlen(s, len);
     return s;
 }
 
@@ -235,15 +374,12 @@ sds sdsgrowzero(sds s, size_t len) {
  * After the call, the passed sds string is no longer valid and all the
  * references must be substituted with the new pointer returned by the call. */
 sds sdscatlen(sds s, const void *t, size_t len) {
-    struct sdshdr *sh;
     size_t curlen = sdslen(s);
 
     s = sdsMakeRoomFor(s,len);
     if (s == NULL) return NULL;
-    sh = (void*) (s-sizeof *sh);
     memcpy(s+curlen, t, len);
-    sh->len = curlen+len;
-    sh->free = sh->free-len;
+    sdssetlen(s, curlen+len);
     s[curlen+len] = '\0';
     return s;
 }
@@ -267,19 +403,13 @@ sds sdscatsds(sds s, const sds t) {
 /* Destructively modify the sds string 's' to hold the specified binary
  * safe string pointed by 't' of length 'len' bytes. */
 sds sdscpylen(sds s, const char *t, size_t len) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
-    size_t totlen = sh->free+sh->len;
-
-    if (totlen < len) {
-        s = sdsMakeRoomFor(s,len-sh->len);
+    if (sdsalloc(s) < len) {
+        s = sdsMakeRoomFor(s,len-sdslen(s));
         if (s == NULL) return NULL;
-        sh = (void*) (s-sizeof *sh);
-        totlen = sh->free+sh->len;
     }
     memcpy(s, t, len);
     s[len] = '\0';
-    sh->len = len;
-    sh->free = totlen-len;
+    sdssetlen(s, len);
     return s;
 }
 
@@ -293,7 +423,7 @@ sds sdscpy(sds s, const char *t) {
  * conversion. 's' must point to a string with room for at least
  * SDS_LLSTR_SIZE bytes.
  *
- * The function returns the lenght of the null-terminated string
+ * The function returns the length of the null-terminated string
  * representation stored at 's'. */
 #define SDS_LLSTR_SIZE 21
 int sdsll2str(char *s, long long value) {
@@ -356,27 +486,52 @@ int sdsull2str(char *s, unsigned long long v) {
     return l;
 }
 
-/* Like sdscatpritf() but gets va_list instead of being variadic. */
+/* Create an sds string from a long long value. It is much faster than:
+ *
+ * sdscatprintf(sdsempty(),"%lld\n", value);
+ */
+sds sdsfromlonglong(long long value) {
+    char buf[SDS_LLSTR_SIZE];
+    int len = sdsll2str(buf,value);
+
+    return sdsnewlen(buf,len);
+}
+
+/* Like sdscatprintf() but gets va_list instead of being variadic. */
 sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
     va_list cpy;
-    char *buf, *t;
-    size_t buflen = 16;
+    char staticbuf[1024], *buf = staticbuf, *t;
+    size_t buflen = strlen(fmt)*2;
 
-    while(1) {
-        buf = malloc(buflen);
+    /* We try to start using a static buffer for speed.
+     * If not possible we revert to heap allocation. */
+    if (buflen > sizeof(staticbuf)) {
+        buf = s_malloc(buflen);
         if (buf == NULL) return NULL;
+    } else {
+        buflen = sizeof(staticbuf);
+    }
+
+    /* Try with buffers two times bigger every time we fail to
+     * fit the string in the current buffer size. */
+    while(1) {
         buf[buflen-2] = '\0';
         va_copy(cpy,ap);
         vsnprintf(buf, buflen, fmt, cpy);
+        va_end(cpy);
         if (buf[buflen-2] != '\0') {
-            free(buf);
+            if (buf != staticbuf) s_free(buf);
             buflen *= 2;
+            buf = s_malloc(buflen);
+            if (buf == NULL) return NULL;
             continue;
         }
         break;
     }
+
+    /* Finally concat the obtained string to the SDS string and return it. */
     t = sdscat(s, buf);
-    free(buf);
+    if (buf != staticbuf) s_free(buf);
     return t;
 }
 
@@ -389,7 +544,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
  * Example:
  *
  * s = sdsnew("Sum is: ");
- * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b);
+ * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
  *
  * Often you need to create a string from scratch with the printf-alike
  * format. When this is the need, just use sdsempty() as the target string:
@@ -419,29 +574,24 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
  * %I - 64 bit signed integer (long long, int64_t)
  * %u - unsigned int
  * %U - 64 bit unsigned integer (unsigned long long, uint64_t)
- * %T - A size_t variable.
  * %% - Verbatim "%" character.
  */
 sds sdscatfmt(sds s, char const *fmt, ...) {
-    struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
-    size_t initlen = sdslen(s);
     const char *f = fmt;
     int i;
     va_list ap;
 
     va_start(ap,fmt);
-    f = fmt;    /* Next format specifier byte to process. */
-    i = initlen; /* Position of the next byte to write to dest str. */
+    i = sdslen(s); /* Position of the next byte to write to dest str. */
     while(*f) {
         char next, *str;
-        int l;
+        size_t l;
         long long num;
         unsigned long long unum;
 
         /* Make sure there is always space for at least 1 char. */
-        if (sh->free == 0) {
+        if (sdsavail(s)==0) {
             s = sdsMakeRoomFor(s,1);
-            sh = (void*) (s-(sizeof(struct sdshdr)));
         }
 
         switch(*f) {
@@ -453,13 +603,11 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
             case 'S':
                 str = va_arg(ap,char*);
                 l = (next == 's') ? strlen(str) : sdslen(str);
-                if (sh->free < l) {
+                if (sdsavail(s) < l) {
                     s = sdsMakeRoomFor(s,l);
-                    sh = (void*) (s-(sizeof(struct sdshdr)));
                 }
                 memcpy(s+i,str,l);
-                sh->len += l;
-                sh->free -= l;
+                sdsinclen(s,l);
                 i += l;
                 break;
             case 'i':
@@ -471,49 +619,40 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
                 {
                     char buf[SDS_LLSTR_SIZE];
                     l = sdsll2str(buf,num);
-                    if (sh->free < l) {
+                    if (sdsavail(s) < l) {
                         s = sdsMakeRoomFor(s,l);
-                        sh = (void*) (s-(sizeof(struct sdshdr)));
                     }
                     memcpy(s+i,buf,l);
-                    sh->len += l;
-                    sh->free -= l;
+                    sdsinclen(s,l);
                     i += l;
                 }
                 break;
             case 'u':
             case 'U':
-            case 'T':
                 if (next == 'u')
                     unum = va_arg(ap,unsigned int);
-                else if(next == 'U')
-                    unum = va_arg(ap,unsigned long long);
                 else
-                    unum = (unsigned long long)va_arg(ap,size_t);
+                    unum = va_arg(ap,unsigned long long);
                 {
                     char buf[SDS_LLSTR_SIZE];
                     l = sdsull2str(buf,unum);
-                    if (sh->free < l) {
+                    if (sdsavail(s) < l) {
                         s = sdsMakeRoomFor(s,l);
-                        sh = (void*) (s-(sizeof(struct sdshdr)));
                     }
                     memcpy(s+i,buf,l);
-                    sh->len += l;
-                    sh->free -= l;
+                    sdsinclen(s,l);
                     i += l;
                 }
                 break;
             default: /* Handle %% and generally %<unknown>. */
                 s[i++] = next;
-                sh->len += 1;
-                sh->free -= 1;
+                sdsinclen(s,1);
                 break;
             }
             break;
         default:
             s[i++] = *f;
-            sh->len += 1;
-            sh->free -= 1;
+            sdsinclen(s,1);
             break;
         }
         f++;
@@ -525,7 +664,6 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
     return s;
 }
 
-
 /* Remove the part of the string from left and from right composed just of
  * contiguous characters found in 'cset', that is a null terminted C string.
  *
@@ -535,25 +673,24 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
  * Example:
  *
  * s = sdsnew("AA...AA.a.aa.aHelloWorld     :::");
- * s = sdstrim(s,"A. :");
+ * s = sdstrim(s,"Aa. :");
  * printf("%s\n", s);
  *
  * Output will be just "Hello World".
  */
-void sdstrim(sds s, const char *cset) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
+sds sdstrim(sds s, const char *cset) {
     char *start, *end, *sp, *ep;
     size_t len;
 
     sp = start = s;
     ep = end = s+sdslen(s)-1;
     while(sp <= end && strchr(cset, *sp)) sp++;
-    while(ep > start && strchr(cset, *ep)) ep--;
+    while(ep > sp && strchr(cset, *ep)) ep--;
     len = (sp > ep) ? 0 : ((ep-sp)+1);
-    if (sh->buf != sp) memmove(sh->buf, sp, len);
-    sh->buf[len] = '\0';
-    sh->free = sh->free+(sh->len-len);
-    sh->len = len;
+    if (s != sp) memmove(s, sp, len);
+    s[len] = '\0';
+    sdssetlen(s,len);
+    return s;
 }
 
 /* Turn the string into a smaller (or equal) string containing only the
@@ -573,7 +710,6 @@ void sdstrim(sds s, const char *cset) {
  * sdsrange(s,1,-1); => "ello World"
  */
 void sdsrange(sds s, int start, int end) {
-    struct sdshdr *sh = (void*) (s-sizeof *sh);
     size_t newlen, len = sdslen(s);
 
     if (len == 0) return;
@@ -596,10 +732,9 @@ void sdsrange(sds s, int start, int end) {
     } else {
         start = 0;
     }
-    if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
-    sh->buf[newlen] = 0;
-    sh->free = sh->free+(sh->len-newlen);
-    sh->len = newlen;
+    if (start && newlen) memmove(s, s+start, newlen);
+    s[newlen] = 0;
+    sdssetlen(s,newlen);
 }
 
 /* Apply tolower() to every character of the sds string 's'. */
@@ -620,8 +755,8 @@ void sdstoupper(sds s) {
  *
  * Return value:
  *
- *     1 if s1 > s2.
- *    -1 if s1 < s2.
+ *     positive if s1 > s2.
+ *     negative if s1 < s2.
  *     0 if s1 and s2 are exactly the same binary string.
  *
  * If two strings share exactly the same prefix, but one of the two has
@@ -661,7 +796,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
 
     if (seplen < 1 || len < 0) return NULL;
 
-    tokens = malloc(sizeof(sds)*slots);
+    tokens = s_malloc(sizeof(sds)*slots);
     if (tokens == NULL) return NULL;
 
     if (len == 0) {
@@ -674,7 +809,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
             sds *newtokens;
 
             slots *= 2;
-            newtokens = realloc(tokens,sizeof(sds)*slots);
+            newtokens = s_realloc(tokens,sizeof(sds)*slots);
             if (newtokens == NULL) goto cleanup;
             tokens = newtokens;
         }
@@ -698,7 +833,7 @@ cleanup:
     {
         int i;
         for (i = 0; i < elements; i++) sdsfree(tokens[i]);
-        free(tokens);
+        s_free(tokens);
         *count = 0;
         return NULL;
     }
@@ -709,26 +844,7 @@ void sdsfreesplitres(sds *tokens, int count) {
     if (!tokens) return;
     while(count--)
         sdsfree(tokens[count]);
-    free(tokens);
-}
-
-/* Create an sds string from a long long value. It is much faster than:
- *
- * sdscatprintf(sdsempty(),"%lld\n", value);
- */
-sds sdsfromlonglong(long long value) {
-    char buf[32], *p;
-    unsigned long long v;
-
-    v = (value < 0) ? -value : value;
-    p = buf+31; /* point to the last character */
-    do {
-        *p-- = '0'+(v%10);
-        v /= 10;
-    } while(v);
-    if (value < 0) *p-- = '-';
-    p++;
-    return sdsnewlen(p,32-(p-buf));
+    s_free(tokens);
 }
 
 /* Append to the sds string "s" an escaped string representation where
@@ -902,13 +1018,13 @@ sds *sdssplitargs(const char *line, int *argc) {
                 if (*p) p++;
             }
             /* add the token to the vector */
-            vector = realloc(vector,((*argc)+1)*sizeof(char*));
+            vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
             vector[*argc] = current;
             (*argc)++;
             current = NULL;
         } else {
             /* Even on empty input string return something not NULL. */
-            if (vector == NULL) vector = malloc(sizeof(void*));
+            if (vector == NULL) vector = s_malloc(sizeof(void*));
             return vector;
         }
     }
@@ -916,7 +1032,7 @@ sds *sdssplitargs(const char *line, int *argc) {
 err:
     while((*argc)--)
         sdsfree(vector[*argc]);
-    free(vector);
+    s_free(vector);
     if (current) sdsfree(current);
     *argc = 0;
     return NULL;
@@ -947,13 +1063,13 @@ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
 
 /* Join an array of C strings using the specified separator (also a C string).
  * Returns the result as an sds string. */
-sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) {
+sds sdsjoin(char **argv, int argc, char *sep) {
     sds join = sdsempty();
     int j;
 
     for (j = 0; j < argc; j++) {
         join = sdscat(join, argv[j]);
-        if (j != argc-1) join = sdscatlen(join,sep,seplen);
+        if (j != argc-1) join = sdscat(join,sep);
     }
     return join;
 }
@@ -970,13 +1086,23 @@ sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) {
     return join;
 }
 
-#ifdef SDS_TEST_MAIN
+/* Wrappers to the allocators used by SDS. Note that SDS will actually
+ * just use the macros defined into sdsalloc.h in order to avoid to pay
+ * the overhead of function calls. Here we define these wrappers only for
+ * the programs SDS is linked to, if they want to touch the SDS internals
+ * even if they use a different allocator. */
+void *sds_malloc(size_t size) { return s_malloc(size); }
+void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); }
+void sds_free(void *ptr) { s_free(ptr); }
+
+#if defined(SDS_TEST_MAIN)
 #include <stdio.h>
 #include "testhelp.h"
+#include "limits.h"
 
-int main(void) {
+#define UNUSED(x) (void)(x)
+int sdsTest(void) {
     {
-        struct sdshdr *sh;
         sds x = sdsnew("foo"), y;
 
         test_cond("Create a string and obtain the length",
@@ -1003,7 +1129,35 @@ int main(void) {
         sdsfree(x);
         x = sdscatprintf(sdsempty(),"%d",123);
         test_cond("sdscatprintf() seems working in the base case",
-            sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
+            sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
+
+        sdsfree(x);
+        x = sdsnew("--");
+        x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);
+        test_cond("sdscatfmt() seems working in the base case",
+            sdslen(x) == 60 &&
+            memcmp(x,"--Hello Hi! World -9223372036854775808,"
+                     "9223372036854775807--",60) == 0)
+        printf("[%s]\n",x);
+
+        sdsfree(x);
+        x = sdsnew("--");
+        x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
+        test_cond("sdscatfmt() seems working with unsigned numbers",
+            sdslen(x) == 35 &&
+            memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
+
+        sdsfree(x);
+        x = sdsnew(" x ");
+        sdstrim(x," x");
+        test_cond("sdstrim() works when all chars match",
+            sdslen(x) == 0)
+
+        sdsfree(x);
+        x = sdsnew(" x ");
+        sdstrim(x," ");
+        test_cond("sdstrim() works when a single char remains",
+            sdslen(x) == 1 && x[0] == 'x')
 
         sdsfree(x);
         x = sdsnew("xxciaoyyy");
@@ -1072,24 +1226,47 @@ int main(void) {
             memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
 
         {
-            int oldfree;
+            unsigned int oldfree;
+            char *p;
+            int step = 10, j, i;
 
             sdsfree(x);
+            sdsfree(y);
             x = sdsnew("0");
-            sh = (void*) (x-(sizeof(struct sdshdr)));
-            test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
-            x = sdsMakeRoomFor(x,1);
-            sh = (void*) (x-(sizeof(struct sdshdr)));
-            test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0);
-            oldfree = sh->free;
-            x[1] = '1';
-            sdsIncrLen(x,1);
-            test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
-            test_cond("sdsIncrLen() -- len", sh->len == 2);
-            test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
+            test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0);
+
+            /* Run the test a few times in order to hit the first two
+             * SDS header types. */
+            for (i = 0; i < 10; i++) {
+                int oldlen = sdslen(x);
+                x = sdsMakeRoomFor(x,step);
+                int type = x[-1]&SDS_TYPE_MASK;
+
+                test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen);
+                if (type != SDS_TYPE_5) {
+                    test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step);
+                    oldfree = sdsavail(x);
+                }
+                p = x+oldlen;
+                for (j = 0; j < step; j++) {
+                    p[j] = 'A'+j;
+                }
+                sdsIncrLen(x,step);
+            }
+            test_cond("sdsMakeRoomFor() content",
+                memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0);
+            test_cond("sdsMakeRoomFor() final length",sdslen(x)==101);
+
+            sdsfree(x);
         }
     }
     test_report()
     return 0;
 }
 #endif
+
+#ifdef SDS_TEST_MAIN
+int main(void) {
+    return sdsTest();
+}
+#endif

+ 273 - 0
ext/hiredis-0.14.1/sds.h

@@ -0,0 +1,273 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SDS_H
+#define __SDS_H
+
+#define SDS_MAX_PREALLOC (1024*1024)
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+typedef char *sds;
+
+/* Note: sdshdr5 is never used, we just access the flags byte directly.
+ * However is here to document the layout of type 5 SDS strings. */
+struct __attribute__ ((__packed__)) sdshdr5 {
+    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr8 {
+    uint8_t len; /* used */
+    uint8_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr16 {
+    uint16_t len; /* used */
+    uint16_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr32 {
+    uint32_t len; /* used */
+    uint32_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr64 {
+    uint64_t len; /* used */
+    uint64_t alloc; /* excluding the header and null terminator */
+    unsigned char flags; /* 3 lsb of type, 5 unused bits */
+    char buf[];
+};
+
+#define SDS_TYPE_5  0
+#define SDS_TYPE_8  1
+#define SDS_TYPE_16 2
+#define SDS_TYPE_32 3
+#define SDS_TYPE_64 4
+#define SDS_TYPE_MASK 7
+#define SDS_TYPE_BITS 3
+#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
+#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
+#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
+
+static inline size_t sdslen(const sds s) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            return SDS_TYPE_5_LEN(flags);
+        case SDS_TYPE_8:
+            return SDS_HDR(8,s)->len;
+        case SDS_TYPE_16:
+            return SDS_HDR(16,s)->len;
+        case SDS_TYPE_32:
+            return SDS_HDR(32,s)->len;
+        case SDS_TYPE_64:
+            return SDS_HDR(64,s)->len;
+    }
+    return 0;
+}
+
+static inline size_t sdsavail(const sds s) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5: {
+            return 0;
+        }
+        case SDS_TYPE_8: {
+            SDS_HDR_VAR(8,s);
+            return sh->alloc - sh->len;
+        }
+        case SDS_TYPE_16: {
+            SDS_HDR_VAR(16,s);
+            return sh->alloc - sh->len;
+        }
+        case SDS_TYPE_32: {
+            SDS_HDR_VAR(32,s);
+            return sh->alloc - sh->len;
+        }
+        case SDS_TYPE_64: {
+            SDS_HDR_VAR(64,s);
+            return sh->alloc - sh->len;
+        }
+    }
+    return 0;
+}
+
+static inline void sdssetlen(sds s, size_t newlen) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            {
+                unsigned char *fp = ((unsigned char*)s)-1;
+                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+            }
+            break;
+        case SDS_TYPE_8:
+            SDS_HDR(8,s)->len = newlen;
+            break;
+        case SDS_TYPE_16:
+            SDS_HDR(16,s)->len = newlen;
+            break;
+        case SDS_TYPE_32:
+            SDS_HDR(32,s)->len = newlen;
+            break;
+        case SDS_TYPE_64:
+            SDS_HDR(64,s)->len = newlen;
+            break;
+    }
+}
+
+static inline void sdsinclen(sds s, size_t inc) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            {
+                unsigned char *fp = ((unsigned char*)s)-1;
+                unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
+                *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+            }
+            break;
+        case SDS_TYPE_8:
+            SDS_HDR(8,s)->len += inc;
+            break;
+        case SDS_TYPE_16:
+            SDS_HDR(16,s)->len += inc;
+            break;
+        case SDS_TYPE_32:
+            SDS_HDR(32,s)->len += inc;
+            break;
+        case SDS_TYPE_64:
+            SDS_HDR(64,s)->len += inc;
+            break;
+    }
+}
+
+/* sdsalloc() = sdsavail() + sdslen() */
+static inline size_t sdsalloc(const sds s) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            return SDS_TYPE_5_LEN(flags);
+        case SDS_TYPE_8:
+            return SDS_HDR(8,s)->alloc;
+        case SDS_TYPE_16:
+            return SDS_HDR(16,s)->alloc;
+        case SDS_TYPE_32:
+            return SDS_HDR(32,s)->alloc;
+        case SDS_TYPE_64:
+            return SDS_HDR(64,s)->alloc;
+    }
+    return 0;
+}
+
+static inline void sdssetalloc(sds s, size_t newlen) {
+    unsigned char flags = s[-1];
+    switch(flags&SDS_TYPE_MASK) {
+        case SDS_TYPE_5:
+            /* Nothing to do, this type has no total allocation info. */
+            break;
+        case SDS_TYPE_8:
+            SDS_HDR(8,s)->alloc = newlen;
+            break;
+        case SDS_TYPE_16:
+            SDS_HDR(16,s)->alloc = newlen;
+            break;
+        case SDS_TYPE_32:
+            SDS_HDR(32,s)->alloc = newlen;
+            break;
+        case SDS_TYPE_64:
+            SDS_HDR(64,s)->alloc = newlen;
+            break;
+    }
+}
+
+sds sdsnewlen(const void *init, size_t initlen);
+sds sdsnew(const char *init);
+sds sdsempty(void);
+sds sdsdup(const sds s);
+void sdsfree(sds s);
+sds sdsgrowzero(sds s, size_t len);
+sds sdscatlen(sds s, const void *t, size_t len);
+sds sdscat(sds s, const char *t);
+sds sdscatsds(sds s, const sds t);
+sds sdscpylen(sds s, const char *t, size_t len);
+sds sdscpy(sds s, const char *t);
+
+sds sdscatvprintf(sds s, const char *fmt, va_list ap);
+#ifdef __GNUC__
+sds sdscatprintf(sds s, const char *fmt, ...)
+    __attribute__((format(printf, 2, 3)));
+#else
+sds sdscatprintf(sds s, const char *fmt, ...);
+#endif
+
+sds sdscatfmt(sds s, char const *fmt, ...);
+sds sdstrim(sds s, const char *cset);
+void sdsrange(sds s, int start, int end);
+void sdsupdatelen(sds s);
+void sdsclear(sds s);
+int sdscmp(const sds s1, const sds s2);
+sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
+void sdsfreesplitres(sds *tokens, int count);
+void sdstolower(sds s);
+void sdstoupper(sds s);
+sds sdsfromlonglong(long long value);
+sds sdscatrepr(sds s, const char *p, size_t len);
+sds *sdssplitargs(const char *line, int *argc);
+sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
+sds sdsjoin(char **argv, int argc, char *sep);
+sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
+
+/* Low level functions exposed to the user API */
+sds sdsMakeRoomFor(sds s, size_t addlen);
+void sdsIncrLen(sds s, int incr);
+sds sdsRemoveFreeSpace(sds s);
+size_t sdsAllocSize(sds s);
+void *sdsAllocPtr(sds s);
+
+/* Export the allocator used by SDS to the program using SDS.
+ * Sometimes the program SDS is linked to, may use a different set of
+ * allocators, but may want to allocate or free things that SDS will
+ * respectively free or allocate. */
+void *sds_malloc(size_t size);
+void *sds_realloc(void *ptr, size_t size);
+void sds_free(void *ptr);
+
+#ifdef REDIS_TEST
+int sdsTest(int argc, char *argv[]);
+#endif
+
+#endif

+ 42 - 0
ext/hiredis-0.14.1/sdsalloc.h

@@ -0,0 +1,42 @@
+/* SDSLib 2.0 -- A C dynamic strings library
+ *
+ * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2015, Oran Agra
+ * Copyright (c) 2015, Redis Labs, Inc
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Redis nor the names of its contributors may be used
+ *     to endorse or promote products derived from this software without
+ *     specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* SDS allocator selection.
+ *
+ * This file is used in order to change the SDS allocator at compile time.
+ * Just define the following defines to what you want to use. Also add
+ * the include of your alternate allocator if needed (not needed in order
+ * to use the default libc allocator). */
+
+#define s_malloc malloc
+#define s_realloc realloc
+#define s_free free

+ 131 - 14
ext/hiredis-vip-0.3.0/test.c → ext/hiredis-0.14.1/test.c

@@ -30,7 +30,7 @@ struct config {
 
     struct {
         const char *path;
-    } unix;
+    } unix_sock;
 };
 
 /* The following lines make up our testing "framework" :) */
@@ -97,10 +97,10 @@ static redisContext *connect(struct config config) {
     if (config.type == CONN_TCP) {
         c = redisConnect(config.tcp.host, config.tcp.port);
     } else if (config.type == CONN_UNIX) {
-        c = redisConnectUnix(config.unix.path);
+        c = redisConnectUnix(config.unix_sock.path);
     } else if (config.type == CONN_FD) {
         /* Create a dummy connection just to get an fd to inherit */
-        redisContext *dummy_ctx = redisConnectUnix(config.unix.path);
+        redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
         if (dummy_ctx) {
             int fd = disconnect(dummy_ctx, 1);
             printf("Connecting to inherited fd %d\n", fd);
@@ -224,6 +224,22 @@ static void test_format_commands(void) {
     test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
         len == 4+4+(3+2)+4+(7+2)+4+(3+2));
     free(cmd);
+
+    sds sds_cmd;
+
+    sds_cmd = sdsempty();
+    test("Format command into sds by passing argc/argv without lengths: ");
+    len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
+    test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(3+2)+4+(3+2));
+    sdsfree(sds_cmd);
+
+    sds_cmd = sdsempty();
+    test("Format command into sds by passing argc/argv with lengths: ");
+    len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
+    test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
+        len == 4+4+(3+2)+4+(7+2)+4+(3+2));
+    sdsfree(sds_cmd);
 }
 
 static void test_append_formatted_commands(struct config config) {
@@ -286,6 +302,82 @@ static void test_reply_reader(void) {
               strncasecmp(reader->errstr,"No support for",14) == 0);
     redisReaderFree(reader);
 
+    test("Correctly parses LLONG_MAX: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, ":9223372036854775807\r\n",22);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK &&
+            ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+            ((redisReply*)reply)->integer == LLONG_MAX);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+
+    test("Set error when > LLONG_MAX: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, ":9223372036854775808\r\n",22);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Bad integer value") == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+
+    test("Correctly parses LLONG_MIN: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_OK &&
+            ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+            ((redisReply*)reply)->integer == LLONG_MIN);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+
+    test("Set error when < LLONG_MIN: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Bad integer value") == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+
+    test("Set error when array < -1: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+
+    test("Set error when bulk < -1: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR &&
+              strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+
+    test("Set error when array > INT_MAX: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR &&
+            strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+
+#if LLONG_MAX > SIZE_MAX
+    test("Set error when bulk > SIZE_MAX: ");
+    reader = redisReaderCreate();
+    redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
+    ret = redisReaderGetReply(reader,&reply);
+    test_cond(ret == REDIS_ERR &&
+            strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+    freeReplyObject(reply);
+    redisReaderFree(reader);
+#endif
+
     test("Works with NULL functions for reply: ");
     reader = redisReaderCreate();
     reader->fn = NULL;
@@ -328,12 +420,12 @@ static void test_reply_reader(void) {
 }
 
 static void test_free_null(void) {
-    void *redisContext = NULL;
+    void *redisCtx = NULL;
     void *reply = NULL;
 
     test("Don't fail when redisFree is passed a NULL value: ");
-    redisFree(redisContext);
-    test_cond(redisContext == NULL);
+    redisFree(redisCtx);
+    test_cond(redisCtx == NULL);
 
     test("Don't fail when freeReplyObject is passed a NULL value: ");
     freeReplyObject(reply);
@@ -351,6 +443,7 @@ static void test_blocking_connection_errors(void) {
          strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
          strcmp(c->errstr,"No address associated with hostname") == 0 ||
          strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
+         strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
          strcmp(c->errstr,"no address associated with name") == 0));
     redisFree(c);
 
@@ -360,7 +453,7 @@ static void test_blocking_connection_errors(void) {
         strcmp(c->errstr,"Connection refused") == 0);
     redisFree(c);
 
-    test("Returns error when the unix socket path doesn't accept connections: ");
+    test("Returns error when the unix_sock socket path doesn't accept connections: ");
     c = redisConnectUnix((char*)"/tmp/idontexist.sock");
     test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
     redisFree(c);
@@ -481,7 +574,7 @@ static void test_blocking_connection_timeouts(struct config config) {
 
     test("Reconnect properly uses owned parameters: ");
     config.tcp.host = "foo";
-    config.unix.path = "foo";
+    config.unix_sock.path = "foo";
     redisReconnect(c);
     reply = redisCommand(c, "PING");
     test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
@@ -551,7 +644,7 @@ static void test_invalid_timeout_errors(struct config config) {
 
     c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
 
-    test_cond(c->err == REDIS_ERR_IO);
+    test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
     redisFree(c);
 
     test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
@@ -561,7 +654,7 @@ static void test_invalid_timeout_errors(struct config config) {
 
     c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
 
-    test_cond(c->err == REDIS_ERR_IO);
+    test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
     redisFree(c);
 }
 
@@ -599,6 +692,17 @@ static void test_throughput(struct config config) {
     free(replies);
     printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
 
+    replies = malloc(sizeof(redisReply*)*num);
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
+
     num = 10000;
     replies = malloc(sizeof(redisReply*)*num);
     for (i = 0; i < num; i++)
@@ -627,6 +731,19 @@ static void test_throughput(struct config config) {
     free(replies);
     printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
 
+    replies = malloc(sizeof(redisReply*)*num);
+    for (i = 0; i < num; i++)
+        redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
+    t1 = usec();
+    for (i = 0; i < num; i++) {
+        assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+        assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+    }
+    t2 = usec();
+    for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+    free(replies);
+    printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
     disconnect(c, 0);
 }
 
@@ -735,7 +852,7 @@ int main(int argc, char **argv) {
             .host = "127.0.0.1",
             .port = 6379
         },
-        .unix = {
+        .unix_sock = {
             .path = "/tmp/redis.sock"
         }
     };
@@ -756,7 +873,7 @@ int main(int argc, char **argv) {
             cfg.tcp.port = atoi(argv[0]);
         } else if (argc >= 2 && !strcmp(argv[0],"-s")) {
             argv++; argc--;
-            cfg.unix.path = argv[0];
+            cfg.unix_sock.path = argv[0];
         } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
             throughput = 0;
         } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
@@ -782,7 +899,7 @@ int main(int argc, char **argv) {
     test_append_formatted_commands(cfg);
     if (throughput) test_throughput(cfg);
 
-    printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path);
+    printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
     cfg.type = CONN_UNIX;
     test_blocking_connection(cfg);
     test_blocking_connection_timeouts(cfg);
@@ -790,7 +907,7 @@ int main(int argc, char **argv) {
     if (throughput) test_throughput(cfg);
 
     if (test_inherit_fd) {
-        printf("\nTesting against inherited fd (%s):\n", cfg.unix.path);
+        printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
         cfg.type = CONN_FD;
         test_blocking_connection(cfg);
     }

+ 42 - 0
ext/hiredis-0.14.1/win32.h

@@ -0,0 +1,42 @@
+#ifndef _WIN32_HELPER_INCLUDE
+#define _WIN32_HELPER_INCLUDE
+#ifdef _MSC_VER
+
+#ifndef inline
+#define inline __inline
+#endif
+
+#ifndef va_copy
+#define va_copy(d,s) ((d) = (s))
+#endif
+
+#ifndef snprintf
+#define snprintf c99_snprintf
+
+__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
+{
+    int count = -1;
+
+    if (size != 0)
+        count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
+    if (count == -1)
+        count = _vscprintf(format, ap);
+
+    return count;
+}
+
+__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
+{
+    int count;
+    va_list ap;
+
+    va_start(ap, format);
+    count = c99_vsnprintf(str, size, format, ap);
+    va_end(ap);
+
+    return count;
+}
+#endif
+
+#endif
+#endif

+ 0 - 16
ext/hiredis-vip-0.3.0/.travis.yml

@@ -1,16 +0,0 @@
-language: c
-compiler:
-  - gcc
-  - clang
-
-env:
-    - CFLAGS="-Werror"
-    - PRE="valgrind --track-origins=yes --leak-check=full"
-    - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
-    - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
-
-install:
-    - sudo apt-get update -qq
-    - sudo apt-get install libc6-dbg libc6-dev libc6-i686:i386 libc6-dev-i386 libc6-dbg:i386 valgrind -y
-
-script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example

+ 0 - 16
ext/hiredis-vip-0.3.0/CHANGELOG.md

@@ -1,16 +0,0 @@
-### 0.3.0 - Dec 07, 2016
-
-* Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011)
-* Add flags HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS. (deep011)
-* Support redisClusterCommandArgv related api. (deep011)
-* Fix some serious bugs. (deep011)
-
-### 0.2.1 - Nov 24, 2015
-
-This release support redis cluster api.
-
-* Add hiredis 0.3.1. (deep011)
-* Support cluster synchronous API. (deep011)
-* Support multi-key command(mget/mset/del) for redis cluster. (deep011)
-* Support cluster pipelining. (deep011)
-* Support cluster asynchronous API. (deep011)

+ 0 - 255
ext/hiredis-vip-0.3.0/README.md

@@ -1,255 +0,0 @@
-
-# HIREDIS-VIP
-
-Hiredis-vip is a C client library for the [Redis](http://redis.io/) database.
-
-Hiredis-vip supported redis cluster.
-
-Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) .
-
-## CLUSTER SUPPORT
-
-### FEATURES:
-
-* **`SUPPORT REDIS CLUSTER`**:
-    * Connect to redis cluster and run commands.
-
-* **`SUPPORT MULTI-KEY COMMAND`**:
-    * Support `MSET`, `MGET` and `DEL`.
-	
-* **`SUPPORT PIPELING`**:
-    * Support redis pipeline and can contain multi-key command like above.
-	
-* **`SUPPORT Asynchronous API`**:
-    * User can run commands with asynchronous mode.
-
-### CLUSTER API:
-
-```c
-redisClusterContext *redisClusterConnect(const char *addrs, int flags);
-redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags);
-redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags);
-void redisClusterFree(redisClusterContext *cc);
-void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count);
-void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len);
-void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap);
-void *redisClusterCommand(redisClusterContext *cc, const char *format, ...);
-void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
-redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags);
-int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len);
-int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap);
-int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...);
-int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
-int redisClusterGetReply(redisClusterContext *cc, void **reply);
-void redisClusterReset(redisClusterContext *cc);
-
-redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags);
-int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn);
-int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn);
-int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len);
-int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap);
-int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...);
-int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
-
-void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc);
-void redisClusterAsyncFree(redisClusterAsyncContext *acc);
-```
-
-## Quick usage
-
-If you want used but not read the follow, please reference the examples:
-https://github.com/vipshop/hiredis-vip/wiki
-
-## Cluster synchronous API
-
-To consume the synchronous API, there are only a few function calls that need to be introduced:
-
-```c
-redisClusterContext *redisClusterConnect(const char *addrs, int flags);
-void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count);
-void *redisClusterCommand(redisClusterContext *cc, const char *format, ...);
-void redisClusterFree(redisClusterContext *cc);
-```
-
-### Cluster connecting
-
-The function `redisClusterConnect` is used to create a so-called `redisClusterContext`. The
-context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext`
-struct has an integer `err` field that is non-zero when the connection is in
-an error state. The field `errstr` will contain a string with a description of
-the error.
-After trying to connect to Redis using `redisClusterContext` you should
-check the `err` field to see if establishing the connection was successful:
-```c
-redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL);
-if (cc != NULL && cc->err) {
-    printf("Error: %s\n", cc->errstr);
-    // handle error
-}
-```
-
-### Cluster sending commands
-
-The next that will be introduced is `redisClusterCommand`. 
-This function takes a format similar to printf. In the simplest form,
-it is used like this:
-```c
-reply = redisClusterCommand(clustercontext, "SET foo bar");
-```
-
-The specifier `%s` interpolates a string in the command, and uses `strlen` to
-determine the length of the string:
-```c
-reply = redisClusterCommand(clustercontext, "SET foo %s", value);
-```
-Internally, Hiredis-vip splits the command in different arguments and will
-convert it to the protocol used to communicate with Redis.
-One or more spaces separates arguments, so you can use the specifiers
-anywhere in an argument:
-```c
-reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value);
-```
-
-### Cluster multi-key commands
-
-Hiredis-vip supports mget/mset/del multi-key commands.
-Those multi-key commands is highly effective.
-Millions of keys in one mget command just used several seconds.
-
-Example:
-```c
-reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4);
-```
-
-### Cluster cleaning up
-
-To disconnect and free the context the following function can be used:
-```c
-void redisClusterFree(redisClusterContext *cc);
-```
-This function immediately closes the socket and then frees the allocations done in
-creating the context.
-
-### Cluster pipelining
-
-The function `redisClusterGetReply` is exported as part of the Hiredis API and can be used 
-when a reply is expected on the socket. To pipeline commands, the only things that needs 
-to be done is filling up the output buffer. For this cause, two commands can be used that 
-are identical to the `redisClusterCommand` family, apart from not returning a reply:
-```c
-int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...);
-int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv);
-```
-After calling either function one or more times, `redisClusterGetReply` can be used to receive the
-subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
-the latter means an error occurred while reading a reply. Just as with the other commands,
-the `err` field in the context can be used to find out what the cause of this error is.
-```c
-void redisClusterReset(redisClusterContext *cc);
-```
-Warning: You must call `redisClusterReset` function after one pipelining anyway.
-
-The following examples shows a simple cluster pipeline:
-```c
-redisReply *reply;
-redisClusterAppendCommand(clusterContext,"SET foo bar");
-redisClusterAppendCommand(clusterContext,"GET foo");
-redisClusterGetReply(clusterContext,&reply); // reply for SET
-freeReplyObject(reply);
-redisClusterGetReply(clusterContext,&reply); // reply for GET
-freeReplyObject(reply);
-redisClusterReset(clusterContext);
-```
-
-## Cluster asynchronous API
-
-Hiredis-vip comes with an cluster asynchronous API that works easily with any event library.
-Now we just support and test for libevent and redis ae, if you need for other event libraries,
-please contact with us, and we will support it quickly.
-
-### Connecting
-
-The function `redisAsyncConnect` can be used to establish a non-blocking connection to
-Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
-should be checked after creation to see if there were errors creating the connection.
-Because the connection that will be created is non-blocking, the kernel is not able to
-instantly return if the specified host and port is able to accept a connection.
-```c
-redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL);
-if (acc->err) {
-    printf("Error: %s\n", acc->errstr);
-    // handle error
-}
-```
-
-The cluster asynchronous context can hold a disconnect callback function that is called when the
-connection is disconnected (either because of an error or per user request). This function should
-have the following prototype:
-```c
-void(const redisAsyncContext *c, int status);
-```
-On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
-user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
-field in the context can be accessed to find out the cause of the error.
-
-You not need to reconnect in the disconnect callback, hiredis-vip will reconnect this connection itself
-when commands come to this redis node.
-
-Setting the disconnect callback can only be done once per context. For subsequent calls it will
-return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
-```c
-int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn);
-```
-### Sending commands and their callbacks
-
-In an cluster asynchronous context, commands are automatically pipelined due to the nature of an event loop.
-Therefore, unlike the cluster synchronous API, there is only a single way to send commands.
-Because commands are sent to Redis cluster asynchronously, issuing a command requires a callback function
-that is called when the reply is received. Reply callbacks should have the following prototype:
-```c
-void(redisClusterAsyncContext *acc, void *reply, void *privdata);
-```
-The `privdata` argument can be used to curry arbitrary data to the callback from the point where
-the command is initially queued for execution.
-
-The functions that can be used to issue commands in an asynchronous context are:
-```c
-int redisClusterAsyncCommand(
-  redisClusterAsyncContext *acc, 
-  redisClusterCallbackFn *fn, 
-  void *privdata, const char *format, ...);
-```
-This function work like their blocking counterparts. The return value is `REDIS_OK` when the command
-was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
-is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
-returned on calls to the `redisClusterAsyncCommand` family.
-
-If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback
-for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only
-valid for the duration of the callback.
-
-All pending callbacks are called with a `NULL` reply when the context encountered an error.
-
-### Disconnecting
-
-An cluster asynchronous connection can be terminated using:
-```c
-void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc);
-```
-When this function is called, the connection is **not** immediately terminated. Instead, new
-commands are no longer accepted and the connection is only terminated when all pending commands
-have been written to the socket, their respective replies have been read and their respective
-callbacks have been executed. After this, the disconnection callback is executed with the
-`REDIS_OK` status and the context object is freed.
-
-### Hooking it up to event library *X*
-
-There are a few hooks that need to be set on the cluster context object after it is created.
-See the `adapters/` directory for bindings to *ae* and *libevent*.
-
-## AUTHORS
-
-Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop).
-The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis).
-The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011).
-Hiredis-vip is released under the BSD license.

+ 0 - 341
ext/hiredis-vip-0.3.0/adlist.c

@@ -1,341 +0,0 @@
-/* adlist.c - A generic doubly linked list implementation
- *
- * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   * Redistributions of source code must retain the above copyright notice,
- *     this list of conditions and the following disclaimer.
- *   * Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- *   * Neither the name of Redis nor the names of its contributors may be used
- *     to endorse or promote products derived from this software without
- *     specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-
-#include <stdlib.h>
-#include "adlist.h"
-#include "hiutil.h"
-
-/* Create a new list. The created list can be freed with
- * AlFreeList(), but private value of every node need to be freed
- * by the user before to call AlFreeList().
- *
- * On error, NULL is returned. Otherwise the pointer to the new list. */
-hilist *listCreate(void)
-{
-    struct hilist *list;
-
-    if ((list = hi_alloc(sizeof(*list))) == NULL)
-        return NULL;
-    list->head = list->tail = NULL;
-    list->len = 0;
-    list->dup = NULL;
-    list->free = NULL;
-    list->match = NULL;
-    return list;
-}
-
-/* Free the whole list.
- *
- * This function can't fail. */
-void listRelease(hilist *list)
-{
-    unsigned long len;
-    listNode *current, *next;
-
-    current = list->head;
-    len = list->len;
-    while(len--) {
-        next = current->next;
-        if (list->free) list->free(current->value);
-        hi_free(current);
-        current = next;
-    }
-    hi_free(list);
-}
-
-/* Add a new node to the list, to head, containing the specified 'value'
- * pointer as value.
- *
- * On error, NULL is returned and no operation is performed (i.e. the
- * list remains unaltered).
- * On success the 'list' pointer you pass to the function is returned. */
-hilist *listAddNodeHead(hilist *list, void *value)
-{
-    listNode *node;
-
-    if ((node = hi_alloc(sizeof(*node))) == NULL)
-        return NULL;
-    node->value = value;
-    if (list->len == 0) {
-        list->head = list->tail = node;
-        node->prev = node->next = NULL;
-    } else {
-        node->prev = NULL;
-        node->next = list->head;
-        list->head->prev = node;
-        list->head = node;
-    }
-    list->len++;
-    return list;
-}
-
-/* Add a new node to the list, to tail, containing the specified 'value'
- * pointer as value.
- *
- * On error, NULL is returned and no operation is performed (i.e. the
- * list remains unaltered).
- * On success the 'list' pointer you pass to the function is returned. */
-hilist *listAddNodeTail(hilist *list, void *value)
-{
-    listNode *node;
-
-    if ((node = hi_alloc(sizeof(*node))) == NULL)
-        return NULL;
-    node->value = value;
-    if (list->len == 0) {
-        list->head = list->tail = node;
-        node->prev = node->next = NULL;
-    } else {
-        node->prev = list->tail;
-        node->next = NULL;
-        list->tail->next = node;
-        list->tail = node;
-    }
-    list->len++;
-    return list;
-}
-
-hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) {
-    listNode *node;
-
-    if ((node = hi_alloc(sizeof(*node))) == NULL)
-        return NULL;
-    node->value = value;
-    if (after) {
-        node->prev = old_node;
-        node->next = old_node->next;
-        if (list->tail == old_node) {
-            list->tail = node;
-        }
-    } else {
-        node->next = old_node;
-        node->prev = old_node->prev;
-        if (list->head == old_node) {
-            list->head = node;
-        }
-    }
-    if (node->prev != NULL) {
-        node->prev->next = node;
-    }
-    if (node->next != NULL) {
-        node->next->prev = node;
-    }
-    list->len++;
-    return list;
-}
-
-/* Remove the specified node from the specified list.
- * It's up to the caller to free the private value of the node.
- *
- * This function can't fail. */
-void listDelNode(hilist *list, listNode *node)
-{
-    if (node->prev)
-        node->prev->next = node->next;
-    else
-        list->head = node->next;
-    if (node->next)
-        node->next->prev = node->prev;
-    else
-        list->tail = node->prev;
-    if (list->free) list->free(node->value);
-    hi_free(node);
-    list->len--;
-}
-
-/* Returns a list iterator 'iter'. After the initialization every
- * call to listNext() will return the next element of the list.
- *
- * This function can't fail. */
-listIter *listGetIterator(hilist *list, int direction)
-{
-    listIter *iter;
-
-    if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL;
-    if (direction == AL_START_HEAD)
-        iter->next = list->head;
-    else
-        iter->next = list->tail;
-    iter->direction = direction;
-    return iter;
-}
-
-/* Release the iterator memory */
-void listReleaseIterator(listIter *iter) {
-    hi_free(iter);
-}
-
-/* Create an iterator in the list private iterator structure */
-void listRewind(hilist *list, listIter *li) {
-    li->next = list->head;
-    li->direction = AL_START_HEAD;
-}
-
-void listRewindTail(hilist *list, listIter *li) {
-    li->next = list->tail;
-    li->direction = AL_START_TAIL;
-}
-
-/* Return the next element of an iterator.
- * It's valid to remove the currently returned element using
- * listDelNode(), but not to remove other elements.
- *
- * The function returns a pointer to the next element of the list,
- * or NULL if there are no more elements, so the classical usage patter
- * is:
- *
- * iter = listGetIterator(list,<direction>);
- * while ((node = listNext(iter)) != NULL) {
- *     doSomethingWith(listNodeValue(node));
- * }
- *
- * */
-listNode *listNext(listIter *iter)
-{
-    listNode *current = iter->next;
-
-    if (current != NULL) {
-        if (iter->direction == AL_START_HEAD)
-            iter->next = current->next;
-        else
-            iter->next = current->prev;
-    }
-    return current;
-}
-
-/* Duplicate the whole list. On out of memory NULL is returned.
- * On success a copy of the original list is returned.
- *
- * The 'Dup' method set with listSetDupMethod() function is used
- * to copy the node value. Otherwise the same pointer value of
- * the original node is used as value of the copied node.
- *
- * The original list both on success or error is never modified. */
-hilist *listDup(hilist *orig)
-{
-    hilist *copy;
-    listIter *iter;
-    listNode *node;
-
-    if ((copy = listCreate()) == NULL)
-        return NULL;
-    copy->dup = orig->dup;
-    copy->free = orig->free;
-    copy->match = orig->match;
-    iter = listGetIterator(orig, AL_START_HEAD);
-    while((node = listNext(iter)) != NULL) {
-        void *value;
-
-        if (copy->dup) {
-            value = copy->dup(node->value);
-            if (value == NULL) {
-                listRelease(copy);
-                listReleaseIterator(iter);
-                return NULL;
-            }
-        } else
-            value = node->value;
-        if (listAddNodeTail(copy, value) == NULL) {
-            listRelease(copy);
-            listReleaseIterator(iter);
-            return NULL;
-        }
-    }
-    listReleaseIterator(iter);
-    return copy;
-}
-
-/* Search the list for a node matching a given key.
- * The match is performed using the 'match' method
- * set with listSetMatchMethod(). If no 'match' method
- * is set, the 'value' pointer of every node is directly
- * compared with the 'key' pointer.
- *
- * On success the first matching node pointer is returned
- * (search starts from head). If no matching node exists
- * NULL is returned. */
-listNode *listSearchKey(hilist *list, void *key)
-{
-    listIter *iter;
-    listNode *node;
-
-    iter = listGetIterator(list, AL_START_HEAD);
-    while((node = listNext(iter)) != NULL) {
-        if (list->match) {
-            if (list->match(node->value, key)) {
-                listReleaseIterator(iter);
-                return node;
-            }
-        } else {
-            if (key == node->value) {
-                listReleaseIterator(iter);
-                return node;
-            }
-        }
-    }
-    listReleaseIterator(iter);
-    return NULL;
-}
-
-/* Return the element at the specified zero-based index
- * where 0 is the head, 1 is the element next to head
- * and so on. Negative integers are used in order to count
- * from the tail, -1 is the last element, -2 the penultimate
- * and so on. If the index is out of range NULL is returned. */
-listNode *listIndex(hilist *list, long index) {
-    listNode *n;
-
-    if (index < 0) {
-        index = (-index)-1;
-        n = list->tail;
-        while(index-- && n) n = n->prev;
-    } else {
-        n = list->head;
-        while(index-- && n) n = n->next;
-    }
-    return n;
-}
-
-/* Rotate the list removing the tail node and inserting it to the head. */
-void listRotate(hilist *list) {
-    listNode *tail = list->tail;
-
-    if (listLength(list) <= 1) return;
-
-    /* Detach current tail */
-    list->tail = tail->prev;
-    list->tail->next = NULL;
-    /* Move it as head */
-    list->head->prev = tail;
-    tail->prev = NULL;
-    tail->next = list->head;
-    list->head = tail;
-}

+ 0 - 93
ext/hiredis-vip-0.3.0/adlist.h

@@ -1,93 +0,0 @@
-/* adlist.h - A generic doubly linked list implementation
- *
- * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   * Redistributions of source code must retain the above copyright notice,
- *     this list of conditions and the following disclaimer.
- *   * Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- *   * Neither the name of Redis nor the names of its contributors may be used
- *     to endorse or promote products derived from this software without
- *     specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef __ADLIST_H__
-#define __ADLIST_H__
-
-/* Node, List, and Iterator are the only data structures used currently. */
-
-typedef struct listNode {
-    struct listNode *prev;
-    struct listNode *next;
-    void *value;
-} listNode;
-
-typedef struct listIter {
-    listNode *next;
-    int direction;
-} listIter;
-
-typedef struct hilist {
-    listNode *head;
-    listNode *tail;
-    void *(*dup)(void *ptr);
-    void (*free)(void *ptr);
-    int (*match)(void *ptr, void *key);
-    unsigned long len;
-} hilist;
-
-/* Functions implemented as macros */
-#define listLength(l) ((l)->len)
-#define listFirst(l) ((l)->head)
-#define listLast(l) ((l)->tail)
-#define listPrevNode(n) ((n)->prev)
-#define listNextNode(n) ((n)->next)
-#define listNodeValue(n) ((n)->value)
-
-#define listSetDupMethod(l,m) ((l)->dup = (m))
-#define listSetFreeMethod(l,m) ((l)->free = (m))
-#define listSetMatchMethod(l,m) ((l)->match = (m))
-
-#define listGetDupMethod(l) ((l)->dup)
-#define listGetFree(l) ((l)->free)
-#define listGetMatchMethod(l) ((l)->match)
-
-/* Prototypes */
-hilist *listCreate(void);
-void listRelease(hilist *list);
-hilist *listAddNodeHead(hilist *list, void *value);
-hilist *listAddNodeTail(hilist *list, void *value);
-hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after);
-void listDelNode(hilist *list, listNode *node);
-listIter *listGetIterator(hilist *list, int direction);
-listNode *listNext(listIter *iter);
-void listReleaseIterator(listIter *iter);
-hilist *listDup(hilist *orig);
-listNode *listSearchKey(hilist *list, void *key);
-listNode *listIndex(hilist *list, long index);
-void listRewind(hilist *list, listIter *li);
-void listRewindTail(hilist *list, listIter *li);
-void listRotate(hilist *list);
-
-/* Directions for iterators */
-#define AL_START_HEAD 0
-#define AL_START_TAIL 1
-
-#endif /* __ADLIST_H__ */

+ 0 - 1700
ext/hiredis-vip-0.3.0/command.c

@@ -1,1700 +0,0 @@
-#include <ctype.h>
-#include <errno.h>
-
-#include "command.h"
-#include "hiutil.h"
-#include "hiarray.h"
-
-
-static uint64_t cmd_id = 0;          /* command id counter */
-
-
-/*
- * Return true, if the redis command take no key, otherwise
- * return false
- */
-static int
-redis_argz(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_PING:
-    case CMD_REQ_REDIS_QUIT:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command accepts no arguments, otherwise
- * return false
- */
-static int
-redis_arg0(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_EXISTS:
-    case CMD_REQ_REDIS_PERSIST:
-    case CMD_REQ_REDIS_PTTL:
-    case CMD_REQ_REDIS_SORT:
-    case CMD_REQ_REDIS_TTL:
-    case CMD_REQ_REDIS_TYPE:
-    case CMD_REQ_REDIS_DUMP:
-
-    case CMD_REQ_REDIS_DECR:
-    case CMD_REQ_REDIS_GET:
-    case CMD_REQ_REDIS_INCR:
-    case CMD_REQ_REDIS_STRLEN:
-
-    case CMD_REQ_REDIS_HGETALL:
-    case CMD_REQ_REDIS_HKEYS:
-    case CMD_REQ_REDIS_HLEN:
-    case CMD_REQ_REDIS_HVALS:
-
-    case CMD_REQ_REDIS_LLEN:
-    case CMD_REQ_REDIS_LPOP:
-    case CMD_REQ_REDIS_RPOP:
-
-    case CMD_REQ_REDIS_SCARD:
-    case CMD_REQ_REDIS_SMEMBERS:
-    case CMD_REQ_REDIS_SPOP:
-
-    case CMD_REQ_REDIS_ZCARD:
-    case CMD_REQ_REDIS_PFCOUNT:
-    case CMD_REQ_REDIS_AUTH:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command accepts exactly 1 argument, otherwise
- * return false
- */
-static int
-redis_arg1(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_EXPIRE:
-    case CMD_REQ_REDIS_EXPIREAT:
-    case CMD_REQ_REDIS_PEXPIRE:
-    case CMD_REQ_REDIS_PEXPIREAT:
-
-    case CMD_REQ_REDIS_APPEND:
-    case CMD_REQ_REDIS_DECRBY:
-    case CMD_REQ_REDIS_GETBIT:
-    case CMD_REQ_REDIS_GETSET:
-    case CMD_REQ_REDIS_INCRBY:
-    case CMD_REQ_REDIS_INCRBYFLOAT:
-    case CMD_REQ_REDIS_SETNX:
-
-    case CMD_REQ_REDIS_HEXISTS:
-    case CMD_REQ_REDIS_HGET:
-
-    case CMD_REQ_REDIS_LINDEX:
-    case CMD_REQ_REDIS_LPUSHX:
-    case CMD_REQ_REDIS_RPOPLPUSH:
-    case CMD_REQ_REDIS_RPUSHX:
-
-    case CMD_REQ_REDIS_SISMEMBER:
-
-    case CMD_REQ_REDIS_ZRANK:
-    case CMD_REQ_REDIS_ZREVRANK:
-    case CMD_REQ_REDIS_ZSCORE:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command accepts exactly 2 arguments, otherwise
- * return false
- */
-static int
-redis_arg2(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_GETRANGE:
-    case CMD_REQ_REDIS_PSETEX:
-    case CMD_REQ_REDIS_SETBIT:
-    case CMD_REQ_REDIS_SETEX:
-    case CMD_REQ_REDIS_SETRANGE:
-
-    case CMD_REQ_REDIS_HINCRBY:
-    case CMD_REQ_REDIS_HINCRBYFLOAT:
-    case CMD_REQ_REDIS_HSET:
-    case CMD_REQ_REDIS_HSETNX:
-
-    case CMD_REQ_REDIS_LRANGE:
-    case CMD_REQ_REDIS_LREM:
-    case CMD_REQ_REDIS_LSET:
-    case CMD_REQ_REDIS_LTRIM:
-
-    case CMD_REQ_REDIS_SMOVE:
-
-    case CMD_REQ_REDIS_ZCOUNT:
-    case CMD_REQ_REDIS_ZLEXCOUNT:
-    case CMD_REQ_REDIS_ZINCRBY:
-    case CMD_REQ_REDIS_ZREMRANGEBYLEX:
-    case CMD_REQ_REDIS_ZREMRANGEBYRANK:
-    case CMD_REQ_REDIS_ZREMRANGEBYSCORE:
-
-    case CMD_REQ_REDIS_RESTORE:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command accepts exactly 3 arguments, otherwise
- * return false
- */
-static int
-redis_arg3(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_LINSERT:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command accepts 0 or more arguments, otherwise
- * return false
- */
-static int
-redis_argn(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_BITCOUNT:
-
-    case CMD_REQ_REDIS_SET:
-    case CMD_REQ_REDIS_HDEL:
-    case CMD_REQ_REDIS_HMGET:
-    case CMD_REQ_REDIS_HMSET:
-    case CMD_REQ_REDIS_HSCAN:
-
-    case CMD_REQ_REDIS_LPUSH:
-    case CMD_REQ_REDIS_RPUSH:
-
-    case CMD_REQ_REDIS_SADD:
-    case CMD_REQ_REDIS_SDIFF:
-    case CMD_REQ_REDIS_SDIFFSTORE:
-    case CMD_REQ_REDIS_SINTER:
-    case CMD_REQ_REDIS_SINTERSTORE:
-    case CMD_REQ_REDIS_SREM:
-    case CMD_REQ_REDIS_SUNION:
-    case CMD_REQ_REDIS_SUNIONSTORE:
-    case CMD_REQ_REDIS_SRANDMEMBER:
-    case CMD_REQ_REDIS_SSCAN:
-
-    case CMD_REQ_REDIS_PFADD:
-    case CMD_REQ_REDIS_PFMERGE:
-
-    case CMD_REQ_REDIS_ZADD:
-    case CMD_REQ_REDIS_ZINTERSTORE:
-    case CMD_REQ_REDIS_ZRANGE:
-    case CMD_REQ_REDIS_ZRANGEBYSCORE:
-    case CMD_REQ_REDIS_ZREM:
-    case CMD_REQ_REDIS_ZREVRANGE:
-    case CMD_REQ_REDIS_ZRANGEBYLEX:
-    case CMD_REQ_REDIS_ZREVRANGEBYSCORE:
-    case CMD_REQ_REDIS_ZUNIONSTORE:
-    case CMD_REQ_REDIS_ZSCAN:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command is a vector command accepting one or
- * more keys, otherwise return false
- */
-static int
-redis_argx(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_MGET:
-    case CMD_REQ_REDIS_DEL:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command is a vector command accepting one or
- * more key-value pairs, otherwise return false
- */
-static int
-redis_argkvx(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_MSET:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Return true, if the redis command is either EVAL or EVALSHA. These commands
- * have a special format with exactly 2 arguments, followed by one or more keys,
- * followed by zero or more arguments (the documentation online seems to suggest
- * that at least one argument is required, but that shouldn't be the case).
- */
-static int
-redis_argeval(struct cmd *r)
-{
-    switch (r->type) {
-    case CMD_REQ_REDIS_EVAL:
-    case CMD_REQ_REDIS_EVALSHA:
-        return 1;
-
-    default:
-        break;
-    }
-
-    return 0;
-}
-
-/*
- * Reference: http://redis.io/topics/protocol
- *
- * Redis >= 1.2 uses the unified protocol to send requests to the Redis
- * server. In the unified protocol all the arguments sent to the server
- * are binary safe and every request has the following general form:
- *
- *   *<number of arguments> CR LF
- *   $<number of bytes of argument 1> CR LF
- *   <argument data> CR LF
- *   ...
- *   $<number of bytes of argument N> CR LF
- *   <argument data> CR LF
- *
- * Before the unified request protocol, redis protocol for requests supported
- * the following commands
- * 1). Inline commands: simple commands where arguments are just space
- *     separated strings. No binary safeness is possible.
- * 2). Bulk commands: bulk commands are exactly like inline commands, but
- *     the last argument is handled in a special way in order to allow for
- *     a binary-safe last argument.
- *
- * only supports the Redis unified protocol for requests.
- */
-void
-redis_parse_cmd(struct cmd *r)
-{
-    int len;
-    char *p, *m, *token = NULL;
-    char *cmd_end;
-    char ch;
-    uint32_t rlen = 0;  /* running length in parsing fsa */
-    uint32_t rnarg = 0; /* running # arg used by parsing fsa */
-    enum {
-        SW_START,
-        SW_NARG,
-        SW_NARG_LF,
-        SW_REQ_TYPE_LEN,
-        SW_REQ_TYPE_LEN_LF,
-        SW_REQ_TYPE,
-        SW_REQ_TYPE_LF,
-        SW_KEY_LEN,
-        SW_KEY_LEN_LF,
-        SW_KEY,
-        SW_KEY_LF,
-        SW_ARG1_LEN,
-        SW_ARG1_LEN_LF,
-        SW_ARG1,
-        SW_ARG1_LF,
-        SW_ARG2_LEN,
-        SW_ARG2_LEN_LF,
-        SW_ARG2,
-        SW_ARG2_LF,
-        SW_ARG3_LEN,
-        SW_ARG3_LEN_LF,
-        SW_ARG3,
-        SW_ARG3_LF,
-        SW_ARGN_LEN,
-        SW_ARGN_LEN_LF,
-        SW_ARGN,
-        SW_ARGN_LF,
-        SW_SENTINEL
-    } state;
-
-    state = SW_START;
-    cmd_end = r->cmd + r->clen;
-
-    ASSERT(state >= SW_START && state < SW_SENTINEL);
-    ASSERT(r->cmd != NULL && r->clen > 0);
-
-    for (p = r->cmd; p < cmd_end; p++) {
-        ch = *p;
-
-        switch (state) {
-
-        case SW_START:
-        case SW_NARG:
-            if (token == NULL) {
-                if (ch != '*') {
-                    goto error;
-                }
-                token = p;
-                /* req_start <- p */
-                r->narg_start = p;
-                rnarg = 0;
-                state = SW_NARG;
-            } else if (isdigit(ch)) {
-                rnarg = rnarg * 10 + (uint32_t)(ch - '0');
-            } else if (ch == CR) {
-                if (rnarg == 0) {
-                    goto error;
-                }
-                r->narg = rnarg;
-                r->narg_end = p;
-                token = NULL;
-                state = SW_NARG_LF;
-            } else {
-                goto error;
-            }
-
-            break;
-
-        case SW_NARG_LF:
-            switch (ch) {
-            case LF:
-                state = SW_REQ_TYPE_LEN;
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_REQ_TYPE_LEN:
-            if (token == NULL) {
-                if (ch != '$') {
-                    goto error;
-                }
-                token = p;
-                rlen = 0;
-            } else if (isdigit(ch)) {
-                rlen = rlen * 10 + (uint32_t)(ch - '0');
-            } else if (ch == CR) {
-                if (rlen == 0 || rnarg == 0) {
-                    goto error;
-                }
-                rnarg--;
-                token = NULL;
-                state = SW_REQ_TYPE_LEN_LF;
-            } else {
-                goto error;
-            }
-
-            break;
-
-        case SW_REQ_TYPE_LEN_LF:
-            switch (ch) {
-            case LF:
-                state = SW_REQ_TYPE;
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_REQ_TYPE:
-            if (token == NULL) {
-                token = p;
-            }
-
-            m = token + rlen;
-            if (m >= cmd_end) {
-                //m = cmd_end - 1;
-                //p = m;
-                //break;
-                goto error;
-            }
-
-            if (*m != CR) {
-                goto error;
-            }
-
-            p = m; /* move forward by rlen bytes */
-            rlen = 0;
-            m = token;
-            token = NULL;
-            r->type = CMD_UNKNOWN;
-
-            switch (p - m) {
-
-            case 3:
-                if (str3icmp(m, 'g', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_GET;
-                    break;
-                }
-
-                if (str3icmp(m, 's', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_SET;
-                    break;
-                }
-
-                if (str3icmp(m, 't', 't', 'l')) {
-                    r->type = CMD_REQ_REDIS_TTL;
-                    break;
-                }
-
-                if (str3icmp(m, 'd', 'e', 'l')) {
-                    r->type = CMD_REQ_REDIS_DEL;
-                    break;
-                }
-
-                break;
-
-            case 4:
-                if (str4icmp(m, 'p', 't', 't', 'l')) {
-                    r->type = CMD_REQ_REDIS_PTTL;
-                    break;
-                }
-
-                if (str4icmp(m, 'd', 'e', 'c', 'r')) {
-                    r->type = CMD_REQ_REDIS_DECR;
-                    break;
-                }
-
-                if (str4icmp(m, 'd', 'u', 'm', 'p')) {
-                    r->type = CMD_REQ_REDIS_DUMP;
-                    break;
-                }
-
-                if (str4icmp(m, 'h', 'd', 'e', 'l')) {
-                    r->type = CMD_REQ_REDIS_HDEL;
-                    break;
-                }
-
-                if (str4icmp(m, 'h', 'g', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_HGET;
-                    break;
-                }
-
-                if (str4icmp(m, 'h', 'l', 'e', 'n')) {
-                    r->type = CMD_REQ_REDIS_HLEN;
-                    break;
-                }
-
-                if (str4icmp(m, 'h', 's', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_HSET;
-                    break;
-                }
-
-                if (str4icmp(m, 'i', 'n', 'c', 'r')) {
-                    r->type = CMD_REQ_REDIS_INCR;
-                    break;
-                }
-
-                if (str4icmp(m, 'l', 'l', 'e', 'n')) {
-                    r->type = CMD_REQ_REDIS_LLEN;
-                    break;
-                }
-
-                if (str4icmp(m, 'l', 'p', 'o', 'p')) {
-                    r->type = CMD_REQ_REDIS_LPOP;
-                    break;
-                }
-
-                if (str4icmp(m, 'l', 'r', 'e', 'm')) {
-                    r->type = CMD_REQ_REDIS_LREM;
-                    break;
-                }
-
-                if (str4icmp(m, 'l', 's', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_LSET;
-                    break;
-                }
-
-                if (str4icmp(m, 'r', 'p', 'o', 'p')) {
-                    r->type = CMD_REQ_REDIS_RPOP;
-                    break;
-                }
-
-                if (str4icmp(m, 's', 'a', 'd', 'd')) {
-                    r->type = CMD_REQ_REDIS_SADD;
-                    break;
-                }
-
-                if (str4icmp(m, 's', 'p', 'o', 'p')) {
-                    r->type = CMD_REQ_REDIS_SPOP;
-                    break;
-                }
-
-                if (str4icmp(m, 's', 'r', 'e', 'm')) {
-                    r->type = CMD_REQ_REDIS_SREM;
-                    break;
-                }
-
-                if (str4icmp(m, 't', 'y', 'p', 'e')) {
-                    r->type = CMD_REQ_REDIS_TYPE;
-                    break;
-                }
-
-                if (str4icmp(m, 'm', 'g', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_MGET;
-                    break;
-                }
-                if (str4icmp(m, 'm', 's', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_MSET;
-                    break;
-                }
-
-                if (str4icmp(m, 'z', 'a', 'd', 'd')) {
-                    r->type = CMD_REQ_REDIS_ZADD;
-                    break;
-                }
-
-                if (str4icmp(m, 'z', 'r', 'e', 'm')) {
-                    r->type = CMD_REQ_REDIS_ZREM;
-                    break;
-                }
-
-                if (str4icmp(m, 'e', 'v', 'a', 'l')) {
-                    r->type = CMD_REQ_REDIS_EVAL;
-                    break;
-                }
-
-                if (str4icmp(m, 's', 'o', 'r', 't')) {
-                    r->type = CMD_REQ_REDIS_SORT;
-                    break;
-                }
-
-                if (str4icmp(m, 'p', 'i', 'n', 'g')) {
-                    r->type = CMD_REQ_REDIS_PING;
-                    r->noforward = 1;
-                    break;
-                }
-
-                if (str4icmp(m, 'q', 'u', 'i', 't')) {
-                    r->type = CMD_REQ_REDIS_QUIT;
-                    r->quit = 1;
-                    break;
-                }
-
-                if (str4icmp(m, 'a', 'u', 't', 'h')) {
-                    r->type = CMD_REQ_REDIS_AUTH;
-                    r->noforward = 1;
-                    break;
-                }
-
-                break;
-
-            case 5:
-                if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) {
-                    r->type = CMD_REQ_REDIS_HKEYS;
-                    break;
-                }
-
-                if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_HMGET;
-                    break;
-                }
-
-                if (str5icmp(m, 'h', 'm', 's', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_HMSET;
-                    break;
-                }
-
-                if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) {
-                    r->type = CMD_REQ_REDIS_HVALS;
-                    break;
-                }
-
-                if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) {
-                    r->type = CMD_REQ_REDIS_HSCAN;
-                    break;
-                }
-
-                if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) {
-                    r->type = CMD_REQ_REDIS_LPUSH;
-                    break;
-                }
-
-                if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) {
-                    r->type = CMD_REQ_REDIS_LTRIM;
-                    break;
-                }
-
-                if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) {
-                    r->type = CMD_REQ_REDIS_RPUSH;
-                    break;
-                }
-
-                if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) {
-                    r->type = CMD_REQ_REDIS_SCARD;
-                    break;
-                }
-
-                if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) {
-                    r->type = CMD_REQ_REDIS_SDIFF;
-                    break;
-                }
-
-                if (str5icmp(m, 's', 'e', 't', 'e', 'x')) {
-                    r->type = CMD_REQ_REDIS_SETEX;
-                    break;
-                }
-
-                if (str5icmp(m, 's', 'e', 't', 'n', 'x')) {
-                    r->type = CMD_REQ_REDIS_SETNX;
-                    break;
-                }
-
-                if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) {
-                    r->type = CMD_REQ_REDIS_SMOVE;
-                    break;
-                }
-
-                if (str5icmp(m, 's', 's', 'c', 'a', 'n')) {
-                    r->type = CMD_REQ_REDIS_SSCAN;
-                    break;
-                }
-
-                if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) {
-                    r->type = CMD_REQ_REDIS_ZCARD;
-                    break;
-                }
-
-                if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) {
-                    r->type = CMD_REQ_REDIS_ZRANK;
-                    break;
-                }
-
-                if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) {
-                    r->type = CMD_REQ_REDIS_ZSCAN;
-                    break;
-                }
-
-                if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) {
-                    r->type = CMD_REQ_REDIS_PFADD;
-                    break;
-                }
-
-                break;
-
-            case 6:
-                if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) {
-                    r->type = CMD_REQ_REDIS_APPEND;
-                    break;
-                }
-
-                if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) {
-                    r->type = CMD_REQ_REDIS_DECRBY;
-                    break;
-                }
-
-                if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) {
-                    r->type = CMD_REQ_REDIS_EXISTS;
-                    break;
-                }
-
-                if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_EXPIRE;
-                    break;
-                }
-
-                if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) {
-                    r->type = CMD_REQ_REDIS_GETBIT;
-                    break;
-                }
-
-                if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) {
-                    r->type = CMD_REQ_REDIS_GETSET;
-                    break;
-                }
-
-                if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) {
-                    r->type = CMD_REQ_REDIS_PSETEX;
-                    break;
-                }
-
-                if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) {
-                    r->type = CMD_REQ_REDIS_HSETNX;
-                    break;
-                }
-
-                if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) {
-                    r->type = CMD_REQ_REDIS_INCRBY;
-                    break;
-                }
-
-                if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) {
-                    r->type = CMD_REQ_REDIS_LINDEX;
-                    break;
-                }
-
-                if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) {
-                    r->type = CMD_REQ_REDIS_LPUSHX;
-                    break;
-                }
-
-                if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) {
-                    r->type = CMD_REQ_REDIS_LRANGE;
-                    break;
-                }
-
-                if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) {
-                    r->type = CMD_REQ_REDIS_RPUSHX;
-                    break;
-                }
-
-                if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) {
-                    r->type = CMD_REQ_REDIS_SETBIT;
-                    break;
-                }
-
-                if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) {
-                    r->type = CMD_REQ_REDIS_SINTER;
-                    break;
-                }
-
-                if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) {
-                    r->type = CMD_REQ_REDIS_STRLEN;
-                    break;
-                }
-
-                if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) {
-                    r->type = CMD_REQ_REDIS_SUNION;
-                    break;
-                }
-
-                if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) {
-                    r->type = CMD_REQ_REDIS_ZCOUNT;
-                    break;
-                }
-
-                if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZRANGE;
-                    break;
-                }
-
-                if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZSCORE;
-                    break;
-                }
-
-                break;
-
-            case 7:
-                if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) {
-                    r->type = CMD_REQ_REDIS_PERSIST;
-                    break;
-                }
-
-                if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_PEXPIRE;
-                    break;
-                }
-
-                if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) {
-                    r->type = CMD_REQ_REDIS_HEXISTS;
-                    break;
-                }
-
-                if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) {
-                    r->type = CMD_REQ_REDIS_HGETALL;
-                    break;
-                }
-
-                if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) {
-                    r->type = CMD_REQ_REDIS_HINCRBY;
-                    break;
-                }
-
-                if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) {
-                    r->type = CMD_REQ_REDIS_LINSERT;
-                    break;
-                }
-
-                if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) {
-                    r->type = CMD_REQ_REDIS_ZINCRBY;
-                    break;
-                }
-
-                if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) {
-                    r->type = CMD_REQ_REDIS_EVALSHA;
-                    break;
-                }
-
-                if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_RESTORE;
-                    break;
-                }
-
-                if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) {
-                    r->type = CMD_REQ_REDIS_PFCOUNT;
-                    break;
-                }
-
-                if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) {
-                    r->type = CMD_REQ_REDIS_PFMERGE;
-                    break;
-                }
-
-                break;
-
-            case 8:
-                if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) {
-                    r->type = CMD_REQ_REDIS_EXPIREAT;
-                    break;
-                }
-
-                if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) {
-                    r->type = CMD_REQ_REDIS_BITCOUNT;
-                    break;
-                }
-
-                if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) {
-                    r->type = CMD_REQ_REDIS_GETRANGE;
-                    break;
-                }
-
-                if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) {
-                    r->type = CMD_REQ_REDIS_SETRANGE;
-                    break;
-                }
-
-                if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) {
-                    r->type = CMD_REQ_REDIS_SMEMBERS;
-                    break;
-                }
-
-                if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) {
-                    r->type = CMD_REQ_REDIS_ZREVRANK;
-                    break;
-                }
-
-                break;
-
-            case 9:
-                if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) {
-                    r->type = CMD_REQ_REDIS_PEXPIREAT;
-                    break;
-                }
-
-                if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) {
-                    r->type = CMD_REQ_REDIS_RPOPLPUSH;
-                    break;
-                }
-
-                if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) {
-                    r->type = CMD_REQ_REDIS_SISMEMBER;
-                    break;
-                }
-
-                if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZREVRANGE;
-                    break;
-                }
-
-                if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) {
-                    r->type = CMD_REQ_REDIS_ZLEXCOUNT;
-                    break;
-                }
-
-                break;
-
-            case 10:
-                if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_SDIFFSTORE;
-                    break;
-                }
-
-            case 11:
-                if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) {
-                    r->type = CMD_REQ_REDIS_INCRBYFLOAT;
-                    break;
-                }
-
-                if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_SINTERSTORE;
-                    break;
-                }
-
-                if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) {
-                    r->type = CMD_REQ_REDIS_SRANDMEMBER;
-                    break;
-                }
-
-                if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_SUNIONSTORE;
-                    break;
-                }
-
-                if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZINTERSTORE;
-                    break;
-                }
-
-                if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZUNIONSTORE;
-                    break;
-                }
-
-                if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) {
-                    r->type = CMD_REQ_REDIS_ZRANGEBYLEX;
-                    break;
-                }
-
-                break;
-
-            case 12:
-                if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) {
-                    r->type = CMD_REQ_REDIS_HINCRBYFLOAT;
-                    break;
-                }
-
-
-                break;
-
-            case 13:
-                if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZRANGEBYSCORE;
-                    break;
-                }
-
-                break;
-
-            case 14:
-                if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) {
-                    r->type = CMD_REQ_REDIS_ZREMRANGEBYLEX;
-                    break;
-                }
-
-                break;
-
-            case 15:
-                if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) {
-                    r->type = CMD_REQ_REDIS_ZREMRANGEBYRANK;
-                    break;
-                }
-
-                break;
-
-            case 16:
-                if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZREMRANGEBYSCORE;
-                    break;
-                }
-
-                if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) {
-                    r->type = CMD_REQ_REDIS_ZREVRANGEBYSCORE;
-                    break;
-                }
-
-                break;
-
-            default:
-                break;
-            }
-
-            if (r->type == CMD_UNKNOWN) {
-                goto error;
-            }
-
-            state = SW_REQ_TYPE_LF;
-            break;
-
-        case SW_REQ_TYPE_LF:
-            switch (ch) {
-            case LF:
-                if (redis_argz(r)) {
-                    goto done;
-                } else if (redis_argeval(r)) {
-                    state = SW_ARG1_LEN;
-                } else {
-                    state = SW_KEY_LEN;
-                }
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_KEY_LEN:
-            if (token == NULL) {
-                if (ch != '$') {
-                    goto error;
-                }
-                token = p;
-                rlen = 0;
-            } else if (isdigit(ch)) {
-                rlen = rlen * 10 + (uint32_t)(ch - '0');
-            } else if (ch == CR) {
-                
-                if (rnarg == 0) {
-                    goto error;
-                }
-                rnarg--;
-                token = NULL;
-                state = SW_KEY_LEN_LF;
-            } else {
-                goto error;
-            }
-
-            break;
-
-        case SW_KEY_LEN_LF:
-            switch (ch) {
-            case LF:
-                state = SW_KEY;
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_KEY:
-            if (token == NULL) {
-                token = p;
-            }
-
-            m = token + rlen;
-            if (m >= cmd_end) {
-                //m = b->last - 1;
-                //p = m;
-                //break;
-                goto error;
-            }
-
-            if (*m != CR) {
-                goto error;
-            } else {        /* got a key */
-                struct keypos *kpos;
-
-                p = m;      /* move forward by rlen bytes */
-                rlen = 0;
-                m = token;
-                token = NULL;
-
-                kpos = hiarray_push(r->keys);
-                if (kpos == NULL) {
-                    goto enomem;
-                }
-                kpos->start = m;
-                kpos->end = p;
-                //kpos->v_len = 0;
-
-                state = SW_KEY_LF;
-            }
-
-            break;
-
-        case SW_KEY_LF:
-            switch (ch) {
-            case LF:
-                if (redis_arg0(r)) {
-                    if (rnarg != 0) {
-                        goto error;
-                    }
-                    goto done;
-                } else if (redis_arg1(r)) {
-                    if (rnarg != 1) {
-                        goto error;
-                    }
-                    state = SW_ARG1_LEN;
-                } else if (redis_arg2(r)) {
-                    if (rnarg != 2) {
-                        goto error;
-                    }
-                    state = SW_ARG1_LEN;
-                } else if (redis_arg3(r)) {
-                    if (rnarg != 3) {
-                        goto error;
-                    }
-                    state = SW_ARG1_LEN;
-                } else if (redis_argn(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_ARG1_LEN;
-                } else if (redis_argx(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_KEY_LEN;
-                } else if (redis_argkvx(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    if (r->narg % 2 == 0) {
-                        goto error;
-                    }
-                    state = SW_ARG1_LEN;
-                } else if (redis_argeval(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_ARGN_LEN;
-                } else {
-                    goto error;
-                }
-
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG1_LEN:
-            if (token == NULL) {
-                if (ch != '$') {
-                    goto error;
-                }
-                rlen = 0;
-                token = p;
-            } else if (isdigit(ch)) {
-                rlen = rlen * 10 + (uint32_t)(ch - '0');
-            } else if (ch == CR) {
-                if ((p - token) <= 1 || rnarg == 0) {
-                    goto error;
-                }
-                rnarg--;
-                token = NULL;
-
-                /*
-                //for mset value length
-                if(redis_argkvx(r))
-                {
-                    struct keypos *kpos;
-                    uint32_t array_len = array_n(r->keys);
-                    if(array_len == 0)
-                    {
-                        goto error;
-                    }
-                    
-                    kpos = array_n(r->keys, array_len-1);
-                    if (kpos == NULL || kpos->v_len != 0) {
-                        goto error;
-                    }
-
-                    kpos->v_len = rlen;
-                }
-                */
-                state = SW_ARG1_LEN_LF;
-            } else {
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG1_LEN_LF:
-            switch (ch) {
-            case LF:
-                state = SW_ARG1;
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG1:
-            m = p + rlen;
-            if (m >= cmd_end) {
-                //rlen -= (uint32_t)(b->last - p);
-                //m = b->last - 1;
-                //p = m;
-                //break;
-                goto error;
-            }
-
-            if (*m != CR) {
-                goto error;
-            }
-
-            p = m; /* move forward by rlen bytes */
-            rlen = 0;
-
-            state = SW_ARG1_LF;
-
-            break;
-
-        case SW_ARG1_LF:
-            switch (ch) {
-            case LF:
-                if (redis_arg1(r)) {
-                    if (rnarg != 0) {
-                        goto error;
-                    }
-                    goto done;
-                } else if (redis_arg2(r)) {
-                    if (rnarg != 1) {
-                        goto error;
-                    }
-                    state = SW_ARG2_LEN;
-                } else if (redis_arg3(r)) {
-                    if (rnarg != 2) {
-                        goto error;
-                    }
-                    state = SW_ARG2_LEN;
-                } else if (redis_argn(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_ARGN_LEN;
-                } else if (redis_argeval(r)) {
-                    if (rnarg < 2) {
-                        goto error;
-                    }
-                    state = SW_ARG2_LEN;
-                } else if (redis_argkvx(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_KEY_LEN;
-                } else {
-                    goto error;
-                }
-
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG2_LEN:
-            if (token == NULL) {
-                if (ch != '$') {
-                    goto error;
-                }
-                rlen = 0;
-                token = p;
-            } else if (isdigit(ch)) {
-                rlen = rlen * 10 + (uint32_t)(ch - '0');
-            } else if (ch == CR) {
-                if ((p - token) <= 1 || rnarg == 0) {
-                    goto error;
-                }
-                rnarg--;
-                token = NULL;
-                state = SW_ARG2_LEN_LF;
-            } else {
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG2_LEN_LF:
-            switch (ch) {
-            case LF:
-                state = SW_ARG2;
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG2:
-            if (token == NULL && redis_argeval(r)) {
-                /*
-                 * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must
-                 * be tokenized and stored in contiguous memory.
-                 */
-                token = p;
-            }
-
-            m = p + rlen;
-            if (m >= cmd_end) {
-                //rlen -= (uint32_t)(b->last - p);
-                //m = b->last - 1;
-                //p = m;
-                //break;
-                goto error;
-            }
-
-            if (*m != CR) {
-                goto error;
-            }
-
-            p = m; /* move forward by rlen bytes */
-            rlen = 0;
-
-            if (redis_argeval(r)) {
-                uint32_t nkey;
-                char *chp;
-
-                /*
-                 * For EVAL/EVALSHA, we need to find the integer value of this
-                 * argument. It tells us the number of keys in the script, and
-                 * we need to error out if number of keys is 0. At this point,
-                 * both p and m point to the end of the argument and r->token
-                 * points to the start.
-                 */
-                if (p - token < 1) {
-                    goto error;
-                }
-
-                for (nkey = 0, chp = token; chp < p; chp++) {
-                    if (isdigit(*chp)) {
-                        nkey = nkey * 10 + (uint32_t)(*chp - '0');
-                    } else {
-                        goto error;
-                    }
-                }
-                if (nkey == 0) {
-                    goto error;
-                }
-
-                token = NULL;
-            }
-
-            state = SW_ARG2_LF;
-
-            break;
-
-        case SW_ARG2_LF:
-            switch (ch) {
-            case LF:
-                if (redis_arg2(r)) {
-                    if (rnarg != 0) {
-                        goto error;
-                    }
-                    goto done;
-                } else if (redis_arg3(r)) {
-                    if (rnarg != 1) {
-                        goto error;
-                    }
-                    state = SW_ARG3_LEN;
-                } else if (redis_argn(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_ARGN_LEN;
-                } else if (redis_argeval(r)) {
-                    if (rnarg < 1) {
-                        goto error;
-                    }
-                    state = SW_KEY_LEN;
-                } else {
-                    goto error;
-                }
-
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG3_LEN:
-            if (token == NULL) {
-                if (ch != '$') {
-                    goto error;
-                }
-                rlen = 0;
-                token = p;
-            } else if (isdigit(ch)) {
-                rlen = rlen * 10 + (uint32_t)(ch - '0');
-            } else if (ch == CR) {
-                if ((p - token) <= 1 || rnarg == 0) {
-                    goto error;
-                }
-                rnarg--;
-                token = NULL;
-                state = SW_ARG3_LEN_LF;
-            } else {
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG3_LEN_LF:
-            switch (ch) {
-            case LF:
-                state = SW_ARG3;
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARG3:
-            m = p + rlen;
-            if (m >= cmd_end) {
-                //rlen -= (uint32_t)(b->last - p);
-                //m = b->last - 1;
-                //p = m;
-                //break;
-                goto error;
-            }
-
-            if (*m != CR) {
-                goto error;
-            }
-
-            p = m; /* move forward by rlen bytes */
-            rlen = 0;
-            state = SW_ARG3_LF;
-
-            break;
-
-        case SW_ARG3_LF:
-            switch (ch) {
-            case LF:
-                if (redis_arg3(r)) {
-                    if (rnarg != 0) {
-                        goto error;
-                    }
-                    goto done;
-                } else if (redis_argn(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_ARGN_LEN;
-                } else {
-                    goto error;
-                }
-
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARGN_LEN:
-            if (token == NULL) {
-                if (ch != '$') {
-                    goto error;
-                }
-                rlen = 0;
-                token = p;
-            } else if (isdigit(ch)) {
-                rlen = rlen * 10 + (uint32_t)(ch - '0');
-            } else if (ch == CR) {
-                if ((p - token) <= 1 || rnarg == 0) {
-                    goto error;
-                }
-                rnarg--;
-                token = NULL;
-                state = SW_ARGN_LEN_LF;
-            } else {
-                goto error;
-            }
-
-            break;
-
-        case SW_ARGN_LEN_LF:
-            switch (ch) {
-            case LF:
-                state = SW_ARGN;
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_ARGN:
-            m = p + rlen;
-            if (m >= cmd_end) {
-                //rlen -= (uint32_t)(b->last - p);
-                //m = b->last - 1;
-                //p = m;
-                //break;
-                goto error;
-            }
-
-            if (*m != CR) {
-                goto error;
-            }
-
-            p = m; /* move forward by rlen bytes */
-            rlen = 0;
-            state = SW_ARGN_LF;
-
-            break;
-
-        case SW_ARGN_LF:
-            switch (ch) {
-            case LF:
-                if (redis_argn(r) || redis_argeval(r)) {
-                    if (rnarg == 0) {
-                        goto done;
-                    }
-                    state = SW_ARGN_LEN;
-                } else {
-                    goto error;
-                }
-
-                break;
-
-            default:
-                goto error;
-            }
-
-            break;
-
-        case SW_SENTINEL:
-        default:
-            NOT_REACHED();
-            break;
-        }
-    }
-
-    ASSERT(p == cmd_end);
-
-    return;
-
-done:
-
-    ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL);
-    
-    r->result = CMD_PARSE_OK;
-
-    return;
-
-enomem:
-    
-    r->result = CMD_PARSE_ENOMEM;
-
-    return;
-
-error:
-    
-    r->result = CMD_PARSE_ERROR;
-    errno = EINVAL;
-    if(r->errstr == NULL){
-        r->errstr = hi_alloc(100*sizeof(*r->errstr));
-    }
-
-    len = _scnprintf(r->errstr, 100, "Parse command error. Cmd type: %d, state: %d, break position: %d.", 
-        r->type, state, (int)(p - r->cmd));
-    r->errstr[len] = '\0';
-}
-
-struct cmd *command_get()
-{
-    struct cmd *command;
-    command = hi_alloc(sizeof(struct cmd));
-    if(command == NULL)
-    {
-        return NULL;
-    }
-        
-    command->id = ++cmd_id;
-    command->result = CMD_PARSE_OK;
-    command->errstr = NULL;
-    command->type = CMD_UNKNOWN;
-    command->cmd = NULL;
-    command->clen = 0;
-    command->keys = NULL;
-    command->narg_start = NULL;
-    command->narg_end = NULL;
-    command->narg = 0;
-    command->quit = 0;
-    command->noforward = 0;
-    command->slot_num = -1;
-    command->frag_seq = NULL;
-    command->reply = NULL;
-    command->sub_commands = NULL;
-
-    command->keys = hiarray_create(1, sizeof(struct keypos));
-    if (command->keys == NULL) 
-    {
-        hi_free(command);
-        return NULL;
-    }
-
-    return command;
-}
-
-void command_destroy(struct cmd *command)
-{
-    if(command == NULL)
-    {
-        return;
-    }
-
-    if(command->cmd != NULL)
-    {
-        free(command->cmd);
-    }
-
-    if(command->errstr != NULL){
-        hi_free(command->errstr);
-    }
-
-    if(command->keys != NULL)
-    {
-        command->keys->nelem = 0;
-        hiarray_destroy(command->keys);
-    }
-
-    if(command->frag_seq != NULL)
-    {
-        hi_free(command->frag_seq);
-        command->frag_seq = NULL;
-    }
-
-    if(command->reply != NULL)
-    {
-        freeReplyObject(command->reply);
-    }
-
-    if(command->sub_commands != NULL)
-    {
-        listRelease(command->sub_commands);
-    }
-    
-    hi_free(command);
-}
-
-

+ 0 - 179
ext/hiredis-vip-0.3.0/command.h

@@ -1,179 +0,0 @@
-#ifndef __COMMAND_H_
-#define __COMMAND_H_
-
-#include <stdint.h>
-
-#include "hiredis.h"
-#include "adlist.h"
-
-typedef enum cmd_parse_result {
-    CMD_PARSE_OK,                         /* parsing ok */
-    CMD_PARSE_ENOMEM,                     /* out of memory */
-    CMD_PARSE_ERROR,                      /* parsing error */
-    CMD_PARSE_REPAIR,                     /* more to parse -> repair parsed & unparsed data */
-    CMD_PARSE_AGAIN,                      /* incomplete -> parse again */
-} cmd_parse_result_t;
-
-#define CMD_TYPE_CODEC(ACTION)                                                                      \
-    ACTION( UNKNOWN )                                                                               \
-    ACTION( REQ_REDIS_DEL )                    /* redis commands - keys */                            \
-    ACTION( REQ_REDIS_EXISTS )                                                                      \
-    ACTION( REQ_REDIS_EXPIRE )                                                                      \
-    ACTION( REQ_REDIS_EXPIREAT )                                                                    \
-    ACTION( REQ_REDIS_PEXPIRE )                                                                     \
-    ACTION( REQ_REDIS_PEXPIREAT )                                                                   \
-    ACTION( REQ_REDIS_PERSIST )                                                                     \
-    ACTION( REQ_REDIS_PTTL )                                                                        \
-    ACTION( REQ_REDIS_SORT )                                                                        \
-    ACTION( REQ_REDIS_TTL )                                                                         \
-    ACTION( REQ_REDIS_TYPE )                                                                        \
-    ACTION( REQ_REDIS_APPEND )                 /* redis requests - string */                             \
-    ACTION( REQ_REDIS_BITCOUNT )                                                                    \
-    ACTION( REQ_REDIS_DECR )                                                                        \
-    ACTION( REQ_REDIS_DECRBY )                                                                      \
-    ACTION( REQ_REDIS_DUMP )                                                                        \
-    ACTION( REQ_REDIS_GET )                                                                         \
-    ACTION( REQ_REDIS_GETBIT )                                                                      \
-    ACTION( REQ_REDIS_GETRANGE )                                                                    \
-    ACTION( REQ_REDIS_GETSET )                                                                      \
-    ACTION( REQ_REDIS_INCR )                                                                        \
-    ACTION( REQ_REDIS_INCRBY )                                                                      \
-    ACTION( REQ_REDIS_INCRBYFLOAT )                                                                 \
-    ACTION( REQ_REDIS_MGET )                                                                        \
-    ACTION( REQ_REDIS_MSET )                                                                        \
-    ACTION( REQ_REDIS_PSETEX )                                                                      \
-    ACTION( REQ_REDIS_RESTORE )                                                                     \
-    ACTION( REQ_REDIS_SET )                                                                         \
-    ACTION( REQ_REDIS_SETBIT )                                                                      \
-    ACTION( REQ_REDIS_SETEX )                                                                       \
-    ACTION( REQ_REDIS_SETNX )                                                                       \
-    ACTION( REQ_REDIS_SETRANGE )                                                                    \
-    ACTION( REQ_REDIS_STRLEN )                                                                      \
-    ACTION( REQ_REDIS_HDEL )                   /* redis requests - hashes */                            \
-    ACTION( REQ_REDIS_HEXISTS )                                                                     \
-    ACTION( REQ_REDIS_HGET )                                                                        \
-    ACTION( REQ_REDIS_HGETALL )                                                                     \
-    ACTION( REQ_REDIS_HINCRBY )                                                                     \
-    ACTION( REQ_REDIS_HINCRBYFLOAT )                                                                \
-    ACTION( REQ_REDIS_HKEYS )                                                                       \
-    ACTION( REQ_REDIS_HLEN )                                                                        \
-    ACTION( REQ_REDIS_HMGET )                                                                       \
-    ACTION( REQ_REDIS_HMSET )                                                                       \
-    ACTION( REQ_REDIS_HSET )                                                                        \
-    ACTION( REQ_REDIS_HSETNX )                                                                      \
-    ACTION( REQ_REDIS_HSCAN)                                                                        \
-    ACTION( REQ_REDIS_HVALS )                                                                       \
-    ACTION( REQ_REDIS_LINDEX )                 /* redis requests - lists */                              \
-    ACTION( REQ_REDIS_LINSERT )                                                                     \
-    ACTION( REQ_REDIS_LLEN )                                                                        \
-    ACTION( REQ_REDIS_LPOP )                                                                        \
-    ACTION( REQ_REDIS_LPUSH )                                                                       \
-    ACTION( REQ_REDIS_LPUSHX )                                                                      \
-    ACTION( REQ_REDIS_LRANGE )                                                                      \
-    ACTION( REQ_REDIS_LREM )                                                                        \
-    ACTION( REQ_REDIS_LSET )                                                                        \
-    ACTION( REQ_REDIS_LTRIM )                                                                       \
-    ACTION( REQ_REDIS_PFADD )                  /* redis requests - hyperloglog */                        \
-    ACTION( REQ_REDIS_PFCOUNT )                                                                     \
-    ACTION( REQ_REDIS_PFMERGE )                                                                     \
-    ACTION( REQ_REDIS_RPOP )                                                                        \
-    ACTION( REQ_REDIS_RPOPLPUSH )                                                                   \
-    ACTION( REQ_REDIS_RPUSH )                                                                       \
-    ACTION( REQ_REDIS_RPUSHX )                                                                      \
-    ACTION( REQ_REDIS_SADD )                   /* redis requests - sets */                              \
-    ACTION( REQ_REDIS_SCARD )                                                                       \
-    ACTION( REQ_REDIS_SDIFF )                                                                       \
-    ACTION( REQ_REDIS_SDIFFSTORE )                                                                  \
-    ACTION( REQ_REDIS_SINTER )                                                                      \
-    ACTION( REQ_REDIS_SINTERSTORE )                                                                 \
-    ACTION( REQ_REDIS_SISMEMBER )                                                                   \
-    ACTION( REQ_REDIS_SMEMBERS )                                                                    \
-    ACTION( REQ_REDIS_SMOVE )                                                                       \
-    ACTION( REQ_REDIS_SPOP )                                                                        \
-    ACTION( REQ_REDIS_SRANDMEMBER )                                                                 \
-    ACTION( REQ_REDIS_SREM )                                                                        \
-    ACTION( REQ_REDIS_SUNION )                                                                      \
-    ACTION( REQ_REDIS_SUNIONSTORE )                                                                 \
-    ACTION( REQ_REDIS_SSCAN)                                                                        \
-    ACTION( REQ_REDIS_ZADD )                   /* redis requests - sorted sets */                        \
-    ACTION( REQ_REDIS_ZCARD )                                                                       \
-    ACTION( REQ_REDIS_ZCOUNT )                                                                      \
-    ACTION( REQ_REDIS_ZINCRBY )                                                                     \
-    ACTION( REQ_REDIS_ZINTERSTORE )                                                                 \
-    ACTION( REQ_REDIS_ZLEXCOUNT )                                                                   \
-    ACTION( REQ_REDIS_ZRANGE )                                                                      \
-    ACTION( REQ_REDIS_ZRANGEBYLEX )                                                                 \
-    ACTION( REQ_REDIS_ZRANGEBYSCORE )                                                               \
-    ACTION( REQ_REDIS_ZRANK )                                                                       \
-    ACTION( REQ_REDIS_ZREM )                                                                        \
-    ACTION( REQ_REDIS_ZREMRANGEBYRANK )                                                             \
-    ACTION( REQ_REDIS_ZREMRANGEBYLEX )                                                              \
-    ACTION( REQ_REDIS_ZREMRANGEBYSCORE )                                                            \
-    ACTION( REQ_REDIS_ZREVRANGE )                                                                   \
-    ACTION( REQ_REDIS_ZREVRANGEBYSCORE )                                                            \
-    ACTION( REQ_REDIS_ZREVRANK )                                                                    \
-    ACTION( REQ_REDIS_ZSCORE )                                                                      \
-    ACTION( REQ_REDIS_ZUNIONSTORE )                                                                 \
-    ACTION( REQ_REDIS_ZSCAN)                                                                        \
-    ACTION( REQ_REDIS_EVAL )                   /* redis requests - eval */                              \
-    ACTION( REQ_REDIS_EVALSHA )                                                                     \
-    ACTION( REQ_REDIS_PING )                   /* redis requests - ping/quit */                         \
-    ACTION( REQ_REDIS_QUIT)                                                                         \
-    ACTION( REQ_REDIS_AUTH)                                                                         \
-    ACTION( RSP_REDIS_STATUS )                 /* redis response */                                   \
-    ACTION( RSP_REDIS_ERROR )                                                                       \
-    ACTION( RSP_REDIS_INTEGER )                                                                     \
-    ACTION( RSP_REDIS_BULK )                                                                        \
-    ACTION( RSP_REDIS_MULTIBULK )                                                                   \
-    ACTION( SENTINEL )                                                                              \
-
-
-#define DEFINE_ACTION(_name) CMD_##_name,
-typedef enum cmd_type {
-    CMD_TYPE_CODEC(DEFINE_ACTION)
-} cmd_type_t;
-#undef DEFINE_ACTION
-
-
-struct keypos {
-    char             *start;        /* key start pos */
-    char             *end;          /* key end pos */
-    uint32_t         remain_len;    /* remain length after keypos->end for more key-value pairs in command, like mset */
-};
-
-struct cmd {
-
-    uint64_t             id;              /* command id */
-    
-    cmd_parse_result_t   result;          /* command parsing result */
-    char                 *errstr;         /* error info when the command parse failed */
-
-    cmd_type_t           type;            /* command type */
-
-    char                 *cmd;
-    uint32_t             clen;            /* command length */
-    
-    struct hiarray       *keys;           /* array of keypos, for req */
-
-    char                 *narg_start;     /* narg start (redis) */
-    char                 *narg_end;       /* narg end (redis) */
-    uint32_t             narg;            /* # arguments (redis) */
-
-    unsigned             quit:1;          /* quit request? */
-    unsigned             noforward:1;     /* not need forward (example: ping) */
-
-    int                  slot_num;        /* this command should send to witch slot? 
-                                                                          * -1:the keys in this command cross different slots*/
-    struct cmd           **frag_seq;      /* sequence of fragment command, map from keys to fragments*/
-
-    redisReply           *reply;
-
-    hilist                 *sub_commands;   /* just for pipeline and multi-key commands */
-};
-
-void redis_parse_cmd(struct cmd *r);
-
-struct cmd *command_get(void);
-void command_destroy(struct cmd *command);
-
-#endif

+ 0 - 23
ext/hiredis-vip-0.3.0/fmacros.h

@@ -1,23 +0,0 @@
-#ifndef __HIREDIS_FMACRO_H
-#define __HIREDIS_FMACRO_H
-
-#if defined(__linux__)
-#ifndef _BSD_SOURCE
-#define _BSD_SOURCE
-#endif
-#define _DEFAULT_SOURCE
-#endif
-
-#if defined(__sun__)
-#define _POSIX_C_SOURCE 200112L
-#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
-#define _XOPEN_SOURCE 600
-#else
-#define _XOPEN_SOURCE
-#endif
-
-#if __APPLE__ && __MACH__
-#define _OSX
-#endif
-
-#endif

+ 0 - 188
ext/hiredis-vip-0.3.0/hiarray.c

@@ -1,188 +0,0 @@
-#include <stdlib.h>
-
-#include "hiutil.h"
-#include "hiarray.h"
-
-struct hiarray *
-hiarray_create(uint32_t n, size_t size)
-{
-    struct hiarray *a;
-
-    ASSERT(n != 0 && size != 0);
-
-    a = hi_alloc(sizeof(*a));
-    if (a == NULL) {
-        return NULL;
-    }
-
-    a->elem = hi_alloc(n * size);
-    if (a->elem == NULL) {
-        hi_free(a);
-        return NULL;
-    }
-
-    a->nelem = 0;
-    a->size = size;
-    a->nalloc = n;
-
-    return a;
-}
-
-void
-hiarray_destroy(struct hiarray *a)
-{
-    hiarray_deinit(a);
-    hi_free(a);
-}
-
-int
-hiarray_init(struct hiarray *a, uint32_t n, size_t size)
-{
-    ASSERT(n != 0 && size != 0);
-
-    a->elem = hi_alloc(n * size);
-    if (a->elem == NULL) {
-        return HI_ENOMEM;
-    }
-
-    a->nelem = 0;
-    a->size = size;
-    a->nalloc = n;
-
-    return HI_OK;
-}
-
-void
-hiarray_deinit(struct hiarray *a)
-{
-    ASSERT(a->nelem == 0);
-
-    if (a->elem != NULL) {
-        hi_free(a->elem);
-    }
-}
-
-uint32_t
-hiarray_idx(struct hiarray *a, void *elem)
-{
-    uint8_t *p, *q;
-    uint32_t off, idx;
-
-    ASSERT(elem >= a->elem);
-
-    p = a->elem;
-    q = elem;
-    off = (uint32_t)(q - p);
-
-    ASSERT(off % (uint32_t)a->size == 0);
-
-    idx = off / (uint32_t)a->size;
-
-    return idx;
-}
-
-void *
-hiarray_push(struct hiarray *a)
-{
-    void *elem, *new;
-    size_t size;
-
-    if (a->nelem == a->nalloc) {
-
-        /* the array is full; allocate new array */
-        size = a->size * a->nalloc;
-        new = hi_realloc(a->elem, 2 * size);
-        if (new == NULL) {
-            return NULL;
-        }
-
-        a->elem = new;
-        a->nalloc *= 2;
-    }
-
-    elem = (uint8_t *)a->elem + a->size * a->nelem;
-    a->nelem++;
-
-    return elem;
-}
-
-void *
-hiarray_pop(struct hiarray *a)
-{
-    void *elem;
-
-    ASSERT(a->nelem != 0);
-
-    a->nelem--;
-    elem = (uint8_t *)a->elem + a->size * a->nelem;
-
-    return elem;
-}
-
-void *
-hiarray_get(struct hiarray *a, uint32_t idx)
-{
-    void *elem;
-
-    ASSERT(a->nelem != 0);
-    ASSERT(idx < a->nelem);
-
-    elem = (uint8_t *)a->elem + (a->size * idx);
-
-    return elem;
-}
-
-void *
-hiarray_top(struct hiarray *a)
-{
-    ASSERT(a->nelem != 0);
-
-    return hiarray_get(a, a->nelem - 1);
-}
-
-void
-hiarray_swap(struct hiarray *a, struct hiarray *b)
-{
-    struct hiarray tmp;
-
-    tmp = *a;
-    *a = *b;
-    *b = tmp;
-}
-
-/*
- * Sort nelem elements of the array in ascending order based on the
- * compare comparator.
- */
-void
-hiarray_sort(struct hiarray *a, hiarray_compare_t compare)
-{
-    ASSERT(a->nelem != 0);
-
-    qsort(a->elem, a->nelem, a->size, compare);
-}
-
-/*
- * Calls the func once for each element in the array as long as func returns
- * success. On failure short-circuits and returns the error status.
- */
-int
-hiarray_each(struct hiarray *a, hiarray_each_t func, void *data)
-{
-    uint32_t i, nelem;
-
-    ASSERT(array_n(a) != 0);
-    ASSERT(func != NULL);
-
-    for (i = 0, nelem = hiarray_n(a); i < nelem; i++) {
-        void *elem = hiarray_get(a, i);
-        rstatus_t status;
-
-        status = func(elem, data);
-        if (status != HI_OK) {
-            return status;
-        }
-    }
-
-    return HI_OK;
-}

+ 0 - 56
ext/hiredis-vip-0.3.0/hiarray.h

@@ -1,56 +0,0 @@
-#ifndef __HIARRAY_H_
-#define __HIARRAY_H_
-
-#include <stdio.h>
-
-typedef int (*hiarray_compare_t)(const void *, const void *);
-typedef int (*hiarray_each_t)(void *, void *);
-
-struct hiarray {
-    uint32_t nelem;  /* # element */
-    void     *elem;  /* element */
-    size_t   size;   /* element size */
-    uint32_t nalloc; /* # allocated element */
-};
-
-#define null_hiarray { 0, NULL, 0, 0 }
-
-static inline void
-hiarray_null(struct hiarray *a)
-{
-    a->nelem = 0;
-    a->elem = NULL;
-    a->size = 0;
-    a->nalloc = 0;
-}
-
-static inline void
-hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc)
-{
-    a->nelem = 0;
-    a->elem = elem;
-    a->size = size;
-    a->nalloc = nalloc;
-}
-
-static inline uint32_t
-hiarray_n(const struct hiarray *a)
-{
-    return a->nelem;
-}
-
-struct hiarray *hiarray_create(uint32_t n, size_t size);
-void hiarray_destroy(struct hiarray *a);
-int hiarray_init(struct hiarray *a, uint32_t n, size_t size);
-void hiarray_deinit(struct hiarray *a);
-
-uint32_t hiarray_idx(struct hiarray *a, void *elem);
-void *hiarray_push(struct hiarray *a);
-void *hiarray_pop(struct hiarray *a);
-void *hiarray_get(struct hiarray *a, uint32_t idx);
-void *hiarray_top(struct hiarray *a);
-void hiarray_swap(struct hiarray *a, struct hiarray *b);
-void hiarray_sort(struct hiarray *a, hiarray_compare_t compare);
-int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data);
-
-#endif

+ 0 - 4747
ext/hiredis-vip-0.3.0/hircluster.c

@@ -1,4747 +0,0 @@
-
-#include "fmacros.h"
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <ctype.h>
-
-#include "hircluster.h"
-#include "hiutil.h"
-#include "adlist.h"
-#include "hiarray.h"
-#include "command.h"
-#include "dict.c"
-
-#define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES"
-#define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS"
-
-#define REDIS_COMMAND_ASKING "ASKING"
-#define REDIS_COMMAND_PING "PING"
-
-#define REDIS_PROTOCOL_ASKING "*1\r\n$6\r\nASKING\r\n"
-
-#define IP_PORT_SEPARATOR ":"
-
-#define CLUSTER_ADDRESS_SEPARATOR ","
-
-#define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5
-
-typedef struct cluster_async_data
-{
-    redisClusterAsyncContext *acc;
-    struct cmd *command;
-    redisClusterCallbackFn *callback;
-    int retry_count;
-    void *privdata;
-}cluster_async_data;
-
-typedef enum CLUSTER_ERR_TYPE{
-    CLUSTER_NOT_ERR = 0,
-    CLUSTER_ERR_MOVED,
-    CLUSTER_ERR_ASK,
-    CLUSTER_ERR_TRYAGAIN,
-    CLUSTER_ERR_CROSSSLOT,
-    CLUSTER_ERR_CLUSTERDOWN,
-    CLUSTER_ERR_SENTINEL
-}CLUSTER_ERR_TYPE;
-
-static void cluster_node_deinit(cluster_node *node);
-static void cluster_slot_destroy(cluster_slot *slot);
-static void cluster_open_slot_destroy(copen_slot *oslot);
-
-void listClusterNodeDestructor(void *val)
-{
-    cluster_node_deinit(val);
-
-    hi_free(val);
-}
-
-void listClusterSlotDestructor(void *val)
-{
-    cluster_slot_destroy(val);
-}
-
-unsigned int dictSdsHash(const void *key) {
-    return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
-}
-
-int dictSdsKeyCompare(void *privdata, const void *key1,
-        const void *key2)
-{
-    int l1,l2;
-    DICT_NOTUSED(privdata);
-
-    l1 = sdslen((sds)key1);
-    l2 = sdslen((sds)key2);
-    if (l1 != l2) return 0;
-    return memcmp(key1, key2, l1) == 0;
-}
-
-void dictSdsDestructor(void *privdata, void *val)
-{
-    DICT_NOTUSED(privdata);
-
-    sdsfree(val);
-}
-
-void dictClusterNodeDestructor(void *privdata, void *val)
-{
-    DICT_NOTUSED(privdata);
-
-    cluster_node_deinit(val);
-
-    hi_free(val);
-}
-
-/* Cluster nodes hash table, mapping nodes 
- * name(437c719f50dc9d0745032f3b280ce7ecc40792ac)  
- * or addresses(1.2.3.4:6379) to clusterNode structures.
- * Those nodes need destroy.
- */
-dictType clusterNodesDictType = {
-    dictSdsHash,                /* hash function */
-    NULL,                       /* key dup */
-    NULL,                       /* val dup */
-    dictSdsKeyCompare,          /* key compare */
-    dictSdsDestructor,          /* key destructor */
-    dictClusterNodeDestructor   /* val destructor */
-};
-
-/* Cluster nodes hash table, mapping nodes 
- * name(437c719f50dc9d0745032f3b280ce7ecc40792ac)  
- * or addresses(1.2.3.4:6379) to clusterNode structures.
- * Those nodes do not need destroy.
- */
-dictType clusterNodesRefDictType = {
-    dictSdsHash,                /* hash function */
-    NULL,                       /* key dup */
-    NULL,                       /* val dup */
-    dictSdsKeyCompare,          /* key compare */
-    dictSdsDestructor,          /* key destructor */
-    NULL                        /* val destructor */
-};
-
-
-void listCommandFree(void *command)
-{
-    struct cmd *cmd = command;
-    command_destroy(cmd);
-}
-
-/* Defined in hiredis.c */
-void __redisSetError(redisContext *c, int type, const char *str);
-
-/* Forward declaration of function in hiredis.c */
-int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
-
-/* Helper function for the redisClusterCommand* family of functions.
- *
- * Write a formatted command to the output buffer. If the given context is
- * blocking, immediately read the reply into the "reply" pointer. When the
- * context is non-blocking, the "reply" pointer will not be used and the
- * command is simply appended to the write buffer.
- *
- * Returns the reply when a reply was succesfully retrieved. Returns NULL
- * otherwise. When NULL is returned in a blocking context, the error field
- * in the context will be set.
- */
-static void *__redisBlockForReply(redisContext *c) {
-    void *reply;
-
-    if (c->flags & REDIS_BLOCK) {
-        if (redisGetReply(c,&reply) != REDIS_OK)
-            return NULL;
-        return reply;
-    }
-    return NULL;
-}
-
-
-/* -----------------------------------------------------------------------------
- * Key space handling
- * -------------------------------------------------------------------------- */
-
-/* We have 16384 hash slots. The hash slot of a given key is obtained
- * as the least significant 14 bits of the crc16 of the key.
- *
- * However if the key contains the {...} pattern, only the part between
- * { and } is hashed. This may be useful in the future to force certain
- * keys to be in the same node (assuming no resharding is in progress). */
-static unsigned int keyHashSlot(char *key, int keylen) {
-    int s, e; /* start-end indexes of { and } */
-
-    for (s = 0; s < keylen; s++)
-        if (key[s] == '{') break;
-
-    /* No '{' ? Hash the whole key. This is the base case. */
-    if (s == keylen) return crc16(key,keylen) & 0x3FFF;
-
-    /* '{' found? Check if we have the corresponding '}'. */
-    for (e = s+1; e < keylen; e++)
-        if (key[e] == '}') break;
-
-    /* No '}' or nothing betweeen {} ? Hash the whole key. */
-    if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
-
-    /* If we are here there is both a { and a } on its right. Hash
-     * what is in the middle between { and }. */
-    return crc16(key+s+1,e-s-1) & 0x3FFF;
-}
-
-static void __redisClusterSetError(redisClusterContext *cc, int type, const char *str) {
-    size_t len;
-
-    if(cc == NULL){
-        return;
-    }
-
-    cc->err = type;
-    if (str != NULL) {
-        len = strlen(str);
-        len = len < (sizeof(cc->errstr)-1) ? len : (sizeof(cc->errstr)-1);
-        memcpy(cc->errstr,str,len);
-        cc->errstr[len] = '\0';
-    } else {
-        /* Only REDIS_ERR_IO may lack a description! */
-        assert(type == REDIS_ERR_IO);
-        __redis_strerror_r(errno, cc->errstr, sizeof(cc->errstr));
-    }
-}
-
-static int cluster_reply_error_type(redisReply *reply)
-{
-
-    if(reply == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    if(reply->type == REDIS_REPLY_ERROR)
-    {
-        if((int)strlen(REDIS_ERROR_MOVED) < reply->len && 
-            strncmp(reply->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0)
-        {
-            return CLUSTER_ERR_MOVED;
-        }
-        else if((int)strlen(REDIS_ERROR_ASK) < reply->len && 
-            strncmp(reply->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0)
-        {
-            return CLUSTER_ERR_ASK;
-        }
-        else if((int)strlen(REDIS_ERROR_TRYAGAIN) < reply->len && 
-            strncmp(reply->str, REDIS_ERROR_TRYAGAIN, strlen(REDIS_ERROR_TRYAGAIN)) == 0)
-        {
-            return CLUSTER_ERR_TRYAGAIN;
-        }
-        else if((int)strlen(REDIS_ERROR_CROSSSLOT) < reply->len && 
-            strncmp(reply->str, REDIS_ERROR_CROSSSLOT, strlen(REDIS_ERROR_CROSSSLOT)) == 0)
-        {
-            return CLUSTER_ERR_CROSSSLOT;
-        }
-        else if((int)strlen(REDIS_ERROR_CLUSTERDOWN) < reply->len && 
-            strncmp(reply->str, REDIS_ERROR_CLUSTERDOWN, strlen(REDIS_ERROR_CLUSTERDOWN)) == 0)
-        {
-            return CLUSTER_ERR_CLUSTERDOWN;
-        }
-        else
-        {
-            return CLUSTER_ERR_SENTINEL;
-        }
-    }
-
-    return CLUSTER_NOT_ERR;
-}
-
-static int cluster_node_init(cluster_node *node)
-{
-    if(node == NULL){
-        return REDIS_ERR;
-    }
-    
-    node->name = NULL;
-    node->addr = NULL;
-    node->host = NULL;
-    node->port = 0;
-    node->role = REDIS_ROLE_NULL;
-    node->myself = 0;
-    node->slaves = NULL;
-    node->con = NULL;
-    node->acon = NULL;
-    node->slots = NULL;
-    node->failure_count = 0;
-    node->data = NULL;
-    node->migrating = NULL;
-    node->importing = NULL;
-    
-    return REDIS_OK;
-}
-
-static void cluster_node_deinit(cluster_node *node)
-{   
-    copen_slot **oslot;
-    
-    if(node == NULL)
-    {
-        return;
-    }
-
-    sdsfree(node->name);
-    sdsfree(node->addr);
-    sdsfree(node->host);
-    node->port = 0;
-    node->role = REDIS_ROLE_NULL;
-    node->myself = 0;
-
-    if(node->con != NULL)
-    {
-        redisFree(node->con);
-    }
-
-    if(node->acon != NULL)
-    {
-        redisAsyncFree(node->acon);
-    }
-
-    if(node->slots != NULL)
-    {
-        listRelease(node->slots);
-    }
-
-    if(node->slaves != NULL)
-    {
-        listRelease(node->slaves);
-    }
-
-    if(node->migrating)
-    {
-        while(hiarray_n(node->migrating))
-        {
-            oslot = hiarray_pop(node->migrating);
-            cluster_open_slot_destroy(*oslot);
-        }
-        
-        hiarray_destroy(node->migrating);
-        node->migrating = NULL;
-    }
-
-    if(node->importing)
-    {
-        while(hiarray_n(node->importing))
-        {
-            oslot = hiarray_pop(node->importing);
-            cluster_open_slot_destroy(*oslot);
-        }
-        
-        hiarray_destroy(node->importing);
-        node->importing = NULL;
-    }
-}
-
-static int cluster_slot_init(cluster_slot *slot, cluster_node *node)
-{
-    slot->start = 0;
-    slot->end = 0;
-    slot->node = node;
-    
-    return REDIS_OK;
-}
-
-static cluster_slot *cluster_slot_create(cluster_node *node)
-{
-    cluster_slot *slot;
-
-    slot = hi_alloc(sizeof(*slot));
-    if(slot == NULL){
-        return NULL;
-    }
-
-    cluster_slot_init(slot, node);
-
-    if(node != NULL){
-        ASSERT(node->role == REDIS_ROLE_MASTER);
-        if(node->slots == NULL){
-            node->slots = listCreate();
-            if(node->slots == NULL)
-            {
-                cluster_slot_destroy(slot);
-                return NULL;
-            }
-
-            node->slots->free = listClusterSlotDestructor;
-        }
-        
-        listAddNodeTail(node->slots, slot);
-    }
-    
-    return slot;
-}
-
-static int cluster_slot_ref_node(cluster_slot * slot, cluster_node *node)
-{
-    if(slot == NULL || node == NULL){
-        return REDIS_ERR;
-    }
-
-    
-    if(node->role != REDIS_ROLE_MASTER){
-        return REDIS_ERR;
-    }
-    
-    if(node->slots == NULL){
-        node->slots = listCreate();
-        if(node->slots == NULL)
-        {
-            return REDIS_ERR;
-        }
-
-        node->slots->free = listClusterSlotDestructor;
-    }
-    
-    listAddNodeTail(node->slots, slot);
-    slot->node = node;
-    
-    return REDIS_OK;
-}
-
-static void cluster_slot_destroy(cluster_slot *slot)
-{
-    slot->start = 0;
-    slot->end = 0;
-    slot->node = NULL;
-    
-    hi_free(slot);
-}
-
-static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, 
-    sds remote_name, cluster_node *node)
-{
-    copen_slot *oslot;
-
-    oslot = hi_alloc(sizeof(*oslot));
-    if(oslot == NULL){
-        return NULL;
-    }
-
-    oslot->slot_num = 0;
-    oslot->migrate = 0;
-    oslot->node = NULL;
-    oslot->remote_name = NULL;
-
-    oslot->slot_num = slot_num;
-    oslot->migrate = migrate;
-    oslot->node = node;
-    oslot->remote_name = sdsdup(remote_name);
-
-    return oslot;
-}
-
-static void cluster_open_slot_destroy(copen_slot *oslot)
-{
-    oslot->slot_num = 0;
-    oslot->migrate = 0;
-    oslot->node = NULL;
-
-    if(oslot->remote_name != NULL){
-        sdsfree(oslot->remote_name);
-        oslot->remote_name = NULL;
-    }
-    
-    hi_free(oslot);
-}
-
-/**
-  * Return a new node with the "cluster slots" command reply.
-  */
-static cluster_node *node_get_with_slots(
-    redisClusterContext *cc, redisReply *host_elem, 
-    redisReply *port_elem, uint8_t role)
-{
-    cluster_node *node = NULL;
-
-    if(host_elem == NULL || port_elem == NULL){
-        return NULL;
-    }
-
-    if(host_elem->type != REDIS_REPLY_STRING ||
-        host_elem->len <= 0){
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "Command(cluster slots) reply error: "
-            "node ip is not string.");
-        goto error;
-    }
-
-    if(port_elem->type != REDIS_REPLY_INTEGER ||
-        port_elem->integer <= 0){
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "Command(cluster slots) reply error: "
-            "node port is not integer.");
-        goto error;
-    }
-
-    if(!hi_valid_port((int)port_elem->integer)){
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "Command(cluster slots) reply error: "
-            "node port is not valid.");
-        goto error;
-    }
-
-    node = hi_alloc(sizeof(cluster_node));
-    if(node == NULL){
-        __redisClusterSetError(cc,
-            REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-    
-    cluster_node_init(node);
-
-    if(role == REDIS_ROLE_MASTER){
-        node->slots = listCreate();
-        if(node->slots == NULL){
-            hi_free(node);
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "slots for node listCreate error");
-            goto error;
-        }
-
-        node->slots->free = listClusterSlotDestructor;
-    }
-    
-    node->name = NULL; 
-    node->addr = sdsnewlen(host_elem->str, host_elem->len);
-    node->addr = sdscatfmt(node->addr, ":%i", port_elem->integer);
-    
-    node->host = sdsnewlen(host_elem->str, host_elem->len);
-    node->port = (int)port_elem->integer;
-    node->role = role;
-    
-    return node;
-
-error:
-    
-    if(node != NULL){
-        hi_free(node);
-    }
-
-    return NULL;
-}
-
-/**
-  * Return a new node with the "cluster nodes" command reply.
-  */
-static cluster_node *node_get_with_nodes(
-    redisClusterContext *cc,
-    sds *node_infos, int info_count, uint8_t role)
-{
-    sds *ip_port = NULL;
-    int count_ip_port = 0;
-    cluster_node *node;
-
-    if(info_count < 8)
-    {
-        return NULL;
-    }
-
-    node = hi_alloc(sizeof(cluster_node));
-    if(node == NULL)
-    {
-        __redisClusterSetError(cc,
-            REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-    
-    cluster_node_init(node);
-
-    if(role == REDIS_ROLE_MASTER)
-    {
-        node->slots = listCreate();
-        if(node->slots == NULL)
-        {
-            hi_free(node);
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "slots for node listCreate error");
-            goto error;
-        }
-
-        node->slots->free = listClusterSlotDestructor;
-    }
-    
-    node->name = node_infos[0]; 
-    node->addr = node_infos[1];
-    
-    ip_port = sdssplitlen(node_infos[1], sdslen(node_infos[1]), 
-        IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port);
-    if(ip_port == NULL || count_ip_port != 2)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "split ip port error");
-        goto error;
-    }
-    node->host = ip_port[0];
-    node->port = hi_atoi(ip_port[1], sdslen(ip_port[1]));
-    node->role = role;
-
-    sdsfree(ip_port[1]);
-    free(ip_port);
-
-    node_infos[0] = NULL;
-    node_infos[1] = NULL;
-    
-    return node;
-
-error:
-    if(ip_port != NULL)
-    {
-        sdsfreesplitres(ip_port, count_ip_port);
-    }
-
-    if(node != NULL)
-    {
-        hi_free(node);
-    }
-
-    return NULL;
-}
-
-static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t)
-{
-    dictIterator *di;
-    dictEntry *de_f, *de_t;
-    cluster_node *node_f, *node_t;
-    redisContext *c;
-    redisAsyncContext *ac;
-
-    if(nodes_f == NULL || nodes_t == NULL){
-        return;
-    }
-
-    di = dictGetIterator(nodes_t);
-    while((de_t = dictNext(di)) != NULL){
-        node_t = dictGetEntryVal(de_t);
-        if(node_t == NULL){
-            continue;
-        }
-        
-        de_f = dictFind(nodes_f, node_t->addr);
-        if(de_f == NULL){
-            continue;
-        }
-
-        node_f = dictGetEntryVal(de_f);
-        if(node_f->con != NULL){
-            c = node_f->con;
-            node_f->con = node_t->con;
-            node_t->con = c;
-        }
-
-        if(node_f->acon != NULL){
-            ac = node_f->acon;
-            node_f->acon = node_t->acon;
-            node_t->acon = ac;
-
-            node_t->acon->data = node_t;
-            if (node_f->acon)
-                node_f->acon->data = node_f;
-        }
-    }
-
-    dictReleaseIterator(di);
-    
-}
-
-static int
-cluster_slot_start_cmp(const void *t1, const void *t2)
-{
-    const cluster_slot **s1 = t1, **s2 = t2;
-
-    return (*s1)->start > (*s2)->start?1:-1;
-}
-
-static int
-cluster_master_slave_mapping_with_name(redisClusterContext *cc,
-    dict **nodes, cluster_node *node, sds master_name)
-{
-    int ret;
-    dictEntry *di;
-    cluster_node *node_old;
-    listNode *lnode;
-
-    if(node == NULL || master_name == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    if(*nodes == NULL)
-    {
-        *nodes = dictCreate(
-            &clusterNodesRefDictType, NULL);
-    }
-
-    di = dictFind(*nodes, master_name);
-    if(di == NULL)
-    {
-        ret = dictAdd(*nodes, 
-            sdsnewlen(master_name, sdslen(master_name)), node);
-        if(ret != DICT_OK)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "the address already exists in the nodes");
-            return REDIS_ERR;
-        }
-
-    }
-    else
-    {
-        node_old = dictGetEntryVal(di);
-        if(node_old == NULL)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "dict get value null");
-            return REDIS_ERR;
-        }
-
-        if(node->role == REDIS_ROLE_MASTER &&
-            node_old->role == REDIS_ROLE_MASTER)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "two masters have the same name");
-            return REDIS_ERR;
-        }
-        else if(node->role == REDIS_ROLE_MASTER
-            && node_old->role == REDIS_ROLE_SLAVE)
-        {
-            if(node->slaves == NULL)
-            {
-                node->slaves = listCreate();
-                if(node->slaves == NULL)
-                {
-                    __redisClusterSetError(cc,REDIS_ERR_OOM,
-                        "Out of memory");
-                    return REDIS_ERR;
-                }
-
-                node->slaves->free = 
-                    listClusterNodeDestructor;
-            }
-        
-            if(node_old->slaves != NULL)
-            {
-                node_old->slaves->free = NULL;
-                while(listLength(node_old->slaves) > 0)
-                {
-                    lnode = listFirst(node_old->slaves);
-                    listAddNodeHead(node->slaves, lnode->value);
-                    listDelNode(node_old->slaves, lnode);
-                }
-                listRelease(node_old->slaves);
-                node_old->slaves = NULL;
-            }
-
-            listAddNodeHead(node->slaves, node_old);
-
-            dictSetHashVal(*nodes, di, node);
-        }
-        else if(node->role == REDIS_ROLE_SLAVE)
-        {
-            if(node_old->slaves == NULL)
-            {
-                node_old->slaves = listCreate();
-                if(node_old->slaves == NULL)
-                {
-                    __redisClusterSetError(cc,REDIS_ERR_OOM,
-                        "Out of memory");
-                    return REDIS_ERR;
-                }
-
-                node_old->slaves->free = 
-                    listClusterNodeDestructor;
-            }
-
-            listAddNodeTail(node_old->slaves, node);
-        }
-        else
-        {
-            NOT_REACHED();
-        }
-    }
-                
-    return REDIS_OK;
-}
-
-/**
-  * Parse the "cluster slots" command reply to nodes dict.
-  */
-dict * 
-parse_cluster_slots(redisClusterContext *cc,
-    redisReply *reply, int flags)
-{
-    int ret;
-    cluster_slot *slot = NULL;
-    dict *nodes = NULL;
-    dictEntry *den;
-    redisReply *elem_slots;
-    redisReply *elem_slots_begin, *elem_slots_end;
-    redisReply *elem_nodes;
-    redisReply *elem_ip, *elem_port;
-    cluster_node *master = NULL, *slave;
-    sds address;
-    uint32_t i, idx;
-
-    if(reply == NULL){
-        return NULL;
-    }
-
-    nodes = dictCreate(&clusterNodesDictType, NULL);
-    if(nodes == NULL){
-        __redisClusterSetError(cc,REDIS_ERR_OOM,
-            "out of memory");
-        goto error;
-    }
-    
-    if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0){
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "Command(cluster slots) reply error: "
-            "reply is not an array.");
-        goto error;
-    }
-
-    for(i = 0; i < reply->elements; i ++){
-        elem_slots = reply->element[i];
-        if(elem_slots->type != REDIS_REPLY_ARRAY || 
-            elem_slots->elements < 3){
-            __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                "Command(cluster slots) reply error: "
-                "first sub_reply is not an array.");
-            goto error;
-        }
-        
-        slot = cluster_slot_create(NULL);
-        if(slot == NULL){
-            __redisClusterSetError(cc, REDIS_ERR_OOM, 
-                "Slot create failed: out of memory.");
-            goto error;
-        }
-
-        //one slots region
-        for(idx = 0; idx < elem_slots->elements; idx ++){
-            if(idx == 0){
-                elem_slots_begin = elem_slots->element[idx];
-                if(elem_slots_begin->type != REDIS_REPLY_INTEGER){
-                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                        "Command(cluster slots) reply error: "
-                        "slot begin is not an integer.");
-                    goto error;
-                }
-                slot->start = (int)(elem_slots_begin->integer);
-            }else if(idx == 1){
-                elem_slots_end = elem_slots->element[idx];
-                if(elem_slots_end->type != REDIS_REPLY_INTEGER){
-                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                        "Command(cluster slots) reply error: "
-                        "slot end is not an integer.");
-                    goto error;
-                }
-                
-                slot->end = (int)(elem_slots_end->integer);
-
-                if(slot->start > slot->end){
-                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                        "Command(cluster slots) reply error: "
-                        "slot begin is bigger than slot end.");
-                    goto error;
-                }
-            }else{
-                elem_nodes = elem_slots->element[idx];
-                if(elem_nodes->type != REDIS_REPLY_ARRAY || 
-                    elem_nodes->elements != 3){
-                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                        "Command(cluster slots) reply error: "
-                        "nodes sub_reply is not an correct array.");
-                    goto error;
-                }
-
-                elem_ip = elem_nodes->element[0];
-                elem_port = elem_nodes->element[1];
-
-                if(elem_ip == NULL || elem_port == NULL ||
-                    elem_ip->type != REDIS_REPLY_STRING || 
-                    elem_port->type != REDIS_REPLY_INTEGER){
-                    __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                        "Command(cluster slots) reply error: "
-                        "master ip or port is not correct.");
-                    goto error;
-                }
-
-                //this is master.
-                if(idx == 2){
-                    address = sdsnewlen(elem_ip->str, elem_ip->len);
-                    address = sdscatfmt(address, ":%i", elem_port->integer);
-
-                    den = dictFind(nodes, address);
-                    //master already exits, break to the next slots region.
-                    if(den != NULL){
-                        sdsfree(address);
-
-                        master = dictGetEntryVal(den);
-                        ret = cluster_slot_ref_node(slot, master);
-                        if(ret != REDIS_OK){
-                            __redisClusterSetError(cc, REDIS_ERR_OOM, 
-                                "Slot ref node failed: out of memory.");
-                            goto error;
-                        }
-
-                        slot = NULL;
-                        break;
-                    }
-
-                    sdsfree(address);
-                    master = node_get_with_slots(cc, elem_ip, 
-                        elem_port, REDIS_ROLE_MASTER);
-                    if(master == NULL){
-                        goto error;
-                    }
-
-                    ret = dictAdd(nodes, 
-                        sdsnewlen(master->addr, sdslen(master->addr)), master);
-                    if(ret != DICT_OK){
-                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                            "The address already exists in the nodes");
-                        cluster_node_deinit(master);
-                        hi_free(master);
-                        goto error;
-                    }
-                    
-                    ret = cluster_slot_ref_node(slot, master);
-                    if(ret != REDIS_OK){
-                        __redisClusterSetError(cc, REDIS_ERR_OOM, 
-                            "Slot ref node failed: out of memory.");
-                        goto error;
-                    }
-
-                    slot = NULL;
-                }else if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){
-                    slave = node_get_with_slots(cc, elem_ip, 
-                            elem_port, REDIS_ROLE_SLAVE);
-                    if(slave == NULL){
-                        goto error;
-                    }
-
-                    if(master->slaves == NULL){
-                        master->slaves = listCreate();
-                        if(master->slaves == NULL){
-                            __redisClusterSetError(cc,REDIS_ERR_OOM,
-                                "Out of memory");
-                            cluster_node_deinit(slave);
-                            goto error;
-                        }
-
-                        master->slaves->free = 
-                            listClusterNodeDestructor;
-                    }
-
-                    listAddNodeTail(master->slaves, slave);
-                }
-            }
-        }
-    }
-
-    return nodes;
-
-error:
-
-    if(nodes != NULL){
-        dictRelease(nodes);
-    }
-
-    if(slot != NULL){
-        cluster_slot_destroy(slot);
-    }
-    
-    return NULL;
-}
-
-/**
-  * Parse the "cluster nodes" command reply to nodes dict.
-  */
-dict *
-parse_cluster_nodes(redisClusterContext *cc, 
-    char *str, int str_len, int flags)
-{
-    int ret;
-    dict *nodes = NULL;
-    dict *nodes_name = NULL;
-    cluster_node *master, *slave;
-    cluster_slot *slot;
-    char *pos, *start, *end, *line_start, *line_end;
-    char *role;
-    int role_len;
-    uint8_t myself = 0;
-    int slot_start, slot_end;
-    sds *part = NULL, *slot_start_end = NULL;
-    int count_part = 0, count_slot_start_end = 0;
-    int k;
-    int len;
-
-    nodes = dictCreate(&clusterNodesDictType, NULL);
-    if(nodes == NULL){
-        __redisClusterSetError(cc,REDIS_ERR_OOM,
-            "out of memory");
-        goto error;
-    }
-
-    start = str;
-    end = start + str_len;
-    
-    line_start = start;
-
-    for(pos = start; pos < end; pos ++){
-        if(*pos == '\n'){
-            line_end = pos - 1;
-            len = line_end - line_start;
-            
-            part = sdssplitlen(line_start, len + 1, " ", 1, &count_part);
-
-            if(part == NULL || count_part < 8){
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                    "split cluster nodes error");
-                goto error;
-            }
-
-            //the address string is ":0", skip this node.
-            if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0){
-                sdsfreesplitres(part, count_part);
-                count_part = 0;
-                part = NULL;
-                
-                start = pos + 1;
-                line_start = start;
-                pos = start;
-                
-                continue;
-            }
-
-            if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0){
-                role_len = sdslen(part[2]) - 7;
-                role = part[2] + 7;
-                myself = 1;
-            }else{
-                role_len = sdslen(part[2]);
-                role = part[2];
-            }
-
-            //add master node
-            if(role_len >= 6 && memcmp(role, "master", 6) == 0){
-                if(count_part < 8){
-                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                        "Master node parts number error: less than 8.");
-                    goto error;
-                }
-                
-                master = node_get_with_nodes(cc, 
-                    part, count_part, REDIS_ROLE_MASTER);
-                if(master == NULL){
-                    goto error;
-                }
-
-                ret = dictAdd(nodes, 
-                    sdsnewlen(master->addr, sdslen(master->addr)), master);
-                if(ret != DICT_OK){
-                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                        "The address already exists in the nodes");
-                    cluster_node_deinit(master);
-                    hi_free(master);
-                    goto error;
-                }
-
-                if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){
-                    ret = cluster_master_slave_mapping_with_name(cc, 
-                        &nodes_name, master, master->name);
-                    if(ret != REDIS_OK){
-                        cluster_node_deinit(master);
-                        hi_free(master);
-                        goto error;
-                    }
-                }
-
-                if(myself) master->myself = 1;
-                
-                for(k = 8; k < count_part; k ++){
-                    slot_start_end = sdssplitlen(part[k], 
-                        sdslen(part[k]), "-", 1, &count_slot_start_end);
-                    
-                    if(slot_start_end == NULL){
-                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                            "split slot start end error(NULL)");
-                        goto error;
-                    }else if(count_slot_start_end == 1){
-                        slot_start = 
-                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));
-                        slot_end = slot_start;
-                    }else if(count_slot_start_end == 2){
-                        slot_start = 
-                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));;
-                        slot_end = 
-                            hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));;
-                    }else{
-                        //add open slot for master
-                        if(flags & HIRCLUSTER_FLAG_ADD_OPENSLOT && 
-                            count_slot_start_end == 3 && 
-                            sdslen(slot_start_end[0]) > 1 &&
-                            sdslen(slot_start_end[1]) == 1 && 
-                            sdslen(slot_start_end[2]) > 1 && 
-                            slot_start_end[0][0] == '[' && 
-                            slot_start_end[2][sdslen(slot_start_end[2])-1] == ']'){
-                            
-                            copen_slot *oslot, **oslot_elem;
-                            
-                            sdsrange(slot_start_end[0], 1, -1);
-                            sdsrange(slot_start_end[2], 0, -2);
-                            
-                            if(slot_start_end[1][0] == '>'){
-                                oslot = cluster_open_slot_create(
-                                    hi_atoi(slot_start_end[0],
-                                    sdslen(slot_start_end[0])), 
-                                    1, slot_start_end[2], master);
-                                if(oslot == NULL){
-                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                                        "create open slot error");
-                                    goto error;
-                                }
- 
-                                if(master->migrating == NULL){
-                                    master->migrating = hiarray_create(1, sizeof(oslot));
-                                    if(master->migrating == NULL){
-                                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                                            "create migrating array error");
-                                        cluster_open_slot_destroy(oslot);
-                                        goto error;
-                                    }
-                                }
-
-                                oslot_elem = hiarray_push(master->migrating);
-                                if(oslot_elem == NULL){
-                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                                        "Push migrating array error: out of memory");
-                                    cluster_open_slot_destroy(oslot);
-                                    goto error;
-                                }
-
-                                *oslot_elem = oslot;
-                            }else if(slot_start_end[1][0] == '<'){
-                                oslot = cluster_open_slot_create(hi_atoi(slot_start_end[0],
-                                    sdslen(slot_start_end[0])), 0, slot_start_end[2],
-                                    master);
-                                if(oslot == NULL){
-                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                                        "create open slot error");
-                                    goto error;
-                                }
-
-                                if(master->importing == NULL){
-                                    master->importing = hiarray_create(1, sizeof(oslot));
-                                    if(master->importing == NULL){
-                                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                                            "create migrating array error");
-                                        cluster_open_slot_destroy(oslot);
-                                        goto error;
-                                    }
-                                }
-
-                                oslot_elem = hiarray_push(master->importing);
-                                if(oslot_elem == NULL){
-                                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                                        "push migrating array error: out of memory");
-                                    cluster_open_slot_destroy(oslot);
-                                    goto error;
-                                }
-
-                                *oslot_elem = oslot;
-                            }
-                        }
-                        
-                        slot_start = -1;
-                        slot_end = -1;
-                    }
-                    
-                    sdsfreesplitres(slot_start_end, count_slot_start_end);
-                    count_slot_start_end = 0;
-                    slot_start_end = NULL;
-
-                    if(slot_start < 0 || slot_end < 0 || 
-                        slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS){
-                        continue;
-                    }
-
-                    slot = cluster_slot_create(master);
-                    if(slot == NULL){
-                        __redisClusterSetError(cc,REDIS_ERR_OOM,
-                            "Out of memory");
-                        goto error;
-                    }
-                    
-                    slot->start = (uint32_t)slot_start;
-                    slot->end = (uint32_t)slot_end;                    
-                }
-
-            }
-            //add slave node
-            else if((flags & HIRCLUSTER_FLAG_ADD_SLAVE) && 
-                (role_len >= 5 && memcmp(role, "slave", 5) == 0)){
-                slave = node_get_with_nodes(cc, part, 
-                    count_part, REDIS_ROLE_SLAVE);
-                if(slave == NULL){
-                    goto error;
-                }
-
-                ret = cluster_master_slave_mapping_with_name(cc, 
-                    &nodes_name, slave, part[3]);
-                if(ret != REDIS_OK){
-                    cluster_node_deinit(slave);
-                    hi_free(slave);
-                    goto error;
-                }
-
-                if(myself) slave->myself = 1;
-            }
-
-            if(myself == 1){
-                myself = 0;
-            }
-
-            sdsfreesplitres(part, count_part);
-            count_part = 0;
-            part = NULL;
-            
-            start = pos + 1;
-            line_start = start;
-            pos = start;
-        }
-    }
-
-    if(nodes_name != NULL){
-        dictRelease(nodes_name);
-    }
-    
-    return nodes;
-
-error:
-        
-    if(part != NULL){
-        sdsfreesplitres(part, count_part);
-        count_part = 0;
-        part = NULL;
-    }
-
-    if(slot_start_end != NULL){
-        sdsfreesplitres(slot_start_end, count_slot_start_end);
-        count_slot_start_end = 0;
-        slot_start_end = NULL;
-    }
-
-    if(nodes != NULL){
-        dictRelease(nodes);
-    }
-
-    if(nodes_name != NULL){
-        dictRelease(nodes_name);
-    }
-    
-    return NULL;
-}
-
-/**
-  * Update route with the "cluster nodes" or "cluster slots" command reply.
-  */
-static int 
-cluster_update_route_by_addr(redisClusterContext *cc, 
-    const char *ip, int port)
-{
-    redisContext *c = NULL;
-    redisReply *reply = NULL;
-    dict *nodes = NULL;
-    struct hiarray *slots = NULL;
-    cluster_node *master;
-    cluster_slot *slot, **slot_elem;
-    dictIterator *dit = NULL;
-    dictEntry *den;
-    listIter *lit = NULL;
-    listNode *lnode;
-    cluster_node *table[REDIS_CLUSTER_SLOTS];
-    uint32_t j, k;
-
-    if(cc == NULL){
-        return REDIS_ERR;
-    }
-
-    if(ip == NULL || port <= 0){
-        __redisClusterSetError(cc,
-            REDIS_ERR_OTHER,"Ip or port error!");
-        goto error;
-    }
-
-    if(cc->timeout){
-        c = redisConnectWithTimeout(ip, port, *cc->timeout);
-    }else{
-        c = redisConnect(ip, port);
-    }
-        
-    if (c == NULL){
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "Init redis context error(return NULL)");
-        goto error;
-    }else if(c->err){
-        __redisClusterSetError(cc,c->err,c->errstr);
-        goto error;
-    }
-
-    if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){
-        reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS);
-        if(reply == NULL){
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "Command(cluster slots) reply error(NULL).");
-            goto error;
-        }else if(reply->type != REDIS_REPLY_ARRAY){
-            if(reply->type == REDIS_REPLY_ERROR){
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                    reply->str);
-            }else{
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                    "Command(cluster slots) reply error: type is not array.");
-            }
-            
-            goto error;
-        }
-
-        nodes = parse_cluster_slots(cc, reply, cc->flags);
-    }else{
-        reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES);
-        if(reply == NULL){
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "Command(cluster nodes) reply error(NULL).");
-            goto error;
-        }else if(reply->type != REDIS_REPLY_STRING){
-            if(reply->type == REDIS_REPLY_ERROR){
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                    reply->str);
-            }else{
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                    "Command(cluster nodes) reply error: type is not string.");
-            }
-            
-            goto error;
-        }
-
-        nodes = parse_cluster_nodes(cc, reply->str, reply->len, cc->flags);
-    }
-
-    if(nodes == NULL){
-        goto error;
-    }
-    
-    memset(table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
-    
-    slots = hiarray_create(dictSize(nodes), sizeof(cluster_slot*));
-    if(slots == NULL){
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "Slots array create failed: out of memory");
-        goto error;
-    }
-    
-    dit = dictGetIterator(nodes);
-    if(dit == NULL){
-        __redisClusterSetError(cc,REDIS_ERR_OOM,
-            "Dict get iterator failed: out of memory");
-        goto error;
-    }
-    
-    while((den = dictNext(dit))){
-        master = dictGetEntryVal(den);
-        if(master->role != REDIS_ROLE_MASTER){
-            __redisClusterSetError(cc,REDIS_ERR_OOM,
-                "Node role must be master");
-            goto error;
-        }
-
-        if(master->slots == NULL){
-            continue;
-        }
-        
-        lit = listGetIterator(master->slots, AL_START_HEAD);
-        if(lit == NULL){
-            __redisClusterSetError(cc, REDIS_ERR_OOM,
-                "List get iterator failed: out of memory");
-            goto error;
-        }
-        
-        while((lnode = listNext(lit))){
-            slot = listNodeValue(lnode);
-            if(slot->start > slot->end || 
-                slot->end >= REDIS_CLUSTER_SLOTS){
-                __redisClusterSetError(cc, REDIS_ERR_OTHER,
-                    "Slot region for node is error");
-                goto error;
-            }
-            
-            slot_elem = hiarray_push(slots);
-            *slot_elem = slot;
-        }
-
-        listReleaseIterator(lit);
-    }
-
-    dictReleaseIterator(dit);
-
-    hiarray_sort(slots, cluster_slot_start_cmp);
-    for(j = 0; j < hiarray_n(slots); j ++){
-        slot_elem = hiarray_get(slots, j);
-        
-        for(k = (*slot_elem)->start; k <= (*slot_elem)->end; k ++){
-            if(table[k] != NULL){
-                __redisClusterSetError(cc, REDIS_ERR_OTHER,
-                    "Diffent node hold a same slot");
-                goto error;
-            }
-            
-            table[k] = (*slot_elem)->node;
-        }
-    }
-    
-    cluster_nodes_swap_ctx(cc->nodes, nodes);
-    if(cc->nodes != NULL){
-        dictRelease(cc->nodes);
-        cc->nodes = NULL;
-    }
-    cc->nodes = nodes;
-
-    if(cc->slots != NULL)
-    {
-        cc->slots->nelem = 0;
-        hiarray_destroy(cc->slots);
-        cc->slots = NULL;
-    }
-    cc->slots = slots;
-
-    memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
-    cc->route_version ++;
-    
-    freeReplyObject(reply);
-
-    if(c != NULL){
-        redisFree(c);
-    }
-    
-    return REDIS_OK;
-
-error:
-
-    if(dit != NULL){
-        dictReleaseIterator(dit);
-    }
-
-    if(lit != NULL){
-        listReleaseIterator(lit);    
-    }
-
-    if(slots != NULL)
-    {
-        if(slots == cc->slots)
-        {
-            cc->slots = NULL;
-        }
-        
-        slots->nelem = 0;
-        hiarray_destroy(slots);
-    }
-
-    if(nodes != NULL){
-        if(nodes == cc->nodes){
-            cc->nodes = NULL;
-        }
-
-        dictRelease(nodes);
-    }
-
-    if(reply != NULL){
-        freeReplyObject(reply);
-        reply = NULL;
-    }
-
-    if(c != NULL){
-        redisFree(c);
-    }
-    
-    return REDIS_ERR;
-}
-
-
-/**
-  * Update route with the "cluster nodes" command reply.
-  */
-static int 
-cluster_update_route_with_nodes_old(redisClusterContext *cc, 
-    const char *ip, int port)
-{
-    int ret;
-    redisContext *c = NULL;
-    redisReply *reply = NULL;
-    struct hiarray *slots = NULL;
-    dict *nodes = NULL;
-    dict *nodes_name = NULL;
-    cluster_node *master, *slave;
-    cluster_slot **slot;
-    char *pos, *start, *end, *line_start, *line_end;
-    char *role;
-    int role_len;
-    uint8_t myself = 0;
-    int slot_start, slot_end;
-    sds *part = NULL, *slot_start_end = NULL;
-    int count_part = 0, count_slot_start_end = 0;
-    int j, k;
-    int len;
-    cluster_node *table[REDIS_CLUSTER_SLOTS] = {NULL};
-
-    if(cc == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    if(ip == NULL || port <= 0)
-    {
-        __redisClusterSetError(cc,
-            REDIS_ERR_OTHER,"ip or port error!");
-        goto error;
-    }
-
-    if(cc->timeout)
-    {
-        c = redisConnectWithTimeout(ip, port, *cc->timeout);
-    }
-    else
-    {
-        c = redisConnect(ip, port);
-    }
-        
-    if (c == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "init redis context error(return NULL)");
-        goto error;
-    }
-    else if(c->err)
-    {
-        __redisClusterSetError(cc,c->err,c->errstr);
-        goto error;
-    }
-
-    reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES);
-
-    if(reply == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "command(cluster nodes) reply error(NULL)");
-        goto error;
-    }
-    else if(reply->type != REDIS_REPLY_STRING)
-    {
-        if(reply->type == REDIS_REPLY_ERROR)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                reply->str);
-        }
-        else
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "command(cluster nodes) reply error(type is not string)");
-        }
-        
-        goto error;
-    }
-
-    nodes = dictCreate(&clusterNodesDictType, NULL);
-    
-    slots = hiarray_create(10, sizeof(cluster_slot*));
-    if(slots == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "array create error");
-        goto error;
-    }
-
-    start = reply->str;
-    end = start + reply->len;
-    
-    line_start = start;
-
-    for(pos = start; pos < end; pos ++)
-    {
-        if(*pos == '\n')
-        {
-            line_end = pos - 1;
-            len = line_end - line_start;
-            
-            part = sdssplitlen(line_start, len + 1, " ", 1, &count_part);
-
-            if(part == NULL || count_part < 8)
-            {
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                    "split cluster nodes error");
-                goto error;
-            }
-
-            //the address string is ":0", skip this node.
-            if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0)
-            {
-                sdsfreesplitres(part, count_part);
-                count_part = 0;
-                part = NULL;
-                
-                start = pos + 1;
-                line_start = start;
-                pos = start;
-                
-                continue;
-            }
-
-            if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0)
-            {
-                role_len = sdslen(part[2]) - 7;
-                role = part[2] + 7;
-                myself = 1;
-            }
-            else
-            {
-                role_len = sdslen(part[2]);
-                role = part[2];
-            }
-
-            //add master node
-            if(role_len >= 6 && memcmp(role, "master", 6) == 0)
-            {
-                if(count_part < 8)
-                {
-                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                        "master node part number error");
-                    goto error;
-                }
-                
-                master = node_get_with_nodes(cc, 
-                    part, count_part, REDIS_ROLE_MASTER);
-                if(master == NULL)
-                {
-                    goto error;
-                }
-
-                ret = dictAdd(nodes, 
-                    sdsnewlen(master->addr, sdslen(master->addr)), master);
-                if(ret != DICT_OK)
-                {
-                    __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                        "the address already exists in the nodes");
-                    cluster_node_deinit(master);
-                    hi_free(master);
-                    goto error;
-                }
-
-                if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE)
-                {
-                    ret = cluster_master_slave_mapping_with_name(cc, 
-                        &nodes_name, master, master->name);
-                    if(ret != REDIS_OK)
-                    {
-                        cluster_node_deinit(master);
-                        hi_free(master);
-                        goto error;
-                    }
-                }
-                
-                if(myself == 1)
-                {
-                    master->con = c;
-                    c = NULL;
-                }
-                
-                for(k = 8; k < count_part; k ++)
-                {
-                    slot_start_end = sdssplitlen(part[k], 
-                        sdslen(part[k]), "-", 1, &count_slot_start_end);
-                    
-                    if(slot_start_end == NULL)
-                    {
-                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                            "split slot start end error(NULL)");
-                        goto error;
-                    }
-                    else if(count_slot_start_end == 1)
-                    {
-                        slot_start = 
-                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));
-                        slot_end = slot_start;
-                    }
-                    else if(count_slot_start_end == 2)
-                    {
-                        slot_start = 
-                            hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));;
-                        slot_end = 
-                            hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));;
-                    }
-                    else
-                    {
-                        slot_start = -1;
-                        slot_end = -1;
-                    }
-                    
-                    sdsfreesplitres(slot_start_end, count_slot_start_end);
-                    count_slot_start_end = 0;
-                    slot_start_end = NULL;
-
-                    if(slot_start < 0 || slot_end < 0 || 
-                        slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS)
-                    {
-                        continue;
-                    }
-
-                    for(j = slot_start; j <= slot_end; j ++)
-                    {
-                        if(table[j] != NULL)
-                        {
-                            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                                "diffent node hold a same slot");
-                            goto error;
-                        }
-                        table[j] = master;
-                    }
-                    
-                    slot = hiarray_push(slots);
-                    if(slot == NULL)
-                    {
-                        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                            "slot push in array error");
-                        goto error;
-                    }
-
-                    *slot = cluster_slot_create(master);
-                    if(*slot == NULL)
-                    {
-                        __redisClusterSetError(cc,REDIS_ERR_OOM,
-                            "Out of memory");
-                        goto error;
-                    }
-
-                    (*slot)->start = (uint32_t)slot_start;
-                    (*slot)->end = (uint32_t)slot_end;                    
-                }
-
-            }
-            //add slave node
-            else if((cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) && 
-                (role_len >= 5 && memcmp(role, "slave", 5) == 0))
-            {
-                slave = node_get_with_nodes(cc, part, 
-                    count_part, REDIS_ROLE_SLAVE);
-                if(slave == NULL)
-                {
-                    goto error;
-                }
-
-                ret = cluster_master_slave_mapping_with_name(cc, 
-                    &nodes_name, slave, part[3]);
-                if(ret != REDIS_OK)
-                {
-                    cluster_node_deinit(slave);
-                    hi_free(slave);
-                    goto error;
-                }
-                
-                if(myself == 1)
-                {
-                    slave->con = c;
-                    c = NULL;
-                }
-            }
-
-            if(myself == 1)
-            {
-                myself = 0;
-            }
-
-            sdsfreesplitres(part, count_part);
-            count_part = 0;
-            part = NULL;
-            
-            start = pos + 1;
-            line_start = start;
-            pos = start;
-        }
-    }
-
-    if(cc->slots != NULL)
-    {
-        cc->slots->nelem = 0;
-        hiarray_destroy(cc->slots);
-        cc->slots = NULL;
-    }
-    cc->slots = slots;
-
-    cluster_nodes_swap_ctx(cc->nodes, nodes);
-
-    if(cc->nodes != NULL)
-    {
-        dictRelease(cc->nodes);
-        cc->nodes = NULL;
-    }
-    cc->nodes = nodes;
-
-    hiarray_sort(cc->slots, cluster_slot_start_cmp);
-
-    memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
-    cc->route_version ++;
-    
-    freeReplyObject(reply);
-
-    if(c != NULL)
-    {
-        redisFree(c);
-    }
-
-    if(nodes_name != NULL)
-    {
-        dictRelease(nodes_name);
-    }
-    
-    return REDIS_OK;
-
-error:
-        
-    if(part != NULL)
-    {
-        sdsfreesplitres(part, count_part);
-        count_part = 0;
-        part = NULL;
-    }
-
-    if(slot_start_end != NULL)
-    {
-        sdsfreesplitres(slot_start_end, count_slot_start_end);
-        count_slot_start_end = 0;
-        slot_start_end = NULL;
-    }
-
-    if(slots != NULL)
-    {
-        if(slots == cc->slots)
-        {
-            cc->slots = NULL;
-        }
-
-        slots->nelem = 0;
-        hiarray_destroy(slots);
-    }
-
-    if(nodes != NULL)
-    {
-        if(nodes == cc->nodes)
-        {
-            cc->nodes = NULL;
-        }
-
-        dictRelease(nodes);
-    }
-
-    if(nodes_name != NULL)
-    {
-        dictRelease(nodes_name);
-    }
-
-    if(reply != NULL)
-    {
-        freeReplyObject(reply);
-        reply = NULL;
-    }
-
-    if(c != NULL)
-    {
-        redisFree(c);
-    }
-    
-    return REDIS_ERR;
-}
-
-int
-cluster_update_route(redisClusterContext *cc)
-{
-    int ret;
-    int flag_err_not_set = 1;
-    cluster_node *node;
-    dictIterator *it;
-    dictEntry *de;
-    
-    if(cc == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    if(cc->ip != NULL && cc->port > 0)
-    {
-        ret = cluster_update_route_by_addr(cc, cc->ip, cc->port);
-        if(ret == REDIS_OK)
-        {
-            return REDIS_OK;
-        }
-
-        flag_err_not_set = 0;
-    }
-
-    if(cc->nodes == NULL)
-    {
-        if(flag_err_not_set)
-        {
-            __redisClusterSetError(cc, REDIS_ERR_OTHER, "no server address");
-        }
-        
-        return REDIS_ERR;
-    }
-
-    it = dictGetIterator(cc->nodes);
-    while ((de = dictNext(it)) != NULL)
-    {
-        node = dictGetEntryVal(de);
-        if(node == NULL || node->host == NULL || node->port < 0)
-        {
-            continue;
-        }
-
-        ret = cluster_update_route_by_addr(cc, node->host, node->port);
-        if(ret == REDIS_OK)
-        {
-            if(cc->err)
-            {
-                cc->err = 0;
-                memset(cc->errstr, '\0', strlen(cc->errstr));
-            }
-            
-            dictReleaseIterator(it);
-            return REDIS_OK;
-        }
-
-        flag_err_not_set = 0;
-    }
-    
-    dictReleaseIterator(it);
-
-    if(flag_err_not_set)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "no valid server address");
-    }
-
-    return REDIS_ERR;
-}
-
-static void print_cluster_node_list(redisClusterContext *cc)
-{
-    dictIterator *di = NULL;
-    dictEntry *de;
-    listIter *it;
-    listNode *ln;
-    cluster_node *master, *slave;
-    hilist *slaves;
-
-    if(cc == NULL)
-    {
-        return;
-    }
-
-    di = dictGetIterator(cc->nodes);
-
-    printf("name\taddress\trole\tslaves\n");
-    
-    while((de = dictNext(di)) != NULL) {
-        master = dictGetEntryVal(de);
-
-        printf("%s\t%s\t%d\t%s\n",master->name, master->addr, 
-            master->role, master->slaves?"hava":"null");
-
-        slaves = master->slaves;
-        if(slaves == NULL)
-        {
-            continue;
-        }
-        
-        it = listGetIterator(slaves, AL_START_HEAD);
-        while((ln = listNext(it)) != NULL)
-        {
-            slave = listNodeValue(ln);
-            printf("%s\t%s\t%d\t%s\n",slave->name, slave->addr, 
-                slave->role, slave->slaves?"hava":"null");
-        }
-
-        listReleaseIterator(it);
-
-        printf("\n");
-    }
-}
-
-
-int test_cluster_update_route(redisClusterContext *cc)
-{
-    int ret;
-    
-    ret = cluster_update_route(cc);
-
-    //print_cluster_node_list(cc);
-    
-    return ret;
-}
-
-static redisClusterContext *redisClusterContextInit(void) {
-    redisClusterContext *cc;
-
-    cc = calloc(1,sizeof(redisClusterContext));
-    if (cc == NULL)
-        return NULL;
-
-    cc->err = 0;
-    cc->errstr[0] = '\0';
-    cc->ip = NULL;
-    cc->port = 0;
-    cc->flags = 0;
-    cc->timeout = NULL;
-    cc->nodes = NULL;
-    cc->slots = NULL;
-    cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT;
-    cc->retry_count = 0;
-    cc->requests = NULL;
-    cc->need_update_route = 0;
-    cc->update_route_time = 0LL;
-
-    cc->route_version = 0LL;
-
-    memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
-    
-    return cc;
-}
-
-void redisClusterFree(redisClusterContext *cc) {
-    
-    if (cc == NULL)
-        return;
-
-    if(cc->ip)
-    {
-        sdsfree(cc->ip);
-        cc->ip = NULL;
-    }
-
-    if (cc->timeout)
-    {
-        free(cc->timeout);
-    }
-
-    memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *));
-
-    if(cc->slots != NULL)
-    {
-        cc->slots->nelem = 0;
-        hiarray_destroy(cc->slots);
-        cc->slots = NULL;
-    }
-
-    if(cc->nodes != NULL)
-    {
-        dictRelease(cc->nodes);
-    }
-
-    if(cc->requests != NULL)
-    {
-        listRelease(cc->requests);
-    }
-    
-    free(cc);
-}
-
-static int redisClusterAddNode(redisClusterContext *cc, const char *addr)
-{
-    dictEntry *node_entry;
-    cluster_node *node;
-    sds *ip_port = NULL;
-    int ip_port_count = 0;
-    sds ip;
-    int port;
-    
-    if(cc == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    if(cc->nodes == NULL)
-    {
-        cc->nodes = dictCreate(&clusterNodesDictType, NULL);
-        if(cc->nodes == NULL)
-        {
-            return REDIS_ERR;
-        }
-    }
-
-    node_entry = dictFind(cc->nodes, addr);
-    if(node_entry == NULL)
-    {
-        ip_port = sdssplitlen(addr, strlen(addr), 
-            IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &ip_port_count);
-        if(ip_port == NULL || ip_port_count != 2 || 
-            sdslen(ip_port[0]) <= 0 || sdslen(ip_port[1]) <= 0)
-        {
-            if(ip_port != NULL)
-            {
-                sdsfreesplitres(ip_port, ip_port_count);
-            }
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,"server address is error(correct is like: 127.0.0.1:1234)");
-            return REDIS_ERR;
-        }
-
-        ip = ip_port[0];
-        port = hi_atoi(ip_port[1], sdslen(ip_port[1]));
-
-        if(port <= 0)
-        {
-            sdsfreesplitres(ip_port, ip_port_count);
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,"server port is error");
-            return REDIS_ERR;
-        }
-
-        sdsfree(ip_port[1]);
-        free(ip_port);
-        ip_port = NULL;
-    
-        node = hi_alloc(sizeof(cluster_node));
-        if(node == NULL)
-        {
-            sdsfree(ip);
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,"alloc cluster node error");
-            return REDIS_ERR;
-        }
-
-        cluster_node_init(node);
-
-        node->addr = sdsnew(addr);
-        if(node->addr == NULL)
-        {
-            sdsfree(ip);
-            hi_free(node);
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,"new node address error");
-            return REDIS_ERR;
-        }
-
-        node->host = ip;
-        node->port = port;
-
-        dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node);
-    }
-    
-    return REDIS_OK;
-}
-
-
-/* Connect to a Redis cluster. On error the field error in the returned
- * context will be set to the return value of the error function.
- * When no set of reply functions is given, the default set will be used. */
-static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const char *addrs) {
-
-    int ret;
-    sds *address = NULL;
-    int address_count = 0;
-    int i;
-
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-    
-
-    address = sdssplitlen(addrs, strlen(addrs), CLUSTER_ADDRESS_SEPARATOR, 
-        strlen(CLUSTER_ADDRESS_SEPARATOR), &address_count);
-    if(address == NULL || address_count <= 0)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address is error(correct is like: 127.0.0.1:1234,127.0.0.2:5678)");
-        return cc;
-    }
-
-    for(i = 0; i < address_count; i ++)
-    {
-        ret = redisClusterAddNode(cc, address[i]);
-        if(ret != REDIS_OK)
-        {
-            sdsfreesplitres(address, address_count);
-            return cc;
-        }
-    }
-
-    sdsfreesplitres(address, address_count);
-    
-    cluster_update_route(cc);
-
-    return cc;
-}
-
-redisClusterContext *redisClusterConnect(const char *addrs, int flags)
-{
-    redisClusterContext *cc;
-
-    cc = redisClusterContextInit();
-
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    cc->flags |= REDIS_BLOCK;
-    if(flags)
-    {
-        cc->flags |= flags;
-    }
-    
-    return _redisClusterConnect(cc, addrs);
-}
-
-redisClusterContext *redisClusterConnectWithTimeout(
-    const char *addrs, const struct timeval tv, int flags)
-{
-    redisClusterContext *cc;
-
-    cc = redisClusterContextInit();
-
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    cc->flags |= REDIS_BLOCK;
-    if(flags)
-    {
-        cc->flags |= flags;
-    }
-    
-    if (cc->timeout == NULL)
-    {
-        cc->timeout = malloc(sizeof(struct timeval));
-    }
-    
-    memcpy(cc->timeout, &tv, sizeof(struct timeval));
-    
-    return _redisClusterConnect(cc, addrs);
-}
-
-redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) {
-
-    redisClusterContext *cc;
-
-    cc = redisClusterContextInit();
-
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    cc->flags &= ~REDIS_BLOCK;
-    if(flags)
-    {
-        cc->flags |= flags;
-    }
-    
-    return _redisClusterConnect(cc, addrs);
-}
-
-redisContext *ctx_get_by_node(cluster_node *node, 
-    const struct timeval *timeout, int flags)
-{
-    redisContext *c = NULL;
-    if(node == NULL)
-    {
-        return NULL;
-    }
-
-    c = node->con;
-    if(c != NULL)
-    {
-        if(c->err)
-        {
-            redisReconnect(c);
-        }
-
-        return c;
-    }
-
-    if(node->host == NULL || node->port <= 0)
-    {
-        return NULL;
-    }
-
-    if(flags & REDIS_BLOCK)
-    {
-        if(timeout)
-        {
-            c = redisConnectWithTimeout(node->host, node->port, *timeout);
-        }
-        else
-        {
-            c = redisConnect(node->host, node->port);
-        }
-    }
-    else
-    {
-        c = redisConnectNonBlock(node->host, node->port);
-    }
-
-    node->con = c;
-
-    return c;
-}
-
-static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num)
-{
-    struct hiarray *slots;
-    uint32_t slot_count;
-    cluster_slot **slot;
-    uint32_t middle, start, end;
-    uint8_t stop = 0;
-    
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    if(slot_num >= REDIS_CLUSTER_SLOTS)
-    {
-        return NULL;
-    }
-
-    slots = cc->slots;
-    if(slots == NULL)
-    {
-        return NULL;
-    }
-    slot_count = hiarray_n(slots);
-
-    start = 0;
-    end = slot_count - 1;
-    middle = 0;
-
-    do{
-        if(start >= end)
-        {
-            stop = 1;
-            middle = end;
-        }
-        else
-        {
-            middle = start + (end - start)/2;
-        }
-
-        ASSERT(middle < slot_count);
-
-        slot = hiarray_get(slots, middle);
-        if((*slot)->start > slot_num)
-        {
-            end = middle - 1;
-        }
-        else if((*slot)->end < slot_num)
-        {
-            start = middle + 1;
-        }
-        else
-        {
-            return (*slot)->node;
-        }
-            
-        
-    }while(!stop);
-
-    printf("slot_num : %d\n", slot_num);
-    printf("slot_count : %d\n", slot_count);
-    printf("start : %d\n", start);
-    printf("end : %d\n", end);
-    printf("middle : %d\n", middle);
-
-    return NULL;
-}
-
-
-static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num)
-{   
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    if(slot_num >= REDIS_CLUSTER_SLOTS)
-    {
-        return NULL;
-    }
-
-    return cc->table[slot_num];
-    
-}
-
-static cluster_node *node_get_witch_connected(redisClusterContext *cc)
-{
-    dictIterator *di;
-    dictEntry *de;
-    struct cluster_node *node;
-    redisContext *c = NULL;
-    redisReply *reply = NULL;
-
-    if(cc == NULL || cc->nodes == NULL)
-    {
-        return NULL;
-    }
-
-    di = dictGetIterator(cc->nodes);
-    while((de = dictNext(di)) != NULL)
-    {
-        node = dictGetEntryVal(de);
-        if(node == NULL)
-        {
-            continue;
-        }
-        
-        c = ctx_get_by_node(node, cc->timeout, REDIS_BLOCK);
-        if(c == NULL || c->err)
-        {
-            continue;
-        }
-
-        reply = redisCommand(c, REDIS_COMMAND_PING);
-        if(reply != NULL && reply->type == REDIS_REPLY_STATUS &&
-            reply->str != NULL && strcmp(reply->str, "PONG") == 0)
-        {
-            freeReplyObject(reply);
-            reply = NULL;
-            
-            dictReleaseIterator(di);            
-        
-            return node;
-        }
-        else if(reply != NULL)
-        {
-            freeReplyObject(reply);
-            reply = NULL;
-        }
-    }
-
-    dictReleaseIterator(di);
-
-    return NULL;
-}
-
-static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len)
-{
-    struct cmd *command = NULL;
-    struct keypos *kp;
-    int key_count;
-    uint32_t i;
-    int slot_num = -1;
-
-    if(cc == NULL || cmd == NULL || len <= 0)
-    {
-        goto done;
-    }
-
-    command = command_get();
-    if(command == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        goto done;
-    }
-    
-    command->cmd = cmd;
-    command->clen = len;
-    redis_parse_cmd(command);
-    if(command->result != CMD_PARSE_OK)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error");
-        goto done;
-    }
-
-    key_count = hiarray_n(command->keys);
-
-    if(key_count <= 0)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "no keys in command(must have keys for redis cluster mode)");
-        goto done;
-    }
-    else if(key_count == 1)
-    {
-        kp = hiarray_get(command->keys, 0);
-        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
-
-        goto done;
-    }
-    
-    for(i = 0; i < hiarray_n(command->keys); i ++)
-    {
-        kp = hiarray_get(command->keys, i);
-
-        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
-    }
-
-done:
-    
-    if(command != NULL)
-    {
-        command->cmd = NULL;
-        command_destroy(command);
-    }
-    
-    return slot_num;
-}
-
-/* Get the cluster config from one node.
-  * Return value: config_value string must free by usr.
-  */
-static char * cluster_config_get(redisClusterContext *cc, 
-    const char *config_name, int *config_value_len)
-{
-    redisContext *c;
-    cluster_node *node;
-    redisReply *reply = NULL, *sub_reply;
-    char *config_value = NULL;
-
-    if(cc == NULL || config_name == NULL
-        || config_value_len == NULL)
-    {
-        return NULL;
-    }
-    
-    node = node_get_witch_connected(cc);
-    if(node == NULL)
-    {
-        __redisClusterSetError(cc, 
-            REDIS_ERR_OTHER, "no reachable node in cluster");
-        goto error;
-    }
-
-    c = ctx_get_by_node(node, cc->timeout, cc->flags);
-    
-    reply = redisCommand(c, "config get %s", config_name);
-    if(reply == NULL)
-    {
-        __redisClusterSetError(cc, 
-            REDIS_ERR_OTHER, "reply for config get is null");
-        goto error;
-    }
-
-    if(reply->type != REDIS_REPLY_ARRAY)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "reply for config get type is not array");
-        goto error;
-    }
-
-    if(reply->elements != 2)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "reply for config get elements number is not 2");
-        goto error;
-    }
-
-    sub_reply = reply->element[0];
-    if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "reply for config get config name is not string");
-        goto error;
-    }
-
-    if(strcmp(sub_reply->str, config_name))
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "reply for config get config name is not we want");
-        goto error;
-    }
-
-    sub_reply = reply->element[1];
-    if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "reply for config get config value type is not string");
-        goto error;
-    }
-
-    config_value = sub_reply->str;
-    *config_value_len = sub_reply->len;
-    sub_reply->str= NULL;
-
-    if(reply != NULL)
-    {
-        freeReplyObject(reply);    
-    }
-
-    return config_value;
-
-error:
-
-    if(reply != NULL)
-    {
-        freeReplyObject(reply);    
-    }
-
-    return NULL;
-}
-
-/* Helper function for the redisClusterAppendCommand* family of functions.
- *
- * Write a formatted command to the output buffer. When this family
- * is used, you need to call redisGetReply yourself to retrieve
- * the reply (or replies in pub/sub).
- */
-static int __redisClusterAppendCommand(redisClusterContext *cc, 
-    struct cmd *command) {
-
-    cluster_node *node;
-    redisContext *c = NULL;
-
-    if(cc == NULL || command == NULL)
-    {
-        return REDIS_ERR;
-    }
-    
-    node = node_get_by_table(cc, (uint32_t)command->slot_num);
-    if(node == NULL)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error");
-        return REDIS_ERR;
-    }
-
-    c = ctx_get_by_node(node, cc->timeout, cc->flags);
-    if(c == NULL)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null");
-        return REDIS_ERR;
-    }
-    else if(c->err)
-    {
-        __redisClusterSetError(cc, c->err, c->errstr);
-        return REDIS_ERR;
-    }
-
-    if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) 
-    {
-        __redisClusterSetError(cc, c->err, c->errstr);
-        return REDIS_ERR;
-    }
-    
-    return REDIS_OK;
-}
-
-/* Helper function for the redisClusterGetReply* family of functions.
- */
-static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply)
-{
-    cluster_node *node;
-    redisContext *c;
-
-    if(cc == NULL || slot_num < 0 || reply == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    node = node_get_by_table(cc, (uint32_t)slot_num);
-    if(node == NULL)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table is null");
-        return REDIS_ERR;
-    }
-
-    c = ctx_get_by_node(node, cc->timeout, cc->flags);
-    if(c == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        return REDIS_ERR;
-    }
-    else if(c->err)
-    {
-        if(cc->need_update_route == 0)
-        {
-            cc->retry_count ++;
-            if(cc->retry_count > cc->max_redirect_count)
-            {
-                cc->need_update_route = 1;
-                cc->retry_count = 0;
-            }
-        }
-        __redisClusterSetError(cc, c->err, c->errstr);
-        return REDIS_ERR;
-    }
-
-    if(redisGetReply(c, reply) != REDIS_OK)
-    {
-        __redisClusterSetError(cc, c->err, c->errstr);
-        return REDIS_ERR;
-    }
-    
-    if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED)
-    {
-        cc->need_update_route = 1;
-    }
-
-    return REDIS_OK;
-}
-
-static cluster_node *node_get_by_ask_error_reply(
-    redisClusterContext *cc, redisReply *reply)
-{
-    sds *part = NULL, *ip_port = NULL;
-    int part_len = 0, ip_port_len;
-    dictEntry *de;
-    cluster_node *node = NULL;
-
-    if(cc == NULL || reply == NULL)
-    {
-        return NULL;
-    }
-
-    if(cluster_reply_error_type(reply) != CLUSTER_ERR_ASK)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "reply is not ask error!");
-        return NULL;
-    }
-    
-    part = sdssplitlen(reply->str, reply->len, " ", 1, &part_len);
-
-    if(part != NULL && part_len == 3)
-    {
-        ip_port = sdssplitlen(part[2], sdslen(part[2]), 
-            ":", 1, &ip_port_len);
-
-        if(ip_port != NULL && ip_port_len == 2)
-        {
-            de = dictFind(cc->nodes, part[2]);
-            if(de == NULL)
-            {
-                node = hi_alloc(sizeof(cluster_node));
-                if(node == NULL)
-                {
-                    __redisClusterSetError(cc, 
-                        REDIS_ERR_OOM, "Out of memory");
-
-                    goto done;
-                }
-
-                cluster_node_init(node);
-                node->addr = part[1];
-                node->host = ip_port[0];
-                node->port = hi_atoi(ip_port[1], sdslen(ip_port[1]));
-                node->role = REDIS_ROLE_MASTER;
-
-                dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node);
-                
-                part = NULL;
-                ip_port = NULL;
-            }
-            else
-            {
-                node = de->val;
-
-                goto done;
-            }
-        }
-        else
-        {
-            __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                "ask error reply address part parse error!");
-
-            goto done;
-        }
-
-    }
-    else
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-            "ask error reply parse error!");
-
-        goto done;
-    }
-
-done:
-
-    if(part != NULL)
-    {
-        sdsfreesplitres(part, part_len);
-        part = NULL;
-    }
-
-    if(ip_port != NULL)
-    {
-        sdsfreesplitres(ip_port, ip_port_len);
-        ip_port = NULL;
-    }
-    
-    return node;
-}
-
-static void *redis_cluster_command_execute(redisClusterContext *cc, 
-    struct cmd *command)
-{
-    int ret;
-    void *reply = NULL;
-    cluster_node *node;
-    redisContext *c = NULL;
-    int error_type;
-
-retry:
-    
-    node = node_get_by_table(cc, (uint32_t)command->slot_num);
-    if(node == NULL)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error");
-        return NULL;
-    }
-
-    c = ctx_get_by_node(node, cc->timeout, cc->flags);
-    if(c == NULL)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null");
-        return NULL;
-    }
-    else if(c->err)
-    {
-        node = node_get_witch_connected(cc);
-        if(node == NULL)
-        {
-            __redisClusterSetError(cc, REDIS_ERR_OTHER, "no reachable node in cluster");
-            return NULL;
-        }
-
-        cc->retry_count ++;
-        if(cc->retry_count > cc->max_redirect_count)
-        {
-            __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, 
-                "too many cluster redirect");
-            return NULL;
-        }
-
-        c = ctx_get_by_node(node, cc->timeout, cc->flags);
-        if(c == NULL)
-        {
-            __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error");
-            return NULL;
-        }
-        else if(c->err)
-        {
-            __redisClusterSetError(cc, c->err, c->errstr);
-            return NULL;
-        }
-    }
-
-ask_retry:
-
-    if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) 
-    {
-        __redisClusterSetError(cc, c->err, c->errstr);
-        return NULL;
-    }
-    
-    reply = __redisBlockForReply(c);
-    if(reply == NULL)
-    {
-        __redisClusterSetError(cc, c->err, c->errstr);
-        return NULL;
-    }
-
-    error_type = cluster_reply_error_type(reply);
-    if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL)
-    {
-        cc->retry_count ++;
-        if(cc->retry_count > cc->max_redirect_count)
-        {
-            __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, 
-                "too many cluster redirect");
-            freeReplyObject(reply);
-            return NULL;
-        }
-        
-        switch(error_type)
-        {
-        case CLUSTER_ERR_MOVED:
-            freeReplyObject(reply);
-            reply = NULL;
-            ret = cluster_update_route(cc);
-            if(ret != REDIS_OK)
-            {
-                __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                    "route update error, please recreate redisClusterContext!");
-                return NULL;
-            }
-            
-            goto retry;
-            
-            break;
-        case CLUSTER_ERR_ASK:
-            node = node_get_by_ask_error_reply(cc, reply);
-            if(node == NULL)
-            {
-                freeReplyObject(reply);
-                return NULL;
-            }
-
-            freeReplyObject(reply);
-            reply = NULL;
-
-            c = ctx_get_by_node(node, cc->timeout, cc->flags);
-            if(c == NULL)
-            {
-                __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error");
-                return NULL;
-            }
-            else if(c->err)
-            {
-                __redisClusterSetError(cc, c->err, c->errstr);
-                return NULL;
-            }
-
-            reply = redisCommand(c, REDIS_COMMAND_ASKING);
-            if(reply == NULL)
-            {
-                __redisClusterSetError(cc, c->err, c->errstr);
-                return NULL;
-            }
-
-            freeReplyObject(reply);
-            reply = NULL;
-            
-            goto ask_retry;
-
-            break;
-        case CLUSTER_ERR_TRYAGAIN:
-        case CLUSTER_ERR_CROSSSLOT:
-        case CLUSTER_ERR_CLUSTERDOWN:
-            freeReplyObject(reply);
-            reply = NULL;
-            goto retry;
-            
-            break;
-        default:
-
-            break;
-        }
-    }
-    
-    return reply;
-}
-
-static int command_pre_fragment(redisClusterContext *cc, 
-    struct cmd *command, hilist *commands)
-{
-    
-    struct keypos *kp, *sub_kp;
-    uint32_t key_count;
-    uint32_t i, j;
-    uint32_t idx;
-    uint32_t key_len;
-    int slot_num = -1;
-    struct cmd *sub_command;
-    struct cmd **sub_commands = NULL;
-    char num_str[12];
-    uint8_t num_str_len;
-    
-
-    if(command == NULL || commands == NULL)
-    {
-        goto done;
-    }
-
-    key_count = hiarray_n(command->keys);
-
-    sub_commands = hi_zalloc(REDIS_CLUSTER_SLOTS * sizeof(*sub_commands));
-    if (sub_commands == NULL) 
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        goto done;
-    }
-
-    command->frag_seq = hi_alloc(key_count * sizeof(*command->frag_seq));
-    if(command->frag_seq == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        goto done;
-    }
-    
-    
-    for(i = 0; i < key_count; i ++)
-    {
-        kp = hiarray_get(command->keys, i);
-
-        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
-
-        if(slot_num < 0 || slot_num >= REDIS_CLUSTER_SLOTS)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,"keyHashSlot return error");
-            goto done;
-        }
-
-        if (sub_commands[slot_num] == NULL) {
-            sub_commands[slot_num] = command_get();
-            if (sub_commands[slot_num] == NULL) {
-                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-                slot_num = -1;
-                goto done;
-            }
-        }
-
-        command->frag_seq[i] = sub_command = sub_commands[slot_num];
-
-        sub_command->narg++;
-
-        sub_kp = hiarray_push(sub_command->keys);
-        if (sub_kp == NULL) {
-            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-            slot_num = -1;
-            goto done;
-        }
-        
-        sub_kp->start = kp->start;
-        sub_kp->end = kp->end;
-
-        key_len = (uint32_t)(kp->end - kp->start);
-
-        sub_command->clen += key_len + uint_len(key_len);
-
-        sub_command->slot_num = slot_num;
-
-        if (command->type == CMD_REQ_REDIS_MSET) {
-            uint32_t len = 0;
-            char *p;
-
-            for (p = sub_kp->end + 1; !isdigit(*p); p++){}
-            
-            p = sub_kp->end + 1;
-            while(!isdigit(*p))
-            {
-                p ++;
-            }
-
-            for (; isdigit(*p); p++) {              
-                len = len * 10 + (uint32_t)(*p - '0');
-            }
-            
-            len += CRLF_LEN * 2;
-            len += (p - sub_kp->end);
-            sub_kp->remain_len = len;
-            sub_command->clen += len;
-        }
-    }
-
-    for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) {     /* prepend command header */
-        sub_command = sub_commands[i];
-        if (sub_command == NULL) {
-            continue;
-        }
-
-        idx = 0;            
-        if (command->type == CMD_REQ_REDIS_MGET) {
-            //"*%d\r\n$4\r\nmget\r\n"
-            
-            sub_command->clen += 5*sub_command->narg;
-
-            sub_command->narg ++;
-
-            hi_itoa(num_str, sub_command->narg);
-            num_str_len = (uint8_t)(strlen(num_str));
-
-            sub_command->clen += 13 + num_str_len;
-
-            sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd));
-            if(sub_command->cmd == NULL)
-            {
-                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-                slot_num = -1;
-                goto done;
-            }
-
-            sub_command->cmd[idx++] = '*';
-            memcpy(sub_command->cmd + idx, num_str, num_str_len);
-            idx += num_str_len;
-            memcpy(sub_command->cmd + idx, "\r\n$4\r\nmget\r\n", 12);
-            idx += 12;
-            
-            for(j = 0; j < hiarray_n(sub_command->keys); j ++)
-            {
-                kp = hiarray_get(sub_command->keys, j);
-                key_len = (uint32_t)(kp->end - kp->start);
-                hi_itoa(num_str, key_len);
-                num_str_len = strlen(num_str);
-
-                sub_command->cmd[idx++] = '$';
-                memcpy(sub_command->cmd + idx, num_str, num_str_len);
-                idx += num_str_len;
-                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
-                idx += CRLF_LEN;
-                memcpy(sub_command->cmd + idx, kp->start, key_len);
-                idx += key_len;
-                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
-                idx += CRLF_LEN;
-            }
-        } else if (command->type == CMD_REQ_REDIS_DEL) {
-            //"*%d\r\n$3\r\ndel\r\n"
-            
-            sub_command->clen += 5*sub_command->narg;
-
-            sub_command->narg ++;
-
-            hi_itoa(num_str, sub_command->narg);
-            num_str_len = (uint8_t)strlen(num_str);
-            
-            sub_command->clen += 12 + num_str_len;
-
-            sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd));
-            if(sub_command->cmd == NULL)
-            {
-                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-                slot_num = -1;
-                goto done;
-            }
-
-            sub_command->cmd[idx++] = '*';
-            memcpy(sub_command->cmd + idx, num_str, num_str_len);
-            idx += num_str_len;
-            memcpy(sub_command->cmd + idx, "\r\n$3\r\ndel\r\n", 11);
-            idx += 11;
-
-            for(j = 0; j < hiarray_n(sub_command->keys); j ++)
-            {
-                kp = hiarray_get(sub_command->keys, j);
-                key_len = (uint32_t)(kp->end - kp->start);
-                hi_itoa(num_str, key_len);
-                num_str_len = strlen(num_str);
-
-                sub_command->cmd[idx++] = '$';
-                memcpy(sub_command->cmd + idx, num_str, num_str_len);
-                idx += num_str_len;
-                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
-                idx += CRLF_LEN;
-                memcpy(sub_command->cmd + idx, kp->start, key_len);
-                idx += key_len;
-                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
-                idx += CRLF_LEN;
-            }
-        } else if (command->type == CMD_REQ_REDIS_MSET) {
-            //"*%d\r\n$4\r\nmset\r\n"
-            
-            sub_command->clen += 3*sub_command->narg;
-
-            sub_command->narg *= 2;
-
-            sub_command->narg ++;
-
-            hi_itoa(num_str, sub_command->narg);
-            num_str_len = (uint8_t)strlen(num_str);
-        
-            sub_command->clen += 13 + num_str_len;
-
-            sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd));
-            if(sub_command->cmd == NULL)
-            {
-                __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-                slot_num = -1;
-                goto done;
-            }
-
-            sub_command->cmd[idx++] = '*';
-            memcpy(sub_command->cmd + idx, num_str, num_str_len);
-            idx += num_str_len;
-            memcpy(sub_command->cmd + idx, "\r\n$4\r\nmset\r\n", 12);
-            idx += 12;
-            
-            for(j = 0; j < hiarray_n(sub_command->keys); j ++)
-            {
-                kp = hiarray_get(sub_command->keys, j);
-                key_len = (uint32_t)(kp->end - kp->start);
-                hi_itoa(num_str, key_len);
-                num_str_len = strlen(num_str);
-
-                sub_command->cmd[idx++] = '$';
-                memcpy(sub_command->cmd + idx, num_str, num_str_len);
-                idx += num_str_len;
-                memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN);
-                idx += CRLF_LEN;
-                memcpy(sub_command->cmd + idx, kp->start, key_len + kp->remain_len);
-                idx += key_len + kp->remain_len;
-                
-            }
-        } else {
-            NOT_REACHED();
-        }
-
-        //printf("len : %d\n", sub_command->clen);
-        //print_string_with_length_fix_CRLF(sub_command->cmd, sub_command->clen);
-        
-        sub_command->type = command->type;
-
-        listAddNodeTail(commands, sub_command);
-    }
-
-done:
-
-    if(sub_commands != NULL)
-    {
-        hi_free(sub_commands);
-    }
-
-    if(slot_num >= 0 && commands != NULL 
-        && listLength(commands) == 1)
-    {
-        listNode *list_node = listFirst(commands);
-        listDelNode(commands, list_node);
-        if(command->frag_seq)
-        {
-            hi_free(command->frag_seq);
-            command->frag_seq = NULL;
-        }
-
-        command->slot_num = slot_num;
-    }
-
-    return slot_num;
-}
-
-static void *command_post_fragment(redisClusterContext *cc, 
-    struct cmd *command, hilist *commands)
-{
-    struct cmd *sub_command;
-    listNode *list_node;
-    listIter *list_iter;
-    redisReply *reply, *sub_reply;
-    long long count = 0;
-    
-    list_iter = listGetIterator(commands, AL_START_HEAD);
-    while((list_node = listNext(list_iter)) != NULL)
-    {
-        sub_command = list_node->value;
-        reply = sub_command->reply;
-        if(reply == NULL)
-        {
-            return NULL;
-        }
-        else if(reply->type == REDIS_REPLY_ERROR)
-        {
-            return reply;
-        }
-
-        if (command->type == CMD_REQ_REDIS_MGET) {
-            if(reply->type != REDIS_REPLY_ARRAY)
-            {
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be array)");
-                return NULL;
-            }
-        }else if(command->type == CMD_REQ_REDIS_DEL){
-            if(reply->type != REDIS_REPLY_INTEGER)
-            {
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be integer)");
-                return NULL;
-            }
-
-            count += reply->integer;
-        }else if(command->type == CMD_REQ_REDIS_MSET){
-            if(reply->type != REDIS_REPLY_STATUS ||
-                reply->len != 2 || strcmp(reply->str, REDIS_STATUS_OK) != 0)
-            {
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be status and ok)");
-                return NULL;
-            }
-        }else {
-            NOT_REACHED();
-        }
-    }
-
-    reply = hi_calloc(1,sizeof(*reply));
-
-    if (reply == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        return NULL;
-    }
-
-    if (command->type == CMD_REQ_REDIS_MGET) {
-        int i;
-        uint32_t key_count;
-
-        reply->type = REDIS_REPLY_ARRAY;
-
-        key_count = hiarray_n(command->keys);
-
-        reply->elements = key_count;
-        reply->element = hi_calloc(key_count, sizeof(*reply));
-        if (reply->element == NULL) {
-            freeReplyObject(reply);
-            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-            return NULL;
-        }
-            
-        for (i = key_count - 1; i >= 0; i--) {      /* for each key */
-            sub_reply = command->frag_seq[i]->reply;            /* get it's reply */
-            if (sub_reply == NULL) {
-                freeReplyObject(reply);
-                __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply is null");
-                return NULL;
-            }
-
-            if(sub_reply->type == REDIS_REPLY_STRING)
-            {
-                reply->element[i] = sub_reply;
-            }
-            else if(sub_reply->type == REDIS_REPLY_ARRAY)
-            {
-                if(sub_reply->elements == 0)
-                {
-                    freeReplyObject(reply);
-                    __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply elements error");
-                    return NULL;
-                }
-                
-                reply->element[i] = sub_reply->element[sub_reply->elements - 1];
-                sub_reply->elements --;
-            }
-        }
-    }else if(command->type == CMD_REQ_REDIS_DEL){
-        reply->type = REDIS_REPLY_INTEGER;
-        reply->integer = count;
-    }else if(command->type == CMD_REQ_REDIS_MSET){
-        reply->type = REDIS_REPLY_STATUS;
-        uint32_t str_len = strlen(REDIS_STATUS_OK);
-        reply->str = hi_alloc((str_len + 1) * sizeof(char*));
-        if(reply->str == NULL)
-        {
-            freeReplyObject(reply);
-            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-            return NULL;
-        }
-
-        reply->len = str_len;
-        memcpy(reply->str, REDIS_STATUS_OK, str_len);
-        reply->str[str_len] = '\0';
-    }else {
-        NOT_REACHED();
-    }
-
-    return reply;
-}
-
-/* 
- * Split the command into subcommands by slot
- * 
- * Returns slot_num
- * If slot_num < 0 or slot_num >=  REDIS_CLUSTER_SLOTS means this function runs error;
- * Otherwise if  the commands > 1 , slot_num is the last subcommand slot number. 
- */
-static int command_format_by_slot(redisClusterContext *cc, 
-    struct cmd *command, hilist *commands)
-{
-    struct keypos *kp;
-    int key_count;
-    int slot_num = -1;
-
-    if(cc == NULL || commands == NULL ||
-        command == NULL || 
-        command->cmd == NULL || command->clen <= 0)
-    {
-        goto done;
-    }
-
-    
-    redis_parse_cmd(command);
-    if(command->result == CMD_PARSE_ENOMEM)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "Parse command error: out of memory");
-        goto done;
-    }
-    else if(command->result != CMD_PARSE_OK)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, command->errstr);
-        goto done;
-    }
-
-    key_count = hiarray_n(command->keys);
-
-    if(key_count <= 0)
-    {
-        __redisClusterSetError(cc, REDIS_ERR_OTHER, "No keys in command(must have keys for redis cluster mode)");
-        goto done;
-    }
-    else if(key_count == 1)
-    {
-        kp = hiarray_get(command->keys, 0);
-        slot_num = keyHashSlot(kp->start, kp->end - kp->start);
-        command->slot_num = slot_num;
-
-        goto done;
-    }
-
-    slot_num = command_pre_fragment(cc, command, commands);
-
-done:
-    
-    return slot_num;
-}
-
-
-void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count)
-{
-    if(cc == NULL || max_redirect_count <= 0)
-    {
-        return;
-    }
-
-    cc->max_redirect_count = max_redirect_count;
-}
-
-void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) {
-    redisReply *reply = NULL;
-    int slot_num;
-    struct cmd *command = NULL, *sub_command;
-    hilist *commands = NULL;
-    listNode *list_node;
-    listIter *list_iter = NULL;
-
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    if(cc->err)
-    {
-        cc->err = 0;
-        memset(cc->errstr, '\0', strlen(cc->errstr));
-    }  
-    
-    command = command_get();
-    if(command == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        return NULL;
-    }
-    
-    command->cmd = cmd;
-    command->clen = len;
-
-    commands = listCreate();
-    if(commands == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-
-    commands->free = listCommandFree;
-
-    slot_num = command_format_by_slot(cc, command, commands);
-
-    if(slot_num < 0)
-    {
-        goto error;
-    }
-    else if(slot_num >= REDIS_CLUSTER_SLOTS)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range");
-        goto error;
-    }
-
-    //all keys belong to one slot
-    if(listLength(commands) == 0)
-    {
-        reply = redis_cluster_command_execute(cc, command);
-        goto done;
-    }
-
-    ASSERT(listLength(commands) != 1);
-
-    list_iter = listGetIterator(commands, AL_START_HEAD);
-    while((list_node = listNext(list_iter)) != NULL)
-    {
-        sub_command = list_node->value;
-        
-        reply = redis_cluster_command_execute(cc, sub_command);
-        if(reply == NULL)
-        {
-            goto error;
-        }
-        else if(reply->type == REDIS_REPLY_ERROR)
-        {
-            goto done;
-        }
-
-        sub_command->reply = reply;
-    }
-
-    reply = command_post_fragment(cc, command, commands);
-    
-done:
-
-    command->cmd = NULL;
-    command_destroy(command);
-
-    if(commands != NULL)
-    {
-        listRelease(commands);
-    }
-
-    if(list_iter != NULL)
-    {
-        listReleaseIterator(list_iter);
-    }
-
-    cc->retry_count = 0;
-    
-    return reply;
-
-error:
-
-    if(command != NULL)
-    {
-        command->cmd = NULL;
-        command_destroy(command);
-    }
-
-    if(commands != NULL)
-    {
-        listRelease(commands);
-    }
-
-    if(list_iter != NULL)
-    {
-        listReleaseIterator(list_iter);
-    }
-
-    cc->retry_count = 0;
-    
-    return NULL;
-}
-
-void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) {
-    redisReply *reply;
-    char *cmd;
-    int len;
-
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    len = redisvFormatCommand(&cmd,format,ap);
-
-    if (len == -1) {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        return NULL;
-    } else if (len == -2) {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string");
-        return NULL;
-    }   
-
-    reply = redisClusterFormattedCommand(cc, cmd, len);
-
-    free(cmd);
-
-    return reply;
-}
-
-void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) {
-    va_list ap;
-    redisReply *reply = NULL;
-    
-    va_start(ap,format);
-    reply = redisClustervCommand(cc, format, ap);
-    va_end(ap);
-
-    return reply;
-}
-
-void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen) {
-    redisReply *reply = NULL;
-    char *cmd;
-    int len;
-
-    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
-    if (len == -1) {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        return NULL;
-    }
-	
-    reply = redisClusterFormattedCommand(cc, cmd, len);
-
-    free(cmd);
-
-    return reply;
-}
-
-int redisClusterAppendFormattedCommand(redisClusterContext *cc, 
-    char *cmd, int len) {
-    int slot_num;
-    struct cmd *command = NULL, *sub_command;
-    hilist *commands = NULL;
-    listNode *list_node;
-    listIter *list_iter = NULL;
-
-    if(cc->requests == NULL)
-    {
-        cc->requests = listCreate();
-        if(cc->requests == NULL)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-            goto error;
-        }
-
-        cc->requests->free = listCommandFree;
-    }
-    
-    command = command_get();
-    if(command == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-    
-    command->cmd = cmd;
-    command->clen = len;
-
-    commands = listCreate();
-    if(commands == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-
-    commands->free = listCommandFree;
-
-    slot_num = command_format_by_slot(cc, command, commands);
-
-    if(slot_num < 0)
-    {
-        goto error;
-    }
-    else if(slot_num >= REDIS_CLUSTER_SLOTS)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range");
-        goto error;
-    }
-
-    //all keys belong to one slot
-    if(listLength(commands) == 0)
-    {
-        if(__redisClusterAppendCommand(cc, command) == REDIS_OK)
-        {
-            goto done;
-        }
-        else
-        {
-            goto error;
-        }
-    }
-
-    ASSERT(listLength(commands) != 1);
-
-    list_iter = listGetIterator(commands, AL_START_HEAD);
-    while((list_node = listNext(list_iter)) != NULL)
-    {
-        sub_command = list_node->value;
-        
-        if(__redisClusterAppendCommand(cc, sub_command) == REDIS_OK)
-        {
-            continue;
-        }
-        else
-        {
-            goto error;
-        }
-    }
-
-done:
-
-    if(command->cmd != NULL)
-    {
-        command->cmd = NULL;
-    }
-    else
-    {
-        goto error;
-    }
-
-    if(commands != NULL)
-    {
-        if(listLength(commands) > 0)
-        {
-            command->sub_commands = commands;
-        }
-        else
-        {
-            listRelease(commands);
-        }
-    }
-
-    if(list_iter != NULL)
-    {
-        listReleaseIterator(list_iter);
-    }
-
-    listAddNodeTail(cc->requests, command);
-    
-    return REDIS_OK;
-
-error:
-
-    if(command != NULL)
-    {
-        command->cmd = NULL;
-        command_destroy(command);
-    }
-
-    if(commands != NULL)
-    {
-        listRelease(commands);
-    }
-
-    if(list_iter != NULL)
-    {
-        listReleaseIterator(list_iter);
-    }
-
-    /* Attention: mybe here we must pop the 
-      sub_commands that had append to the nodes.  
-      But now we do not handle it. */
-    
-    return REDIS_ERR;
-}
-
-
-int redisClustervAppendCommand(redisClusterContext *cc, 
-    const char *format, va_list ap) {
-    int ret;
-    char *cmd;
-    int len;
-    
-    len = redisvFormatCommand(&cmd,format,ap);  
-    if (len == -1) {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        return REDIS_ERR;
-    } else if (len == -2) {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string");
-        return REDIS_ERR;
-    }   
-
-    ret = redisClusterAppendFormattedCommand(cc, cmd, len);
-
-    free(cmd);
-
-    return ret;
-}
-
-int redisClusterAppendCommand(redisClusterContext *cc, 
-    const char *format, ...) {
-
-    int ret;
-    va_list ap;
-
-    if(cc == NULL || format == NULL)
-    {
-        return REDIS_ERR;
-    }
-    
-    va_start(ap,format);
-    ret = redisClustervAppendCommand(cc, format, ap);
-    va_end(ap);
-
-    return ret;
-}
-
-int redisClusterAppendCommandArgv(redisClusterContext *cc, 
-    int argc, const char **argv, const size_t *argvlen) {
-    int ret;
-    char *cmd;
-    int len;
-
-    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
-    if (len == -1) {
-        __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory");
-        return REDIS_ERR;
-    }
-    
-    ret = redisClusterAppendFormattedCommand(cc, cmd, len);
-    
-    free(cmd);
-
-    return ret;
-}
-
-static int redisCLusterSendAll(redisClusterContext *cc)
-{
-    dictIterator *di;
-    dictEntry *de;
-    struct cluster_node *node;
-    redisContext *c = NULL;
-    int wdone = 0;
-    
-    if(cc == NULL || cc->nodes == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    di = dictGetIterator(cc->nodes);
-    while((de = dictNext(di)) != NULL)
-    {
-        node = dictGetEntryVal(de);
-        if(node == NULL)
-        {
-            continue;
-        }
-        
-        c = ctx_get_by_node(node, cc->timeout, cc->flags);
-        if(c == NULL)
-        {
-            continue;
-        }
-
-        if (c->flags & REDIS_BLOCK) {
-            /* Write until done */
-            do {
-                if (redisBufferWrite(c,&wdone) == REDIS_ERR)
-                {
-                    dictReleaseIterator(di);
-                    return REDIS_ERR;
-                }
-            } while (!wdone);
-        }
-    }
-    
-    dictReleaseIterator(di);
-
-    return REDIS_OK;
-}
-
-static int redisCLusterClearAll(redisClusterContext *cc)
-{
-    dictIterator *di;
-    dictEntry *de;
-    struct cluster_node *node;
-    redisContext *c = NULL;
-    
-    if (cc == NULL) {
-        return REDIS_ERR;
-    }
-
-    if (cc->err) {
-        cc->err = 0;
-        memset(cc->errstr, '\0', strlen(cc->errstr));
-    }
-
-    if (cc->nodes == NULL) {
-        return REDIS_ERR;
-    }
-    di = dictGetIterator(cc->nodes);
-    while((de = dictNext(di)) != NULL)
-    {
-        node = dictGetEntryVal(de);
-        if(node == NULL)
-        {
-            continue;
-        }
-
-        c = node->con;
-        if(c == NULL)
-        {
-            continue;
-        }
-
-        redisFree(c);
-        node->con = NULL;
-    }
-    
-    dictReleaseIterator(di);
-    
-    return REDIS_OK;
-}
-
-int redisClusterGetReply(redisClusterContext *cc, void **reply) {
-
-    struct cmd *command, *sub_command;
-    hilist *commands = NULL;
-    listNode *list_command, *list_sub_command;
-    listIter *list_iter;
-    int slot_num;
-    void *sub_reply;
-
-    if(cc == NULL || reply == NULL)
-        return REDIS_ERR;
-
-    cc->err = 0;
-    cc->errstr[0] = '\0';
-
-    *reply = NULL;
-
-    if (cc->requests == NULL)
-        return REDIS_ERR;
-
-    list_command = listFirst(cc->requests);
-
-    //no more reply
-    if(list_command == NULL)
-    {
-        *reply = NULL;
-        return REDIS_OK;
-    }
-    
-    command = list_command->value;
-    if(command == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "command in the requests list is null");
-        goto error;
-    }
-    
-    slot_num = command->slot_num;
-    if(slot_num >= 0)
-    {
-        listDelNode(cc->requests, list_command);
-        return __redisClusterGetReply(cc, slot_num, reply);
-    }
-
-    commands = command->sub_commands;
-    if(commands == NULL)
-    {
-        __redisClusterSetError(cc,REDIS_ERR_OTHER,
-            "sub_commands in command is null");
-        goto error;
-    }
-
-    ASSERT(listLength(commands) != 1);
-
-    list_iter = listGetIterator(commands, AL_START_HEAD);
-    while((list_sub_command = listNext(list_iter)) != NULL)
-    {
-        sub_command = list_sub_command->value;
-        if(sub_command == NULL)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "sub_command is null");
-            goto error;
-        }
-        
-        slot_num = sub_command->slot_num;
-        if(slot_num < 0)
-        {
-            __redisClusterSetError(cc,REDIS_ERR_OTHER,
-                "sub_command slot_num is less then zero");
-            goto error;
-        }
-        
-        if(__redisClusterGetReply(cc, slot_num, &sub_reply) != REDIS_OK)
-        {
-            goto error;
-        }
-
-        sub_command->reply = sub_reply;
-    }
-
-    *reply = command_post_fragment(cc, command, commands);
-    if(*reply == NULL)
-    {
-        goto error;
-    }
-
-    listDelNode(cc->requests, list_command);
-    return REDIS_OK;
-
-error:
-
-    listDelNode(cc->requests, list_command);
-    return REDIS_ERR;
-}
-
-void redisClusterReset(redisClusterContext *cc)
-{
-    int status;
-    void *reply;
-    
-    if(cc == NULL || cc->nodes == NULL)
-    {
-        return;
-    }
-
-    if (cc->err) {
-        redisCLusterClearAll(cc);
-    } else {
-        redisCLusterSendAll(cc);
-        
-        do {
-            status = redisClusterGetReply(cc, &reply);
-            if (status == REDIS_OK) {
-                freeReplyObject(reply);
-            } else {
-                redisCLusterClearAll(cc);
-                break;
-            }
-        } while(reply != NULL);
-    }
-    
-    if(cc->requests)
-    {
-        listRelease(cc->requests);
-        cc->requests = NULL;
-    }
-
-    if(cc->need_update_route)
-    {
-        status = cluster_update_route(cc);
-        if(status != REDIS_OK)
-        {
-            __redisClusterSetError(cc, REDIS_ERR_OTHER, 
-                "route update error, please recreate redisClusterContext!");
-            return;
-        }
-        cc->need_update_route = 0;
-    }
-}
-
-/*############redis cluster async############*/
-
-/* We want the error field to be accessible directly instead of requiring
- * an indirection to the redisContext struct. */
-static void __redisClusterAsyncCopyError(redisClusterAsyncContext *acc) {
-    if (!acc)
-        return;
-
-    redisClusterContext *cc = acc->cc;
-    acc->err = cc->err;
-    memcpy(acc->errstr, cc->errstr, 128);
-}
-
-static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, 
-    int type, const char *str) {
-    
-    size_t len;
-
-    acc->err = type;
-    if (str != NULL) {
-        len = strlen(str);
-        len = len < (sizeof(acc->errstr)-1) ? len : (sizeof(acc->errstr)-1);
-        memcpy(acc->errstr,str,len);
-        acc->errstr[len] = '\0';
-    } else {
-        /* Only REDIS_ERR_IO may lack a description! */
-        assert(type == REDIS_ERR_IO);
-        __redis_strerror_r(errno, acc->errstr, sizeof(acc->errstr));
-    }
-}
-
-static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) {
-    redisClusterAsyncContext *acc;
-
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    acc = hi_alloc(sizeof(redisClusterAsyncContext));
-    if (acc == NULL)
-        return NULL;
-
-    acc->cc = cc;
-
-    acc->err = 0;
-    acc->data = NULL;
-    acc->adapter = NULL;
-    acc->attach_fn = NULL;
-
-    acc->onConnect = NULL;
-    acc->onDisconnect = NULL;
-
-    return acc;
-}
-
-static cluster_async_data *cluster_async_data_get(void)
-{
-    cluster_async_data *cad;
-
-    cad = hi_alloc(sizeof(cluster_async_data));
-    if(cad == NULL)
-    {
-        return NULL;
-    }
-
-    cad->acc = NULL;
-    cad->command = NULL;
-    cad->callback = NULL;
-    cad->privdata = NULL;
-    cad->retry_count = 0;
-
-    return cad;
-}
-
-static void cluster_async_data_free(cluster_async_data *cad)
-{
-    if(cad == NULL)
-    {
-        return;
-    }
-
-    if(cad->command != NULL)
-    {
-        command_destroy(cad->command);
-    }
-    
-    hi_free(cad);
-    cad = NULL;
-}
-
-static void unlinkAsyncContextAndNode(redisAsyncContext* ac)
-{
-    cluster_node *node;
-
-    if (ac->data) {
-        node = (cluster_node *)(ac->data);
-        node->acon = NULL;
-    }
-}
-
-redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, 
-    cluster_node *node)
-{
-    redisAsyncContext *ac;
-    
-    if(node == NULL)
-    {
-        return NULL;
-    }
-
-    ac = node->acon;
-    if(ac != NULL)
-    {
-        if (ac->c.err == 0) {
-            return ac;
-        } else {
-            NOT_REACHED();
-        }
-    }
-
-    if(node->host == NULL || node->port <= 0)
-    {
-        __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error");
-        return NULL;
-    }
-
-    ac = redisAsyncConnect(node->host, node->port);
-    if(ac == NULL)
-    {
-        __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error");
-        return NULL;
-    }
-
-    if(acc->adapter)
-    {
-        acc->attach_fn(ac, acc->adapter);
-    }
-
-    if(acc->onConnect)
-    {
-        redisAsyncSetConnectCallback(ac, acc->onConnect);
-    }
-
-    if(acc->onDisconnect)
-    {
-        redisAsyncSetDisconnectCallback(ac, acc->onDisconnect);
-    }
-
-    ac->data = node;
-    ac->dataHandler = unlinkAsyncContextAndNode;
-    node->acon = ac;
-    
-    return ac;
-}
-
-static redisAsyncContext *actx_get_after_update_route_by_slot(
-    redisClusterAsyncContext *acc, int slot_num)
-{
-    int ret;
-    redisClusterContext *cc;
-    redisAsyncContext *ac;
-    cluster_node *node;
-
-    if(acc == NULL || slot_num < 0)
-    {
-        return NULL;
-    }
-
-    cc = acc->cc;
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-    
-    ret = cluster_update_route(cc);
-    if(ret != REDIS_OK)
-    {
-        __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, 
-            "route update error, please recreate redisClusterContext!");
-        return NULL;
-    }
-
-    node = node_get_by_table(cc, (uint32_t)slot_num);
-    if(node == NULL)
-    {
-        __redisClusterAsyncSetError(acc, 
-            REDIS_ERR_OTHER, "node get by table error");
-        return NULL;
-    }
-
-    ac = actx_get_by_node(acc, node);
-    if(ac == NULL)
-    {
-        __redisClusterAsyncSetError(acc, 
-            REDIS_ERR_OTHER, "actx get by node error");
-        return NULL;
-    }
-    else if(ac->err)
-    {
-        __redisClusterAsyncSetError(acc, ac->err, ac->errstr);
-        return NULL;
-    }
-
-    return ac;
-}
-
-redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) {
-
-    redisClusterContext *cc;
-    redisClusterAsyncContext *acc;
-
-    cc = redisClusterConnectNonBlock(addrs, flags);
-    if(cc == NULL)
-    {
-        return NULL;
-    }
-
-    acc = redisClusterAsyncInitialize(cc);
-    if (acc == NULL) {
-        redisClusterFree(cc);
-        return NULL;
-    }
-    
-    __redisClusterAsyncCopyError(acc);
-    
-    return acc;
-}
-
-
-int redisClusterAsyncSetConnectCallback(
-    redisClusterAsyncContext *acc, redisConnectCallback *fn) 
-{    
-    if (acc->onConnect == NULL) {
-        acc->onConnect = fn;
-        return REDIS_OK;
-    }
-    return REDIS_ERR;
-}
-
-int redisClusterAsyncSetDisconnectCallback(
-    redisClusterAsyncContext *acc, redisDisconnectCallback *fn)
-{
-    if (acc->onDisconnect == NULL) {
-        acc->onDisconnect = fn;
-        return REDIS_OK;
-    }
-    return REDIS_ERR;
-}
-
-static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *privdata) {
-    int ret;
-    redisReply *reply = r;
-    cluster_async_data *cad = privdata;
-    redisClusterAsyncContext *acc;
-    redisClusterContext *cc;
-    redisAsyncContext *ac_retry = NULL;
-    int error_type;
-    cluster_node *node;
-    struct cmd *command;
-    int64_t now, next;
-
-    if(cad == NULL)
-    {
-        goto error;
-    }
-
-    acc = cad->acc;
-    if(acc == NULL)
-    {
-        goto error;
-    }
-
-    cc = acc->cc;
-    if(cc == NULL)
-    {
-        goto error;
-    }
-
-    command = cad->command;
-    if(command == NULL)
-    {
-        goto error;
-    }
-    
-    if(reply == NULL)
-    {
-        //Note: 
-        //I can't decide witch is the best way to deal with connect 
-        //problem for hiredis cluster async api.
-        //But now the way is : when enough null reply for a node,
-        //we will update the route after the cluster node timeout.
-        //If you have a better idea, please contact with me. Thank you.
-        //My email: [email protected]
-        
-        node = (cluster_node *)(ac->data);
-        ASSERT(node != NULL);
-        
-        __redisClusterAsyncSetError(acc, 
-            ac->err, ac->errstr);
-        
-        if(cc->update_route_time != 0)
-        {
-            now = hi_usec_now();
-            if(now >= cc->update_route_time)
-            {
-                ret = cluster_update_route(cc);
-                if(ret != REDIS_OK)
-                {
-                    __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, 
-                        "route update error, please recreate redisClusterContext!");
-                }
-                
-                cc->update_route_time = 0LL;
-            }
-            
-            goto done;
-        }
-        
-        node->failure_count ++;
-        if(node->failure_count > cc->max_redirect_count)
-        {
-            char *cluster_timeout_str;
-            int cluster_timeout_str_len;
-            int cluster_timeout;
-
-            node->failure_count = 0;
-            if(cc->update_route_time != 0)
-            {
-                goto done;
-            }
-            
-            cluster_timeout_str = cluster_config_get(cc, 
-                "cluster-node-timeout", &cluster_timeout_str_len);
-            if(cluster_timeout_str == NULL)
-            {
-                __redisClusterAsyncSetError(acc, 
-                    cc->err, cc->errstr);
-                goto done;
-            }
-
-            cluster_timeout = hi_atoi(cluster_timeout_str, 
-                cluster_timeout_str_len);
-            free(cluster_timeout_str);
-            if(cluster_timeout <= 0)
-            {
-                __redisClusterAsyncSetError(acc, 
-                    REDIS_ERR_OTHER, 
-                    "cluster_timeout_str convert to integer error");
-                goto done;
-            }
-
-            now = hi_usec_now();
-            if (now < 0) {
-                __redisClusterAsyncSetError(acc, 
-                    REDIS_ERR_OTHER, 
-                    "get now usec time error");
-                goto done;
-            }
-
-            next = now + (cluster_timeout * 1000LL);
-
-            cc->update_route_time = next;
-            
-        }
-
-        goto done;
-    }
-
-    error_type = cluster_reply_error_type(reply);
-
-    if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL)
-    {
-        cad->retry_count ++;
-        if(cad->retry_count > cc->max_redirect_count)
-        {
-            cad->retry_count = 0;
-            __redisClusterAsyncSetError(acc, 
-                REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, 
-                "too many cluster redirect");
-            goto done;
-        }
-        
-        switch(error_type)
-        {
-        case CLUSTER_ERR_MOVED:
-            ac_retry = actx_get_after_update_route_by_slot(acc, command->slot_num);
-            if(ac_retry == NULL)
-            {
-                goto done;
-            }
-            
-            break;
-        case CLUSTER_ERR_ASK:
-            node = node_get_by_ask_error_reply(cc, reply);
-            if(node == NULL)
-            {
-                __redisClusterAsyncSetError(acc, 
-                    cc->err, cc->errstr);
-                goto done;
-            }
-
-            ac_retry = actx_get_by_node(acc, node);
-            if(ac_retry == NULL)
-            {
-                __redisClusterAsyncSetError(acc, 
-                    REDIS_ERR_OTHER, "actx get by node error");
-                goto done;
-            }
-            else if(ac_retry->err)
-            {
-                __redisClusterAsyncSetError(acc, 
-                    ac_retry->err, ac_retry->errstr);
-                goto done;
-            }
-
-            ret = redisAsyncCommand(ac_retry,
-                NULL,NULL,REDIS_COMMAND_ASKING);
-            if(ret != REDIS_OK)
-            {
-                goto error;
-            }
-            
-            break;
-        case CLUSTER_ERR_TRYAGAIN:
-        case CLUSTER_ERR_CROSSSLOT:
-        case CLUSTER_ERR_CLUSTERDOWN:
-            ac_retry = ac;
-            
-            break;
-        default:
-
-            goto done;
-            break;
-        }
-
-        goto retry;
-    }
-
-done:
-
-    if(acc->err)
-    {
-        cad->callback(acc, NULL, cad->privdata);
-    }
-    else
-    {
-        cad->callback(acc, r, cad->privdata);
-    }
-
-    if(cc->err)
-    {
-        cc->err = 0;
-        memset(cc->errstr, '\0', strlen(cc->errstr));
-    }
-
-    if(acc->err)
-    {
-        acc->err = 0;
-        memset(acc->errstr, '\0', strlen(acc->errstr));
-    }
-    
-    if(cad != NULL)
-    {
-        cluster_async_data_free(cad);
-    }
-
-    return;
-
-retry:
-
-    ret = redisAsyncFormattedCommand(ac_retry,
-        redisClusterAsyncCallback,cad,command->cmd,command->clen);
-    if(ret != REDIS_OK)
-    {
-        goto error;
-    }
-    
-    return;
-
-error:
-
-    if(cad != NULL)
-    {
-        cluster_async_data_free(cad);
-    }
-}
-
-int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, 
-    redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) {
-    
-    redisClusterContext *cc;
-    int status = REDIS_OK;
-    int slot_num;
-    cluster_node *node;
-    redisAsyncContext *ac;
-    struct cmd *command = NULL;
-    hilist *commands = NULL;
-    cluster_async_data *cad;
-
-    if(acc == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    cc = acc->cc;
-
-    if(cc->err)
-    {
-        cc->err = 0;
-        memset(cc->errstr, '\0', strlen(cc->errstr));
-    }
-
-    if(acc->err)
-    {
-        acc->err = 0;
-        memset(acc->errstr, '\0', strlen(acc->errstr));
-    }
-
-    command = command_get();
-    if(command == NULL)
-    {
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-    
-    command->cmd = malloc(len*sizeof(*command->cmd));
-    if(command->cmd == NULL)
-    {
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-    memcpy(command->cmd, cmd, len);
-    command->clen = len;
-
-    commands = listCreate();
-    if(commands == NULL)
-    {
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-
-    commands->free = listCommandFree;
-
-    slot_num = command_format_by_slot(cc, command, commands);
-
-    if(slot_num < 0)
-    {
-        __redisClusterAsyncSetError(acc,
-            cc->err, cc->errstr);
-        goto error;
-    }
-    else if(slot_num >= REDIS_CLUSTER_SLOTS)
-    {
-        __redisClusterAsyncSetError(acc,
-            REDIS_ERR_OTHER,"slot_num is out of range");
-        goto error;
-    }
-
-    //all keys not belong to one slot
-    if(listLength(commands) > 0)
-    {
-        ASSERT(listLength(commands) != 1);
-        
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,
-            "Asynchronous API now not support multi-key command");
-        goto error;
-    }
-
-    node = node_get_by_table(cc, (uint32_t) slot_num);
-    if(node == NULL)
-    {
-        __redisClusterAsyncSetError(acc, 
-            REDIS_ERR_OTHER, "node get by table error");
-        goto error;
-    }
-    
-    ac = actx_get_by_node(acc, node);
-    if(ac == NULL)
-    {
-        __redisClusterAsyncSetError(acc, 
-            REDIS_ERR_OTHER, "actx get by node error");
-        goto error;
-    }
-    else if(ac->err)
-    {
-        __redisClusterAsyncSetError(acc, ac->err, ac->errstr);
-        goto error;
-    }
-
-    cad = cluster_async_data_get();
-    if(cad == NULL)
-    {
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
-        goto error;
-    }
-
-    cad->acc = acc;
-    cad->command = command;
-    cad->callback = fn;
-    cad->privdata = privdata;
-    
-    status = redisAsyncFormattedCommand(ac,
-        redisClusterAsyncCallback,cad,cmd,len);
-    if(status != REDIS_OK)
-    {
-        goto error;
-    }
-
-    if(commands != NULL)
-    {
-        listRelease(commands);
-    }
-
-    return REDIS_OK;
-
-error: 
-    
-    if(command != NULL)
-    {
-        command_destroy(command);
-    }
-
-    if(commands != NULL)
-    {
-        listRelease(commands);
-    }
-
-    return REDIS_ERR;
-}
-
-
-int redisClustervAsyncCommand(redisClusterAsyncContext *acc, 
-    redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) {
-    int ret;
-    char *cmd;
-    int len;
-
-    if(acc == NULL)
-    {
-        return REDIS_ERR;
-    }
-
-    len = redisvFormatCommand(&cmd,format,ap);
-    if (len == -1) {
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
-        return REDIS_ERR;
-    } else if (len == -2) {
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string");
-        return REDIS_ERR;
-    }
-
-    ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len);
-
-    free(cmd);
-
-    return ret;
-}
-
-int redisClusterAsyncCommand(redisClusterAsyncContext *acc, 
-    redisClusterCallbackFn *fn, void *privdata, const char *format, ...) {
-    int ret;
-    va_list ap;
-
-    va_start(ap,format);
-    ret = redisClustervAsyncCommand(acc, fn, privdata, format, ap);
-    va_end(ap);
-
-    return ret;
-}
-
-int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, 
-    redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
-    int ret;
-    char *cmd;
-    int len;
-    
-    len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
-    if (len == -1) {
-        __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory");
-        return REDIS_ERR;
-    }
-
-    ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len);
-
-    free(cmd);
-
-    return ret;
-}
-
-void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) {
-
-    redisClusterContext *cc;
-    redisAsyncContext *ac;
-    dictIterator *di;
-    dictEntry *de;
-    dict *nodes;
-    struct cluster_node *node;
-
-    if(acc == NULL)
-    {
-        return;
-    }
-
-    cc = acc->cc;
-
-    nodes = cc->nodes;
-
-    if(nodes == NULL)
-    {
-        return;
-    }
-    
-    di = dictGetIterator(nodes);
-
-    while((de = dictNext(di)) != NULL) 
-    {
-        node = dictGetEntryVal(de);
-
-        ac = node->acon;
-
-        if(ac == NULL || ac->err)
-        {
-            continue;
-        }
-
-        redisAsyncDisconnect(ac);
-
-        node->acon = NULL;
-    }
-}
-
-void redisClusterAsyncFree(redisClusterAsyncContext *acc)
-{
-    redisClusterContext *cc;
-    
-    if(acc == NULL)
-    {
-        return;
-    }
-
-    cc = acc->cc;
-
-    redisClusterFree(cc);
-
-    hi_free(acc);
-}
-

+ 0 - 178
ext/hiredis-vip-0.3.0/hircluster.h

@@ -1,178 +0,0 @@
-
-#ifndef __HIRCLUSTER_H
-#define __HIRCLUSTER_H
-
-#include "hiredis.h"
-#include "async.h"
-
-#define HIREDIS_VIP_MAJOR 0
-#define HIREDIS_VIP_MINOR 3
-#define HIREDIS_VIP_PATCH 0
-
-#define REDIS_CLUSTER_SLOTS 16384
-
-#define REDIS_ROLE_NULL     0
-#define REDIS_ROLE_MASTER   1
-#define REDIS_ROLE_SLAVE    2
-
-
-#define HIRCLUSTER_FLAG_NULL                0x0
-/* The flag to decide whether add slave node in 
-  * redisClusterContext->nodes. This is set in the
-  * least significant bit of the flags field in 
-  * redisClusterContext. (1000000000000) */
-#define HIRCLUSTER_FLAG_ADD_SLAVE           0x1000
-/* The flag to decide whether add open slot  
-  * for master node. (10000000000000) */
-#define HIRCLUSTER_FLAG_ADD_OPENSLOT        0x2000
-/* The flag to decide whether get the route 
-  * table by 'cluster slots' command. Default   
-  * is 'cluster nodes' command.*/
-#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS     0x4000
-
-struct dict;
-struct hilist;
-
-typedef struct cluster_node
-{
-    sds name;
-    sds addr;
-    sds host;
-    int port;
-    uint8_t role;
-    uint8_t myself;   /* myself ? */
-    redisContext *con;
-    redisAsyncContext *acon;
-    struct hilist *slots;
-    struct hilist *slaves;
-    int failure_count;
-    void *data;     /* Not used by hiredis */
-    struct hiarray *migrating;  /* copen_slot[] */
-    struct hiarray *importing;  /* copen_slot[] */
-}cluster_node;
-
-typedef struct cluster_slot
-{
-    uint32_t start;
-    uint32_t end;
-    cluster_node *node; /* master that this slot region belong to */
-}cluster_slot;
-
-typedef struct copen_slot
-{
-    uint32_t slot_num;  /* slot number */
-    int migrate;        /* migrating or importing? */
-    sds remote_name;    /* name for the node that this slot migrating to/importing from */
-    cluster_node *node; /* master that this slot belong to */
-}copen_slot;
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Context for a connection to Redis cluster */
-typedef struct redisClusterContext {
-    int err; /* Error flags, 0 when there is no error */
-    char errstr[128]; /* String representation of error when applicable */
-    sds ip;
-    int port;
-
-    int flags;
-
-    enum redisConnectionType connection_type;
-    struct timeval *timeout;
-    
-    struct hiarray *slots;
-
-    struct dict *nodes;
-    cluster_node *table[REDIS_CLUSTER_SLOTS];
-
-    uint64_t route_version;
-
-    int max_redirect_count;
-    int retry_count;
-
-    struct hilist *requests;
-
-    int need_update_route;
-    int64_t update_route_time;
-} redisClusterContext;
-
-redisClusterContext *redisClusterConnect(const char *addrs, int flags);
-redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, 
-    const struct timeval tv, int flags);
-redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags);
-
-void redisClusterFree(redisClusterContext *cc);
-
-void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count);
-
-void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len);
-void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap);
-void *redisClusterCommand(redisClusterContext *cc, const char *format, ...);
-void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
-
-redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags);
-
-int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len);
-int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap);
-int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...);
-int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen);
-int redisClusterGetReply(redisClusterContext *cc, void **reply);
-void redisClusterReset(redisClusterContext *cc);
-
-int cluster_update_route(redisClusterContext *cc);
-int test_cluster_update_route(redisClusterContext *cc);
-struct dict *parse_cluster_nodes(redisClusterContext *cc, char *str, int str_len, int flags);
-struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int flags);
-
-
-/*############redis cluster async############*/
-
-struct redisClusterAsyncContext;
-
-typedef int (adapterAttachFn)(redisAsyncContext*, void*);
-
-typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*);
-
-/* Context for an async connection to Redis */
-typedef struct redisClusterAsyncContext {
-    
-    redisClusterContext *cc;
-
-    /* Setup error flags so they can be used directly. */
-    int err;
-    char errstr[128]; /* String representation of error when applicable */
-
-    /* Not used by hiredis */
-    void *data;
-
-    void *adapter;
-    adapterAttachFn *attach_fn;
-
-    /* Called when either the connection is terminated due to an error or per
-     * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
-    redisDisconnectCallback *onDisconnect;
-
-    /* Called when the first write event was received. */
-    redisConnectCallback *onConnect;
-
-} redisClusterAsyncContext;
-
-redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags);
-int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn);
-int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn);
-int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len);
-int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap);
-int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...);
-int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
-void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc);
-void redisClusterAsyncFree(redisClusterAsyncContext *acc);
-
-redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif

+ 0 - 554
ext/hiredis-vip-0.3.0/hiutil.c

@@ -1,554 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#include <sys/time.h>
-#include <sys/types.h>
-
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-
-
-#include "hiutil.h"
-
-#ifdef HI_HAVE_BACKTRACE
-# include <execinfo.h>
-#endif
-
-int
-hi_set_blocking(int sd)
-{
-    int flags;
-
-    flags = fcntl(sd, F_GETFL, 0);
-    if (flags < 0) {
-        return flags;
-    }
-
-    return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK);
-}
-
-int
-hi_set_nonblocking(int sd)
-{
-    int flags;
-
-    flags = fcntl(sd, F_GETFL, 0);
-    if (flags < 0) {
-        return flags;
-    }
-
-    return fcntl(sd, F_SETFL, flags | O_NONBLOCK);
-}
-
-int
-hi_set_reuseaddr(int sd)
-{
-    int reuse;
-    socklen_t len;
-
-    reuse = 1;
-    len = sizeof(reuse);
-
-    return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len);
-}
-
-/*
- * Disable Nagle algorithm on TCP socket.
- *
- * This option helps to minimize transmit latency by disabling coalescing
- * of data to fill up a TCP segment inside the kernel. Sockets with this
- * option must use readv() or writev() to do data transfer in bulk and
- * hence avoid the overhead of small packets.
- */
-int
-hi_set_tcpnodelay(int sd)
-{
-    int nodelay;
-    socklen_t len;
-
-    nodelay = 1;
-    len = sizeof(nodelay);
-
-    return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len);
-}
-
-int
-hi_set_linger(int sd, int timeout)
-{
-    struct linger linger;
-    socklen_t len;
-
-    linger.l_onoff = 1;
-    linger.l_linger = timeout;
-
-    len = sizeof(linger);
-
-    return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len);
-}
-
-int
-hi_set_sndbuf(int sd, int size)
-{
-    socklen_t len;
-
-    len = sizeof(size);
-
-    return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len);
-}
-
-int
-hi_set_rcvbuf(int sd, int size)
-{
-    socklen_t len;
-
-    len = sizeof(size);
-
-    return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len);
-}
-
-int
-hi_get_soerror(int sd)
-{
-    int status, err;
-    socklen_t len;
-
-    err = 0;
-    len = sizeof(err);
-
-    status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len);
-    if (status == 0) {
-        errno = err;
-    }
-
-    return status;
-}
-
-int
-hi_get_sndbuf(int sd)
-{
-    int status, size;
-    socklen_t len;
-
-    size = 0;
-    len = sizeof(size);
-
-    status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len);
-    if (status < 0) {
-        return status;
-    }
-
-    return size;
-}
-
-int
-hi_get_rcvbuf(int sd)
-{
-    int status, size;
-    socklen_t len;
-
-    size = 0;
-    len = sizeof(size);
-
-    status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len);
-    if (status < 0) {
-        return status;
-    }
-
-    return size;
-}
-
-int
-_hi_atoi(uint8_t *line, size_t n)
-{
-    int value;
-
-    if (n == 0) {
-        return -1;
-    }
-
-    for (value = 0; n--; line++) {
-        if (*line < '0' || *line > '9') {
-            return -1;
-        }
-
-        value = value * 10 + (*line - '0');
-    }
-
-    if (value < 0) {
-        return -1;
-    }
-
-    return value;
-}
-
-void 
-_hi_itoa(uint8_t *s, int num)
-{
-    uint8_t c;
-    uint8_t sign = 0;
-
-    if(s == NULL)
-    {
-        return;
-    }
-
-    uint32_t len, i;
-    len = 0;
-
-    if(num < 0)
-    {
-        sign = 1;
-        num = abs(num);
-    }
-    else if(num == 0)
-    {
-        s[len++] = '0';
-        return;
-    }
-
-    while(num % 10 || num /10)
-    {
-        c = num %10 + '0';
-        num = num /10;
-        s[len+1] = s[len];
-        s[len] = c;
-        len ++;
-    }
-
-    if(sign == 1)
-    {
-        s[len++] = '-';
-    }
-
-    s[len] = '\0';
-    
-    for(i = 0; i < len/2; i ++)
-    {
-        c = s[i];
-        s[i] = s[len - i -1];
-        s[len - i -1] = c;
-    }
-
-}
-
-
-int
-hi_valid_port(int n)
-{
-    if (n < 1 || n > UINT16_MAX) {
-        return 0;
-    }
-
-    return 1;
-}
-
-int _uint_len(uint32_t num)
-{
-    int n = 0;
-
-    if(num == 0)
-    {
-        return 1;
-    }
-
-    while(num != 0)
-    {
-        n ++;
-        num /= 10;
-    }
-
-    return n;
-}
-
-void *
-_hi_alloc(size_t size, const char *name, int line)
-{
-    void *p;
-
-    ASSERT(size != 0);
-
-    p = malloc(size);
-
-    if(name == NULL && line == 1)
-    {
-
-    }
-
-    return p;
-}
-
-void *
-_hi_zalloc(size_t size, const char *name, int line)
-{
-    void *p;
-
-    p = _hi_alloc(size, name, line);
-    if (p != NULL) {
-        memset(p, 0, size);
-    }
-
-    return p;
-}
-
-void *
-_hi_calloc(size_t nmemb, size_t size, const char *name, int line)
-{
-    return _hi_zalloc(nmemb * size, name, line);
-}
-
-void *
-_hi_realloc(void *ptr, size_t size, const char *name, int line)
-{
-    void *p;
-
-    ASSERT(size != 0);
-
-    p = realloc(ptr, size);
-
-    if(name == NULL && line == 1)
-    {
-
-    }
-    
-    return p;
-}
-
-void
-_hi_free(void *ptr, const char *name, int line)
-{
-    ASSERT(ptr != NULL);
-
-    if(name == NULL && line == 1)
-    {
-
-    }
-
-    free(ptr);
-}
-
-void
-hi_stacktrace(int skip_count)
-{
-    if(skip_count > 0)
-    {
-
-    }
-
-#ifdef HI_HAVE_BACKTRACE
-    void *stack[64];
-    char **symbols;
-    int size, i, j;
-
-    size = backtrace(stack, 64);
-    symbols = backtrace_symbols(stack, size);
-    if (symbols == NULL) {
-        return;
-    }
-
-    skip_count++; /* skip the current frame also */
-
-    for (i = skip_count, j = 0; i < size; i++, j++) {
-        printf("[%d] %s\n", j, symbols[i]);
-    }
-
-    free(symbols);
-#endif
-}
-
-void
-hi_stacktrace_fd(int fd)
-{
-    if(fd > 0)
-    {
-        
-    }
-#ifdef HI_HAVE_BACKTRACE
-    void *stack[64];
-    int size;
-
-    size = backtrace(stack, 64);
-    backtrace_symbols_fd(stack, size, fd);
-#endif
-}
-
-void
-hi_assert(const char *cond, const char *file, int line, int panic)
-{
-    
-    printf("File: %s Line: %d: %s\n", file, line, cond);
-    
-    if (panic) {
-        hi_stacktrace(1);
-        abort();
-    }
-    abort();
-}
-
-int
-_vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
-{
-    int n;
-
-    n = vsnprintf(buf, size, fmt, args);
-
-    /*
-     * The return value is the number of characters which would be written
-     * into buf not including the trailing '\0'. If size is == 0 the
-     * function returns 0.
-     *
-     * On error, the function also returns 0. This is to allow idiom such
-     * as len += _vscnprintf(...)
-     *
-     * See: http://lwn.net/Articles/69419/
-     */
-    if (n <= 0) {
-        return 0;
-    }
-
-    if (n < (int) size) {
-        return n;
-    }
-
-    return (int)(size - 1);
-}
-
-int
-_scnprintf(char *buf, size_t size, const char *fmt, ...)
-{
-    va_list args;
-    int n;
-
-    va_start(args, fmt);
-    n = _vscnprintf(buf, size, fmt, args);
-    va_end(args);
-
-    return n;
-}
-
-/*
- * Send n bytes on a blocking descriptor
- */
-ssize_t
-_hi_sendn(int sd, const void *vptr, size_t n)
-{
-    size_t nleft;
-    ssize_t nsend;
-    const char *ptr;
-
-    ptr = vptr;
-    nleft = n;
-    while (nleft > 0) {
-        nsend = send(sd, ptr, nleft, 0);
-        if (nsend < 0) {
-            if (errno == EINTR) {
-                continue;
-            }
-            return nsend;
-        }
-        if (nsend == 0) {
-            return -1;
-        }
-
-        nleft -= (size_t)nsend;
-        ptr += nsend;
-    }
-
-    return (ssize_t)n;
-}
-
-/*
- * Recv n bytes from a blocking descriptor
- */
-ssize_t
-_hi_recvn(int sd, void *vptr, size_t n)
-{
-    size_t nleft;
-    ssize_t nrecv;
-    char *ptr;
-
-    ptr = vptr;
-    nleft = n;
-    while (nleft > 0) {
-        nrecv = recv(sd, ptr, nleft, 0);
-        if (nrecv < 0) {
-            if (errno == EINTR) {
-                continue;
-            }
-            return nrecv;
-        }
-        if (nrecv == 0) {
-            break;
-        }
-
-        nleft -= (size_t)nrecv;
-        ptr += nrecv;
-    }
-
-    return (ssize_t)(n - nleft);
-}
-
-/*
- * Return the current time in microseconds since Epoch
- */
-int64_t
-hi_usec_now(void)
-{
-    struct timeval now;
-    int64_t usec;
-    int status;
-
-    status = gettimeofday(&now, NULL);
-    if (status < 0) {
-        return -1;
-    }
-
-    usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec;
-
-    return usec;
-}
-
-/*
- * Return the current time in milliseconds since Epoch
- */
-int64_t
-hi_msec_now(void)
-{
-    return hi_usec_now() / 1000LL;
-}
-
-void print_string_with_length(char *s, size_t len)
-{
-    char *token;
-    for(token = s; token <= s + len; token ++)
-    {
-        printf("%c", *token);
-    }
-    printf("\n");
-}
-
-void print_string_with_length_fix_CRLF(char *s, size_t len)
-{
-    char *token;
-    for(token = s; token < s + len; token ++)
-    {
-        if(*token == CR)
-        {
-            printf("\\r");
-        }
-        else if(*token == LF)
-        {
-            printf("\\n");
-        }
-        else
-        {
-            printf("%c", *token);
-        }
-    }
-    printf("\n");
-}
-

+ 0 - 265
ext/hiredis-vip-0.3.0/hiutil.h

@@ -1,265 +0,0 @@
-#ifndef __HIUTIL_H_
-#define __HIUTIL_H_
-
-#include <stdarg.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#define HI_OK        0
-#define HI_ERROR    -1
-#define HI_EAGAIN   -2
-#define HI_ENOMEM   -3
-
-typedef int rstatus_t; /* return type */
-
-#define LF                  (uint8_t) 10
-#define CR                  (uint8_t) 13
-#define CRLF                "\x0d\x0a"
-#define CRLF_LEN            (sizeof("\x0d\x0a") - 1)
-
-#define NELEMS(a)           ((sizeof(a)) / sizeof((a)[0]))
-
-#define MIN(a, b)           ((a) < (b) ? (a) : (b))
-#define MAX(a, b)           ((a) > (b) ? (a) : (b))
-
-#define SQUARE(d)           ((d) * (d))
-#define VAR(s, s2, n)       (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1))
-#define STDDEV(s, s2, n)    (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n))))
-
-#define HI_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1)
-#define HI_INET6_ADDRSTRLEN \
-    (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1)
-#define HI_INET_ADDRSTRLEN  MAX(HI_INET4_ADDRSTRLEN, HI_INET6_ADDRSTRLEN)
-#define HI_UNIX_ADDRSTRLEN  \
-    (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path))
-
-#define HI_MAXHOSTNAMELEN   256
-
-/*
- * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral
- * type (uintmax_t) in ascii, including the null terminator '\0'
- *
- * From stdint.h, we have:
- * # define UINT8_MAX   (255)
- * # define UINT16_MAX  (65535)
- * # define UINT32_MAX  (4294967295U)
- * # define UINT64_MAX  (__UINT64_C(18446744073709551615))
- */
-#define HI_UINT8_MAXLEN     (3 + 1)
-#define HI_UINT16_MAXLEN    (5 + 1)
-#define HI_UINT32_MAXLEN    (10 + 1)
-#define HI_UINT64_MAXLEN    (20 + 1)
-#define HI_UINTMAX_MAXLEN   HI_UINT64_MAXLEN
-
-/*
- * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2
- * of 2.
- */
-#define HI_ALIGNMENT        sizeof(unsigned long) /* platform word */
-#define HI_ALIGN(d, n)      (((d) + (n - 1)) & ~(n - 1))
-#define HI_ALIGN_PTR(p, n)  \
-    (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1))
-
-
-
-#define str3icmp(m, c0, c1, c2)                                                             \
-    ((m[0] == c0 || m[0] == (c0 ^ 0x20)) &&                                                 \
-     (m[1] == c1 || m[1] == (c1 ^ 0x20)) &&                                                 \
-     (m[2] == c2 || m[2] == (c2 ^ 0x20)))
-
-#define str4icmp(m, c0, c1, c2, c3)                                                         \
-    (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20)))
-
-#define str5icmp(m, c0, c1, c2, c3, c4)                                                     \
-    (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20)))
-
-#define str6icmp(m, c0, c1, c2, c3, c4, c5)                                                 \
-    (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20)))
-
-#define str7icmp(m, c0, c1, c2, c3, c4, c5, c6)                                             \
-    (str6icmp(m, c0, c1, c2, c3, c4, c5) &&                                                 \
-     (m[6] == c6 || m[6] == (c6 ^ 0x20)))
-
-#define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7)                                         \
-    (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) &&                                             \
-     (m[7] == c7 || m[7] == (c7 ^ 0x20)))
-
-#define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                                     \
-    (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) &&                                         \
-     (m[8] == c8 || m[8] == (c8 ^ 0x20)))
-
-#define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9)                                \
-    (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) &&                                     \
-     (m[9] == c9 || m[9] == (c9 ^ 0x20)))
-
-#define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10)                           \
-    (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) &&                                \
-     (m[10] == c10 || m[10] == (c10 ^ 0x20)))
-
-#define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11)                      \
-    (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) &&                           \
-     (m[11] == c11 || m[11] == (c11 ^ 0x20)))
-
-#define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12)                 \
-    (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) &&                      \
-     (m[12] == c12 || m[12] == (c12 ^ 0x20)))
-
-#define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13)            \
-    (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) &&                 \
-     (m[13] == c13 || m[13] == (c13 ^ 0x20)))
-
-#define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14)       \
-    (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) &&            \
-     (m[14] == c14 || m[14] == (c14 ^ 0x20)))
-
-#define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15)  \
-    (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) &&       \
-     (m[15] == c15 || m[15] == (c15 ^ 0x20)))
-
-
-
-/*
- * Wrapper to workaround well known, safe, implicit type conversion when
- * invoking system calls.
- */
-#define hi_gethostname(_name, _len) \
-    gethostname((char *)_name, (size_t)_len)
-
-#define hi_atoi(_line, _n)          \
-    _hi_atoi((uint8_t *)_line, (size_t)_n)
-#define hi_itoa(_line, _n)          \
-        _hi_itoa((uint8_t *)_line, (int)_n)
-
-#define uint_len(_n)        \
-    _uint_len((uint32_t)_n)
-
-
-int hi_set_blocking(int sd);
-int hi_set_nonblocking(int sd);
-int hi_set_reuseaddr(int sd);
-int hi_set_tcpnodelay(int sd);
-int hi_set_linger(int sd, int timeout);
-int hi_set_sndbuf(int sd, int size);
-int hi_set_rcvbuf(int sd, int size);
-int hi_get_soerror(int sd);
-int hi_get_sndbuf(int sd);
-int hi_get_rcvbuf(int sd);
-
-int _hi_atoi(uint8_t *line, size_t n);
-void _hi_itoa(uint8_t *s, int num);
-
-int hi_valid_port(int n);
-
-int _uint_len(uint32_t num);
-
-
-/*
- * Memory allocation and free wrappers.
- *
- * These wrappers enables us to loosely detect double free, dangling
- * pointer access and zero-byte alloc.
- */
-#define hi_alloc(_s)                    \
-    _hi_alloc((size_t)(_s), __FILE__, __LINE__)
-
-#define hi_zalloc(_s)                   \
-    _hi_zalloc((size_t)(_s), __FILE__, __LINE__)
-
-#define hi_calloc(_n, _s)               \
-    _hi_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__)
-
-#define hi_realloc(_p, _s)              \
-    _hi_realloc(_p, (size_t)(_s), __FILE__, __LINE__)
-
-#define hi_free(_p) do {                \
-    _hi_free(_p, __FILE__, __LINE__);   \
-    (_p) = NULL;                        \
-} while (0)
-
-void *_hi_alloc(size_t size, const char *name, int line);
-void *_hi_zalloc(size_t size, const char *name, int line);
-void *_hi_calloc(size_t nmemb, size_t size, const char *name, int line);
-void *_hi_realloc(void *ptr, size_t size, const char *name, int line);
-void _hi_free(void *ptr, const char *name, int line);
-
-
-#define hi_strndup(_s, _n)              \
-    strndup((char *)(_s), (size_t)(_n));
-
-/*
- * Wrappers to send or receive n byte message on a blocking
- * socket descriptor.
- */
-#define hi_sendn(_s, _b, _n)    \
-    _hi_sendn(_s, _b, (size_t)(_n))
-
-#define hi_recvn(_s, _b, _n)    \
-    _hi_recvn(_s, _b, (size_t)(_n))
-
-/*
- * Wrappers to read or write data to/from (multiple) buffers
- * to a file or socket descriptor.
- */
-#define hi_read(_d, _b, _n)     \
-    read(_d, _b, (size_t)(_n))
-
-#define hi_readv(_d, _b, _n)    \
-    readv(_d, _b, (int)(_n))
-
-#define hi_write(_d, _b, _n)    \
-    write(_d, _b, (size_t)(_n))
-
-#define hi_writev(_d, _b, _n)   \
-    writev(_d, _b, (int)(_n))
-
-ssize_t _hi_sendn(int sd, const void *vptr, size_t n);
-ssize_t _hi_recvn(int sd, void *vptr, size_t n);
-
-/*
- * Wrappers for defining custom assert based on whether macro
- * HI_ASSERT_PANIC or HI_ASSERT_LOG was defined at the moment
- * ASSERT was called.
- */
-#ifdef HI_ASSERT_PANIC
-
-#define ASSERT(_x) do {                         \
-    if (!(_x)) {                                \
-        hi_assert(#_x, __FILE__, __LINE__, 1);  \
-    }                                           \
-} while (0)
-
-#define NOT_REACHED() ASSERT(0)
-
-#elif HI_ASSERT_LOG
-
-#define ASSERT(_x) do {                         \
-    if (!(_x)) {                                \
-        hi_assert(#_x, __FILE__, __LINE__, 0);  \
-    }                                           \
-} while (0)
-
-#define NOT_REACHED() ASSERT(0)
-
-#else
-
-#define ASSERT(_x)
-
-#define NOT_REACHED()
-
-#endif
-
-void hi_assert(const char *cond, const char *file, int line, int panic);
-void hi_stacktrace(int skip_count);
-void hi_stacktrace_fd(int fd);
-
-int _scnprintf(char *buf, size_t size, const char *fmt, ...);
-int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args);
-int64_t hi_usec_now(void);
-int64_t hi_msec_now(void);
-
-void print_string_with_length(char *s, size_t len);
-void print_string_with_length_fix_CRLF(char *s, size_t len);
-
-uint16_t crc16(const char *buf, int len);
-
-#endif

+ 0 - 105
ext/hiredis-vip-0.3.0/sds.h

@@ -1,105 +0,0 @@
-/* SDS (Simple Dynamic Strings), A C dynamic strings library.
- *
- * Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   * Redistributions of source code must retain the above copyright notice,
- *     this list of conditions and the following disclaimer.
- *   * Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- *   * Neither the name of Redis nor the names of its contributors may be used
- *     to endorse or promote products derived from this software without
- *     specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef __SDS_H
-#define __SDS_H
-
-#define SDS_MAX_PREALLOC (1024*1024)
-
-#include <sys/types.h>
-#include <stdarg.h>
-#ifdef _MSC_VER
-#include "win32.h"
-#endif
-
-typedef char *sds;
-
-struct sdshdr {
-    int len;
-    int free;
-    char buf[];
-};
-
-static inline size_t sdslen(const sds s) {
-    struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh);
-    return sh->len;
-}
-
-static inline size_t sdsavail(const sds s) {
-    struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh);
-    return sh->free;
-}
-
-sds sdsnewlen(const void *init, size_t initlen);
-sds sdsnew(const char *init);
-sds sdsempty(void);
-size_t sdslen(const sds s);
-sds sdsdup(const sds s);
-void sdsfree(sds s);
-size_t sdsavail(const sds s);
-sds sdsgrowzero(sds s, size_t len);
-sds sdscatlen(sds s, const void *t, size_t len);
-sds sdscat(sds s, const char *t);
-sds sdscatsds(sds s, const sds t);
-sds sdscpylen(sds s, const char *t, size_t len);
-sds sdscpy(sds s, const char *t);
-
-sds sdscatvprintf(sds s, const char *fmt, va_list ap);
-#ifdef __GNUC__
-sds sdscatprintf(sds s, const char *fmt, ...)
-    __attribute__((format(printf, 2, 3)));
-#else
-sds sdscatprintf(sds s, const char *fmt, ...);
-#endif
-
-sds sdscatfmt(sds s, char const *fmt, ...);
-void sdstrim(sds s, const char *cset);
-void sdsrange(sds s, int start, int end);
-void sdsupdatelen(sds s);
-void sdsclear(sds s);
-int sdscmp(const sds s1, const sds s2);
-sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
-void sdsfreesplitres(sds *tokens, int count);
-void sdstolower(sds s);
-void sdstoupper(sds s);
-sds sdsfromlonglong(long long value);
-sds sdscatrepr(sds s, const char *p, size_t len);
-sds *sdssplitargs(const char *line, int *argc);
-sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
-sds sdsjoin(char **argv, int argc, char *sep, size_t seplen);
-sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
-
-/* Low level functions exposed to the user API */
-sds sdsMakeRoomFor(sds s, size_t addlen);
-void sdsIncrLen(sds s, int incr);
-sds sdsRemoveFreeSpace(sds s);
-size_t sdsAllocSize(sds s);
-
-#endif

+ 32 - 0
ext/redis-plus-plus-1.1.1/.gitignore

@@ -0,0 +1,32 @@
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app

+ 51 - 0
ext/redis-plus-plus-1.1.1/CMakeLists.txt

@@ -0,0 +1,51 @@
+project(redis++)
+
+if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+    cmake_minimum_required(VERSION 3.0.0)
+else()
+    cmake_minimum_required(VERSION 2.8.0)
+endif()
+
+set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -W -Werror -fPIC")
+
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
+
+set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis++)
+
+file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp")
+
+set(STATIC_LIB static)
+#set(SHARED_LIB shared)
+
+add_library(${STATIC_LIB} STATIC ${PROJECT_SOURCE_FILES})
+# add_library(${SHARED_LIB} SHARED ${PROJECT_SOURCE_FILES})
+
+# hiredis dependency
+find_path(HIREDIS_HEADER hiredis)
+target_include_directories(${STATIC_LIB} PUBLIC ${HIREDIS_HEADER})
+# target_include_directories(${SHARED_LIB} PUBLIC ${HIREDIS_HEADER})
+
+#find_library(HIREDIS_LIB hiredis)
+#target_link_libraries(${SHARED_LIB} ${HIREDIS_LIB})
+
+set_target_properties(${STATIC_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
+#set_target_properties(${SHARED_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
+
+set_target_properties(${STATIC_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1)
+#set_target_properties(${SHARED_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1)
+
+# add_subdirectory(test)
+
+
+# Install static lib.
+install(TARGETS ${STATIC_LIB}
+        ARCHIVE DESTINATION lib)
+
+# Install shared lib.
+#install(TARGETS ${SHARED_LIB}
+#        LIBRARY DESTINATION lib)
+
+#Install headers.
+set(HEADER_PATH "sw/redis++")
+file(GLOB HEADERS "${PROJECT_SOURCE_DIR}/*.h*")
+install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${HEADER_PATH})

+ 201 - 0
ext/redis-plus-plus-1.1.1/LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 1776 - 0
ext/redis-plus-plus-1.1.1/README.md

@@ -0,0 +1,1776 @@
+# redis-plus-plus
+
+- [Overview](#overview)
+    - [Features](#features)
+- [Installation](#installation)
+    - [Install hiredis](#install-hiredis)
+    - [Install redis-plus-plus](#install-redis-plus-plus)
+    - [Run Tests (Optional)](#run-tests-optional)
+    - [Use redis-plus-plus In Your Project](#use-redis-plus-plus-in-your-project)
+- [Getting Started](#getting-started)
+- [API Reference](#api-reference)
+    - [Connection](#connection)
+    - [Send Command to Redis Server](#send-command-to-redis-server)
+    - [Generic Command Interface](#generic-command-interface)
+    - [Publish/Subscribe](#publishsubscribe)
+    - [Pipeline](#pipeline)
+    - [Transaction](#transaction)
+    - [Redis Cluster](#redis-cluster)
+    - [Redis Sentinel](#redis-sentinel)
+    - [Redis Stream](#redis-stream)
+- [Author](#author)
+
+## Overview
+
+This is a C++ client for Redis. It's based on [hiredis](https://github.com/redis/hiredis), and written in C++ 11.
+
+**NOTE**: I'm not a native speaker. So if the documentation is unclear, please feel free to open an issue or pull request. I'll response ASAP.
+
+### Features
+- Most commands for Redis.
+- Connection pool.
+- Redis scripting.
+- Thread safe unless otherwise stated.
+- Redis publish/subscribe.
+- Redis pipeline.
+- Redis transaction.
+- Redis Cluster.
+- Redis Sentinel.
+- STL-like interfaces.
+- Generic command interface.
+
+## Installation
+
+### Install hiredis
+
+Since *redis-plus-plus* is based on *hiredis*, you should install *hiredis* first. The minimum version requirement for *hiredis* is **v0.12.1**, and you'd better use the latest release of *hiredis*.
+
+```
+git clone https://github.com/redis/hiredis.git
+
+cd hiredis
+
+make
+
+make install
+```
+
+By default, *hiredis* is installed at */usr/local*. If you want to install *hiredis* at non-default location, use the following commands to specify the installation path.
+
+```
+make PREFIX=/non/default/path
+
+make PREFIX=/non/default/path install
+```
+
+### Install redis-plus-plus
+
+*redis-plus-plus* is built with [CMAKE](https://cmake.org).
+
+```
+git clone https://github.com/sewenew/redis-plus-plus.git
+
+cd redis-plus-plus
+
+mkdir compile
+
+cd compile
+
+cmake -DCMAKE_BUILD_TYPE=Release ..
+
+make
+
+make install
+
+cd ..
+```
+
+If *hiredis* is installed at non-default location, you should use `CMAKE_PREFIX_PATH` to specify the installation path of *hiredis*. By default, *redis-plus-plus* is installed at */usr/local*. However, you can use `CMAKE_INSTALL_PREFIX` to install *redis-plus-plus* at non-default location.
+
+```
+cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/path/to/hiredis -DCMAKE_INSTALL_PREFIX=/path/to/install/redis-plus-plus ..
+```
+
+### Run Tests (Optional)
+
+*redis-plus-plus* has been fully tested with the following compilers:
+
+```
+gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
+gcc version 5.5.0 20171010 (Ubuntu 5.5.0-12ubuntu1)
+gcc version 6.5.0 20181026 (Ubuntu 6.5.0-2ubuntu1~18.04)
+gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
+gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.04.1)
+clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)
+clang version 4.0.1-10 (tags/RELEASE_401/final)
+clang version 5.0.1-4 (tags/RELEASE_501/final)
+clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
+clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final)
+Apple clang version 11.0.0 (clang-1100.0.33.8)
+```
+
+After compiling with cmake, you'll get a test program in *compile/test* directory: *compile/test/test_redis++*.
+
+In order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version (by now, it's 5.0). Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future.
+
+**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, e.g. Redis 2.0, Redis 3.0, Redis 4.0, Redis 5.0.
+
+**NEVER** run the test program in production envronment, since the keys, which the test program reads or writes, might conflict with your application.
+
+In order to run tests with both Redis and Redis Cluster, you can run the test program with the following command:
+
+```
+./compile/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port
+```
+
+- *host* and *port* are the host and port number of the Redis instance.
+- *cluster_node* and *cluster_port* are the host and port number of Redis Cluster. You only need to set the host and port number of a single node in the cluster, *redis-plus-plus* will find other nodes automatically.
+- *auth* is the password of the Redis instance and Redis Cluster. The Redis instance and Redis Cluster must be configured with the same password. If there's no password configured, don't set this option.
+
+If you only want to run tests with Redis, you only need to specify *host*, *port* and *auth* options:
+
+```
+./compile/test/test_redis++ -h host -p port -a auth
+```
+
+Similarly, if you only want to run tests with Redis Cluster, just specify *cluster_node*, *cluster_port* and *auth* options:
+
+```
+./compile/test/test_redis++ -a auth -n cluster_node -c cluster_port
+```
+
+The test program will test running *redis-plus-plus* in multi-threads environment, and this test will cost a long time. If you want to skip it (not recommended), just comment out the following lines in *test/src/sw/redis++/test_main.cpp* file.
+
+```C++
+sw::redis::test::ThreadsTest threads_test(opts, cluster_node_opts);
+threads_test.run();
+```
+
+If all tests have been passed, the test program will print the following message:
+
+```
+Pass all tests
+```
+
+Otherwise, it prints the error message.
+
+#### Performance
+
+*redis-plus-plus* runs as fast as *hiredis*, since it's a wrapper of *hiredis*. You can run *test_redis++* in benchmark mode to check the performance in your environment.
+
+```
+./compile/test/test_redis++ -h host -p port -a auth -n cluster_node -c cluster_port -b -t thread_num -s connection_pool_size -r request_num -k key_len -v val_len
+```
+
+- *-b* option turns the test program into benchmark mode.
+- *thread_num* specifies the number of worker threads. `10` by default.
+- *connection_pool_size* specifies the size of the connection pool. `5` by default.
+- *request_num* specifies the total number of requests sent to server for each test. `100000` by default.
+- *key_len* specifies the length of the key for each operation. `10` by default.
+- *val_len* specifies the length of the value. `10` by default.
+
+The bechmark will generate `100` random binary keys for testing, and the size of these keys is specified by *key_len*. When the benchmark runs, it will read/write with these keys. So **NEVER** run the test program in your production environment, otherwise, it might inaccidently delete your data.
+
+### Use redis-plus-plus In Your Project
+
+After compiling the code, you'll get both shared library and static library. Since *redis-plus-plus* depends on *hiredis*, you need to link both libraries to your Application. Also don't forget to specify the `-std=c++11` and thread-related option.
+
+#### Use Static Libraries
+
+Take gcc as an example.
+
+```
+g++ -std=c++11 -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread
+```
+
+If *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` option to specify the header path.
+
+```
+g++ -std=c++11 -I/non-default/install/include/path -o app app.cpp /path/to/libredis++.a /path/to/libhiredis.a -pthread
+```
+
+#### Use Shared Libraries
+
+```
+g++ -std=c++11 -o app app.cpp -lredis++ -lhiredis -pthread
+```
+
+If *hiredis* and *redis-plus-plus* are installed at non-default location, you should use `-I` and `-L` options to specify the header and library paths.
+
+```
+g++ -std=c++11 -I/non-default/install/include/path -L/non-default/install/lib/path -o app app.cpp -lredis++ -lhiredis -pthread
+```
+
+When linking with shared libraries, and running your application, you might get the following error message:
+
+```
+error while loading shared libraries: xxx: cannot open shared object file: No such file or directory.
+```
+
+That's because the linker cannot find the shared libraries. In order to solve the problem, you can add the path where you installed *hiredis* and *redis-plus-plus* libraries, to `LD_LIBRARY_PATH` environment variable. For example:
+
+```
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
+```
+
+Check [this StackOverflow question](https://stackoverflow.com/questions/480764) for details on how to solve the problem.
+
+#### Build With Cmake
+
+If you're using cmake to build your application, you need to add *hiredis* and *redis-plus-plus* dependencies in your *CMakeLists.txt*:
+
+```CMake
+# <------------ add hiredis dependency --------------->
+find_path(HIREDIS_HEADER hiredis)
+target_include_directories(target PUBLIC ${HIREDIS_HEADER})
+
+find_library(HIREDIS_LIB hiredis)
+target_link_libraries(target ${HIREDIS_LIB})
+
+# <------------ add redis-plus-plus dependency -------------->
+# NOTE: this should be *sw* NOT *redis++*
+find_path(REDIS_PLUS_PLUS_HEADER sw)
+target_include_directories(target PUBLIC ${REDIS_PLUS_PLUS_HEADER})
+
+find_library(REDIS_PLUS_PLUS_LIB redis++)
+target_link_libraries(target ${REDIS_PLUS_PLUS_LIB})
+```
+
+See [this issue](https://github.com/sewenew/redis-plus-plus/issues/5) for a complete example of *CMakeLists.txt*.
+
+Also, if you installed *hiredis* and *redis-plus-plus* at non-default location, you need to run cmake with `CMAKE_PREFIX_PATH` option to specify the installation path of these two libraries.
+
+```
+cmake -DCMAKE_PREFIX_PATH=/installation/path/to/the/two/libs ..
+```
+
+## Getting Started
+
+```C++
+#include <sw/redis++/redis++.h>
+
+using namespace sw::redis;
+
+try {
+    // Create an Redis object, which is movable but NOT copyable.
+    auto redis = Redis("tcp://127.0.0.1:6379");
+
+    // ***** STRING commands *****
+
+    redis.set("key", "val");
+    auto val = redis.get("key");    // val is of type OptionalString. See 'API Reference' section for details.
+    if (val) {
+        // Dereference val to get the returned value of std::string type.
+        std::cout << *val << std::endl;
+    }   // else key doesn't exist.
+
+    // ***** LIST commands *****
+
+    // std::vector<std::string> to Redis LIST.
+    std::vector<std::string> vec = {"a", "b", "c"};
+    redis.rpush("list", vec.begin(), vec.end());
+
+    // std::initializer_list to Redis LIST.
+    redis.rpush("list", {"a", "b", "c"});
+
+    // Redis LIST to std::vector<std::string>.
+    vec.clear();
+    redis.lrange("list", 0, -1, std::back_inserter(vec));
+
+    // ***** HASH commands *****
+
+    redis.hset("hash", "field", "val");
+
+    // Another way to do the same job.
+    redis.hset("hash", std::make_pair("field", "val"));
+
+    // std::unordered_map<std::string, std::string> to Redis HASH.
+    std::unordered_map<std::string, std::string> m = {
+        {"field1", "val1"},
+        {"field2", "val2"}
+    };
+    redis.hmset("hash", m.begin(), m.end());
+
+    // Redis HASH to std::unordered_map<std::string, std::string>.
+    m.clear();
+    redis.hgetall("hash", std::inserter(m, m.begin()));
+
+    // Get value only.
+    // NOTE: since field might NOT exist, so we need to parse it to OptionalString.
+    std::vector<OptionalString> vals;
+    redis.hmget("hash", {"field1", "field2"}, std::back_inserter(vals));
+
+    // ***** SET commands *****
+
+    redis.sadd("set", "m1");
+
+    // std::unordered_set<std::string> to Redis SET.
+    std::unordered_set<std::string> set = {"m2", "m3"};
+    redis.sadd("set", set.begin(), set.end());
+
+    // std::initializer_list to Redis SET.
+    redis.sadd("set", {"m2", "m3"});
+
+    // Redis SET to std::unordered_set<std::string>.
+    set.clear();
+    redis.smembers("set", std::inserter(set, set.begin()));
+
+    if (redis.sismember("set", "m1")) {
+        std::cout << "m1 exists" << std::endl;
+    }   // else NOT exist.
+
+    // ***** SORTED SET commands *****
+
+    redis.zadd("sorted_set", "m1", 1.3);
+
+    // std::unordered_map<std::string, double> to Redis SORTED SET.
+    std::unordered_map<std::string, double> scores = {
+        {"m2", 2.3},
+        {"m3", 4.5}
+    };
+    redis.zadd("sorted_set", scores.begin(), scores.end());
+
+    // Redis SORTED SET to std::unordered_map<std::string, double>.
+    scores.clear();
+    redis.zrangebyscore("sorted_set",
+            UnboundedInterval<double>{},            // (-inf, +inf)
+            std::inserter(scores, scores.begin()));
+
+    // Only get member names:
+    // pass an inserter of std::vector<std::string> type as output parameter.
+    std::vector<std::string> without_score;
+    redis.zrangebyscore("sorted_set",
+            BoundedInterval<double>(1.5, 3.4, BoundType::CLOSED),   // [1.5, 3.4]
+            std::back_inserter(without_score));
+
+    // Get both member names and scores:
+    // pass an inserter of std::unordered_map<std::string, double> as output parameter.
+    std::unordered_map<std::string, double> with_score;
+    redis.zrangebyscore("sorted_set",
+            BoundedInterval<double>(1.5, 3.4, BoundType::LEFT_OPEN),    // (1.5, 3.4]
+            std::inserter(with_score, with_score.end()));
+
+    // ***** SCRIPTING commands *****
+
+    // Script returns a single element.
+    auto num = redis.eval<long long>("return 1", {}, {});
+
+    // Script returns an array of elements.
+    std::vector<long long> nums;
+    redis.eval("return {ARGV[1], ARGV[2]}", {}, {"1", "2"}, std::back_inserter(nums));
+
+    // ***** Pipeline *****
+
+    // Create a pipeline.
+    auto pipe = redis.pipeline();
+
+    // Send mulitple commands and get all replies.
+    auto pipe_replies = pipe.set("key", "value")
+                            .get("key")
+                            .rename("key", "new-key")
+                            .rpush("list", {"a", "b", "c"})
+                            .lrange("list", 0, -1)
+                            .exec();
+
+    // Parse reply with reply type and index.
+    auto set_cmd_result = pipe_replies.get<bool>(0);
+
+    auto get_cmd_result = pipe_replies.get<OptionalString>(1);
+
+    // rename command result
+    pipe_replies.get<void>(2);
+
+    auto rpush_cmd_result = pipe_replies.get<long long>(3);
+
+    std::vector<std::string> lrange_cmd_result;
+    pipe_replies.get(4, back_inserter(lrange_cmd_result));
+
+    // ***** Transaction *****
+
+    // Create a transaction.
+    auto tx = redis.transaction();
+
+    // Run multiple commands in a transaction, and get all replies.
+    auto tx_replies = tx.incr("num0")
+                        .incr("num1")
+                        .mget({"num0", "num1"})
+                        .exec();
+
+    // Parse reply with reply type and index.
+    auto incr_result0 = tx_replies.get<long long>(0);
+
+    auto incr_result1 = tx_replies.get<long long>(1);
+
+    std::vector<OptionalString> mget_cmd_result;
+    tx_replies.get(2, back_inserter(mget_cmd_result));
+
+    // ***** Generic Command Interface *****
+
+    // There's no *Redis::client_getname* interface.
+    // But you can use *Redis::command* to get the client name.
+    val = redis.command<OptionalString>("client", "getname");
+    if (val) {
+        std::cout << *val << std::endl;
+    }
+
+    // Same as above.
+    auto getname_cmd_str = {"client", "getname"};
+    val = redis.command<OptionalString>(getname_cmd_str.begin(), getname_cmd_str.end());
+
+    // There's no *Redis::sort* interface.
+    // But you can use *Redis::command* to send sort the list.
+    std::vector<std::string> sorted_list;
+    redis.command("sort", "list", "ALPHA", std::back_inserter(sorted_list));
+
+    // Another *Redis::command* to do the same work.
+    auto sort_cmd_str = {"sort", "list", "ALPHA"};
+    redis.command(sort_cmd_str.begin(), sort_cmd_str.end(), std::back_inserter(sorted_list));
+
+    // ***** Redis Cluster *****
+
+    // Create a RedisCluster object, which is movable but NOT copyable.
+    auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000");
+
+    // RedisCluster has similar interfaces as Redis.
+    redis_cluster.set("key", "value");
+    val = redis_cluster.get("key");
+    if (val) {
+        std::cout << *val << std::endl;
+    }   // else key doesn't exist.
+
+    // Keys with hash-tag.
+    redis_cluster.set("key{tag}1", "val1");
+    redis_cluster.set("key{tag}2", "val2");
+    redis_cluster.set("key{tag}3", "val3");
+
+    std::vector<OptionalString> hash_tag_res;
+    redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"},
+            std::back_inserter(hash_tag_res));
+
+} catch (const Error &e) {
+    // Error handling.
+}
+```
+
+## API Reference
+
+### Connection
+
+`Redis` class maintains a connection pool to Redis server. If the connection is broken, `Redis` reconnects to Redis server automatically.
+
+You can initialize a `Redis` instance with `ConnectionOptions` and `ConnectionPoolOptions`. `ConnectionOptions` specifies options for connection to Redis server, and `ConnectionPoolOptions` specifies options for conneciton pool. `ConnectionPoolOptions` is optional. If not specified, `Redis` maintains a single connection to Redis server.
+
+```C++
+ConnectionOptions connection_options;
+connection_options.host = "127.0.0.1";  // Required.
+connection_options.port = 6666; // Optional. The default port is 6379.
+connection_options.password = "auth";   // Optional. No password by default.
+connection_options.db = 1;  // Optional. Use the 0th database by default.
+
+// Optional. Timeout before we successfully send request to or receive response from redis.
+// By default, the timeout is 0ms, i.e. never timeout and block until we send or receive successfuly.
+// NOTE: if any command is timed out, we throw a TimeoutError exception.
+connection_options.socket_timeout = std::chrono::milliseconds(200);
+
+// Connect to Redis server with a single connection.
+Redis redis1(connection_options);
+
+ConnectionPoolOptions pool_options;
+pool_options.size = 3;  // Pool size, i.e. max number of connections.
+
+// Connect to Redis server with a connection pool.
+Redis redis2(connection_options, pool_options);
+```
+
+See [ConnectionOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection.h#L40) and [ConnectionPoolOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/connection_pool.h#L30) for more options.
+
+**NOTE**: `Redis` class is movable but NOT copyable.
+
+```C++
+// auto redis3 = redis1;    // this won't compile.
+
+// But it's movable.
+auto redis3 = std::move(redis1);
+```
+
+*redis-plus-plus* also supports connecting to Redis server with Unix Domain Socket.
+
+```C++
+ConnectionOptions options;
+options.type = ConnectionType::UNIX;
+options.path = "/path/to/socket";
+Redis redis(options);
+```
+
+You can also connect to Redis server with a URI. However, in this case, you can only specify *host* and *port*, or *Unix Domain Socket path*. In order to specify other options, you need to use `ConnectionOptions` and `ConnectionPoolOptions`.
+
+```C++
+// Single connection to the given host and port.
+Redis redis1("tcp://127.0.0.1:6666");
+
+// Use default port, i.e. 6379.
+Redis redis2("tcp://127.0.0.1");
+
+// Connect to Unix Domain Socket.
+Redis redis3("unix://path/to/socket");
+```
+
+#### Lazily Create Connection
+
+Connections in the pool are lazily created. When the connection pool is initialized, i.e. the constructor of `Redis`, `Redis` does NOT connect to the server. Instead, it connects to the server only when you try to send command. In this way, we can avoid unnecessary connections. So if the pool size is 5, but the number of max concurrent connections is 3, there will be only 3 connections in the pool.
+
+#### Connection Failure
+
+You don't need to check whether `Redis` object connects to server successfully. If `Redis` fails to create a connection to Redis server, or the connection is broken at some time, it throws an exception of type `Error` when you try to send command with `Redis`. Even when you get an exception, i.e. the connection is broken, you don't need to create a new `Redis` object. You can reuse the `Redis` object to send commands, and the `Redis` object will try to reconnect to server automatically. If it reconnects successfully, it sends command to server. Otherwise, it throws an exception again.
+
+See the [Exception section](#exception) for details on exceptions.
+
+#### Reuse Redis object As Much As Possible
+
+It's NOT cheap to create a `Redis` object, since it will create new connections to Redis server. So you'd better reuse `Redis` object as much as possible. Also, it's safe to call `Redis`' member functions in multi-thread environment, and you can share `Redis` object in multiple threads.
+
+```C++
+// This is GOOD practice.
+auto redis = Redis("tcp://127.0.0.1");
+for (auto idx = 0; idx < 100; ++idx) {
+    // Reuse the Redis object in the loop.
+    redis.set("key", "val");
+}
+
+// This is VERY BAD! It's very inefficient.
+// NEVER DO IT!!!
+for (auto idx = 0; idx < 100; ++idx) {
+    // Create a new Redis object for each iteration.
+    auto redis = Redis("tcp://127.0.0.1");
+    redis.set("key", "val");
+}
+```
+
+### Send Command to Redis Server
+
+You can send [Redis commands](https://redis.io/commands) through `Redis` object. `Redis` has one or more (overloaded) methods for each Redis command. The method has the same (lowercased) name as the corresponding command. For example, we have 3 overload methods for the `DEL key [key ...]` command:
+
+```C++
+// Delete a single key.
+long long Redis::del(const StringView &key);
+
+// Delete a batch of keys: [first, last).
+template <typename Input>
+long long Redis::del(Input first, Input last);
+
+// Delete keys in the initializer_list.
+template <typename T>
+long long Redis::del(std::initializer_list<T> il);
+```
+
+With input parameters, these methods build a Redis command based on [Redis protocol](https://redis.io/topics/protocol), and send the command to Redis server. Then synchronously receive the reply, parse it, and return to the caller.
+
+Let's take a closer look at these methods' parameters and return values.
+
+#### Parameter Type
+
+Most of these methods have the same parameters as the corresponding commands. The following is a list of parameter types:
+
+| Parameter Type | Explaination | Example | Note |
+| :------------: | ------------ | ------- | ---- |
+| **StringView** | Parameters of string type. Normally used for key, value, member name, field name and so on | ***bool Redis::hset(const StringView &key, const StringView &field, const StringView &val)*** | See the [StringView section](#stringview) for details on `StringView` |
+| **long long** | Parameters of integer type. Normally used for index (e.g. list commands) or integer | ***void ltrim(const StringView &key, long long start, long long stop)*** <br> ***long long decrby(const StringView &key, long long decrement)*** | |
+| **double** | Parameters of floating-point type. Normally used for score (e.g. sorted set commands) or number of floating-point type | ***double incrbyfloat(const StringView &key, double increment)*** | |
+| **std::chrono::duration** <br> **std::chrono::time_point** | Time-related parameters | ***bool expire(const StringView &key, const std::chrono::seconds &timeout)*** <br> ***bool expireat(const StringView &key, const std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds> &tp)*** | |
+| **std::pair<StringView, StringView>** | Used for Redis hash's (field, value) pair | ***bool hset(const StringView &key, const std::pair<StringView, StringView> &item)*** | |
+| **std::pair<double, double>** | Used for Redis geo's (longitude, latitude) pair | ***OptionalLongLong georadius(const StringView &key, const std::pair<double, double> &location, double radius, GeoUnit unit, const StringView &destination, bool store_dist, long long count)*** | |
+| **pair of iterators** | Use a pair of iterators to specify a range of input, so that we can pass the data in a STL container to these methods | ***template < typename Input >*** <br> ***long long del(Input first, Input last)*** | Throw an exception, if it's an empty range, i.e. *first == last* |
+| **std::initializer_list< T >** | Use an initializer list to specify a batch of input | ***template < typename T >*** <br> ***long long del(std::initializer_list< T > il)*** | |
+| **some options** | Options for some commands | ***UpdateType***, ***template < typename T > class BoundedInterval*** | See [command_options.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/command_options.h) for details |
+
+##### StringView
+
+[std::string_view](http://en.cppreference.com/w/cpp/string/basic_string_view) is a good option for the type of string parameters. However, by now, not all compilers support `std::string_view`. So we wrote a [simple version](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/utils.h#L48), i.e. `StringView`. Since there are conversions from `std::string` and c-style string to `StringView`, you can just pass `std::string` or c-style string to methods that need a `StringView` parameter.
+
+```C++
+// bool Redis::hset(const StringView &key, const StringView &field, const StringView &val)
+
+// Pass c-style string to StringView.
+redis.hset("key", "field", "value");
+
+// Pass std::string to StringView.
+std::string key = "key";
+std::string field = "field";
+std::string val = "val";
+redis.hset(key, field, val);
+
+// Mix std::string and c-style string.
+redis.hset(key, field, "value");
+```
+
+#### Return Type
+
+[Redis protocol](https://redis.io/topics/protocol) defines 5 kinds of replies:
+- *Status Reply*: Also known as *Simple String Reply*. It's a non-binary string reply.
+- *Bulk String Reply*: Binary safe string reply.
+- *Integer Reply*: Signed integer reply. Large enough to hold `long long`.
+- *Array Reply*: (Nested) Array reply.
+- *Error Reply*: Non-binary string reply that gives error info.
+
+Also these replies might be *NULL*. For instance, when you try to `GET` the value of a nonexistent key, Redis returns a *NULL Bulk String Reply*.
+
+As we mentioned above, replies are parsed into return values of these methods. The following is a list of return types:
+
+| Return Type | Explaination | Example | Note |
+| :---------: | ------------ | ------- | ---- |
+| **void** | *Status Reply* that should always return a string of "OK" | *RENAME*, *SETEX* | |
+| **std::string** | *Status Reply* that NOT always return "OK", and *Bulk String Reply* | *PING*, *INFO* | |
+| **bool** | *Integer Reply* that always returns 0 or 1 | *EXPIRE*, *HSET* | See the [Boolean Return Value section](#boolean-return-value) for the meaning of a boolean return value |
+| **long long** | *Integer Reply* that not always return 0 or 1 | *DEL*, *APPEND* | |
+| **double** | *Bulk String Reply* that represents a double | *INCRBYFLOAT*, *ZINCRBY* | |
+| **std::pair** | *Array Reply* with exactly 2 elements. Since the return value is always an array of 2 elements, we return the 2 elements as a `std::pair`'s first and second elements | *BLPOP* | |
+| **std::tuple** | *Array Reply* with fixed length and has more than 2 elements. Since length of the returned array is fixed, we return the array as a `std::tuple` | *BZPOPMAX* | |
+| **output iterator** | General *Array Reply* with non-fixed/dynamic length. We use STL-like interface to return this kind of array replies, so that you can insert the return value into a STL container easily | *MGET*, *LRANGE* | Also, sometimes the type of output iterator decides which options to send with the command. See the [Examples section](#command-overloads) for details |
+| **Optional< T >** | For any reply of type `T` that might be *NULL* | *GET*, *LPOP*, *BLPOP*, *BZPOPMAX* | See the [Optional section](#optional) for details on `Optional<T>` |
+
+##### Boolean Return Value
+
+The return type of some methods, e.g. `EXPIRE`, `HSET`, is `bool`. If the method returns `false`, it DOES NOT mean that `Redis` failed to send the command to Redis server. Instead, it means that Redis server returns an *Integer Reply*, and the value of the reply is `0`. Accordingly, if the method returns `true`, it means that Redis server returns an *Integer Reply*, and the value of the reply is `1`. You can 
+check [Redis commands manual](http://redis.io/commands) for what do `0` and `1` stand for.
+
+For example, when we send `EXPIRE` command to Redis server, it returns `1` if the timeout was set, and it returns `0` if the key doesn't exist. Accordingly, if the timeout was set, `Redis::expire` returns `true`, and if the key doesn't exist, `Redis::expire` returns `false`.
+
+So, never use the return value to check if the command has been successfully sent to Redis server. Instead, if `Redis` failed to send command to server, it throws an exception of type `Error`. See the [Exception section](#exception) for details on exceptions.
+
+##### Optional
+
+[std::optional](http://en.cppreference.com/w/cpp/utility/optional) is a good option for return type, if Redis might return *NULL REPLY*. Again, since not all compilers support `std::optional` so far, we implement our own [simple version](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/utils.h#L85), i.e. `Optional<T>`.
+
+Take the [GET](https://redis.io/commands/get) and [MGET](https://redis.io/commands/mget) commands for example:
+
+```C++
+// Or just: auto val = redis.get("key");
+Optional<std::string> val = redis.get("key");
+
+// Optional<T> has a conversion to bool.
+// If it's NOT a null Optional<T> object, it's converted to true.
+// Otherwise, it's converted to false.
+if (val) {
+    // Key exists. Dereference val to get the string result.
+    std::cout << *val << std::endl;
+} else {
+    // Redis server returns a NULL Bulk String Reply.
+    // It's invalid to dereference a null Optional<T> object.
+    std::cout << "key doesn't exist." << std::endl;
+}
+
+std::vector<Optional<std::string>> values;
+redis.mget({"key1", "key2", "key3"}, std::back_inserter(values));
+for (const auto &val : values) {
+    if (val) {
+        // Key exist, process the value.
+    }
+}
+```
+
+We also have some typedefs for some commonly used `Optional<T>`:
+
+```C++
+using OptionalString = Optional<std::string>;
+
+using OptionalLongLong = Optional<long long>;
+
+using OptionalDouble = Optional<double>;
+
+using OptionalStringPair = Optional<std::pair<std::string, std::string>>;
+```
+
+#### Exception
+
+`Redis` throws exceptions if it receives an *Error Reply* or something bad happens, e.g. failed to create a connection to server, or connection to server is broken. All exceptions derived from `Error` class. See [errors.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/errors.h) for details.
+
+- `Error`: Generic error. It's also the base class of other exceptions.
+- `IoError`: There's some IO error with the connection.
+- `TimeoutError`: Read or write operation was timed out. It's a derived class of `IoError`.
+- `ClosedError`: Redis server closed the connection.
+- `ProtoError`: The command or reply is invalid, and we cannot process it with Redis protocol.
+- `OomError`: *hiredis* library got an out-of-memory error.
+- `ReplyError`: Redis server returned an error reply, e.g. we try to call `redis::lrange` on a Redis hash.
+- `WatchError`: Watched key has been modified. See [Watch section](#watch) for details.
+
+**NOTE**: *NULL REPLY*` is not taken as an exception. For example, if we try to `GET` a non-existent key, we'll get a *NULL Bulk String Reply*. Instead of throwing an exception, we return the *NULL REPLY* as a null `Optional<T>` object. Also see [Optional section](#optional).
+
+#### Examples
+
+Let's see some examples on how to send commands to Redis server.
+
+##### Various Parameter Types
+
+```C++
+// ***** Parameters of StringView type *****
+
+// Implicitly construct StringView with c-style string.
+redis.set("key", "value");
+
+// Implicitly construct StringView with std::string.
+std::string key("key");
+std::string val("value");
+redis.set(key, val);
+
+// Explicitly pass StringView as parameter.
+std::vector<char> large_data;
+// Avoid copying.
+redis.set("key", StringView(large_data.data(), large_data.size()));
+
+// ***** Parameters of long long type *****
+
+// For index.
+redis.bitcount(key, 1, 3);
+
+// For number.
+redis.incrby("num", 100);
+
+// ***** Parameters of double type *****
+
+// For score.
+redis.zadd("zset", "m1", 2.5);
+redis.zadd("zset", "m2", 3.5);
+redis.zadd("zset", "m3", 5);
+
+// For (longitude, latitude).
+redis.geoadd("geo", std::make_tuple("member", 13.5, 15.6));
+
+// ***** Time-related parameters *****
+
+using namespace std::chrono;
+
+redis.expire(key, seconds(1000));
+
+auto tp = time_point_cast<seconds>(system_clock::now() + seconds(100));
+redis.expireat(key, tp);
+
+// ***** Some options for commands *****
+
+if (redis.set(key, "value", milliseconds(100), UpdateType::NOT_EXIST)) {
+    std::cout << "set OK" << std::endl;
+}
+
+redis.linsert("list", InsertPosition::BEFORE, "pivot", "val");
+
+std::vector<std::string> res;
+
+// (-inf, inf)
+redis.zrangebyscore("zset", UnboundedInterval<double>{}, std::back_inserter(res));
+
+// [3, 6]
+redis.zrangebyscore("zset",
+    BoundedInterval<double>(3, 6, BoundType::CLOSED),
+    std::back_inserter(res));
+
+// (3, 6]
+redis.zrangebyscore("zset",
+    BoundedInterval<double>(3, 6, BoundType::LEFT_OPEN),
+    std::back_inserter(res));
+
+// (3, 6)
+redis.zrangebyscore("zset",
+    BoundedInterval<double>(3, 6, BoundType::OPEN),
+    std::back_inserter(res));
+
+// [3, 6)
+redis.zrangebyscore("zset",
+    BoundedInterval<double>(3, 6, BoundType::RIGHT_OPEN),
+    std::back_inserter(res));
+
+// [3, +inf)
+redis.zrangebyscore("zset",
+    LeftBoundedInterval<double>(3, BoundType::RIGHT_OPEN),
+    std::back_inserter(res));
+
+// (3, +inf)
+redis.zrangebyscore("zset",
+    LeftBoundedInterval<double>(3, BoundType::OPEN),
+    std::back_inserter(res));
+
+// (-inf, 6]
+redis.zrangebyscore("zset",
+    RightBoundedInterval<double>(6, BoundType::LEFT_OPEN),
+    std::back_inserter(res));
+
+// (-inf, 6)
+redis.zrangebyscore("zset",
+    RightBoundedInterval<double>(6, BoundType::OPEN),
+    std::back_inserter(res));
+
+// ***** Pair of iterators *****
+
+std::vector<std::pair<std::string, std::string>> kvs = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}};
+redis.mset(kvs.begin(), kvs.end());
+
+std::unordered_map<std::string, std::string> kv_map = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}};
+redis.mset(kv_map.begin(), kv_map.end());
+
+std::unordered_map<std::string, std::string> str_map = {{"f1", "v1"}, {"f2", "v2"}, {"f3", "v3"}};
+redis.hmset("hash", str_map.begin(), str_map.end());
+
+std::unordered_map<std::string, double> score_map = {{"m1", 20}, {"m2", 12.5}, {"m3", 3.14}};
+redis.zadd("zset", score_map.begin(), score_map.end());
+
+std::vector<std::string> keys = {"k1", "k2", "k3"};
+redis.del(keys.begin(), keys.end());
+
+// ***** Parameters of initializer_list type *****
+
+redis.mset({
+    std::make_pair("k1", "v1"),
+    std::make_pair("k2", "v2"),
+    std::make_pair("k3", "v3")
+});
+
+redis.hmset("hash",
+    {
+        std::make_pair("f1", "v1"),
+        std::make_pair("f2", "v2"),
+        std::make_pair("f3", "v3")
+    });
+
+redis.zadd("zset",
+    {
+        std::make_pair("m1", 20.0),
+        std::make_pair("m2", 34.5),
+        std::make_pair("m3", 23.4)
+    });
+
+redis.del({"k1", "k2", "k3"});
+```
+
+##### Various Return Types
+
+```C++
+// ***** Return void *****
+
+redis.save();
+
+// ***** Return std::string *****
+
+auto info = redis.info();
+
+// ***** Return bool *****
+
+if (!redis.expire("nonexistent", std::chrono::seconds(100))) {
+    std::cerr << "key doesn't exist" << std::endl;
+}
+
+if (redis.setnx("key", "val")) {
+    std::cout << "set OK" << std::endl;
+}
+
+// ***** Return long long *****
+
+auto len = redis.strlen("key");
+auto num = redis.del({"a", "b", "c"});
+num = redis.incr("a");
+
+// ***** Return double *****
+
+auto real = redis.incrbyfloat("b", 23.4);
+real = redis.hincrbyfloat("c", "f", 34.5);
+
+// ***** Return Optional<std::string>, i.e. OptionalString *****
+
+auto os = redis.get("kk");
+if (os) {
+    std::cout << *os << std::endl;
+} else {
+    std::cerr << "key doesn't exist" << std::endl;
+}
+
+os = redis.spop("set");
+if (os) {
+    std::cout << *os << std::endl;
+} else {
+    std::cerr << "set is empty" << std::endl;
+}
+
+// ***** Return Optional<long long>, i.e. OptionalLongLong *****
+
+auto oll = redis.zrank("zset", "mem");
+if (oll) {
+    std::cout << "rank is " << *oll << std::endl;
+} else {
+    std::cerr << "member doesn't exist" << std::endl;
+}
+
+// ***** Return Optional<double>, i.e. OptionalDouble *****
+
+auto ob = redis.zscore("zset", "m1");
+if (ob) {
+    std::cout << "score is " << *ob << std::endl;
+} else {
+    std::cerr << "member doesn't exist" << std::endl;
+}
+
+// ***** Return Optional<pair<string, string>> *****
+
+auto op = redis.blpop({"list1", "list2"}, std::chrono::seconds(2));
+if (op) {
+    std::cout << "key is " << op->first << ", value is " << op->second << std::endl;
+} else {
+    std::cerr << "timeout" << std::endl;
+}
+
+// ***** Output iterators *****
+
+std::vector<OptionalString> os_vec;
+redis.mget({"k1", "k2", "k3"}, std::back_inserter(os_vec));
+
+std::vector<std::string> s_vec;
+redis.lrange("list", 0, -1, std::back_inserter(s_vec));
+
+std::unordered_map<std::string, std::string> hash;
+redis.hgetall("hash", std::inserter(hash, hash.end()));
+// You can also save the result in a vecotr of string pair.
+std::vector<std::pair<std::string, std::string>> hash_vec;
+redis.hgetall("hash", std::back_inserter(hash_vec));
+
+std::unordered_set<std::string> str_set;
+redis.smembers("s1", std::inserter(str_set, str_set.end()));
+// You can also save the result in a vecotr of string.
+s_vec.clear();
+redis.smembers("s1", std::back_inserter(s_vec));
+```
+
+##### SCAN Commands
+
+```C++
+auto cursor = 0LL;
+auto pattern = "*pattern*";
+auto count = 5;
+std::vector<std::string> scan_vec;
+while (true) {
+    cursor = redis.scan(cursor, pattern, count, std::back_inserter(scan_vec));
+    // Default pattern is "*", and default count is 10
+    // cursor = redis.scan(cursor, std::back_inserter(scan_vec));
+
+    if (cursor == 0) {
+        break;
+    }
+}
+```
+
+##### Command Overloads
+
+Sometimes the type of output iterator decides which options to send with the command.
+
+```C++
+// If the output iterator is an iterator of a container of string,
+// we send *ZRANGE* command without the *WITHSCORES* option.
+std::vector<std::string> members;
+redis.zrange("list", 0, -1, std::back_inserter(members));
+
+// If it's an iterator of a container of a <string, double> pair,
+// we send *ZRANGE* command with *WITHSCORES* option.
+std::unordered_map<std::string, double> res_with_score;
+redis.zrange("list", 0, -1, std::inserter(res_with_score, res_with_score.end()));
+
+// The above examples also apply to other command with the *WITHSCORES* options,
+// e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*.
+
+// Another example is the *GEORADIUS* command.
+
+// Only get members.
+members.clear();
+redis.georadius("geo",
+            std::make_pair(10.1, 11.1),
+            100,
+            GeoUnit::KM,
+            10,
+            true,
+            std::back_inserter(members));
+
+// If the iterator is an iterator of a container of tuple<string, double>,
+// we send the *GEORADIUS* command with *WITHDIST* option.
+std::vector<std::tuple<std::string, double>> mem_with_dist;
+redis.georadius("geo",
+            std::make_pair(10.1, 11.1),
+            100,
+            GeoUnit::KM,
+            10,
+            true,
+            std::back_inserter(mem_with_dist));
+
+// If the iterator is an iterator of a container of tuple<string, double, string>,
+// we send the *GEORADIUS* command with *WITHDIST* and *WITHHASH* options.
+std::vector<std::tuple<std::string, double, std::string>> mem_with_dist_hash;
+redis.georadius("geo",
+            std::make_pair(10.1, 11.1),
+            100,
+            GeoUnit::KM,
+            10,
+            true,
+            std::back_inserter(mem_with_dist_hash));
+
+// If the iterator is an iterator of a container of
+// tuple<string, string, pair<double, double>, double>,
+// we send the *GEORADIUS* command with *WITHHASH*, *WITHCOORD* and *WITHDIST* options.
+std::vector<std::tuple<std::string, double, std::string>> mem_with_hash_coord_dist;
+redis.georadius("geo",
+            std::make_pair(10.1, 11.1),
+            100,
+            GeoUnit::KM,
+            10,
+            true,
+            std::back_inserter(mem_with_hash_coord_dist));
+```
+
+Please see [redis.h](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/redis.h) for more API references, and see the [tests](https://github.com/sewenew/redis-plus-plus/tree/master/test/src/sw/redis%2B%2B) for more examples.
+
+### Generic Command Interface
+
+There're too many Redis commands, we haven't implemented all of them. However, you can use the generic `Redis::command` methods to send any commands to Redis. Unlike other client libraries, `Redis::command` doesn't use format string to combine command arguments into a command string. Instead, you can directly pass command arguments of `StringView` type or arithmetic type as parameters of `Redis::command`. For the reason why we don't use format string, please see [this discussion](https://github.com/sewenew/redis-plus-plus/pull/2).
+
+```C++
+auto redis = Redis("tcp://127.0.0.1");
+
+// Redis class doesn't have built-in *CLIENT SETNAME* method.
+// However, you can use Redis::command to send the command manually.
+redis.command<void>("client", "setname", "name");
+auto val = redis.command<OptionalString>("client", "getname");
+if (val) {
+    std::cout << *val << std::endl;
+}
+
+// NOTE: the following code is for example only. In fact, Redis has built-in
+// methods for the following commands.
+
+// Arguments of the command can be strings.
+// NOTE: for SET command, the return value is NOT always void, I'll explain latter.
+redis.command<void>("set", "key", "100");
+
+// Arguments of the command can be a combination of strings and integers.
+auto num = redis.command<long long>("incrby", "key", 1);
+
+// Argument can also be double.
+auto real = redis.command<double>("incrbyfloat", "key", 2.3);
+
+// Even the key of the command can be of arithmetic type.
+redis.command<void>("set", 100, "value");
+
+val = redis.command<OptionalString>("get", 100);
+
+// If the command returns an array of elements.
+std::vector<OptionalString> result;
+redis.command("mget", "k1", "k2", "k3", std::back_inserter(result));
+
+// Or just parse it into a vector.
+result = redis.command<std::vector<OptionalString>>("mget", "k1", "k2", "k3");
+
+// Arguments of the command can be a range of strings.
+auto set_cmd_strs = {"set", "key", "value"};
+redis.command<void>(set_cmd_strs.begin(), set_cmd_strs.end());
+
+auto get_cmd_strs = {"get", "key"};
+val = redis.command<OptionalString>(get_cmd_strs.begin(), get_cmd_strs.end());
+
+// If it returns an array of elements.
+result.clear();
+auto mget_cmd_strs = {"mget", "key1", "key2"};
+redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end(), std::back_inserter(result));
+```
+
+**NOTE**: The name of some Redis commands is composed with two strings, e.g. *CLIENT SETNAME*. In this case, you need to pass these two strings as two arguments for `Redis::command`.
+
+```C++
+// This is GOOD.
+redis.command<void>("client", "setname", "name");
+
+// This is BAD, and will fail to send command to Redis server.
+// redis.command<void>("client setname", "name");
+```
+
+As I mentioned in the comments, the `SET` command not always returns `void`. Because if you try to set a (key, value) pair with *NX* or *XX* option, you might fail, and Redis will return a *NULL REPLY*. Besides the `SET` command, there're other commands whose return value is NOT a fixed type, you need to parse it by yourself. For example, `Redis::set` method rewrite the reply of `SET` command, and make it return `bool` type, i.e. if no *NX* or *XX* option specified, Redis server will always return an "OK" string, and `Redis::set` returns `true`; if *NX* or *XX* specified, and Redis server returns a *NULL REPLY*, `Redis::set` returns `false`.
+
+So `Redis` class also has other overloaded `command` methods, these methods return a `ReplyUPtr`, i.e. `std::unique_ptr<redisReply, ReplyDeleter>`, object. Normally you don't need to parse it manually. Instead, you only need to pass the reply to `template <typename T> T reply::parse(redisReply &)` to get a value of type `T`. Check the [Return Type section](#return-type) for valid `T` types. If the command returns an array of elements, besides calling `reply::parse` to parse the reply to an STL container, you can also call `template <typename Output> reply::to_array(redisReply &reply, Output output)` to parse the result into an array or STL container with an output iterator.
+
+Let's rewrite the above examples:
+
+```C++
+auto redis = Redis("tcp://127.0.0.1");
+
+redis.command("client", "setname", "name");
+auto r = redis.command("client", "getname");
+assert(r);
+
+// If the command returns a single element,
+// use `reply::parse<T>(redisReply&)` to parse it.
+auto val = reply::parse<OptionalString>(*r);
+if (val) {
+    std::cout << *val << std::endl;
+}
+
+// Arguments of the command can be strings.
+redis.command("set", "key", "100");
+
+// Arguments of the command can be a combination of strings and integers.
+r = redis.command("incrby", "key", 1);
+auto num = reply::parse<long long>(*r);
+
+// Argument can also be double.
+r = redis.command("incrbyfloat", "key", 2.3);
+auto real = reply::parse<double>(*r);
+
+// Even the key of the command can be of arithmetic type.
+redis.command("set", 100, "value");
+
+r = redis.command("get", 100);
+val = reply::parse<OptionalString>(*r);
+
+// If the command returns an array of elements.
+r = redis.command("mget", "k1", "k2", "k3");
+// Use `reply::to_array(redisReply&, OutputIterator)` to parse the result into an STL container.
+std::vector<OptionalString> result;
+reply::to_array(*r, std::back_inserter(result));
+
+// Or just call `reply::parse` to parse it into vector.
+result = reply::parse<std::vector<OptionalString>>(*r);
+
+// Arguments of the command can be a range of strings.
+auto get_cmd_strs = {"get", "key"};
+r = redis.command(get_cmd_strs.begin(), get_cmd_strs.end());
+val = reply::parse<OptionalString>(*r);
+
+// If it returns an array of elements.
+result.clear();
+auto mget_cmd_strs = {"mget", "key1", "key2"};
+r = redis.command(mget_cmd_strs.begin(), mget_cmd_strs.end());
+reply::to_array(*r, std::back_inserter(result));
+```
+
+In fact, there's one more `Redis::command` method:
+
+```C++
+template <typename Cmd, typename ...Args>
+auto command(Cmd cmd, Args &&...args)
+    -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type;
+```
+
+However, this method exposes some implementation details, and is only for internal use. You should NOT use this method.
+
+### Publish/Subscribe
+
+You can use `Redis::publish` to publish messages to channels. `Redis` randomly picks a connection from the underlying connection pool, and publishes message with that connection. So you might publish two messages with two different connections.
+
+When you subscribe to a channel with a connection, all messages published to the channel are sent back to that connection. So there's NO `Redis::subscribe` method. Instead, you can call `Redis::subscriber` to create a `Subscriber` and the `Subscriber` maintains a connection to Redis. The underlying connection is a new connection, NOT picked from the connection pool. This new connection has the same `ConnectionOptions` as the `Redis` object.
+
+With `Subscriber`, you can call `Subscriber::subscribe`, `Subscriber::unsubscribe`, `Subscriber::psubscribe` and `Subscriber::punsubscribe` to send *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* commands to Redis.
+
+#### Thread Safety
+
+`Subscriber` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually.
+
+#### Subscriber Callbacks
+
+There are 6 kinds of messages:
+- *MESSAGE*: message sent to a channel.
+- *PMESSAGE*: message sent to channels of a given pattern.
+- *SUBSCRIBE*: message sent when we successfully subscribe to a channel.
+- *UNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel.
+- *PSUBSCRIBE*: message sent when we successfully subscribe to a channel pattern.
+- *PUNSUBSCRIBE*: message sent when we successfully unsubscribe to a channel pattern.
+
+We call messages of *SUBSCRIBE*, *UNSUBSCRIBE*, *PSUBSCRIBE* and *PUNSUBSCRIBE* types as *META MESSAGE*s.
+
+In order to process these messages, you can set callback functions on `Subscriber`:
+- `Subscriber::on_message(MsgCallback)`: set callback function for messages of *MESSAGE* type, and the callback interface is: `void (std::string channel, std::string msg)`.
+- `Subscriber::on_pmessage(PatternMsgCallback)`: set the callback function for messages of *PMESSAGE* type, and the callback interface is: `void (std::string pattern, std::string channel, std::string msg)`.
+- `Subscriber::on_meta(MetaCallback)`: set callback function for messages of *META MESSAGE* type, and the callback interface is: `void (Subscriber::MsgType type, OptionalString channel, long long num)`. `type` is an enum, it can be one of the following enum: `Subscriber::MsgType::SUBSCRIBE`, `Subscriber::MsgType::UNSUBSCRIBE`, `Subscriber::MsgType::PSUBSCRIBE`, `Subscriber::MsgType::PUNSUBSCRIBE`, `Subscriber::MsgType::MESSAGE`, and `Subscriber::MsgType::PMESSAGE`. If you haven't subscribe/psubscribe to any channel/pattern, and try to unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all channels/patterns, *channel* will be null. So the second parameter of meta callback is of type `OptionalString`.
+
+All these callback interfaces pass `std::string` by value, and you can take their ownership (i.e. `std::move`) safely.
+
+#### Consume Messages
+
+You can call `Subscriber::consume` to consume messages published to channels/patterns that the `Subscriber` has been subscribed.
+
+`Subscriber::consume` waits for message from the underlying connection. If the `ConnectionOptions::socket_timeout` is reached, and there's no message sent to this connection, `Subscriber::consume` throws a `TimeoutError` exception. If `ConnectionOptions::socket_timeout` is `0ms`, `Subscriber::consume` blocks until it receives a message.
+
+After receiving the message, `Subscriber::consume` calls the callback function to process the message based on message type. However, if you don't set callback for a specific kind of message, `Subscriber::consume` will ignore the received message, i.e. no callback will be called.
+
+#### Examples
+
+The following example is a common pattern for using `Subscriber`:
+
+```C++
+// Create a Subscriber.
+auto sub = redis.subscriber();
+
+// Set callback functions.
+sub.on_message([](std::string channel, std::string msg) {
+            // Process message of MESSAGE type.
+        });
+
+sub.on_pmessage([](std::string pattern, std::string channel, std::string msg) {
+            // Process message of PMESSAGE type.
+        });
+
+sub.on_meta([](Subscriber::MsgType type, OptionalString channel, long long num) {
+            // Process message of META type.
+        });
+
+// Subscribe to channels and patterns.
+sub.subscribe("channel1");
+sub.subscribe({"channel2", "channel3"});
+
+sub.psubscribe("pattern1*");
+
+// Consume messages in a loop.
+while (true) {
+    try {
+        sub.consume();
+    } catch (const Error &err) {
+        // Handle exceptions.
+    }
+}
+```
+
+If `ConnectionOptions::socket_timeout` is set, you might get `TimeoutError` exception before receiving a message:
+
+```C++
+while (true) {
+    try {
+        sub.consume();
+    } catch (const TimeoutError &e) {
+        // Try again.
+        continue;
+    } catch (const Error &err) {
+        // Handle other exceptions.
+    }
+}
+```
+
+The above examples use lambda as callback. If you're not familiar with lambda, you can also set a free function as callback. Check [this issue](https://github.com/sewenew/redis-plus-plus/issues/16) for detail.
+
+### Pipeline
+
+[Pipeline](https://redis.io/topics/pipelining) is used to reduce *RTT* (Round Trip Time), and speed up Redis queries. *redis-plus-plus* supports pipeline with the `Pipeline` class.
+
+#### Create Pipeline
+
+You can create a pipeline with `Redis::pipeline` method, which returns a `Pipeline` object.
+
+```C++
+ConnectionOptions connection_options;
+ConnectionPoolOptions pool_options;
+
+Redis redis(connection_options, pool_options);
+
+auto pipe = redis.pipeline();
+```
+
+When creating a `Pipeline` object, `Redis::pipeline` method creates a new connection to Redis server. This connection is NOT picked from the connection pool, but a newly created connection. This connection has the same `ConnectionOptions` as other connections in the connection pool. `Pipeline` object maintains the new connection, and all piped commands are sent through this connection.
+
+**NOTE**: Creating a `Pipeline` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Pipeline` object as much as possible.
+
+#### Send Commands
+
+You can send Redis commands through the `Pipeline` object. Just like the `Redis` class, `Pipeline` has one or more (overloaded) methods for each Redis command. However, you CANNOT get the replies until you call `Pipeline::exec`. So these methods do NOT return the reply, instead they return the `Pipeline` object itself. And you can chain these methods calls.
+
+```C++
+pipe.set("key", "val").incr("num").rpush("list", {0, 1, 2}).command("hset", "key", "field", "value");
+```
+
+#### Get Replies
+
+Once you finish sending commands to Redis, you can call `Pipeline::exec` to get replies of these commands. You can also chain `Pipeline::exec` with other commands.
+
+```C++
+pipe.set("key", "val").incr("num");
+auto replies = pipe.exec();
+
+// The same as:
+replies = pipe.set("key", "val").incr("num).exec();
+```
+
+In fact, these commands won't be sent to Redis, until you call `Pipeline::exec`. So `Pipeline::exec` does 2 work in order: send all piped commands, then get all replies from Redis.
+
+Also you can call `Pipeline::discard` to discard those piped commands.
+
+```C++
+pipe.set("key", "val").incr("num");
+
+pipe.discard();
+```
+
+#### Parse Replies
+
+`Pipeline::exec` returns a `QueuedReplies` object, which contains replies of all commands that have been sent to Redis. You can use `QueuedReplies::get` method to get and parse the `ith` reply. It has 3 overloads:
+
+- `template <typename Result> Result get(std::size_t idx)`: Return the `ith` reply as a return value, and you need to specify the return type as tempalte parameter.
+- `template <typename Output> void get(std::size_t idx, Output output)`: If the reply is of type *Array Reply*, you can call this method to write the `ith` reply to an output iterator. Normally, compiler will deduce the type of the output iterator, and you don't need to specify the type parameter explicitly.
+- `redisReply& get(std::size_t idx)`: If the reply is NOT a fixed type, call this method to get a reference to `redisReply` object. In this case, you need to call `template <typename T> T reply::parse(redisReply &)` to parse the reply manually.
+
+Check the [Return Type section](#return-type) for details on the return types of the result.
+
+```C++
+auto replies = pipe.set("key", "val").incr("num").lrange("list", 0, -1).exec();
+
+auto set_cmd_result = replies.get<bool>(0);
+
+auto incr_cmd_result = replies.get<long long>(1);
+
+std::vector<std::string> list_cmd_result;
+replies.get(2, std::back_inserter(list_cmd_result));
+```
+
+#### Exception
+
+If any of `Pipeline`'s method throws an exception, the `Pipeline` object enters an invalid state. You CANNOT use it any more, but only destroy the object, and create a new one.
+
+#### Thread Safety
+
+`Pipeline` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually.
+
+### Transaction
+
+[Transaction](https://redis.io/topics/transactions) is used to make multiple commands runs atomically.
+
+#### Create Transaction
+
+You can create a transaction with `Redis::transaction` method, which returns a `Transaction` object.
+
+```C++
+ConnectionOptions connection_options;
+ConnectionPoolOptions pool_options;
+
+Redis redis(connection_options, pool_options);
+
+auto tx = redis.transaction();
+```
+
+As the `Pipeline` class, `Transaction` maintains a newly created connection to Redis. This connection has the same `ConnectionOptions` as the `Redis` object.
+
+**NOTE**: Creating a `Transaction` object is NOT cheap, since it creates a new connection. So you'd better reuse the `Transaction` as much as possible.
+
+Also you don't need to send [MULTI](https://redis.io/commands/multi) command to Redis. `Transaction` will do that for you automatically.
+
+#### Send Commands
+
+`Transaction` shares most of implementation with `Pipeline`. It has the same interfaces as `Pipeline`. You can send commands as what you do with `Pipeline` object.
+
+```C++
+tx.set("key", "val").incr("num").lpush("list", {0, 1, 2}).command("hset", "key", "field", "val");
+```
+
+#### Execute Transaction
+
+When you call `Transaction::exec`, you explicitly ask Redis to execute those queued commands, and return the replies. Otherwise, these commands won't be executed. Also, you can call `Transaction::discard` to discard the execution, i.e. no command will be executed. Both `Transaction::exec` and `Transaction::discard` can be chained with other commands.
+
+```C++
+auto replies = tx.set("key", "val").incr("num").exec();
+
+tx.set("key", "val").incr("num");
+
+// Discard the transaction.
+tx.discard();
+```
+
+#### Parse Replies
+
+See [Pipeline's Parse Replies section](#parse-replies) for how to parse the replies.
+
+#### Piped Transaction
+
+Normally, we always send multiple commnds in a transaction. In order to improve the performance, you can send these commands in a pipeline. You can create a piped transaction by passing `true` as parameter of `Redis::transaction` method.
+
+```C++
+// Create a piped transaction
+auto tx = redis.transaction(true);
+```
+
+With this piped transaction, all commands are sent to Redis in a pipeline.
+
+#### Exception
+
+If any of `Transaction`'s method throws an exception other than `WatchError`, the `Transaction` object enters an invalid state. You CANNOT use it any more, but only destroy the object and create a new one.
+
+#### Thread Safety
+
+`Transacation` is NOT thread-safe. If you want to call its member functions in multi-thread environment, you need to synchronize between threads manually.
+
+#### Watch
+
+[WATCH is used to provide a check-and-set(CAS) behavior to Redis transactions](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set).
+
+The `WATCH` command must be sent in the same connection as the transaction. And normally after the `WATCH` command, we also need to send some other commands to get data from Redis before executing the transaction. Take the following check-and-set case as an example:
+
+```
+WATCH key           // watch a key
+val = GET key       // get value of the key
+new_val = val + 1   // incr the value
+MULTI               // begin the transaction
+SET key new_val     // set value only if the value is NOT modified by others
+EXEC                // try to execute the transaction.
+                    // if val has been modified, the transaction won't be executed.
+```
+
+However, with `Transaction` object, you CANNOT get the result of commands until the whole transaction has been finished. Instead, you need to create a `Redis` object from the `Transaction` object. The created `Redis` object shares the connection with `Transaction` object. With this created `Redis` object, you can send `WATCH` command and any other Redis commands to Redis server, and get the result immediately.
+
+Let's see how to implement the above example with *redis-plus-plus*:
+
+```C++
+auto redis = Redis("tcp://127.0.0.1");
+
+// Create a transaction.
+auto tx = redis.transaction();
+
+// Create a Redis object from the Transaction object. Both objects share the same connection.
+auto r = tx.redis();
+
+// If the watched key has been modified by other clients, the transaction might fail.
+// So we need to retry the transaction in a loop.
+while (true) {
+    try {
+        // Watch a key.
+        r.watch("key");
+
+        // Get the old value.
+        auto val = r.get("key");
+        auto num = 0;
+        if (val) {
+            num = std::stoi(*val);
+        } // else use default value, i.e. 0.
+
+        // Incr value.
+        ++num;
+
+        // Execute the transaction.
+        auto replies = tx.set("key", std::to_string(num)).exec();
+
+        // Transaction has been executed successfully. Check the result and break.
+
+        assert(replies.size() == 1 && replies.get<bool>(0) == true);
+
+        break;
+    } catch (const WatchError &err) {
+        // Key has been modified by other clients, retry.
+        continue;
+    } catch (const Error &err) {
+        // Something bad happens, and the Transaction object is no longer valid.
+        throw;
+    }
+}
+```
+
+### Redis Cluster
+
+*redis-plus-plus* supports [Redis Cluster](https://redis.io/topics/cluster-tutorial). You can use `RedisCluster` class to send commands to Redis Cluster. It has similar interfaces as `Redis` class.
+
+#### Connection
+
+`RedisCluster` connects to all master nodes in the cluster. For each master node, it maintains a connection pool. By now, it doesn't connect to slave nodes.
+
+You can initialize a `RedisCluster` instance with `ConnectionOptions` and `ConnectionPoolOptions`. You only need to set one master node's host & port in `ConnectionOptions`, and `RedisCluster` will get other nodes' info automatically (with the *CLUSTER SLOTS* command). For each master node, it creates a connection pool with the specified `ConnectionPoolOptions`. If `ConnectionPoolOptions` is not specified, `RedisCluster` maintains a single connection to every master node.
+
+```C++
+// Set a master node's host & port.
+ConnectionOptions connection_options;
+connection_options.host = "127.0.0.1";  // Required.
+connection_options.port = 7000; // Optional. The default port is 6379.
+connection_options.password = "auth"; // Optional. No password by default.
+
+// Automatically get other nodes' info,
+// and connect to every master node with a single connection.
+RedisCluster cluster1(connection_options);
+
+ConnectionPoolOptions pool_options;
+pool_options.size = 3;
+
+// For each master node, maintains a connection pool of size 3.
+RedisCluster cluster2(connection_options, pool_options);
+```
+
+You can also specify connection option with an URI. However, in this way, you can only use default `ConnectionPoolOptions`, i.e. pool of size 1, and CANNOT specify password.
+
+```C++
+// Specify a master node's host & port.
+RedisCluster cluster3("tcp://127.0.0.1:7000");
+
+// Use default port, i.e. 6379.
+RedisCluster cluster4("tcp://127.0.0.1");
+```
+
+##### Note
+
+- `RedisCluster` only works with tcp connection. It CANNOT connect to Unix Domain Socket. If you specify Unix Domain Socket in `ConnectionOptions`, it throws an exception.
+- All nodes in the cluster should have the same password.
+- Since [Redis Cluster does NOT support multiple databses](https://redis.io/topics/cluster-spec#implemented-subset), `ConnectionOptions::db` is ignored.
+
+#### Interfaces
+
+As we mentioned above, `RedisCluster`'s interfaces are similar to `Redis`. It supports most of `Redis`' interfaces, including the [generic command interface](#generic-command-interface) (see `Redis`' [API Reference section](#api-reference) for details), except the following:
+
+- Not support commands without key as argument, e.g. `PING`, `INFO`.
+- Not support Lua script without key parameters.
+
+Since there's no key parameter, `RedisCluster` has no idea on to which node these commands should be sent. However there're 2 workarounds for this problem:
+
+- If you want to send these commands to a specific node, you can create a `Redis` object with that node's host and port, and use the `Redis` object to do the work.
+- Instead of host and port, you can also call `Redis RedisCluster::redis(const StringView &hash_tag)` to create a `Redis` object with a hash-tag specifying the node. In this case, the returned `Redis` object creates a new connection to Redis server.
+
+Also you can use the [hash tags](https://redis.io/topics/cluster-spec#keys-hash-tags) to send multiple-key commands.
+
+See the [example section](#examples-2) for details.
+
+##### Publish/Subscribe
+
+You can publish and subscribe messages with `RedisCluster`. The interfaces are exactly the same as `Redis`, i.e. use `RedisCluster::publish` to publish messages, and use `RedisCluster::subscriber` to create a subscriber to consume messages. See [Publish/Subscribe section](#publishsubscribe) for details.
+
+##### Pipeline and Transaction
+
+You can also create `Pipeline` and `Transaction` objects with `RedisCluster`, but the interfaces are different from `Redis`. Since all commands in the pipeline and transaction should be sent to a single node in a single connection, we need to tell `RedisCluster` with which node the pipeline or transaction should be created.
+
+Instead of specifing the node's IP and port, `RedisCluster`'s pipeline and transaction interfaces allow you to specify the node with a *hash tag*. `RedisCluster` will calculate the slot number with the given *hash tag*, and create a pipeline or transaction with the node holding the slot.
+
+```C++
+Pipeline RedisCluster::pipeline(const StringView &hash_tag);
+
+Transaction RedisCluster::transaction(const StringView &hash_tag, bool piped = false);
+```
+
+With the created `Pipeline` or `Transaction` object, you can send commands with keys located on the same node as the given *hash_tag*. See [Examples section](#examples-2) for an example.
+
+#### Examples
+
+```C++
+#include <sw/redis++/redis++.h>
+
+using namespace sw::redis;
+
+auto redis_cluster = RedisCluster("tcp://127.0.0.1:7000");
+
+redis_cluster.set("key", "value");
+auto val = redis_cluster.get("key");
+if (val) {
+    std::cout << *val << std::endl;
+}
+
+// With hash-tag.
+redis_cluster.set("key{tag}1", "val1");
+redis_cluster.set("key{tag}2", "val2");
+redis_cluster.set("key{tag}3", "val3");
+std::vector<OptionalString> hash_tag_res;
+redis_cluster.mget({"key{tag}1", "key{tag}2", "key{tag}3"},
+        std::back_inserter(hash_tag_res));
+
+redis_cluster.lpush("list", {"1", "2", "3"});
+std::vector<std::string> list;
+redis_cluster.lrange("list", 0, -1, std::back_inserter(list));
+
+// Pipline.
+auto pipe = redis_cluster.pipeline("counter");
+auto replies = pipe.incr("{counter}:1").incr("{counter}:2").exec();
+
+// Transaction.
+auto tx = redis_cluster.transaction("key");
+replies = tx.incr("key").get("key").exec();
+
+// Create a Redis object with hash-tag.
+// It connects to the Redis instance that holds the given key, i.e. hash-tag.
+auto r = redis_cluster.redis("hash-tag");
+
+// And send command without key parameter to the server.
+r.command("client", "setname", "connection-name");
+```
+
+**NOTE**: When you use `RedisCluster::redis(const StringView &hash_tag)` to create a `Redis` object, instead of picking a connection from the underlying connection pool, it creates a new connection to the corresponding Redis server. So this is NOT a cheap operation, and you should try to reuse this newly created `Redis` object as much as possible.
+
+```C++
+// This is BAD! It's very inefficient.
+// NEVER DO IT!!!
+// After sending PING command, the newly created Redis object will be destroied.
+cluster.redis("key").ping();
+
+// Then it creates a connection to Redis, and closes the connection after sending the command.
+cluster.redis("key").command("client", "setname", "hello");
+
+// Instead you should reuse the Redis object.
+// This is GOOD!
+auto redis = cluster.redis("key");
+
+redis.ping();
+redis.command("client", "setname", "hello");
+```
+
+#### Details
+
+`RedisCluster` maintains the newest slot-node mapping, and sends command directly to the right node. Normally it works as fast as `Redis`. If the cluster reshards, `RedisCluster` will follow the redirection, and it will finally update the slot-node mapping. It can correctly handle the following resharding cases:
+
+- Data migration between exist nodes.
+- Add new node to the cluster.
+- Remove node from the cluster.
+
+`redis-plus-plus` is able to handle both [MOVED](https://redis.io/topics/cluster-spec#moved-redirection) and [ASK](https://redis.io/topics/cluster-spec#ask-redirection) redirections, so it's a complete Redis Cluster client.
+
+If master is down, the cluster will promote one of its replicas to be the new master. *redis-plus-plus* can also handle this case:
+
+- When the master is down, *redis-plus-plus* losts connection to it. In this case, if you try to send commands to this master, *redis-plus-plus* will try to update slot-node mapping from other nodes. If the mapping remains unchanged, i.e. new master hasn't been elected yet, it fails to send command to Redis Cluster and throws exception.
+- When the new master has been elected, the slot-node mapping will be updated by the cluster. In this case, if you send commands to the cluster, *redis-plus-plus* can get an update-to-date mapping, and sends commands to the new master.
+
+### Redis Sentinel
+
+[Redis Sentinel provides high availability for Redis](https://redis.io/topics/sentinel). If Redis master is down, Redis Sentinels will elect a new master from slaves, i.e. failover. Besides, Redis Sentinel can also act like a configuration provider for clients, and clients can query master or slave address from Redis Sentinel. So that if a failover occurs, clients can ask the new master address from Redis Sentinel.
+
+*redis-plus-plus* supports getting Redis master or slave's IP and port from Redis Sentinel. In order to use this feature, you only need to initialize `Redis` object with Redis Sentinel info, which is composed with 3 parts: `std::shared_ptr<Sentinel>`, master name and role (master or slave).
+
+Before using Redis Sentinel with *redis-plus-plus*, ensure that you have read Redis Sentinel's [doc](https://redis.io/topics/sentinel).
+
+#### Sentinel
+
+You can create a `std::shared_ptr<Sentinel>` object with `SentinelOptions`.
+
+```C++
+SentinelOptions sentinel_opts;
+sentinel_opts.nodes = {{"127.0.0.1", 9000},
+                        {"127.0.0.1", 9001},
+                        {"127.0.0.1", 9002}};   // Required. List of Redis Sentinel nodes.
+
+// Optional. Timeout before we successfully connect to Redis Sentinel.
+// By default, the timeout is 100ms.
+sentinel_opts.connect_timeout = std::chrono::milliseconds(200);
+
+// Optional. Timeout before we successfully send request to or receive response from Redis Sentinel.
+// By default, the timeout is 100ms.
+sentinel_opts.socket_timeout = std::chrono::milliseconds(200);
+
+auto sentinel = std::make_shared<Sentinel>(sentinel_opts);
+```
+
+`SentinelOptions::connect_timeout` and `SentinelOptions::socket_timeout` CANNOT be 0ms, i.e. no timeout and block forever. Otherwise, *redis-plus-plus* will throw an exception.
+
+See [SentinelOptions](https://github.com/sewenew/redis-plus-plus/blob/master/src/sw/redis%2B%2B/sentinel.h#L33) for more options.
+
+#### Role
+
+Besides `std::shared_ptr<Sentinel>` and master name, you also need to specify a role. There are two roles: `Role::MASTER`, and `Role::SLAVE`.
+
+With `Role::MASTER`, *redis-plus-plus* will always connect to current master instance, even if a failover occurs. Each time when *redis-plus-plus* needs to create a new connection to master, or a connection is broken, and it needs to reconnect to master, *redis-plus-plus* will ask master address from Redis Sentinel, and connects to current master. If a failover occurs, *redis-plus-plus* can automatically get the address of the new master, and refresh all connections in the underlying connection pool.
+
+Similarly, with `Role::SLAVE`, *redis-plus-plus* will always connect to a slave instance. A master might have several slaves, *redis-plus-plus* will randomly pick one, and connect to it, i.e. all connections in the underlying connection pool, connect to the same slave instance. If the connection is broken, while this slave instance is still an alive slave, *redis-plus-plus* will reconnect to this slave. However, if this slave instance is down, or it has been promoted to be the master, *redis-plus-plus* will randomly connect to another slave. If there's no slave alive, it throws an exception.
+
+#### Create Redis With Sentinel
+
+When creating a `Redis` object with sentinel, besides the sentinel info, you should also provide `ConnectionOptions` and `ConnectionPoolOptions`. These two options are used to connect to Redis instance. `ConnectionPoolOptions` is optional, if not specified, it creates a single connection the instance.
+
+```C++
+ConnectionOptions connection_opts;
+connection_opts.password = "auth";  // Optional. No password by default.
+connection_opts.connect_timeout = std::chrono::milliseconds(100);   // Required.
+connection_opts.socket_timeout = std::chrono::milliseconds(100);    // Required.
+
+ConnectionPoolOptions pool_opts;
+pool_opts.size = 3; // Optional. The default size is 1.
+
+auto redis = Redis(sentinel, "master_name", Role::MASTER, connection_opts, pool_opts);
+```
+
+You might have noticed that we didn't specify the `host` and `port` fields for `ConnectionOptions`. Because, `Redis` will get these info from Redis Sentinel. Also, in this case, `ConnectionOptions::connect_timeout` and `ConnectionOptions::socket_timeout` CANNOT be 0ms, otherwise, it throws an exception. So you always need to specify these two timeouts manually.
+
+After creating the `Redis` object with sentinel, you can send commands with it, just like an ordinary `Redis` object.
+
+If you want to write to master, and scale read with slaves. You can use the following pattern:
+
+```C++
+auto sentinel = std::make_shared<Sentinel>(sentinel_opts);
+
+auto master = Redis(sentinel, "master_name", Role::MASTER, connection_opts, pool_opts);
+
+auto slave = Redis(sentinel, "master_name", Role::SLAVE, connection_opts, pool_opts);
+
+// Write to master.
+master.set("key", "value");
+
+// Read from slave.
+slave.get("key");
+```
+
+### Redis Stream
+
+Since Redis 5.0, it introduces a new data type: *Redis Stream*. *redis-plus-plus* has built-in methods for all stream commands except the *XINFO* command (of course, you can use the [Generic Command Interface](#generic-command-interface) to send *XINFO* command).
+
+However, the replies of some streams commands, i.e. *XPENDING*, *XREAD*, are complex. So I'll give some examples to show you how to work with these built-in methods.
+
+#### Examples
+
+```C++
+auto redis = Redis("tcp://127.0.0.1");
+
+using Attrs = std::vector<std::pair<std::string, std::string>>;
+
+// You can also use std::unordered_map, if you don't care the order of attributes:
+// using Attrs = std::unordered_map<std::string, std::string>;
+
+Attrs attrs = { {"f1", "v1"}, {"f2", "v2"} };
+
+// Add an item into the stream. This method returns the auto generated id.
+auto id = redis.xadd("key", "*", attrs.begin(), attrs.end());
+
+// Each item is assigned with an id: pair<id, attributes>.
+using Item = std::pair<std::string, Attrs>;
+using ItemStream = std::vector<Item>;
+
+// If you don't care the order of items in the stream, you can also use unordered_map:
+// using ItemStream = std::unordered_map<std::string, Attrs>;
+
+// Read items from a stream, and return at most 10 items.
+// You need to specify a key and an id (timestamp + offset).
+std::unordered_map<std::string, ItemStream> result;
+redis.xread("key", id, 10, std::inserter(result, result.end()));
+
+// Read from multiple streams. For each stream, you need to specify a key and an id.
+std::unordered_map<std::string, std::string> keys = { {"key", id}, {"another-key", "0-0"} };
+redis.xread(keys.begin(), keys.end(), 10, std::inserter(result, result.end()));
+
+// Block for at most 1 second if currently there's no data in the stream.
+redis.xread("key", id, std::chrono::seconds(1), 10, std::inserter(result, result.end()));
+
+// Block for multiple streams.
+redis.xread(keys.begin(), keys.end(), std::chrono::seconds(1), 10, std::inserter(result, result.end()));
+
+// Read items in a range:
+ItemStream item_stream;
+redis.xrange("key", "-", "+", std::back_inserter(item_stream));
+
+// Trim the stream to a given number of items. After the operation, the stream length is NOT exactly
+// 10. Instead, it might be much larger than 10.
+// `XTRIM key MAXLEN 10`
+redis.xtrim("key", 10);
+
+// In order to trim the stream to exactly 10 items, specify the third argument, i.e. approx, as false.
+// `XTRIM key MAXLEN ~ 10`
+redis.xtrim("key", 10, false);
+
+// Delete an item from the stream.
+redis.xdel("key", id);
+
+// Create a consumer group.
+redis.xgroup_create("key", "group", "$");
+
+// If the stream doesn't exist, you can set the fourth argument, i.e. MKSTREAM, to be true.
+// redis.xgroup_create("key", "group", "$", true);
+
+id = redis.xadd("key", "*", attrs.begin(), attrs.end());
+
+// Read item by a consumer of a consumer group.
+redis.xreadgroup("group", "consumer", "key", ">", 1, std::inserter(result, result.end()));
+
+using PendingItem = std::tuple<std::string, std::string, long long, long long>;
+std::vector<PendingItem> pending_items;
+
+// Get pending items of a speicified consumer.
+redis.xpending("key", "group", "-", "+", 1, "consumer", std::back_inserter(pending_items));
+
+redis.xack("key", "group", id);
+
+redis.xgroup_delconsumer("key", "group", "consumer");
+redis.xgroup_destroy("key", "group");
+```
+
+If you have any problem on sending stream commands to Redis, please feel free to let me know.
+
+## Author
+
+*redis-plus-plus* is written by sewenew, who is also active on [StackOverflow](https://stackoverflow.com/users/5384363/for-stack).

+ 2233 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h

@@ -0,0 +1,2233 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_H
+#define SEWENEW_REDISPLUSPLUS_COMMAND_H
+
+#include <cassert>
+#include <ctime>
+#include <string>
+#include <chrono>
+#include "connection.h"
+#include "command_options.h"
+#include "command_args.h"
+#include "utils.h"
+
+namespace sw {
+
+namespace redis {
+
+namespace cmd {
+
+// CONNECTION command.
+inline void auth(Connection &connection, const StringView &password) {
+    connection.send("AUTH %b", password.data(), password.size());
+}
+
+inline void echo(Connection &connection, const StringView &msg) {
+    connection.send("ECHO %b", msg.data(), msg.size());
+}
+
+inline void ping(Connection &connection) {
+    connection.send("PING");
+}
+
+inline void quit(Connection &connection) {
+    connection.send("QUIT");
+}
+
+inline void ping(Connection &connection, const StringView &msg) {
+    // If *msg* is empty, Redis returns am empty reply of REDIS_REPLY_STRING type.
+    connection.send("PING %b", msg.data(), msg.size());
+}
+
+inline void select(Connection &connection, long long idx) {
+    connection.send("SELECT %lld", idx);
+}
+
+inline void swapdb(Connection &connection, long long idx1, long long idx2) {
+    connection.send("SWAPDB %lld %lld", idx1, idx2);
+}
+
+// SERVER commands.
+
+inline void bgrewriteaof(Connection &connection) {
+    connection.send("BGREWRITEAOF");
+}
+
+inline void bgsave(Connection &connection) {
+    connection.send("BGSAVE");
+}
+
+inline void dbsize(Connection &connection) {
+    connection.send("DBSIZE");
+}
+
+inline void flushall(Connection &connection, bool async) {
+    if (async) {
+        connection.send("FLUSHALL ASYNC");
+    } else {
+        connection.send("FLUSHALL");
+    }
+}
+
+inline void flushdb(Connection &connection, bool async) {
+    if (async) {
+        connection.send("FLUSHDB ASYNC");
+    } else {
+        connection.send("FLUSHDB");
+    }
+}
+
+inline void info(Connection &connection) {
+    connection.send("INFO");
+}
+
+inline void info(Connection &connection, const StringView &section) {
+    connection.send("INFO %b", section.data(), section.size());
+}
+
+inline void lastsave(Connection &connection) {
+    connection.send("LASTSAVE");
+}
+
+inline void save(Connection &connection) {
+    connection.send("SAVE");
+}
+
+// KEY commands.
+
+inline void del(Connection &connection, const StringView &key) {
+    connection.send("DEL %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void del_range(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "DEL" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void dump(Connection &connection, const StringView &key) {
+    connection.send("DUMP %b", key.data(), key.size());
+}
+
+inline void exists(Connection &connection, const StringView &key) {
+    connection.send("EXISTS %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void exists_range(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "EXISTS" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void expire(Connection &connection,
+                    const StringView &key,
+                    long long timeout) {
+    connection.send("EXPIRE %b %lld",
+                    key.data(), key.size(),
+                    timeout);
+}
+
+inline void expireat(Connection &connection,
+                        const StringView &key,
+                        long long timestamp) {
+    connection.send("EXPIREAT %b %lld",
+                    key.data(), key.size(),
+                    timestamp);
+}
+
+inline void keys(Connection &connection, const StringView &pattern) {
+    connection.send("KEYS %b", pattern.data(), pattern.size());
+}
+
+inline void move(Connection &connection, const StringView &key, long long db) {
+    connection.send("MOVE %b %lld",
+                    key.data(), key.size(),
+                    db);
+}
+
+inline void persist(Connection &connection, const StringView &key) {
+    connection.send("PERSIST %b", key.data(), key.size());
+}
+
+inline void pexpire(Connection &connection,
+                    const StringView &key,
+                    long long timeout) {
+    connection.send("PEXPIRE %b %lld",
+                    key.data(), key.size(),
+                    timeout);
+}
+
+inline void pexpireat(Connection &connection,
+                        const StringView &key,
+                        long long timestamp) {
+    connection.send("PEXPIREAT %b %lld",
+                    key.data(), key.size(),
+                    timestamp);
+}
+
+inline void pttl(Connection &connection, const StringView &key) {
+    connection.send("PTTL %b", key.data(), key.size());
+}
+
+inline void randomkey(Connection &connection) {
+    connection.send("RANDOMKEY");
+}
+
+inline void rename(Connection &connection,
+                    const StringView &key,
+                    const StringView &newkey) {
+    connection.send("RENAME %b %b",
+                    key.data(), key.size(),
+                    newkey.data(), newkey.size());
+}
+
+inline void renamenx(Connection &connection,
+                        const StringView &key,
+                        const StringView &newkey) {
+    connection.send("RENAMENX %b %b",
+                    key.data(), key.size(),
+                    newkey.data(), newkey.size());
+}
+
+void restore(Connection &connection,
+                const StringView &key,
+                const StringView &val,
+                long long ttl,
+                bool replace);
+
+inline void scan(Connection &connection,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count) {
+    connection.send("SCAN %lld MATCH %b COUNT %lld",
+                    cursor,
+                    pattern.data(), pattern.size(),
+                    count);
+}
+
+inline void touch(Connection &connection, const StringView &key) {
+    connection.send("TOUCH %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void touch_range(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "TOUCH" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void ttl(Connection &connection, const StringView &key) {
+    connection.send("TTL %b", key.data(), key.size());
+}
+
+inline void type(Connection &connection, const StringView &key) {
+    connection.send("TYPE %b", key.data(), key.size());
+}
+
+inline void unlink(Connection &connection, const StringView &key) {
+    connection.send("UNLINK %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void unlink_range(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "UNLINK" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void wait(Connection &connection, long long numslave, long long timeout) {
+    connection.send("WAIT %lld %lld", numslave, timeout);
+}
+
+// STRING commands.
+
+inline void append(Connection &connection, const StringView &key, const StringView &str) {
+    connection.send("APPEND %b %b",
+                    key.data(), key.size(),
+                    str.data(), str.size());
+}
+
+inline void bitcount(Connection &connection,
+                        const StringView &key,
+                        long long start,
+                        long long end) {
+    connection.send("BITCOUNT %b %lld %lld",
+                    key.data(), key.size(),
+                    start, end);
+}
+
+void bitop(Connection &connection,
+            BitOp op,
+            const StringView &destination,
+            const StringView &key);
+
+template <typename Input>
+void bitop_range(Connection &connection,
+                    BitOp op,
+                    const StringView &destination,
+                    Input first,
+                    Input last);
+
+inline void bitpos(Connection &connection,
+                    const StringView &key,
+                    long long bit,
+                    long long start,
+                    long long end) {
+    connection.send("BITPOS %b %lld %lld %lld",
+                    key.data(), key.size(),
+                    bit,
+                    start,
+                    end);
+}
+
+inline void decr(Connection &connection, const StringView &key) {
+    connection.send("DECR %b", key.data(), key.size());
+}
+
+inline void decrby(Connection &connection, const StringView &key, long long decrement) {
+    connection.send("DECRBY %b %lld",
+                    key.data(), key.size(),
+                    decrement);
+}
+
+inline void get(Connection &connection, const StringView &key) {
+    connection.send("GET %b",
+                    key.data(), key.size());
+}
+
+inline void getbit(Connection &connection, const StringView &key, long long offset) {
+    connection.send("GETBIT %b %lld",
+                    key.data(), key.size(),
+                    offset);
+}
+
+inline void getrange(Connection &connection,
+                        const StringView &key,
+                        long long start,
+                        long long end) {
+    connection.send("GETRANGE %b %lld %lld",
+                    key.data(), key.size(),
+                    start,
+                    end);
+}
+
+inline void getset(Connection &connection,
+                    const StringView &key,
+                    const StringView &val) {
+    connection.send("GETSET %b %b",
+                    key.data(), key.size(),
+                    val.data(), val.size());
+}
+
+inline void incr(Connection &connection, const StringView &key) {
+    connection.send("INCR %b", key.data(), key.size());
+}
+
+inline void incrby(Connection &connection, const StringView &key, long long increment) {
+    connection.send("INCRBY %b %lld",
+                    key.data(), key.size(),
+                    increment);
+}
+
+inline void incrbyfloat(Connection &connection, const StringView &key, double increment) {
+    connection.send("INCRBYFLOAT %b %f",
+                    key.data(), key.size(),
+                    increment);
+}
+
+template <typename Input>
+inline void mget(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "MGET" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+inline void mset(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "MSET" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+inline void msetnx(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "MSETNX" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void psetex(Connection &connection,
+                    const StringView &key,
+                    long long ttl,
+                    const StringView &val) {
+    connection.send("PSETEX %b %lld %b",
+                    key.data(), key.size(),
+                    ttl,
+                    val.data(), val.size());
+}
+
+void set(Connection &connection,
+            const StringView &key,
+            const StringView &val,
+            long long ttl,
+            UpdateType type);
+
+inline void setex(Connection &connection,
+                    const StringView &key,
+                    long long ttl,
+                    const StringView &val) {
+    connection.send("SETEX %b %lld %b",
+                    key.data(), key.size(),
+                    ttl,
+                    val.data(), val.size());
+}
+
+inline void setnx(Connection &connection,
+                    const StringView &key,
+                    const StringView &val) {
+    connection.send("SETNX %b %b",
+                    key.data(), key.size(),
+                    val.data(), val.size());
+}
+
+inline void setrange(Connection &connection,
+                        const StringView &key,
+                        long long offset,
+                        const StringView &val) {
+    connection.send("SETRANGE %b %lld %b",
+                    key.data(), key.size(),
+                    offset,
+                    val.data(), val.size());
+}
+
+inline void strlen(Connection &connection, const StringView &key) {
+    connection.send("STRLEN %b", key.data(), key.size());
+}
+
+// LIST commands.
+
+inline void blpop(Connection &connection, const StringView &key, long long timeout) {
+    connection.send("BLPOP %b %lld",
+                    key.data(), key.size(),
+                    timeout);
+}
+
+template <typename Input>
+inline void blpop_range(Connection &connection,
+                        Input first,
+                        Input last,
+                        long long timeout) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "BLPOP" << std::make_pair(first, last) << timeout;
+
+    connection.send(args);
+}
+
+inline void brpop(Connection &connection, const StringView &key, long long timeout) {
+    connection.send("BRPOP %b %lld",
+                    key.data(), key.size(),
+                    timeout);
+}
+
+template <typename Input>
+inline void brpop_range(Connection &connection,
+                        Input first,
+                        Input last,
+                        long long timeout) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "BRPOP" << std::make_pair(first, last) << timeout;
+
+    connection.send(args);
+}
+
+inline void brpoplpush(Connection &connection,
+                        const StringView &source,
+                        const StringView &destination,
+                        long long timeout) {
+    connection.send("BRPOPLPUSH %b %b %lld",
+                    source.data(), source.size(),
+                    destination.data(), destination.size(),
+                    timeout);
+}
+
+inline void lindex(Connection &connection, const StringView &key, long long index) {
+    connection.send("LINDEX %b %lld",
+                    key.data(), key.size(),
+                    index);
+}
+
+void linsert(Connection &connection,
+                const StringView &key,
+                InsertPosition position,
+                const StringView &pivot,
+                const StringView &val);
+
+inline void llen(Connection &connection,
+                    const StringView &key) {
+    connection.send("LLEN %b", key.data(), key.size());
+}
+
+inline void lpop(Connection &connection, const StringView &key) {
+    connection.send("LPOP %b",
+                    key.data(), key.size());
+}
+
+inline void lpush(Connection &connection, const StringView &key, const StringView &val) {
+    connection.send("LPUSH %b %b",
+                    key.data(), key.size(),
+                    val.data(), val.size());
+}
+
+template <typename Input>
+inline void lpush_range(Connection &connection,
+                        const StringView &key,
+                        Input first,
+                        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "LPUSH" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void lpushx(Connection &connection, const StringView &key, const StringView &val) {
+    connection.send("LPUSHX %b %b",
+                    key.data(), key.size(),
+                    val.data(), val.size());
+}
+
+inline void lrange(Connection &connection,
+                    const StringView &key,
+                    long long start,
+                    long long stop) {
+    connection.send("LRANGE %b %lld %lld",
+                    key.data(), key.size(),
+                    start,
+                    stop);
+}
+
+inline void lrem(Connection &connection,
+                    const StringView &key,
+                    long long count,
+                    const StringView &val) {
+    connection.send("LREM %b %lld %b",
+                    key.data(), key.size(),
+                    count,
+                    val.data(), val.size());
+}
+
+inline void lset(Connection &connection,
+                    const StringView &key,
+                    long long index,
+                    const StringView &val) {
+    connection.send("LSET %b %lld %b",
+                    key.data(), key.size(),
+                    index,
+                    val.data(), val.size());
+}
+
+inline void ltrim(Connection &connection,
+                    const StringView &key,
+                    long long start,
+                    long long stop) {
+    connection.send("LTRIM %b %lld %lld",
+                    key.data(), key.size(),
+                    start,
+                    stop);
+}
+
+inline void rpop(Connection &connection, const StringView &key) {
+    connection.send("RPOP %b", key.data(), key.size());
+}
+
+inline void rpoplpush(Connection &connection,
+                        const StringView &source,
+                        const StringView &destination) {
+    connection.send("RPOPLPUSH %b %b",
+                    source.data(), source.size(),
+                    destination.data(), destination.size());
+}
+
+inline void rpush(Connection &connection, const StringView &key, const StringView &val) {
+    connection.send("RPUSH %b %b",
+                    key.data(), key.size(),
+                    val.data(), val.size());
+}
+
+template <typename Input>
+inline void rpush_range(Connection &connection,
+                        const StringView &key,
+                        Input first,
+                        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "RPUSH" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void rpushx(Connection &connection, const StringView &key, const StringView &val) {
+    connection.send("RPUSHX %b %b",
+                    key.data(), key.size(),
+                    val.data(), val.size());
+}
+
+// HASH commands.
+
+inline void hdel(Connection &connection, const StringView &key, const StringView &field) {
+    connection.send("HDEL %b %b",
+                    key.data(), key.size(),
+                    field.data(), field.size());
+}
+
+template <typename Input>
+inline void hdel_range(Connection &connection,
+                        const StringView &key,
+                        Input first,
+                        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HDEL" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void hexists(Connection &connection, const StringView &key, const StringView &field) {
+    connection.send("HEXISTS %b %b",
+                    key.data(), key.size(),
+                    field.data(), field.size());
+}
+
+inline void hget(Connection &connection, const StringView &key, const StringView &field) {
+    connection.send("HGET %b %b",
+                    key.data(), key.size(),
+                    field.data(), field.size());
+}
+
+inline void hgetall(Connection &connection, const StringView &key) {
+    connection.send("HGETALL %b", key.data(), key.size());
+}
+
+inline void hincrby(Connection &connection,
+                    const StringView &key,
+                    const StringView &field,
+                    long long increment) {
+    connection.send("HINCRBY %b %b %lld",
+                    key.data(), key.size(),
+                    field.data(), field.size(),
+                    increment);
+}
+
+inline void hincrbyfloat(Connection &connection,
+                            const StringView &key,
+                            const StringView &field,
+                            double increment) {
+    connection.send("HINCRBYFLOAT %b %b %f",
+                    key.data(), key.size(),
+                    field.data(), field.size(),
+                    increment);
+}
+
+inline void hkeys(Connection &connection, const StringView &key) {
+    connection.send("HKEYS %b", key.data(), key.size());
+}
+
+inline void hlen(Connection &connection, const StringView &key) {
+    connection.send("HLEN %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void hmget(Connection &connection,
+                    const StringView &key,
+                    Input first,
+                    Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HMGET" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+inline void hmset(Connection &connection,
+                    const StringView &key,
+                    Input first,
+                    Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "HMSET" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void hscan(Connection &connection,
+                    const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count) {
+    connection.send("HSCAN %b %lld MATCH %b COUNT %lld",
+                    key.data(), key.size(),
+                    cursor,
+                    pattern.data(), pattern.size(),
+                    count);
+}
+
+inline void hset(Connection &connection,
+                    const StringView &key,
+                    const StringView &field,
+                    const StringView &val) {
+    connection.send("HSET %b %b %b",
+                    key.data(), key.size(),
+                    field.data(), field.size(),
+                    val.data(), val.size());
+}
+
+inline void hsetnx(Connection &connection,
+                    const StringView &key,
+                    const StringView &field,
+                    const StringView &val) {
+    connection.send("HSETNX %b %b %b",
+                    key.data(), key.size(),
+                    field.data(), field.size(),
+                    val.data(), val.size());
+}
+
+inline void hstrlen(Connection &connection,
+                    const StringView &key,
+                    const StringView &field) {
+    connection.send("HSTRLEN %b %b",
+                    key.data(), key.size(),
+                    field.data(), field.size());
+}
+
+inline void hvals(Connection &connection, const StringView &key) {
+    connection.send("HVALS %b", key.data(), key.size());
+}
+
+// SET commands
+
+inline void sadd(Connection &connection,
+                    const StringView &key,
+                    const StringView &member) {
+    connection.send("SADD %b %b",
+                    key.data(), key.size(),
+                    member.data(), member.size());
+}
+
+template <typename Input>
+inline void sadd_range(Connection &connection,
+                        const StringView &key,
+                        Input first,
+                        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SADD" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void scard(Connection &connection, const StringView &key) {
+    connection.send("SCARD %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void sdiff(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SDIFF" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void sdiffstore(Connection &connection,
+                        const StringView &destination,
+                        const StringView &key) {
+    connection.send("SDIFFSTORE %b %b",
+                    destination.data(), destination.size(),
+                    key.data(), key.size());
+}
+
+template <typename Input>
+inline void sdiffstore_range(Connection &connection,
+                                const StringView &destination,
+                                Input first,
+                                Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SDIFFSTORE" << destination << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+inline void sinter(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SINTER" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void sinterstore(Connection &connection,
+                        const StringView &destination,
+                        const StringView &key) {
+    connection.send("SINTERSTORE %b %b",
+                    destination.data(), destination.size(),
+                    key.data(), key.size());
+}
+
+template <typename Input>
+inline void sinterstore_range(Connection &connection,
+                                const StringView &destination,
+                                Input first,
+                                Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SINTERSTORE" << destination << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void sismember(Connection &connection,
+                        const StringView &key,
+                        const StringView &member) {
+    connection.send("SISMEMBER %b %b",
+                    key.data(), key.size(),
+                    member.data(), member.size());
+}
+
+inline void smembers(Connection &connection, const StringView &key) {
+    connection.send("SMEMBERS %b", key.data(), key.size());
+}
+
+inline void smove(Connection &connection,
+                    const StringView &source,
+                    const StringView &destination,
+                    const StringView &member) {
+    connection.send("SMOVE %b %b %b",
+                    source.data(), source.size(),
+                    destination.data(), destination.size(),
+                    member.data(), member.size());
+}
+
+inline void spop(Connection &connection, const StringView &key) {
+    connection.send("SPOP %b", key.data(), key.size());
+}
+
+inline void spop_range(Connection &connection, const StringView &key, long long count) {
+    connection.send("SPOP %b %lld",
+                    key.data(), key.size(),
+                    count);
+}
+
+inline void srandmember(Connection &connection, const StringView &key) {
+    connection.send("SRANDMEMBER %b", key.data(), key.size());
+}
+
+inline void srandmember_range(Connection &connection,
+                                const StringView &key,
+                                long long count) {
+    connection.send("SRANDMEMBER %b %lld",
+                    key.data(), key.size(),
+                    count);
+}
+
+inline void srem(Connection &connection,
+                    const StringView &key,
+                    const StringView &member) {
+    connection.send("SREM %b %b",
+                    key.data(), key.size(),
+                    member.data(), member.size());
+}
+
+template <typename Input>
+inline void srem_range(Connection &connection,
+                    const StringView &key,
+                    Input first,
+                    Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SREM" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void sscan(Connection &connection,
+                    const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count) {
+    connection.send("SSCAN %b %lld MATCH %b COUNT %lld",
+                    key.data(), key.size(),
+                    cursor,
+                    pattern.data(), pattern.size(),
+                    count);
+}
+
+template <typename Input>
+inline void sunion(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SUNION" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void sunionstore(Connection &connection,
+                        const StringView &destination,
+                        const StringView &key) {
+    connection.send("SUNIONSTORE %b %b",
+                    destination.data(), destination.size(),
+                    key.data(), key.size());
+}
+
+template <typename Input>
+inline void sunionstore_range(Connection &connection,
+                                const StringView &destination,
+                                Input first,
+                                Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SUNIONSTORE" << destination << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+// Sorted Set commands.
+
+inline void bzpopmax(Connection &connection, const StringView &key, long long timeout) {
+    connection.send("BZPOPMAX %b %lld", key.data(), key.size(), timeout);
+}
+
+template <typename Input>
+void bzpopmax_range(Connection &connection,
+                    Input first,
+                    Input last,
+                    long long timeout) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "BZPOPMAX" << std::make_pair(first, last) << timeout;
+
+    connection.send(args);
+}
+
+inline void bzpopmin(Connection &connection, const StringView &key, long long timeout) {
+    connection.send("BZPOPMIN %b %lld", key.data(), key.size(), timeout);
+}
+
+template <typename Input>
+void bzpopmin_range(Connection &connection,
+                    Input first,
+                    Input last,
+                    long long timeout) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "BZPOPMIN" << std::make_pair(first, last) << timeout;
+
+    connection.send(args);
+}
+
+template <typename Input>
+void zadd_range(Connection &connection,
+                const StringView &key,
+                Input first,
+                Input last,
+                UpdateType type,
+                bool changed);
+
+inline void zadd(Connection &connection,
+                    const StringView &key,
+                    const StringView &member,
+                    double score,
+                    UpdateType type,
+                    bool changed) {
+    auto tmp = {std::make_pair(member, score)};
+
+    zadd_range(connection, key, tmp.begin(), tmp.end(), type, changed);
+}
+
+inline void zcard(Connection &connection, const StringView &key) {
+    connection.send("ZCARD %b", key.data(), key.size());
+}
+
+template <typename Interval>
+inline void zcount(Connection &connection,
+                    const StringView &key,
+                    const Interval &interval) {
+    connection.send("ZCOUNT %b %s %s",
+                    key.data(), key.size(),
+                    interval.min().c_str(),
+                    interval.max().c_str());
+}
+
+inline void zincrby(Connection &connection,
+                    const StringView &key,
+                    double increment,
+                    const StringView &member) {
+    connection.send("ZINCRBY %b %f %b",
+                    key.data(), key.size(),
+                    increment,
+                    member.data(), member.size());
+}
+
+inline void zinterstore(Connection &connection,
+                        const StringView &destination,
+                        const StringView &key,
+                        double weight) {
+    connection.send("ZINTERSTORE %b 1 %b WEIGHTS %f",
+                    destination.data(), destination.size(),
+                    key.data(), key.size(),
+                    weight);
+}
+
+template <typename Input>
+void zinterstore_range(Connection &connection,
+                        const StringView &destination,
+                        Input first,
+                        Input last,
+                        Aggregation aggr);
+
+template <typename Interval>
+inline void zlexcount(Connection &connection,
+                        const StringView &key,
+                        const Interval &interval) {
+    const auto &min = interval.min();
+    const auto &max = interval.max();
+
+    connection.send("ZLEXCOUNT %b %b %b",
+                    key.data(), key.size(),
+                    min.data(), min.size(),
+                    max.data(), max.size());
+}
+
+inline void zpopmax(Connection &connection, const StringView &key, long long count) {
+    connection.send("ZPOPMAX %b %lld",
+                        key.data(), key.size(),
+                        count);
+}
+
+inline void zpopmin(Connection &connection, const StringView &key, long long count) {
+    connection.send("ZPOPMIN %b %lld",
+                        key.data(), key.size(),
+                        count);
+}
+
+inline void zrange(Connection &connection,
+                    const StringView &key,
+                    long long start,
+                    long long stop,
+                    bool with_scores) {
+    if (with_scores) {
+        connection.send("ZRANGE %b %lld %lld WITHSCORES",
+                        key.data(), key.size(),
+                        start,
+                        stop);
+    } else {
+        connection.send("ZRANGE %b %lld %lld",
+                        key.data(), key.size(),
+                        start,
+                        stop);
+    }
+}
+
+template <typename Interval>
+inline void zrangebylex(Connection &connection,
+                        const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts) {
+    const auto &min = interval.min();
+    const auto &max = interval.max();
+
+    connection.send("ZRANGEBYLEX %b %b %b LIMIT %lld %lld",
+                    key.data(), key.size(),
+                    min.data(), min.size(),
+                    max.data(), max.size(),
+                    opts.offset,
+                    opts.count);
+}
+
+template <typename Interval>
+void zrangebyscore(Connection &connection,
+                    const StringView &key,
+                    const Interval &interval,
+                    const LimitOptions &opts,
+                    bool with_scores) {
+    const auto &min = interval.min();
+    const auto &max = interval.max();
+
+    if (with_scores) {
+        connection.send("ZRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld",
+                        key.data(), key.size(),
+                        min.data(), min.size(),
+                        max.data(), max.size(),
+                        opts.offset,
+                        opts.count);
+    } else {
+        connection.send("ZRANGEBYSCORE %b %b %b LIMIT %lld %lld",
+                        key.data(), key.size(),
+                        min.data(), min.size(),
+                        max.data(), max.size(),
+                        opts.offset,
+                        opts.count);
+    }
+}
+
+inline void zrank(Connection &connection,
+                    const StringView &key,
+                    const StringView &member) {
+    connection.send("ZRANK %b %b",
+                    key.data(), key.size(),
+                    member.data(), member.size());
+}
+
+inline void zrem(Connection &connection,
+                    const StringView &key,
+                    const StringView &member) {
+    connection.send("ZREM %b %b",
+                    key.data(), key.size(),
+                    member.data(), member.size());
+}
+
+template <typename Input>
+inline void zrem_range(Connection &connection,
+                        const StringView &key,
+                        Input first,
+                        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "ZREM" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Interval>
+inline void zremrangebylex(Connection &connection,
+                            const StringView &key,
+                            const Interval &interval) {
+    const auto &min = interval.min();
+    const auto &max = interval.max();
+
+    connection.send("ZREMRANGEBYLEX %b %b %b",
+                    key.data(), key.size(),
+                    min.data(), min.size(),
+                    max.data(), max.size());
+}
+
+inline void zremrangebyrank(Connection &connection,
+                            const StringView &key,
+                            long long start,
+                            long long stop) {
+    connection.send("zremrangebyrank %b %lld %lld",
+                    key.data(), key.size(),
+                    start,
+                    stop);
+}
+
+template <typename Interval>
+inline void zremrangebyscore(Connection &connection,
+                                const StringView &key,
+                                const Interval &interval) {
+    const auto &min = interval.min();
+    const auto &max = interval.max();
+
+    connection.send("ZREMRANGEBYSCORE %b %b %b",
+                    key.data(), key.size(),
+                    min.data(), min.size(),
+                    max.data(), max.size());
+}
+
+inline void zrevrange(Connection &connection,
+                        const StringView &key,
+                        long long start,
+                        long long stop,
+                        bool with_scores) {
+    if (with_scores) {
+        connection.send("ZREVRANGE %b %lld %lld WITHSCORES",
+                        key.data(), key.size(),
+                        start,
+                        stop);
+    } else {
+        connection.send("ZREVRANGE %b %lld %lld",
+                        key.data(), key.size(),
+                        start,
+                        stop);
+    }
+}
+
+template <typename Interval>
+inline void zrevrangebylex(Connection &connection,
+                            const StringView &key,
+                            const Interval &interval,
+                            const LimitOptions &opts) {
+    const auto &min = interval.min();
+    const auto &max = interval.max();
+
+    connection.send("ZREVRANGEBYLEX %b %b %b LIMIT %lld %lld",
+                    key.data(), key.size(),
+                    max.data(), max.size(),
+                    min.data(), min.size(),
+                    opts.offset,
+                    opts.count);
+}
+
+template <typename Interval>
+void zrevrangebyscore(Connection &connection,
+                        const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        bool with_scores) {
+    const auto &min = interval.min();
+    const auto &max = interval.max();
+
+    if (with_scores) {
+        connection.send("ZREVRANGEBYSCORE %b %b %b WITHSCORES LIMIT %lld %lld",
+                        key.data(), key.size(),
+                        max.data(), max.size(),
+                        min.data(), min.size(),
+                        opts.offset,
+                        opts.count);
+    } else {
+        connection.send("ZREVRANGEBYSCORE %b %b %b LIMIT %lld %lld",
+                        key.data(), key.size(),
+                        max.data(), max.size(),
+                        min.data(), min.size(),
+                        opts.offset,
+                        opts.count);
+    }
+}
+
+inline void zrevrank(Connection &connection,
+                        const StringView &key,
+                        const StringView &member) {
+    connection.send("ZREVRANK %b %b",
+                    key.data(), key.size(),
+                    member.data(), member.size());
+}
+
+inline void zscan(Connection &connection,
+                    const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count) {
+    connection.send("ZSCAN %b %lld MATCH %b COUNT %lld",
+                    key.data(), key.size(),
+                    cursor,
+                    pattern.data(), pattern.size(),
+                    count);
+}
+
+inline void zscore(Connection &connection,
+                    const StringView &key,
+                    const StringView &member) {
+    connection.send("ZSCORE %b %b",
+                    key.data(), key.size(),
+                    member.data(), member.size());
+}
+
+inline void zunionstore(Connection &connection,
+                        const StringView &destination,
+                        const StringView &key,
+                        double weight) {
+    connection.send("ZUNIONSTORE %b 1 %b WEIGHTS %f",
+                    destination.data(), destination.size(),
+                    key.data(), key.size(),
+                    weight);
+}
+
+template <typename Input>
+void zunionstore_range(Connection &connection,
+                        const StringView &destination,
+                        Input first,
+                        Input last,
+                        Aggregation aggr);
+
+// HYPERLOGLOG commands.
+
+inline void pfadd(Connection &connection,
+                    const StringView &key,
+                    const StringView &element) {
+    connection.send("PFADD %b %b",
+                    key.data(), key.size(),
+                    element.data(), element.size());
+}
+
+template <typename Input>
+inline void pfadd_range(Connection &connection,
+                        const StringView &key,
+                        Input first,
+                        Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "PFADD" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void pfcount(Connection &connection, const StringView &key) {
+    connection.send("PFCOUNT %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void pfcount_range(Connection &connection,
+                            Input first,
+                            Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "PFCOUNT" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void pfmerge(Connection &connection, const StringView &destination, const StringView &key) {
+    connection.send("PFMERGE %b %b",
+                    destination.data(), destination.size(),
+                    key.data(), key.size());
+}
+
+template <typename Input>
+inline void pfmerge_range(Connection &connection,
+                            const StringView &destination,
+                            Input first,
+                            Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "PFMERGE" << destination << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+// GEO commands.
+
+inline void geoadd(Connection &connection,
+                    const StringView &key,
+                    const std::tuple<StringView, double, double> &member) {
+    const auto &mem = std::get<0>(member);
+
+    connection.send("GEOADD %b %f %f %b",
+                    key.data(), key.size(),
+                    std::get<1>(member),
+                    std::get<2>(member),
+                    mem.data(), mem.size());
+}
+
+template <typename Input>
+inline void geoadd_range(Connection &connection,
+                            const StringView &key,
+                            Input first,
+                            Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "GEOADD" << key;
+
+    while (first != last) {
+        const auto &member = *first;
+        args << std::get<1>(member) << std::get<2>(member) << std::get<0>(member);
+        ++first;
+    }
+
+    connection.send(args);
+}
+
+void geodist(Connection &connection,
+                const StringView &key,
+                const StringView &member1,
+                const StringView &member2,
+                GeoUnit unit);
+
+template <typename Input>
+inline void geohash_range(Connection &connection,
+                            const StringView &key,
+                            Input first,
+                            Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "GEOHASH" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+inline void geopos_range(Connection &connection,
+                            const StringView &key,
+                            Input first,
+                            Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "GEOPOS" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+void georadius(Connection &connection,
+                const StringView &key,
+                const std::pair<double, double> &loc,
+                double radius,
+                GeoUnit unit,
+                long long count,
+                bool asc,
+                bool with_coord,
+                bool with_dist,
+                bool with_hash);
+
+void georadius_store(Connection &connection,
+                        const StringView &key,
+                        const std::pair<double, double> &loc,
+                        double radius,
+                        GeoUnit unit,
+                        const StringView &destination,
+                        bool store_dist,
+                        long long count);
+
+void georadiusbymember(Connection &connection,
+                        const StringView &key,
+                        const StringView &member,
+                        double radius,
+                        GeoUnit unit,
+                        long long count,
+                        bool asc,
+                        bool with_coord,
+                        bool with_dist,
+                        bool with_hash);
+
+void georadiusbymember_store(Connection &connection,
+                                const StringView &key,
+                                const StringView &member,
+                                double radius,
+                                GeoUnit unit,
+                                const StringView &destination,
+                                bool store_dist,
+                                long long count);
+
+// SCRIPTING commands.
+
+inline void eval(Connection &connection,
+                    const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args) {
+    CmdArgs cmd_args;
+
+    cmd_args << "EVAL" << script << keys.size()
+            << std::make_pair(keys.begin(), keys.end())
+            << std::make_pair(args.begin(), args.end());
+
+    connection.send(cmd_args);
+}
+
+inline void evalsha(Connection &connection,
+                    const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args) {
+    CmdArgs cmd_args;
+
+    cmd_args << "EVALSHA" << script << keys.size()
+            << std::make_pair(keys.begin(), keys.end())
+            << std::make_pair(args.begin(), args.end());
+
+    connection.send(cmd_args);
+}
+
+inline void script_exists(Connection &connection, const StringView &sha) {
+    connection.send("SCRIPT EXISTS %b", sha.data(), sha.size());
+}
+
+template <typename Input>
+inline void script_exists_range(Connection &connection, Input first, Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+    args << "SCRIPT" << "EXISTS" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void script_flush(Connection &connection) {
+    connection.send("SCRIPT FLUSH");
+}
+
+inline void script_kill(Connection &connection) {
+    connection.send("SCRIPT KILL");
+}
+
+inline void script_load(Connection &connection, const StringView &script) {
+    connection.send("SCRIPT LOAD %b", script.data(), script.size());
+}
+
+// PUBSUB commands.
+
+inline void psubscribe(Connection &connection, const StringView &pattern) {
+    connection.send("PSUBSCRIBE %b", pattern.data(), pattern.size());
+}
+
+template <typename Input>
+inline void psubscribe_range(Connection &connection, Input first, Input last) {
+    if (first == last) {
+        throw Error("PSUBSCRIBE: no key specified");
+    }
+
+    CmdArgs args;
+    args << "PSUBSCRIBE" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void publish(Connection &connection,
+                    const StringView &channel,
+                    const StringView &message) {
+    connection.send("PUBLISH %b %b",
+                    channel.data(), channel.size(),
+                    message.data(), message.size());
+}
+
+inline void punsubscribe(Connection &connection) {
+    connection.send("PUNSUBSCRIBE");
+}
+
+inline void punsubscribe(Connection &connection, const StringView &pattern) {
+    connection.send("PUNSUBSCRIBE %b", pattern.data(), pattern.size());
+}
+
+template <typename Input>
+inline void punsubscribe_range(Connection &connection, Input first, Input last) {
+    if (first == last) {
+        throw Error("PUNSUBSCRIBE: no key specified");
+    }
+
+    CmdArgs args;
+    args << "PUNSUBSCRIBE" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void subscribe(Connection &connection, const StringView &channel) {
+    connection.send("SUBSCRIBE %b", channel.data(), channel.size());
+}
+
+template <typename Input>
+inline void subscribe_range(Connection &connection, Input first, Input last) {
+    if (first == last) {
+        throw Error("SUBSCRIBE: no key specified");
+    }
+
+    CmdArgs args;
+    args << "SUBSCRIBE" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void unsubscribe(Connection &connection) {
+    connection.send("UNSUBSCRIBE");
+}
+
+inline void unsubscribe(Connection &connection, const StringView &channel) {
+    connection.send("UNSUBSCRIBE %b", channel.data(), channel.size());
+}
+
+template <typename Input>
+inline void unsubscribe_range(Connection &connection, Input first, Input last) {
+    if (first == last) {
+        throw Error("UNSUBSCRIBE: no key specified");
+    }
+
+    CmdArgs args;
+    args << "UNSUBSCRIBE" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+// Transaction commands.
+
+inline void discard(Connection &connection) {
+    connection.send("DISCARD");
+}
+
+inline void exec(Connection &connection) {
+    connection.send("EXEC");
+}
+
+inline void multi(Connection &connection) {
+    connection.send("MULTI");
+}
+
+inline void unwatch(Connection &connection, const StringView &key) {
+    connection.send("UNWATCH %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void unwatch_range(Connection &connection, Input first, Input last) {
+    if (first == last) {
+        throw Error("UNWATCH: no key specified");
+    }
+
+    CmdArgs args;
+    args << "UNWATCH" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void watch(Connection &connection, const StringView &key) {
+    connection.send("WATCH %b", key.data(), key.size());
+}
+
+template <typename Input>
+inline void watch_range(Connection &connection, Input first, Input last) {
+    if (first == last) {
+        throw Error("WATCH: no key specified");
+    }
+
+    CmdArgs args;
+    args << "WATCH" << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+// Stream commands.
+
+inline void xack(Connection &connection,
+                    const StringView &key,
+                    const StringView &group,
+                    const StringView &id) {
+    connection.send("XACK %b %b %b",
+                    key.data(), key.size(),
+                    group.data(), group.size(),
+                    id.data(), id.size());
+}
+
+template <typename Input>
+void xack_range(Connection &connection,
+                const StringView &key,
+                const StringView &group,
+                Input first,
+                Input last) {
+    CmdArgs args;
+    args << "XACK" << key << group << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void xadd_range(Connection &connection,
+                const StringView &key,
+                const StringView &id,
+                Input first,
+                Input last) {
+    CmdArgs args;
+    args << "XADD" << key << id << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void xadd_maxlen_range(Connection &connection,
+                        const StringView &key,
+                        const StringView &id,
+                        Input first,
+                        Input last,
+                        long long count,
+                        bool approx) {
+    CmdArgs args;
+    args << "XADD" << key << "MAXLEN";
+
+    if (approx) {
+        args << "~";
+    }
+
+    args << count << id << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void xclaim(Connection &connection,
+                    const StringView &key,
+                    const StringView &group,
+                    const StringView &consumer,
+                    long long min_idle_time,
+                    const StringView &id) {
+    connection.send("XCLAIM %b %b %b %lld %b",
+                    key.data(), key.size(),
+                    group.data(), group.size(),
+                    consumer.data(), consumer.size(),
+                    min_idle_time,
+                    id.data(), id.size());
+}
+
+template <typename Input>
+void xclaim_range(Connection &connection,
+                    const StringView &key,
+                    const StringView &group,
+                    const StringView &consumer,
+                    long long min_idle_time,
+                    Input first,
+                    Input last) {
+    CmdArgs args;
+    args << "XCLAIM" << key << group << consumer << min_idle_time << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void xdel(Connection &connection, const StringView &key, const StringView &id) {
+    connection.send("XDEL %b %b", key.data(), key.size(), id.data(), id.size());
+}
+
+template <typename Input>
+void xdel_range(Connection &connection, const StringView &key, Input first, Input last) {
+    CmdArgs args;
+    args << "XDEL" << key << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+inline void xgroup_create(Connection &connection,
+                            const StringView &key,
+                            const StringView &group,
+                            const StringView &id,
+                            bool mkstream) {
+    CmdArgs args;
+    args << "XGROUP" << "CREATE" << key << group << id;
+
+    if (mkstream) {
+        args << "MKSTREAM";
+    }
+
+    connection.send(args);
+}
+
+inline void xgroup_setid(Connection &connection,
+                            const StringView &key,
+                            const StringView &group,
+                            const StringView &id) {
+    connection.send("XGROUP SETID %b %b %b",
+                    key.data(), key.size(),
+                    group.data(), group.size(),
+                    id.data(), id.size());
+}
+
+inline void xgroup_destroy(Connection &connection,
+                            const StringView &key,
+                            const StringView &group) {
+    connection.send("XGROUP DESTROY %b %b",
+                    key.data(), key.size(),
+                    group.data(), group.size());
+}
+
+inline void xgroup_delconsumer(Connection &connection,
+                                const StringView &key,
+                                const StringView &group,
+                                const StringView &consumer) {
+    connection.send("XGROUP DELCONSUMER %b %b %b",
+                    key.data(), key.size(),
+                    group.data(), group.size(),
+                    consumer.data(), consumer.size());
+}
+
+inline void xlen(Connection &connection, const StringView &key) {
+    connection.send("XLEN %b", key.data(), key.size());
+}
+
+inline void xpending(Connection &connection, const StringView &key, const StringView &group) {
+    connection.send("XPENDING %b %b",
+                    key.data(), key.size(),
+                    group.data(), group.size());
+}
+
+inline void xpending_detail(Connection &connection,
+                            const StringView &key,
+                            const StringView &group,
+                            const StringView &start,
+                            const StringView &end,
+                            long long count) {
+    connection.send("XPENDING %b %b %b %b %lld",
+                    key.data(), key.size(),
+                    group.data(), group.size(),
+                    start.data(), start.size(),
+                    end.data(), end.size(),
+                    count);
+}
+
+inline void xpending_per_consumer(Connection &connection,
+                                    const StringView &key,
+                                    const StringView &group,
+                                    const StringView &start,
+                                    const StringView &end,
+                                    long long count,
+                                    const StringView &consumer) {
+    connection.send("XPENDING %b %b %b %b %lld %b",
+                    key.data(), key.size(),
+                    group.data(), group.size(),
+                    start.data(), start.size(),
+                    end.data(), end.size(),
+                    count,
+                    consumer.data(), consumer.size());
+}
+
+inline void xrange(Connection &connection,
+                    const StringView &key,
+                    const StringView &start,
+                    const StringView &end) {
+    connection.send("XRANGE %b %b %b",
+                    key.data(), key.size(),
+                    start.data(), start.size(),
+                    end.data(), end.size());
+}
+
+inline void xrange_count(Connection &connection,
+                            const StringView &key,
+                            const StringView &start,
+                            const StringView &end,
+                            long long count) {
+    connection.send("XRANGE %b %b %b COUNT %lld",
+                    key.data(), key.size(),
+                    start.data(), start.size(),
+                    end.data(), end.size(),
+                    count);
+}
+
+inline void xread(Connection &connection,
+                    const StringView &key,
+                    const StringView &id,
+                    long long count) {
+    connection.send("XREAD COUNT %lld STREAMS %b %b",
+                    count,
+                    key.data(), key.size(),
+                    id.data(), id.size());
+}
+
+template <typename Input>
+void xread_range(Connection &connection, Input first, Input last, long long count) {
+    CmdArgs args;
+    args << "XREAD" << "COUNT" << count << "STREAMS";
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->first;
+    }
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->second;
+    }
+
+    connection.send(args);
+}
+
+inline void xread_block(Connection &connection,
+                        const StringView &key,
+                        const StringView &id,
+                        long long timeout,
+                        long long count) {
+    connection.send("XREAD COUNT %lld BLOCK %lld STREAMS %b %b",
+                    count,
+                    timeout,
+                    key.data(), key.size(),
+                    id.data(), id.size());
+}
+
+template <typename Input>
+void xread_block_range(Connection &connection,
+                        Input first,
+                        Input last,
+                        long long timeout,
+                        long long count) {
+    CmdArgs args;
+    args << "XREAD" << "COUNT" << count << "BLOCK" << timeout << "STREAMS";
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->first;
+    }
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->second;
+    }
+
+    connection.send(args);
+}
+
+inline void xreadgroup(Connection &connection,
+                        const StringView &group,
+                        const StringView &consumer,
+                        const StringView &key,
+                        const StringView &id,
+                        long long count,
+                        bool noack) {
+    CmdArgs args;
+    args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count;
+
+    if (noack) {
+        args << "NOACK";
+    }
+
+    args << "STREAMS" << key << id;
+
+    connection.send(args);
+}
+
+template <typename Input>
+void xreadgroup_range(Connection &connection,
+                        const StringView &group,
+                        const StringView &consumer,
+                        Input first,
+                        Input last,
+                        long long count,
+                        bool noack) {
+    CmdArgs args;
+    args << "XREADGROUP" << "GROUP" << group << consumer << "COUNT" << count;
+
+    if (noack) {
+        args << "NOACK";
+    }
+
+    args << "STREAMS";
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->first;
+    }
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->second;
+    }
+
+    connection.send(args);
+}
+
+inline void xreadgroup_block(Connection &connection,
+                                const StringView &group,
+                                const StringView &consumer,
+                                const StringView &key,
+                                const StringView &id,
+                                long long timeout,
+                                long long count,
+                                bool noack) {
+    CmdArgs args;
+    args << "XREADGROUP" << "GROUP" << group << consumer
+        << "COUNT" << count << "BLOCK" << timeout;
+
+    if (noack) {
+        args << "NOACK";
+    }
+
+    args << "STREAMS" << key << id;
+
+    connection.send(args);
+}
+
+template <typename Input>
+void xreadgroup_block_range(Connection &connection,
+                            const StringView &group,
+                            const StringView &consumer,
+                            Input first,
+                            Input last,
+                            long long timeout,
+                            long long count,
+                            bool noack) {
+    CmdArgs args;
+    args << "XREADGROUP" << "GROUP" << group << consumer
+        << "COUNT" << count << "BLOCK" << timeout;
+
+    if (noack) {
+        args << "NOACK";
+    }
+
+    args << "STREAMS";
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->first;
+    }
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->second;
+    }
+
+    connection.send(args);
+}
+
+inline void xrevrange(Connection &connection,
+                    const StringView &key,
+                    const StringView &end,
+                    const StringView &start) {
+    connection.send("XREVRANGE %b %b %b",
+                    key.data(), key.size(),
+                    end.data(), end.size(),
+                    start.data(), start.size());
+}
+
+inline void xrevrange_count(Connection &connection,
+                            const StringView &key,
+                            const StringView &end,
+                            const StringView &start,
+                            long long count) {
+    connection.send("XREVRANGE %b %b %b COUNT %lld",
+                    key.data(), key.size(),
+                    end.data(), end.size(),
+                    start.data(), start.size(),
+                    count);
+}
+
+void xtrim(Connection &connection, const StringView &key, long long count, bool approx);
+
+namespace detail {
+
+void set_bitop(CmdArgs &args, BitOp op);
+
+void set_update_type(CmdArgs &args, UpdateType type);
+
+void set_aggregation_type(CmdArgs &args, Aggregation type);
+
+template <typename Input>
+void zinterstore(std::false_type,
+                    Connection &connection,
+                    const StringView &destination,
+                    Input first,
+                    Input last,
+                    Aggregation aggr) {
+    CmdArgs args;
+    args << "ZINTERSTORE" << destination << std::distance(first, last)
+        << std::make_pair(first, last);
+
+    set_aggregation_type(args, aggr);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void zinterstore(std::true_type,
+                    Connection &connection,
+                    const StringView &destination,
+                    Input first,
+                    Input last,
+                    Aggregation aggr) {
+    CmdArgs args;
+    args << "ZINTERSTORE" << destination << std::distance(first, last);
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->first;
+    }
+
+    args << "WEIGHTS";
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->second;
+    }
+
+    set_aggregation_type(args, aggr);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void zunionstore(std::false_type,
+                    Connection &connection,
+                    const StringView &destination,
+                    Input first,
+                    Input last,
+                    Aggregation aggr) {
+    CmdArgs args;
+    args << "ZUNIONSTORE" << destination << std::distance(first, last)
+        << std::make_pair(first, last);
+
+    set_aggregation_type(args, aggr);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void zunionstore(std::true_type,
+                    Connection &connection,
+                    const StringView &destination,
+                    Input first,
+                    Input last,
+                    Aggregation aggr) {
+    CmdArgs args;
+    args << "ZUNIONSTORE" << destination << std::distance(first, last);
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->first;
+    }
+
+    args << "WEIGHTS";
+
+    for (auto iter = first; iter != last; ++iter) {
+        args << iter->second;
+    }
+
+    set_aggregation_type(args, aggr);
+
+    connection.send(args);
+}
+
+void set_geo_unit(CmdArgs &args, GeoUnit unit);
+
+void set_georadius_store_parameters(CmdArgs &args,
+                                    double radius,
+                                    GeoUnit unit,
+                                    const StringView &destination,
+                                    bool store_dist,
+                                    long long count);
+
+void set_georadius_parameters(CmdArgs &args,
+                                double radius,
+                                GeoUnit unit,
+                                long long count,
+                                bool asc,
+                                bool with_coord,
+                                bool with_dist,
+                                bool with_hash);
+
+}
+
+}
+
+}
+
+}
+
+namespace sw {
+
+namespace redis {
+
+namespace cmd {
+
+template <typename Input>
+void bitop_range(Connection &connection,
+                    BitOp op,
+                    const StringView &destination,
+                    Input first,
+                    Input last) {
+    assert(first != last);
+
+    CmdArgs args;
+
+    detail::set_bitop(args, op);
+
+    args << destination << std::make_pair(first, last);
+
+    connection.send(args);
+}
+
+template <typename Input>
+void zadd_range(Connection &connection,
+                const StringView &key,
+                Input first,
+                Input last,
+                UpdateType type,
+                bool changed) {
+    assert(first != last);
+
+    CmdArgs args;
+
+    args << "ZADD" << key;
+
+    detail::set_update_type(args, type);
+
+    if (changed) {
+        args << "CH";
+    }
+
+    while (first != last) {
+        // Swap the <member, score> pair to <score, member> pair.
+        args << first->second << first->first;
+        ++first;
+    }
+
+    connection.send(args);
+}
+
+template <typename Input>
+void zinterstore_range(Connection &connection,
+                        const StringView &destination,
+                        Input first,
+                        Input last,
+                        Aggregation aggr) {
+    assert(first != last);
+
+    detail::zinterstore(typename IsKvPairIter<Input>::type(),
+                        connection,
+                        destination,
+                        first,
+                        last,
+                        aggr);
+}
+
+template <typename Input>
+void zunionstore_range(Connection &connection,
+                        const StringView &destination,
+                        Input first,
+                        Input last,
+                        Aggregation aggr) {
+    assert(first != last);
+
+    detail::zunionstore(typename IsKvPairIter<Input>::type(),
+                        connection,
+                        destination,
+                        first,
+                        last,
+                        aggr);
+}
+
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_H

+ 180 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h

@@ -0,0 +1,180 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
+#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
+
+#include <vector>
+#include <list>
+#include <string>
+#include <tuple>
+#include "utils.h"
+
+namespace sw {
+
+namespace redis {
+
+class CmdArgs {
+public:
+    template <typename Arg>
+    CmdArgs& append(Arg &&arg);
+
+    template <typename Arg, typename ...Args>
+    CmdArgs& append(Arg &&arg, Args &&...args);
+
+    // All overloads of operator<< are for internal use only.
+    CmdArgs& operator<<(const StringView &arg);
+
+    template <typename T,
+                 typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
+                                        int>::type = 0>
+    CmdArgs& operator<<(T &&arg);
+
+    template <typename Iter>
+    CmdArgs& operator<<(const std::pair<Iter, Iter> &range);
+
+    template <std::size_t N, typename ...Args>
+    auto operator<<(const std::tuple<Args...> &) ->
+        typename std::enable_if<N == sizeof...(Args), CmdArgs&>::type {
+        return *this;
+    }
+
+    template <std::size_t N = 0, typename ...Args>
+    auto operator<<(const std::tuple<Args...> &arg) ->
+        typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type;
+
+    const char** argv() {
+        return _argv.data();
+    }
+
+    const std::size_t* argv_len() {
+        return _argv_len.data();
+    }
+
+    std::size_t size() const {
+        return _argv.size();
+    }
+
+private:
+    // Deep copy.
+    CmdArgs& _append(std::string arg);
+
+    // Shallow copy.
+    CmdArgs& _append(const StringView &arg);
+
+    // Shallow copy.
+    CmdArgs& _append(const char *arg);
+
+    template <typename T,
+                 typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
+                                        int>::type = 0>
+    CmdArgs& _append(T &&arg) {
+        return operator<<(std::forward<T>(arg));
+    }
+
+    template <typename Iter>
+    CmdArgs& _append(std::true_type, const std::pair<Iter, Iter> &range);
+
+    template <typename Iter>
+    CmdArgs& _append(std::false_type, const std::pair<Iter, Iter> &range);
+
+    std::vector<const char *> _argv;
+    std::vector<std::size_t> _argv_len;
+
+    std::list<std::string> _args;
+};
+
+template <typename Arg>
+inline CmdArgs& CmdArgs::append(Arg &&arg) {
+    return _append(std::forward<Arg>(arg));
+}
+
+template <typename Arg, typename ...Args>
+inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) {
+    _append(std::forward<Arg>(arg));
+
+    return append(std::forward<Args>(args)...);
+}
+
+inline CmdArgs& CmdArgs::operator<<(const StringView &arg) {
+    _argv.push_back(arg.data());
+    _argv_len.push_back(arg.size());
+
+    return *this;
+}
+
+template <typename Iter>
+inline CmdArgs& CmdArgs::operator<<(const std::pair<Iter, Iter> &range) {
+    return _append(IsKvPair<typename std::decay<decltype(*std::declval<Iter>())>::type>(), range);
+}
+
+template <typename T,
+             typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
+                                    int>::type>
+inline CmdArgs& CmdArgs::operator<<(T &&arg) {
+    return _append(std::to_string(std::forward<T>(arg)));
+}
+
+template <std::size_t N, typename ...Args>
+auto CmdArgs::operator<<(const std::tuple<Args...> &arg) ->
+    typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type {
+    operator<<(std::get<N>(arg));
+
+    return operator<<<N + 1, Args...>(arg);
+}
+
+inline CmdArgs& CmdArgs::_append(std::string arg) {
+    _args.push_back(std::move(arg));
+    return operator<<(_args.back());
+}
+
+inline CmdArgs& CmdArgs::_append(const StringView &arg) {
+    return operator<<(arg);
+}
+
+inline CmdArgs& CmdArgs::_append(const char *arg) {
+    return operator<<(arg);
+}
+
+template <typename Iter>
+CmdArgs& CmdArgs::_append(std::false_type, const std::pair<Iter, Iter> &range) {
+    auto first = range.first;
+    auto last = range.second;
+    while (first != last) {
+        *this << *first;
+        ++first;
+    }
+
+    return *this;
+}
+
+template <typename Iter>
+CmdArgs& CmdArgs::_append(std::true_type, const std::pair<Iter, Iter> &range) {
+    auto first = range.first;
+    auto last = range.second;
+    while (first != last) {
+        *this << first->first << first->second;
+        ++first;
+    }
+
+    return *this;
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H

+ 211 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h

@@ -0,0 +1,211 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
+#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
+
+#include <string>
+#include "utils.h"
+
+namespace sw {
+
+namespace redis {
+
+enum class UpdateType {
+    EXIST,
+    NOT_EXIST,
+    ALWAYS
+};
+
+enum class InsertPosition {
+    BEFORE,
+    AFTER
+};
+
+enum class BoundType {
+    CLOSED,
+    OPEN,
+    LEFT_OPEN,
+    RIGHT_OPEN
+};
+
+// (-inf, +inf)
+template <typename T>
+class UnboundedInterval;
+
+// [min, max], (min, max), (min, max], [min, max)
+template <typename T>
+class BoundedInterval;
+
+// [min, +inf), (min, +inf)
+template <typename T>
+class LeftBoundedInterval;
+
+// (-inf, max], (-inf, max)
+template <typename T>
+class RightBoundedInterval;
+
+template <>
+class UnboundedInterval<double> {
+public:
+    const std::string& min() const;
+
+    const std::string& max() const;
+};
+
+template <>
+class BoundedInterval<double> {
+public:
+    BoundedInterval(double min, double max, BoundType type);
+
+    const std::string& min() const {
+        return _min;
+    }
+
+    const std::string& max() const {
+        return _max;
+    }
+
+private:
+    std::string _min;
+    std::string _max;
+};
+
+template <>
+class LeftBoundedInterval<double> {
+public:
+    LeftBoundedInterval(double min, BoundType type);
+
+    const std::string& min() const {
+        return _min;
+    }
+
+    const std::string& max() const;
+
+private:
+    std::string _min;
+};
+
+template <>
+class RightBoundedInterval<double> {
+public:
+    RightBoundedInterval(double max, BoundType type);
+
+    const std::string& min() const;
+
+    const std::string& max() const {
+        return _max;
+    }
+
+private:
+    std::string _max;
+};
+
+template <>
+class UnboundedInterval<std::string> {
+public:
+    const std::string& min() const;
+
+    const std::string& max() const;
+};
+
+template <>
+class BoundedInterval<std::string> {
+public:
+    BoundedInterval(const std::string &min, const std::string &max, BoundType type);
+
+    const std::string& min() const {
+        return _min;
+    }
+
+    const std::string& max() const {
+        return _max;
+    }
+
+private:
+    std::string _min;
+    std::string _max;
+};
+
+template <>
+class LeftBoundedInterval<std::string> {
+public:
+    LeftBoundedInterval(const std::string &min, BoundType type);
+
+    const std::string& min() const {
+        return _min;
+    }
+
+    const std::string& max() const;
+
+private:
+    std::string _min;
+};
+
+template <>
+class RightBoundedInterval<std::string> {
+public:
+    RightBoundedInterval(const std::string &max, BoundType type);
+
+    const std::string& min() const;
+
+    const std::string& max() const {
+        return _max;
+    }
+
+private:
+    std::string _max;
+};
+
+struct LimitOptions {
+    long long offset = 0;
+    long long count = -1;
+};
+
+enum class Aggregation {
+    SUM,
+    MIN,
+    MAX
+};
+
+enum class BitOp {
+    AND,
+    OR,
+    XOR,
+    NOT
+};
+
+enum class GeoUnit {
+    M,
+    KM,
+    MI,
+    FT
+};
+
+template <typename T>
+struct WithCoord : TupleWithType<std::pair<double, double>, T> {};
+
+template <typename T>
+struct WithDist : TupleWithType<double, T> {};
+
+template <typename T>
+struct WithHash : TupleWithType<long long, T> {};
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H

+ 194 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h

@@ -0,0 +1,194 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H
+#define SEWENEW_REDISPLUSPLUS_CONNECTION_H
+
+#include <cerrno>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <sstream>
+#include <chrono>
+#include <hiredis/hiredis.h>
+#include "errors.h"
+#include "reply.h"
+#include "utils.h"
+
+namespace sw {
+
+namespace redis {
+
+enum class ConnectionType {
+    TCP = 0,
+    UNIX
+};
+
+struct ConnectionOptions {
+public:
+    ConnectionOptions() = default;
+
+    explicit ConnectionOptions(const std::string &uri);
+
+    ConnectionOptions(const ConnectionOptions &) = default;
+    ConnectionOptions& operator=(const ConnectionOptions &) = default;
+
+    ConnectionOptions(ConnectionOptions &&) = default;
+    ConnectionOptions& operator=(ConnectionOptions &&) = default;
+
+    ~ConnectionOptions() = default;
+
+    ConnectionType type = ConnectionType::TCP;
+
+    std::string host;
+
+    int port = 6379;
+
+    std::string path;
+
+    std::string password;
+
+    int db = 0;
+
+    bool keep_alive = false;
+
+    std::chrono::milliseconds connect_timeout{0};
+
+    std::chrono::milliseconds socket_timeout{0};
+
+private:
+    ConnectionOptions _parse_options(const std::string &uri) const;
+
+    ConnectionOptions _parse_tcp_options(const std::string &path) const;
+
+    ConnectionOptions _parse_unix_options(const std::string &path) const;
+
+    auto _split_string(const std::string &str, const std::string &delimiter) const ->
+            std::pair<std::string, std::string>;
+};
+
+class CmdArgs;
+
+class Connection {
+public:
+    explicit Connection(const ConnectionOptions &opts);
+
+    Connection(const Connection &) = delete;
+    Connection& operator=(const Connection &) = delete;
+
+    Connection(Connection &&) = default;
+    Connection& operator=(Connection &&) = default;
+
+    ~Connection() = default;
+
+    // Check if the connection is broken. Client needs to do this check
+    // before sending some command to the connection. If it's broken,
+    // client needs to reconnect it.
+    bool broken() const noexcept {
+        return _ctx->err != REDIS_OK;
+    }
+
+    void reset() noexcept {
+        _ctx->err = 0;
+    }
+
+    void reconnect();
+
+    auto last_active() const
+        -> std::chrono::time_point<std::chrono::steady_clock> {
+        return _last_active;
+    }
+
+    template <typename ...Args>
+    void send(const char *format, Args &&...args);
+
+    void send(int argc, const char **argv, const std::size_t *argv_len);
+
+    void send(CmdArgs &args);
+
+    ReplyUPtr recv();
+
+    const ConnectionOptions& options() const {
+        return _opts;
+    }
+
+    friend void swap(Connection &lhs, Connection &rhs) noexcept;
+
+private:
+    class Connector;
+
+    struct ContextDeleter {
+        void operator()(redisContext *context) const {
+            if (context != nullptr) {
+                redisFree(context);
+            }
+        };
+    };
+
+    using ContextUPtr = std::unique_ptr<redisContext, ContextDeleter>;
+
+    void _set_options();
+
+    void _auth();
+
+    void _select_db();
+
+    redisContext* _context();
+
+    ContextUPtr _ctx;
+
+    // The time that the connection is created or the time that
+    // the connection is used, i.e. *context()* is called.
+    std::chrono::time_point<std::chrono::steady_clock> _last_active{};
+
+    ConnectionOptions _opts;
+};
+
+using ConnectionSPtr = std::shared_ptr<Connection>;
+
+enum class Role {
+    MASTER,
+    SLAVE
+};
+
+// Inline implementaions.
+
+template <typename ...Args>
+inline void Connection::send(const char *format, Args &&...args) {
+    auto ctx = _context();
+
+    assert(ctx != nullptr);
+
+    if (redisAppendCommand(ctx,
+                format,
+                std::forward<Args>(args)...) != REDIS_OK) {
+        throw_error(*ctx, "Failed to send command");
+    }
+
+    assert(!broken());
+}
+
+inline redisContext* Connection::_context() {
+    _last_active = std::chrono::steady_clock::now();
+
+    return _ctx.get();
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H

+ 115 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h

@@ -0,0 +1,115 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
+#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
+
+#include <chrono>
+#include <mutex>
+#include <memory>
+#include <condition_variable>
+#include <deque>
+#include "connection.h"
+#include "sentinel.h"
+
+namespace sw {
+
+namespace redis {
+
+struct ConnectionPoolOptions {
+    // Max number of connections, including both in-use and idle ones.
+    std::size_t size = 1;
+
+    // Max time to wait for a connection. 0ms means client waits forever.
+    std::chrono::milliseconds wait_timeout{0};
+
+    // Max lifetime of a connection. 0ms means we never expire the connection.
+    std::chrono::milliseconds connection_lifetime{0};
+};
+
+class ConnectionPool {
+public:
+    ConnectionPool(const ConnectionPoolOptions &pool_opts,
+                    const ConnectionOptions &connection_opts);
+
+    ConnectionPool(SimpleSentinel sentinel,
+                    const ConnectionPoolOptions &pool_opts,
+                    const ConnectionOptions &connection_opts);
+
+    ConnectionPool() = default;
+
+    ConnectionPool(ConnectionPool &&that);
+    ConnectionPool& operator=(ConnectionPool &&that);
+
+    ConnectionPool(const ConnectionPool &) = delete;
+    ConnectionPool& operator=(const ConnectionPool &) = delete;
+
+    ~ConnectionPool() = default;
+
+    // Fetch a connection from pool.
+    Connection fetch();
+
+    ConnectionOptions connection_options();
+
+    void release(Connection connection);
+
+    // Create a new connection.
+    Connection create();
+
+private:
+    void _move(ConnectionPool &&that);
+
+    // NOT thread-safe
+    Connection _create();
+
+    Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked);
+
+    Connection _fetch();
+
+    void _wait_for_connection(std::unique_lock<std::mutex> &lock);
+
+    bool _need_reconnect(const Connection &connection,
+                            const std::chrono::milliseconds &connection_lifetime) const;
+
+    void _update_connection_opts(const std::string &host, int port) {
+        _opts.host = host;
+        _opts.port = port;
+    }
+
+    bool _role_changed(const ConnectionOptions &opts) const {
+        return opts.port != _opts.port || opts.host != _opts.host;
+    }
+
+    ConnectionOptions _opts;
+
+    ConnectionPoolOptions _pool_opts;
+
+    std::deque<Connection> _pool;
+
+    std::size_t _used_connections = 0;
+
+    std::mutex _mutex;
+
+    std::condition_variable _cv;
+
+    SimpleSentinel _sentinel;
+};
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H

+ 159 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h

@@ -0,0 +1,159 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H
+#define SEWENEW_REDISPLUSPLUS_ERRORS_H
+
+#include <exception>
+#include <string>
+#include <hiredis/hiredis.h>
+
+namespace sw {
+
+namespace redis {
+
+enum ReplyErrorType {
+    ERR,
+    MOVED,
+    ASK
+};
+
+class Error : public std::exception {
+public:
+    explicit Error(const std::string &msg) : _msg(msg) {}
+
+    Error(const Error &) = default;
+    Error& operator=(const Error &) = default;
+
+    Error(Error &&) = default;
+    Error& operator=(Error &&) = default;
+
+    virtual ~Error() = default;
+
+    virtual const char* what() const noexcept {
+        return _msg.data();
+    }
+
+private:
+    std::string _msg;
+};
+
+class IoError : public Error {
+public:
+    explicit IoError(const std::string &msg) : Error(msg) {}
+
+    IoError(const IoError &) = default;
+    IoError& operator=(const IoError &) = default;
+
+    IoError(IoError &&) = default;
+    IoError& operator=(IoError &&) = default;
+
+    virtual ~IoError() = default;
+};
+
+class TimeoutError : public IoError {
+public:
+    explicit TimeoutError(const std::string &msg) : IoError(msg) {}
+
+    TimeoutError(const TimeoutError &) = default;
+    TimeoutError& operator=(const TimeoutError &) = default;
+
+    TimeoutError(TimeoutError &&) = default;
+    TimeoutError& operator=(TimeoutError &&) = default;
+
+    virtual ~TimeoutError() = default;
+};
+
+class ClosedError : public Error {
+public:
+    explicit ClosedError(const std::string &msg) : Error(msg) {}
+
+    ClosedError(const ClosedError &) = default;
+    ClosedError& operator=(const ClosedError &) = default;
+
+    ClosedError(ClosedError &&) = default;
+    ClosedError& operator=(ClosedError &&) = default;
+
+    virtual ~ClosedError() = default;
+};
+
+class ProtoError : public Error {
+public:
+    explicit ProtoError(const std::string &msg) : Error(msg) {}
+
+    ProtoError(const ProtoError &) = default;
+    ProtoError& operator=(const ProtoError &) = default;
+
+    ProtoError(ProtoError &&) = default;
+    ProtoError& operator=(ProtoError &&) = default;
+
+    virtual ~ProtoError() = default;
+};
+
+class OomError : public Error {
+public:
+    explicit OomError(const std::string &msg) : Error(msg) {}
+
+    OomError(const OomError &) = default;
+    OomError& operator=(const OomError &) = default;
+
+    OomError(OomError &&) = default;
+    OomError& operator=(OomError &&) = default;
+
+    virtual ~OomError() = default;
+};
+
+class ReplyError : public Error {
+public:
+    explicit ReplyError(const std::string &msg) : Error(msg) {}
+
+    ReplyError(const ReplyError &) = default;
+    ReplyError& operator=(const ReplyError &) = default;
+
+    ReplyError(ReplyError &&) = default;
+    ReplyError& operator=(ReplyError &&) = default;
+
+    virtual ~ReplyError() = default;
+};
+
+class WatchError : public Error {
+public:
+    explicit WatchError() : Error("Watched key has been modified") {}
+
+    WatchError(const WatchError &) = default;
+    WatchError& operator=(const WatchError &) = default;
+
+    WatchError(WatchError &&) = default;
+    WatchError& operator=(WatchError &&) = default;
+
+    virtual ~WatchError() = default;
+};
+
+
+// MovedError and AskError are defined in shards.h
+class MovedError;
+
+class AskError;
+
+void throw_error(redisContext &context, const std::string &err_info);
+
+void throw_error(const redisReply &reply);
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H

+ 49 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h

@@ -0,0 +1,49 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H
+#define SEWENEW_REDISPLUSPLUS_PIPELINE_H
+
+#include <cassert>
+#include <vector>
+#include "connection.h"
+
+namespace sw {
+
+namespace redis {
+
+class PipelineImpl {
+public:
+    template <typename Cmd, typename ...Args>
+    void command(Connection &connection, Cmd cmd, Args &&...args) {
+        assert(!connection.broken());
+
+        cmd(connection, std::forward<Args>(args)...);
+    }
+
+    std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
+
+    void discard(Connection &connection, std::size_t /*cmd_num*/) {
+        // Reconnect to Redis to discard all commands.
+        connection.reconnect();
+    }
+};
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H

+ 1844 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h

@@ -0,0 +1,1844 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H
+#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H
+
+#include <cassert>
+#include <chrono>
+#include <initializer_list>
+#include <vector>
+#include "connection.h"
+#include "utils.h"
+#include "reply.h"
+#include "command.h"
+#include "redis.h"
+
+namespace sw {
+
+namespace redis {
+
+class QueuedReplies;
+
+// If any command throws, QueuedRedis resets the connection, and becomes invalid.
+// In this case, the only thing we can do is to destory the QueuedRedis object.
+template <typename Impl>
+class QueuedRedis {
+public:
+    QueuedRedis(QueuedRedis &&) = default;
+    QueuedRedis& operator=(QueuedRedis &&) = default;
+
+    // When it destructs, the underlying *Connection* will be closed,
+    // and any command that has NOT been executed will be ignored.
+    ~QueuedRedis() = default;
+
+    Redis redis();
+
+    template <typename Cmd, typename ...Args>
+    auto command(Cmd cmd, Args &&...args)
+        -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value,
+                                    QueuedRedis&>::type;
+
+    template <typename ...Args>
+    QueuedRedis& command(const StringView &cmd_name, Args &&...args);
+
+    template <typename Input>
+    auto command(Input first, Input last)
+        -> typename std::enable_if<IsIter<Input>::value, QueuedRedis&>::type;
+
+    QueuedReplies exec();
+
+    void discard();
+
+    // CONNECTION commands.
+
+    QueuedRedis& auth(const StringView &password) {
+        return command(cmd::auth, password);
+    }
+
+    QueuedRedis& echo(const StringView &msg) {
+        return command(cmd::echo, msg);
+    }
+
+    QueuedRedis& ping() {
+        return command<void (*)(Connection &)>(cmd::ping);
+    }
+
+    QueuedRedis& ping(const StringView &msg) {
+        return command<void (*)(Connection &, const StringView &)>(cmd::ping, msg);
+    }
+
+    // We DO NOT support the QUIT command. See *Redis::quit* doc for details.
+    //
+    // QueuedRedis& quit();
+
+    QueuedRedis& select(long long idx) {
+        return command(cmd::select, idx);
+    }
+
+    QueuedRedis& swapdb(long long idx1, long long idx2) {
+        return command(cmd::swapdb, idx1, idx2);
+    }
+
+    // SERVER commands.
+
+    QueuedRedis& bgrewriteaof() {
+        return command(cmd::bgrewriteaof);
+    }
+
+    QueuedRedis& bgsave() {
+        return command(cmd::bgsave);
+    }
+
+    QueuedRedis& dbsize() {
+        return command(cmd::dbsize);
+    }
+
+    QueuedRedis& flushall(bool async = false) {
+        return command(cmd::flushall, async);
+    }
+
+    QueuedRedis& flushdb(bool async = false) {
+        return command(cmd::flushdb, async);
+    }
+
+    QueuedRedis& info() {
+        return command<void (*)(Connection &)>(cmd::info);
+    }
+
+    QueuedRedis& info(const StringView &section) {
+        return command<void (*)(Connection &, const StringView &)>(cmd::info, section);
+    }
+
+    QueuedRedis& lastsave() {
+        return command(cmd::lastsave);
+    }
+
+    QueuedRedis& save() {
+        return command(cmd::save);
+    }
+
+    // KEY commands.
+
+    QueuedRedis& del(const StringView &key) {
+        return command(cmd::del, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& del(Input first, Input last) {
+        return command(cmd::del_range<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& del(std::initializer_list<T> il) {
+        return del(il.begin(), il.end());
+    }
+
+    QueuedRedis& dump(const StringView &key) {
+        return command(cmd::dump, key);
+    }
+
+    QueuedRedis& exists(const StringView &key) {
+        return command(cmd::exists, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& exists(Input first, Input last) {
+        return command(cmd::exists_range<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& exists(std::initializer_list<T> il) {
+        return exists(il.begin(), il.end());
+    }
+
+    QueuedRedis& expire(const StringView &key, long long timeout) {
+        return command(cmd::expire, key, timeout);
+    }
+
+    QueuedRedis& expire(const StringView &key,
+                        const std::chrono::seconds &timeout) {
+        return expire(key, timeout.count());
+    }
+
+    QueuedRedis& expireat(const StringView &key, long long timestamp) {
+        return command(cmd::expireat, key, timestamp);
+    }
+
+    QueuedRedis& expireat(const StringView &key,
+                            const std::chrono::time_point<std::chrono::system_clock,
+                                                            std::chrono::seconds> &tp) {
+        return expireat(key, tp.time_since_epoch().count());
+    }
+
+    QueuedRedis& keys(const StringView &pattern) {
+        return command(cmd::keys, pattern);
+    }
+
+    QueuedRedis& move(const StringView &key, long long db) {
+        return command(cmd::move, key, db);
+    }
+
+    QueuedRedis& persist(const StringView &key) {
+        return command(cmd::persist, key);
+    }
+
+    QueuedRedis& pexpire(const StringView &key, long long timeout) {
+        return command(cmd::pexpire, key, timeout);
+    }
+
+    QueuedRedis& pexpire(const StringView &key,
+                            const std::chrono::milliseconds &timeout) {
+        return pexpire(key, timeout.count());
+    }
+
+    QueuedRedis& pexpireat(const StringView &key, long long timestamp) {
+        return command(cmd::pexpireat, key, timestamp);
+    }
+
+    QueuedRedis& pexpireat(const StringView &key,
+                            const std::chrono::time_point<std::chrono::system_clock,
+                                                            std::chrono::milliseconds> &tp) {
+        return pexpireat(key, tp.time_since_epoch().count());
+    }
+
+    QueuedRedis& pttl(const StringView &key) {
+        return command(cmd::pttl, key);
+    }
+
+    QueuedRedis& randomkey() {
+        return command(cmd::randomkey);
+    }
+
+    QueuedRedis& rename(const StringView &key, const StringView &newkey) {
+        return command(cmd::rename, key, newkey);
+    }
+
+    QueuedRedis& renamenx(const StringView &key, const StringView &newkey) {
+        return command(cmd::renamenx, key, newkey);
+    }
+
+    QueuedRedis& restore(const StringView &key,
+                                const StringView &val,
+                                long long ttl,
+                                bool replace = false) {
+        return command(cmd::restore, key, val, ttl, replace);
+    }
+
+    QueuedRedis& restore(const StringView &key,
+                            const StringView &val,
+                            const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0},
+                            bool replace = false) {
+        return restore(key, val, ttl.count(), replace);
+    }
+
+    // TODO: sort
+
+    QueuedRedis& scan(long long cursor,
+                        const StringView &pattern,
+                        long long count) {
+        return command(cmd::scan, cursor, pattern, count);
+    }
+
+    QueuedRedis& scan(long long cursor) {
+        return scan(cursor, "*", 10);
+    }
+
+    QueuedRedis& scan(long long cursor,
+                        const StringView &pattern) {
+        return scan(cursor, pattern, 10);
+    }
+
+    QueuedRedis& scan(long long cursor,
+                        long long count) {
+        return scan(cursor, "*", count);
+    }
+
+    QueuedRedis& touch(const StringView &key) {
+        return command(cmd::touch, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& touch(Input first, Input last) {
+        return command(cmd::touch_range<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& touch(std::initializer_list<T> il) {
+        return touch(il.begin(), il.end());
+    }
+
+    QueuedRedis& ttl(const StringView &key) {
+        return command(cmd::ttl, key);
+    }
+
+    QueuedRedis& type(const StringView &key) {
+        return command(cmd::type, key);
+    }
+
+    QueuedRedis& unlink(const StringView &key) {
+        return command(cmd::unlink, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& unlink(Input first, Input last) {
+        return command(cmd::unlink_range<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& unlink(std::initializer_list<T> il) {
+        return unlink(il.begin(), il.end());
+    }
+
+    QueuedRedis& wait(long long numslaves, long long timeout) {
+        return command(cmd::wait, numslaves, timeout);
+    }
+
+    QueuedRedis& wait(long long numslaves, const std::chrono::milliseconds &timeout) {
+        return wait(numslaves, timeout.count());
+    }
+
+    // STRING commands.
+
+    QueuedRedis& append(const StringView &key, const StringView &str) {
+        return command(cmd::append, key, str);
+    }
+
+    QueuedRedis& bitcount(const StringView &key,
+                            long long start = 0,
+                            long long end = -1) {
+        return command(cmd::bitcount, key, start, end);
+    }
+
+    QueuedRedis& bitop(BitOp op,
+                        const StringView &destination,
+                        const StringView &key) {
+        return command(cmd::bitop, op, destination, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& bitop(BitOp op,
+                        const StringView &destination,
+                        Input first,
+                        Input last) {
+        return command(cmd::bitop_range<Input>, op, destination, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& bitop(BitOp op,
+                        const StringView &destination,
+                        std::initializer_list<T> il) {
+        return bitop(op, destination, il.begin(), il.end());
+    }
+
+    QueuedRedis& bitpos(const StringView &key,
+                        long long bit,
+                        long long start = 0,
+                        long long end = -1) {
+        return command(cmd::bitpos, key, bit, start, end);
+    }
+
+    QueuedRedis& decr(const StringView &key) {
+        return command(cmd::decr, key);
+    }
+
+    QueuedRedis& decrby(const StringView &key, long long decrement) {
+        return command(cmd::decrby, key, decrement);
+    }
+
+    QueuedRedis& get(const StringView &key) {
+        return command(cmd::get, key);
+    }
+
+    QueuedRedis& getbit(const StringView &key, long long offset) {
+        return command(cmd::getbit, key, offset);
+    }
+
+    QueuedRedis& getrange(const StringView &key, long long start, long long end) {
+        return command(cmd::getrange, key, start, end);
+    }
+
+    QueuedRedis& getset(const StringView &key, const StringView &val) {
+        return command(cmd::getset, key, val);
+    }
+
+    QueuedRedis& incr(const StringView &key) {
+        return command(cmd::incr, key);
+    }
+
+    QueuedRedis& incrby(const StringView &key, long long increment) {
+        return command(cmd::incrby, key, increment);
+    }
+
+    QueuedRedis& incrbyfloat(const StringView &key, double increment) {
+        return command(cmd::incrbyfloat, key, increment);
+    }
+
+    template <typename Input>
+    QueuedRedis& mget(Input first, Input last) {
+        return command(cmd::mget<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& mget(std::initializer_list<T> il) {
+        return mget(il.begin(), il.end());
+    }
+
+    template <typename Input>
+    QueuedRedis& mset(Input first, Input last) {
+        return command(cmd::mset<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& mset(std::initializer_list<T> il) {
+        return mset(il.begin(), il.end());
+    }
+
+    template <typename Input>
+    QueuedRedis& msetnx(Input first, Input last) {
+        return command(cmd::msetnx<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& msetnx(std::initializer_list<T> il) {
+        return msetnx(il.begin(), il.end());
+    }
+
+    QueuedRedis& psetex(const StringView &key,
+                        long long ttl,
+                        const StringView &val) {
+        return command(cmd::psetex, key, ttl, val);
+    }
+
+    QueuedRedis& psetex(const StringView &key,
+                        const std::chrono::milliseconds &ttl,
+                        const StringView &val) {
+        return psetex(key, ttl.count(), val);
+    }
+
+    QueuedRedis& set(const StringView &key,
+                        const StringView &val,
+                        const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
+                        UpdateType type = UpdateType::ALWAYS) {
+        _set_cmd_indexes.push_back(_cmd_num);
+
+        return command(cmd::set, key, val, ttl.count(), type);
+    }
+
+    QueuedRedis& setex(const StringView &key,
+                        long long ttl,
+                        const StringView &val) {
+        return command(cmd::setex, key, ttl, val);
+    }
+
+    QueuedRedis& setex(const StringView &key,
+                        const std::chrono::seconds &ttl,
+                        const StringView &val) {
+        return setex(key, ttl.count(), val);
+    }
+
+    QueuedRedis& setnx(const StringView &key, const StringView &val) {
+        return command(cmd::setnx, key, val);
+    }
+
+    QueuedRedis& setrange(const StringView &key,
+                            long long offset,
+                            const StringView &val) {
+        return command(cmd::setrange, key, offset, val);
+    }
+
+    QueuedRedis& strlen(const StringView &key) {
+        return command(cmd::strlen, key);
+    }
+
+    // LIST commands.
+
+    QueuedRedis& blpop(const StringView &key, long long timeout) {
+        return command(cmd::blpop, key, timeout);
+    }
+
+    QueuedRedis& blpop(const StringView &key,
+                        const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return blpop(key, timeout.count());
+    }
+
+    template <typename Input>
+    QueuedRedis& blpop(Input first, Input last, long long timeout) {
+        return command(cmd::blpop_range<Input>, first, last, timeout);
+    }
+
+    template <typename T>
+    QueuedRedis& blpop(std::initializer_list<T> il, long long timeout) {
+        return blpop(il.begin(), il.end(), timeout);
+    }
+
+    template <typename Input>
+    QueuedRedis& blpop(Input first,
+                        Input last,
+                        const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return blpop(first, last, timeout.count());
+    }
+
+    template <typename T>
+    QueuedRedis& blpop(std::initializer_list<T> il,
+                        const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return blpop(il.begin(), il.end(), timeout);
+    }
+
+    QueuedRedis& brpop(const StringView &key, long long timeout) {
+        return command(cmd::brpop, key, timeout);
+    }
+
+    QueuedRedis& brpop(const StringView &key,
+                        const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return brpop(key, timeout.count());
+    }
+
+    template <typename Input>
+    QueuedRedis& brpop(Input first, Input last, long long timeout) {
+        return command(cmd::brpop_range<Input>, first, last, timeout);
+    }
+
+    template <typename T>
+    QueuedRedis& brpop(std::initializer_list<T> il, long long timeout) {
+        return brpop(il.begin(), il.end(), timeout);
+    }
+
+    template <typename Input>
+    QueuedRedis& brpop(Input first,
+                        Input last,
+                        const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return brpop(first, last, timeout.count());
+    }
+
+    template <typename T>
+    QueuedRedis& brpop(std::initializer_list<T> il,
+                        const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return brpop(il.begin(), il.end(), timeout);
+    }
+
+    QueuedRedis& brpoplpush(const StringView &source,
+                            const StringView &destination,
+                            long long timeout) {
+        return command(cmd::brpoplpush, source, destination, timeout);
+    }
+
+    QueuedRedis& brpoplpush(const StringView &source,
+                            const StringView &destination,
+                            const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return brpoplpush(source, destination, timeout.count());
+    }
+
+    QueuedRedis& lindex(const StringView &key, long long index) {
+        return command(cmd::lindex, key, index);
+    }
+
+    QueuedRedis& linsert(const StringView &key,
+                            InsertPosition position,
+                            const StringView &pivot,
+                            const StringView &val) {
+        return command(cmd::linsert, key, position, pivot, val);
+    }
+
+    QueuedRedis& llen(const StringView &key) {
+        return command(cmd::llen, key);
+    }
+
+    QueuedRedis& lpop(const StringView &key) {
+        return command(cmd::lpop, key);
+    }
+
+    QueuedRedis& lpush(const StringView &key, const StringView &val) {
+        return command(cmd::lpush, key, val);
+    }
+
+    template <typename Input>
+    QueuedRedis& lpush(const StringView &key, Input first, Input last) {
+        return command(cmd::lpush_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& lpush(const StringView &key, std::initializer_list<T> il) {
+        return lpush(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& lpushx(const StringView &key, const StringView &val) {
+        return command(cmd::lpushx, key, val);
+    }
+
+    QueuedRedis& lrange(const StringView &key,
+                        long long start,
+                        long long stop) {
+        return command(cmd::lrange, key, start, stop);
+    }
+
+    QueuedRedis& lrem(const StringView &key, long long count, const StringView &val) {
+        return command(cmd::lrem, key, count, val);
+    }
+
+    QueuedRedis& lset(const StringView &key, long long index, const StringView &val) {
+        return command(cmd::lset, key, index, val);
+    }
+
+    QueuedRedis& ltrim(const StringView &key, long long start, long long stop) {
+        return command(cmd::ltrim, key, start, stop);
+    }
+
+    QueuedRedis& rpop(const StringView &key) {
+        return command(cmd::rpop, key);
+    }
+
+    QueuedRedis& rpoplpush(const StringView &source, const StringView &destination) {
+        return command(cmd::rpoplpush, source, destination);
+    }
+
+    QueuedRedis& rpush(const StringView &key, const StringView &val) {
+        return command(cmd::rpush, key, val);
+    }
+
+    template <typename Input>
+    QueuedRedis& rpush(const StringView &key, Input first, Input last) {
+        return command(cmd::rpush_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& rpush(const StringView &key, std::initializer_list<T> il) {
+        return rpush(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& rpushx(const StringView &key, const StringView &val) {
+        return command(cmd::rpushx, key, val);
+    }
+
+    // HASH commands.
+
+    QueuedRedis& hdel(const StringView &key, const StringView &field) {
+        return command(cmd::hdel, key, field);
+    }
+
+    template <typename Input>
+    QueuedRedis& hdel(const StringView &key, Input first, Input last) {
+        return command(cmd::hdel_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& hdel(const StringView &key, std::initializer_list<T> il) {
+        return hdel(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& hexists(const StringView &key, const StringView &field) {
+        return command(cmd::hexists, key, field);
+    }
+
+    QueuedRedis& hget(const StringView &key, const StringView &field) {
+        return command(cmd::hget, key, field);
+    }
+
+    QueuedRedis& hgetall(const StringView &key) {
+        return command(cmd::hgetall, key);
+    }
+
+    QueuedRedis& hincrby(const StringView &key,
+                            const StringView &field,
+                            long long increment) {
+        return command(cmd::hincrby, key, field, increment);
+    }
+
+    QueuedRedis& hincrbyfloat(const StringView &key,
+                                const StringView &field,
+                                double increment) {
+        return command(cmd::hincrbyfloat, key, field, increment);
+    }
+
+    QueuedRedis& hkeys(const StringView &key) {
+        return command(cmd::hkeys, key);
+    }
+
+    QueuedRedis& hlen(const StringView &key) {
+        return command(cmd::hlen, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& hmget(const StringView &key, Input first, Input last) {
+        return command(cmd::hmget<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& hmget(const StringView &key, std::initializer_list<T> il) {
+        return hmget(key, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    QueuedRedis& hmset(const StringView &key, Input first, Input last) {
+        return command(cmd::hmset<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& hmset(const StringView &key, std::initializer_list<T> il) {
+        return hmset(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& hscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count) {
+        return command(cmd::hscan, key, cursor, pattern, count);
+    }
+
+    QueuedRedis& hscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern) {
+        return hscan(key, cursor, pattern, 10);
+    }
+
+    QueuedRedis& hscan(const StringView &key,
+                        long long cursor,
+                        long long count) {
+        return hscan(key, cursor, "*", count);
+    }
+
+    QueuedRedis& hscan(const StringView &key,
+                        long long cursor) {
+        return hscan(key, cursor, "*", 10);
+    }
+
+    QueuedRedis& hset(const StringView &key, const StringView &field, const StringView &val) {
+        return command(cmd::hset, key, field, val);
+    }
+
+    QueuedRedis& hset(const StringView &key, const std::pair<StringView, StringView> &item) {
+        return hset(key, item.first, item.second);
+    }
+
+    QueuedRedis& hsetnx(const StringView &key, const StringView &field, const StringView &val) {
+        return command(cmd::hsetnx, key, field, val);
+    }
+
+    QueuedRedis& hsetnx(const StringView &key, const std::pair<StringView, StringView> &item) {
+        return hsetnx(key, item.first, item.second);
+    }
+
+    QueuedRedis& hstrlen(const StringView &key, const StringView &field) {
+        return command(cmd::hstrlen, key, field);
+    }
+
+    QueuedRedis& hvals(const StringView &key) {
+        return command(cmd::hvals, key);
+    }
+
+    // SET commands.
+
+    QueuedRedis& sadd(const StringView &key, const StringView &member) {
+        return command(cmd::sadd, key, member);
+    }
+
+    template <typename Input>
+    QueuedRedis& sadd(const StringView &key, Input first, Input last) {
+        return command(cmd::sadd_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& sadd(const StringView &key, std::initializer_list<T> il) {
+        return sadd(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& scard(const StringView &key) {
+        return command(cmd::scard, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& sdiff(Input first, Input last) {
+        return command(cmd::sdiff<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& sdiff(std::initializer_list<T> il) {
+        return sdiff(il.begin(), il.end());
+    }
+
+    QueuedRedis& sdiffstore(const StringView &destination, const StringView &key) {
+        return command(cmd::sdiffstore, destination, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& sdiffstore(const StringView &destination,
+                            Input first,
+                            Input last) {
+        return command(cmd::sdiffstore_range<Input>, destination, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& sdiffstore(const StringView &destination, std::initializer_list<T> il) {
+        return sdiffstore(destination, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    QueuedRedis& sinter(Input first, Input last) {
+        return command(cmd::sinter<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& sinter(std::initializer_list<T> il) {
+        return sinter(il.begin(), il.end());
+    }
+
+    QueuedRedis& sinterstore(const StringView &destination, const StringView &key) {
+        return command(cmd::sinterstore, destination, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& sinterstore(const StringView &destination,
+                                Input first,
+                                Input last) {
+        return command(cmd::sinterstore_range<Input>, destination, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& sinterstore(const StringView &destination, std::initializer_list<T> il) {
+        return sinterstore(destination, il.begin(), il.end());
+    }
+
+    QueuedRedis& sismember(const StringView &key, const StringView &member) {
+        return command(cmd::sismember, key, member);
+    }
+
+    QueuedRedis& smembers(const StringView &key) {
+        return command(cmd::smembers, key);
+    }
+
+    QueuedRedis& smove(const StringView &source,
+                        const StringView &destination,
+                        const StringView &member) {
+        return command(cmd::smove, source, destination, member);
+    }
+
+    QueuedRedis& spop(const StringView &key) {
+        return command(cmd::spop, key);
+    }
+
+    QueuedRedis& spop(const StringView &key, long long count) {
+        return command(cmd::spop_range, key, count);
+    }
+
+    QueuedRedis& srandmember(const StringView &key) {
+        return command(cmd::srandmember, key);
+    }
+
+    QueuedRedis& srandmember(const StringView &key, long long count) {
+        return command(cmd::srandmember_range, key, count);
+    }
+
+    QueuedRedis& srem(const StringView &key, const StringView &member) {
+        return command(cmd::srem, key, member);
+    }
+
+    template <typename Input>
+    QueuedRedis& srem(const StringView &key, Input first, Input last) {
+        return command(cmd::srem_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& srem(const StringView &key, std::initializer_list<T> il) {
+        return srem(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& sscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count) {
+        return command(cmd::sscan, key, cursor, pattern, count);
+    }
+
+    QueuedRedis& sscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern) {
+        return sscan(key, cursor, pattern, 10);
+    }
+
+    QueuedRedis& sscan(const StringView &key,
+                        long long cursor,
+                        long long count) {
+        return sscan(key, cursor, "*", count);
+    }
+
+    QueuedRedis& sscan(const StringView &key,
+                        long long cursor) {
+        return sscan(key, cursor, "*", 10);
+    }
+
+    template <typename Input>
+    QueuedRedis& sunion(Input first, Input last) {
+        return command(cmd::sunion<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& sunion(std::initializer_list<T> il) {
+        return sunion(il.begin(), il.end());
+    }
+
+    QueuedRedis& sunionstore(const StringView &destination, const StringView &key) {
+        return command(cmd::sunionstore, destination, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& sunionstore(const StringView &destination, Input first, Input last) {
+        return command(cmd::sunionstore_range<Input>, destination, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& sunionstore(const StringView &destination, std::initializer_list<T> il) {
+        return sunionstore(destination, il.begin(), il.end());
+    }
+
+    // SORTED SET commands.
+
+    QueuedRedis& bzpopmax(const StringView &key, long long timeout) {
+        return command(cmd::bzpopmax, key, timeout);
+    }
+
+    QueuedRedis& bzpopmax(const StringView &key,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return bzpopmax(key, timeout.count());
+    }
+
+    template <typename Input>
+    QueuedRedis& bzpopmax(Input first, Input last, long long timeout) {
+        return command(cmd::bzpopmax_range<Input>, first, last, timeout);
+    }
+
+    template <typename Input>
+    QueuedRedis& bzpopmax(Input first,
+                            Input last,
+                            const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return bzpopmax(first, last, timeout.count());
+    }
+
+    template <typename T>
+    QueuedRedis& bzpopmax(std::initializer_list<T> il, long long timeout) {
+        return bzpopmax(il.begin(), il.end(), timeout);
+    }
+
+    template <typename T>
+    QueuedRedis& bzpopmax(std::initializer_list<T> il,
+                            const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return bzpopmax(il.begin(), il.end(), timeout);
+    }
+
+    QueuedRedis& bzpopmin(const StringView &key, long long timeout) {
+        return command(cmd::bzpopmin, key, timeout);
+    }
+
+    QueuedRedis& bzpopmin(const StringView &key,
+                            const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return bzpopmin(key, timeout.count());
+    }
+
+    template <typename Input>
+    QueuedRedis& bzpopmin(Input first, Input last, long long timeout) {
+        return command(cmd::bzpopmin_range<Input>, first, last, timeout);
+    }
+
+    template <typename Input>
+    QueuedRedis& bzpopmin(Input first,
+                            Input last,
+                            const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return bzpopmin(first, last, timeout.count());
+    }
+
+    template <typename T>
+    QueuedRedis& bzpopmin(std::initializer_list<T> il, long long timeout) {
+        return bzpopmin(il.begin(), il.end(), timeout);
+    }
+
+    template <typename T>
+    QueuedRedis& bzpopmin(std::initializer_list<T> il,
+                            const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return bzpopmin(il.begin(), il.end(), timeout);
+    }
+
+    // We don't support the INCR option, since you can always use ZINCRBY instead.
+    QueuedRedis& zadd(const StringView &key,
+                        const StringView &member,
+                        double score,
+                        UpdateType type = UpdateType::ALWAYS,
+                        bool changed = false) {
+        return command(cmd::zadd, key, member, score, type, changed);
+    }
+
+    template <typename Input>
+    QueuedRedis& zadd(const StringView &key,
+                        Input first,
+                        Input last,
+                        UpdateType type = UpdateType::ALWAYS,
+                        bool changed = false) {
+        return command(cmd::zadd_range<Input>, key, first, last, type, changed);
+    }
+
+    QueuedRedis& zcard(const StringView &key) {
+        return command(cmd::zcard, key);
+    }
+
+    template <typename Interval>
+    QueuedRedis& zcount(const StringView &key, const Interval &interval) {
+        return command(cmd::zcount<Interval>, key, interval);
+    }
+
+    QueuedRedis& zincrby(const StringView &key, double increment, const StringView &member) {
+        return command(cmd::zincrby, key, increment, member);
+    }
+
+    QueuedRedis& zinterstore(const StringView &destination,
+                                const StringView &key,
+                                double weight) {
+        return command(cmd::zinterstore, destination, key, weight);
+    }
+
+    template <typename Input>
+    QueuedRedis& zinterstore(const StringView &destination,
+                                Input first,
+                                Input last,
+                                Aggregation type = Aggregation::SUM) {
+        return command(cmd::zinterstore_range<Input>, destination, first, last, type);
+    }
+
+    template <typename T>
+    QueuedRedis& zinterstore(const StringView &destination,
+                                std::initializer_list<T> il,
+                                Aggregation type = Aggregation::SUM) {
+        return zinterstore(destination, il.begin(), il.end(), type);
+    }
+
+    template <typename Interval>
+    QueuedRedis& zlexcount(const StringView &key, const Interval &interval) {
+        return command(cmd::zlexcount<Interval>, key, interval);
+    }
+
+    QueuedRedis& zpopmax(const StringView &key) {
+        return command(cmd::zpopmax, key, 1);
+    }
+
+    QueuedRedis& zpopmax(const StringView &key, long long count) {
+        return command(cmd::zpopmax, key, count);
+    }
+
+    QueuedRedis& zpopmin(const StringView &key) {
+        return command(cmd::zpopmin, key, 1);
+    }
+
+    QueuedRedis& zpopmin(const StringView &key, long long count) {
+        return command(cmd::zpopmin, key, count);
+    }
+
+    // NOTE: *QueuedRedis::zrange*'s parameters are different from *Redis::zrange*.
+    // *Redis::zrange* is overloaded by the output iterator, however, there's no such
+    // iterator in *QueuedRedis::zrange*. So we have to use an extra parameter: *with_scores*,
+    // to decide whether we should send *WITHSCORES* option to Redis. This also applies to
+    // other commands with the *WITHSCORES* option, e.g. *ZRANGEBYSCORE*, *ZREVRANGE*,
+    // *ZREVRANGEBYSCORE*.
+    QueuedRedis& zrange(const StringView &key,
+                        long long start,
+                        long long stop,
+                        bool with_scores = false) {
+        return command(cmd::zrange, key, start, stop, with_scores);
+    }
+
+    template <typename Interval>
+    QueuedRedis& zrangebylex(const StringView &key,
+                                const Interval &interval,
+                                const LimitOptions &opts) {
+        return command(cmd::zrangebylex<Interval>, key, interval, opts);
+    }
+
+    template <typename Interval>
+    QueuedRedis& zrangebylex(const StringView &key, const Interval &interval) {
+        return zrangebylex(key, interval, {});
+    }
+
+    // See comments on *ZRANGE*.
+    template <typename Interval>
+    QueuedRedis& zrangebyscore(const StringView &key,
+                                const Interval &interval,
+                                const LimitOptions &opts,
+                                bool with_scores = false) {
+        return command(cmd::zrangebyscore<Interval>, key, interval, opts, with_scores);
+    }
+
+    // See comments on *ZRANGE*.
+    template <typename Interval>
+    QueuedRedis& zrangebyscore(const StringView &key,
+                                const Interval &interval,
+                                bool with_scores = false) {
+        return zrangebyscore(key, interval, {}, with_scores);
+    }
+
+    QueuedRedis& zrank(const StringView &key, const StringView &member) {
+        return command(cmd::zrank, key, member);
+    }
+
+    QueuedRedis& zrem(const StringView &key, const StringView &member) {
+        return command(cmd::zrem, key, member);
+    }
+
+    template <typename Input>
+    QueuedRedis& zrem(const StringView &key, Input first, Input last) {
+        return command(cmd::zrem_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& zrem(const StringView &key, std::initializer_list<T> il) {
+        return zrem(key, il.begin(), il.end());
+    }
+
+    template <typename Interval>
+    QueuedRedis& zremrangebylex(const StringView &key, const Interval &interval) {
+        return command(cmd::zremrangebylex<Interval>, key, interval);
+    }
+
+    QueuedRedis& zremrangebyrank(const StringView &key, long long start, long long stop) {
+        return command(cmd::zremrangebyrank, key, start, stop);
+    }
+
+    template <typename Interval>
+    QueuedRedis& zremrangebyscore(const StringView &key, const Interval &interval) {
+        return command(cmd::zremrangebyscore<Interval>, key, interval);
+    }
+
+    // See comments on *ZRANGE*.
+    QueuedRedis& zrevrange(const StringView &key,
+                            long long start,
+                            long long stop,
+                            bool with_scores = false) {
+        return command(cmd::zrevrange, key, start, stop, with_scores);
+    }
+
+    template <typename Interval>
+    QueuedRedis& zrevrangebylex(const StringView &key,
+                                const Interval &interval,
+                                const LimitOptions &opts) {
+        return command(cmd::zrevrangebylex<Interval>, key, interval, opts);
+    }
+
+    template <typename Interval>
+    QueuedRedis& zrevrangebylex(const StringView &key, const Interval &interval) {
+        return zrevrangebylex(key, interval, {});
+    }
+
+    // See comments on *ZRANGE*.
+    template <typename Interval>
+    QueuedRedis& zrevrangebyscore(const StringView &key,
+                                    const Interval &interval,
+                                    const LimitOptions &opts,
+                                    bool with_scores = false) {
+        return command(cmd::zrevrangebyscore<Interval>, key, interval, opts, with_scores);
+    }
+
+    // See comments on *ZRANGE*.
+    template <typename Interval>
+    QueuedRedis& zrevrangebyscore(const StringView &key,
+                                    const Interval &interval,
+                                    bool with_scores = false) {
+        return zrevrangebyscore(key, interval, {}, with_scores);
+    }
+
+    QueuedRedis& zrevrank(const StringView &key, const StringView &member) {
+        return command(cmd::zrevrank, key, member);
+    }
+
+    QueuedRedis& zscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count) {
+        return command(cmd::zscan, key, cursor, pattern, count);
+    }
+
+    QueuedRedis& zscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern) {
+        return zscan(key, cursor, pattern, 10);
+    }
+
+    QueuedRedis& zscan(const StringView &key,
+                        long long cursor,
+                        long long count) {
+        return zscan(key, cursor, "*", count);
+    }
+
+    QueuedRedis& zscan(const StringView &key,
+                        long long cursor) {
+        return zscan(key, cursor, "*", 10);
+    }
+
+    QueuedRedis& zscore(const StringView &key, const StringView &member) {
+        return command(cmd::zscore, key, member);
+    }
+
+    QueuedRedis& zunionstore(const StringView &destination,
+                                const StringView &key,
+                                double weight) {
+        return command(cmd::zunionstore, destination, key, weight);
+    }
+
+    template <typename Input>
+    QueuedRedis& zunionstore(const StringView &destination,
+                                Input first,
+                                Input last,
+                                Aggregation type = Aggregation::SUM) {
+        return command(cmd::zunionstore_range<Input>, destination, first, last, type);
+    }
+
+    template <typename T>
+    QueuedRedis& zunionstore(const StringView &destination,
+                                std::initializer_list<T> il,
+                                Aggregation type = Aggregation::SUM) {
+        return zunionstore(destination, il.begin(), il.end(), type);
+    }
+
+    // HYPERLOGLOG commands.
+
+    QueuedRedis& pfadd(const StringView &key, const StringView &element) {
+        return command(cmd::pfadd, key, element);
+    }
+
+    template <typename Input>
+    QueuedRedis& pfadd(const StringView &key, Input first, Input last) {
+        return command(cmd::pfadd_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& pfadd(const StringView &key, std::initializer_list<T> il) {
+        return pfadd(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& pfcount(const StringView &key) {
+        return command(cmd::pfcount, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& pfcount(Input first, Input last) {
+        return command(cmd::pfcount_range<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& pfcount(std::initializer_list<T> il) {
+        return pfcount(il.begin(), il.end());
+    }
+
+    QueuedRedis& pfmerge(const StringView &destination, const StringView &key) {
+        return command(cmd::pfmerge, destination, key);
+    }
+
+    template <typename Input>
+    QueuedRedis& pfmerge(const StringView &destination, Input first, Input last) {
+        return command(cmd::pfmerge_range<Input>, destination, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& pfmerge(const StringView &destination, std::initializer_list<T> il) {
+        return pfmerge(destination, il.begin(), il.end());
+    }
+
+    // GEO commands.
+
+    QueuedRedis& geoadd(const StringView &key,
+                        const std::tuple<StringView, double, double> &member) {
+        return command(cmd::geoadd, key, member);
+    }
+
+    template <typename Input>
+    QueuedRedis& geoadd(const StringView &key,
+                        Input first,
+                        Input last) {
+        return command(cmd::geoadd_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& geoadd(const StringView &key, std::initializer_list<T> il) {
+        return geoadd(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& geodist(const StringView &key,
+                            const StringView &member1,
+                            const StringView &member2,
+                            GeoUnit unit = GeoUnit::M) {
+        return command(cmd::geodist, key, member1, member2, unit);
+    }
+
+    template <typename Input>
+    QueuedRedis& geohash(const StringView &key, Input first, Input last) {
+        return command(cmd::geohash_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& geohash(const StringView &key, std::initializer_list<T> il) {
+        return geohash(key, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    QueuedRedis& geopos(const StringView &key, Input first, Input last) {
+        return command(cmd::geopos_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& geopos(const StringView &key, std::initializer_list<T> il) {
+        return geopos(key, il.begin(), il.end());
+    }
+
+    // TODO:
+    // 1. since we have different overloads for georadius and georadius-store,
+    //    we might use the GEORADIUS_RO command in the future.
+    // 2. there're too many parameters for this method, we might refactor it.
+    QueuedRedis& georadius(const StringView &key,
+                            const std::pair<double, double> &loc,
+                            double radius,
+                            GeoUnit unit,
+                            const StringView &destination,
+                            bool store_dist,
+                            long long count) {
+        _georadius_cmd_indexes.push_back(_cmd_num);
+
+        return command(cmd::georadius_store,
+                        key,
+                        loc,
+                        radius,
+                        unit,
+                        destination,
+                        store_dist,
+                        count);
+    }
+
+    // NOTE: *QueuedRedis::georadius*'s parameters are different from *Redis::georadius*.
+    // *Redis::georadius* is overloaded by the output iterator, however, there's no such
+    // iterator in *QueuedRedis::georadius*. So we have to use extra parameters to decide
+    // whether we should send options to Redis. This also applies to *GEORADIUSBYMEMBER*.
+    QueuedRedis& georadius(const StringView &key,
+                            const std::pair<double, double> &loc,
+                            double radius,
+                            GeoUnit unit,
+                            long long count,
+                            bool asc,
+                            bool with_coord,
+                            bool with_dist,
+                            bool with_hash) {
+        return command(cmd::georadius,
+                        key,
+                        loc,
+                        radius,
+                        unit,
+                        count,
+                        asc,
+                        with_coord,
+                        with_dist,
+                        with_hash);
+    }
+
+    QueuedRedis& georadiusbymember(const StringView &key,
+                                    const StringView &member,
+                                    double radius,
+                                    GeoUnit unit,
+                                    const StringView &destination,
+                                    bool store_dist,
+                                    long long count) {
+        _georadius_cmd_indexes.push_back(_cmd_num);
+
+        return command(cmd::georadiusbymember,
+                        key,
+                        member,
+                        radius,
+                        unit,
+                        destination,
+                        store_dist,
+                        count);
+    }
+
+    // See the comments on *GEORADIUS*.
+    QueuedRedis& georadiusbymember(const StringView &key,
+                                    const StringView &member,
+                                    double radius,
+                                    GeoUnit unit,
+                                    long long count,
+                                    bool asc,
+                                    bool with_coord,
+                                    bool with_dist,
+                                    bool with_hash) {
+        return command(cmd::georadiusbymember,
+                        key,
+                        member,
+                        radius,
+                        unit,
+                        count,
+                        asc,
+                        with_coord,
+                        with_dist,
+                        with_hash);
+    }
+
+    // SCRIPTING commands.
+
+    QueuedRedis& eval(const StringView &script,
+                        std::initializer_list<StringView> keys,
+                        std::initializer_list<StringView> args) {
+        return command(cmd::eval, script, keys, args);
+    }
+
+    QueuedRedis& evalsha(const StringView &script,
+                            std::initializer_list<StringView> keys,
+                            std::initializer_list<StringView> args) {
+        return command(cmd::evalsha, script, keys, args);
+    }
+
+    template <typename Input>
+    QueuedRedis& script_exists(Input first, Input last) {
+        return command(cmd::script_exists_range<Input>, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& script_exists(std::initializer_list<T> il) {
+        return script_exists(il.begin(), il.end());
+    }
+
+    QueuedRedis& script_flush() {
+        return command(cmd::script_flush);
+    }
+
+    QueuedRedis& script_kill() {
+        return command(cmd::script_kill);
+    }
+
+    QueuedRedis& script_load(const StringView &script) {
+        return command(cmd::script_load, script);
+    }
+
+    // PUBSUB commands.
+
+    QueuedRedis& publish(const StringView &channel, const StringView &message) {
+        return command(cmd::publish, channel, message);
+    }
+
+    // Stream commands.
+
+    QueuedRedis& xack(const StringView &key, const StringView &group, const StringView &id) {
+        return command(cmd::xack, key, group, id);
+    }
+
+    template <typename Input>
+    QueuedRedis& xack(const StringView &key, const StringView &group, Input first, Input last) {
+        return command(cmd::xack_range<Input>, key, group, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& xack(const StringView &key, const StringView &group, std::initializer_list<T> il) {
+        return xack(key, group, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    QueuedRedis& xadd(const StringView &key, const StringView &id, Input first, Input last) {
+        return command(cmd::xadd_range<Input>, key, id, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& xadd(const StringView &key, const StringView &id, std::initializer_list<T> il) {
+        return xadd(key, id, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    QueuedRedis& xadd(const StringView &key,
+                        const StringView &id,
+                        Input first,
+                        Input last,
+                        long long count,
+                        bool approx = true) {
+        return command(cmd::xadd_maxlen_range<Input>, key, id, first, last, count, approx);
+    }
+
+    template <typename T>
+    QueuedRedis& xadd(const StringView &key,
+                        const StringView &id,
+                        std::initializer_list<T> il,
+                        long long count,
+                        bool approx = true) {
+        return xadd(key, id, il.begin(), il.end(), count, approx);
+    }
+
+    QueuedRedis& xclaim(const StringView &key,
+                        const StringView &group,
+                        const StringView &consumer,
+                        const std::chrono::milliseconds &min_idle_time,
+                        const StringView &id) {
+        return command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id);
+    }
+
+    template <typename Input>
+    QueuedRedis& xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                Input first,
+                Input last) {
+        return command(cmd::xclaim_range<Input>,
+                        key,
+                        group,
+                        consumer,
+                        min_idle_time.count(),
+                        first,
+                        last);
+    }
+
+    template <typename T>
+    QueuedRedis& xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                std::initializer_list<T> il) {
+        return xclaim(key, group, consumer, min_idle_time, il.begin(), il.end());
+    }
+
+    QueuedRedis& xdel(const StringView &key, const StringView &id) {
+        return command(cmd::xdel, key, id);
+    }
+
+    template <typename Input>
+    QueuedRedis& xdel(const StringView &key, Input first, Input last) {
+        return command(cmd::xdel_range<Input>, key, first, last);
+    }
+
+    template <typename T>
+    QueuedRedis& xdel(const StringView &key, std::initializer_list<T> il) {
+        return xdel(key, il.begin(), il.end());
+    }
+
+    QueuedRedis& xgroup_create(const StringView &key,
+                                const StringView &group,
+                                const StringView &id,
+                                bool mkstream = false) {
+        return command(cmd::xgroup_create, key, group, id, mkstream);
+    }
+
+    QueuedRedis& xgroup_setid(const StringView &key,
+                                const StringView &group,
+                                const StringView &id) {
+        return command(cmd::xgroup_setid, key, group, id);
+    }
+
+    QueuedRedis& xgroup_destroy(const StringView &key, const StringView &group) {
+        return command(cmd::xgroup_destroy, key, group);
+    }
+
+    QueuedRedis& xgroup_delconsumer(const StringView &key,
+                                    const StringView &group,
+                                    const StringView &consumer) {
+        return command(cmd::xgroup_delconsumer, key, group, consumer);
+    }
+
+    QueuedRedis& xlen(const StringView &key) {
+        return command(cmd::xlen, key);
+    }
+
+    QueuedRedis& xpending(const StringView &key, const StringView &group) {
+        return command(cmd::xpending, key, group);
+    }
+
+    QueuedRedis& xpending(const StringView &key,
+                            const StringView &group,
+                            const StringView &start,
+                            const StringView &end,
+                            long long count) {
+        return command(cmd::xpending_detail, key, group, start, end, count);
+    }
+
+    QueuedRedis& xpending(const StringView &key,
+                            const StringView &group,
+                            const StringView &start,
+                            const StringView &end,
+                            long long count,
+                            const StringView &consumer) {
+        return command(cmd::xpending_per_consumer, key, group, start, end, count, consumer);
+    }
+
+    QueuedRedis& xrange(const StringView &key,
+                        const StringView &start,
+                        const StringView &end) {
+        return command(cmd::xrange, key, start, end);
+    }
+
+    QueuedRedis& xrange(const StringView &key,
+                        const StringView &start,
+                        const StringView &end,
+                        long long count) {
+        return command(cmd::xrange, key, start, end, count);
+    }
+
+    QueuedRedis& xread(const StringView &key, const StringView &id, long long count) {
+        return command(cmd::xread, key, id, count);
+    }
+
+    QueuedRedis& xread(const StringView &key, const StringView &id) {
+        return xread(key, id, 0);
+    }
+
+    template <typename Input>
+    auto xread(Input first, Input last, long long count)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return command(cmd::xread_range<Input>, first, last, count);
+    }
+
+    template <typename Input>
+    auto xread(Input first, Input last)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return xread(first, last, 0);
+    }
+
+    QueuedRedis& xread(const StringView &key,
+                        const StringView &id,
+                        const std::chrono::milliseconds &timeout,
+                        long long count) {
+        return command(cmd::xread_block, key, id, timeout.count(), count);
+    }
+
+    QueuedRedis& xread(const StringView &key,
+                        const StringView &id,
+                        const std::chrono::milliseconds &timeout) {
+        return xread(key, id, timeout, 0);
+    }
+
+    template <typename Input>
+    auto xread(Input first,
+                Input last,
+                const std::chrono::milliseconds &timeout,
+                long long count)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return command(cmd::xread_block_range<Input>, first, last, timeout.count(), count);
+    }
+
+    template <typename Input>
+    auto xread(Input first,
+                Input last,
+                const std::chrono::milliseconds &timeout)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return xread(first, last, timeout, 0);
+    }
+
+    QueuedRedis& xreadgroup(const StringView &group,
+                            const StringView &consumer,
+                            const StringView &key,
+                            const StringView &id,
+                            long long count,
+                            bool noack) {
+        return command(cmd::xreadgroup, group, consumer, key, id, count, noack);
+    }
+
+    QueuedRedis& xreadgroup(const StringView &group,
+                            const StringView &consumer,
+                            const StringView &key,
+                            const StringView &id,
+                            long long count) {
+        return xreadgroup(group, consumer, key, id, count, false);
+    }
+
+    template <typename Input>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    long long count,
+                    bool noack)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return command(cmd::xreadgroup_range<Input>, group, consumer, first, last, count, noack);
+    }
+
+    template <typename Input>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    long long count)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return xreadgroup(group, consumer, first ,last, count, false);
+    }
+
+    template <typename Input>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return xreadgroup(group, consumer, first ,last, 0, false);
+    }
+
+    QueuedRedis& xreadgroup(const StringView &group,
+                            const StringView &consumer,
+                            const StringView &key,
+                            const StringView &id,
+                            const std::chrono::milliseconds &timeout,
+                            long long count,
+                            bool noack) {
+        return command(cmd::xreadgroup_block,
+                        group,
+                        consumer,
+                        key,
+                        id,
+                        timeout.count(),
+                        count,
+                        noack);
+    }
+
+    QueuedRedis& xreadgroup(const StringView &group,
+                            const StringView &consumer,
+                            const StringView &key,
+                            const StringView &id,
+                            const std::chrono::milliseconds &timeout,
+                            long long count) {
+        return xreadgroup(group, consumer, key, id, timeout, count, false);
+    }
+
+    QueuedRedis& xreadgroup(const StringView &group,
+                            const StringView &consumer,
+                            const StringView &key,
+                            const StringView &id,
+                            const std::chrono::milliseconds &timeout) {
+        return xreadgroup(group, consumer, key, id, timeout, 0, false);
+    }
+
+    template <typename Input>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    bool noack)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return command(cmd::xreadgroup_block_range<Input>,
+                        group,
+                        consumer,
+                        first,
+                        last,
+                        timeout.count(),
+                        count,
+                        noack);
+    }
+
+    template <typename Input>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    long long count)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return xreadgroup(group, consumer, first, last, timeout, count, false);
+    }
+
+    template <typename Input>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value,
+                                    QueuedRedis&>::type {
+        return xreadgroup(group, consumer, first, last, timeout, 0, false);
+    }
+
+    QueuedRedis& xrevrange(const StringView &key,
+                            const StringView &end,
+                            const StringView &start) {
+        return command(cmd::xrevrange, key, end, start);
+    }
+
+    QueuedRedis& xrevrange(const StringView &key,
+                            const StringView &end,
+                            const StringView &start,
+                            long long count) {
+        return command(cmd::xrevrange, key, end, start, count);
+    }
+
+    QueuedRedis& xtrim(const StringView &key, long long count, bool approx = true) {
+        return command(cmd::xtrim, key, count, approx);
+    }
+
+private:
+    friend class Redis;
+
+    friend class RedisCluster;
+
+    template <typename ...Args>
+    QueuedRedis(const ConnectionSPtr &connection, Args &&...args);
+
+    void _sanity_check() const;
+
+    void _reset();
+
+    void _invalidate();
+
+    void _rewrite_replies(std::vector<ReplyUPtr> &replies) const;
+
+    template <typename Func>
+    void _rewrite_replies(const std::vector<std::size_t> &indexes,
+                            Func rewriter,
+                            std::vector<ReplyUPtr> &replies) const;
+
+    ConnectionSPtr _connection;
+
+    Impl _impl;
+
+    std::size_t _cmd_num = 0;
+
+    std::vector<std::size_t> _set_cmd_indexes;
+
+    std::vector<std::size_t> _georadius_cmd_indexes;
+
+    bool _valid = true;
+};
+
+class QueuedReplies {
+public:
+    std::size_t size() const;
+
+    redisReply& get(std::size_t idx);
+
+    template <typename Result>
+    Result get(std::size_t idx);
+
+    template <typename Output>
+    void get(std::size_t idx, Output output);
+
+private:
+    template <typename Impl>
+    friend class QueuedRedis;
+
+    explicit QueuedReplies(std::vector<ReplyUPtr> replies) : _replies(std::move(replies)) {}
+
+    void _index_check(std::size_t idx) const;
+
+    std::vector<ReplyUPtr> _replies;
+};
+
+}
+
+}
+
+#include "queued_redis.hpp"
+
+#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_H

+ 208 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp

@@ -0,0 +1,208 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
+#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
+
+namespace sw {
+
+namespace redis {
+
+template <typename Impl>
+template <typename ...Args>
+QueuedRedis<Impl>::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) :
+            _connection(connection),
+            _impl(std::forward<Args>(args)...) {
+    assert(_connection);
+}
+
+template <typename Impl>
+Redis QueuedRedis<Impl>::redis() {
+    return Redis(_connection);
+}
+
+template <typename Impl>
+template <typename Cmd, typename ...Args>
+auto QueuedRedis<Impl>::command(Cmd cmd, Args &&...args)
+    -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value,
+                                QueuedRedis<Impl>&>::type {
+    try {
+        _sanity_check();
+
+        _impl.command(*_connection, cmd, std::forward<Args>(args)...);
+
+        ++_cmd_num;
+    } catch (const Error &e) {
+        _invalidate();
+        throw;
+    }
+
+    return *this;
+}
+
+template <typename Impl>
+template <typename ...Args>
+QueuedRedis<Impl>& QueuedRedis<Impl>::command(const StringView &cmd_name, Args &&...args) {
+    auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) {
+                    CmdArgs cmd_args;
+                    cmd_args.append(cmd_name, std::forward<Args>(args)...);
+                    connection.send(cmd_args);
+    };
+
+    return command(cmd, cmd_name, std::forward<Args>(args)...);
+}
+
+template <typename Impl>
+template <typename Input>
+auto QueuedRedis<Impl>::command(Input first, Input last)
+    -> typename std::enable_if<IsIter<Input>::value, QueuedRedis<Impl>&>::type {
+    if (first == last) {
+        throw Error("command: empty range");
+    }
+
+    auto cmd = [](Connection &connection, Input first, Input last) {
+                    CmdArgs cmd_args;
+                    while (first != last) {
+                        cmd_args.append(*first);
+                        ++first;
+                    }
+                    connection.send(cmd_args);
+    };
+
+    return command(cmd, first, last);
+}
+
+template <typename Impl>
+QueuedReplies QueuedRedis<Impl>::exec() {
+    try {
+        _sanity_check();
+
+        auto replies = _impl.exec(*_connection, _cmd_num);
+
+        _rewrite_replies(replies);
+
+        _reset();
+
+        return QueuedReplies(std::move(replies));
+    } catch (const Error &e) {
+        _invalidate();
+        throw;
+    }
+}
+
+template <typename Impl>
+void QueuedRedis<Impl>::discard() {
+    try {
+        _sanity_check();
+
+        _impl.discard(*_connection, _cmd_num);
+
+        _reset();
+    } catch (const Error &e) {
+        _invalidate();
+        throw;
+    }
+}
+
+template <typename Impl>
+void QueuedRedis<Impl>::_sanity_check() const {
+    if (!_valid) {
+        throw Error("Not in valid state");
+    }
+
+    if (_connection->broken()) {
+        throw Error("Connection is broken");
+    }
+}
+
+template <typename Impl>
+inline void QueuedRedis<Impl>::_reset() {
+    _cmd_num = 0;
+
+    _set_cmd_indexes.clear();
+
+    _georadius_cmd_indexes.clear();
+}
+
+template <typename Impl>
+void QueuedRedis<Impl>::_invalidate() {
+    _valid = false;
+
+    _reset();
+}
+
+template <typename Impl>
+void QueuedRedis<Impl>::_rewrite_replies(std::vector<ReplyUPtr> &replies) const {
+    _rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies);
+
+    _rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies);
+}
+
+template <typename Impl>
+template <typename Func>
+void QueuedRedis<Impl>::_rewrite_replies(const std::vector<std::size_t> &indexes,
+                                            Func rewriter,
+                                            std::vector<ReplyUPtr> &replies) const {
+    for (auto idx : indexes) {
+        assert(idx < replies.size());
+
+        auto &reply = replies[idx];
+
+        assert(reply);
+
+        rewriter(*reply);
+    }
+}
+
+inline std::size_t QueuedReplies::size() const {
+    return _replies.size();
+}
+
+inline redisReply& QueuedReplies::get(std::size_t idx) {
+    _index_check(idx);
+
+    auto &reply = _replies[idx];
+
+    assert(reply);
+
+    return *reply;
+}
+
+template <typename Result>
+inline Result QueuedReplies::get(std::size_t idx) {
+    auto &reply = get(idx);
+
+    return reply::parse<Result>(reply);
+}
+
+template <typename Output>
+inline void QueuedReplies::get(std::size_t idx, Output output) {
+    auto &reply = get(idx);
+
+    reply::to_array(reply, output);
+}
+
+inline void QueuedReplies::_index_check(std::size_t idx) const {
+    if (idx >= size()) {
+        throw Error("Out of range");
+    }
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP

+ 25 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h

@@ -0,0 +1,25 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
+#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
+
+#include "redis.h"
+#include "redis_cluster.h"
+#include "queued_redis.h"
+#include "sentinel.h"
+
+#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H

+ 1523 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h

@@ -0,0 +1,1523 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_REDIS_H
+#define SEWENEW_REDISPLUSPLUS_REDIS_H
+
+#include <string>
+#include <chrono>
+#include <memory>
+#include <initializer_list>
+#include <tuple>
+#include "connection_pool.h"
+#include "reply.h"
+#include "command_options.h"
+#include "utils.h"
+#include "subscriber.h"
+#include "pipeline.h"
+#include "transaction.h"
+#include "sentinel.h"
+
+namespace sw {
+
+namespace redis {
+
+template <typename Impl>
+class QueuedRedis;
+
+using Transaction = QueuedRedis<TransactionImpl>;
+
+using Pipeline = QueuedRedis<PipelineImpl>;
+
+class Redis {
+public:
+    Redis(const ConnectionOptions &connection_opts,
+            const ConnectionPoolOptions &pool_opts = {}) : _pool(pool_opts, connection_opts) {}
+
+    // Construct Redis instance with URI:
+    // "tcp://127.0.0.1", "tcp://127.0.0.1:6379", or "unix://path/to/socket"
+    explicit Redis(const std::string &uri);
+
+    Redis(const std::shared_ptr<Sentinel> &sentinel,
+            const std::string &master_name,
+            Role role,
+            const ConnectionOptions &connection_opts,
+            const ConnectionPoolOptions &pool_opts = {}) :
+                _pool(SimpleSentinel(sentinel, master_name, role), pool_opts, connection_opts) {}
+
+    Redis(const Redis &) = delete;
+    Redis& operator=(const Redis &) = delete;
+
+    Redis(Redis &&) = default;
+    Redis& operator=(Redis &&) = default;
+
+    Pipeline pipeline();
+
+    Transaction transaction(bool piped = false);
+
+    Subscriber subscriber();
+
+    template <typename Cmd, typename ...Args>
+    auto command(Cmd cmd, Args &&...args)
+        -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type;
+
+    template <typename ...Args>
+    auto command(const StringView &cmd_name, Args &&...args)
+        -> typename std::enable_if<!IsIter<typename LastType<Args...>::type>::value,
+                                    ReplyUPtr>::type;
+
+    template <typename ...Args>
+    auto command(const StringView &cmd_name, Args &&...args)
+        -> typename std::enable_if<IsIter<typename LastType<Args...>::type>::value, void>::type;
+
+    template <typename Result, typename ...Args>
+    Result command(const StringView &cmd_name, Args &&...args);
+
+    template <typename Input>
+    auto command(Input first, Input last)
+        -> typename std::enable_if<IsIter<Input>::value, ReplyUPtr>::type;
+
+    template <typename Result, typename Input>
+    auto command(Input first, Input last)
+        -> typename std::enable_if<IsIter<Input>::value, Result>::type;
+
+    template <typename Input, typename Output>
+    auto command(Input first, Input last, Output output)
+        -> typename std::enable_if<IsIter<Input>::value, void>::type;
+
+    // CONNECTION commands.
+
+    void auth(const StringView &password);
+
+    std::string echo(const StringView &msg);
+
+    std::string ping();
+
+    std::string ping(const StringView &msg);
+
+    // After sending QUIT, only the current connection will be close, while
+    // other connections in the pool is still open. This is a strange behavior.
+    // So we DO NOT support the QUIT command. If you want to quit connection to
+    // server, just destroy the Redis object.
+    //
+    // void quit();
+
+    // We get a connection from the pool, and send the SELECT command to switch
+    // to a specified DB. However, when we try to send other commands to the
+    // given DB, we might get a different connection from the pool, and these
+    // commands, in fact, work on other DB. e.g.
+    //
+    // redis.select(1); // get a connection from the pool and switch to the 1th DB
+    // redis.get("key"); // might get another connection from the pool,
+    //                   // and try to get 'key' on the default DB
+    //
+    // Obviously, this is NOT what we expect. So we DO NOT support SELECT command.
+    // In order to select a DB, we can specify the DB index with the ConnectionOptions.
+    //
+    // However, since Pipeline and Transaction always send multiple commands on a
+    // single connection, these two classes have a *select* method.
+    //
+    // void select(long long idx);
+
+    void swapdb(long long idx1, long long idx2);
+
+    // SERVER commands.
+
+    void bgrewriteaof();
+
+    void bgsave();
+
+    long long dbsize();
+
+    void flushall(bool async = false);
+
+    void flushdb(bool async = false);
+
+    std::string info();
+
+    std::string info(const StringView &section);
+
+    long long lastsave();
+
+    void save();
+
+    // KEY commands.
+
+    long long del(const StringView &key);
+
+    template <typename Input>
+    long long del(Input first, Input last);
+
+    template <typename T>
+    long long del(std::initializer_list<T> il) {
+        return del(il.begin(), il.end());
+    }
+
+    OptionalString dump(const StringView &key);
+
+    long long exists(const StringView &key);
+
+    template <typename Input>
+    long long exists(Input first, Input last);
+
+    template <typename T>
+    long long exists(std::initializer_list<T> il) {
+        return exists(il.begin(), il.end());
+    }
+
+    bool expire(const StringView &key, long long timeout);
+
+    bool expire(const StringView &key, const std::chrono::seconds &timeout);
+
+    bool expireat(const StringView &key, long long timestamp);
+
+    bool expireat(const StringView &key,
+                    const std::chrono::time_point<std::chrono::system_clock,
+                                                    std::chrono::seconds> &tp);
+
+    template <typename Output>
+    void keys(const StringView &pattern, Output output);
+
+    bool move(const StringView &key, long long db);
+
+    bool persist(const StringView &key);
+
+    bool pexpire(const StringView &key, long long timeout);
+
+    bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout);
+
+    bool pexpireat(const StringView &key, long long timestamp);
+
+    bool pexpireat(const StringView &key,
+                    const std::chrono::time_point<std::chrono::system_clock,
+                                                    std::chrono::milliseconds> &tp);
+
+    long long pttl(const StringView &key);
+
+    OptionalString randomkey();
+
+    void rename(const StringView &key, const StringView &newkey);
+
+    bool renamenx(const StringView &key, const StringView &newkey);
+
+    void restore(const StringView &key,
+                    const StringView &val,
+                    long long ttl,
+                    bool replace = false);
+
+    void restore(const StringView &key,
+                    const StringView &val,
+                    const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0},
+                    bool replace = false);
+
+    // TODO: sort
+
+    template <typename Output>
+    long long scan(long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long scan(long long cursor,
+                    Output output);
+
+    template <typename Output>
+    long long scan(long long cursor,
+                    const StringView &pattern,
+                    Output output);
+
+    template <typename Output>
+    long long scan(long long cursor,
+                    long long count,
+                    Output output);
+
+    long long touch(const StringView &key);
+
+    template <typename Input>
+    long long touch(Input first, Input last);
+
+    template <typename T>
+    long long touch(std::initializer_list<T> il) {
+        return touch(il.begin(), il.end());
+    }
+
+    long long ttl(const StringView &key);
+
+    std::string type(const StringView &key);
+
+    long long unlink(const StringView &key);
+
+    template <typename Input>
+    long long unlink(Input first, Input last);
+
+    template <typename T>
+    long long unlink(std::initializer_list<T> il) {
+        return unlink(il.begin(), il.end());
+    }
+
+    long long wait(long long numslaves, long long timeout);
+
+    long long wait(long long numslaves, const std::chrono::milliseconds &timeout);
+
+    // STRING commands.
+
+    long long append(const StringView &key, const StringView &str);
+
+    long long bitcount(const StringView &key, long long start = 0, long long end = -1);
+
+    long long bitop(BitOp op, const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long bitop(BitOp op, const StringView &destination, Input first, Input last);
+
+    template <typename T>
+    long long bitop(BitOp op, const StringView &destination, std::initializer_list<T> il) {
+        return bitop(op, destination, il.begin(), il.end());
+    }
+
+    long long bitpos(const StringView &key,
+                        long long bit,
+                        long long start = 0,
+                        long long end = -1);
+
+    long long decr(const StringView &key);
+
+    long long decrby(const StringView &key, long long decrement);
+
+    OptionalString get(const StringView &key);
+
+    long long getbit(const StringView &key, long long offset);
+
+    std::string getrange(const StringView &key, long long start, long long end);
+
+    OptionalString getset(const StringView &key, const StringView &val);
+
+    long long incr(const StringView &key);
+
+    long long incrby(const StringView &key, long long increment);
+
+    double incrbyfloat(const StringView &key, double increment);
+
+    template <typename Input, typename Output>
+    void mget(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void mget(std::initializer_list<T> il, Output output) {
+        mget(il.begin(), il.end(), output);
+    }
+
+    template <typename Input>
+    void mset(Input first, Input last);
+
+    template <typename T>
+    void mset(std::initializer_list<T> il) {
+        mset(il.begin(), il.end());
+    }
+
+    template <typename Input>
+    bool msetnx(Input first, Input last);
+
+    template <typename T>
+    bool msetnx(std::initializer_list<T> il) {
+        return msetnx(il.begin(), il.end());
+    }
+
+    void psetex(const StringView &key,
+                long long ttl,
+                const StringView &val);
+
+    void psetex(const StringView &key,
+                const std::chrono::milliseconds &ttl,
+                const StringView &val);
+
+    bool set(const StringView &key,
+                const StringView &val,
+                const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
+                UpdateType type = UpdateType::ALWAYS);
+
+    void setex(const StringView &key,
+                long long ttl,
+                const StringView &val);
+
+    void setex(const StringView &key,
+                const std::chrono::seconds &ttl,
+                const StringView &val);
+
+    bool setnx(const StringView &key, const StringView &val);
+
+    long long setrange(const StringView &key, long long offset, const StringView &val);
+
+    long long strlen(const StringView &key);
+
+    // LIST commands.
+
+    OptionalStringPair blpop(const StringView &key, long long timeout);
+
+    OptionalStringPair blpop(const StringView &key,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename Input>
+    OptionalStringPair blpop(Input first, Input last, long long timeout);
+
+    template <typename T>
+    OptionalStringPair blpop(std::initializer_list<T> il, long long timeout) {
+        return blpop(il.begin(), il.end(), timeout);
+    }
+
+    template <typename Input>
+    OptionalStringPair blpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename T>
+    OptionalStringPair blpop(std::initializer_list<T> il,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return blpop(il.begin(), il.end(), timeout);
+    }
+
+    OptionalStringPair brpop(const StringView &key, long long timeout);
+
+    OptionalStringPair brpop(const StringView &key,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename Input>
+    OptionalStringPair brpop(Input first, Input last, long long timeout);
+
+    template <typename T>
+    OptionalStringPair brpop(std::initializer_list<T> il, long long timeout) {
+        return brpop(il.begin(), il.end(), timeout);
+    }
+
+    template <typename Input>
+    OptionalStringPair brpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename T>
+    OptionalStringPair brpop(std::initializer_list<T> il,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return brpop(il.begin(), il.end(), timeout);
+    }
+
+    OptionalString brpoplpush(const StringView &source,
+                                const StringView &destination,
+                                long long timeout);
+
+    OptionalString brpoplpush(const StringView &source,
+                                const StringView &destination,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    OptionalString lindex(const StringView &key, long long index);
+
+    long long linsert(const StringView &key,
+                        InsertPosition position,
+                        const StringView &pivot,
+                        const StringView &val);
+
+    long long llen(const StringView &key);
+
+    OptionalString lpop(const StringView &key);
+
+    long long lpush(const StringView &key, const StringView &val);
+
+    template <typename Input>
+    long long lpush(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long lpush(const StringView &key, std::initializer_list<T> il) {
+        return lpush(key, il.begin(), il.end());
+    }
+
+    long long lpushx(const StringView &key, const StringView &val);
+
+    template <typename Output>
+    void lrange(const StringView &key, long long start, long long stop, Output output);
+
+    long long lrem(const StringView &key, long long count, const StringView &val);
+
+    void lset(const StringView &key, long long index, const StringView &val);
+
+    void ltrim(const StringView &key, long long start, long long stop);
+
+    OptionalString rpop(const StringView &key);
+
+    OptionalString rpoplpush(const StringView &source, const StringView &destination);
+
+    long long rpush(const StringView &key, const StringView &val);
+
+    template <typename Input>
+    long long rpush(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long rpush(const StringView &key, std::initializer_list<T> il) {
+        return rpush(key, il.begin(), il.end());
+    }
+
+    long long rpushx(const StringView &key, const StringView &val);
+
+    // HASH commands.
+
+    long long hdel(const StringView &key, const StringView &field);
+
+    template <typename Input>
+    long long hdel(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long hdel(const StringView &key, std::initializer_list<T> il) {
+        return hdel(key, il.begin(), il.end());
+    }
+
+    bool hexists(const StringView &key, const StringView &field);
+
+    OptionalString hget(const StringView &key, const StringView &field);
+
+    template <typename Output>
+    void hgetall(const StringView &key, Output output);
+
+    long long hincrby(const StringView &key, const StringView &field, long long increment);
+
+    double hincrbyfloat(const StringView &key, const StringView &field, double increment);
+
+    template <typename Output>
+    void hkeys(const StringView &key, Output output);
+
+    long long hlen(const StringView &key);
+
+    template <typename Input, typename Output>
+    void hmget(const StringView &key, Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void hmget(const StringView &key, std::initializer_list<T> il, Output output) {
+        hmget(key, il.begin(), il.end(), output);
+    }
+
+    template <typename Input>
+    void hmset(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    void hmset(const StringView &key, std::initializer_list<T> il) {
+        hmset(key, il.begin(), il.end());
+    }
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    Output output);
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    Output output);
+
+    bool hset(const StringView &key, const StringView &field, const StringView &val);
+
+    bool hset(const StringView &key, const std::pair<StringView, StringView> &item);
+
+    bool hsetnx(const StringView &key, const StringView &field, const StringView &val);
+
+    bool hsetnx(const StringView &key, const std::pair<StringView, StringView> &item);
+
+    long long hstrlen(const StringView &key, const StringView &field);
+
+    template <typename Output>
+    void hvals(const StringView &key, Output output);
+
+    // SET commands.
+
+    long long sadd(const StringView &key, const StringView &member);
+
+    template <typename Input>
+    long long sadd(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long sadd(const StringView &key, std::initializer_list<T> il) {
+        return sadd(key, il.begin(), il.end());
+    }
+
+    long long scard(const StringView &key);
+
+    template <typename Input, typename Output>
+    void sdiff(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void sdiff(std::initializer_list<T> il, Output output) {
+        sdiff(il.begin(), il.end(), output);
+    }
+
+    long long sdiffstore(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long sdiffstore(const StringView &destination,
+                            Input first,
+                            Input last);
+
+    template <typename T>
+    long long sdiffstore(const StringView &destination,
+                            std::initializer_list<T> il) {
+        return sdiffstore(destination, il.begin(), il.end());
+    }
+
+    template <typename Input, typename Output>
+    void sinter(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void sinter(std::initializer_list<T> il, Output output) {
+        sinter(il.begin(), il.end(), output);
+    }
+
+    long long sinterstore(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long sinterstore(const StringView &destination,
+                            Input first,
+                            Input last);
+
+    template <typename T>
+    long long sinterstore(const StringView &destination,
+                            std::initializer_list<T> il) {
+        return sinterstore(destination, il.begin(), il.end());
+    }
+
+    bool sismember(const StringView &key, const StringView &member);
+
+    template <typename Output>
+    void smembers(const StringView &key, Output output);
+
+    bool smove(const StringView &source,
+                const StringView &destination,
+                const StringView &member);
+
+    OptionalString spop(const StringView &key);
+
+    template <typename Output>
+    void spop(const StringView &key, long long count, Output output);
+
+    OptionalString srandmember(const StringView &key);
+
+    template <typename Output>
+    void srandmember(const StringView &key, long long count, Output output);
+
+    long long srem(const StringView &key, const StringView &member);
+
+    template <typename Input>
+    long long srem(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long srem(const StringView &key, std::initializer_list<T> il) {
+        return srem(key, il.begin(), il.end());
+    }
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    Output output);
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    Output output);
+
+    template <typename Input, typename Output>
+    void sunion(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void sunion(std::initializer_list<T> il, Output output) {
+        sunion(il.begin(), il.end(), output);
+    }
+
+    long long sunionstore(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long sunionstore(const StringView &destination, Input first, Input last);
+
+    template <typename T>
+    long long sunionstore(const StringView &destination, std::initializer_list<T> il) {
+        return sunionstore(destination, il.begin(), il.end());
+    }
+
+    // SORTED SET commands.
+
+    auto bzpopmax(const StringView &key, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    auto bzpopmax(const StringView &key,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmax(Input first, Input last, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmax(Input first,
+                    Input last,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename T>
+    auto bzpopmax(std::initializer_list<T> il, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmax(il.begin(), il.end(), timeout);
+    }
+
+    template <typename T>
+    auto bzpopmax(std::initializer_list<T> il,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmax(il.begin(), il.end(), timeout);
+    }
+
+    auto bzpopmin(const StringView &key, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    auto bzpopmin(const StringView &key,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmin(Input first, Input last, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmin(Input first,
+                    Input last,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename T>
+    auto bzpopmin(std::initializer_list<T> il, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmin(il.begin(), il.end(), timeout);
+    }
+
+    template <typename T>
+    auto bzpopmin(std::initializer_list<T> il,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmin(il.begin(), il.end(), timeout);
+    }
+
+    // We don't support the INCR option, since you can always use ZINCRBY instead.
+    long long zadd(const StringView &key,
+                    const StringView &member,
+                    double score,
+                    UpdateType type = UpdateType::ALWAYS,
+                    bool changed = false);
+
+    template <typename Input>
+    long long zadd(const StringView &key,
+                    Input first,
+                    Input last,
+                    UpdateType type = UpdateType::ALWAYS,
+                    bool changed = false);
+
+    template <typename T>
+    long long zadd(const StringView &key,
+                    std::initializer_list<T> il,
+                    UpdateType type = UpdateType::ALWAYS,
+                    bool changed = false) {
+        return zadd(key, il.begin(), il.end(), type, changed);
+    }
+
+    long long zcard(const StringView &key);
+
+    template <typename Interval>
+    long long zcount(const StringView &key, const Interval &interval);
+
+    double zincrby(const StringView &key, double increment, const StringView &member);
+
+    // There's no aggregation type parameter for single key overload, since these 3 types
+    // have the same effect.
+    long long zinterstore(const StringView &destination, const StringView &key, double weight);
+
+    // If *Input* is an iterator of a container of string,
+    // we use the default weight, i.e. 1, and send
+    // *ZINTERSTORE destination numkeys key [key ...] [AGGREGATE SUM|MIN|MAX]* command.
+    // If *Input* is an iterator of a container of pair<string, double>, i.e. key-weight pair,
+    // we send the command with the given weights:
+    // *ZINTERSTORE destination numkeys key [key ...]
+    // [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]*
+    //
+    // The following code use the default weight:
+    //
+    // vector<string> keys = {"k1", "k2", "k3"};
+    // redis.zinterstore(destination, keys.begin(), keys.end());
+    //
+    // On the other hand, the following code use the given weights:
+    //
+    // vector<pair<string, double>> keys_with_weights = {{"k1", 1}, {"k2", 2}, {"k3", 3}};
+    // redis.zinterstore(destination, keys_with_weights.begin(), keys_with_weights.end());
+    //
+    // NOTE: `keys_with_weights` can also be of type `unordered_map<string, double>`.
+    // However, it will be slower than vector<pair<string, double>>, since we use
+    // `distance(first, last)` to calculate the *numkeys* parameter.
+    //
+    // This also applies to *ZUNIONSTORE* command.
+    template <typename Input>
+    long long zinterstore(const StringView &destination,
+                            Input first,
+                            Input last,
+                            Aggregation type = Aggregation::SUM);
+
+    template <typename T>
+    long long zinterstore(const StringView &destination,
+                            std::initializer_list<T> il,
+                            Aggregation type = Aggregation::SUM) {
+        return zinterstore(destination, il.begin(), il.end(), type);
+    }
+
+    template <typename Interval>
+    long long zlexcount(const StringView &key, const Interval &interval);
+
+    Optional<std::pair<std::string, double>> zpopmax(const StringView &key);
+
+    template <typename Output>
+    void zpopmax(const StringView &key, long long count, Output output);
+
+    Optional<std::pair<std::string, double>> zpopmin(const StringView &key);
+
+    template <typename Output>
+    void zpopmin(const StringView &key, long long count, Output output);
+
+    // If *output* is an iterator of a container of string,
+    // we send *ZRANGE key start stop* command.
+    // If it's an iterator of a container of pair<string, double>,
+    // we send *ZRANGE key start stop WITHSCORES* command.
+    //
+    // The following code sends *ZRANGE* without the *WITHSCORES* option:
+    //
+    // vector<string> result;
+    // redis.zrange("key", 0, -1, back_inserter(result));
+    //
+    // On the other hand, the following code sends command with *WITHSCORES* option:
+    //
+    // unordered_map<string, double> with_score;
+    // redis.zrange("key", 0, -1, inserter(with_score, with_score.end()));
+    //
+    // This also applies to other commands with the *WITHSCORES* option,
+    // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*.
+    template <typename Output>
+    void zrange(const StringView &key, long long start, long long stop, Output output);
+
+    template <typename Interval, typename Output>
+    void zrangebylex(const StringView &key, const Interval &interval, Output output);
+
+    template <typename Interval, typename Output>
+    void zrangebylex(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrangebyscore(const StringView &key, const Interval &interval, Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrangebyscore(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output);
+
+    OptionalLongLong zrank(const StringView &key, const StringView &member);
+
+    long long zrem(const StringView &key, const StringView &member);
+
+    template <typename Input>
+    long long zrem(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long zrem(const StringView &key, std::initializer_list<T> il) {
+        return zrem(key, il.begin(), il.end());
+    }
+
+    template <typename Interval>
+    long long zremrangebylex(const StringView &key, const Interval &interval);
+
+    long long zremrangebyrank(const StringView &key, long long start, long long stop);
+
+    template <typename Interval>
+    long long zremrangebyscore(const StringView &key, const Interval &interval);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Output>
+    void zrevrange(const StringView &key, long long start, long long stop, Output output);
+
+    template <typename Interval, typename Output>
+    void zrevrangebylex(const StringView &key, const Interval &interval, Output output);
+
+    template <typename Interval, typename Output>
+    void zrevrangebylex(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrevrangebyscore(const StringView &key, const Interval &interval, Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrevrangebyscore(const StringView &key,
+                            const Interval &interval,
+                            const LimitOptions &opts,
+                            Output output);
+
+    OptionalLongLong zrevrank(const StringView &key, const StringView &member);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    Output output);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    Output output);
+
+    OptionalDouble zscore(const StringView &key, const StringView &member);
+
+    // There's no aggregation type parameter for single key overload, since these 3 types
+    // have the same effect.
+    long long zunionstore(const StringView &destination, const StringView &key, double weight);
+
+    // See *zinterstore* comment for how to use this method.
+    template <typename Input>
+    long long zunionstore(const StringView &destination,
+                            Input first,
+                            Input last,
+                            Aggregation type = Aggregation::SUM);
+
+    template <typename T>
+    long long zunionstore(const StringView &destination,
+                            std::initializer_list<T> il,
+                            Aggregation type = Aggregation::SUM) {
+        return zunionstore(destination, il.begin(), il.end(), type);
+    }
+
+    // HYPERLOGLOG commands.
+
+    bool pfadd(const StringView &key, const StringView &element);
+
+    template <typename Input>
+    bool pfadd(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    bool pfadd(const StringView &key, std::initializer_list<T> il) {
+        return pfadd(key, il.begin(), il.end());
+    }
+
+    long long pfcount(const StringView &key);
+
+    template <typename Input>
+    long long pfcount(Input first, Input last);
+
+    template <typename T>
+    long long pfcount(std::initializer_list<T> il) {
+        return pfcount(il.begin(), il.end());
+    }
+
+    void pfmerge(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    void pfmerge(const StringView &destination, Input first, Input last);
+
+    template <typename T>
+    void pfmerge(const StringView &destination, std::initializer_list<T> il) {
+        pfmerge(destination, il.begin(), il.end());
+    }
+
+    // GEO commands.
+
+    long long geoadd(const StringView &key,
+                        const std::tuple<StringView, double, double> &member);
+
+    template <typename Input>
+    long long geoadd(const StringView &key,
+                        Input first,
+                        Input last);
+
+    template <typename T>
+    long long geoadd(const StringView &key,
+                        std::initializer_list<T> il) {
+        return geoadd(key, il.begin(), il.end());
+    }
+
+    OptionalDouble geodist(const StringView &key,
+                            const StringView &member1,
+                            const StringView &member2,
+                            GeoUnit unit = GeoUnit::M);
+
+    template <typename Input, typename Output>
+    void geohash(const StringView &key, Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void geohash(const StringView &key, std::initializer_list<T> il, Output output) {
+        geohash(key, il.begin(), il.end(), output);
+    }
+
+    template <typename Input, typename Output>
+    void geopos(const StringView &key, Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void geopos(const StringView &key, std::initializer_list<T> il, Output output) {
+        geopos(key, il.begin(), il.end(), output);
+    }
+
+    // TODO:
+    // 1. since we have different overloads for georadius and georadius-store,
+    //    we might use the GEORADIUS_RO command in the future.
+    // 2. there're too many parameters for this method, we might refactor it.
+    OptionalLongLong georadius(const StringView &key,
+                                const std::pair<double, double> &loc,
+                                double radius,
+                                GeoUnit unit,
+                                const StringView &destination,
+                                bool store_dist,
+                                long long count);
+
+    // If *output* is an iterator of a container of string, we send *GEORADIUS* command
+    // without any options and only get the members in the specified geo range.
+    // If *output* is an iterator of a container of a tuple, the type of the tuple decides
+    // options we send with the *GEORADIUS* command. If the tuple has an element of type
+    // double, we send the *WITHDIST* option. If it has an element of type string, we send
+    // the *WITHHASH* option. If it has an element of type pair<double, double>, we send
+    // the *WITHCOORD* option. For example:
+    //
+    // The following code only gets the members in range, i.e. without any option.
+    //
+    // vector<string> members;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(members))
+    //
+    // The following code sends the command with *WITHDIST* option.
+    //
+    // vector<tuple<string, double>> with_dist;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(with_dist))
+    //
+    // The following code sends the command with *WITHDIST* and *WITHHASH* options.
+    //
+    // vector<tuple<string, double, string>> with_dist_hash;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(with_dist_hash))
+    //
+    // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options.
+    //
+    // vector<tuple<string, double, pair<double, double>, string>> with_dist_coord_hash;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(with_dist_coord_hash))
+    //
+    // This also applies to *GEORADIUSBYMEMBER*.
+    template <typename Output>
+    void georadius(const StringView &key,
+                    const std::pair<double, double> &loc,
+                    double radius,
+                    GeoUnit unit,
+                    long long count,
+                    bool asc,
+                    Output output);
+
+    OptionalLongLong georadiusbymember(const StringView &key,
+                                        const StringView &member,
+                                        double radius,
+                                        GeoUnit unit,
+                                        const StringView &destination,
+                                        bool store_dist,
+                                        long long count);
+
+    // See comments on *GEORADIUS*.
+    template <typename Output>
+    void georadiusbymember(const StringView &key,
+                            const StringView &member,
+                            double radius,
+                            GeoUnit unit,
+                            long long count,
+                            bool asc,
+                            Output output);
+
+    // SCRIPTING commands.
+
+    template <typename Result>
+    Result eval(const StringView &script,
+                std::initializer_list<StringView> keys,
+                std::initializer_list<StringView> args);
+
+    template <typename Output>
+    void eval(const StringView &script,
+                std::initializer_list<StringView> keys,
+                std::initializer_list<StringView> args,
+                Output output);
+
+    template <typename Result>
+    Result evalsha(const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args);
+
+    template <typename Output>
+    void evalsha(const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args,
+                    Output output);
+
+    template <typename Input, typename Output>
+    void script_exists(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void script_exists(std::initializer_list<T> il, Output output) {
+        script_exists(il.begin(), il.end(), output);
+    }
+
+    void script_flush();
+
+    void script_kill();
+
+    std::string script_load(const StringView &script);
+
+    // PUBSUB commands.
+
+    long long publish(const StringView &channel, const StringView &message);
+
+    // Transaction commands.
+    void watch(const StringView &key);
+
+    template <typename Input>
+    void watch(Input first, Input last);
+
+    template <typename T>
+    void watch(std::initializer_list<T> il) {
+        watch(il.begin(), il.end());
+    }
+
+    // Stream commands.
+
+    long long xack(const StringView &key, const StringView &group, const StringView &id);
+
+    template <typename Input>
+    long long xack(const StringView &key, const StringView &group, Input first, Input last);
+
+    template <typename T>
+    long long xack(const StringView &key, const StringView &group, std::initializer_list<T> il) {
+        return xack(key, group, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    std::string xadd(const StringView &key, const StringView &id, Input first, Input last);
+
+    template <typename T>
+    std::string xadd(const StringView &key, const StringView &id, std::initializer_list<T> il) {
+        return xadd(key, id, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    std::string xadd(const StringView &key,
+                        const StringView &id,
+                        Input first,
+                        Input last,
+                        long long count,
+                        bool approx = true);
+
+    template <typename T>
+    std::string xadd(const StringView &key,
+                        const StringView &id,
+                        std::initializer_list<T> il,
+                        long long count,
+                        bool approx = true) {
+        return xadd(key, id, il.begin(), il.end(), count, approx);
+    }
+
+    template <typename Output>
+    void xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                const StringView &id,
+                Output output);
+
+    template <typename Input, typename Output>
+    void xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                Input first,
+                Input last,
+                Output output);
+
+    template <typename T, typename Output>
+    void xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                std::initializer_list<T> il,
+                Output output) {
+        xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output);
+    }
+
+    long long xdel(const StringView &key, const StringView &id);
+
+    template <typename Input>
+    long long xdel(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long xdel(const StringView &key, std::initializer_list<T> il) {
+        return xdel(key, il.begin(), il.end());
+    }
+
+    void xgroup_create(const StringView &key,
+                        const StringView &group,
+                        const StringView &id,
+                        bool mkstream = false);
+
+    void xgroup_setid(const StringView &key, const StringView &group, const StringView &id);
+
+    long long xgroup_destroy(const StringView &key, const StringView &group);
+
+    long long xgroup_delconsumer(const StringView &key,
+                                    const StringView &group,
+                                    const StringView &consumer);
+
+    long long xlen(const StringView &key);
+
+    template <typename Output>
+    auto xpending(const StringView &key, const StringView &group, Output output)
+        -> std::tuple<long long, OptionalString, OptionalString>;
+
+    template <typename Output>
+    void xpending(const StringView &key,
+                    const StringView &group,
+                    const StringView &start,
+                    const StringView &end,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    void xpending(const StringView &key,
+                    const StringView &group,
+                    const StringView &start,
+                    const StringView &end,
+                    long long count,
+                    const StringView &consumer,
+                    Output output);
+
+    template <typename Output>
+    void xrange(const StringView &key,
+                const StringView &start,
+                const StringView &end,
+                Output output);
+
+    template <typename Output>
+    void xrange(const StringView &key,
+                const StringView &start,
+                const StringView &end,
+                long long count,
+                Output output);
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                long long count,
+                Output output);
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                Output output) {
+        xread(key, id, 0, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xread(Input first, Input last, long long count, Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xread(Input first, Input last, Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xread(first ,last, 0, output);
+    }
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                const std::chrono::milliseconds &timeout,
+                long long count,
+                Output output);
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                const std::chrono::milliseconds &timeout,
+                Output output) {
+        xread(key, id, timeout, 0, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xread(Input first,
+                Input last,
+                const std::chrono::milliseconds &timeout,
+                long long count,
+                Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xread(Input first,
+                Input last,
+                const std::chrono::milliseconds &timeout,
+                Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xread(first, last, timeout, 0, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    long long count,
+                    bool noack,
+                    Output output);
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    long long count,
+                    Output output) {
+        xreadgroup(group, consumer, key, id, count, false, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    Output output) {
+        xreadgroup(group, consumer, key, id, 0, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    long long count,
+                    bool noack,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    long long count,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first ,last, count, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first ,last, 0, false, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    bool noack,
+                    Output output);
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    Output output) {
+        xreadgroup(group, consumer, key, id, timeout, count, false, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    const std::chrono::milliseconds &timeout,
+                    Output output) {
+        xreadgroup(group, consumer, key, id, timeout, 0, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    bool noack,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first, last, timeout, count, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first, last, timeout, 0, false, output);
+    }
+
+    template <typename Output>
+    void xrevrange(const StringView &key,
+                    const StringView &end,
+                    const StringView &start,
+                    Output output);
+
+    template <typename Output>
+    void xrevrange(const StringView &key,
+                    const StringView &end,
+                    const StringView &start,
+                    long long count,
+                    Output output);
+
+    long long xtrim(const StringView &key, long long count, bool approx = true);
+
+private:
+    class ConnectionPoolGuard {
+    public:
+        ConnectionPoolGuard(ConnectionPool &pool,
+                            Connection &connection) : _pool(pool), _connection(connection) {}
+
+        ~ConnectionPoolGuard() {
+            _pool.release(std::move(_connection));
+        }
+
+    private:
+        ConnectionPool &_pool;
+        Connection &_connection;
+    };
+
+    template <typename Impl>
+    friend class QueuedRedis;
+
+    friend class RedisCluster;
+
+    // For internal use.
+    explicit Redis(const ConnectionSPtr &connection);
+
+    template <std::size_t ...Is, typename ...Args>
+    ReplyUPtr _command(const StringView &cmd_name, const IndexSequence<Is...> &, Args &&...args) {
+        return command(cmd_name, NthValue<Is>(std::forward<Args>(args)...)...);
+    }
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _command(Connection &connection, Cmd cmd, Args &&...args);
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args);
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args);
+
+    template <typename Output, typename Cmd, typename ...Args>
+    ReplyUPtr _score_command(Cmd cmd, Args &&... args);
+
+    // Pool Mode.
+    // Public constructors create a *Redis* instance with a pool.
+    // In this case, *_connection* is a null pointer, and is never used.
+    ConnectionPool _pool;
+
+    // Single Connection Mode.
+    // Private constructor creats a *Redis* instance with a single connection.
+    // This is used when we create Transaction, Pipeline and Subscriber.
+    // In this case, *_pool* is empty, and is never used.
+    ConnectionSPtr _connection;
+};
+
+}
+
+}
+
+#include "redis.hpp"
+
+#endif // end SEWENEW_REDISPLUSPLUS_REDIS_H

+ 1365 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp

@@ -0,0 +1,1365 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_REDIS_HPP
+#define SEWENEW_REDISPLUSPLUS_REDIS_HPP
+
+#include "command.h"
+#include "reply.h"
+#include "utils.h"
+#include "errors.h"
+
+namespace sw {
+
+namespace redis {
+
+template <typename Cmd, typename ...Args>
+auto Redis::command(Cmd cmd, Args &&...args)
+    -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type {
+    if (_connection) {
+        // Single Connection Mode.
+        // TODO: In this case, should we reconnect?
+        if (_connection->broken()) {
+            throw Error("Connection is broken");
+        }
+
+        return _command(*_connection, cmd, std::forward<Args>(args)...);
+    } else {
+        // Pool Mode, i.e. get connection from pool.
+        auto connection = _pool.fetch();
+
+        assert(!connection.broken());
+
+        ConnectionPoolGuard guard(_pool, connection);
+
+        return _command(connection, cmd, std::forward<Args>(args)...);
+    }
+}
+
+template <typename ...Args>
+auto Redis::command(const StringView &cmd_name, Args &&...args)
+    -> typename std::enable_if<!IsIter<typename LastType<Args...>::type>::value, ReplyUPtr>::type {
+    auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) {
+                    CmdArgs cmd_args;
+                    cmd_args.append(cmd_name, std::forward<Args>(args)...);
+                    connection.send(cmd_args);
+    };
+
+    return command(cmd, cmd_name, std::forward<Args>(args)...);
+}
+
+template <typename Input>
+auto Redis::command(Input first, Input last)
+    -> typename std::enable_if<IsIter<Input>::value, ReplyUPtr>::type {
+    if (first == last) {
+        throw Error("command: empty range");
+    }
+
+    auto cmd = [](Connection &connection, Input first, Input last) {
+                    CmdArgs cmd_args;
+                    while (first != last) {
+                        cmd_args.append(*first);
+                        ++first;
+                    }
+                    connection.send(cmd_args);
+    };
+
+    return command(cmd, first, last);
+}
+
+template <typename Result, typename ...Args>
+Result Redis::command(const StringView &cmd_name, Args &&...args) {
+    auto r = command(cmd_name, std::forward<Args>(args)...);
+
+    assert(r);
+
+    return reply::parse<Result>(*r);
+}
+
+template <typename ...Args>
+auto Redis::command(const StringView &cmd_name, Args &&...args)
+    -> typename std::enable_if<IsIter<typename LastType<Args...>::type>::value, void>::type {
+    auto r = _command(cmd_name,
+                        MakeIndexSequence<sizeof...(Args) - 1>(),
+                        std::forward<Args>(args)...);
+
+    assert(r);
+
+    reply::to_array(*r, LastValue(std::forward<Args>(args)...));
+}
+
+template <typename Result, typename Input>
+auto Redis::command(Input first, Input last)
+    -> typename std::enable_if<IsIter<Input>::value, Result>::type {
+    auto r = command(first, last);
+
+    assert(r);
+
+    return reply::parse<Result>(*r);
+}
+
+template <typename Input, typename Output>
+auto Redis::command(Input first, Input last, Output output)
+    -> typename std::enable_if<IsIter<Input>::value, void>::type {
+    auto r = command(first, last);
+
+    assert(r);
+
+    reply::to_array(*r, output);
+}
+
+// KEY commands.
+
+template <typename Input>
+long long Redis::del(Input first, Input last) {
+    if (first == last) {
+        throw Error("DEL: no key specified");
+    }
+
+    auto reply = command(cmd::del_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+long long Redis::exists(Input first, Input last) {
+    if (first == last) {
+        throw Error("EXISTS: no key specified");
+    }
+
+    auto reply = command(cmd::exists_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+inline bool Redis::expire(const StringView &key, const std::chrono::seconds &timeout) {
+    return expire(key, timeout.count());
+}
+
+inline bool Redis::expireat(const StringView &key,
+                                    const std::chrono::time_point<std::chrono::system_clock,
+                                                                    std::chrono::seconds> &tp) {
+    return expireat(key, tp.time_since_epoch().count());
+}
+
+template <typename Output>
+void Redis::keys(const StringView &pattern, Output output) {
+    auto reply = command(cmd::keys, pattern);
+
+    reply::to_array(*reply, output);
+}
+
+inline bool Redis::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) {
+    return pexpire(key, timeout.count());
+}
+
+inline bool Redis::pexpireat(const StringView &key,
+                                const std::chrono::time_point<std::chrono::system_clock,
+                                                                std::chrono::milliseconds> &tp) {
+    return pexpireat(key, tp.time_since_epoch().count());
+}
+
+inline void Redis::restore(const StringView &key,
+                            const StringView &val,
+                            const std::chrono::milliseconds &ttl,
+                            bool replace) {
+    return restore(key, val, ttl.count(), replace);
+}
+
+template <typename Output>
+long long Redis::scan(long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output) {
+    auto reply = command(cmd::scan, cursor, pattern, count);
+
+    return reply::parse_scan_reply(*reply, output);
+}
+
+template <typename Output>
+inline long long Redis::scan(long long cursor,
+                                const StringView &pattern,
+                                Output output) {
+    return scan(cursor, pattern, 10, output);
+}
+
+template <typename Output>
+inline long long Redis::scan(long long cursor,
+                                long long count,
+                                Output output) {
+    return scan(cursor, "*", count, output);
+}
+
+template <typename Output>
+inline long long Redis::scan(long long cursor,
+                                Output output) {
+    return scan(cursor, "*", 10, output);
+}
+
+template <typename Input>
+long long Redis::touch(Input first, Input last) {
+    if (first == last) {
+        throw Error("TOUCH: no key specified");
+    }
+
+    auto reply = command(cmd::touch_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+long long Redis::unlink(Input first, Input last) {
+    if (first == last) {
+        throw Error("UNLINK: no key specified");
+    }
+
+    auto reply = command(cmd::unlink_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) {
+    return wait(numslaves, timeout.count());
+}
+
+// STRING commands.
+
+template <typename Input>
+long long Redis::bitop(BitOp op, const StringView &destination, Input first, Input last) {
+    if (first == last) {
+        throw Error("BITOP: no key specified");
+    }
+
+    auto reply = command(cmd::bitop_range<Input>, op, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void Redis::mget(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("MGET: no key specified");
+    }
+
+    auto reply = command(cmd::mget<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+void Redis::mset(Input first, Input last) {
+    if (first == last) {
+        throw Error("MSET: no key specified");
+    }
+
+    auto reply = command(cmd::mset<Input>, first, last);
+
+    reply::parse<void>(*reply);
+}
+
+template <typename Input>
+bool Redis::msetnx(Input first, Input last) {
+    if (first == last) {
+        throw Error("MSETNX: no key specified");
+    }
+
+    auto reply = command(cmd::msetnx<Input>, first, last);
+
+    return reply::parse<bool>(*reply);
+}
+
+inline void Redis::psetex(const StringView &key,
+                            const std::chrono::milliseconds &ttl,
+                            const StringView &val) {
+    return psetex(key, ttl.count(), val);
+}
+
+inline void Redis::setex(const StringView &key,
+                            const std::chrono::seconds &ttl,
+                            const StringView &val) {
+    setex(key, ttl.count(), val);
+}
+
+// LIST commands.
+
+template <typename Input>
+OptionalStringPair Redis::blpop(Input first, Input last, long long timeout) {
+    if (first == last) {
+        throw Error("BLPOP: no key specified");
+    }
+
+    auto reply = command(cmd::blpop_range<Input>, first, last, timeout);
+
+    return reply::parse<OptionalStringPair>(*reply);
+}
+
+template <typename Input>
+OptionalStringPair Redis::blpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout) {
+    return blpop(first, last, timeout.count());
+}
+
+template <typename Input>
+OptionalStringPair Redis::brpop(Input first, Input last, long long timeout) {
+    if (first == last) {
+        throw Error("BRPOP: no key specified");
+    }
+
+    auto reply = command(cmd::brpop_range<Input>, first, last, timeout);
+
+    return reply::parse<OptionalStringPair>(*reply);
+}
+
+template <typename Input>
+OptionalStringPair Redis::brpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout) {
+    return brpop(first, last, timeout.count());
+}
+
+inline OptionalString Redis::brpoplpush(const StringView &source,
+                                        const StringView &destination,
+                                        const std::chrono::seconds &timeout) {
+    return brpoplpush(source, destination, timeout.count());
+}
+
+template <typename Input>
+inline long long Redis::lpush(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("LPUSH: no key specified");
+    }
+
+    auto reply = command(cmd::lpush_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+inline void Redis::lrange(const StringView &key, long long start, long long stop, Output output) {
+    auto reply = command(cmd::lrange, key, start, stop);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+inline long long Redis::rpush(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("RPUSH: no key specified");
+    }
+
+    auto reply = command(cmd::rpush_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+// HASH commands.
+
+template <typename Input>
+inline long long Redis::hdel(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("HDEL: no key specified");
+    }
+
+    auto reply = command(cmd::hdel_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+inline void Redis::hgetall(const StringView &key, Output output) {
+    auto reply = command(cmd::hgetall, key);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+inline void Redis::hkeys(const StringView &key, Output output) {
+    auto reply = command(cmd::hkeys, key);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+inline void Redis::hmget(const StringView &key, Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("HMGET: no key specified");
+    }
+
+    auto reply = command(cmd::hmget<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+inline void Redis::hmset(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("HMSET: no key specified");
+    }
+
+    auto reply = command(cmd::hmset<Input>, key, first, last);
+
+    reply::parse<void>(*reply);
+}
+
+template <typename Output>
+long long Redis::hscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::hscan, key, cursor, pattern, count);
+
+    return reply::parse_scan_reply(*reply, output);
+}
+
+template <typename Output>
+inline long long Redis::hscan(const StringView &key,
+                                long long cursor,
+                                const StringView &pattern,
+                                Output output) {
+    return hscan(key, cursor, pattern, 10, output);
+}
+
+template <typename Output>
+inline long long Redis::hscan(const StringView &key,
+                                long long cursor,
+                                long long count,
+                                Output output) {
+    return hscan(key, cursor, "*", count, output);
+}
+
+template <typename Output>
+inline long long Redis::hscan(const StringView &key,
+                                long long cursor,
+                                Output output) {
+    return hscan(key, cursor, "*", 10, output);
+}
+
+template <typename Output>
+inline void Redis::hvals(const StringView &key, Output output) {
+    auto reply = command(cmd::hvals, key);
+
+    reply::to_array(*reply, output);
+}
+
+// SET commands.
+
+template <typename Input>
+long long Redis::sadd(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("SADD: no key specified");
+    }
+
+    auto reply = command(cmd::sadd_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void Redis::sdiff(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("SDIFF: no key specified");
+    }
+
+    auto reply = command(cmd::sdiff<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long Redis::sdiffstore(const StringView &destination,
+                            Input first,
+                            Input last) {
+    if (first == last) {
+        throw Error("SDIFFSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::sdiffstore_range<Input>, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void Redis::sinter(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("SINTER: no key specified");
+    }
+
+    auto reply = command(cmd::sinter<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long Redis::sinterstore(const StringView &destination,
+                            Input first,
+                            Input last) {
+    if (first == last) {
+        throw Error("SINTERSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::sinterstore_range<Input>, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+void Redis::smembers(const StringView &key, Output output) {
+    auto reply = command(cmd::smembers, key);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::spop(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::spop_range, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::srandmember(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::srandmember_range, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long Redis::srem(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("SREM: no key specified");
+    }
+
+    auto reply = command(cmd::srem_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+long long Redis::sscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::sscan, key, cursor, pattern, count);
+
+    return reply::parse_scan_reply(*reply, output);
+}
+
+template <typename Output>
+inline long long Redis::sscan(const StringView &key,
+                                long long cursor,
+                                const StringView &pattern,
+                                Output output) {
+    return sscan(key, cursor, pattern, 10, output);
+}
+
+template <typename Output>
+inline long long Redis::sscan(const StringView &key,
+                                long long cursor,
+                                long long count,
+                                Output output) {
+    return sscan(key, cursor, "*", count, output);
+}
+
+template <typename Output>
+inline long long Redis::sscan(const StringView &key,
+                                long long cursor,
+                                Output output) {
+    return sscan(key, cursor, "*", 10, output);
+}
+
+template <typename Input, typename Output>
+void Redis::sunion(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("SUNION: no key specified");
+    }
+
+    auto reply = command(cmd::sunion<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long Redis::sunionstore(const StringView &destination, Input first, Input last) {
+    if (first == last) {
+        throw Error("SUNIONSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::sunionstore_range<Input>, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+// SORTED SET commands.
+
+inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmax(key, timeout.count());
+}
+
+template <typename Input>
+auto Redis::bzpopmax(Input first, Input last, long long timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    auto reply = command(cmd::bzpopmax_range<Input>, first, last, timeout);
+
+    return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
+}
+
+template <typename Input>
+inline auto Redis::bzpopmax(Input first,
+                            Input last,
+                            const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmax(first, last, timeout.count());
+}
+
+inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmin(key, timeout.count());
+}
+
+template <typename Input>
+auto Redis::bzpopmin(Input first, Input last, long long timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    auto reply = command(cmd::bzpopmin_range<Input>, first, last, timeout);
+
+    return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
+}
+
+template <typename Input>
+inline auto Redis::bzpopmin(Input first,
+                            Input last,
+                            const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmin(first, last, timeout.count());
+}
+
+template <typename Input>
+long long Redis::zadd(const StringView &key,
+                        Input first,
+                        Input last,
+                        UpdateType type,
+                        bool changed) {
+    if (first == last) {
+        throw Error("ZADD: no key specified");
+    }
+
+    auto reply = command(cmd::zadd_range<Input>, key, first, last, type, changed);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long Redis::zcount(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zcount<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+long long Redis::zinterstore(const StringView &destination,
+                                Input first,
+                                Input last,
+                                Aggregation type) {
+    if (first == last) {
+        throw Error("ZINTERSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::zinterstore_range<Input>,
+                            destination,
+                            first,
+                            last,
+                            type);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long Redis::zlexcount(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zlexcount<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+void Redis::zpopmax(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::zpopmax, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::zpopmin(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::zpopmin, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::zrange(const StringView &key, long long start, long long stop, Output output) {
+    auto reply = _score_command<Output>(cmd::zrange, key, start, stop);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) {
+    zrangebylex(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void Redis::zrangebylex(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output) {
+    auto reply = command(cmd::zrangebylex<Interval>, key, interval, opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+void Redis::zrangebyscore(const StringView &key,
+                            const Interval &interval,
+                            Output output) {
+    zrangebyscore(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void Redis::zrangebyscore(const StringView &key,
+                            const Interval &interval,
+                            const LimitOptions &opts,
+                            Output output) {
+    auto reply = _score_command<Output>(cmd::zrangebyscore<Interval>,
+                                        key,
+                                        interval,
+                                        opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long Redis::zrem(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("ZREM: no key specified");
+    }
+
+    auto reply = command(cmd::zrem_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long Redis::zremrangebylex(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zremrangebylex<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zremrangebyscore<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) {
+    auto reply = _score_command<Output>(cmd::zrevrange, key, start, stop);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+inline void Redis::zrevrangebylex(const StringView &key,
+                                    const Interval &interval,
+                                    Output output) {
+    zrevrangebylex(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void Redis::zrevrangebylex(const StringView &key,
+                            const Interval &interval,
+                            const LimitOptions &opts,
+                            Output output) {
+    auto reply = command(cmd::zrevrangebylex<Interval>, key, interval, opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) {
+    zrevrangebyscore(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void Redis::zrevrangebyscore(const StringView &key,
+                                const Interval &interval,
+                                const LimitOptions &opts,
+                                Output output) {
+    auto reply = _score_command<Output>(cmd::zrevrangebyscore<Interval>, key, interval, opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+long long Redis::zscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::zscan, key, cursor, pattern, count);
+
+    return reply::parse_scan_reply(*reply, output);
+}
+
+template <typename Output>
+inline long long Redis::zscan(const StringView &key,
+                                long long cursor,
+                                const StringView &pattern,
+                                Output output) {
+    return zscan(key, cursor, pattern, 10, output);
+}
+
+template <typename Output>
+inline long long Redis::zscan(const StringView &key,
+                                long long cursor,
+                                long long count,
+                                Output output) {
+    return zscan(key, cursor, "*", count, output);
+}
+
+template <typename Output>
+inline long long Redis::zscan(const StringView &key,
+                                long long cursor,
+                                Output output) {
+    return zscan(key, cursor, "*", 10, output);
+}
+
+template <typename Input>
+long long Redis::zunionstore(const StringView &destination,
+                                    Input first,
+                                    Input last,
+                                    Aggregation type) {
+    if (first == last) {
+        throw Error("ZUNIONSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::zunionstore_range<Input>,
+                            destination,
+                            first,
+                            last,
+                            type);
+
+    return reply::parse<long long>(*reply);
+}
+
+// HYPERLOGLOG commands.
+
+template <typename Input>
+bool Redis::pfadd(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("PFADD: no key specified");
+    }
+
+    auto reply = command(cmd::pfadd_range<Input>, key, first, last);
+
+    return reply::parse<bool>(*reply);
+}
+
+template <typename Input>
+long long Redis::pfcount(Input first, Input last) {
+    if (first == last) {
+        throw Error("PFCOUNT: no key specified");
+    }
+
+    auto reply = command(cmd::pfcount_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+void Redis::pfmerge(const StringView &destination,
+                    Input first,
+                    Input last) {
+    if (first == last) {
+        throw Error("PFMERGE: no key specified");
+    }
+
+    auto reply = command(cmd::pfmerge_range<Input>, destination, first, last);
+
+    reply::parse<void>(*reply);
+}
+
+// GEO commands.
+
+template <typename Input>
+inline long long Redis::geoadd(const StringView &key,
+                                Input first,
+                                Input last) {
+    if (first == last) {
+        throw Error("GEOADD: no key specified");
+    }
+
+    auto reply = command(cmd::geoadd_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void Redis::geohash(const StringView &key, Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("GEOHASH: no key specified");
+    }
+
+    auto reply = command(cmd::geohash_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::geopos(const StringView &key, Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("GEOPOS: no key specified");
+    }
+
+    auto reply = command(cmd::geopos_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::georadius(const StringView &key,
+                        const std::pair<double, double> &loc,
+                        double radius,
+                        GeoUnit unit,
+                        long long count,
+                        bool asc,
+                        Output output) {
+    auto reply = command(cmd::georadius,
+                            key,
+                            loc,
+                            radius,
+                            unit,
+                            count,
+                            asc,
+                            WithCoord<typename IterType<Output>::type>::value,
+                            WithDist<typename IterType<Output>::type>::value,
+                            WithHash<typename IterType<Output>::type>::value);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::georadiusbymember(const StringView &key,
+                                const StringView &member,
+                                double radius,
+                                GeoUnit unit,
+                                long long count,
+                                bool asc,
+                                Output output) {
+    auto reply = command(cmd::georadiusbymember,
+                            key,
+                            member,
+                            radius,
+                            unit,
+                            count,
+                            asc,
+                            WithCoord<typename IterType<Output>::type>::value,
+                            WithDist<typename IterType<Output>::type>::value,
+                            WithHash<typename IterType<Output>::type>::value);
+
+    reply::to_array(*reply, output);
+}
+
+// SCRIPTING commands.
+
+template <typename Result>
+Result Redis::eval(const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args) {
+    auto reply = command(cmd::eval, script, keys, args);
+
+    return reply::parse<Result>(*reply);
+}
+
+template <typename Output>
+void Redis::eval(const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args,
+                    Output output) {
+    auto reply = command(cmd::eval, script, keys, args);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Result>
+Result Redis::evalsha(const StringView &script,
+                        std::initializer_list<StringView> keys,
+                        std::initializer_list<StringView> args) {
+    auto reply = command(cmd::evalsha, script, keys, args);
+
+    return reply::parse<Result>(*reply);
+}
+
+template <typename Output>
+void Redis::evalsha(const StringView &script,
+                        std::initializer_list<StringView> keys,
+                        std::initializer_list<StringView> args,
+                        Output output) {
+    auto reply = command(cmd::evalsha, script, keys, args);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::script_exists(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("SCRIPT EXISTS: no key specified");
+    }
+
+    auto reply = command(cmd::script_exists_range<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+// Transaction commands.
+
+template <typename Input>
+void Redis::watch(Input first, Input last) {
+    auto reply = command(cmd::watch_range<Input>, first, last);
+
+    reply::parse<void>(*reply);
+}
+
+// Stream commands.
+
+template <typename Input>
+long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) {
+    auto reply = command(cmd::xack_range<Input>, key, group, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) {
+    auto reply = command(cmd::xadd_range<Input>, key, id, first, last);
+
+    return reply::parse<std::string>(*reply);
+}
+
+template <typename Input>
+std::string Redis::xadd(const StringView &key,
+                        const StringView &id,
+                        Input first,
+                        Input last,
+                        long long count,
+                        bool approx) {
+    auto reply = command(cmd::xadd_maxlen_range<Input>, key, id, first, last, count, approx);
+
+    return reply::parse<std::string>(*reply);
+}
+
+template <typename Output>
+void Redis::xclaim(const StringView &key,
+                    const StringView &group,
+                    const StringView &consumer,
+                    const std::chrono::milliseconds &min_idle_time,
+                    const StringView &id,
+                    Output output) {
+    auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void Redis::xclaim(const StringView &key,
+                    const StringView &group,
+                    const StringView &consumer,
+                    const std::chrono::milliseconds &min_idle_time,
+                    Input first,
+                    Input last,
+                    Output output) {
+    auto reply = command(cmd::xclaim_range<Input>,
+                            key,
+                            group,
+                            consumer,
+                            min_idle_time.count(),
+                            first,
+                            last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long Redis::xdel(const StringView &key, Input first, Input last) {
+    auto reply = command(cmd::xdel_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+auto Redis::xpending(const StringView &key, const StringView &group, Output output)
+    -> std::tuple<long long, OptionalString, OptionalString> {
+    auto reply = command(cmd::xpending, key, group);
+
+    return reply::parse_xpending_reply(*reply, output);
+}
+
+template <typename Output>
+void Redis::xpending(const StringView &key,
+                        const StringView &group,
+                        const StringView &start,
+                        const StringView &end,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::xpending_detail, key, group, start, end, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::xpending(const StringView &key,
+                        const StringView &group,
+                        const StringView &start,
+                        const StringView &end,
+                        long long count,
+                        const StringView &consumer,
+                        Output output) {
+    auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::xrange(const StringView &key,
+                    const StringView &start,
+                    const StringView &end,
+                    Output output) {
+    auto reply = command(cmd::xrange, key, start, end);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::xrange(const StringView &key,
+                    const StringView &start,
+                    const StringView &end,
+                    long long count,
+                    Output output) {
+    auto reply = command(cmd::xrange_count, key, start, end, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::xread(const StringView &key,
+                    const StringView &id,
+                    long long count,
+                    Output output) {
+    auto reply = command(cmd::xread, key, id, count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto Redis::xread(Input first, Input last, long long count, Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREAD: no key specified");
+    }
+
+    auto reply = command(cmd::xread_range<Input>, first, last, count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void Redis::xread(const StringView &key,
+                    const StringView &id,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    Output output) {
+    auto reply = command(cmd::xread_block, key, id, timeout.count(), count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto Redis::xread(Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREAD: no key specified");
+    }
+
+    auto reply = command(cmd::xread_block_range<Input>, first, last, timeout.count(), count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void Redis::xreadgroup(const StringView &group,
+                        const StringView &consumer,
+                        const StringView &key,
+                        const StringView &id,
+                        long long count,
+                        bool noack,
+                        Output output) {
+    auto reply = command(cmd::xreadgroup, group, consumer, key, id, count, noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto Redis::xreadgroup(const StringView &group,
+                        const StringView &consumer,
+                        Input first,
+                        Input last,
+                        long long count,
+                        bool noack,
+                        Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREADGROUP: no key specified");
+    }
+
+    auto reply = command(cmd::xreadgroup_range<Input>, group, consumer, first, last, count, noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void Redis::xreadgroup(const StringView &group,
+                        const StringView &consumer,
+                        const StringView &key,
+                        const StringView &id,
+                        const std::chrono::milliseconds &timeout,
+                        long long count,
+                        bool noack,
+                        Output output) {
+    auto reply = command(cmd::xreadgroup_block,
+                            group,
+                            consumer,
+                            key,
+                            id,
+                            timeout.count(),
+                            count,
+                            noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto Redis::xreadgroup(const StringView &group,
+                        const StringView &consumer,
+                        Input first,
+                        Input last,
+                        const std::chrono::milliseconds &timeout,
+                        long long count,
+                        bool noack,
+                        Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREADGROUP: no key specified");
+    }
+
+    auto reply = command(cmd::xreadgroup_block_range<Input>,
+                            group,
+                            consumer,
+                            first,
+                            last,
+                            timeout.count(),
+                            count,
+                            noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void Redis::xrevrange(const StringView &key,
+                        const StringView &end,
+                        const StringView &start,
+                        Output output) {
+    auto reply = command(cmd::xrevrange, key, end, start);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void Redis::xrevrange(const StringView &key,
+                        const StringView &end,
+                        const StringView &start,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::xrevrange_count, key, end, start, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Cmd, typename ...Args>
+ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) {
+    assert(!connection.broken());
+
+    cmd(connection, std::forward<Args>(args)...);
+
+    auto reply = connection.recv();
+
+    return reply;
+}
+
+template <typename Cmd, typename ...Args>
+inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) {
+    return command(cmd, std::forward<Args>(args)..., true);
+}
+
+template <typename Cmd, typename ...Args>
+inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) {
+    return command(cmd, std::forward<Args>(args)..., false);
+}
+
+template <typename Output, typename Cmd, typename ...Args>
+inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) {
+    return _score_command(typename IsKvPairIter<Output>::type(),
+                            cmd,
+                            std::forward<Args>(args)...);
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP

+ 1395 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h

@@ -0,0 +1,1395 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H
+#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H
+
+#include <string>
+#include <chrono>
+#include <initializer_list>
+#include <tuple>
+#include "shards_pool.h"
+#include "reply.h"
+#include "command_options.h"
+#include "utils.h"
+#include "subscriber.h"
+#include "pipeline.h"
+#include "transaction.h"
+#include "redis.h"
+
+namespace sw {
+
+namespace redis {
+
+template <typename Impl>
+class QueuedRedis;
+
+using Transaction = QueuedRedis<TransactionImpl>;
+
+using Pipeline = QueuedRedis<PipelineImpl>;
+
+class RedisCluster {
+public:
+    RedisCluster(const ConnectionOptions &connection_opts,
+                    const ConnectionPoolOptions &pool_opts = {}) :
+                        _pool(pool_opts, connection_opts) {}
+
+    // Construct RedisCluster with URI:
+    // "tcp://127.0.0.1" or "tcp://127.0.0.1:6379"
+    // Only need to specify one URI.
+    explicit RedisCluster(const std::string &uri);
+
+    RedisCluster(const RedisCluster &) = delete;
+    RedisCluster& operator=(const RedisCluster &) = delete;
+
+    RedisCluster(RedisCluster &&) = default;
+    RedisCluster& operator=(RedisCluster &&) = default;
+
+    Redis redis(const StringView &hash_tag);
+
+    Pipeline pipeline(const StringView &hash_tag);
+
+    Transaction transaction(const StringView &hash_tag, bool piped = false);
+
+    Subscriber subscriber();
+
+    template <typename Cmd, typename Key, typename ...Args>
+    auto command(Cmd cmd, Key &&key, Args &&...args)
+        -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type;
+
+    template <typename Key, typename ...Args>
+    auto command(const StringView &cmd_name, Key &&key, Args &&...args)
+        -> typename std::enable_if<(std::is_convertible<Key, StringView>::value
+                || std::is_arithmetic<typename std::decay<Key>::type>::value)
+                && !IsIter<typename LastType<Key, Args...>::type>::value, ReplyUPtr>::type;
+
+    template <typename Key, typename ...Args>
+    auto command(const StringView &cmd_name, Key &&key, Args &&...args)
+        -> typename std::enable_if<(std::is_convertible<Key, StringView>::value
+                || std::is_arithmetic<typename std::decay<Key>::type>::value)
+                && IsIter<typename LastType<Key, Args...>::type>::value, void>::type;
+
+    template <typename Result, typename Key, typename ...Args>
+    auto command(const StringView &cmd_name, Key &&key, Args &&...args)
+        -> typename std::enable_if<std::is_convertible<Key, StringView>::value
+                || std::is_arithmetic<typename std::decay<Key>::type>::value, Result>::type;
+
+    template <typename Input>
+    auto command(Input first, Input last)
+        -> typename std::enable_if<IsIter<Input>::value, ReplyUPtr>::type;
+
+    template <typename Result, typename Input>
+    auto command(Input first, Input last)
+        -> typename std::enable_if<IsIter<Input>::value, Result>::type;
+
+    template <typename Input, typename Output>
+    auto command(Input first, Input last, Output output)
+        -> typename std::enable_if<IsIter<Input>::value, void>::type;
+
+    // KEY commands.
+
+    long long del(const StringView &key);
+
+    template <typename Input>
+    long long del(Input first, Input last);
+
+    template <typename T>
+    long long del(std::initializer_list<T> il) {
+        return del(il.begin(), il.end());
+    }
+
+    OptionalString dump(const StringView &key);
+
+    long long exists(const StringView &key);
+
+    template <typename Input>
+    long long exists(Input first, Input last);
+
+    template <typename T>
+    long long exists(std::initializer_list<T> il) {
+        return exists(il.begin(), il.end());
+    }
+
+    bool expire(const StringView &key, long long timeout);
+
+    bool expire(const StringView &key, const std::chrono::seconds &timeout);
+
+    bool expireat(const StringView &key, long long timestamp);
+
+    bool expireat(const StringView &key,
+                    const std::chrono::time_point<std::chrono::system_clock,
+                                                    std::chrono::seconds> &tp);
+
+    bool persist(const StringView &key);
+
+    bool pexpire(const StringView &key, long long timeout);
+
+    bool pexpire(const StringView &key, const std::chrono::milliseconds &timeout);
+
+    bool pexpireat(const StringView &key, long long timestamp);
+
+    bool pexpireat(const StringView &key,
+                    const std::chrono::time_point<std::chrono::system_clock,
+                                                    std::chrono::milliseconds> &tp);
+
+    long long pttl(const StringView &key);
+
+    void rename(const StringView &key, const StringView &newkey);
+
+    bool renamenx(const StringView &key, const StringView &newkey);
+
+    void restore(const StringView &key,
+                    const StringView &val,
+                    long long ttl,
+                    bool replace = false);
+
+    void restore(const StringView &key,
+                    const StringView &val,
+                    const std::chrono::milliseconds &ttl = std::chrono::milliseconds{0},
+                    bool replace = false);
+
+    // TODO: sort
+
+    long long touch(const StringView &key);
+
+    template <typename Input>
+    long long touch(Input first, Input last);
+
+    template <typename T>
+    long long touch(std::initializer_list<T> il) {
+        return touch(il.begin(), il.end());
+    }
+
+    long long ttl(const StringView &key);
+
+    std::string type(const StringView &key);
+
+    long long unlink(const StringView &key);
+
+    template <typename Input>
+    long long unlink(Input first, Input last);
+
+    template <typename T>
+    long long unlink(std::initializer_list<T> il) {
+        return unlink(il.begin(), il.end());
+    }
+
+    // STRING commands.
+
+    long long append(const StringView &key, const StringView &str);
+
+    long long bitcount(const StringView &key, long long start = 0, long long end = -1);
+
+    long long bitop(BitOp op, const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long bitop(BitOp op, const StringView &destination, Input first, Input last);
+
+    template <typename T>
+    long long bitop(BitOp op, const StringView &destination, std::initializer_list<T> il) {
+        return bitop(op, destination, il.begin(), il.end());
+    }
+
+    long long bitpos(const StringView &key,
+                        long long bit,
+                        long long start = 0,
+                        long long end = -1);
+
+    long long decr(const StringView &key);
+
+    long long decrby(const StringView &key, long long decrement);
+
+    OptionalString get(const StringView &key);
+
+    long long getbit(const StringView &key, long long offset);
+
+    std::string getrange(const StringView &key, long long start, long long end);
+
+    OptionalString getset(const StringView &key, const StringView &val);
+
+    long long incr(const StringView &key);
+
+    long long incrby(const StringView &key, long long increment);
+
+    double incrbyfloat(const StringView &key, double increment);
+
+    template <typename Input, typename Output>
+    void mget(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void mget(std::initializer_list<T> il, Output output) {
+        mget(il.begin(), il.end(), output);
+    }
+
+    template <typename Input>
+    void mset(Input first, Input last);
+
+    template <typename T>
+    void mset(std::initializer_list<T> il) {
+        mset(il.begin(), il.end());
+    }
+
+    template <typename Input>
+    bool msetnx(Input first, Input last);
+
+    template <typename T>
+    bool msetnx(std::initializer_list<T> il) {
+        return msetnx(il.begin(), il.end());
+    }
+
+    void psetex(const StringView &key,
+                long long ttl,
+                const StringView &val);
+
+    void psetex(const StringView &key,
+                const std::chrono::milliseconds &ttl,
+                const StringView &val);
+
+    bool set(const StringView &key,
+                const StringView &val,
+                const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
+                UpdateType type = UpdateType::ALWAYS);
+
+    void setex(const StringView &key,
+                long long ttl,
+                const StringView &val);
+
+    void setex(const StringView &key,
+                const std::chrono::seconds &ttl,
+                const StringView &val);
+
+    bool setnx(const StringView &key, const StringView &val);
+
+    long long setrange(const StringView &key, long long offset, const StringView &val);
+
+    long long strlen(const StringView &key);
+
+    // LIST commands.
+
+    OptionalStringPair blpop(const StringView &key, long long timeout);
+
+    OptionalStringPair blpop(const StringView &key,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename Input>
+    OptionalStringPair blpop(Input first, Input last, long long timeout);
+
+    template <typename T>
+    OptionalStringPair blpop(std::initializer_list<T> il, long long timeout) {
+        return blpop(il.begin(), il.end(), timeout);
+    }
+
+    template <typename Input>
+    OptionalStringPair blpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename T>
+    OptionalStringPair blpop(std::initializer_list<T> il,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return blpop(il.begin(), il.end(), timeout);
+    }
+
+    OptionalStringPair brpop(const StringView &key, long long timeout);
+
+    OptionalStringPair brpop(const StringView &key,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename Input>
+    OptionalStringPair brpop(Input first, Input last, long long timeout);
+
+    template <typename T>
+    OptionalStringPair brpop(std::initializer_list<T> il, long long timeout) {
+        return brpop(il.begin(), il.end(), timeout);
+    }
+
+    template <typename Input>
+    OptionalStringPair brpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    template <typename T>
+    OptionalStringPair brpop(std::initializer_list<T> il,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0}) {
+        return brpop(il.begin(), il.end(), timeout);
+    }
+
+    OptionalString brpoplpush(const StringView &source,
+                                const StringView &destination,
+                                long long timeout);
+
+    OptionalString brpoplpush(const StringView &source,
+                                const StringView &destination,
+                                const std::chrono::seconds &timeout = std::chrono::seconds{0});
+
+    OptionalString lindex(const StringView &key, long long index);
+
+    long long linsert(const StringView &key,
+                        InsertPosition position,
+                        const StringView &pivot,
+                        const StringView &val);
+
+    long long llen(const StringView &key);
+
+    OptionalString lpop(const StringView &key);
+
+    long long lpush(const StringView &key, const StringView &val);
+
+    template <typename Input>
+    long long lpush(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long lpush(const StringView &key, std::initializer_list<T> il) {
+        return lpush(key, il.begin(), il.end());
+    }
+
+    long long lpushx(const StringView &key, const StringView &val);
+
+    template <typename Output>
+    void lrange(const StringView &key, long long start, long long stop, Output output);
+
+    long long lrem(const StringView &key, long long count, const StringView &val);
+
+    void lset(const StringView &key, long long index, const StringView &val);
+
+    void ltrim(const StringView &key, long long start, long long stop);
+
+    OptionalString rpop(const StringView &key);
+
+    OptionalString rpoplpush(const StringView &source, const StringView &destination);
+
+    long long rpush(const StringView &key, const StringView &val);
+
+    template <typename Input>
+    long long rpush(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long rpush(const StringView &key, std::initializer_list<T> il) {
+        return rpush(key, il.begin(), il.end());
+    }
+
+    long long rpushx(const StringView &key, const StringView &val);
+
+    // HASH commands.
+
+    long long hdel(const StringView &key, const StringView &field);
+
+    template <typename Input>
+    long long hdel(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long hdel(const StringView &key, std::initializer_list<T> il) {
+        return hdel(key, il.begin(), il.end());
+    }
+
+    bool hexists(const StringView &key, const StringView &field);
+
+    OptionalString hget(const StringView &key, const StringView &field);
+
+    template <typename Output>
+    void hgetall(const StringView &key, Output output);
+
+    long long hincrby(const StringView &key, const StringView &field, long long increment);
+
+    double hincrbyfloat(const StringView &key, const StringView &field, double increment);
+
+    template <typename Output>
+    void hkeys(const StringView &key, Output output);
+
+    long long hlen(const StringView &key);
+
+    template <typename Input, typename Output>
+    void hmget(const StringView &key, Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void hmget(const StringView &key, std::initializer_list<T> il, Output output) {
+        hmget(key, il.begin(), il.end(), output);
+    }
+
+    template <typename Input>
+    void hmset(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    void hmset(const StringView &key, std::initializer_list<T> il) {
+        hmset(key, il.begin(), il.end());
+    }
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    Output output);
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long hscan(const StringView &key,
+                    long long cursor,
+                    Output output);
+
+    bool hset(const StringView &key, const StringView &field, const StringView &val);
+
+    bool hset(const StringView &key, const std::pair<StringView, StringView> &item);
+
+    bool hsetnx(const StringView &key, const StringView &field, const StringView &val);
+
+    bool hsetnx(const StringView &key, const std::pair<StringView, StringView> &item);
+
+    long long hstrlen(const StringView &key, const StringView &field);
+
+    template <typename Output>
+    void hvals(const StringView &key, Output output);
+
+    // SET commands.
+
+    long long sadd(const StringView &key, const StringView &member);
+
+    template <typename Input>
+    long long sadd(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long sadd(const StringView &key, std::initializer_list<T> il) {
+        return sadd(key, il.begin(), il.end());
+    }
+
+    long long scard(const StringView &key);
+
+    template <typename Input, typename Output>
+    void sdiff(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void sdiff(std::initializer_list<T> il, Output output) {
+        sdiff(il.begin(), il.end(), output);
+    }
+
+    long long sdiffstore(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long sdiffstore(const StringView &destination,
+                            Input first,
+                            Input last);
+
+    template <typename T>
+    long long sdiffstore(const StringView &destination,
+                            std::initializer_list<T> il) {
+        return sdiffstore(destination, il.begin(), il.end());
+    }
+
+    template <typename Input, typename Output>
+    void sinter(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void sinter(std::initializer_list<T> il, Output output) {
+        sinter(il.begin(), il.end(), output);
+    }
+
+    long long sinterstore(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long sinterstore(const StringView &destination,
+                            Input first,
+                            Input last);
+
+    template <typename T>
+    long long sinterstore(const StringView &destination,
+                            std::initializer_list<T> il) {
+        return sinterstore(destination, il.begin(), il.end());
+    }
+
+    bool sismember(const StringView &key, const StringView &member);
+
+    template <typename Output>
+    void smembers(const StringView &key, Output output);
+
+    bool smove(const StringView &source,
+                const StringView &destination,
+                const StringView &member);
+
+    OptionalString spop(const StringView &key);
+
+    template <typename Output>
+    void spop(const StringView &key, long long count, Output output);
+
+    OptionalString srandmember(const StringView &key);
+
+    template <typename Output>
+    void srandmember(const StringView &key, long long count, Output output);
+
+    long long srem(const StringView &key, const StringView &member);
+
+    template <typename Input>
+    long long srem(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long srem(const StringView &key, std::initializer_list<T> il) {
+        return srem(key, il.begin(), il.end());
+    }
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    Output output);
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long sscan(const StringView &key,
+                    long long cursor,
+                    Output output);
+
+    template <typename Input, typename Output>
+    void sunion(Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void sunion(std::initializer_list<T> il, Output output) {
+        sunion(il.begin(), il.end(), output);
+    }
+
+    long long sunionstore(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    long long sunionstore(const StringView &destination, Input first, Input last);
+
+    template <typename T>
+    long long sunionstore(const StringView &destination, std::initializer_list<T> il) {
+        return sunionstore(destination, il.begin(), il.end());
+    }
+
+    // SORTED SET commands.
+
+    auto bzpopmax(const StringView &key, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    auto bzpopmax(const StringView &key,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmax(Input first, Input last, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmax(Input first,
+                    Input last,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename T>
+    auto bzpopmax(std::initializer_list<T> il, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmax(il.begin(), il.end(), timeout);
+    }
+
+    template <typename T>
+    auto bzpopmax(std::initializer_list<T> il,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmax(il.begin(), il.end(), timeout);
+    }
+
+    auto bzpopmin(const StringView &key, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    auto bzpopmin(const StringView &key,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmin(Input first, Input last, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename Input>
+    auto bzpopmin(Input first,
+                    Input last,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>>;
+
+    template <typename T>
+    auto bzpopmin(std::initializer_list<T> il, long long timeout)
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmin(il.begin(), il.end(), timeout);
+    }
+
+    template <typename T>
+    auto bzpopmin(std::initializer_list<T> il,
+                    const std::chrono::seconds &timeout = std::chrono::seconds{0})
+        -> Optional<std::tuple<std::string, std::string, double>> {
+        return bzpopmin(il.begin(), il.end(), timeout);
+    }
+
+    // We don't support the INCR option, since you can always use ZINCRBY instead.
+    long long zadd(const StringView &key,
+                    const StringView &member,
+                    double score,
+                    UpdateType type = UpdateType::ALWAYS,
+                    bool changed = false);
+
+    template <typename Input>
+    long long zadd(const StringView &key,
+                    Input first,
+                    Input last,
+                    UpdateType type = UpdateType::ALWAYS,
+                    bool changed = false);
+
+    template <typename T>
+    long long zadd(const StringView &key,
+                    std::initializer_list<T> il,
+                    UpdateType type = UpdateType::ALWAYS,
+                    bool changed = false) {
+        return zadd(key, il.begin(), il.end(), type, changed);
+    }
+
+    long long zcard(const StringView &key);
+
+    template <typename Interval>
+    long long zcount(const StringView &key, const Interval &interval);
+
+    double zincrby(const StringView &key, double increment, const StringView &member);
+
+    long long zinterstore(const StringView &destination, const StringView &key, double weight);
+
+    template <typename Input>
+    long long zinterstore(const StringView &destination,
+                            Input first,
+                            Input last,
+                            Aggregation type = Aggregation::SUM);
+
+    template <typename T>
+    long long zinterstore(const StringView &destination,
+                            std::initializer_list<T> il,
+                            Aggregation type = Aggregation::SUM) {
+        return zinterstore(destination, il.begin(), il.end(), type);
+    }
+
+    template <typename Interval>
+    long long zlexcount(const StringView &key, const Interval &interval);
+
+    Optional<std::pair<std::string, double>> zpopmax(const StringView &key);
+
+    template <typename Output>
+    void zpopmax(const StringView &key, long long count, Output output);
+
+    Optional<std::pair<std::string, double>> zpopmin(const StringView &key);
+
+    template <typename Output>
+    void zpopmin(const StringView &key, long long count, Output output);
+
+    // If *output* is an iterator of a container of string,
+    // we send *ZRANGE key start stop* command.
+    // If it's an iterator of a container of pair<string, double>,
+    // we send *ZRANGE key start stop WITHSCORES* command.
+    //
+    // The following code sends *ZRANGE* without the *WITHSCORES* option:
+    //
+    // vector<string> result;
+    // redis.zrange("key", 0, -1, back_inserter(result));
+    //
+    // On the other hand, the following code sends command with *WITHSCORES* option:
+    //
+    // unordered_map<string, double> with_score;
+    // redis.zrange("key", 0, -1, inserter(with_score, with_score.end()));
+    //
+    // This also applies to other commands with the *WITHSCORES* option,
+    // e.g. *ZRANGEBYSCORE*, *ZREVRANGE*, *ZREVRANGEBYSCORE*.
+    template <typename Output>
+    void zrange(const StringView &key, long long start, long long stop, Output output);
+
+    template <typename Interval, typename Output>
+    void zrangebylex(const StringView &key, const Interval &interval, Output output);
+
+    template <typename Interval, typename Output>
+    void zrangebylex(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrangebyscore(const StringView &key, const Interval &interval, Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrangebyscore(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output);
+
+    OptionalLongLong zrank(const StringView &key, const StringView &member);
+
+    long long zrem(const StringView &key, const StringView &member);
+
+    template <typename Input>
+    long long zrem(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long zrem(const StringView &key, std::initializer_list<T> il) {
+        return zrem(key, il.begin(), il.end());
+    }
+
+    template <typename Interval>
+    long long zremrangebylex(const StringView &key, const Interval &interval);
+
+    long long zremrangebyrank(const StringView &key, long long start, long long stop);
+
+    template <typename Interval>
+    long long zremrangebyscore(const StringView &key, const Interval &interval);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Output>
+    void zrevrange(const StringView &key, long long start, long long stop, Output output);
+
+    template <typename Interval, typename Output>
+    void zrevrangebylex(const StringView &key, const Interval &interval, Output output);
+
+    template <typename Interval, typename Output>
+    void zrevrangebylex(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrevrangebyscore(const StringView &key, const Interval &interval, Output output);
+
+    // See *zrange* comment on how to send command with *WITHSCORES* option.
+    template <typename Interval, typename Output>
+    void zrevrangebyscore(const StringView &key,
+                            const Interval &interval,
+                            const LimitOptions &opts,
+                            Output output);
+
+    OptionalLongLong zrevrank(const StringView &key, const StringView &member);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    const StringView &pattern,
+                    Output output);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    long long zscan(const StringView &key,
+                    long long cursor,
+                    Output output);
+
+    OptionalDouble zscore(const StringView &key, const StringView &member);
+
+    long long zunionstore(const StringView &destination, const StringView &key, double weight);
+
+    template <typename Input>
+    long long zunionstore(const StringView &destination,
+                            Input first,
+                            Input last,
+                            Aggregation type = Aggregation::SUM);
+
+    template <typename T>
+    long long zunionstore(const StringView &destination,
+                            std::initializer_list<T> il,
+                            Aggregation type = Aggregation::SUM) {
+        return zunionstore(destination, il.begin(), il.end(), type);
+    }
+
+    // HYPERLOGLOG commands.
+
+    bool pfadd(const StringView &key, const StringView &element);
+
+    template <typename Input>
+    bool pfadd(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    bool pfadd(const StringView &key, std::initializer_list<T> il) {
+        return pfadd(key, il.begin(), il.end());
+    }
+
+    long long pfcount(const StringView &key);
+
+    template <typename Input>
+    long long pfcount(Input first, Input last);
+
+    template <typename T>
+    long long pfcount(std::initializer_list<T> il) {
+        return pfcount(il.begin(), il.end());
+    }
+
+    void pfmerge(const StringView &destination, const StringView &key);
+
+    template <typename Input>
+    void pfmerge(const StringView &destination, Input first, Input last);
+
+    template <typename T>
+    void pfmerge(const StringView &destination, std::initializer_list<T> il) {
+        pfmerge(destination, il.begin(), il.end());
+    }
+
+    // GEO commands.
+
+    long long geoadd(const StringView &key,
+                        const std::tuple<StringView, double, double> &member);
+
+    template <typename Input>
+    long long geoadd(const StringView &key,
+                        Input first,
+                        Input last);
+
+    template <typename T>
+    long long geoadd(const StringView &key,
+                        std::initializer_list<T> il) {
+        return geoadd(key, il.begin(), il.end());
+    }
+
+    OptionalDouble geodist(const StringView &key,
+                            const StringView &member1,
+                            const StringView &member2,
+                            GeoUnit unit = GeoUnit::M);
+
+    template <typename Input, typename Output>
+    void geohash(const StringView &key, Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void geohash(const StringView &key, std::initializer_list<T> il, Output output) {
+        geohash(key, il.begin(), il.end(), output);
+    }
+
+    template <typename Input, typename Output>
+    void geopos(const StringView &key, Input first, Input last, Output output);
+
+    template <typename T, typename Output>
+    void geopos(const StringView &key, std::initializer_list<T> il, Output output) {
+        geopos(key, il.begin(), il.end(), output);
+    }
+
+    // TODO:
+    // 1. since we have different overloads for georadius and georadius-store,
+    //    we might use the GEORADIUS_RO command in the future.
+    // 2. there're too many parameters for this method, we might refactor it.
+    OptionalLongLong georadius(const StringView &key,
+                                const std::pair<double, double> &loc,
+                                double radius,
+                                GeoUnit unit,
+                                const StringView &destination,
+                                bool store_dist,
+                                long long count);
+
+    // If *output* is an iterator of a container of string, we send *GEORADIUS* command
+    // without any options and only get the members in the specified geo range.
+    // If *output* is an iterator of a container of a tuple, the type of the tuple decides
+    // options we send with the *GEORADIUS* command. If the tuple has an element of type
+    // double, we send the *WITHDIST* option. If it has an element of type string, we send
+    // the *WITHHASH* option. If it has an element of type pair<double, double>, we send
+    // the *WITHCOORD* option. For example:
+    //
+    // The following code only gets the members in range, i.e. without any option.
+    //
+    // vector<string> members;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(members))
+    //
+    // The following code sends the command with *WITHDIST* option.
+    //
+    // vector<tuple<string, double>> with_dist;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(with_dist))
+    //
+    // The following code sends the command with *WITHDIST* and *WITHHASH* options.
+    //
+    // vector<tuple<string, double, string>> with_dist_hash;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(with_dist_hash))
+    //
+    // The following code sends the command with *WITHDIST*, *WITHCOORD* and *WITHHASH* options.
+    //
+    // vector<tuple<string, double, pair<double, double>, string>> with_dist_coord_hash;
+    // redis.georadius("key", make_pair(10.1, 10.2), 10, GeoUnit::KM, 10, true,
+    //                  back_inserter(with_dist_coord_hash))
+    //
+    // This also applies to *GEORADIUSBYMEMBER*.
+    template <typename Output>
+    void georadius(const StringView &key,
+                    const std::pair<double, double> &loc,
+                    double radius,
+                    GeoUnit unit,
+                    long long count,
+                    bool asc,
+                    Output output);
+
+    OptionalLongLong georadiusbymember(const StringView &key,
+                                        const StringView &member,
+                                        double radius,
+                                        GeoUnit unit,
+                                        const StringView &destination,
+                                        bool store_dist,
+                                        long long count);
+
+    // See comments on *GEORADIUS*.
+    template <typename Output>
+    void georadiusbymember(const StringView &key,
+                            const StringView &member,
+                            double radius,
+                            GeoUnit unit,
+                            long long count,
+                            bool asc,
+                            Output output);
+
+    // SCRIPTING commands.
+
+    template <typename Result>
+    Result eval(const StringView &script,
+                std::initializer_list<StringView> keys,
+                std::initializer_list<StringView> args);
+
+    template <typename Output>
+    void eval(const StringView &script,
+                std::initializer_list<StringView> keys,
+                std::initializer_list<StringView> args,
+                Output output);
+
+    template <typename Result>
+    Result evalsha(const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args);
+
+    template <typename Output>
+    void evalsha(const StringView &script,
+                    std::initializer_list<StringView> keys,
+                    std::initializer_list<StringView> args,
+                    Output output);
+
+    // PUBSUB commands.
+
+    long long publish(const StringView &channel, const StringView &message);
+
+    // Stream commands.
+
+    long long xack(const StringView &key, const StringView &group, const StringView &id);
+
+    template <typename Input>
+    long long xack(const StringView &key, const StringView &group, Input first, Input last);
+
+    template <typename T>
+    long long xack(const StringView &key, const StringView &group, std::initializer_list<T> il) {
+        return xack(key, group, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    std::string xadd(const StringView &key, const StringView &id, Input first, Input last);
+
+    template <typename T>
+    std::string xadd(const StringView &key, const StringView &id, std::initializer_list<T> il) {
+        return xadd(key, id, il.begin(), il.end());
+    }
+
+    template <typename Input>
+    std::string xadd(const StringView &key,
+                        const StringView &id,
+                        Input first,
+                        Input last,
+                        long long count,
+                        bool approx = true);
+
+    template <typename T>
+    std::string xadd(const StringView &key,
+                        const StringView &id,
+                        std::initializer_list<T> il,
+                        long long count,
+                        bool approx = true) {
+        return xadd(key, id, il.begin(), il.end(), count, approx);
+    }
+
+    template <typename Output>
+    void xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                const StringView &id,
+                Output output);
+
+    template <typename Input, typename Output>
+    void xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                Input first,
+                Input last,
+                Output output);
+
+    template <typename T, typename Output>
+    void xclaim(const StringView &key,
+                const StringView &group,
+                const StringView &consumer,
+                const std::chrono::milliseconds &min_idle_time,
+                std::initializer_list<T> il,
+                Output output) {
+        xclaim(key, group, consumer, min_idle_time, il.begin(), il.end(), output);
+    }
+
+    long long xdel(const StringView &key, const StringView &id);
+
+    template <typename Input>
+    long long xdel(const StringView &key, Input first, Input last);
+
+    template <typename T>
+    long long xdel(const StringView &key, std::initializer_list<T> il) {
+        return xdel(key, il.begin(), il.end());
+    }
+
+    void xgroup_create(const StringView &key,
+                        const StringView &group,
+                        const StringView &id,
+                        bool mkstream = false);
+
+    void xgroup_setid(const StringView &key, const StringView &group, const StringView &id);
+
+    long long xgroup_destroy(const StringView &key, const StringView &group);
+
+    long long xgroup_delconsumer(const StringView &key,
+                                    const StringView &group,
+                                    const StringView &consumer);
+
+    long long xlen(const StringView &key);
+
+    template <typename Output>
+    auto xpending(const StringView &key, const StringView &group, Output output)
+        -> std::tuple<long long, OptionalString, OptionalString>;
+
+    template <typename Output>
+    void xpending(const StringView &key,
+                    const StringView &group,
+                    const StringView &start,
+                    const StringView &end,
+                    long long count,
+                    Output output);
+
+    template <typename Output>
+    void xpending(const StringView &key,
+                    const StringView &group,
+                    const StringView &start,
+                    const StringView &end,
+                    long long count,
+                    const StringView &consumer,
+                    Output output);
+
+    template <typename Output>
+    void xrange(const StringView &key,
+                const StringView &start,
+                const StringView &end,
+                Output output);
+
+    template <typename Output>
+    void xrange(const StringView &key,
+                const StringView &start,
+                const StringView &end,
+                long long count,
+                Output output);
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                long long count,
+                Output output);
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                Output output) {
+        xread(key, id, 0, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xread(Input first, Input last, long long count, Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xread(Input first, Input last, Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xread(first, last, 0, output);
+    }
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                const std::chrono::milliseconds &timeout,
+                long long count,
+                Output output);
+
+    template <typename Output>
+    void xread(const StringView &key,
+                const StringView &id,
+                const std::chrono::milliseconds &timeout,
+                Output output) {
+        xread(key, id, timeout, 0, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xread(Input first,
+                Input last,
+                const std::chrono::milliseconds &timeout,
+                long long count,
+                Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xread(Input first,
+                Input last,
+                const std::chrono::milliseconds &timeout,
+                Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xread(first, last, timeout, 0, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    long long count,
+                    bool noack,
+                    Output output);
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    long long count,
+                    Output output) {
+        xreadgroup(group, consumer, key, id, count, false, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    Output output) {
+        xreadgroup(group, consumer, key, id, 0, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    long long count,
+                    bool noack,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    long long count,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first ,last, count, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first ,last, 0, false, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    bool noack,
+                    Output output);
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    Output output) {
+        return xreadgroup(group, consumer, key, id, timeout, count, false, output);
+    }
+
+    template <typename Output>
+    void xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    const StringView &key,
+                    const StringView &id,
+                    const std::chrono::milliseconds &timeout,
+                    Output output) {
+        return xreadgroup(group, consumer, key, id, timeout, 0, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    bool noack,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type;
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    long long count,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first, last, timeout, count, false, output);
+    }
+
+    template <typename Input, typename Output>
+    auto xreadgroup(const StringView &group,
+                    const StringView &consumer,
+                    Input first,
+                    Input last,
+                    const std::chrono::milliseconds &timeout,
+                    Output output)
+        -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+        xreadgroup(group, consumer, first, last, timeout, 0, false, output);
+    }
+
+    template <typename Output>
+    void xrevrange(const StringView &key,
+                    const StringView &end,
+                    const StringView &start,
+                    Output output);
+
+    template <typename Output>
+    void xrevrange(const StringView &key,
+                    const StringView &end,
+                    const StringView &start,
+                    long long count,
+                    Output output);
+
+    long long xtrim(const StringView &key, long long count, bool approx = true);
+
+private:
+    class Command {
+    public:
+        explicit Command(const StringView &cmd_name) : _cmd_name(cmd_name) {}
+
+        template <typename ...Args>
+        void operator()(Connection &connection, Args &&...args) const {
+            CmdArgs cmd_args;
+            cmd_args.append(_cmd_name, std::forward<Args>(args)...);
+            connection.send(cmd_args);
+        }
+
+    private:
+        StringView _cmd_name;
+    };
+
+    template <typename Cmd, typename Key, typename ...Args>
+    auto _generic_command(Cmd cmd, Key &&key, Args &&...args)
+        -> typename std::enable_if<std::is_convertible<Key, StringView>::value,
+                                    ReplyUPtr>::type;
+
+    template <typename Cmd, typename Key, typename ...Args>
+    auto _generic_command(Cmd cmd, Key &&key, Args &&...args)
+        -> typename std::enable_if<std::is_arithmetic<typename std::decay<Key>::type>::value,
+                                    ReplyUPtr>::type;
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _command(Cmd cmd, Connection &connection, Args &&...args);
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _command(Cmd cmd, const StringView &key, Args &&...args);
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _command(Cmd cmd, std::true_type, const StringView &key, Args &&...args);
+
+    template <typename Cmd, typename Input, typename ...Args>
+    ReplyUPtr _command(Cmd cmd, std::false_type, Input &&first, Args &&...args);
+
+    template <std::size_t ...Is, typename ...Args>
+    ReplyUPtr _command(const StringView &cmd_name, const IndexSequence<Is...> &, Args &&...args) {
+        return command(cmd_name, NthValue<Is>(std::forward<Args>(args)...)...);
+    }
+
+    template <typename Cmd, typename Input, typename ...Args>
+    ReplyUPtr _range_command(Cmd cmd, std::true_type, Input input, Args &&...args);
+
+    template <typename Cmd, typename Input, typename ...Args>
+    ReplyUPtr _range_command(Cmd cmd, std::false_type, Input input, Args &&...args);
+
+    void _asking(Connection &connection);
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _score_command(std::true_type, Cmd cmd, Args &&... args);
+
+    template <typename Cmd, typename ...Args>
+    ReplyUPtr _score_command(std::false_type, Cmd cmd, Args &&... args);
+
+    template <typename Output, typename Cmd, typename ...Args>
+    ReplyUPtr _score_command(Cmd cmd, Args &&... args);
+
+    ShardsPool _pool;
+};
+
+}
+
+}
+
+#include "redis_cluster.hpp"
+
+#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_H

+ 1415 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp

@@ -0,0 +1,1415 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP
+#define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP
+
+#include <utility>
+#include "command.h"
+#include "reply.h"
+#include "utils.h"
+#include "errors.h"
+#include "shards_pool.h"
+
+namespace sw {
+
+namespace redis {
+
+template <typename Cmd, typename Key, typename ...Args>
+auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args)
+    -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type {
+    return _command(cmd,
+                    std::is_convertible<typename std::decay<Key>::type, StringView>(),
+                    std::forward<Key>(key),
+                    std::forward<Args>(args)...);
+}
+
+template <typename Key, typename ...Args>
+auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args)
+    -> typename std::enable_if<(std::is_convertible<Key, StringView>::value
+        || std::is_arithmetic<typename std::decay<Key>::type>::value)
+        && !IsIter<typename LastType<Key, Args...>::type>::value, ReplyUPtr>::type {
+    auto cmd = Command(cmd_name);
+
+    return _generic_command(cmd, std::forward<Key>(key), std::forward<Args>(args)...);
+}
+
+template <typename Result, typename Key, typename ...Args>
+auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args)
+    -> typename std::enable_if<std::is_convertible<Key, StringView>::value
+            || std::is_arithmetic<typename std::decay<Key>::type>::value, Result>::type {
+    auto r = command(cmd_name, std::forward<Key>(key), std::forward<Args>(args)...);
+
+    assert(r);
+
+    return reply::parse<Result>(*r);
+}
+
+template <typename Key, typename ...Args>
+auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args)
+    -> typename std::enable_if<(std::is_convertible<Key, StringView>::value
+            || std::is_arithmetic<typename std::decay<Key>::type>::value)
+            && IsIter<typename LastType<Key, Args...>::type>::value, void>::type {
+    auto r = _command(cmd_name,
+                        MakeIndexSequence<sizeof...(Args)>(),
+                        std::forward<Key>(key),
+                        std::forward<Args>(args)...);
+
+    assert(r);
+
+    reply::to_array(*r, LastValue(std::forward<Args>(args)...));
+}
+
+template <typename Input>
+auto RedisCluster::command(Input first, Input last)
+    -> typename std::enable_if<IsIter<Input>::value, ReplyUPtr>::type {
+    if (first == last || std::next(first) == last) {
+        throw Error("command: invalid range");
+    }
+
+    const auto &key = *first;
+    ++first;
+
+    auto cmd = [&key](Connection &connection, Input first, Input last) {
+                        CmdArgs cmd_args;
+                        cmd_args.append(key);
+                        while (first != last) {
+                            cmd_args.append(*first);
+                            ++first;
+                        }
+                        connection.send(cmd_args);
+    };
+
+    return command(cmd, first, last);
+}
+
+template <typename Result, typename Input>
+auto RedisCluster::command(Input first, Input last)
+    -> typename std::enable_if<IsIter<Input>::value, Result>::type {
+    auto r = command(first, last);
+
+    assert(r);
+
+    return reply::parse<Result>(*r);
+}
+
+template <typename Input, typename Output>
+auto RedisCluster::command(Input first, Input last, Output output)
+    -> typename std::enable_if<IsIter<Input>::value, void>::type {
+    auto r = command(first, last);
+
+    assert(r);
+
+    reply::to_array(*r, output);
+}
+
+// KEY commands.
+
+template <typename Input>
+long long RedisCluster::del(Input first, Input last) {
+    if (first == last) {
+        throw Error("DEL: no key specified");
+    }
+
+    auto reply = command(cmd::del_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+long long RedisCluster::exists(Input first, Input last) {
+    if (first == last) {
+        throw Error("EXISTS: no key specified");
+    }
+
+    auto reply = command(cmd::exists_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) {
+    return expire(key, timeout.count());
+}
+
+inline bool RedisCluster::expireat(const StringView &key,
+                                    const std::chrono::time_point<std::chrono::system_clock,
+                                                                    std::chrono::seconds> &tp) {
+    return expireat(key, tp.time_since_epoch().count());
+}
+
+inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) {
+    return pexpire(key, timeout.count());
+}
+
+inline bool RedisCluster::pexpireat(const StringView &key,
+                                const std::chrono::time_point<std::chrono::system_clock,
+                                                                std::chrono::milliseconds> &tp) {
+    return pexpireat(key, tp.time_since_epoch().count());
+}
+
+inline void RedisCluster::restore(const StringView &key,
+                            const StringView &val,
+                            const std::chrono::milliseconds &ttl,
+                            bool replace) {
+    return restore(key, val, ttl.count(), replace);
+}
+
+template <typename Input>
+long long RedisCluster::touch(Input first, Input last) {
+    if (first == last) {
+        throw Error("TOUCH: no key specified");
+    }
+
+    auto reply = command(cmd::touch_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+long long RedisCluster::unlink(Input first, Input last) {
+    if (first == last) {
+        throw Error("UNLINK: no key specified");
+    }
+
+    auto reply = command(cmd::unlink_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+// STRING commands.
+
+template <typename Input>
+long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) {
+    if (first == last) {
+        throw Error("BITOP: no key specified");
+    }
+
+    auto reply = _command(cmd::bitop_range<Input>, destination, op, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::mget(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("MGET: no key specified");
+    }
+
+    auto reply = command(cmd::mget<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+void RedisCluster::mset(Input first, Input last) {
+    if (first == last) {
+        throw Error("MSET: no key specified");
+    }
+
+    auto reply = command(cmd::mset<Input>, first, last);
+
+    reply::parse<void>(*reply);
+}
+
+template <typename Input>
+bool RedisCluster::msetnx(Input first, Input last) {
+    if (first == last) {
+        throw Error("MSETNX: no key specified");
+    }
+
+    auto reply = command(cmd::msetnx<Input>, first, last);
+
+    return reply::parse<bool>(*reply);
+}
+
+inline void RedisCluster::psetex(const StringView &key,
+                            const std::chrono::milliseconds &ttl,
+                            const StringView &val) {
+    return psetex(key, ttl.count(), val);
+}
+
+inline void RedisCluster::setex(const StringView &key,
+                            const std::chrono::seconds &ttl,
+                            const StringView &val) {
+    setex(key, ttl.count(), val);
+}
+
+// LIST commands.
+
+template <typename Input>
+OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) {
+    if (first == last) {
+        throw Error("BLPOP: no key specified");
+    }
+
+    auto reply = command(cmd::blpop_range<Input>, first, last, timeout);
+
+    return reply::parse<OptionalStringPair>(*reply);
+}
+
+template <typename Input>
+OptionalStringPair RedisCluster::blpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout) {
+    return blpop(first, last, timeout.count());
+}
+
+template <typename Input>
+OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) {
+    if (first == last) {
+        throw Error("BRPOP: no key specified");
+    }
+
+    auto reply = command(cmd::brpop_range<Input>, first, last, timeout);
+
+    return reply::parse<OptionalStringPair>(*reply);
+}
+
+template <typename Input>
+OptionalStringPair RedisCluster::brpop(Input first,
+                                Input last,
+                                const std::chrono::seconds &timeout) {
+    return brpop(first, last, timeout.count());
+}
+
+inline OptionalString RedisCluster::brpoplpush(const StringView &source,
+                                        const StringView &destination,
+                                        const std::chrono::seconds &timeout) {
+    return brpoplpush(source, destination, timeout.count());
+}
+
+template <typename Input>
+inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("LPUSH: no key specified");
+    }
+
+    auto reply = command(cmd::lpush_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) {
+    auto reply = command(cmd::lrange, key, start, stop);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("RPUSH: no key specified");
+    }
+
+    auto reply = command(cmd::rpush_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+// HASH commands.
+
+template <typename Input>
+inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("HDEL: no key specified");
+    }
+
+    auto reply = command(cmd::hdel_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+inline void RedisCluster::hgetall(const StringView &key, Output output) {
+    auto reply = command(cmd::hgetall, key);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+inline void RedisCluster::hkeys(const StringView &key, Output output) {
+    auto reply = command(cmd::hkeys, key);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("HMGET: no key specified");
+    }
+
+    auto reply = command(cmd::hmget<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+inline void RedisCluster::hmset(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("HMSET: no key specified");
+    }
+
+    auto reply = command(cmd::hmset<Input>, key, first, last);
+
+    reply::parse<void>(*reply);
+}
+
+template <typename Output>
+long long RedisCluster::hscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::hscan, key, cursor, pattern, count);
+
+    return reply::parse_scan_reply(*reply, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::hscan(const StringView &key,
+                                long long cursor,
+                                const StringView &pattern,
+                                Output output) {
+    return hscan(key, cursor, pattern, 10, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::hscan(const StringView &key,
+                                long long cursor,
+                                long long count,
+                                Output output) {
+    return hscan(key, cursor, "*", count, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::hscan(const StringView &key,
+                                long long cursor,
+                                Output output) {
+    return hscan(key, cursor, "*", 10, output);
+}
+
+template <typename Output>
+inline void RedisCluster::hvals(const StringView &key, Output output) {
+    auto reply = command(cmd::hvals, key);
+
+    reply::to_array(*reply, output);
+}
+
+// SET commands.
+
+template <typename Input>
+long long RedisCluster::sadd(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("SADD: no key specified");
+    }
+
+    auto reply = command(cmd::sadd_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::sdiff(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("SDIFF: no key specified");
+    }
+
+    auto reply = command(cmd::sdiff<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long RedisCluster::sdiffstore(const StringView &destination,
+                                    Input first,
+                                    Input last) {
+    if (first == last) {
+        throw Error("SDIFFSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::sdiffstore_range<Input>, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::sinter(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("SINTER: no key specified");
+    }
+
+    auto reply = command(cmd::sinter<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long RedisCluster::sinterstore(const StringView &destination,
+                                    Input first,
+                                    Input last) {
+    if (first == last) {
+        throw Error("SINTERSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::sinterstore_range<Input>, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+void RedisCluster::smembers(const StringView &key, Output output) {
+    auto reply = command(cmd::smembers, key);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::spop(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::spop_range, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::srandmember(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::srandmember_range, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long RedisCluster::srem(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("SREM: no key specified");
+    }
+
+    auto reply = command(cmd::srem_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+long long RedisCluster::sscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::sscan, key, cursor, pattern, count);
+
+    return reply::parse_scan_reply(*reply, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::sscan(const StringView &key,
+                                long long cursor,
+                                const StringView &pattern,
+                                Output output) {
+    return sscan(key, cursor, pattern, 10, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::sscan(const StringView &key,
+                                long long cursor,
+                                long long count,
+                                Output output) {
+    return sscan(key, cursor, "*", count, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::sscan(const StringView &key,
+                                long long cursor,
+                                Output output) {
+    return sscan(key, cursor, "*", 10, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::sunion(Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("SUNION: no key specified");
+    }
+
+    auto reply = command(cmd::sunion<Input>, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) {
+    if (first == last) {
+        throw Error("SUNIONSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::sunionstore_range<Input>, destination, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+// SORTED SET commands.
+
+inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmax(key, timeout.count());
+}
+
+template <typename Input>
+auto RedisCluster::bzpopmax(Input first, Input last, long long timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    auto reply = command(cmd::bzpopmax_range<Input>, first, last, timeout);
+
+    return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
+}
+
+template <typename Input>
+inline auto RedisCluster::bzpopmax(Input first,
+                                    Input last,
+                                    const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmax(first, last, timeout.count());
+}
+
+inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmin(key, timeout.count());
+}
+
+template <typename Input>
+auto RedisCluster::bzpopmin(Input first, Input last, long long timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    auto reply = command(cmd::bzpopmin_range<Input>, first, last, timeout);
+
+    return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
+}
+
+template <typename Input>
+inline auto RedisCluster::bzpopmin(Input first,
+                                    Input last,
+                                    const std::chrono::seconds &timeout)
+    -> Optional<std::tuple<std::string, std::string, double>> {
+    return bzpopmin(first, last, timeout.count());
+}
+
+template <typename Input>
+long long RedisCluster::zadd(const StringView &key,
+                        Input first,
+                        Input last,
+                        UpdateType type,
+                        bool changed) {
+    if (first == last) {
+        throw Error("ZADD: no key specified");
+    }
+
+    auto reply = command(cmd::zadd_range<Input>, key, first, last, type, changed);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long RedisCluster::zcount(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zcount<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+long long RedisCluster::zinterstore(const StringView &destination,
+                                Input first,
+                                Input last,
+                                Aggregation type) {
+    if (first == last) {
+        throw Error("ZINTERSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::zinterstore_range<Input>,
+                            destination,
+                            first,
+                            last,
+                            type);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zlexcount<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+void RedisCluster::zpopmax(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::zpopmax, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::zpopmin(const StringView &key, long long count, Output output) {
+    auto reply = command(cmd::zpopmin, key, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) {
+    auto reply = _score_command<Output>(cmd::zrange, key, start, stop);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) {
+    zrangebylex(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void RedisCluster::zrangebylex(const StringView &key,
+                        const Interval &interval,
+                        const LimitOptions &opts,
+                        Output output) {
+    auto reply = command(cmd::zrangebylex<Interval>, key, interval, opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+void RedisCluster::zrangebyscore(const StringView &key,
+                            const Interval &interval,
+                            Output output) {
+    zrangebyscore(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void RedisCluster::zrangebyscore(const StringView &key,
+                            const Interval &interval,
+                            const LimitOptions &opts,
+                            Output output) {
+    auto reply = _score_command<Output>(cmd::zrangebyscore<Interval>,
+                                        key,
+                                        interval,
+                                        opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long RedisCluster::zrem(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("ZREM: no key specified");
+    }
+
+    auto reply = command(cmd::zrem_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zremrangebylex<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Interval>
+long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) {
+    auto reply = command(cmd::zremrangebyscore<Interval>, key, interval);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) {
+    auto reply = _score_command<Output>(cmd::zrevrange, key, start, stop);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+inline void RedisCluster::zrevrangebylex(const StringView &key,
+                                    const Interval &interval,
+                                    Output output) {
+    zrevrangebylex(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void RedisCluster::zrevrangebylex(const StringView &key,
+                            const Interval &interval,
+                            const LimitOptions &opts,
+                            Output output) {
+    auto reply = command(cmd::zrevrangebylex<Interval>, key, interval, opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Interval, typename Output>
+void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) {
+    zrevrangebyscore(key, interval, {}, output);
+}
+
+template <typename Interval, typename Output>
+void RedisCluster::zrevrangebyscore(const StringView &key,
+                                const Interval &interval,
+                                const LimitOptions &opts,
+                                Output output) {
+    auto reply = _score_command<Output>(cmd::zrevrangebyscore<Interval>, key, interval, opts);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+long long RedisCluster::zscan(const StringView &key,
+                        long long cursor,
+                        const StringView &pattern,
+                        long long count,
+                        Output output) {
+    auto reply = command(cmd::zscan, key, cursor, pattern, count);
+
+    return reply::parse_scan_reply(*reply, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::zscan(const StringView &key,
+                                long long cursor,
+                                const StringView &pattern,
+                                Output output) {
+    return zscan(key, cursor, pattern, 10, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::zscan(const StringView &key,
+                                long long cursor,
+                                long long count,
+                                Output output) {
+    return zscan(key, cursor, "*", count, output);
+}
+
+template <typename Output>
+inline long long RedisCluster::zscan(const StringView &key,
+                                long long cursor,
+                                Output output) {
+    return zscan(key, cursor, "*", 10, output);
+}
+
+template <typename Input>
+long long RedisCluster::zunionstore(const StringView &destination,
+                                    Input first,
+                                    Input last,
+                                    Aggregation type) {
+    if (first == last) {
+        throw Error("ZUNIONSTORE: no key specified");
+    }
+
+    auto reply = command(cmd::zunionstore_range<Input>,
+                            destination,
+                            first,
+                            last,
+                            type);
+
+    return reply::parse<long long>(*reply);
+}
+
+// HYPERLOGLOG commands.
+
+template <typename Input>
+bool RedisCluster::pfadd(const StringView &key, Input first, Input last) {
+    if (first == last) {
+        throw Error("PFADD: no key specified");
+    }
+
+    auto reply = command(cmd::pfadd_range<Input>, key, first, last);
+
+    return reply::parse<bool>(*reply);
+}
+
+template <typename Input>
+long long RedisCluster::pfcount(Input first, Input last) {
+    if (first == last) {
+        throw Error("PFCOUNT: no key specified");
+    }
+
+    auto reply = command(cmd::pfcount_range<Input>, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+void RedisCluster::pfmerge(const StringView &destination,
+                    Input first,
+                    Input last) {
+    if (first == last) {
+        throw Error("PFMERGE: no key specified");
+    }
+
+    auto reply = command(cmd::pfmerge_range<Input>, destination, first, last);
+
+    reply::parse<void>(*reply);
+}
+
+// GEO commands.
+
+template <typename Input>
+inline long long RedisCluster::geoadd(const StringView &key,
+                                Input first,
+                                Input last) {
+    if (first == last) {
+        throw Error("GEOADD: no key specified");
+    }
+
+    auto reply = command(cmd::geoadd_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("GEOHASH: no key specified");
+    }
+
+    auto reply = command(cmd::geohash_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) {
+    if (first == last) {
+        throw Error("GEOPOS: no key specified");
+    }
+
+    auto reply = command(cmd::geopos_range<Input>, key, first, last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::georadius(const StringView &key,
+                        const std::pair<double, double> &loc,
+                        double radius,
+                        GeoUnit unit,
+                        long long count,
+                        bool asc,
+                        Output output) {
+    auto reply = command(cmd::georadius,
+                            key,
+                            loc,
+                            radius,
+                            unit,
+                            count,
+                            asc,
+                            WithCoord<typename IterType<Output>::type>::value,
+                            WithDist<typename IterType<Output>::type>::value,
+                            WithHash<typename IterType<Output>::type>::value);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::georadiusbymember(const StringView &key,
+                                const StringView &member,
+                                double radius,
+                                GeoUnit unit,
+                                long long count,
+                                bool asc,
+                                Output output) {
+    auto reply = command(cmd::georadiusbymember,
+                            key,
+                            member,
+                            radius,
+                            unit,
+                            count,
+                            asc,
+                            WithCoord<typename IterType<Output>::type>::value,
+                            WithDist<typename IterType<Output>::type>::value,
+                            WithHash<typename IterType<Output>::type>::value);
+
+    reply::to_array(*reply, output);
+}
+
+// SCRIPTING commands.
+
+template <typename Result>
+Result RedisCluster::eval(const StringView &script,
+                            std::initializer_list<StringView> keys,
+                            std::initializer_list<StringView> args) {
+    if (keys.size() == 0) {
+        throw Error("DO NOT support Lua script without key");
+    }
+
+    auto reply = _command(cmd::eval, *keys.begin(), script, keys, args);
+
+    return reply::parse<Result>(*reply);
+}
+
+template <typename Output>
+void RedisCluster::eval(const StringView &script,
+                        std::initializer_list<StringView> keys,
+                        std::initializer_list<StringView> args,
+                        Output output) {
+    if (keys.size() == 0) {
+        throw Error("DO NOT support Lua script without key");
+    }
+
+    auto reply = _command(cmd::eval, *keys.begin(), script, keys, args);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Result>
+Result RedisCluster::evalsha(const StringView &script,
+                                std::initializer_list<StringView> keys,
+                                std::initializer_list<StringView> args) {
+    if (keys.size() == 0) {
+        throw Error("DO NOT support Lua script without key");
+    }
+
+    auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args);
+
+    return reply::parse<Result>(*reply);
+}
+
+template <typename Output>
+void RedisCluster::evalsha(const StringView &script,
+                            std::initializer_list<StringView> keys,
+                            std::initializer_list<StringView> args,
+                            Output output) {
+    if (keys.size() == 0) {
+        throw Error("DO NOT support Lua script without key");
+    }
+
+    auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args);
+
+    reply::to_array(*reply, output);
+}
+
+// Stream commands.
+
+template <typename Input>
+long long RedisCluster::xack(const StringView &key,
+                                const StringView &group,
+                                Input first,
+                                Input last) {
+    auto reply = command(cmd::xack_range<Input>, key, group, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Input>
+std::string RedisCluster::xadd(const StringView &key,
+                                const StringView &id,
+                                Input first,
+                                Input last) {
+    auto reply = command(cmd::xadd_range<Input>, key, id, first, last);
+
+    return reply::parse<std::string>(*reply);
+}
+
+template <typename Input>
+std::string RedisCluster::xadd(const StringView &key,
+                                const StringView &id,
+                                Input first,
+                                Input last,
+                                long long count,
+                                bool approx) {
+    auto reply = command(cmd::xadd_maxlen_range<Input>, key, id, first, last, count, approx);
+
+    return reply::parse<std::string>(*reply);
+}
+
+template <typename Output>
+void RedisCluster::xclaim(const StringView &key,
+                            const StringView &group,
+                            const StringView &consumer,
+                            const std::chrono::milliseconds &min_idle_time,
+                            const StringView &id,
+                            Output output) {
+    auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input, typename Output>
+void RedisCluster::xclaim(const StringView &key,
+                            const StringView &group,
+                            const StringView &consumer,
+                            const std::chrono::milliseconds &min_idle_time,
+                            Input first,
+                            Input last,
+                            Output output) {
+    auto reply = command(cmd::xclaim_range<Input>,
+                            key,
+                            group,
+                            consumer,
+                            min_idle_time.count(),
+                            first,
+                            last);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Input>
+long long RedisCluster::xdel(const StringView &key, Input first, Input last) {
+    auto reply = command(cmd::xdel_range<Input>, key, first, last);
+
+    return reply::parse<long long>(*reply);
+}
+
+template <typename Output>
+auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output)
+    -> std::tuple<long long, OptionalString, OptionalString> {
+    auto reply = command(cmd::xpending, key, group);
+
+    return reply::parse_xpending_reply(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::xpending(const StringView &key,
+                            const StringView &group,
+                            const StringView &start,
+                            const StringView &end,
+                            long long count,
+                            Output output) {
+    auto reply = command(cmd::xpending_detail, key, group, start, end, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::xpending(const StringView &key,
+                            const StringView &group,
+                            const StringView &start,
+                            const StringView &end,
+                            long long count,
+                            const StringView &consumer,
+                            Output output) {
+    auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::xrange(const StringView &key,
+                            const StringView &start,
+                            const StringView &end,
+                            Output output) {
+    auto reply = command(cmd::xrange, key, start, end);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::xrange(const StringView &key,
+                            const StringView &start,
+                            const StringView &end,
+                            long long count,
+                            Output output) {
+    auto reply = command(cmd::xrange_count, key, start, end, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::xread(const StringView &key,
+                            const StringView &id,
+                            long long count,
+                            Output output) {
+    auto reply = command(cmd::xread, key, id, count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto RedisCluster::xread(Input first, Input last, long long count, Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREAD: no key specified");
+    }
+
+    auto reply = command(cmd::xread_range<Input>, first, last, count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void RedisCluster::xread(const StringView &key,
+                            const StringView &id,
+                            const std::chrono::milliseconds &timeout,
+                            long long count,
+                            Output output) {
+    auto reply = command(cmd::xread_block, key, id, timeout.count(), count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto RedisCluster::xread(Input first,
+                            Input last,
+                            const std::chrono::milliseconds &timeout,
+                            long long count,
+                            Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREAD: no key specified");
+    }
+
+    auto reply = command(cmd::xread_block_range<Input>, first, last, timeout.count(), count);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void RedisCluster::xreadgroup(const StringView &group,
+                                const StringView &consumer,
+                                const StringView &key,
+                                const StringView &id,
+                                long long count,
+                                bool noack,
+                                Output output) {
+    auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto RedisCluster::xreadgroup(const StringView &group,
+                                const StringView &consumer,
+                                Input first,
+                                Input last,
+                                long long count,
+                                bool noack,
+                                Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREADGROUP: no key specified");
+    }
+
+    auto reply = _command(cmd::xreadgroup_range<Input>,
+                            first->first,
+                            group,
+                            consumer,
+                            first,
+                            last,
+                            count,
+                            noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void RedisCluster::xreadgroup(const StringView &group,
+                                const StringView &consumer,
+                                const StringView &key,
+                                const StringView &id,
+                                const std::chrono::milliseconds &timeout,
+                                long long count,
+                                bool noack,
+                                Output output) {
+    auto reply = _command(cmd::xreadgroup_block,
+                            key,
+                            group,
+                            consumer,
+                            key,
+                            id,
+                            timeout.count(),
+                            count,
+                            noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Input, typename Output>
+auto RedisCluster::xreadgroup(const StringView &group,
+                                const StringView &consumer,
+                                Input first,
+                                Input last,
+                                const std::chrono::milliseconds &timeout,
+                                long long count,
+                                bool noack,
+                                Output output)
+    -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
+    if (first == last) {
+        throw Error("XREADGROUP: no key specified");
+    }
+
+    auto reply = _command(cmd::xreadgroup_block_range<Input>,
+                            first->first,
+                            group,
+                            consumer,
+                            first,
+                            last,
+                            timeout.count(),
+                            count,
+                            noack);
+
+    if (!reply::is_nil(*reply)) {
+        reply::to_array(*reply, output);
+    }
+}
+
+template <typename Output>
+void RedisCluster::xrevrange(const StringView &key,
+                            const StringView &end,
+                            const StringView &start,
+                            Output output) {
+    auto reply = command(cmd::xrevrange, key, end, start);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Output>
+void RedisCluster::xrevrange(const StringView &key,
+                                const StringView &end,
+                                const StringView &start,
+                                long long count,
+                                Output output) {
+    auto reply = command(cmd::xrevrange_count, key, end, start, count);
+
+    reply::to_array(*reply, output);
+}
+
+template <typename Cmd, typename Key, typename ...Args>
+auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args)
+    -> typename std::enable_if<std::is_convertible<Key, StringView>::value,
+                                ReplyUPtr>::type {
+    return command(cmd, std::forward<Key>(key), std::forward<Args>(args)...);
+}
+
+template <typename Cmd, typename Key, typename ...Args>
+auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args)
+    -> typename std::enable_if<std::is_arithmetic<typename std::decay<Key>::type>::value,
+                                ReplyUPtr>::type {
+    auto k = std::to_string(std::forward<Key>(key));
+    return command(cmd, k, std::forward<Args>(args)...);
+}
+
+template <typename Cmd, typename ...Args>
+ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) {
+    return _command(cmd, key, key, std::forward<Args>(args)...);
+}
+
+template <typename Cmd, typename Input, typename ...Args>
+ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) {
+    return _range_command(cmd,
+                            std::is_convertible<
+                                typename std::decay<
+                                    decltype(*std::declval<Input>())>::type, StringView>(),
+                            std::forward<Input>(first),
+                            std::forward<Args>(args)...);
+}
+
+template <typename Cmd, typename Input, typename ...Args>
+ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) {
+    return _command(cmd, *input, input, std::forward<Args>(args)...);
+}
+
+template <typename Cmd, typename Input, typename ...Args>
+ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) {
+    return _command(cmd, std::get<0>(*input), input, std::forward<Args>(args)...);
+}
+
+template <typename Cmd, typename ...Args>
+ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) {
+    assert(!connection.broken());
+
+    cmd(connection, std::forward<Args>(args)...);
+
+    return connection.recv();
+}
+
+template <typename Cmd, typename ...Args>
+ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) {
+    for (auto idx = 0; idx < 2; ++idx) {
+        try {
+            auto guarded_connection = _pool.fetch(key);
+
+            return _command(cmd, guarded_connection.connection(), std::forward<Args>(args)...);
+        } catch (const IoError &err) {
+            // When master is down, one of its replicas will be promoted to be the new master.
+            // If we try to send command to the old master, we'll get an *IoError*.
+            // In this case, we need to update the slots mapping.
+            _pool.update();
+        } catch (const ClosedError &err) {
+            // Node might be removed.
+            // 1. Get up-to-date slot mapping to check if the node still exists.
+            _pool.update();
+
+            // TODO:
+            // 2. If it's NOT exist, update slot mapping, and retry.
+            // 3. If it's still exist, that means the node is down, NOT removed, throw exception.
+        } catch (const MovedError &err) {
+            // Slot mapping has been changed, update it and try again.
+            _pool.update();
+        } catch (const AskError &err) {
+            auto guarded_connection = _pool.fetch(err.node());
+            auto &connection = guarded_connection.connection();
+
+            // 1. send ASKING command.
+            _asking(connection);
+
+            // 2. resend last command.
+            try {
+                return _command(cmd, connection, std::forward<Args>(args)...);
+            } catch (const MovedError &err) {
+                throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state");
+            }
+        } // For other exceptions, just throw it.
+    }
+
+    // Possible failures:
+    // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx',
+    //    while the destination node has NOT run it.
+    //    In this case, client will be redirected by both nodes with MovedError.
+    // 2. Other failures...
+    throw Error("Failed to send command with key: " + std::string(key.data(), key.size()));
+}
+
+template <typename Cmd, typename ...Args>
+inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) {
+    return command(cmd, std::forward<Args>(args)..., true);
+}
+
+template <typename Cmd, typename ...Args>
+inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) {
+    return command(cmd, std::forward<Args>(args)..., false);
+}
+
+template <typename Output, typename Cmd, typename ...Args>
+inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) {
+    return _score_command(typename IsKvPairIter<Output>::type(),
+                            cmd,
+                            std::forward<Args>(args)...);
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP

+ 363 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h

@@ -0,0 +1,363 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H
+#define SEWENEW_REDISPLUSPLUS_REPLY_H
+
+#include <cassert>
+#include <string>
+#include <memory>
+#include <functional>
+#include <tuple>
+#include <hiredis/hiredis.h>
+#include "errors.h"
+#include "utils.h"
+
+namespace sw {
+
+namespace redis {
+
+struct ReplyDeleter {
+    void operator()(redisReply *reply) const {
+        if (reply != nullptr) {
+            freeReplyObject(reply);
+        }
+    }
+};
+
+using ReplyUPtr = std::unique_ptr<redisReply, ReplyDeleter>;
+
+namespace reply {
+
+template <typename T>
+struct ParseTag {};
+
+template <typename T>
+inline T parse(redisReply &reply) {
+    return parse(ParseTag<T>(), reply);
+}
+
+void parse(ParseTag<void>, redisReply &reply);
+
+std::string parse(ParseTag<std::string>, redisReply &reply);
+
+long long parse(ParseTag<long long>, redisReply &reply);
+
+double parse(ParseTag<double>, redisReply &reply);
+
+bool parse(ParseTag<bool>, redisReply &reply);
+
+template <typename T>
+Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply);
+
+template <typename T, typename U>
+std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply);
+
+template <typename ...Args>
+std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply);
+
+template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type = 0>
+T parse(ParseTag<T>, redisReply &reply);
+
+template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type = 0>
+T parse(ParseTag<T>, redisReply &reply);
+
+template <typename Output>
+long long parse_scan_reply(redisReply &reply, Output output);
+
+inline bool is_error(redisReply &reply) {
+    return reply.type == REDIS_REPLY_ERROR;
+}
+
+inline bool is_nil(redisReply &reply) {
+    return reply.type == REDIS_REPLY_NIL;
+}
+
+inline bool is_string(redisReply &reply) {
+    return reply.type == REDIS_REPLY_STRING;
+}
+
+inline bool is_status(redisReply &reply) {
+    return reply.type == REDIS_REPLY_STATUS;
+}
+
+inline bool is_integer(redisReply &reply) {
+    return reply.type == REDIS_REPLY_INTEGER;
+}
+
+inline bool is_array(redisReply &reply) {
+    return reply.type == REDIS_REPLY_ARRAY;
+}
+
+std::string to_status(redisReply &reply);
+
+template <typename Output>
+void to_array(redisReply &reply, Output output);
+
+// Rewrite set reply to bool type
+void rewrite_set_reply(redisReply &reply);
+
+// Rewrite georadius reply to OptionalLongLong type
+void rewrite_georadius_reply(redisReply &reply);
+
+template <typename Output>
+auto parse_xpending_reply(redisReply &reply, Output output)
+    -> std::tuple<long long, OptionalString, OptionalString>;
+
+}
+
+// Inline implementations.
+
+namespace reply {
+
+namespace detail {
+
+template <typename Output>
+void to_array(redisReply &reply, Output output) {
+    if (!is_array(reply)) {
+        throw ProtoError("Expect ARRAY reply");
+    }
+
+    if (reply.element == nullptr) {
+        // Empty array.
+        return;
+    }
+
+    for (std::size_t idx = 0; idx != reply.elements; ++idx) {
+        auto *sub_reply = reply.element[idx];
+        if (sub_reply == nullptr) {
+            throw ProtoError("Null array element reply");
+        }
+
+        *output = parse<typename IterType<Output>::type>(*sub_reply);
+
+        ++output;
+    }
+}
+
+bool is_flat_array(redisReply &reply);
+
+template <typename Output>
+void to_flat_array(redisReply &reply, Output output) {
+    if (reply.element == nullptr) {
+        // Empty array.
+        return;
+    }
+
+    if (reply.elements % 2 != 0) {
+        throw ProtoError("Not string pair array reply");
+    }
+
+    for (std::size_t idx = 0; idx != reply.elements; idx += 2) {
+        auto *key_reply = reply.element[idx];
+        auto *val_reply = reply.element[idx + 1];
+        if (key_reply == nullptr || val_reply == nullptr) {
+            throw ProtoError("Null string array reply");
+        }
+
+        using Pair = typename IterType<Output>::type;
+        using FirstType = typename std::decay<typename Pair::first_type>::type;
+        using SecondType = typename std::decay<typename Pair::second_type>::type;
+        *output = std::make_pair(parse<FirstType>(*key_reply),
+                                    parse<SecondType>(*val_reply));
+
+        ++output;
+    }
+}
+
+template <typename Output>
+void to_array(std::true_type, redisReply &reply, Output output) {
+    if (is_flat_array(reply)) {
+        to_flat_array(reply, output);
+    } else {
+        to_array(reply, output);
+    }
+}
+
+template <typename Output>
+void to_array(std::false_type, redisReply &reply, Output output) {
+    to_array(reply, output);
+}
+
+template <typename T>
+std::tuple<T> parse_tuple(redisReply **reply, std::size_t idx) {
+    assert(reply != nullptr);
+
+    auto *sub_reply = reply[idx];
+    if (sub_reply == nullptr) {
+        throw ProtoError("Null reply");
+    }
+
+    return std::make_tuple(parse<T>(*sub_reply));
+}
+
+template <typename T, typename ...Args>
+auto parse_tuple(redisReply **reply, std::size_t idx) ->
+    typename std::enable_if<sizeof...(Args) != 0, std::tuple<T, Args...>>::type {
+    assert(reply != nullptr);
+
+    return std::tuple_cat(parse_tuple<T>(reply, idx),
+                            parse_tuple<Args...>(reply, idx + 1));
+}
+
+}
+
+template <typename T>
+Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
+    if (reply::is_nil(reply)) {
+        return {};
+    }
+
+    return Optional<T>(parse<T>(reply));
+}
+
+template <typename T, typename U>
+std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply) {
+    if (!is_array(reply)) {
+        throw ProtoError("Expect ARRAY reply");
+    }
+
+    if (reply.elements != 2) {
+        throw ProtoError("NOT key-value PAIR reply");
+    }
+
+    if (reply.element == nullptr) {
+        throw ProtoError("Null PAIR reply");
+    }
+
+    auto *first = reply.element[0];
+    auto *second = reply.element[1];
+    if (first == nullptr || second == nullptr) {
+        throw ProtoError("Null pair reply");
+    }
+
+    return std::make_pair(parse<typename std::decay<T>::type>(*first),
+                            parse<typename std::decay<U>::type>(*second));
+}
+
+template <typename ...Args>
+std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply) {
+    constexpr auto size = sizeof...(Args);
+
+    static_assert(size > 0, "DO NOT support parsing tuple with 0 element");
+
+    if (!is_array(reply)) {
+        throw ProtoError("Expect ARRAY reply");
+    }
+
+    if (reply.elements != size) {
+        throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements");
+    }
+
+    if (reply.element == nullptr) {
+        throw ProtoError("Null TUPLE reply");
+    }
+
+    return detail::parse_tuple<Args...>(reply.element, 0);
+}
+
+template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type>
+T parse(ParseTag<T>, redisReply &reply) {
+    if (!is_array(reply)) {
+        throw ProtoError("Expect ARRAY reply");
+    }
+
+    T container;
+
+    to_array(reply, std::back_inserter(container));
+
+    return container;
+}
+
+template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type>
+T parse(ParseTag<T>, redisReply &reply) {
+    if (!is_array(reply)) {
+        throw ProtoError("Expect ARRAY reply");
+    }
+
+    T container;
+
+    to_array(reply, std::inserter(container, container.end()));
+
+    return container;
+}
+
+template <typename Output>
+long long parse_scan_reply(redisReply &reply, Output output) {
+    if (reply.elements != 2 || reply.element == nullptr) {
+        throw ProtoError("Invalid scan reply");
+    }
+
+    auto *cursor_reply = reply.element[0];
+    auto *data_reply = reply.element[1];
+    if (cursor_reply == nullptr || data_reply == nullptr) {
+        throw ProtoError("Invalid cursor reply or data reply");
+    }
+
+    auto cursor_str = reply::parse<std::string>(*cursor_reply);
+    auto new_cursor = 0;
+    try {
+        new_cursor = std::stoll(cursor_str);
+    } catch (const std::exception &e) {
+        throw ProtoError("Invalid cursor reply: " + cursor_str);
+    }
+
+    reply::to_array(*data_reply, output);
+
+    return new_cursor;
+}
+
+template <typename Output>
+void to_array(redisReply &reply, Output output) {
+    if (!is_array(reply)) {
+        throw ProtoError("Expect ARRAY reply");
+    }
+
+    detail::to_array(typename IsKvPairIter<Output>::type(), reply, output);
+}
+
+template <typename Output>
+auto parse_xpending_reply(redisReply &reply, Output output)
+    -> std::tuple<long long, OptionalString, OptionalString> {
+    if (!is_array(reply) || reply.elements != 4) {
+        throw ProtoError("expect array reply with 4 elements");
+    }
+
+    for (std::size_t idx = 0; idx != reply.elements; ++idx) {
+        if (reply.element[idx] == nullptr) {
+            throw ProtoError("null array reply");
+        }
+    }
+
+    auto num = parse<long long>(*(reply.element[0]));
+    auto start = parse<OptionalString>(*(reply.element[1]));
+    auto end = parse<OptionalString>(*(reply.element[2]));
+
+    auto &entry_reply = *(reply.element[3]);
+    if (!is_nil(entry_reply)) {
+        to_array(entry_reply, output);
+    }
+
+    return std::make_tuple(num, std::move(start), std::move(end));
+}
+
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H

+ 138 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h

@@ -0,0 +1,138 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H
+#define SEWENEW_REDISPLUSPLUS_SENTINEL_H
+
+#include <string>
+#include <list>
+#include <vector>
+#include <memory>
+#include <mutex>
+#include "connection.h"
+#include "shards.h"
+#include "reply.h"
+
+namespace sw {
+
+namespace redis {
+
+struct SentinelOptions {
+    std::vector<std::pair<std::string, int>> nodes;
+
+    std::string password;
+
+    bool keep_alive = true;
+
+    std::chrono::milliseconds connect_timeout{100};
+
+    std::chrono::milliseconds socket_timeout{100};
+
+    std::chrono::milliseconds retry_interval{100};
+
+    std::size_t max_retry = 2;
+};
+
+class Sentinel {
+public:
+    explicit Sentinel(const SentinelOptions &sentinel_opts);
+
+    Sentinel(const Sentinel &) = delete;
+    Sentinel& operator=(const Sentinel &) = delete;
+
+    Sentinel(Sentinel &&) = delete;
+    Sentinel& operator=(Sentinel &&) = delete;
+
+    ~Sentinel() = default;
+
+private:
+    Connection master(const std::string &master_name, const ConnectionOptions &opts);
+
+    Connection slave(const std::string &master_name, const ConnectionOptions &opts);
+
+    class Iterator;
+
+    friend class SimpleSentinel;
+
+    std::list<ConnectionOptions> _parse_options(const SentinelOptions &opts) const;
+
+    Optional<Node> _get_master_addr_by_name(Connection &connection, const StringView &name);
+
+    std::vector<Node> _get_slave_addr_by_name(Connection &connection, const StringView &name);
+
+    Connection _connect_redis(const Node &node, ConnectionOptions opts);
+
+    Role _get_role(Connection &connection);
+
+    std::vector<Node> _parse_slave_info(redisReply &reply) const;
+
+    std::list<Connection> _healthy_sentinels;
+
+    std::list<ConnectionOptions> _broken_sentinels;
+
+    SentinelOptions _sentinel_opts;
+
+    std::mutex _mutex;
+};
+
+class SimpleSentinel {
+public:
+    SimpleSentinel(const std::shared_ptr<Sentinel> &sentinel,
+                    const std::string &master_name,
+                    Role role);
+
+    SimpleSentinel() = default;
+
+    SimpleSentinel(const SimpleSentinel &) = default;
+    SimpleSentinel& operator=(const SimpleSentinel &) = default;
+
+    SimpleSentinel(SimpleSentinel &&) = default;
+    SimpleSentinel& operator=(SimpleSentinel &&) = default;
+
+    ~SimpleSentinel() = default;
+
+    explicit operator bool() const {
+        return bool(_sentinel);
+    }
+
+    Connection create(const ConnectionOptions &opts);
+
+private:
+    std::shared_ptr<Sentinel> _sentinel;
+
+    std::string _master_name;
+
+    Role _role = Role::MASTER;
+};
+
+class StopIterError : public Error {
+public:
+    StopIterError() : Error("StopIterError") {}
+
+    StopIterError(const StopIterError &) = default;
+    StopIterError& operator=(const StopIterError &) = default;
+
+    StopIterError(StopIterError &&) = default;
+    StopIterError& operator=(StopIterError &&) = default;
+
+    virtual ~StopIterError() = default;
+};
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H

+ 115 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h

@@ -0,0 +1,115 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H
+#define SEWENEW_REDISPLUSPLUS_SHARDS_H
+
+#include <string>
+#include <map>
+#include "errors.h"
+
+namespace sw {
+
+namespace redis {
+
+using Slot = std::size_t;
+
+struct SlotRange {
+    Slot min;
+    Slot max;
+};
+
+inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) {
+    return lhs.max < rhs.max;
+}
+
+struct Node {
+    std::string host;
+    int port;
+};
+
+inline bool operator==(const Node &lhs, const Node &rhs) {
+    return lhs.host == rhs.host && lhs.port == rhs.port;
+}
+
+struct NodeHash {
+    std::size_t operator()(const Node &node) const noexcept {
+        auto host_hash = std::hash<std::string>{}(node.host);
+        auto port_hash = std::hash<int>{}(node.port);
+        return host_hash ^ (port_hash << 1);
+    }
+};
+
+using Shards = std::map<SlotRange, Node>;
+
+class RedirectionError : public ReplyError {
+public:
+    RedirectionError(const std::string &msg);
+
+    RedirectionError(const RedirectionError &) = default;
+    RedirectionError& operator=(const RedirectionError &) = default;
+
+    RedirectionError(RedirectionError &&) = default;
+    RedirectionError& operator=(RedirectionError &&) = default;
+
+    virtual ~RedirectionError() = default;
+
+    Slot slot() const {
+        return _slot;
+    }
+
+    const Node& node() const {
+        return _node;
+    }
+
+private:
+    std::pair<Slot, Node> _parse_error(const std::string &msg) const;
+
+    Slot _slot = 0;
+    Node _node;
+};
+
+class MovedError : public RedirectionError {
+public:
+    explicit MovedError(const std::string &msg) : RedirectionError(msg) {}
+
+    MovedError(const MovedError &) = default;
+    MovedError& operator=(const MovedError &) = default;
+
+    MovedError(MovedError &&) = default;
+    MovedError& operator=(MovedError &&) = default;
+
+    virtual ~MovedError() = default;
+};
+
+class AskError : public RedirectionError {
+public:
+    explicit AskError(const std::string &msg) : RedirectionError(msg) {}
+
+    AskError(const AskError &) = default;
+    AskError& operator=(const AskError &) = default;
+
+    AskError(AskError &&) = default;
+    AskError& operator=(AskError &&) = default;
+
+    virtual ~AskError() = default;
+};
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H

+ 137 - 0
ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h

@@ -0,0 +1,137 @@
+/**************************************************************************
+   Copyright (c) 2017 sewenew
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ *************************************************************************/
+
+#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
+#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
+
+#include <cassert>
+#include <unordered_map>
+#include <string>
+#include <random>
+#include <memory>
+#include "reply.h"
+#include "connection_pool.h"
+#include "shards.h"
+
+namespace sw {
+
+namespace redis {
+
+using ConnectionPoolSPtr = std::shared_ptr<ConnectionPool>;
+
+class GuardedConnection {
+public:
+    GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool),
+                                                        _connection(_pool->fetch()) {
+        assert(!_connection.broken());
+    }
+
+    GuardedConnection(const GuardedConnection &) = delete;
+    GuardedConnection& operator=(const GuardedConnection &) = delete;
+
+    GuardedConnection(GuardedConnection &&) = default;
+    GuardedConnection& operator=(GuardedConnection &&) = default;
+
+    ~GuardedConnection() {
+        _pool->release(std::move(_connection));
+    }
+
+    Connection& connection() {
+        return _connection;
+    }
+
+private:
+    ConnectionPoolSPtr _pool;
+    Connection _connection;
+};
+
+class ShardsPool {
+public:
+    ShardsPool() = default;
+
+    ShardsPool(const ShardsPool &that) = delete;
+    ShardsPool& operator=(const ShardsPool &that) = delete;
+
+    ShardsPool(ShardsPool &&that);
+    ShardsPool& operator=(ShardsPool &&that);
+
+    ~ShardsPool() = default;
+
+    ShardsPool(const ConnectionPoolOptions &pool_opts,
+                const ConnectionOptions &connection_opts);
+
+    // Fetch a connection by key.
+    GuardedConnection fetch(const StringView &key);
+
+    // Randomly pick a connection.
+    GuardedConnection fetch();
+
+    // Fetch a connection by node.
+    GuardedConnection fetch(const Node &node);
+
+    void update();
+
+    ConnectionOptions connection_options(const StringView &key);
+
+    ConnectionOptions connection_options();
+
+private:
+    void _move(ShardsPool &&that);
+
+    void _init_pool(const Shards &shards);
+
+    Shards _cluster_slots(Connection &connection) const;
+
+    ReplyUPtr _cluster_slots_command(Connection &connection) const;
+
+    Shards _parse_reply(redisReply &reply) const;
+
+    std::pair<SlotRange, Node> _parse_slot_info(redisReply &reply) const;
+
+    // Get slot by key.
+    std::size_t _slot(const StringView &key) const;
+
+    // Randomly pick a slot.
+    std::size_t _slot() const;
+
+    ConnectionPoolSPtr& _get_pool(Slot slot);
+
+    GuardedConnection _fetch(Slot slot);
+
+    ConnectionOptions _connection_options(Slot slot);
+
+    using NodeMap = std::unordered_map<Node, ConnectionPoolSPtr, NodeHash>;
+
+    NodeMap::iterator _add_node(const Node &node);
+
+    ConnectionPoolOptions _pool_opts;
+
+    ConnectionOptions _connection_opts;
+
+    Shards _shards;
+
+    NodeMap _pools;
+
+    std::mutex _mutex;
+
+    static const std::size_t SHARDS = 16383;
+};
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H

部分文件因为文件数量过多而无法显示