/************************************************************************** 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 auto Redis::command(Cmd cmd, Args &&...args) -> typename std::enable_if::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)...); } 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)...); } } template auto Redis::command(const StringView &cmd_name, Args &&...args) -> typename std::enable_if::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)...); connection.send(cmd_args); }; return command(cmd, cmd_name, std::forward(args)...); } template auto Redis::command(Input first, Input last) -> typename std::enable_if::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 Result Redis::command(const StringView &cmd_name, Args &&...args) { auto r = command(cmd_name, std::forward(args)...); assert(r); return reply::parse(*r); } template auto Redis::command(const StringView &cmd_name, Args &&...args) -> typename std::enable_if::type>::value, void>::type { auto r = _command(cmd_name, MakeIndexSequence(), std::forward(args)...); assert(r); reply::to_array(*r, LastValue(std::forward(args)...)); } template auto Redis::command(Input first, Input last) -> typename std::enable_if::value, Result>::type { auto r = command(first, last); assert(r); return reply::parse(*r); } template auto Redis::command(Input first, Input last, Output output) -> typename std::enable_if::value, void>::type { auto r = command(first, last); assert(r); reply::to_array(*r, output); } // KEY commands. template long long Redis::del(Input first, Input last) { if (first == last) { throw Error("DEL: no key specified"); } auto reply = command(cmd::del_range, first, last); return reply::parse(*reply); } template long long Redis::exists(Input first, Input last) { if (first == last) { throw Error("EXISTS: no key specified"); } auto reply = command(cmd::exists_range, first, last); return reply::parse(*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 &tp) { return expireat(key, tp.time_since_epoch().count()); } template 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 &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 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 inline long long Redis::scan(long long cursor, const StringView &pattern, Output output) { return scan(cursor, pattern, 10, output); } template inline long long Redis::scan(long long cursor, long long count, Output output) { return scan(cursor, "*", count, output); } template inline long long Redis::scan(long long cursor, Output output) { return scan(cursor, "*", 10, output); } template long long Redis::touch(Input first, Input last) { if (first == last) { throw Error("TOUCH: no key specified"); } auto reply = command(cmd::touch_range, first, last); return reply::parse(*reply); } template long long Redis::unlink(Input first, Input last) { if (first == last) { throw Error("UNLINK: no key specified"); } auto reply = command(cmd::unlink_range, first, last); return reply::parse(*reply); } inline long long Redis::wait(long long numslaves, const std::chrono::milliseconds &timeout) { return wait(numslaves, timeout.count()); } // STRING commands. template 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, op, destination, first, last); return reply::parse(*reply); } template void Redis::mget(Input first, Input last, Output output) { if (first == last) { throw Error("MGET: no key specified"); } auto reply = command(cmd::mget, first, last); reply::to_array(*reply, output); } template void Redis::mset(Input first, Input last) { if (first == last) { throw Error("MSET: no key specified"); } auto reply = command(cmd::mset, first, last); reply::parse(*reply); } template bool Redis::msetnx(Input first, Input last) { if (first == last) { throw Error("MSETNX: no key specified"); } auto reply = command(cmd::msetnx, first, last); return reply::parse(*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 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, first, last, timeout); return reply::parse(*reply); } template OptionalStringPair Redis::blpop(Input first, Input last, const std::chrono::seconds &timeout) { return blpop(first, last, timeout.count()); } template 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, first, last, timeout); return reply::parse(*reply); } template 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 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, key, first, last); return reply::parse(*reply); } template 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 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, key, first, last); return reply::parse(*reply); } // HASH commands. template 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, key, first, last); return reply::parse(*reply); } template inline void Redis::hgetall(const StringView &key, Output output) { auto reply = command(cmd::hgetall, key); reply::to_array(*reply, output); } template inline void Redis::hkeys(const StringView &key, Output output) { auto reply = command(cmd::hkeys, key); reply::to_array(*reply, output); } template 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, key, first, last); reply::to_array(*reply, output); } template 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, key, first, last); reply::parse(*reply); } template 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 inline long long Redis::hscan(const StringView &key, long long cursor, const StringView &pattern, Output output) { return hscan(key, cursor, pattern, 10, output); } template inline long long Redis::hscan(const StringView &key, long long cursor, long long count, Output output) { return hscan(key, cursor, "*", count, output); } template inline long long Redis::hscan(const StringView &key, long long cursor, Output output) { return hscan(key, cursor, "*", 10, output); } template inline void Redis::hvals(const StringView &key, Output output) { auto reply = command(cmd::hvals, key); reply::to_array(*reply, output); } // SET commands. template 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, key, first, last); return reply::parse(*reply); } template void Redis::sdiff(Input first, Input last, Output output) { if (first == last) { throw Error("SDIFF: no key specified"); } auto reply = command(cmd::sdiff, first, last); reply::to_array(*reply, output); } template 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, destination, first, last); return reply::parse(*reply); } template void Redis::sinter(Input first, Input last, Output output) { if (first == last) { throw Error("SINTER: no key specified"); } auto reply = command(cmd::sinter, first, last); reply::to_array(*reply, output); } template 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, destination, first, last); return reply::parse(*reply); } template void Redis::smembers(const StringView &key, Output output) { auto reply = command(cmd::smembers, key); reply::to_array(*reply, output); } template 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 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 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, key, first, last); return reply::parse(*reply); } template 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 inline long long Redis::sscan(const StringView &key, long long cursor, const StringView &pattern, Output output) { return sscan(key, cursor, pattern, 10, output); } template inline long long Redis::sscan(const StringView &key, long long cursor, long long count, Output output) { return sscan(key, cursor, "*", count, output); } template inline long long Redis::sscan(const StringView &key, long long cursor, Output output) { return sscan(key, cursor, "*", 10, output); } template void Redis::sunion(Input first, Input last, Output output) { if (first == last) { throw Error("SUNION: no key specified"); } auto reply = command(cmd::sunion, first, last); reply::to_array(*reply, output); } template 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, destination, first, last); return reply::parse(*reply); } // SORTED SET commands. inline auto Redis::bzpopmax(const StringView &key, const std::chrono::seconds &timeout) -> Optional> { return bzpopmax(key, timeout.count()); } template auto Redis::bzpopmax(Input first, Input last, long long timeout) -> Optional> { auto reply = command(cmd::bzpopmax_range, first, last, timeout); return reply::parse>>(*reply); } template inline auto Redis::bzpopmax(Input first, Input last, const std::chrono::seconds &timeout) -> Optional> { return bzpopmax(first, last, timeout.count()); } inline auto Redis::bzpopmin(const StringView &key, const std::chrono::seconds &timeout) -> Optional> { return bzpopmin(key, timeout.count()); } template auto Redis::bzpopmin(Input first, Input last, long long timeout) -> Optional> { auto reply = command(cmd::bzpopmin_range, first, last, timeout); return reply::parse>>(*reply); } template inline auto Redis::bzpopmin(Input first, Input last, const std::chrono::seconds &timeout) -> Optional> { return bzpopmin(first, last, timeout.count()); } template 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, key, first, last, type, changed); return reply::parse(*reply); } template long long Redis::zcount(const StringView &key, const Interval &interval) { auto reply = command(cmd::zcount, key, interval); return reply::parse(*reply); } template 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, destination, first, last, type); return reply::parse(*reply); } template long long Redis::zlexcount(const StringView &key, const Interval &interval) { auto reply = command(cmd::zlexcount, key, interval); return reply::parse(*reply); } template void Redis::zpopmax(const StringView &key, long long count, Output output) { auto reply = command(cmd::zpopmax, key, count); reply::to_array(*reply, output); } template void Redis::zpopmin(const StringView &key, long long count, Output output) { auto reply = command(cmd::zpopmin, key, count); reply::to_array(*reply, output); } template void Redis::zrange(const StringView &key, long long start, long long stop, Output output) { auto reply = _score_command(cmd::zrange, key, start, stop); reply::to_array(*reply, output); } template void Redis::zrangebylex(const StringView &key, const Interval &interval, Output output) { zrangebylex(key, interval, {}, output); } template void Redis::zrangebylex(const StringView &key, const Interval &interval, const LimitOptions &opts, Output output) { auto reply = command(cmd::zrangebylex, key, interval, opts); reply::to_array(*reply, output); } template void Redis::zrangebyscore(const StringView &key, const Interval &interval, Output output) { zrangebyscore(key, interval, {}, output); } template void Redis::zrangebyscore(const StringView &key, const Interval &interval, const LimitOptions &opts, Output output) { auto reply = _score_command(cmd::zrangebyscore, key, interval, opts); reply::to_array(*reply, output); } template 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, key, first, last); return reply::parse(*reply); } template long long Redis::zremrangebylex(const StringView &key, const Interval &interval) { auto reply = command(cmd::zremrangebylex, key, interval); return reply::parse(*reply); } template long long Redis::zremrangebyscore(const StringView &key, const Interval &interval) { auto reply = command(cmd::zremrangebyscore, key, interval); return reply::parse(*reply); } template void Redis::zrevrange(const StringView &key, long long start, long long stop, Output output) { auto reply = _score_command(cmd::zrevrange, key, start, stop); reply::to_array(*reply, output); } template inline void Redis::zrevrangebylex(const StringView &key, const Interval &interval, Output output) { zrevrangebylex(key, interval, {}, output); } template void Redis::zrevrangebylex(const StringView &key, const Interval &interval, const LimitOptions &opts, Output output) { auto reply = command(cmd::zrevrangebylex, key, interval, opts); reply::to_array(*reply, output); } template void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) { zrevrangebyscore(key, interval, {}, output); } template void Redis::zrevrangebyscore(const StringView &key, const Interval &interval, const LimitOptions &opts, Output output) { auto reply = _score_command(cmd::zrevrangebyscore, key, interval, opts); reply::to_array(*reply, output); } template 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 inline long long Redis::zscan(const StringView &key, long long cursor, const StringView &pattern, Output output) { return zscan(key, cursor, pattern, 10, output); } template inline long long Redis::zscan(const StringView &key, long long cursor, long long count, Output output) { return zscan(key, cursor, "*", count, output); } template inline long long Redis::zscan(const StringView &key, long long cursor, Output output) { return zscan(key, cursor, "*", 10, output); } template 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, destination, first, last, type); return reply::parse(*reply); } // HYPERLOGLOG commands. template 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, key, first, last); return reply::parse(*reply); } template long long Redis::pfcount(Input first, Input last) { if (first == last) { throw Error("PFCOUNT: no key specified"); } auto reply = command(cmd::pfcount_range, first, last); return reply::parse(*reply); } template 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, destination, first, last); reply::parse(*reply); } // GEO commands. template 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, key, first, last); return reply::parse(*reply); } template 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, key, first, last); reply::to_array(*reply, output); } template 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, key, first, last); reply::to_array(*reply, output); } template void Redis::georadius(const StringView &key, const std::pair &loc, double radius, GeoUnit unit, long long count, bool asc, Output output) { auto reply = command(cmd::georadius, key, loc, radius, unit, count, asc, WithCoord::type>::value, WithDist::type>::value, WithHash::type>::value); reply::to_array(*reply, output); } template 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::type>::value, WithDist::type>::value, WithHash::type>::value); reply::to_array(*reply, output); } // SCRIPTING commands. template Result Redis::eval(const StringView &script, std::initializer_list keys, std::initializer_list args) { auto reply = command(cmd::eval, script, keys, args); return reply::parse(*reply); } template void Redis::eval(const StringView &script, std::initializer_list keys, std::initializer_list args, Output output) { auto reply = command(cmd::eval, script, keys, args); reply::to_array(*reply, output); } template Result Redis::evalsha(const StringView &script, std::initializer_list keys, std::initializer_list args) { auto reply = command(cmd::evalsha, script, keys, args); return reply::parse(*reply); } template void Redis::evalsha(const StringView &script, std::initializer_list keys, std::initializer_list args, Output output) { auto reply = command(cmd::evalsha, script, keys, args); reply::to_array(*reply, output); } template 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, first, last); reply::to_array(*reply, output); } // Transaction commands. template void Redis::watch(Input first, Input last) { auto reply = command(cmd::watch_range, first, last); reply::parse(*reply); } // Stream commands. template long long Redis::xack(const StringView &key, const StringView &group, Input first, Input last) { auto reply = command(cmd::xack_range, key, group, first, last); return reply::parse(*reply); } template std::string Redis::xadd(const StringView &key, const StringView &id, Input first, Input last) { auto reply = command(cmd::xadd_range, key, id, first, last); return reply::parse(*reply); } template 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, key, id, first, last, count, approx); return reply::parse(*reply); } template 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 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, key, group, consumer, min_idle_time.count(), first, last); reply::to_array(*reply, output); } template long long Redis::xdel(const StringView &key, Input first, Input last) { auto reply = command(cmd::xdel_range, key, first, last); return reply::parse(*reply); } template auto Redis::xpending(const StringView &key, const StringView &group, Output output) -> std::tuple { auto reply = command(cmd::xpending, key, group); return reply::parse_xpending_reply(*reply, output); } template 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 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 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 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 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 auto Redis::xread(Input first, Input last, long long count, Output output) -> typename std::enable_if::value>::type { if (first == last) { throw Error("XREAD: no key specified"); } auto reply = command(cmd::xread_range, first, last, count); if (!reply::is_nil(*reply)) { reply::to_array(*reply, output); } } template 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 auto Redis::xread(Input first, Input last, const std::chrono::milliseconds &timeout, long long count, Output output) -> typename std::enable_if::value>::type { if (first == last) { throw Error("XREAD: no key specified"); } auto reply = command(cmd::xread_block_range, first, last, timeout.count(), count); if (!reply::is_nil(*reply)) { reply::to_array(*reply, output); } } template 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 auto Redis::xreadgroup(const StringView &group, const StringView &consumer, Input first, Input last, long long count, bool noack, Output output) -> typename std::enable_if::value>::type { if (first == last) { throw Error("XREADGROUP: no key specified"); } auto reply = command(cmd::xreadgroup_range, group, consumer, first, last, count, noack); if (!reply::is_nil(*reply)) { reply::to_array(*reply, output); } } template 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 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::value>::type { if (first == last) { throw Error("XREADGROUP: no key specified"); } auto reply = command(cmd::xreadgroup_block_range, group, consumer, first, last, timeout.count(), count, noack); if (!reply::is_nil(*reply)) { reply::to_array(*reply, output); } } template 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 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 ReplyUPtr Redis::_command(Connection &connection, Cmd cmd, Args &&...args) { assert(!connection.broken()); cmd(connection, std::forward(args)...); auto reply = connection.recv(); return reply; } template inline ReplyUPtr Redis::_score_command(std::true_type, Cmd cmd, Args &&... args) { return command(cmd, std::forward(args)..., true); } template inline ReplyUPtr Redis::_score_command(std::false_type, Cmd cmd, Args &&... args) { return command(cmd, std::forward(args)..., false); } template inline ReplyUPtr Redis::_score_command(Cmd cmd, Args &&... args) { return _score_command(typename IsKvPairIter::type(), cmd, std::forward(args)...); } } } #endif // end SEWENEW_REDISPLUSPLUS_REDIS_HPP