Jelajahi Sumber

centos8 binaries for libhiredis and libredis++

Grant Limberg 5 tahun lalu
induk
melakukan
5babd01d40
22 mengubah file dengan 12246 tambahan dan 0 penghapusan
  1. TEMPAT SAMPAH
      ext/hiredis-0.14.1/lib/centos8/libhiredis.a
  2. 2233 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command.h
  3. 180 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_args.h
  4. 211 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/command_options.h
  5. 194 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection.h
  6. 115 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/connection_pool.h
  7. 159 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/errors.h
  8. 49 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/pipeline.h
  9. 1844 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.h
  10. 208 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/queued_redis.hpp
  11. 25 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis++.h
  12. 1523 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.h
  13. 1365 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis.hpp
  14. 1395 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.h
  15. 1415 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/redis_cluster.hpp
  16. 363 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/reply.h
  17. 138 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/sentinel.h
  18. 115 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards.h
  19. 137 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/shards_pool.h
  20. 231 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/subscriber.h
  21. 77 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/transaction.h
  22. 269 0
      ext/redis-plus-plus-1.1.1/install/centos8/include/sw/redis++/utils.h

TEMPAT SAMPAH
ext/hiredis-0.14.1/lib/centos8/libhiredis.a


+ 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

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

@@ -0,0 +1,231 @@
+/**************************************************************************
+   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_SUBSCRIBER_H
+#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
+
+#include <unordered_map>
+#include <string>
+#include <functional>
+#include "connection.h"
+#include "reply.h"
+#include "command.h"
+#include "utils.h"
+
+namespace sw {
+
+namespace redis {
+
+// @NOTE: Subscriber is NOT thread-safe.
+// Subscriber uses callbacks to handle messages. There are 6 kinds of messages:
+// 1) MESSAGE: message sent to a channel.
+// 2) PMESSAGE: message sent to channels of a given pattern.
+// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel.
+// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel.
+// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern.
+// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern.
+//
+// Use Subscriber::on_message(MsgCallback) to set the callback function for message of
+// *MESSAGE* type, and the callback interface is:
+// void (std::string channel, std::string msg)
+//
+// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of
+// *PMESSAGE* type, and the callback interface is:
+// void (std::string pattern, std::string channel, std::string msg)
+//
+// Messages of other types are called *META MESSAGE*, they have the same callback interface.
+// Use Subscriber::on_meta(MetaCallback) to set the callback function:
+// void (Subscriber::MsgType type, OptionalString channel, long long num)
+//
+// NOTE: If we 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.
+//
+// If you don't set callback for a specific kind of message, Subscriber::consume() will
+// receive the message, and ignore it, i.e. no callback will be called.
+class Subscriber {
+public:
+    Subscriber(const Subscriber &) = delete;
+    Subscriber& operator=(const Subscriber &) = delete;
+
+    Subscriber(Subscriber &&) = default;
+    Subscriber& operator=(Subscriber &&) = default;
+
+    ~Subscriber() = default;
+
+    enum class MsgType {
+        SUBSCRIBE,
+        UNSUBSCRIBE,
+        PSUBSCRIBE,
+        PUNSUBSCRIBE,
+        MESSAGE,
+        PMESSAGE
+    };
+
+    template <typename MsgCb>
+    void on_message(MsgCb msg_callback);
+
+    template <typename PMsgCb>
+    void on_pmessage(PMsgCb pmsg_callback);
+
+    template <typename MetaCb>
+    void on_meta(MetaCb meta_callback);
+
+    void subscribe(const StringView &channel);
+
+    template <typename Input>
+    void subscribe(Input first, Input last);
+
+    template <typename T>
+    void subscribe(std::initializer_list<T> channels) {
+        subscribe(channels.begin(), channels.end());
+    }
+
+    void unsubscribe();
+
+    void unsubscribe(const StringView &channel);
+
+    template <typename Input>
+    void unsubscribe(Input first, Input last);
+
+    template <typename T>
+    void unsubscribe(std::initializer_list<T> channels) {
+        unsubscribe(channels.begin(), channels.end());
+    }
+
+    void psubscribe(const StringView &pattern);
+
+    template <typename Input>
+    void psubscribe(Input first, Input last);
+
+    template <typename T>
+    void psubscribe(std::initializer_list<T> channels) {
+        psubscribe(channels.begin(), channels.end());
+    }
+
+    void punsubscribe();
+
+    void punsubscribe(const StringView &channel);
+
+    template <typename Input>
+    void punsubscribe(Input first, Input last);
+
+    template <typename T>
+    void punsubscribe(std::initializer_list<T> channels) {
+        punsubscribe(channels.begin(), channels.end());
+    }
+
+    void consume();
+
+private:
+    friend class Redis;
+
+    friend class RedisCluster;
+
+    explicit Subscriber(Connection connection);
+
+    MsgType _msg_type(redisReply *reply) const;
+
+    void _check_connection();
+
+    void _handle_message(redisReply &reply);
+
+    void _handle_pmessage(redisReply &reply);
+
+    void _handle_meta(MsgType type, redisReply &reply);
+
+    using MsgCallback = std::function<void (std::string channel, std::string msg)>;
+
+    using PatternMsgCallback = std::function<void (std::string pattern,
+                                                    std::string channel,
+                                                    std::string msg)>;
+
+    using MetaCallback = std::function<void (MsgType type,
+                                                OptionalString channel,
+                                                long long num)>;
+
+    using TypeIndex = std::unordered_map<std::string, MsgType>;
+    static const TypeIndex _msg_type_index;
+
+    Connection _connection;
+
+    MsgCallback _msg_callback = nullptr;
+
+    PatternMsgCallback _pmsg_callback = nullptr;
+
+    MetaCallback _meta_callback = nullptr;
+};
+
+template <typename MsgCb>
+void Subscriber::on_message(MsgCb msg_callback) {
+    _msg_callback = msg_callback;
+}
+
+template <typename PMsgCb>
+void Subscriber::on_pmessage(PMsgCb pmsg_callback) {
+    _pmsg_callback = pmsg_callback;
+}
+
+template <typename MetaCb>
+void Subscriber::on_meta(MetaCb meta_callback) {
+    _meta_callback = meta_callback;
+}
+
+template <typename Input>
+void Subscriber::subscribe(Input first, Input last) {
+    if (first == last) {
+        return;
+    }
+
+    _check_connection();
+
+    cmd::subscribe_range(_connection, first, last);
+}
+
+template <typename Input>
+void Subscriber::unsubscribe(Input first, Input last) {
+    _check_connection();
+
+    cmd::unsubscribe_range(_connection, first, last);
+}
+
+template <typename Input>
+void Subscriber::psubscribe(Input first, Input last) {
+    if (first == last) {
+        return;
+    }
+
+    _check_connection();
+
+    cmd::psubscribe_range(_connection, first, last);
+}
+
+template <typename Input>
+void Subscriber::punsubscribe(Input first, Input last) {
+    _check_connection();
+
+    cmd::punsubscribe_range(_connection, first, last);
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H

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

@@ -0,0 +1,77 @@
+/**************************************************************************
+   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_TRANSACTION_H
+#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H
+
+#include <cassert>
+#include <vector>
+#include "connection.h"
+#include "errors.h"
+
+namespace sw {
+
+namespace redis {
+
+class TransactionImpl {
+public:
+    explicit TransactionImpl(bool piped) : _piped(piped) {}
+
+    template <typename Cmd, typename ...Args>
+    void command(Connection &connection, Cmd cmd, Args &&...args);
+
+    std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
+
+    void discard(Connection &connection, std::size_t cmd_num);
+
+private:
+    void _open_transaction(Connection &connection);
+
+    void _close_transaction();
+
+    void _get_queued_reply(Connection &connection);
+
+    void _get_queued_replies(Connection &connection, std::size_t cmd_num);
+
+    std::vector<ReplyUPtr> _exec(Connection &connection);
+
+    void _discard(Connection &connection);
+
+    bool _in_transaction = false;
+
+    bool _piped;
+};
+
+template <typename Cmd, typename ...Args>
+void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) {
+    assert(!connection.broken());
+
+    if (!_in_transaction) {
+        _open_transaction(connection);
+    }
+
+    cmd(connection, std::forward<Args>(args)...);
+
+    if (!_piped) {
+        _get_queued_reply(connection);
+    }
+}
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H

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

@@ -0,0 +1,269 @@
+/**************************************************************************
+   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_UTILS_H
+#define SEWENEW_REDISPLUSPLUS_UTILS_H
+
+#include <cstring>
+#include <string>
+#include <type_traits>
+
+namespace sw {
+
+namespace redis {
+
+// By now, not all compilers support std::string_view,
+// so we make our own implementation.
+class StringView {
+public:
+    constexpr StringView() noexcept = default;
+
+    constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {}
+
+    StringView(const char *data) : _data(data), _size(std::strlen(data)) {}
+
+    StringView(const std::string &str) : _data(str.data()), _size(str.size()) {}
+
+    constexpr StringView(const StringView &) noexcept = default;
+
+    StringView& operator=(const StringView &) noexcept = default;
+
+    constexpr const char* data() const noexcept {
+        return _data;
+    }
+
+    constexpr std::size_t size() const noexcept {
+        return _size;
+    }
+
+private:
+    const char *_data = nullptr;
+    std::size_t _size = 0;
+};
+
+template <typename T>
+class Optional {
+public:
+    Optional() = default;
+
+    Optional(const Optional &) = default;
+    Optional& operator=(const Optional &) = default;
+
+    Optional(Optional &&) = default;
+    Optional& operator=(Optional &&) = default;
+
+    ~Optional() = default;
+
+    template <typename ...Args>
+    explicit Optional(Args &&...args) : _value(true, T(std::forward<Args>(args)...)) {}
+
+    explicit operator bool() const {
+        return _value.first;
+    }
+
+    T& value() {
+        return _value.second;
+    }
+
+    const T& value() const {
+        return _value.second;
+    }
+
+    T* operator->() {
+        return &(_value.second);
+    }
+
+    const T* operator->() const {
+        return &(_value.second);
+    }
+
+    T& operator*() {
+        return _value.second;
+    }
+
+    const T& operator*() const {
+        return _value.second;
+    }
+
+private:
+    std::pair<bool, T> _value;
+};
+
+using OptionalString = Optional<std::string>;
+
+using OptionalLongLong = Optional<long long>;
+
+using OptionalDouble = Optional<double>;
+
+using OptionalStringPair = Optional<std::pair<std::string, std::string>>;
+
+template <typename ...>
+struct IsKvPair : std::false_type {};
+
+template <typename T, typename U>
+struct IsKvPair<std::pair<T, U>> : std::true_type {};
+
+template <typename ...>
+using Void = void;
+
+template <typename T, typename U = Void<>>
+struct IsInserter : std::false_type {};
+
+template <typename T>
+//struct IsInserter<T, Void<typename T::container_type>> : std::true_type {};
+struct IsInserter<T,
+    typename std::enable_if<!std::is_void<typename T::container_type>::value>::type>
+        : std::true_type {};
+
+template <typename Iter, typename T = Void<>>
+struct IterType {
+    using type = typename std::iterator_traits<Iter>::value_type;
+};
+
+template <typename Iter>
+//struct IterType<Iter, Void<typename Iter::container_type>> {
+struct IterType<Iter,
+    //typename std::enable_if<std::is_void<typename Iter::value_type>::value>::type> {
+    typename std::enable_if<IsInserter<Iter>::value>::type> {
+    using type = typename std::decay<typename Iter::container_type::value_type>::type;
+};
+
+template <typename Iter, typename T = Void<>>
+struct IsIter : std::false_type {};
+
+template <typename Iter>
+struct IsIter<Iter, typename std::enable_if<IsInserter<Iter>::value>::type> : std::true_type {};
+
+template <typename Iter>
+//struct IsIter<Iter, Void<typename std::iterator_traits<Iter>::iterator_category>>
+struct IsIter<Iter,
+    typename std::enable_if<!std::is_void<
+        typename std::iterator_traits<Iter>::value_type>::value>::type>
+            : std::integral_constant<bool, !std::is_convertible<Iter, StringView>::value> {};
+
+template <typename T>
+struct IsKvPairIter : IsKvPair<typename IterType<T>::type> {};
+
+template <typename T, typename Tuple>
+struct TupleWithType : std::false_type {};
+
+template <typename T>
+struct TupleWithType<T, std::tuple<>> : std::false_type {};
+
+template <typename T, typename U, typename ...Args>
+struct TupleWithType<T, std::tuple<U, Args...>> : TupleWithType<T, std::tuple<Args...>> {};
+
+template <typename T, typename ...Args>
+struct TupleWithType<T, std::tuple<T, Args...>> : std::true_type {};
+
+template <std::size_t ...Is>
+struct IndexSequence {};
+
+template <std::size_t I, std::size_t ...Is>
+struct MakeIndexSequence : MakeIndexSequence<I - 1, I - 1, Is...> {};
+
+template <std::size_t ...Is>
+struct MakeIndexSequence<0, Is...> : IndexSequence<Is...> {};
+
+// NthType and NthValue are taken from
+// https://stackoverflow.com/questions/14261183
+template <std::size_t I, typename ...Args>
+struct NthType {};
+
+template <typename Arg, typename ...Args>
+struct NthType<0, Arg, Args...> {
+    using type = Arg;
+};
+
+template <std::size_t I, typename Arg, typename ...Args>
+struct NthType<I, Arg, Args...> {
+    using type = typename NthType<I - 1, Args...>::type;
+};
+
+template <typename ...Args>
+struct LastType {
+    using type = typename NthType<sizeof...(Args) - 1, Args...>::type;
+};
+
+struct InvalidLastType {};
+
+template <>
+struct LastType<> {
+    using type = InvalidLastType;
+};
+
+template <std::size_t I, typename Arg, typename ...Args>
+auto NthValue(Arg &&arg, Args &&...)
+    -> typename std::enable_if<(I == 0), decltype(std::forward<Arg>(arg))>::type {
+    return std::forward<Arg>(arg);
+}
+
+template <std::size_t I, typename Arg, typename ...Args>
+auto NthValue(Arg &&, Args &&...args)
+    -> typename std::enable_if<(I > 0),
+            decltype(std::forward<typename NthType<I, Arg, Args...>::type>(
+                    std::declval<typename NthType<I, Arg, Args...>::type>()))>::type {
+    return std::forward<typename NthType<I, Arg, Args...>::type>(
+            NthValue<I - 1>(std::forward<Args>(args)...));
+}
+
+template <typename ...Args>
+auto LastValue(Args &&...args)
+    -> decltype(std::forward<typename LastType<Args...>::type>(
+            std::declval<typename LastType<Args...>::type>())) {
+    return std::forward<typename LastType<Args...>::type>(
+            NthValue<sizeof...(Args) - 1>(std::forward<Args>(args)...));
+}
+
+template <typename T, typename = Void<>>
+struct HasPushBack : std::false_type {};
+
+template <typename T>
+struct HasPushBack<T,
+    typename std::enable_if<
+        std::is_void<decltype(
+            std::declval<T>().push_back(std::declval<typename T::value_type>())
+                )>::value>::type> : std::true_type {};
+
+template <typename T, typename = Void<>>
+struct HasInsert : std::false_type {};
+
+template <typename T>
+struct HasInsert<T,
+    typename std::enable_if<
+        std::is_same<
+            decltype(std::declval<T>().insert(std::declval<typename T::const_iterator>(),
+                                                std::declval<typename T::value_type>())),
+            typename T::iterator>::value>::type> : std::true_type {};
+
+template <typename T>
+struct IsSequenceContainer
+    : std::integral_constant<bool,
+        HasPushBack<T>::value
+            && !std::is_same<typename std::decay<T>::type, std::string>::value> {};
+
+template <typename T>
+struct IsAssociativeContainer
+    : std::integral_constant<bool,
+        HasInsert<T>::value && !HasPushBack<T>::value> {};
+
+uint16_t crc16(const char *buf, int len);
+
+}
+
+}
+
+#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H