1 /**************************************************************************
2    Copyright (c) 2017 sewenew
3 
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7 
8        http://www.apache.org/licenses/LICENSE-2.0
9 
10    Unless required by applicable law or agreed to in writing, software
11    distributed under the License is distributed on an "AS IS" BASIS,
12    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    See the License for the specific language governing permissions and
14    limitations under the License.
15  *************************************************************************/
16 
17 #ifndef SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP
18 #define SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP
19 
20 #include <utility>
21 #include "command.h"
22 #include "reply.h"
23 #include "utils.h"
24 #include "errors.h"
25 #include "shards_pool.h"
26 
27 namespace sw {
28 
29 namespace redis {
30 
31 template <typename Cmd, typename Key, typename ...Args>
command(Cmd cmd,Key && key,Args &&...args)32 auto RedisCluster::command(Cmd cmd, Key &&key, Args &&...args)
33     -> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value, ReplyUPtr>::type {
34     return _command(cmd,
35                     std::is_convertible<typename std::decay<Key>::type, StringView>(),
36                     std::forward<Key>(key),
37                     std::forward<Args>(args)...);
38 }
39 
40 template <typename Key, typename ...Args>
command(const StringView & cmd_name,Key && key,Args &&...args)41 auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args)
42     -> typename std::enable_if<(std::is_convertible<Key, StringView>::value
43         || std::is_arithmetic<typename std::decay<Key>::type>::value)
44         && !IsIter<typename LastType<Key, Args...>::type>::value, ReplyUPtr>::type {
45     auto cmd = Command(cmd_name);
46 
47     return _generic_command(cmd, std::forward<Key>(key), std::forward<Args>(args)...);
48 }
49 
50 template <typename Result, typename Key, typename ...Args>
command(const StringView & cmd_name,Key && key,Args &&...args)51 auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args)
52     -> typename std::enable_if<std::is_convertible<Key, StringView>::value
53             || std::is_arithmetic<typename std::decay<Key>::type>::value, Result>::type {
54     auto r = command(cmd_name, std::forward<Key>(key), std::forward<Args>(args)...);
55 
56     assert(r);
57 
58     return reply::parse<Result>(*r);
59 }
60 
61 template <typename Key, typename ...Args>
command(const StringView & cmd_name,Key && key,Args &&...args)62 auto RedisCluster::command(const StringView &cmd_name, Key &&key, Args &&...args)
63     -> typename std::enable_if<(std::is_convertible<Key, StringView>::value
64             || std::is_arithmetic<typename std::decay<Key>::type>::value)
65             && IsIter<typename LastType<Key, Args...>::type>::value, void>::type {
66     auto r = _command(cmd_name,
67                         MakeIndexSequence<sizeof...(Args)>(),
68                         std::forward<Key>(key),
69                         std::forward<Args>(args)...);
70 
71     assert(r);
72 
73     reply::to_array(*r, LastValue(std::forward<Args>(args)...));
74 }
75 
76 template <typename Input>
command(Input first,Input last)77 auto RedisCluster::command(Input first, Input last)
78     -> typename std::enable_if<IsIter<Input>::value, ReplyUPtr>::type {
79     if (first == last || std::next(first) == last) {
80         throw Error("command: invalid range");
81     }
82 
83     const auto &key = *first;
84     ++first;
85 
86     auto cmd = [&key](Connection &connection, Input first, Input last) {
87                         CmdArgs cmd_args;
88                         cmd_args.append(key);
89                         while (first != last) {
90                             cmd_args.append(*first);
91                             ++first;
92                         }
93                         connection.send(cmd_args);
94     };
95 
96     return command(cmd, first, last);
97 }
98 
99 template <typename Result, typename Input>
command(Input first,Input last)100 auto RedisCluster::command(Input first, Input last)
101     -> typename std::enable_if<IsIter<Input>::value, Result>::type {
102     auto r = command(first, last);
103 
104     assert(r);
105 
106     return reply::parse<Result>(*r);
107 }
108 
109 template <typename Input, typename Output>
command(Input first,Input last,Output output)110 auto RedisCluster::command(Input first, Input last, Output output)
111     -> typename std::enable_if<IsIter<Input>::value, void>::type {
112     auto r = command(first, last);
113 
114     assert(r);
115 
116     reply::to_array(*r, output);
117 }
118 
119 // KEY commands.
120 
121 template <typename Input>
del(Input first,Input last)122 long long RedisCluster::del(Input first, Input last) {
123     if (first == last) {
124         throw Error("DEL: no key specified");
125     }
126 
127     auto reply = command(cmd::del_range<Input>, first, last);
128 
129     return reply::parse<long long>(*reply);
130 }
131 
132 template <typename Input>
exists(Input first,Input last)133 long long RedisCluster::exists(Input first, Input last) {
134     if (first == last) {
135         throw Error("EXISTS: no key specified");
136     }
137 
138     auto reply = command(cmd::exists_range<Input>, first, last);
139 
140     return reply::parse<long long>(*reply);
141 }
142 
expire(const StringView & key,const std::chrono::seconds & timeout)143 inline bool RedisCluster::expire(const StringView &key, const std::chrono::seconds &timeout) {
144     return expire(key, timeout.count());
145 }
146 
expireat(const StringView & key,const std::chrono::time_point<std::chrono::system_clock,std::chrono::seconds> & tp)147 inline bool RedisCluster::expireat(const StringView &key,
148                                     const std::chrono::time_point<std::chrono::system_clock,
149                                                                     std::chrono::seconds> &tp) {
150     return expireat(key, tp.time_since_epoch().count());
151 }
152 
pexpire(const StringView & key,const std::chrono::milliseconds & timeout)153 inline bool RedisCluster::pexpire(const StringView &key, const std::chrono::milliseconds &timeout) {
154     return pexpire(key, timeout.count());
155 }
156 
pexpireat(const StringView & key,const std::chrono::time_point<std::chrono::system_clock,std::chrono::milliseconds> & tp)157 inline bool RedisCluster::pexpireat(const StringView &key,
158                                 const std::chrono::time_point<std::chrono::system_clock,
159                                                                 std::chrono::milliseconds> &tp) {
160     return pexpireat(key, tp.time_since_epoch().count());
161 }
162 
restore(const StringView & key,const StringView & val,const std::chrono::milliseconds & ttl,bool replace)163 inline void RedisCluster::restore(const StringView &key,
164                             const StringView &val,
165                             const std::chrono::milliseconds &ttl,
166                             bool replace) {
167     return restore(key, val, ttl.count(), replace);
168 }
169 
170 template <typename Input>
touch(Input first,Input last)171 long long RedisCluster::touch(Input first, Input last) {
172     if (first == last) {
173         throw Error("TOUCH: no key specified");
174     }
175 
176     auto reply = command(cmd::touch_range<Input>, first, last);
177 
178     return reply::parse<long long>(*reply);
179 }
180 
181 template <typename Input>
unlink(Input first,Input last)182 long long RedisCluster::unlink(Input first, Input last) {
183     if (first == last) {
184         throw Error("UNLINK: no key specified");
185     }
186 
187     auto reply = command(cmd::unlink_range<Input>, first, last);
188 
189     return reply::parse<long long>(*reply);
190 }
191 
192 // STRING commands.
193 
194 template <typename Input>
bitop(BitOp op,const StringView & destination,Input first,Input last)195 long long RedisCluster::bitop(BitOp op, const StringView &destination, Input first, Input last) {
196     if (first == last) {
197         throw Error("BITOP: no key specified");
198     }
199 
200     auto reply = _command(cmd::bitop_range<Input>, destination, op, destination, first, last);
201 
202     return reply::parse<long long>(*reply);
203 }
204 
205 template <typename Input, typename Output>
mget(Input first,Input last,Output output)206 void RedisCluster::mget(Input first, Input last, Output output) {
207     if (first == last) {
208         throw Error("MGET: no key specified");
209     }
210 
211     auto reply = command(cmd::mget<Input>, first, last);
212 
213     reply::to_array(*reply, output);
214 }
215 
216 template <typename Input>
mset(Input first,Input last)217 void RedisCluster::mset(Input first, Input last) {
218     if (first == last) {
219         throw Error("MSET: no key specified");
220     }
221 
222     auto reply = command(cmd::mset<Input>, first, last);
223 
224     reply::parse<void>(*reply);
225 }
226 
227 template <typename Input>
msetnx(Input first,Input last)228 bool RedisCluster::msetnx(Input first, Input last) {
229     if (first == last) {
230         throw Error("MSETNX: no key specified");
231     }
232 
233     auto reply = command(cmd::msetnx<Input>, first, last);
234 
235     return reply::parse<bool>(*reply);
236 }
237 
psetex(const StringView & key,const std::chrono::milliseconds & ttl,const StringView & val)238 inline void RedisCluster::psetex(const StringView &key,
239                             const std::chrono::milliseconds &ttl,
240                             const StringView &val) {
241     return psetex(key, ttl.count(), val);
242 }
243 
setex(const StringView & key,const std::chrono::seconds & ttl,const StringView & val)244 inline void RedisCluster::setex(const StringView &key,
245                             const std::chrono::seconds &ttl,
246                             const StringView &val) {
247     setex(key, ttl.count(), val);
248 }
249 
250 // LIST commands.
251 
252 template <typename Input>
blpop(Input first,Input last,long long timeout)253 OptionalStringPair RedisCluster::blpop(Input first, Input last, long long timeout) {
254     if (first == last) {
255         throw Error("BLPOP: no key specified");
256     }
257 
258     auto reply = command(cmd::blpop_range<Input>, first, last, timeout);
259 
260     return reply::parse<OptionalStringPair>(*reply);
261 }
262 
263 template <typename Input>
blpop(Input first,Input last,const std::chrono::seconds & timeout)264 OptionalStringPair RedisCluster::blpop(Input first,
265                                 Input last,
266                                 const std::chrono::seconds &timeout) {
267     return blpop(first, last, timeout.count());
268 }
269 
270 template <typename Input>
brpop(Input first,Input last,long long timeout)271 OptionalStringPair RedisCluster::brpop(Input first, Input last, long long timeout) {
272     if (first == last) {
273         throw Error("BRPOP: no key specified");
274     }
275 
276     auto reply = command(cmd::brpop_range<Input>, first, last, timeout);
277 
278     return reply::parse<OptionalStringPair>(*reply);
279 }
280 
281 template <typename Input>
brpop(Input first,Input last,const std::chrono::seconds & timeout)282 OptionalStringPair RedisCluster::brpop(Input first,
283                                 Input last,
284                                 const std::chrono::seconds &timeout) {
285     return brpop(first, last, timeout.count());
286 }
287 
brpoplpush(const StringView & source,const StringView & destination,const std::chrono::seconds & timeout)288 inline OptionalString RedisCluster::brpoplpush(const StringView &source,
289                                         const StringView &destination,
290                                         const std::chrono::seconds &timeout) {
291     return brpoplpush(source, destination, timeout.count());
292 }
293 
294 template <typename Input>
lpush(const StringView & key,Input first,Input last)295 inline long long RedisCluster::lpush(const StringView &key, Input first, Input last) {
296     if (first == last) {
297         throw Error("LPUSH: no key specified");
298     }
299 
300     auto reply = command(cmd::lpush_range<Input>, key, first, last);
301 
302     return reply::parse<long long>(*reply);
303 }
304 
305 template <typename Output>
lrange(const StringView & key,long long start,long long stop,Output output)306 inline void RedisCluster::lrange(const StringView &key, long long start, long long stop, Output output) {
307     auto reply = command(cmd::lrange, key, start, stop);
308 
309     reply::to_array(*reply, output);
310 }
311 
312 template <typename Input>
rpush(const StringView & key,Input first,Input last)313 inline long long RedisCluster::rpush(const StringView &key, Input first, Input last) {
314     if (first == last) {
315         throw Error("RPUSH: no key specified");
316     }
317 
318     auto reply = command(cmd::rpush_range<Input>, key, first, last);
319 
320     return reply::parse<long long>(*reply);
321 }
322 
323 // HASH commands.
324 
325 template <typename Input>
hdel(const StringView & key,Input first,Input last)326 inline long long RedisCluster::hdel(const StringView &key, Input first, Input last) {
327     if (first == last) {
328         throw Error("HDEL: no key specified");
329     }
330 
331     auto reply = command(cmd::hdel_range<Input>, key, first, last);
332 
333     return reply::parse<long long>(*reply);
334 }
335 
336 template <typename Output>
hgetall(const StringView & key,Output output)337 inline void RedisCluster::hgetall(const StringView &key, Output output) {
338     auto reply = command(cmd::hgetall, key);
339 
340     reply::to_array(*reply, output);
341 }
342 
343 template <typename Output>
hkeys(const StringView & key,Output output)344 inline void RedisCluster::hkeys(const StringView &key, Output output) {
345     auto reply = command(cmd::hkeys, key);
346 
347     reply::to_array(*reply, output);
348 }
349 
350 template <typename Input, typename Output>
hmget(const StringView & key,Input first,Input last,Output output)351 inline void RedisCluster::hmget(const StringView &key, Input first, Input last, Output output) {
352     if (first == last) {
353         throw Error("HMGET: no key specified");
354     }
355 
356     auto reply = command(cmd::hmget<Input>, key, first, last);
357 
358     reply::to_array(*reply, output);
359 }
360 
361 template <typename Input>
hmset(const StringView & key,Input first,Input last)362 inline void RedisCluster::hmset(const StringView &key, Input first, Input last) {
363     if (first == last) {
364         throw Error("HMSET: no key specified");
365     }
366 
367     auto reply = command(cmd::hmset<Input>, key, first, last);
368 
369     reply::parse<void>(*reply);
370 }
371 
372 template <typename Output>
hscan(const StringView & key,long long cursor,const StringView & pattern,long long count,Output output)373 long long RedisCluster::hscan(const StringView &key,
374                         long long cursor,
375                         const StringView &pattern,
376                         long long count,
377                         Output output) {
378     auto reply = command(cmd::hscan, key, cursor, pattern, count);
379 
380     return reply::parse_scan_reply(*reply, output);
381 }
382 
383 template <typename Output>
hscan(const StringView & key,long long cursor,const StringView & pattern,Output output)384 inline long long RedisCluster::hscan(const StringView &key,
385                                 long long cursor,
386                                 const StringView &pattern,
387                                 Output output) {
388     return hscan(key, cursor, pattern, 10, output);
389 }
390 
391 template <typename Output>
hscan(const StringView & key,long long cursor,long long count,Output output)392 inline long long RedisCluster::hscan(const StringView &key,
393                                 long long cursor,
394                                 long long count,
395                                 Output output) {
396     return hscan(key, cursor, "*", count, output);
397 }
398 
399 template <typename Output>
hscan(const StringView & key,long long cursor,Output output)400 inline long long RedisCluster::hscan(const StringView &key,
401                                 long long cursor,
402                                 Output output) {
403     return hscan(key, cursor, "*", 10, output);
404 }
405 
406 template <typename Output>
hvals(const StringView & key,Output output)407 inline void RedisCluster::hvals(const StringView &key, Output output) {
408     auto reply = command(cmd::hvals, key);
409 
410     reply::to_array(*reply, output);
411 }
412 
413 // SET commands.
414 
415 template <typename Input>
sadd(const StringView & key,Input first,Input last)416 long long RedisCluster::sadd(const StringView &key, Input first, Input last) {
417     if (first == last) {
418         throw Error("SADD: no key specified");
419     }
420 
421     auto reply = command(cmd::sadd_range<Input>, key, first, last);
422 
423     return reply::parse<long long>(*reply);
424 }
425 
426 template <typename Input, typename Output>
sdiff(Input first,Input last,Output output)427 void RedisCluster::sdiff(Input first, Input last, Output output) {
428     if (first == last) {
429         throw Error("SDIFF: no key specified");
430     }
431 
432     auto reply = command(cmd::sdiff<Input>, first, last);
433 
434     reply::to_array(*reply, output);
435 }
436 
437 template <typename Input>
sdiffstore(const StringView & destination,Input first,Input last)438 long long RedisCluster::sdiffstore(const StringView &destination,
439                                     Input first,
440                                     Input last) {
441     if (first == last) {
442         throw Error("SDIFFSTORE: no key specified");
443     }
444 
445     auto reply = command(cmd::sdiffstore_range<Input>, destination, first, last);
446 
447     return reply::parse<long long>(*reply);
448 }
449 
450 template <typename Input, typename Output>
sinter(Input first,Input last,Output output)451 void RedisCluster::sinter(Input first, Input last, Output output) {
452     if (first == last) {
453         throw Error("SINTER: no key specified");
454     }
455 
456     auto reply = command(cmd::sinter<Input>, first, last);
457 
458     reply::to_array(*reply, output);
459 }
460 
461 template <typename Input>
sinterstore(const StringView & destination,Input first,Input last)462 long long RedisCluster::sinterstore(const StringView &destination,
463                                     Input first,
464                                     Input last) {
465     if (first == last) {
466         throw Error("SINTERSTORE: no key specified");
467     }
468 
469     auto reply = command(cmd::sinterstore_range<Input>, destination, first, last);
470 
471     return reply::parse<long long>(*reply);
472 }
473 
474 template <typename Output>
smembers(const StringView & key,Output output)475 void RedisCluster::smembers(const StringView &key, Output output) {
476     auto reply = command(cmd::smembers, key);
477 
478     reply::to_array(*reply, output);
479 }
480 
481 template <typename Output>
spop(const StringView & key,long long count,Output output)482 void RedisCluster::spop(const StringView &key, long long count, Output output) {
483     auto reply = command(cmd::spop_range, key, count);
484 
485     reply::to_array(*reply, output);
486 }
487 
488 template <typename Output>
srandmember(const StringView & key,long long count,Output output)489 void RedisCluster::srandmember(const StringView &key, long long count, Output output) {
490     auto reply = command(cmd::srandmember_range, key, count);
491 
492     reply::to_array(*reply, output);
493 }
494 
495 template <typename Input>
srem(const StringView & key,Input first,Input last)496 long long RedisCluster::srem(const StringView &key, Input first, Input last) {
497     if (first == last) {
498         throw Error("SREM: no key specified");
499     }
500 
501     auto reply = command(cmd::srem_range<Input>, key, first, last);
502 
503     return reply::parse<long long>(*reply);
504 }
505 
506 template <typename Output>
sscan(const StringView & key,long long cursor,const StringView & pattern,long long count,Output output)507 long long RedisCluster::sscan(const StringView &key,
508                         long long cursor,
509                         const StringView &pattern,
510                         long long count,
511                         Output output) {
512     auto reply = command(cmd::sscan, key, cursor, pattern, count);
513 
514     return reply::parse_scan_reply(*reply, output);
515 }
516 
517 template <typename Output>
sscan(const StringView & key,long long cursor,const StringView & pattern,Output output)518 inline long long RedisCluster::sscan(const StringView &key,
519                                 long long cursor,
520                                 const StringView &pattern,
521                                 Output output) {
522     return sscan(key, cursor, pattern, 10, output);
523 }
524 
525 template <typename Output>
sscan(const StringView & key,long long cursor,long long count,Output output)526 inline long long RedisCluster::sscan(const StringView &key,
527                                 long long cursor,
528                                 long long count,
529                                 Output output) {
530     return sscan(key, cursor, "*", count, output);
531 }
532 
533 template <typename Output>
sscan(const StringView & key,long long cursor,Output output)534 inline long long RedisCluster::sscan(const StringView &key,
535                                 long long cursor,
536                                 Output output) {
537     return sscan(key, cursor, "*", 10, output);
538 }
539 
540 template <typename Input, typename Output>
sunion(Input first,Input last,Output output)541 void RedisCluster::sunion(Input first, Input last, Output output) {
542     if (first == last) {
543         throw Error("SUNION: no key specified");
544     }
545 
546     auto reply = command(cmd::sunion<Input>, first, last);
547 
548     reply::to_array(*reply, output);
549 }
550 
551 template <typename Input>
sunionstore(const StringView & destination,Input first,Input last)552 long long RedisCluster::sunionstore(const StringView &destination, Input first, Input last) {
553     if (first == last) {
554         throw Error("SUNIONSTORE: no key specified");
555     }
556 
557     auto reply = command(cmd::sunionstore_range<Input>, destination, first, last);
558 
559     return reply::parse<long long>(*reply);
560 }
561 
562 // SORTED SET commands.
563 
bzpopmax(const StringView & key,const std::chrono::seconds & timeout)564 inline auto RedisCluster::bzpopmax(const StringView &key, const std::chrono::seconds &timeout)
565     -> Optional<std::tuple<std::string, std::string, double>> {
566     return bzpopmax(key, timeout.count());
567 }
568 
569 template <typename Input>
bzpopmax(Input first,Input last,long long timeout)570 auto RedisCluster::bzpopmax(Input first, Input last, long long timeout)
571     -> Optional<std::tuple<std::string, std::string, double>> {
572     auto reply = command(cmd::bzpopmax_range<Input>, first, last, timeout);
573 
574     return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
575 }
576 
577 template <typename Input>
bzpopmax(Input first,Input last,const std::chrono::seconds & timeout)578 inline auto RedisCluster::bzpopmax(Input first,
579                                     Input last,
580                                     const std::chrono::seconds &timeout)
581     -> Optional<std::tuple<std::string, std::string, double>> {
582     return bzpopmax(first, last, timeout.count());
583 }
584 
bzpopmin(const StringView & key,const std::chrono::seconds & timeout)585 inline auto RedisCluster::bzpopmin(const StringView &key, const std::chrono::seconds &timeout)
586     -> Optional<std::tuple<std::string, std::string, double>> {
587     return bzpopmin(key, timeout.count());
588 }
589 
590 template <typename Input>
bzpopmin(Input first,Input last,long long timeout)591 auto RedisCluster::bzpopmin(Input first, Input last, long long timeout)
592     -> Optional<std::tuple<std::string, std::string, double>> {
593     auto reply = command(cmd::bzpopmin_range<Input>, first, last, timeout);
594 
595     return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
596 }
597 
598 template <typename Input>
bzpopmin(Input first,Input last,const std::chrono::seconds & timeout)599 inline auto RedisCluster::bzpopmin(Input first,
600                                     Input last,
601                                     const std::chrono::seconds &timeout)
602     -> Optional<std::tuple<std::string, std::string, double>> {
603     return bzpopmin(first, last, timeout.count());
604 }
605 
606 template <typename Input>
zadd(const StringView & key,Input first,Input last,UpdateType type,bool changed)607 long long RedisCluster::zadd(const StringView &key,
608                         Input first,
609                         Input last,
610                         UpdateType type,
611                         bool changed) {
612     if (first == last) {
613         throw Error("ZADD: no key specified");
614     }
615 
616     auto reply = command(cmd::zadd_range<Input>, key, first, last, type, changed);
617 
618     return reply::parse<long long>(*reply);
619 }
620 
621 template <typename Interval>
zcount(const StringView & key,const Interval & interval)622 long long RedisCluster::zcount(const StringView &key, const Interval &interval) {
623     auto reply = command(cmd::zcount<Interval>, key, interval);
624 
625     return reply::parse<long long>(*reply);
626 }
627 
628 template <typename Input>
zinterstore(const StringView & destination,Input first,Input last,Aggregation type)629 long long RedisCluster::zinterstore(const StringView &destination,
630                                 Input first,
631                                 Input last,
632                                 Aggregation type) {
633     if (first == last) {
634         throw Error("ZINTERSTORE: no key specified");
635     }
636 
637     auto reply = command(cmd::zinterstore_range<Input>,
638                             destination,
639                             first,
640                             last,
641                             type);
642 
643     return reply::parse<long long>(*reply);
644 }
645 
646 template <typename Interval>
zlexcount(const StringView & key,const Interval & interval)647 long long RedisCluster::zlexcount(const StringView &key, const Interval &interval) {
648     auto reply = command(cmd::zlexcount<Interval>, key, interval);
649 
650     return reply::parse<long long>(*reply);
651 }
652 
653 template <typename Output>
zpopmax(const StringView & key,long long count,Output output)654 void RedisCluster::zpopmax(const StringView &key, long long count, Output output) {
655     auto reply = command(cmd::zpopmax, key, count);
656 
657     reply::to_array(*reply, output);
658 }
659 
660 template <typename Output>
zpopmin(const StringView & key,long long count,Output output)661 void RedisCluster::zpopmin(const StringView &key, long long count, Output output) {
662     auto reply = command(cmd::zpopmin, key, count);
663 
664     reply::to_array(*reply, output);
665 }
666 
667 template <typename Output>
zrange(const StringView & key,long long start,long long stop,Output output)668 void RedisCluster::zrange(const StringView &key, long long start, long long stop, Output output) {
669     auto reply = _score_command<Output>(cmd::zrange, key, start, stop);
670 
671     reply::to_array(*reply, output);
672 }
673 
674 template <typename Interval, typename Output>
zrangebylex(const StringView & key,const Interval & interval,Output output)675 void RedisCluster::zrangebylex(const StringView &key, const Interval &interval, Output output) {
676     zrangebylex(key, interval, {}, output);
677 }
678 
679 template <typename Interval, typename Output>
zrangebylex(const StringView & key,const Interval & interval,const LimitOptions & opts,Output output)680 void RedisCluster::zrangebylex(const StringView &key,
681                         const Interval &interval,
682                         const LimitOptions &opts,
683                         Output output) {
684     auto reply = command(cmd::zrangebylex<Interval>, key, interval, opts);
685 
686     reply::to_array(*reply, output);
687 }
688 
689 template <typename Interval, typename Output>
zrangebyscore(const StringView & key,const Interval & interval,Output output)690 void RedisCluster::zrangebyscore(const StringView &key,
691                             const Interval &interval,
692                             Output output) {
693     zrangebyscore(key, interval, {}, output);
694 }
695 
696 template <typename Interval, typename Output>
zrangebyscore(const StringView & key,const Interval & interval,const LimitOptions & opts,Output output)697 void RedisCluster::zrangebyscore(const StringView &key,
698                             const Interval &interval,
699                             const LimitOptions &opts,
700                             Output output) {
701     auto reply = _score_command<Output>(cmd::zrangebyscore<Interval>,
702                                         key,
703                                         interval,
704                                         opts);
705 
706     reply::to_array(*reply, output);
707 }
708 
709 template <typename Input>
zrem(const StringView & key,Input first,Input last)710 long long RedisCluster::zrem(const StringView &key, Input first, Input last) {
711     if (first == last) {
712         throw Error("ZREM: no key specified");
713     }
714 
715     auto reply = command(cmd::zrem_range<Input>, key, first, last);
716 
717     return reply::parse<long long>(*reply);
718 }
719 
720 template <typename Interval>
zremrangebylex(const StringView & key,const Interval & interval)721 long long RedisCluster::zremrangebylex(const StringView &key, const Interval &interval) {
722     auto reply = command(cmd::zremrangebylex<Interval>, key, interval);
723 
724     return reply::parse<long long>(*reply);
725 }
726 
727 template <typename Interval>
zremrangebyscore(const StringView & key,const Interval & interval)728 long long RedisCluster::zremrangebyscore(const StringView &key, const Interval &interval) {
729     auto reply = command(cmd::zremrangebyscore<Interval>, key, interval);
730 
731     return reply::parse<long long>(*reply);
732 }
733 
734 template <typename Output>
zrevrange(const StringView & key,long long start,long long stop,Output output)735 void RedisCluster::zrevrange(const StringView &key, long long start, long long stop, Output output) {
736     auto reply = _score_command<Output>(cmd::zrevrange, key, start, stop);
737 
738     reply::to_array(*reply, output);
739 }
740 
741 template <typename Interval, typename Output>
zrevrangebylex(const StringView & key,const Interval & interval,Output output)742 inline void RedisCluster::zrevrangebylex(const StringView &key,
743                                     const Interval &interval,
744                                     Output output) {
745     zrevrangebylex(key, interval, {}, output);
746 }
747 
748 template <typename Interval, typename Output>
zrevrangebylex(const StringView & key,const Interval & interval,const LimitOptions & opts,Output output)749 void RedisCluster::zrevrangebylex(const StringView &key,
750                             const Interval &interval,
751                             const LimitOptions &opts,
752                             Output output) {
753     auto reply = command(cmd::zrevrangebylex<Interval>, key, interval, opts);
754 
755     reply::to_array(*reply, output);
756 }
757 
758 template <typename Interval, typename Output>
zrevrangebyscore(const StringView & key,const Interval & interval,Output output)759 void RedisCluster::zrevrangebyscore(const StringView &key, const Interval &interval, Output output) {
760     zrevrangebyscore(key, interval, {}, output);
761 }
762 
763 template <typename Interval, typename Output>
zrevrangebyscore(const StringView & key,const Interval & interval,const LimitOptions & opts,Output output)764 void RedisCluster::zrevrangebyscore(const StringView &key,
765                                 const Interval &interval,
766                                 const LimitOptions &opts,
767                                 Output output) {
768     auto reply = _score_command<Output>(cmd::zrevrangebyscore<Interval>, key, interval, opts);
769 
770     reply::to_array(*reply, output);
771 }
772 
773 template <typename Output>
zscan(const StringView & key,long long cursor,const StringView & pattern,long long count,Output output)774 long long RedisCluster::zscan(const StringView &key,
775                         long long cursor,
776                         const StringView &pattern,
777                         long long count,
778                         Output output) {
779     auto reply = command(cmd::zscan, key, cursor, pattern, count);
780 
781     return reply::parse_scan_reply(*reply, output);
782 }
783 
784 template <typename Output>
zscan(const StringView & key,long long cursor,const StringView & pattern,Output output)785 inline long long RedisCluster::zscan(const StringView &key,
786                                 long long cursor,
787                                 const StringView &pattern,
788                                 Output output) {
789     return zscan(key, cursor, pattern, 10, output);
790 }
791 
792 template <typename Output>
zscan(const StringView & key,long long cursor,long long count,Output output)793 inline long long RedisCluster::zscan(const StringView &key,
794                                 long long cursor,
795                                 long long count,
796                                 Output output) {
797     return zscan(key, cursor, "*", count, output);
798 }
799 
800 template <typename Output>
zscan(const StringView & key,long long cursor,Output output)801 inline long long RedisCluster::zscan(const StringView &key,
802                                 long long cursor,
803                                 Output output) {
804     return zscan(key, cursor, "*", 10, output);
805 }
806 
807 template <typename Input>
zunionstore(const StringView & destination,Input first,Input last,Aggregation type)808 long long RedisCluster::zunionstore(const StringView &destination,
809                                     Input first,
810                                     Input last,
811                                     Aggregation type) {
812     if (first == last) {
813         throw Error("ZUNIONSTORE: no key specified");
814     }
815 
816     auto reply = command(cmd::zunionstore_range<Input>,
817                             destination,
818                             first,
819                             last,
820                             type);
821 
822     return reply::parse<long long>(*reply);
823 }
824 
825 // HYPERLOGLOG commands.
826 
827 template <typename Input>
pfadd(const StringView & key,Input first,Input last)828 bool RedisCluster::pfadd(const StringView &key, Input first, Input last) {
829     if (first == last) {
830         throw Error("PFADD: no key specified");
831     }
832 
833     auto reply = command(cmd::pfadd_range<Input>, key, first, last);
834 
835     return reply::parse<bool>(*reply);
836 }
837 
838 template <typename Input>
pfcount(Input first,Input last)839 long long RedisCluster::pfcount(Input first, Input last) {
840     if (first == last) {
841         throw Error("PFCOUNT: no key specified");
842     }
843 
844     auto reply = command(cmd::pfcount_range<Input>, first, last);
845 
846     return reply::parse<long long>(*reply);
847 }
848 
849 template <typename Input>
pfmerge(const StringView & destination,Input first,Input last)850 void RedisCluster::pfmerge(const StringView &destination,
851                     Input first,
852                     Input last) {
853     if (first == last) {
854         throw Error("PFMERGE: no key specified");
855     }
856 
857     auto reply = command(cmd::pfmerge_range<Input>, destination, first, last);
858 
859     reply::parse<void>(*reply);
860 }
861 
862 // GEO commands.
863 
864 template <typename Input>
geoadd(const StringView & key,Input first,Input last)865 inline long long RedisCluster::geoadd(const StringView &key,
866                                 Input first,
867                                 Input last) {
868     if (first == last) {
869         throw Error("GEOADD: no key specified");
870     }
871 
872     auto reply = command(cmd::geoadd_range<Input>, key, first, last);
873 
874     return reply::parse<long long>(*reply);
875 }
876 
877 template <typename Input, typename Output>
geohash(const StringView & key,Input first,Input last,Output output)878 void RedisCluster::geohash(const StringView &key, Input first, Input last, Output output) {
879     if (first == last) {
880         throw Error("GEOHASH: no key specified");
881     }
882 
883     auto reply = command(cmd::geohash_range<Input>, key, first, last);
884 
885     reply::to_array(*reply, output);
886 }
887 
888 template <typename Input, typename Output>
geopos(const StringView & key,Input first,Input last,Output output)889 void RedisCluster::geopos(const StringView &key, Input first, Input last, Output output) {
890     if (first == last) {
891         throw Error("GEOPOS: no key specified");
892     }
893 
894     auto reply = command(cmd::geopos_range<Input>, key, first, last);
895 
896     reply::to_array(*reply, output);
897 }
898 
899 template <typename Output>
georadius(const StringView & key,const std::pair<double,double> & loc,double radius,GeoUnit unit,long long count,bool asc,Output output)900 void RedisCluster::georadius(const StringView &key,
901                         const std::pair<double, double> &loc,
902                         double radius,
903                         GeoUnit unit,
904                         long long count,
905                         bool asc,
906                         Output output) {
907     auto reply = command(cmd::georadius,
908                             key,
909                             loc,
910                             radius,
911                             unit,
912                             count,
913                             asc,
914                             WithCoord<typename IterType<Output>::type>::value,
915                             WithDist<typename IterType<Output>::type>::value,
916                             WithHash<typename IterType<Output>::type>::value);
917 
918     reply::to_array(*reply, output);
919 }
920 
921 template <typename Output>
georadiusbymember(const StringView & key,const StringView & member,double radius,GeoUnit unit,long long count,bool asc,Output output)922 void RedisCluster::georadiusbymember(const StringView &key,
923                                 const StringView &member,
924                                 double radius,
925                                 GeoUnit unit,
926                                 long long count,
927                                 bool asc,
928                                 Output output) {
929     auto reply = command(cmd::georadiusbymember,
930                             key,
931                             member,
932                             radius,
933                             unit,
934                             count,
935                             asc,
936                             WithCoord<typename IterType<Output>::type>::value,
937                             WithDist<typename IterType<Output>::type>::value,
938                             WithHash<typename IterType<Output>::type>::value);
939 
940     reply::to_array(*reply, output);
941 }
942 
943 // SCRIPTING commands.
944 
945 template <typename Result>
eval(const StringView & script,std::initializer_list<StringView> keys,std::initializer_list<StringView> args)946 Result RedisCluster::eval(const StringView &script,
947                             std::initializer_list<StringView> keys,
948                             std::initializer_list<StringView> args) {
949     if (keys.size() == 0) {
950         throw Error("DO NOT support Lua script without key");
951     }
952 
953     auto reply = _command(cmd::eval, *keys.begin(), script, keys, args);
954 
955     return reply::parse<Result>(*reply);
956 }
957 
958 template <typename Output>
eval(const StringView & script,std::initializer_list<StringView> keys,std::initializer_list<StringView> args,Output output)959 void RedisCluster::eval(const StringView &script,
960                         std::initializer_list<StringView> keys,
961                         std::initializer_list<StringView> args,
962                         Output output) {
963     if (keys.size() == 0) {
964         throw Error("DO NOT support Lua script without key");
965     }
966 
967     auto reply = _command(cmd::eval, *keys.begin(), script, keys, args);
968 
969     reply::to_array(*reply, output);
970 }
971 
972 template <typename Result>
evalsha(const StringView & script,std::initializer_list<StringView> keys,std::initializer_list<StringView> args)973 Result RedisCluster::evalsha(const StringView &script,
974                                 std::initializer_list<StringView> keys,
975                                 std::initializer_list<StringView> args) {
976     if (keys.size() == 0) {
977         throw Error("DO NOT support Lua script without key");
978     }
979 
980     auto reply = _command(cmd::evalsha, *keys.begin(), script, keys, args);
981 
982     return reply::parse<Result>(*reply);
983 }
984 
985 template <typename Output>
evalsha(const StringView & script,std::initializer_list<StringView> keys,std::initializer_list<StringView> args,Output output)986 void RedisCluster::evalsha(const StringView &script,
987                             std::initializer_list<StringView> keys,
988                             std::initializer_list<StringView> args,
989                             Output output) {
990     if (keys.size() == 0) {
991         throw Error("DO NOT support Lua script without key");
992     }
993 
994     auto reply = command(cmd::evalsha, *keys.begin(), script, keys, args);
995 
996     reply::to_array(*reply, output);
997 }
998 
999 // Stream commands.
1000 
1001 template <typename Input>
xack(const StringView & key,const StringView & group,Input first,Input last)1002 long long RedisCluster::xack(const StringView &key,
1003                                 const StringView &group,
1004                                 Input first,
1005                                 Input last) {
1006     auto reply = command(cmd::xack_range<Input>, key, group, first, last);
1007 
1008     return reply::parse<long long>(*reply);
1009 }
1010 
1011 template <typename Input>
xadd(const StringView & key,const StringView & id,Input first,Input last)1012 std::string RedisCluster::xadd(const StringView &key,
1013                                 const StringView &id,
1014                                 Input first,
1015                                 Input last) {
1016     auto reply = command(cmd::xadd_range<Input>, key, id, first, last);
1017 
1018     return reply::parse<std::string>(*reply);
1019 }
1020 
1021 template <typename Input>
xadd(const StringView & key,const StringView & id,Input first,Input last,long long count,bool approx)1022 std::string RedisCluster::xadd(const StringView &key,
1023                                 const StringView &id,
1024                                 Input first,
1025                                 Input last,
1026                                 long long count,
1027                                 bool approx) {
1028     auto reply = command(cmd::xadd_maxlen_range<Input>, key, id, first, last, count, approx);
1029 
1030     return reply::parse<std::string>(*reply);
1031 }
1032 
1033 template <typename Output>
xclaim(const StringView & key,const StringView & group,const StringView & consumer,const std::chrono::milliseconds & min_idle_time,const StringView & id,Output output)1034 void RedisCluster::xclaim(const StringView &key,
1035                             const StringView &group,
1036                             const StringView &consumer,
1037                             const std::chrono::milliseconds &min_idle_time,
1038                             const StringView &id,
1039                             Output output) {
1040     auto reply = command(cmd::xclaim, key, group, consumer, min_idle_time.count(), id);
1041 
1042     reply::to_array(*reply, output);
1043 }
1044 
1045 template <typename Input, typename Output>
xclaim(const StringView & key,const StringView & group,const StringView & consumer,const std::chrono::milliseconds & min_idle_time,Input first,Input last,Output output)1046 void RedisCluster::xclaim(const StringView &key,
1047                             const StringView &group,
1048                             const StringView &consumer,
1049                             const std::chrono::milliseconds &min_idle_time,
1050                             Input first,
1051                             Input last,
1052                             Output output) {
1053     auto reply = command(cmd::xclaim_range<Input>,
1054                             key,
1055                             group,
1056                             consumer,
1057                             min_idle_time.count(),
1058                             first,
1059                             last);
1060 
1061     reply::to_array(*reply, output);
1062 }
1063 
1064 template <typename Input>
xdel(const StringView & key,Input first,Input last)1065 long long RedisCluster::xdel(const StringView &key, Input first, Input last) {
1066     auto reply = command(cmd::xdel_range<Input>, key, first, last);
1067 
1068     return reply::parse<long long>(*reply);
1069 }
1070 
1071 template <typename Output>
xpending(const StringView & key,const StringView & group,Output output)1072 auto RedisCluster::xpending(const StringView &key, const StringView &group, Output output)
1073     -> std::tuple<long long, OptionalString, OptionalString> {
1074     auto reply = command(cmd::xpending, key, group);
1075 
1076     return reply::parse_xpending_reply(*reply, output);
1077 }
1078 
1079 template <typename Output>
xpending(const StringView & key,const StringView & group,const StringView & start,const StringView & end,long long count,Output output)1080 void RedisCluster::xpending(const StringView &key,
1081                             const StringView &group,
1082                             const StringView &start,
1083                             const StringView &end,
1084                             long long count,
1085                             Output output) {
1086     auto reply = command(cmd::xpending_detail, key, group, start, end, count);
1087 
1088     reply::to_array(*reply, output);
1089 }
1090 
1091 template <typename Output>
xpending(const StringView & key,const StringView & group,const StringView & start,const StringView & end,long long count,const StringView & consumer,Output output)1092 void RedisCluster::xpending(const StringView &key,
1093                             const StringView &group,
1094                             const StringView &start,
1095                             const StringView &end,
1096                             long long count,
1097                             const StringView &consumer,
1098                             Output output) {
1099     auto reply = command(cmd::xpending_per_consumer, key, group, start, end, count, consumer);
1100 
1101     reply::to_array(*reply, output);
1102 }
1103 
1104 template <typename Output>
xrange(const StringView & key,const StringView & start,const StringView & end,Output output)1105 void RedisCluster::xrange(const StringView &key,
1106                             const StringView &start,
1107                             const StringView &end,
1108                             Output output) {
1109     auto reply = command(cmd::xrange, key, start, end);
1110 
1111     reply::to_array(*reply, output);
1112 }
1113 
1114 template <typename Output>
xrange(const StringView & key,const StringView & start,const StringView & end,long long count,Output output)1115 void RedisCluster::xrange(const StringView &key,
1116                             const StringView &start,
1117                             const StringView &end,
1118                             long long count,
1119                             Output output) {
1120     auto reply = command(cmd::xrange_count, key, start, end, count);
1121 
1122     reply::to_array(*reply, output);
1123 }
1124 
1125 template <typename Output>
xread(const StringView & key,const StringView & id,long long count,Output output)1126 void RedisCluster::xread(const StringView &key,
1127                             const StringView &id,
1128                             long long count,
1129                             Output output) {
1130     auto reply = command(cmd::xread, key, id, count);
1131 
1132     if (!reply::is_nil(*reply)) {
1133         reply::to_array(*reply, output);
1134     }
1135 }
1136 
1137 template <typename Input, typename Output>
xread(Input first,Input last,long long count,Output output)1138 auto RedisCluster::xread(Input first, Input last, long long count, Output output)
1139     -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
1140     if (first == last) {
1141         throw Error("XREAD: no key specified");
1142     }
1143 
1144     auto reply = command(cmd::xread_range<Input>, first, last, count);
1145 
1146     if (!reply::is_nil(*reply)) {
1147         reply::to_array(*reply, output);
1148     }
1149 }
1150 
1151 template <typename Output>
xread(const StringView & key,const StringView & id,const std::chrono::milliseconds & timeout,long long count,Output output)1152 void RedisCluster::xread(const StringView &key,
1153                             const StringView &id,
1154                             const std::chrono::milliseconds &timeout,
1155                             long long count,
1156                             Output output) {
1157     auto reply = command(cmd::xread_block, key, id, timeout.count(), count);
1158 
1159     if (!reply::is_nil(*reply)) {
1160         reply::to_array(*reply, output);
1161     }
1162 }
1163 
1164 template <typename Input, typename Output>
xread(Input first,Input last,const std::chrono::milliseconds & timeout,long long count,Output output)1165 auto RedisCluster::xread(Input first,
1166                             Input last,
1167                             const std::chrono::milliseconds &timeout,
1168                             long long count,
1169                             Output output)
1170     -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
1171     if (first == last) {
1172         throw Error("XREAD: no key specified");
1173     }
1174 
1175     auto reply = command(cmd::xread_block_range<Input>, first, last, timeout.count(), count);
1176 
1177     if (!reply::is_nil(*reply)) {
1178         reply::to_array(*reply, output);
1179     }
1180 }
1181 
1182 template <typename Output>
xreadgroup(const StringView & group,const StringView & consumer,const StringView & key,const StringView & id,long long count,bool noack,Output output)1183 void RedisCluster::xreadgroup(const StringView &group,
1184                                 const StringView &consumer,
1185                                 const StringView &key,
1186                                 const StringView &id,
1187                                 long long count,
1188                                 bool noack,
1189                                 Output output) {
1190     auto reply = _command(cmd::xreadgroup, key, group, consumer, key, id, count, noack);
1191 
1192     if (!reply::is_nil(*reply)) {
1193         reply::to_array(*reply, output);
1194     }
1195 }
1196 
1197 template <typename Input, typename Output>
xreadgroup(const StringView & group,const StringView & consumer,Input first,Input last,long long count,bool noack,Output output)1198 auto RedisCluster::xreadgroup(const StringView &group,
1199                                 const StringView &consumer,
1200                                 Input first,
1201                                 Input last,
1202                                 long long count,
1203                                 bool noack,
1204                                 Output output)
1205     -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
1206     if (first == last) {
1207         throw Error("XREADGROUP: no key specified");
1208     }
1209 
1210     auto reply = _command(cmd::xreadgroup_range<Input>,
1211                             first->first,
1212                             group,
1213                             consumer,
1214                             first,
1215                             last,
1216                             count,
1217                             noack);
1218 
1219     if (!reply::is_nil(*reply)) {
1220         reply::to_array(*reply, output);
1221     }
1222 }
1223 
1224 template <typename Output>
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)1225 void RedisCluster::xreadgroup(const StringView &group,
1226                                 const StringView &consumer,
1227                                 const StringView &key,
1228                                 const StringView &id,
1229                                 const std::chrono::milliseconds &timeout,
1230                                 long long count,
1231                                 bool noack,
1232                                 Output output) {
1233     auto reply = _command(cmd::xreadgroup_block,
1234                             key,
1235                             group,
1236                             consumer,
1237                             key,
1238                             id,
1239                             timeout.count(),
1240                             count,
1241                             noack);
1242 
1243     if (!reply::is_nil(*reply)) {
1244         reply::to_array(*reply, output);
1245     }
1246 }
1247 
1248 template <typename Input, typename Output>
xreadgroup(const StringView & group,const StringView & consumer,Input first,Input last,const std::chrono::milliseconds & timeout,long long count,bool noack,Output output)1249 auto RedisCluster::xreadgroup(const StringView &group,
1250                                 const StringView &consumer,
1251                                 Input first,
1252                                 Input last,
1253                                 const std::chrono::milliseconds &timeout,
1254                                 long long count,
1255                                 bool noack,
1256                                 Output output)
1257     -> typename std::enable_if<!std::is_convertible<Input, StringView>::value>::type {
1258     if (first == last) {
1259         throw Error("XREADGROUP: no key specified");
1260     }
1261 
1262     auto reply = _command(cmd::xreadgroup_block_range<Input>,
1263                             first->first,
1264                             group,
1265                             consumer,
1266                             first,
1267                             last,
1268                             timeout.count(),
1269                             count,
1270                             noack);
1271 
1272     if (!reply::is_nil(*reply)) {
1273         reply::to_array(*reply, output);
1274     }
1275 }
1276 
1277 template <typename Output>
xrevrange(const StringView & key,const StringView & end,const StringView & start,Output output)1278 void RedisCluster::xrevrange(const StringView &key,
1279                             const StringView &end,
1280                             const StringView &start,
1281                             Output output) {
1282     auto reply = command(cmd::xrevrange, key, end, start);
1283 
1284     reply::to_array(*reply, output);
1285 }
1286 
1287 template <typename Output>
xrevrange(const StringView & key,const StringView & end,const StringView & start,long long count,Output output)1288 void RedisCluster::xrevrange(const StringView &key,
1289                                 const StringView &end,
1290                                 const StringView &start,
1291                                 long long count,
1292                                 Output output) {
1293     auto reply = command(cmd::xrevrange_count, key, end, start, count);
1294 
1295     reply::to_array(*reply, output);
1296 }
1297 
1298 template <typename Cmd, typename Key, typename ...Args>
_generic_command(Cmd cmd,Key && key,Args &&...args)1299 auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args)
1300     -> typename std::enable_if<std::is_convertible<Key, StringView>::value,
1301                                 ReplyUPtr>::type {
1302     return command(cmd, std::forward<Key>(key), std::forward<Args>(args)...);
1303 }
1304 
1305 template <typename Cmd, typename Key, typename ...Args>
_generic_command(Cmd cmd,Key && key,Args &&...args)1306 auto RedisCluster::_generic_command(Cmd cmd, Key &&key, Args &&...args)
1307     -> typename std::enable_if<std::is_arithmetic<typename std::decay<Key>::type>::value,
1308                                 ReplyUPtr>::type {
1309     auto k = std::to_string(std::forward<Key>(key));
1310     return command(cmd, k, std::forward<Args>(args)...);
1311 }
1312 
1313 template <typename Cmd, typename ...Args>
_command(Cmd cmd,std::true_type,const StringView & key,Args &&...args)1314 ReplyUPtr RedisCluster::_command(Cmd cmd, std::true_type, const StringView &key, Args &&...args) {
1315     return _command(cmd, key, key, std::forward<Args>(args)...);
1316 }
1317 
1318 template <typename Cmd, typename Input, typename ...Args>
_command(Cmd cmd,std::false_type,Input && first,Args &&...args)1319 ReplyUPtr RedisCluster::_command(Cmd cmd, std::false_type, Input &&first, Args &&...args) {
1320     return _range_command(cmd,
1321                             std::is_convertible<
1322                                 typename std::decay<
1323                                     decltype(*std::declval<Input>())>::type, StringView>(),
1324                             std::forward<Input>(first),
1325                             std::forward<Args>(args)...);
1326 }
1327 
1328 template <typename Cmd, typename Input, typename ...Args>
_range_command(Cmd cmd,std::true_type,Input input,Args &&...args)1329 ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::true_type, Input input, Args &&...args) {
1330     return _command(cmd, *input, input, std::forward<Args>(args)...);
1331 }
1332 
1333 template <typename Cmd, typename Input, typename ...Args>
_range_command(Cmd cmd,std::false_type,Input input,Args &&...args)1334 ReplyUPtr RedisCluster::_range_command(Cmd cmd, std::false_type, Input input, Args &&...args) {
1335     return _command(cmd, std::get<0>(*input), input, std::forward<Args>(args)...);
1336 }
1337 
1338 template <typename Cmd, typename ...Args>
_command(Cmd cmd,Connection & connection,Args &&...args)1339 ReplyUPtr RedisCluster::_command(Cmd cmd, Connection &connection, Args &&...args) {
1340     assert(!connection.broken());
1341 
1342     cmd(connection, std::forward<Args>(args)...);
1343 
1344     return connection.recv();
1345 }
1346 
1347 template <typename Cmd, typename ...Args>
_command(Cmd cmd,const StringView & key,Args &&...args)1348 ReplyUPtr RedisCluster::_command(Cmd cmd, const StringView &key, Args &&...args) {
1349     for (auto idx = 0; idx < 2; ++idx) {
1350         try {
1351             auto guarded_connection = _pool.fetch(key);
1352 
1353             return _command(cmd, guarded_connection.connection(), std::forward<Args>(args)...);
1354         } catch (const IoError &err) {
1355             // When master is down, one of its replicas will be promoted to be the new master.
1356             // If we try to send command to the old master, we'll get an *IoError*.
1357             // In this case, we need to update the slots mapping.
1358             _pool.update();
1359         } catch (const ClosedError &err) {
1360             // Node might be removed.
1361             // 1. Get up-to-date slot mapping to check if the node still exists.
1362             _pool.update();
1363 
1364             // TODO:
1365             // 2. If it's NOT exist, update slot mapping, and retry.
1366             // 3. If it's still exist, that means the node is down, NOT removed, throw exception.
1367         } catch (const MovedError &err) {
1368             // Slot mapping has been changed, update it and try again.
1369             _pool.update();
1370         } catch (const AskError &err) {
1371             auto guarded_connection = _pool.fetch(err.node());
1372             auto &connection = guarded_connection.connection();
1373 
1374             // 1. send ASKING command.
1375             _asking(connection);
1376 
1377             // 2. resend last command.
1378             try {
1379                 return _command(cmd, connection, std::forward<Args>(args)...);
1380             } catch (const MovedError &err) {
1381                 throw Error("Slot migrating... ASKING node hasn't been set to IMPORTING state");
1382             }
1383         } // For other exceptions, just throw it.
1384     }
1385 
1386     // Possible failures:
1387     // 1. Source node has already run 'CLUSTER SETSLOT xxx NODE xxx',
1388     //    while the destination node has NOT run it.
1389     //    In this case, client will be redirected by both nodes with MovedError.
1390     // 2. Other failures...
1391     throw Error("Failed to send command with key: " + std::string(key.data(), key.size()));
1392 }
1393 
1394 template <typename Cmd, typename ...Args>
_score_command(std::true_type,Cmd cmd,Args &&...args)1395 inline ReplyUPtr RedisCluster::_score_command(std::true_type, Cmd cmd, Args &&... args) {
1396     return command(cmd, std::forward<Args>(args)..., true);
1397 }
1398 
1399 template <typename Cmd, typename ...Args>
_score_command(std::false_type,Cmd cmd,Args &&...args)1400 inline ReplyUPtr RedisCluster::_score_command(std::false_type, Cmd cmd, Args &&... args) {
1401     return command(cmd, std::forward<Args>(args)..., false);
1402 }
1403 
1404 template <typename Output, typename Cmd, typename ...Args>
_score_command(Cmd cmd,Args &&...args)1405 inline ReplyUPtr RedisCluster::_score_command(Cmd cmd, Args &&... args) {
1406     return _score_command(typename IsKvPairIter<Output>::type(),
1407                             cmd,
1408                             std::forward<Args>(args)...);
1409 }
1410 
1411 }
1412 
1413 }
1414 
1415 #endif // end SEWENEW_REDISPLUSPLUS_REDIS_CLUSTER_HPP
1416