1 /*
2  * Hunt - A redis client library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.redis.Redis;
13 
14 import hunt.redis.BinaryRedis;
15 import hunt.redis.BinaryRedisPubSub;
16 import hunt.redis.BitOP;
17 import hunt.redis.BitPosParams;
18 import hunt.redis.BuilderFactory;
19 import hunt.redis.Client;
20 import hunt.redis.ClusterReset;
21 import hunt.redis.GeoCoordinate;
22 import hunt.redis.GeoRadiusResponse;
23 import hunt.redis.GeoUnit;
24 import hunt.redis.HostAndPort;
25 import hunt.redis.ListPosition;
26 import hunt.redis.Module;
27 import hunt.redis.Pipeline;
28 import hunt.redis.Protocol;
29 import hunt.redis.RedisMonitor;
30 import hunt.redis.RedisPubSub;
31 import hunt.redis.RedisShardInfo;
32 import hunt.redis.ScanParams;
33 import hunt.redis.ScanResult;
34 import hunt.redis.SortingParams;
35 import hunt.redis.StreamEntry;
36 import hunt.redis.StreamEntryID;
37 import hunt.redis.StreamPendingEntry;
38 import hunt.redis.Transaction;
39 import hunt.redis.Tuple;
40 import hunt.redis.ZParams;
41 
42 import hunt.redis.commands.AdvancedRedisCommands;
43 import hunt.redis.commands.BasicCommands;
44 import hunt.redis.commands.ClusterCommands;
45 import hunt.redis.commands.RedisCommands;
46 import hunt.redis.commands.ModuleCommands;
47 import hunt.redis.commands.MultiKeyCommands;
48 import hunt.redis.Protocol;
49 import hunt.redis.commands.ScriptingCommands;
50 import hunt.redis.commands.SentinelCommands;
51 import hunt.redis.params.ClientKillParams;
52 import hunt.redis.params.GeoRadiusParam;
53 import hunt.redis.params.MigrateParams;
54 import hunt.redis.params.SetParams;
55 import hunt.redis.params.ZAddParams;
56 import hunt.redis.params.ZIncrByParams;
57 import hunt.redis.util.SafeEncoder;
58 import hunt.redis.util.Slowlog;
59 
60 import hunt.Byte;
61 import hunt.collection;
62 import hunt.Double;
63 import hunt.Long;
64 import hunt.Exceptions;
65 import hunt.logging.ConsoleLogger;
66 import hunt.net.util.HttpURI;
67 
68 import std.conv;
69 
70 
71 /**
72  * 
73  */
74 class Redis : BinaryRedis, RedisCommands, MultiKeyCommands,
75         AdvancedRedisCommands, ScriptingCommands, BasicCommands, 
76         ClusterCommands, SentinelCommands, ModuleCommands {
77 
78     // protected RedisPoolAbstract dataSource = null;
79 
80     this() {
81         super();
82     }
83 
84     this(string host) {
85         super(host);
86     }
87 
88     this(HostAndPort hp) {
89         super(hp);
90     }
91 
92     this(string host, int port) {
93         super(host, port);
94     }
95 
96     this(string host, int port, bool ssl) {
97         super(host, port, ssl);
98     }
99 
100     // this(string host, int port, bool ssl,
101     //     SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
102     //     HostnameVerifier hostnameVerifier) {
103     //   super(host, port, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
104     // }
105 
106     this(string host, int port, int timeout) {
107         super(host, port, timeout);
108     }
109 
110     this(string host, int port, int timeout, bool ssl) {
111         super(host, port, timeout, ssl);
112     }
113 
114     // this(string host, int port, int timeout, bool ssl,
115     //     SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
116     //     HostnameVerifier hostnameVerifier) {
117     //   super(host, port, timeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
118     // }
119 
120     this(string host, int port, int connectionTimeout, int soTimeout) {
121         super(host, port, connectionTimeout, soTimeout);
122     }
123 
124     this(string host, int port, int connectionTimeout, int soTimeout,
125             bool ssl) {
126         super(host, port, connectionTimeout, soTimeout, ssl);
127     }
128 
129     // this(string host, int port, int connectionTimeout, int soTimeout,
130     //     bool ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
131     //     HostnameVerifier hostnameVerifier) {
132     //   super(host, port, connectionTimeout, soTimeout, ssl, sslSocketFactory, sslParameters,
133     //       hostnameVerifier);
134     // }
135 
136     this(RedisShardInfo shardInfo) {
137         super(shardInfo);
138     }
139 
140     this(HttpURI uri) {
141         super(uri);
142     }
143 
144     // this(HttpURI uri, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
145     //     HostnameVerifier hostnameVerifier) {
146     //   super(uri, sslSocketFactory, sslParameters, hostnameVerifier);
147     // }
148 
149     this(HttpURI uri, int timeout) {
150         super(uri, timeout);
151     }
152 
153     // this(HttpURI uri, int timeout, SSLSocketFactory sslSocketFactory,
154     //     SSLParameters sslParameters, HostnameVerifier hostnameVerifier) {
155     //   super(uri, timeout, sslSocketFactory, sslParameters, hostnameVerifier);
156     // }
157 
158     this(HttpURI uri, int connectionTimeout, int soTimeout) {
159         super(uri, connectionTimeout, soTimeout);
160     }
161 
162     // this(HttpURI uri, int connectionTimeout, int soTimeout,
163     //     SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
164     //     HostnameVerifier hostnameVerifier) {
165     //   super(uri, connectionTimeout, soTimeout, sslSocketFactory, sslParameters, hostnameVerifier);
166     // }
167 
168     /**
169      * Works same as <tt>ping()</tt> but returns argument message instead of <tt>PONG</tt>.
170      * @param message
171      * @return message
172      */
173     string ping(string message) {
174         checkIsInMultiOrPipeline();
175         client.ping(message);
176         return client.getBulkReply();
177     }
178     alias ping = BinaryRedis.ping;
179 
180 
181     /**
182      * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
183      * GB).
184      * <p>
185      * Time complexity: O(1)
186      * @param key
187      * @param value
188      * @return Status code reply
189      */
190     string set(string key, string value) {
191         checkIsInMultiOrPipeline();
192         client.set(key, value);
193         return client.getStatusCodeReply();
194     }
195     alias set = BinaryRedis.set;
196 
197     /**
198      * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
199      * GB).
200      * @param key
201      * @param value
202      * @param params NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the
203      *          key if it already exist. EX|PX, expire time units: EX = seconds; PX = milliseconds
204      * @return Status code reply
205      */
206     string set(string key, string value, SetParams params) {
207         checkIsInMultiOrPipeline();
208         client.set(key, value, params);
209         return client.getStatusCodeReply();
210     }
211 
212     /**
213      * Get the value of the specified key. If the key does not exist null is returned. If the value
214      * stored at key is not a string an error is returned because GET can only handle string values.
215      * <p>
216      * Time complexity: O(1)
217      * @param key
218      * @return Bulk reply
219      */
220     string get(string key) {
221         checkIsInMultiOrPipeline();
222         client.get(key);
223         return client.getBulkReply();
224     }
225     alias get = BinaryRedis.get;
226 
227     /**
228      * Test if the specified keys exist. The command returns the number of keys exist.
229      * Time complexity: O(N)
230      * @param keys
231      * @return Integer reply, specifically: an integer greater than 0 if one or more keys exist,
232      *         0 if none of the specified keys exist.
233      */
234     Long exists(string[] keys...) {
235         checkIsInMultiOrPipeline();
236         client.exists(keys);
237         return client.getIntegerReply();
238     }
239     alias exists = BinaryRedis.exists;
240 
241     /**
242      * Test if the specified key exists. The command returns true if the key exists, otherwise false is
243      * returned. Note that even keys set with an empty string as value will return true. Time
244      * complexity: O(1)
245      * @param key
246      * @return bool reply, true if the key exists, otherwise false
247      */
248     // override
249     bool exists(string key) {
250         checkIsInMultiOrPipeline();
251         client.exists(key);
252         return client.getIntegerReply() == 1;
253     }
254 
255     /**
256      * Remove the specified keys. If a given key does not exist no operation is performed for this
257      * key. The command returns the number of keys removed. Time complexity: O(1)
258      * @param keys
259      * @return Integer reply, specifically: an integer greater than 0 if one or more keys were removed
260      *         0 if none of the specified key existed
261      */
262     Long del(string[] keys...) {
263         checkIsInMultiOrPipeline();
264         client.del(keys);
265         return client.getIntegerReply();
266     }
267     alias del = BinaryRedis.del;
268 
269     Long del(string key) {
270         checkIsInMultiOrPipeline();
271         client.del(key);
272         return client.getIntegerReply();
273     }
274 
275     /**
276      * This command is very similar to DEL: it removes the specified keys. Just like DEL a key is
277      * ignored if it does not exist. However the command performs the actual memory reclaiming in a
278      * different thread, so it is not blocking, while DEL is. This is where the command name comes
279      * from: the command just unlinks the keys from the keyspace. The actual removal will happen later
280      * asynchronously.
281      * <p>
282      * Time complexity: O(1) for each key removed regardless of its size. Then the command does O(N)
283      * work in a different thread in order to reclaim memory, where N is the number of allocations the
284      * deleted objects where composed of.
285      * @param keys
286      * @return Integer reply: The number of keys that were unlinked
287      */
288     Long unlink(string[] keys...) {
289         checkIsInMultiOrPipeline();
290         client.unlink(keys);
291         return client.getIntegerReply();
292     }
293     alias unlink = BinaryRedis.unlink;
294 
295     Long unlink(string key) {
296         client.unlink(key);
297         return client.getIntegerReply();
298     }
299 
300     /**
301      * Return the type of the value stored at key in form of a string. The type can be one of "none",
302      * "string", "list", "set". "none" is returned if the key does not exist. Time complexity: O(1)
303      * @param key
304      * @return Status code reply, specifically: "none" if the key does not exist "string" if the key
305      *         contains a string value "list" if the key contains a List value "set" if the key
306      *         contains a Set value "zset" if the key contains a Sorted Set value "hash" if the key
307      *         contains a Hash value
308      */
309     string type(string key) {
310         checkIsInMultiOrPipeline();
311         client.type(key);
312         return client.getStatusCodeReply();
313     }
314     alias type = BinaryRedis.type;
315 
316     Set!(string) keys(string pattern) {
317         checkIsInMultiOrPipeline();
318         client.keys(pattern);
319         return BuilderFactory.STRING_SET.build(cast(Object)client.getBinaryMultiBulkReply());
320     }
321     alias keys = BinaryRedis.keys;
322 
323     /**
324      * Return a randomly selected key from the currently selected DB.
325      * <p>
326      * Time complexity: O(1)
327      * @return Singe line reply, specifically the randomly selected key or an empty string is the
328      *         database is empty
329      */
330     string randomKey() {
331         checkIsInMultiOrPipeline();
332         client.randomKey();
333         return client.getBulkReply();
334     }
335 
336     /**
337      * Atomically renames the key oldkey to newkey. If the source and destination name are the same an
338      * error is returned. If newkey already exists it is overwritten.
339      * <p>
340      * Time complexity: O(1)
341      * @param oldkey
342      * @param newkey
343      * @return Status code repy
344      */
345     string rename(string oldkey, string newkey) {
346         checkIsInMultiOrPipeline();
347         client.rename(oldkey, newkey);
348         return client.getStatusCodeReply();
349     }
350     alias rename = BinaryRedis.rename;
351 
352     /**
353      * Rename oldkey into newkey but fails if the destination key newkey already exists.
354      * <p>
355      * Time complexity: O(1)
356      * @param oldkey
357      * @param newkey
358      * @return Integer reply, specifically: 1 if the key was renamed 0 if the target key already exist
359      */
360     // override
361     Long renamenx(string oldkey, string newkey) {
362         checkIsInMultiOrPipeline();
363         client.renamenx(oldkey, newkey);
364         return client.getIntegerReply();
365     }
366 
367     /**
368      * Set a timeout on the specified key. After the timeout the key will be automatically deleted by
369      * the server. A key with an associated timeout is said to be volatile in Redis terminology.
370      * <p>
371      * Volatile keys are stored on disk like the other keys, the timeout is persistent too like all the
372      * other aspects of the dataset. Saving a dataset containing expires and stopping the server does
373      * not stop the flow of time as Redis stores on disk the time when the key will no longer be
374      * available as Unix time, and not the remaining seconds.
375      * <p>
376      * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire
377      * set. It is also possible to undo the expire at all turning the key into a normal key using the
378      * {@link #persist(string) PERSIST} command.
379      * <p>
380      * Time complexity: O(1)
381      * @see <a href="http://redis.io/commands/expire">Expire Command</a>
382      * @param key
383      * @param seconds
384      * @return Integer reply, specifically: 1: the timeout was set. 0: the timeout was not set since
385      *         the key already has an associated timeout (this may happen only in Redis versions &lt;
386      *         2.1.3, Redis &gt;= 2.1.3 will happily update the timeout), or the key does not exist.
387      */
388     Long expire(string key, int seconds) {
389         checkIsInMultiOrPipeline();
390         client.expire(key, seconds);
391         return client.getIntegerReply();
392     }
393     alias expire = BinaryRedis.expire;
394 
395     /**
396      * EXPIREAT works exactly like {@link #expire(string, int) EXPIRE} but instead to get the number of
397      * seconds representing the Time To Live of the key as a second argument (that is a relative way
398      * of specifying the TTL), it takes an absolute one in the form of a UNIX timestamp (Number of
399      * seconds elapsed since 1 Gen 1970).
400      * <p>
401      * EXPIREAT was introduced in order to implement the Append Only File persistence mode so that
402      * EXPIRE commands are automatically translated into EXPIREAT commands for the append only file.
403      * Of course EXPIREAT can also used by programmers that need a way to simply specify that a given
404      * key should expire at a given time in the future.
405      * <p>
406      * Since Redis 2.1.3 you can update the value of the timeout of a key already having an expire
407      * set. It is also possible to undo the expire at all turning the key into a normal key using the
408      * {@link #persist(string) PERSIST} command.
409      * <p>
410      * Time complexity: O(1)
411      * @see <a href="http://redis.io/commands/expire">Expire Command</a>
412      * @param key
413      * @param unixTime
414      * @return Integer reply, specifically: 1: the timeout was set. 0: the timeout was not set since
415      *         the key already has an associated timeout (this may happen only in Redis versions &lt;
416      *         2.1.3, Redis &gt;= 2.1.3 will happily update the timeout), or the key does not exist.
417      */
418     Long expireAt(string key, long unixTime) {
419         checkIsInMultiOrPipeline();
420         client.expireAt(key, unixTime);
421         return client.getIntegerReply();
422     }
423     alias expireAt = BinaryRedis.expireAt;
424 
425     /**
426      * The TTL command returns the remaining time to live in seconds of a key that has an
427      * {@link #expire(string, int) EXPIRE} set. This introspection capability allows a Redis client to
428      * check how many seconds a given key will continue to be part of the dataset.
429      * @param key
430      * @return Integer reply, returns the remaining time to live in seconds of a key that has an
431      *         EXPIRE. In Redis 2.6 or older, if the Key does not exists or does not have an
432      *         associated expire, -1 is returned. In Redis 2.8 or newer, if the Key does not have an
433      *         associated expire, -1 is returned or if the Key does not exists, -2 is returned.
434      */
435     Long ttl(string key) {
436         checkIsInMultiOrPipeline();
437         client.ttl(key);
438         return client.getIntegerReply();
439     }
440     alias ttl = BinaryRedis.ttl;
441 
442     /**
443      * Alters the last access time of a key(s). A key is ignored if it does not exist.
444      * Time complexity: O(N) where N is the number of keys that will be touched.
445      * @param keys
446      * @return Integer reply: The number of keys that were touched.
447      */
448     Long touch(string[] keys...) {
449         checkIsInMultiOrPipeline();
450         client.touch(keys);
451         return client.getIntegerReply();
452     }
453     alias touch = BinaryRedis.touch;
454 
455     Long touch(string key) {
456         checkIsInMultiOrPipeline();
457         client.touch(key);
458         return client.getIntegerReply();
459     }
460     alias touch = BinaryRedis.touch;
461 
462     /**
463      * Move the specified key from the currently selected DB to the specified destination DB. Note
464      * that this command returns 1 only if the key was successfully moved, and 0 if the target key was
465      * already there or if the source key was not found at all, so it is possible to use MOVE as a
466      * locking primitive.
467      * @param key
468      * @param dbIndex
469      * @return Integer reply, specifically: 1 if the key was moved 0 if the key was not moved because
470      *         already present on the target DB or was not found in the current DB.
471      */
472     Long move(string key, int dbIndex) {
473         checkIsInMultiOrPipeline();
474         client.move(key, dbIndex);
475         return client.getIntegerReply();
476     }
477     alias move = BinaryRedis.move;
478 
479     /**
480      * GETSET is an atomic set this value and return the old value command. Set key to the string
481      * value and return the old value stored at key. The string can't be longer than 1073741824 bytes
482      * (1 GB).
483      * <p>
484      * Time complexity: O(1)
485      * @param key
486      * @param value
487      * @return Bulk reply
488      */
489     string getSet(string key, string value) {
490         checkIsInMultiOrPipeline();
491         client.getSet(key, value);
492         return client.getBulkReply();
493     }
494     alias getSet = BinaryRedis.getSet;
495 
496     /**
497      * Get the values of all the specified keys. If one or more keys don't exist or is not of type
498      * string, a 'nil' value is returned instead of the value of the specified key, but the operation
499      * never fails.
500      * <p>
501      * Time complexity: O(1) for every key
502      * @param keys
503      * @return Multi bulk reply
504      */
505     List!(string) mget(string[] keys...) {
506         checkIsInMultiOrPipeline();
507         client.mget(keys);
508         return client.getMultiBulkReply();
509     }
510     alias mget = BinaryRedis.mget;
511 
512     /**
513      * SETNX works exactly like {@link #set(string, string) SET} with the only difference that if the
514      * key already exists no operation is performed. SETNX actually means "SET if Not eXists".
515      * <p>
516      * Time complexity: O(1)
517      * @param key
518      * @param value
519      * @return Integer reply, specifically: 1 if the key was set 0 if the key was not set
520      */
521     Long setnx(string key, string value) {
522         checkIsInMultiOrPipeline();
523         client.setnx(key, value);
524         return client.getIntegerReply();
525     }
526     alias setnx = BinaryRedis.setnx;
527 
528     /**
529      * The command is exactly equivalent to the following group of commands:
530      * {@link #set(string, string) SET} + {@link #expire(string, int) EXPIRE}. The operation is
531      * atomic.
532      * <p>
533      * Time complexity: O(1)
534      * @param key
535      * @param seconds
536      * @param value
537      * @return Status code reply
538      */
539     string setex(string key, int seconds, string value) {
540         checkIsInMultiOrPipeline();
541         client.setex(key, seconds, value);
542         return client.getStatusCodeReply();
543     }
544     alias setex = BinaryRedis.setex;
545 
546     /**
547      * Set the the respective keys to the respective values. MSET will replace old values with new
548      * values, while {@link #msetnx(string...) MSETNX} will not perform any operation at all even if
549      * just a single key already exists.
550      * <p>
551      * Because of this semantic MSETNX can be used in order to set different keys representing
552      * different fields of an unique logic object in a way that ensures that either all the fields or
553      * none at all are set.
554      * <p>
555      * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B
556      * are modified, another client talking to Redis can either see the changes to both A and B at
557      * once, or no modification at all.
558      * @see #msetnx(string...)
559      * @param keysvalues
560      * @return Status code reply Basically +OK as MSET can't fail
561      */
562     string mset(string[] keysvalues...) {
563         checkIsInMultiOrPipeline();
564         client.mset(keysvalues);
565         return client.getStatusCodeReply();
566     }
567     alias mset = BinaryRedis.mset;
568 
569     /**
570      * Set the the respective keys to the respective values. {@link #mset(string...) MSET} will
571      * replace old values with new values, while MSETNX will not perform any operation at all even if
572      * just a single key already exists.
573      * <p>
574      * Because of this semantic MSETNX can be used in order to set different keys representing
575      * different fields of an unique logic object in a way that ensures that either all the fields or
576      * none at all are set.
577      * <p>
578      * Both MSET and MSETNX are atomic operations. This means that for instance if the keys A and B
579      * are modified, another client talking to Redis can either see the changes to both A and B at
580      * once, or no modification at all.
581      * @see #mset(string...)
582      * @param keysvalues
583      * @return Integer reply, specifically: 1 if the all the keys were set 0 if no key was set (at
584      *         least one key already existed)
585      */
586     Long msetnx(string[] keysvalues...) {
587         checkIsInMultiOrPipeline();
588         client.msetnx(keysvalues);
589         return client.getIntegerReply();
590     }
591     alias msetnx = BinaryRedis.msetnx;
592 
593     /**
594      * IDECRBY work just like {@link #decr(string) INCR} but instead to decrement by 1 the decrement
595      * is integer.
596      * <p>
597      * INCR commands are limited to 64 bit signed integers.
598      * <p>
599      * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
600      * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
601      * and then converted back as a string.
602      * <p>
603      * Time complexity: O(1)
604      * @see #incr(string)
605      * @see #decr(string)
606      * @see #incrBy(string, long)
607      * @param key
608      * @param decrement
609      * @return Integer reply, this commands will reply with the new value of key after the increment.
610      */
611     Long decrBy(string key, long decrement) {
612         checkIsInMultiOrPipeline();
613         client.decrBy(key, decrement);
614         return client.getIntegerReply();
615     }
616     alias decrBy = BinaryRedis.decrBy;
617 
618     /**
619      * Decrement the number stored at key by one. If the key does not exist or contains a value of a
620      * wrong type, set the key to the value of "0" before to perform the decrement operation.
621      * <p>
622      * INCR commands are limited to 64 bit signed integers.
623      * <p>
624      * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
625      * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
626      * and then converted back as a string.
627      * <p>
628      * Time complexity: O(1)
629      * @see #incr(string)
630      * @see #incrBy(string, long)
631      * @see #decrBy(string, long)
632      * @param key
633      * @return Integer reply, this commands will reply with the new value of key after the increment.
634      */
635     Long decr(string key) {
636         checkIsInMultiOrPipeline();
637         client.decr(key);
638         return client.getIntegerReply();
639     }
640     alias decr = BinaryRedis.decr;
641 
642     /**
643      * INCRBY work just like {@link #incr(string) INCR} but instead to increment by 1 the increment is
644      * integer.
645      * <p>
646      * INCR commands are limited to 64 bit signed integers.
647      * <p>
648      * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
649      * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
650      * and then converted back as a string.
651      * <p>
652      * Time complexity: O(1)
653      * @see #incr(string)
654      * @see #decr(string)
655      * @see #decrBy(string, long)
656      * @param key
657      * @param increment
658      * @return Integer reply, this commands will reply with the new value of key after the increment.
659      */
660     Long incrBy(string key, long increment) {
661         checkIsInMultiOrPipeline();
662         client.incrBy(key, increment);
663         return client.getIntegerReply();
664     }
665     alias incrBy = BinaryRedis.incrBy;
666 
667     /**
668      * INCRBYFLOAT
669      * <p>
670      * INCRBYFLOAT commands are limited to double precision floating point values.
671      * <p>
672      * Note: this is actually a string operation, that is, in Redis there are not "double" types.
673      * Simply the string stored at the key is parsed as a base double precision floating point value,
674      * incremented, and then converted back as a string. There is no DECRYBYFLOAT but providing a
675      * negative value will work as expected.
676      * <p>
677      * Time complexity: O(1)
678      * @param key
679      * @param increment
680      * @return Double reply, this commands will reply with the new value of key after the increment.
681      */
682     Double incrByFloat(string key, double increment) {
683         checkIsInMultiOrPipeline();
684         client.incrByFloat(key, increment);
685         string dval = client.getBulkReply();
686         return (dval !is null ? new Double(dval) : null);
687     }
688     alias incrByFloat = BinaryRedis.incrByFloat;
689 
690     /**
691      * Increment the number stored at key by one. If the key does not exist or contains a value of a
692      * wrong type, set the key to the value of "0" before to perform the increment operation.
693      * <p>
694      * INCR commands are limited to 64 bit signed integers.
695      * <p>
696      * Note: this is actually a string operation, that is, in Redis there are not "integer" types.
697      * Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented,
698      * and then converted back as a string.
699      * <p>
700      * Time complexity: O(1)
701      * @see #incrBy(string, long)
702      * @see #decr(string)
703      * @see #decrBy(string, long)
704      * @param key
705      * @return Integer reply, this commands will reply with the new value of key after the increment.
706      */
707     Long incr(string key) {
708         checkIsInMultiOrPipeline();
709         client.incr(key);
710         return client.getIntegerReply();
711     }
712     alias incr = BinaryRedis.incr;
713 
714     /**
715      * If the key already exists and is a string, this command appends the provided value at the end
716      * of the string. If the key does not exist it is created and set as an empty string, so APPEND
717      * will be very similar to SET in this special case.
718      * <p>
719      * Time complexity: O(1). The amortized time complexity is O(1) assuming the appended value is
720      * small and the already present value is of any size, since the dynamic string library used by
721      * Redis will double the free space available on every reallocation.
722      * @param key
723      * @param value
724      * @return Integer reply, specifically the total length of the string after the append operation.
725      */
726     Long append(string key, string value) {
727         checkIsInMultiOrPipeline();
728         client.append(key, value);
729         return client.getIntegerReply();
730     }
731     alias append = BinaryRedis.append;
732 
733     /**
734      * Return a subset of the string from offset start to offset end (both offsets are inclusive).
735      * Negative offsets can be used in order to provide an offset starting from the end of the string.
736      * So -1 means the last char, -2 the penultimate and so forth.
737      * <p>
738      * The function handles out of range requests without raising an error, but just limiting the
739      * resulting range to the actual length of the string.
740      * <p>
741      * Time complexity: O(start+n) (with start being the start index and n the total length of the
742      * requested range). Note that the lookup part of this command is O(1) so for small strings this
743      * is actually an O(1) command.
744      * @param key
745      * @param start
746      * @param end
747      * @return Bulk reply
748      */
749     string substr(string key, int start, int end) {
750         checkIsInMultiOrPipeline();
751         client.substr(key, start, end);
752         return client.getBulkReply();
753     }
754     alias substr = BinaryRedis.substr;
755 
756     /**
757      * Set the specified hash field to the specified value.
758      * <p>
759      * If key does not exist, a new key holding a hash is created.
760      * <p>
761      * <b>Time complexity:</b> O(1)
762      * @param key
763      * @param field
764      * @param value
765      * @return If the field already exists, and the HSET just produced an update of the value, 0 is
766      *         returned, otherwise if a new field is created 1 is returned.
767      */
768     Long hset(string key, string field, string value) {
769         checkIsInMultiOrPipeline();
770         client.hset(key, field, value);
771         return client.getIntegerReply();
772     }
773     alias hset = BinaryRedis.hset;
774 
775     Long hset(string key, Map!(string, string) hash) {
776         checkIsInMultiOrPipeline();
777         client.hset(key, hash);
778         return client.getIntegerReply();
779     }
780 
781     /**
782      * If key holds a hash, retrieve the value associated to the specified field.
783      * <p>
784      * If the field is not found or the key does not exist, a special 'nil' value is returned.
785      * <p>
786      * <b>Time complexity:</b> O(1)
787      * @param key
788      * @param field
789      * @return Bulk reply
790      */
791     string hget(string key, string field) {
792         checkIsInMultiOrPipeline();
793         client.hget(key, field);
794         return client.getBulkReply();
795     }
796     alias hget = BinaryRedis.hget;
797 
798     /**
799      * Set the specified hash field to the specified value if the field not exists. <b>Time
800      * complexity:</b> O(1)
801      * @param key
802      * @param field
803      * @param value
804      * @return If the field already exists, 0 is returned, otherwise if a new field is created 1 is
805      *         returned.
806      */
807     Long hsetnx(string key, string field, string value) {
808         checkIsInMultiOrPipeline();
809         client.hsetnx(key, field, value);
810         return client.getIntegerReply();
811     }
812     alias hsetnx = BinaryRedis.hsetnx;
813 
814     /**
815      * Set the respective fields to the respective values. HMSET replaces old values with new values.
816      * <p>
817      * If key does not exist, a new key holding a hash is created.
818      * <p>
819      * <b>Time complexity:</b> O(N) (with N being the number of fields)
820      * @param key
821      * @param hash
822      * @return Return OK or Exception if hash is empty
823      */
824     string hmset(string key, Map!(string, string) hash) {
825         checkIsInMultiOrPipeline();
826         client.hmset(key, hash);
827         return client.getStatusCodeReply();
828     }
829     alias hmset = BinaryRedis.hmset;
830 
831     /**
832      * Retrieve the values associated to the specified fields.
833      * <p>
834      * If some of the specified fields do not exist, nil values are returned. Non existing keys are
835      * considered like empty hashes.
836      * <p>
837      * <b>Time complexity:</b> O(N) (with N being the number of fields)
838      * @param key
839      * @param fields
840      * @return Multi Bulk Reply specifically a list of all the values associated with the specified
841      *         fields, in the same order of the request.
842      */
843     List!(string) hmget(string key, string[] fields...) {
844         checkIsInMultiOrPipeline();
845         client.hmget(key, fields);
846         return client.getMultiBulkReply();
847     }
848     alias hmget = BinaryRedis.hmget;
849 
850     /**
851      * Increment the number stored at field in the hash at key by value. If key does not exist, a new
852      * key holding a hash is created. If field does not exist or holds a string, the value is set to 0
853      * before applying the operation. Since the value argument is signed you can use this command to
854      * perform both increments and decrements.
855      * <p>
856      * The range of values supported by HINCRBY is limited to 64 bit signed integers.
857      * <p>
858      * <b>Time complexity:</b> O(1)
859      * @param key
860      * @param field
861      * @param value
862      * @return Integer reply The new value at field after the increment operation.
863      */
864     Long hincrBy(string key, string field, long value) {
865         checkIsInMultiOrPipeline();
866         client.hincrBy(key, field, value);
867         return client.getIntegerReply();
868     }
869     alias hincrBy = BinaryRedis.hincrBy;
870 
871     /**
872      * Increment the number stored at field in the hash at key by a double precision floating point
873      * value. If key does not exist, a new key holding a hash is created. If field does not exist or
874      * holds a string, the value is set to 0 before applying the operation. Since the value argument
875      * is signed you can use this command to perform both increments and decrements.
876      * <p>
877      * The range of values supported by HINCRBYFLOAT is limited to double precision floating point
878      * values.
879      * <p>
880      * <b>Time complexity:</b> O(1)
881      * @param key
882      * @param field
883      * @param value
884      * @return Double precision floating point reply The new value at field after the increment
885      *         operation.
886      */
887     Double hincrByFloat(string key, string field, double value) {
888         checkIsInMultiOrPipeline();
889         client.hincrByFloat(key, field, value);
890         string dval = client.getBulkReply();
891         return (dval !is null ? new Double(dval) : null);
892     }
893     alias hincrByFloat = BinaryRedis.hincrByFloat;
894 
895     /**
896      * Test for existence of a specified field in a hash. <b>Time complexity:</b> O(1)
897      * @param key
898      * @param field
899      * @return Return true if the hash stored at key contains the specified field. Return false if the key is
900      *         not found or the field is not present.
901      */
902     // override
903     bool hexists(string key, string field) {
904         checkIsInMultiOrPipeline();
905         client.hexists(key, field);
906         return client.getIntegerReply() == 1;
907     }
908     alias hexists = BinaryRedis.hexists;
909 
910     /**
911      * Remove the specified field from an hash stored at key.
912      * <p>
913      * <b>Time complexity:</b> O(1)
914      * @param key
915      * @param fields
916      * @return If the field was present in the hash it is deleted and 1 is returned, otherwise 0 is
917      *         returned and no operation is performed.
918      */
919     Long hdel(string key, string[] fields...) {
920         checkIsInMultiOrPipeline();
921         client.hdel(key, fields);
922         return client.getIntegerReply();
923     }
924     alias hdel = BinaryRedis.hdel;
925 
926     /**
927      * Return the number of items in a hash.
928      * <p>
929      * <b>Time complexity:</b> O(1)
930      * @param key
931      * @return The number of entries (fields) contained in the hash stored at key. If the specified
932      *         key does not exist, 0 is returned assuming an empty hash.
933      */
934     Long hlen(string key) {
935         checkIsInMultiOrPipeline();
936         client.hlen(key);
937         return client.getIntegerReply();
938     }
939     alias hlen = BinaryRedis.hlen;
940 
941     /**
942      * Return all the fields in a hash.
943      * <p>
944      * <b>Time complexity:</b> O(N), where N is the total number of entries
945      * @param key
946      * @return All the fields names contained into a hash.
947      */
948     Set!(string) hkeys(string key) {
949         checkIsInMultiOrPipeline();
950         client.hkeys(key);
951         return BuilderFactory.STRING_SET.build(cast(Object)client.getBinaryMultiBulkReply());
952     }
953     alias hkeys = BinaryRedis.hkeys;
954 
955     /**
956      * Return all the values in a hash.
957      * <p>
958      * <b>Time complexity:</b> O(N), where N is the total number of entries
959      * @param key
960      * @return All the fields values contained into a hash.
961      */
962     List!(string) hvals(string key) {
963         checkIsInMultiOrPipeline();
964         client.hvals(key);
965         List!(string) lresult = client.getMultiBulkReply();
966         return lresult;
967     }
968     alias hvals = BinaryRedis.hvals;
969 
970     /**
971      * Return all the fields and associated values in a hash.
972      * <p>
973      * <b>Time complexity:</b> O(N), where N is the total number of entries
974      * @param key
975      * @return All the fields and values contained into a hash.
976      */
977     Map!(string, string) hgetAll(string key) {
978         checkIsInMultiOrPipeline();
979         client.hgetAll(key);
980         return BuilderFactory.STRING_MAP.build(cast(Object)client.getBinaryMultiBulkReply());
981     }
982     alias hgetAll = BinaryRedis.hgetAll;
983 
984     /**
985      * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key
986      * does not exist an empty list is created just before the append operation. If the key exists but
987      * is not a List an error is returned.
988      * <p>
989      * Time complexity: O(1)
990      * @param key
991      * @param strings
992      * @return Integer reply, specifically, the number of elements inside the list after the push
993      *         operation.
994      */
995     Long rpush(string key, string[] strings...) {
996         checkIsInMultiOrPipeline();
997         client.rpush(key, strings);
998         return client.getIntegerReply();
999     }
1000     alias rpush = BinaryRedis.rpush;
1001 
1002     /**
1003      * Add the string value to the head (LPUSH) or tail (RPUSH) of the list stored at key. If the key
1004      * does not exist an empty list is created just before the append operation. If the key exists but
1005      * is not a List an error is returned.
1006      * <p>
1007      * Time complexity: O(1)
1008      * @param key
1009      * @param strings
1010      * @return Integer reply, specifically, the number of elements inside the list after the push
1011      *         operation.
1012      */
1013     Long lpush(string key, string[] strings...) {
1014         checkIsInMultiOrPipeline();
1015         client.lpush(key, strings);
1016         return client.getIntegerReply();
1017     }
1018     alias lpush = BinaryRedis.lpush;
1019 
1020     /**
1021      * Return the length of the list stored at the specified key. If the key does not exist zero is
1022      * returned (the same behaviour as for empty lists). If the value stored at key is not a list an
1023      * error is returned.
1024      * <p>
1025      * Time complexity: O(1)
1026      * @param key
1027      * @return The length of the list.
1028      */
1029     Long llen(string key) {
1030         checkIsInMultiOrPipeline();
1031         client.llen(key);
1032         return client.getIntegerReply();
1033     }
1034     alias llen = BinaryRedis.llen;
1035 
1036     /**
1037      * Return the specified elements of the list stored at the specified key. Start and end are
1038      * zero-based indexes. 0 is the first element of the list (the list head), 1 the next element and
1039      * so on.
1040      * <p>
1041      * For example LRANGE foobar 0 2 will return the first three elements of the list.
1042      * <p>
1043      * start and end can also be negative numbers indicating offsets from the end of the list. For
1044      * example -1 is the last element of the list, -2 the penultimate element and so on.
1045      * <p>
1046      * <b>Consistency with range functions in various programming languages</b>
1047      * <p>
1048      * Note that if you have a list of numbers from 0 to 100, LRANGE 0 10 will return 11 elements,
1049      * that is, rightmost item is included. This may or may not be consistent with behavior of
1050      * range-related functions in your programming language of choice (think Ruby's Range.new,
1051      * Array#slice or Python's range() function).
1052      * <p>
1053      * LRANGE behavior is consistent with one of Tcl.
1054      * <p>
1055      * <b>Out-of-range indexes</b>
1056      * <p>
1057      * Indexes out of range will not produce an error: if start is over the end of the list, or start
1058      * &gt; end, an empty list is returned. If end is over the end of the list Redis will threat it
1059      * just like the last element of the list.
1060      * <p>
1061      * Time complexity: O(start+n) (with n being the length of the range and start being the start
1062      * offset)
1063      * @param key
1064      * @param start
1065      * @param stop
1066      * @return Multi bulk reply, specifically a list of elements in the specified range.
1067      */
1068     List!(string) lrange(string key, long start, long stop) {
1069         checkIsInMultiOrPipeline();
1070         client.lrange(key, start, stop);
1071         return client.getMultiBulkReply();
1072     }
1073     alias lrange = BinaryRedis.lrange;
1074 
1075     /**
1076      * Trim an existing list so that it will contain only the specified range of elements specified.
1077      * Start and end are zero-based indexes. 0 is the first element of the list (the list head), 1 the
1078      * next element and so on.
1079      * <p>
1080      * For example LTRIM foobar 0 2 will modify the list stored at foobar key so that only the first
1081      * three elements of the list will remain.
1082      * <p>
1083      * start and end can also be negative numbers indicating offsets from the end of the list. For
1084      * example -1 is the last element of the list, -2 the penultimate element and so on.
1085      * <p>
1086      * Indexes out of range will not produce an error: if start is over the end of the list, or start
1087      * &gt; end, an empty list is left as value. If end over the end of the list Redis will threat it
1088      * just like the last element of the list.
1089      * <p>
1090      * Hint: the obvious use of LTRIM is together with LPUSH/RPUSH. For example:
1091      * <p>
1092      * {@code lpush("mylist", "someelement"); ltrim("mylist", 0, 99); * }
1093      * <p>
1094      * The above two commands will push elements in the list taking care that the list will not grow
1095      * without limits. This is very useful when using Redis to store logs for example. It is important
1096      * to note that when used in this way LTRIM is an O(1) operation because in the average case just
1097      * one element is removed from the tail of the list.
1098      * <p>
1099      * Time complexity: O(n) (with n being len of list - len of range)
1100      * @param key
1101      * @param start
1102      * @param stop
1103      * @return Status code reply
1104      */
1105     string ltrim(string key, long start, long stop) {
1106         checkIsInMultiOrPipeline();
1107         client.ltrim(key, start, stop);
1108         return client.getStatusCodeReply();
1109     }
1110     alias ltrim = BinaryRedis.ltrim;
1111 
1112     /**
1113      * Return the specified element of the list stored at the specified key. 0 is the first element, 1
1114      * the second and so on. Negative indexes are supported, for example -1 is the last element, -2
1115      * the penultimate and so on.
1116      * <p>
1117      * If the value stored at key is not of list type an error is returned. If the index is out of
1118      * range a 'nil' reply is returned.
1119      * <p>
1120      * Note that even if the average time complexity is O(n) asking for the first or the last element
1121      * of the list is O(1).
1122      * <p>
1123      * Time complexity: O(n) (with n being the length of the list)
1124      * @param key
1125      * @param index
1126      * @return Bulk reply, specifically the requested element
1127      */
1128     string lindex(string key, long index) {
1129         checkIsInMultiOrPipeline();
1130         client.lindex(key, index);
1131         return client.getBulkReply();
1132     }
1133     alias lindex = BinaryRedis.lindex;
1134 
1135     /**
1136      * Set a new value as the element at index position of the List at key.
1137      * <p>
1138      * Out of range indexes will generate an error.
1139      * <p>
1140      * Similarly to other list commands accepting indexes, the index can be negative to access
1141      * elements starting from the end of the list. So -1 is the last element, -2 is the penultimate,
1142      * and so forth.
1143      * <p>
1144      * <b>Time complexity:</b>
1145      * <p>
1146      * O(N) (with N being the length of the list), setting the first or last elements of the list is
1147      * O(1).
1148      * @see #lindex(string, long)
1149      * @param key
1150      * @param index
1151      * @param value
1152      * @return Status code reply
1153      */
1154     string lset(string key, long index, string value) {
1155         checkIsInMultiOrPipeline();
1156         client.lset(key, index, value);
1157         return client.getStatusCodeReply();
1158     }
1159     alias lset = BinaryRedis.lset;
1160 
1161     /**
1162      * Remove the first count occurrences of the value element from the list. If count is zero all the
1163      * elements are removed. If count is negative elements are removed from tail to head, instead to
1164      * go from head to tail that is the normal behaviour. So for example LREM with count -2 and hello
1165      * as value to remove against the list (a,b,c,hello,x,hello,hello) will leave the list
1166      * (a,b,c,hello,x). The number of removed elements is returned as an integer, see below for more
1167      * information about the returned value. Note that non existing keys are considered like empty
1168      * lists by LREM, so LREM against non existing keys will always return 0.
1169      * <p>
1170      * Time complexity: O(N) (with N being the length of the list)
1171      * @param key
1172      * @param count
1173      * @param value
1174      * @return Integer Reply, specifically: The number of removed elements if the operation succeeded
1175      */
1176     Long lrem(string key, long count, string value) {
1177         checkIsInMultiOrPipeline();
1178         client.lrem(key, count, value);
1179         return client.getIntegerReply();
1180     }
1181     alias lrem = BinaryRedis.lrem;
1182 
1183     /**
1184      * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example
1185      * if the list contains the elements "a","b","c" LPOP will return "a" and the list will become
1186      * "b","c".
1187      * <p>
1188      * If the key does not exist or the list is already empty the special value 'nil' is returned.
1189      * @see #rpop(string)
1190      * @param key
1191      * @return Bulk reply
1192      */
1193     string lpop(string key) {
1194         checkIsInMultiOrPipeline();
1195         client.lpop(key);
1196         return client.getBulkReply();
1197     }
1198     alias lpop = BinaryRedis.lpop;
1199 
1200     /**
1201      * Atomically return and remove the first (LPOP) or last (RPOP) element of the list. For example
1202      * if the list contains the elements "a","b","c" RPOP will return "c" and the list will become
1203      * "a","b".
1204      * <p>
1205      * If the key does not exist or the list is already empty the special value 'nil' is returned.
1206      * @see #lpop(string)
1207      * @param key
1208      * @return Bulk reply
1209      */
1210     string rpop(string key) {
1211         checkIsInMultiOrPipeline();
1212         client.rpop(key);
1213         return client.getBulkReply();
1214     }
1215     alias rpop = BinaryRedis.rpop;
1216 
1217     /**
1218      * Atomically return and remove the last (tail) element of the srckey list, and push the element
1219      * as the first (head) element of the dstkey list. For example if the source list contains the
1220      * elements "a","b","c" and the destination list contains the elements "foo","bar" after an
1221      * RPOPLPUSH command the content of the two lists will be "a","b" and "c","foo","bar".
1222      * <p>
1223      * If the key does not exist or the list is already empty the special value 'nil' is returned. If
1224      * the srckey and dstkey are the same the operation is equivalent to removing the last element
1225      * from the list and pushing it as first element of the list, so it's a "list rotation" command.
1226      * <p>
1227      * Time complexity: O(1)
1228      * @param srckey
1229      * @param dstkey
1230      * @return Bulk reply
1231      */
1232     string rpoplpush(string srckey, string dstkey) {
1233         checkIsInMultiOrPipeline();
1234         client.rpoplpush(srckey, dstkey);
1235         return client.getBulkReply();
1236     }
1237     alias rpoplpush = BinaryRedis.rpoplpush;
1238 
1239     /**
1240      * Add the specified member to the set value stored at key. If member is already a member of the
1241      * set no operation is performed. If key does not exist a new set with the specified member as
1242      * sole member is created. If the key exists but does not hold a set value an error is returned.
1243      * <p>
1244      * Time complexity O(1)
1245      * @param key
1246      * @param members
1247      * @return Integer reply, specifically: 1 if the new element was added 0 if the element was
1248      *         already a member of the set
1249      */
1250     Long sadd(string key, string[] members...) {
1251         checkIsInMultiOrPipeline();
1252         client.sadd(key, members);
1253         return client.getIntegerReply();
1254     }
1255     alias sadd = BinaryRedis.sadd;
1256 
1257     /**
1258      * Return all the members (elements) of the set value stored at key. This is just syntax glue for
1259      * {@link #sinter(string...) SINTER}.
1260      * <p>
1261      * Time complexity O(N)
1262      * @param key
1263      * @return Multi bulk reply
1264      */
1265     Set!(string) smembers(string key) {
1266         checkIsInMultiOrPipeline();
1267         client.smembers(key);
1268         List!(string) members = client.getMultiBulkReply();
1269         return new SetFromList!string(members);
1270     }
1271     alias smembers = BinaryRedis.smembers;
1272 
1273     /**
1274      * Remove the specified member from the set value stored at key. If member was not a member of the
1275      * set no operation is performed. If key does not hold a set value an error is returned.
1276      * <p>
1277      * Time complexity O(1)
1278      * @param key
1279      * @param members
1280      * @return Integer reply, specifically: 1 if the new element was removed 0 if the new element was
1281      *         not a member of the set
1282      */
1283     Long srem(string key, string[] members...) {
1284         checkIsInMultiOrPipeline();
1285         client.srem(key, members);
1286         return client.getIntegerReply();
1287     }
1288     alias srem = BinaryRedis.srem;
1289 
1290     /**
1291      * Remove a random element from a Set returning it as return value. If the Set is empty or the key
1292      * does not exist, a nil object is returned.
1293      * <p>
1294      * The {@link #srandmember(string)} command does a similar work but the returned element is not
1295      * removed from the Set.
1296      * <p>
1297      * Time complexity O(1)
1298      * @param key
1299      * @return Bulk reply
1300      */
1301     string spop(string key) {
1302         checkIsInMultiOrPipeline();
1303         client.spop(key);
1304         return client.getBulkReply();
1305     }
1306     alias spop = BinaryRedis.spop;
1307 
1308     Set!(string) spop(string key, long count) {
1309         checkIsInMultiOrPipeline();
1310         client.spop(key, count);
1311         List!(string) members = client.getMultiBulkReply();
1312         if (members is null) return null;
1313         return new SetFromList!string(members);
1314     }
1315 
1316     /**
1317      * Move the specified member from the set at srckey to the set at dstkey. This operation is
1318      * atomic, in every given moment the element will appear to be in the source or destination set
1319      * for accessing clients.
1320      * <p>
1321      * If the source set does not exist or does not contain the specified element no operation is
1322      * performed and zero is returned, otherwise the element is removed from the source set and added
1323      * to the destination set. On success one is returned, even if the element was already present in
1324      * the destination set.
1325      * <p>
1326      * An error is raised if the source or destination keys contain a non Set value.
1327      * <p>
1328      * Time complexity O(1)
1329      * @param srckey
1330      * @param dstkey
1331      * @param member
1332      * @return Integer reply, specifically: 1 if the element was moved 0 if the element was not found
1333      *         on the first set and no operation was performed
1334      */
1335     Long smove(string srckey, string dstkey, string member) {
1336         checkIsInMultiOrPipeline();
1337         client.smove(srckey, dstkey, member);
1338         return client.getIntegerReply();
1339     }
1340     alias smove = BinaryRedis.smove;
1341 
1342     /**
1343      * Return the set cardinality (number of elements). If the key does not exist 0 is returned, like
1344      * for empty sets.
1345      * @param key
1346      * @return Integer reply, specifically: the cardinality (number of elements) of the set as an
1347      *         integer.
1348      */
1349     Long scard(string key) {
1350         checkIsInMultiOrPipeline();
1351         client.scard(key);
1352         return client.getIntegerReply();
1353     }
1354     alias scard = BinaryRedis.scard;
1355 
1356     /**
1357      * Return true if member is a member of the set stored at key, otherwise false is returned.
1358      * <p>
1359      * Time complexity O(1)
1360      * @param key
1361      * @param member
1362      * @return bool reply, specifically: true if the element is a member of the set false if the element
1363      *         is not a member of the set OR if the key does not exist
1364      */
1365     bool sismember(string key, string member) {
1366         checkIsInMultiOrPipeline();
1367         client.sismember(key, member);
1368         return client.getIntegerReply() == 1;
1369     }
1370     alias sismember = BinaryRedis.sismember;
1371 
1372     /**
1373      * Return the members of a set resulting from the intersection of all the sets hold at the
1374      * specified keys. Like in {@link #lrange(string, long, long) LRANGE} the result is sent to the
1375      * client as a multi-bulk reply (see the protocol specification for more information). If just a
1376      * single key is specified, then this command produces the same result as
1377      * {@link #smembers(string) SMEMBERS}. Actually SMEMBERS is just syntax sugar for SINTER.
1378      * <p>
1379      * Non existing keys are considered like empty sets, so if one of the keys is missing an empty set
1380      * is returned (since the intersection with an empty set always is an empty set).
1381      * <p>
1382      * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the
1383      * number of sets
1384      * @param keys
1385      * @return Multi bulk reply, specifically the list of common elements.
1386      */
1387     Set!(string) sinter(string[] keys...) {
1388         checkIsInMultiOrPipeline();
1389         client.sinter(keys);
1390         List!(string) members = client.getMultiBulkReply();
1391         return new SetFromList!string(members);
1392     }
1393     alias sinter = BinaryRedis.sinter;
1394 
1395     /**
1396      * This command works exactly like {@link #sinter(string...) SINTER} but instead of being returned
1397      * the resulting set is stored as dstkey.
1398      * <p>
1399      * Time complexity O(N*M) worst case where N is the cardinality of the smallest set and M the
1400      * number of sets
1401      * @param dstkey
1402      * @param keys
1403      * @return Status code reply
1404      */
1405     Long sinterstore(string dstkey, string[] keys...) {
1406         checkIsInMultiOrPipeline();
1407         client.sinterstore(dstkey, keys);
1408         return client.getIntegerReply();
1409     }
1410     alias sinterstore = BinaryRedis.sinterstore;
1411 
1412     /**
1413      * Return the members of a set resulting from the union of all the sets hold at the specified
1414      * keys. Like in {@link #lrange(string, long, long) LRANGE} the result is sent to the client as a
1415      * multi-bulk reply (see the protocol specification for more information). If just a single key is
1416      * specified, then this command produces the same result as {@link #smembers(string) SMEMBERS}.
1417      * <p>
1418      * Non existing keys are considered like empty sets.
1419      * <p>
1420      * Time complexity O(N) where N is the total number of elements in all the provided sets
1421      * @param keys
1422      * @return Multi bulk reply, specifically the list of common elements.
1423      */
1424     Set!(string) sunion(string[] keys...) {
1425         checkIsInMultiOrPipeline();
1426         client.sunion(keys);
1427         List!(string) members = client.getMultiBulkReply();
1428         return new SetFromList!string(members);
1429     }
1430     alias sunion = BinaryRedis.sunion;
1431 
1432     /**
1433      * This command works exactly like {@link #sunion(string...) SUNION} but instead of being returned
1434      * the resulting set is stored as dstkey. Any existing value in dstkey will be over-written.
1435      * <p>
1436      * Time complexity O(N) where N is the total number of elements in all the provided sets
1437      * @param dstkey
1438      * @param keys
1439      * @return Status code reply
1440      */
1441     Long sunionstore(string dstkey, string[] keys...) {
1442         checkIsInMultiOrPipeline();
1443         client.sunionstore(dstkey, keys);
1444         return client.getIntegerReply();
1445     }
1446     alias sunionstore = BinaryRedis.sunionstore;
1447 
1448     /**
1449      * Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN
1450      * <p>
1451      * <b>Example:</b>
1452      * 
1453      * <pre>
1454      * key1 = [x, a, b, c]
1455      * key2 = [c]
1456      * key3 = [a, d]
1457      * SDIFF key1,key2,key3 =&gt; [x, b]
1458      * </pre>
1459      * 
1460      * Non existing keys are considered like empty sets.
1461      * <p>
1462      * <b>Time complexity:</b>
1463      * <p>
1464      * O(N) with N being the total number of elements of all the sets
1465      * @param keys
1466      * @return Return the members of a set resulting from the difference between the first set
1467      *         provided and all the successive sets.
1468      */
1469     Set!(string) sdiff(string[] keys...) {
1470         checkIsInMultiOrPipeline();
1471         client.sdiff(keys);
1472         return BuilderFactory.STRING_SET.build(cast(Object)client.getBinaryMultiBulkReply());
1473     }
1474 
1475     /**
1476      * This command works exactly like {@link #sdiff(string...) SDIFF} but instead of being returned
1477      * the resulting set is stored in dstkey.
1478      * @param dstkey
1479      * @param keys
1480      * @return Status code reply
1481      */
1482     Long sdiffstore(string dstkey, string[] keys...) {
1483         checkIsInMultiOrPipeline();
1484         client.sdiffstore(dstkey, keys);
1485         return client.getIntegerReply();
1486     }
1487     alias sdiffstore = BinaryRedis.sdiffstore;
1488 
1489     /**
1490      * Return a random element from a Set, without removing the element. If the Set is empty or the
1491      * key does not exist, a nil object is returned.
1492      * <p>
1493      * The SPOP command does a similar work but the returned element is popped (removed) from the Set.
1494      * <p>
1495      * Time complexity O(1)
1496      * @param key
1497      * @return Bulk reply
1498      */
1499     string srandmember(string key) {
1500         checkIsInMultiOrPipeline();
1501         client.srandmember(key);
1502         return client.getBulkReply();
1503     }
1504     alias srandmember = BinaryRedis.srandmember;
1505 
1506     List!(string) srandmember(string key, int count) {
1507         checkIsInMultiOrPipeline();
1508         client.srandmember(key, count);
1509         return client.getMultiBulkReply();
1510     }
1511 
1512     /**
1513      * Add the specified member having the specified score to the sorted set stored at key. If member
1514      * is already a member of the sorted set the score is updated, and the element reinserted in the
1515      * right position to ensure sorting. If key does not exist a new sorted set with the specified
1516      * member as sole member is created. If the key exists but does not hold a sorted set value an
1517      * error is returned.
1518      * <p>
1519      * The score value can be the string representation of a double precision floating point number.
1520      * <p>
1521      * Time complexity O(log(N)) with N being the number of elements in the sorted set
1522      * @param key
1523      * @param score
1524      * @param member
1525      * @return Integer reply, specifically: 1 if the new element was added 0 if the element was
1526      *         already a member of the sorted set and the score was updated
1527      */
1528     Long zadd(string key, double score, string member) {
1529         checkIsInMultiOrPipeline();
1530         client.zadd(key, score, member);
1531         return client.getIntegerReply();
1532     }
1533     alias zadd = BinaryRedis.zadd;
1534 
1535     Long zadd(string key, double score, string member,
1536             ZAddParams params) {
1537         checkIsInMultiOrPipeline();
1538         client.zadd(key, score, member, params);
1539         return client.getIntegerReply();
1540     }
1541 
1542     Long zadd(string key, Map!(string, double) scoreMembers) {
1543         checkIsInMultiOrPipeline();
1544         client.zadd(key, scoreMembers);
1545         return client.getIntegerReply();
1546     }
1547 
1548     Long zadd(string key, Map!(string, double) scoreMembers, ZAddParams params) {
1549         checkIsInMultiOrPipeline();
1550         client.zadd(key, scoreMembers, params);
1551         return client.getIntegerReply();
1552     }
1553 
1554     string[] zrange(string key, long start, long stop) {
1555         checkIsInMultiOrPipeline();
1556         client.zrange(key, start, stop);
1557         List!(string) members = client.getMultiBulkReply();
1558         // return new SetFromList!string(members);
1559         return members.toArray();
1560     }
1561     alias zrange = BinaryRedis.zrange;
1562 
1563     /**
1564      * Remove the specified member from the sorted set value stored at key. If member was not a member
1565      * of the set no operation is performed. If key does not not hold a set value an error is
1566      * returned.
1567      * <p>
1568      * Time complexity O(log(N)) with N being the number of elements in the sorted set
1569      * @param key
1570      * @param members
1571      * @return Integer reply, specifically: 1 if the new element was removed 0 if the new element was
1572      *         not a member of the set
1573      */
1574     Long zrem(string key, string[] members...) {
1575         checkIsInMultiOrPipeline();
1576         client.zrem(key, members);
1577         return client.getIntegerReply();
1578     }
1579     alias zrem = BinaryRedis.zrem;
1580 
1581     /**
1582      * If member already exists in the sorted set adds the increment to its score and updates the
1583      * position of the element in the sorted set accordingly. If member does not already exist in the
1584      * sorted set it is added with increment as score (that is, like if the previous score was
1585      * virtually zero). If key does not exist a new sorted set with the specified member as sole
1586      * member is created. If the key exists but does not hold a sorted set value an error is returned.
1587      * <p>
1588      * The score value can be the string representation of a double precision floating point number.
1589      * It's possible to provide a negative value to perform a decrement.
1590      * <p>
1591      * For an introduction to sorted sets check the Introduction to Redis data types page.
1592      * <p>
1593      * Time complexity O(log(N)) with N being the number of elements in the sorted set
1594      * @param key
1595      * @param increment
1596      * @param member
1597      * @return The new score
1598      */
1599     double zincrby(string key, double increment, string member) {
1600         checkIsInMultiOrPipeline();
1601         client.zincrby(key, increment, member);
1602         Double r = BuilderFactory.DOUBLE.build(cast(Object)client.getOne());
1603         return r.value();
1604     }
1605     alias zincrby = BinaryRedis.zincrby;
1606 
1607     double zincrby(string key, double increment, string member, ZIncrByParams params) {
1608         checkIsInMultiOrPipeline();
1609         client.zincrby(key, increment, member, params);
1610         Double r = BuilderFactory.DOUBLE.build(cast(Object)client.getOne());
1611         return r.value();
1612     }
1613 
1614     /**
1615      * Return the rank (or index) of member in the sorted set at key, with scores being ordered from
1616      * low to high.
1617      * <p>
1618      * When the given member does not exist in the sorted set, the special value 'nil' is returned.
1619      * The returned rank (or index) of the member is 0-based for both commands.
1620      * <p>
1621      * <b>Time complexity:</b>
1622      * <p>
1623      * O(log(N))
1624      * @see #zrevrank(string, string)
1625      * @param key
1626      * @param member
1627      * @return Integer reply or a nil bulk reply, specifically: the rank of the element as an integer
1628      *         reply if the element exists. A nil bulk reply if there is no such element.
1629      */
1630     Long zrank(string key, string member) {
1631         checkIsInMultiOrPipeline();
1632         client.zrank(key, member);
1633         return client.getIntegerReply();
1634     }
1635     alias zrank = BinaryRedis.zrank;
1636 
1637     /**
1638      * Return the rank (or index) of member in the sorted set at key, with scores being ordered from
1639      * high to low.
1640      * <p>
1641      * When the given member does not exist in the sorted set, the special value 'nil' is returned.
1642      * The returned rank (or index) of the member is 0-based for both commands.
1643      * <p>
1644      * <b>Time complexity:</b>
1645      * <p>
1646      * O(log(N))
1647      * @see #zrank(string, string)
1648      * @param key
1649      * @param member
1650      * @return Integer reply or a nil bulk reply, specifically: the rank of the element as an integer
1651      *         reply if the element exists. A nil bulk reply if there is no such element.
1652      */
1653     Long zrevrank(string key, string member) {
1654         checkIsInMultiOrPipeline();
1655         client.zrevrank(key, member);
1656         return client.getIntegerReply();
1657     }
1658     alias zrevrank = BinaryRedis.zrevrank;
1659 
1660     string[] zrevrange(string key, long start, long stop) {
1661         checkIsInMultiOrPipeline();
1662         client.zrevrange(key, start, stop);
1663         List!(string) members = client.getMultiBulkReply();
1664         return members.toArray();
1665         // return new SetFromList!string(members);
1666     }
1667     alias zrevrange = BinaryRedis.zrevrange;
1668 
1669     Set!(Tuple) zrangeWithScores(string key, long start, long stop) {
1670         checkIsInMultiOrPipeline();
1671         client.zrangeWithScores(key, start, stop);
1672         return getTupledSet();
1673     }
1674     alias zrangeWithScores = BinaryRedis.zrangeWithScores;
1675 
1676     Set!(Tuple) zrevrangeWithScores(string key, long start, long stop) {
1677         checkIsInMultiOrPipeline();
1678         client.zrevrangeWithScores(key, start, stop);
1679         return getTupledSet();
1680     }
1681     alias zrevrangeWithScores = BinaryRedis.zrevrangeWithScores;
1682 
1683     /**
1684      * Return the sorted set cardinality (number of elements). If the key does not exist 0 is
1685      * returned, like for empty sorted sets.
1686      * <p>
1687      * Time complexity O(1)
1688      * @param key
1689      * @return the cardinality (number of elements) of the set as an integer.
1690      */
1691     Long zcard(string key) {
1692         checkIsInMultiOrPipeline();
1693         client.zcard(key);
1694         return client.getIntegerReply();
1695     }
1696     alias zcard = BinaryRedis.zcard;
1697 
1698     /**
1699      * Return the score of the specified element of the sorted set at key. If the specified element
1700      * does not exist in the sorted set, or the key does not exist at all, a special 'nil' value is
1701      * returned.
1702      * <p>
1703      * <b>Time complexity:</b> O(1)
1704      * @param key
1705      * @param member
1706      * @return the score
1707      */
1708     Double zscore(string key, string member) {
1709         checkIsInMultiOrPipeline();
1710         client.zscore(key, member);
1711         return BuilderFactory.DOUBLE.build(cast(Object)client.getOne());
1712     }
1713     alias zscore = BinaryRedis.zscore;
1714 
1715     string watch(string[] keys...) {
1716         client.watch(keys);
1717         return client.getStatusCodeReply();
1718     }
1719     alias watch = BinaryRedis.watch;
1720 
1721     /**
1722      * Sort a Set or a List.
1723      * <p>
1724      * Sort the elements contained in the List, Set, or Sorted Set value at key. By default sorting is
1725      * numeric with elements being compared as double precision floating point numbers. This is the
1726      * simplest form of SORT.
1727      * @see #sort(string, string)
1728      * @see #sort(string, SortingParams)
1729      * @see #sort(string, SortingParams, string)
1730      * @param key
1731      * @return Assuming the Set/List at key contains a list of numbers, the return value will be the
1732      *         list of numbers ordered from the smallest to the biggest number.
1733      */
1734     List!(string) sort(string key) {
1735         checkIsInMultiOrPipeline();
1736         client.sort(key);
1737         return client.getMultiBulkReply();
1738     }
1739     alias sort = BinaryRedis.sort;
1740 
1741     /**
1742      * Sort a Set or a List accordingly to the specified parameters.
1743      * <p>
1744      * <b>examples:</b>
1745      * <p>
1746      * Given are the following sets and key/values:
1747      * 
1748      * <pre>
1749      * x = [1, 2, 3]
1750      * y = [a, b, c]
1751      * 
1752      * k1 = z
1753      * k2 = y
1754      * k3 = x
1755      * 
1756      * w1 = 9
1757      * w2 = 8
1758      * w3 = 7
1759      * </pre>
1760      * 
1761      * Sort Order:
1762      * 
1763      * <pre>
1764      * sort(x) or sort(x, sp.asc())
1765      * -&gt; [1, 2, 3]
1766      * 
1767      * sort(x, sp.desc())
1768      * -&gt; [3, 2, 1]
1769      * 
1770      * sort(y)
1771      * -&gt; [c, a, b]
1772      * 
1773      * sort(y, sp.alpha())
1774      * -&gt; [a, b, c]
1775      * 
1776      * sort(y, sp.alpha().desc())
1777      * -&gt; [c, a, b]
1778      * </pre>
1779      * 
1780      * Limit (e.g. for Pagination):
1781      * 
1782      * <pre>
1783      * sort(x, sp.limit(0, 2))
1784      * -&gt; [1, 2]
1785      * 
1786      * sort(y, sp.alpha().desc().limit(1, 2))
1787      * -&gt; [b, a]
1788      * </pre>
1789      * 
1790      * Sorting by external keys:
1791      * 
1792      * <pre>
1793      * sort(x, sb.by(w*))
1794      * -&gt; [3, 2, 1]
1795      * 
1796      * sort(x, sb.by(w*).desc())
1797      * -&gt; [1, 2, 3]
1798      * </pre>
1799      * 
1800      * Getting external keys:
1801      * 
1802      * <pre>
1803      * sort(x, sp.by(w*).get(k*))
1804      * -&gt; [x, y, z]
1805      * 
1806      * sort(x, sp.by(w*).get(#).get(k*))
1807      * -&gt; [3, x, 2, y, 1, z]
1808      * </pre>
1809      * @see #sort(string)
1810      * @see #sort(string, SortingParams, string)
1811      * @param key
1812      * @param sortingParameters
1813      * @return a list of sorted elements.
1814      */
1815     List!(string) sort(string key, SortingParams sortingParameters) {
1816         checkIsInMultiOrPipeline();
1817         client.sort(key, sortingParameters);
1818         return client.getMultiBulkReply();
1819     }
1820 
1821     /**
1822      * BLPOP (and BRPOP) is a blocking list pop primitive. You can see this commands as blocking
1823      * versions of LPOP and RPOP able to block if the specified keys don't exist or contain empty
1824      * lists.
1825      * <p>
1826      * The following is a description of the exact semantic. We describe BLPOP but the two commands
1827      * are identical, the only difference is that BLPOP pops the element from the left (head) of the
1828      * list, and BRPOP pops from the right (tail).
1829      * <p>
1830      * <b>Non blocking behavior</b>
1831      * <p>
1832      * When BLPOP is called, if at least one of the specified keys contain a non empty list, an
1833      * element is popped from the head of the list and returned to the caller together with the name
1834      * of the key (BLPOP returns a two elements array, the first element is the key, the second the
1835      * popped value).
1836      * <p>
1837      * Keys are scanned from left to right, so for instance if you issue BLPOP list1 list2 list3 0
1838      * against a dataset where list1 does not exist but list2 and list3 contain non empty lists, BLPOP
1839      * guarantees to return an element from the list stored at list2 (since it is the first non empty
1840      * list starting from the left).
1841      * <p>
1842      * <b>Blocking behavior</b>
1843      * <p>
1844      * If none of the specified keys exist or contain non empty lists, BLPOP blocks until some other
1845      * client performs a LPUSH or an RPUSH operation against one of the lists.
1846      * <p>
1847      * Once new data is present on one of the lists, the client finally returns with the name of the
1848      * key unblocking it and the popped value.
1849      * <p>
1850      * When blocking, if a non-zero timeout is specified, the client will unblock returning a nil
1851      * special value if the specified amount of seconds passed without a push operation against at
1852      * least one of the specified keys.
1853      * <p>
1854      * The timeout argument is interpreted as an integer value. A timeout of zero means instead to
1855      * block forever.
1856      * <p>
1857      * <b>Multiple clients blocking for the same keys</b>
1858      * <p>
1859      * Multiple clients can block for the same key. They are put into a queue, so the first to be
1860      * served will be the one that started to wait earlier, in a first-blpopping first-served fashion.
1861      * <p>
1862      * <b>blocking POP inside a MULTI/EXEC transaction</b>
1863      * <p>
1864      * BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies
1865      * in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis
1866      * transaction).
1867      * <p>
1868      * The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil
1869      * reply, exactly what happens when the timeout is reached. If you like science fiction, think at
1870      * it like if inside MULTI/EXEC the time will flow at infinite speed :)
1871      * <p>
1872      * Time complexity: O(1)
1873      * @see #brpop(int, string...)
1874      * @param timeout
1875      * @param keys
1876      * @return BLPOP returns a two-elements array via a multi bulk reply in order to return both the
1877      *         unblocking key and the popped value.
1878      *         <p>
1879      *         When a non-zero timeout is specified, and the BLPOP operation timed out, the return
1880      *         value is a nil multi bulk reply. Most client values will return false or nil
1881      *         accordingly to the programming language used.
1882      */
1883     List!(string) blpop(int timeout, string[] keys...) {
1884         return blpop(getArgsAddTimeout(timeout, keys));
1885     }
1886     alias blpop = BinaryRedis.blpop;
1887 
1888     private string[] getArgsAddTimeout(int timeout, string[] keys) {
1889         int keyCount = cast(int)keys.length;
1890         string[] args = new string[keyCount + 1];
1891         for (int at = 0; at != keyCount; ++at) {
1892             args[at] = keys[at];
1893         }
1894 
1895         args[keyCount] = to!string(timeout);
1896         return args;
1897     }
1898 
1899     List!(string) blpop(string[] args...) {
1900         checkIsInMultiOrPipeline();
1901         client.blpop(args);
1902         client.setTimeoutInfinite();
1903         try {
1904             return client.getMultiBulkReply();
1905         } finally {
1906             client.rollbackTimeout();
1907         }
1908     }
1909 
1910     List!(string) brpop(string[] args...) {
1911         checkIsInMultiOrPipeline();
1912         client.brpop(args);
1913         client.setTimeoutInfinite();
1914         try {
1915             return client.getMultiBulkReply();
1916         } finally {
1917             client.rollbackTimeout();
1918         }
1919     }
1920     alias brpop = BinaryRedis.brpop;
1921 
1922     /**
1923      * Sort a Set or a List accordingly to the specified parameters and store the result at dstkey.
1924      * @see #sort(string, SortingParams)
1925      * @see #sort(string)
1926      * @see #sort(string, string)
1927      * @param key
1928      * @param sortingParameters
1929      * @param dstkey
1930      * @return The number of elements of the list at dstkey.
1931      */
1932     Long sort(string key, SortingParams sortingParameters, string dstkey) {
1933         checkIsInMultiOrPipeline();
1934         client.sort(key, sortingParameters, dstkey);
1935         return client.getIntegerReply();
1936     }
1937     alias sort = BinaryRedis.sort;
1938 
1939     /**
1940      * Sort a Set or a List and Store the Result at dstkey.
1941      * <p>
1942      * Sort the elements contained in the List, Set, or Sorted Set value at key and store the result
1943      * at dstkey. By default sorting is numeric with elements being compared as double precision
1944      * floating point numbers. This is the simplest form of SORT.
1945      * @see #sort(string)
1946      * @see #sort(string, SortingParams)
1947      * @see #sort(string, SortingParams, string)
1948      * @param key
1949      * @param dstkey
1950      * @return The number of elements of the list at dstkey.
1951      */
1952     Long sort(string key, string dstkey) {
1953         checkIsInMultiOrPipeline();
1954         client.sort(key, dstkey);
1955         return client.getIntegerReply();
1956     }
1957 
1958     /**
1959      * BLPOP (and BRPOP) is a blocking list pop primitive. You can see this commands as blocking
1960      * versions of LPOP and RPOP able to block if the specified keys don't exist or contain empty
1961      * lists.
1962      * <p>
1963      * The following is a description of the exact semantic. We describe BLPOP but the two commands
1964      * are identical, the only difference is that BLPOP pops the element from the left (head) of the
1965      * list, and BRPOP pops from the right (tail).
1966      * <p>
1967      * <b>Non blocking behavior</b>
1968      * <p>
1969      * When BLPOP is called, if at least one of the specified keys contain a non empty list, an
1970      * element is popped from the head of the list and returned to the caller together with the name
1971      * of the key (BLPOP returns a two elements array, the first element is the key, the second the
1972      * popped value).
1973      * <p>
1974      * Keys are scanned from left to right, so for instance if you issue BLPOP list1 list2 list3 0
1975      * against a dataset where list1 does not exist but list2 and list3 contain non empty lists, BLPOP
1976      * guarantees to return an element from the list stored at list2 (since it is the first non empty
1977      * list starting from the left).
1978      * <p>
1979      * <b>Blocking behavior</b>
1980      * <p>
1981      * If none of the specified keys exist or contain non empty lists, BLPOP blocks until some other
1982      * client performs a LPUSH or an RPUSH operation against one of the lists.
1983      * <p>
1984      * Once new data is present on one of the lists, the client finally returns with the name of the
1985      * key unblocking it and the popped value.
1986      * <p>
1987      * When blocking, if a non-zero timeout is specified, the client will unblock returning a nil
1988      * special value if the specified amount of seconds passed without a push operation against at
1989      * least one of the specified keys.
1990      * <p>
1991      * The timeout argument is interpreted as an integer value. A timeout of zero means instead to
1992      * block forever.
1993      * <p>
1994      * <b>Multiple clients blocking for the same keys</b>
1995      * <p>
1996      * Multiple clients can block for the same key. They are put into a queue, so the first to be
1997      * served will be the one that started to wait earlier, in a first-blpopping first-served fashion.
1998      * <p>
1999      * <b>blocking POP inside a MULTI/EXEC transaction</b>
2000      * <p>
2001      * BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies
2002      * in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis
2003      * transaction).
2004      * <p>
2005      * The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil
2006      * reply, exactly what happens when the timeout is reached. If you like science fiction, think at
2007      * it like if inside MULTI/EXEC the time will flow at infinite speed :)
2008      * <p>
2009      * Time complexity: O(1)
2010      * @see #blpop(int, string...)
2011      * @param timeout
2012      * @param keys
2013      * @return BLPOP returns a two-elements array via a multi bulk reply in order to return both the
2014      *         unblocking key and the popped value.
2015      *         <p>
2016      *         When a non-zero timeout is specified, and the BLPOP operation timed out, the return
2017      *         value is a nil multi bulk reply. Most client values will return false or nil
2018      *         accordingly to the programming language used.
2019      */
2020     List!(string) brpop(int timeout, string[] keys...) {
2021         return brpop(getArgsAddTimeout(timeout, keys));
2022     }
2023     alias brpop = BinaryRedis.brpop;
2024 
2025     Long zcount(string key, double min, double max) {
2026         checkIsInMultiOrPipeline();
2027         client.zcount(key, min, max);
2028         return client.getIntegerReply();
2029     }
2030     alias zcount = BinaryRedis.zcount;
2031 
2032     Long zcount(string key, string min, string max) {
2033         checkIsInMultiOrPipeline();
2034         client.zcount(key, min, max);
2035         return client.getIntegerReply();
2036     }
2037 
2038     /**
2039      * Return the all the elements in the sorted set at key with a score between min and max
2040      * (including elements with score equal to min or max).
2041      * <p>
2042      * The elements having the same score are returned sorted lexicographically as ASCII strings (this
2043      * follows from a property of Redis sorted sets and does not involve further computation).
2044      * <p>
2045      * Using the optional {@link #zrangeByScore(string, double, double, int, int) LIMIT} it's possible
2046      * to get only a range of the matching elements in an SQL-alike way. Note that if offset is large
2047      * the commands needs to traverse the list for offset elements and this adds up to the O(M)
2048      * figure.
2049      * <p>
2050      * The {@link #zcount(string, double, double) ZCOUNT} command is similar to
2051      * {@link #zrangeByScore(string, double, double) ZRANGEBYSCORE} but instead of returning the
2052      * actual elements in the specified interval, it just returns the number of matching elements.
2053      * <p>
2054      * <b>Exclusive intervals and infinity</b>
2055      * <p>
2056      * min and max can be -inf and +inf, so that you are not required to know what's the greatest or
2057      * smallest element in order to take, for instance, elements "up to a given value".
2058      * <p>
2059      * Also while the interval is for default closed (inclusive) it's possible to specify open
2060      * intervals prefixing the score with a "(" character, so for instance:
2061      * <p>
2062      * {@code ZRANGEBYSCORE zset (1.3 5}
2063      * <p>
2064      * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:
2065      * <p>
2066      * {@code ZRANGEBYSCORE zset (5 (10}
2067      * <p>
2068      * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).
2069      * <p>
2070      * <b>Time complexity:</b>
2071      * <p>
2072      * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
2073      * elements returned by the command, so if M is constant (for instance you always ask for the
2074      * first ten elements with LIMIT) you can consider it O(log(N))
2075      * @see #zrangeByScore(string, double, double)
2076      * @see #zrangeByScore(string, double, double, int, int)
2077      * @see #zrangeByScoreWithScores(string, double, double)
2078      * @see #zrangeByScoreWithScores(string, string, string)
2079      * @see #zrangeByScoreWithScores(string, double, double, int, int)
2080      * @see #zcount(string, double, double)
2081      * @param key
2082      * @param min a double or Double.NEGATIVE_INFINITY for "-inf"
2083      * @param max a double or Double.POSITIVE_INFINITY for "+inf"
2084      * @return Multi bulk reply specifically a list of elements in the specified score range.
2085      */
2086     Set!(string) zrangeByScore(string key, double min, double max) {
2087         checkIsInMultiOrPipeline();
2088         client.zrangeByScore(key, min, max);
2089         List!(string) members = client.getMultiBulkReply();
2090         return new SetFromList!string(members);
2091     }
2092     alias zrangeByScore = BinaryRedis.zrangeByScore;
2093 
2094     Set!(string) zrangeByScore(string key, string min, string max) {
2095         checkIsInMultiOrPipeline();
2096         client.zrangeByScore(key, min, max);
2097         List!(string) members = client.getMultiBulkReply();
2098         return new SetFromList!string(members);
2099     }
2100 
2101     /**
2102      * Return the all the elements in the sorted set at key with a score between min and max
2103      * (including elements with score equal to min or max).
2104      * <p>
2105      * The elements having the same score are returned sorted lexicographically as ASCII strings (this
2106      * follows from a property of Redis sorted sets and does not involve further computation).
2107      * <p>
2108      * Using the optional {@link #zrangeByScore(string, double, double, int, int) LIMIT} it's possible
2109      * to get only a range of the matching elements in an SQL-alike way. Note that if offset is large
2110      * the commands needs to traverse the list for offset elements and this adds up to the O(M)
2111      * figure.
2112      * <p>
2113      * The {@link #zcount(string, double, double) ZCOUNT} command is similar to
2114      * {@link #zrangeByScore(string, double, double) ZRANGEBYSCORE} but instead of returning the
2115      * actual elements in the specified interval, it just returns the number of matching elements.
2116      * <p>
2117      * <b>Exclusive intervals and infinity</b>
2118      * <p>
2119      * min and max can be -inf and +inf, so that you are not required to know what's the greatest or
2120      * smallest element in order to take, for instance, elements "up to a given value".
2121      * <p>
2122      * Also while the interval is for default closed (inclusive) it's possible to specify open
2123      * intervals prefixing the score with a "(" character, so for instance:
2124      * <p>
2125      * {@code ZRANGEBYSCORE zset (1.3 5}
2126      * <p>
2127      * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:
2128      * <p>
2129      * {@code ZRANGEBYSCORE zset (5 (10}
2130      * <p>
2131      * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).
2132      * <p>
2133      * <b>Time complexity:</b>
2134      * <p>
2135      * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
2136      * elements returned by the command, so if M is constant (for instance you always ask for the
2137      * first ten elements with LIMIT) you can consider it O(log(N))
2138      * @see #zrangeByScore(string, double, double)
2139      * @see #zrangeByScore(string, double, double, int, int)
2140      * @see #zrangeByScoreWithScores(string, double, double)
2141      * @see #zrangeByScoreWithScores(string, double, double, int, int)
2142      * @see #zcount(string, double, double)
2143      * @param key
2144      * @param min
2145      * @param max
2146      * @param offset
2147      * @param count
2148      * @return Multi bulk reply specifically a list of elements in the specified score range.
2149      */
2150     Set!(string) zrangeByScore(string key, double min, double max,
2151             int offset, int count) {
2152         checkIsInMultiOrPipeline();
2153         client.zrangeByScore(key, min, max, offset, count);
2154         List!(string) members = client.getMultiBulkReply();
2155         return new SetFromList!string(members);
2156     }
2157 
2158     Set!(string) zrangeByScore(string key, string min, string max,
2159             int offset, int count) {
2160         checkIsInMultiOrPipeline();
2161         client.zrangeByScore(key, min, max, offset, count);
2162         List!(string) members = client.getMultiBulkReply();
2163         return new SetFromList!string(members);
2164     }
2165 
2166     /**
2167      * Return the all the elements in the sorted set at key with a score between min and max
2168      * (including elements with score equal to min or max).
2169      * <p>
2170      * The elements having the same score are returned sorted lexicographically as ASCII strings (this
2171      * follows from a property of Redis sorted sets and does not involve further computation).
2172      * <p>
2173      * Using the optional {@link #zrangeByScore(string, double, double, int, int) LIMIT} it's possible
2174      * to get only a range of the matching elements in an SQL-alike way. Note that if offset is large
2175      * the commands needs to traverse the list for offset elements and this adds up to the O(M)
2176      * figure.
2177      * <p>
2178      * The {@link #zcount(string, double, double) ZCOUNT} command is similar to
2179      * {@link #zrangeByScore(string, double, double) ZRANGEBYSCORE} but instead of returning the
2180      * actual elements in the specified interval, it just returns the number of matching elements.
2181      * <p>
2182      * <b>Exclusive intervals and infinity</b>
2183      * <p>
2184      * min and max can be -inf and +inf, so that you are not required to know what's the greatest or
2185      * smallest element in order to take, for instance, elements "up to a given value".
2186      * <p>
2187      * Also while the interval is for default closed (inclusive) it's possible to specify open
2188      * intervals prefixing the score with a "(" character, so for instance:
2189      * <p>
2190      * {@code ZRANGEBYSCORE zset (1.3 5}
2191      * <p>
2192      * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:
2193      * <p>
2194      * {@code ZRANGEBYSCORE zset (5 (10}
2195      * <p>
2196      * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).
2197      * <p>
2198      * <b>Time complexity:</b>
2199      * <p>
2200      * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
2201      * elements returned by the command, so if M is constant (for instance you always ask for the
2202      * first ten elements with LIMIT) you can consider it O(log(N))
2203      * @see #zrangeByScore(string, double, double)
2204      * @see #zrangeByScore(string, double, double, int, int)
2205      * @see #zrangeByScoreWithScores(string, double, double)
2206      * @see #zrangeByScoreWithScores(string, double, double, int, int)
2207      * @see #zcount(string, double, double)
2208      * @param key
2209      * @param min
2210      * @param max
2211      * @return Multi bulk reply specifically a list of elements in the specified score range.
2212      */
2213     Set!(Tuple) zrangeByScoreWithScores(string key, double min, double max) {
2214         checkIsInMultiOrPipeline();
2215         client.zrangeByScoreWithScores(key, min, max);
2216         return getTupledSet();
2217     }
2218     alias zrangeByScoreWithScores = BinaryRedis.zrangeByScoreWithScores;
2219 
2220     Set!(Tuple) zrangeByScoreWithScores(string key, string min, string max) {
2221         checkIsInMultiOrPipeline();
2222         client.zrangeByScoreWithScores(key, min, max);
2223         return getTupledSet();
2224     }
2225 
2226     /**
2227      * Return the all the elements in the sorted set at key with a score between min and max
2228      * (including elements with score equal to min or max).
2229      * <p>
2230      * The elements having the same score are returned sorted lexicographically as ASCII strings (this
2231      * follows from a property of Redis sorted sets and does not involve further computation).
2232      * <p>
2233      * Using the optional {@link #zrangeByScore(string, double, double, int, int) LIMIT} it's possible
2234      * to get only a range of the matching elements in an SQL-alike way. Note that if offset is large
2235      * the commands needs to traverse the list for offset elements and this adds up to the O(M)
2236      * figure.
2237      * <p>
2238      * The {@link #zcount(string, double, double) ZCOUNT} command is similar to
2239      * {@link #zrangeByScore(string, double, double) ZRANGEBYSCORE} but instead of returning the
2240      * actual elements in the specified interval, it just returns the number of matching elements.
2241      * <p>
2242      * <b>Exclusive intervals and infinity</b>
2243      * <p>
2244      * min and max can be -inf and +inf, so that you are not required to know what's the greatest or
2245      * smallest element in order to take, for instance, elements "up to a given value".
2246      * <p>
2247      * Also while the interval is for default closed (inclusive) it's possible to specify open
2248      * intervals prefixing the score with a "(" character, so for instance:
2249      * <p>
2250      * {@code ZRANGEBYSCORE zset (1.3 5}
2251      * <p>
2252      * Will return all the values with score &gt; 1.3 and &lt;= 5, while for instance:
2253      * <p>
2254      * {@code ZRANGEBYSCORE zset (5 (10}
2255      * <p>
2256      * Will return all the values with score &gt; 5 and &lt; 10 (5 and 10 excluded).
2257      * <p>
2258      * <b>Time complexity:</b>
2259      * <p>
2260      * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
2261      * elements returned by the command, so if M is constant (for instance you always ask for the
2262      * first ten elements with LIMIT) you can consider it O(log(N))
2263      * @see #zrangeByScore(string, double, double)
2264      * @see #zrangeByScore(string, double, double, int, int)
2265      * @see #zrangeByScoreWithScores(string, double, double)
2266      * @see #zrangeByScoreWithScores(string, double, double, int, int)
2267      * @see #zcount(string, double, double)
2268      * @param key
2269      * @param min
2270      * @param max
2271      * @param offset
2272      * @param count
2273      * @return Multi bulk reply specifically a list of elements in the specified score range.
2274      */
2275     Set!(Tuple) zrangeByScoreWithScores(string key, double min, double max,
2276             int offset, int count) {
2277         checkIsInMultiOrPipeline();
2278         client.zrangeByScoreWithScores(key, min, max, offset, count);
2279         return getTupledSet();
2280     }
2281 
2282     Set!(Tuple) zrangeByScoreWithScores(string key, string min, string max,
2283             int offset, int count) {
2284         checkIsInMultiOrPipeline();
2285         client.zrangeByScoreWithScores(key, min, max, offset, count);
2286         return getTupledSet();
2287     }
2288 
2289     Set!(string) zrevrangeByScore(string key, double max, double min) {
2290         checkIsInMultiOrPipeline();
2291         client.zrevrangeByScore(key, max, min);
2292         List!(string) members = client.getMultiBulkReply();
2293         return new SetFromList!string(members);
2294     }
2295     alias zrevrangeByScore = BinaryRedis.zrevrangeByScore;
2296 
2297     Set!(string) zrevrangeByScore(string key, string max, string min) {
2298         checkIsInMultiOrPipeline();
2299         client.zrevrangeByScore(key, max, min);
2300         List!(string) members = client.getMultiBulkReply();
2301         return new SetFromList!string(members);
2302     }
2303 
2304     Set!(string) zrevrangeByScore(string key, double max, double min,
2305             int offset, int count) {
2306         checkIsInMultiOrPipeline();
2307         client.zrevrangeByScore(key, max, min, offset, count);
2308         List!(string) members = client.getMultiBulkReply();
2309         return new SetFromList!string(members);
2310     }
2311 
2312     Set!(Tuple) zrevrangeByScoreWithScores(string key, double max, double min) {
2313         checkIsInMultiOrPipeline();
2314         client.zrevrangeByScoreWithScores(key, max, min);
2315         return getTupledSet();
2316     }
2317     alias zrevrangeByScoreWithScores = BinaryRedis.zrevrangeByScoreWithScores;
2318 
2319     Set!(Tuple) zrevrangeByScoreWithScores(string key, double max,
2320             double min, int offset, int count) {
2321         checkIsInMultiOrPipeline();
2322         client.zrevrangeByScoreWithScores(key, max, min, offset, count);
2323         return getTupledSet();
2324     }
2325 
2326     Set!(Tuple) zrevrangeByScoreWithScores(string key, string max,
2327             string min, int offset, int count) {
2328         checkIsInMultiOrPipeline();
2329         client.zrevrangeByScoreWithScores(key, max, min, offset, count);
2330         return getTupledSet();
2331     }
2332 
2333     Set!(string) zrevrangeByScore(string key, string max, string min,
2334             int offset, int count) {
2335         checkIsInMultiOrPipeline();
2336         client.zrevrangeByScore(key, max, min, offset, count);
2337         List!(string) members = client.getMultiBulkReply();
2338         return new SetFromList!string(members);
2339     }
2340 
2341     Set!(Tuple) zrevrangeByScoreWithScores(string key, string max, string min) {
2342         checkIsInMultiOrPipeline();
2343         client.zrevrangeByScoreWithScores(key, max, min);
2344         return getTupledSet();
2345     }
2346 
2347     /**
2348      * Remove all elements in the sorted set at key with rank between start and end. Start and end are
2349      * 0-based with rank 0 being the element with the lowest score. Both start and end can be negative
2350      * numbers, where they indicate offsets starting at the element with the highest rank. For
2351      * example: -1 is the element with the highest score, -2 the element with the second highest score
2352      * and so forth.
2353      * <p>
2354      * <b>Time complexity:</b> O(log(N))+O(M) with N being the number of elements in the sorted set
2355      * and M the number of elements removed by the operation
2356      * @param key
2357      * @param start
2358      * @param stop
2359      * @return 
2360      */
2361     Long zremrangeByRank(string key, long start, long stop) {
2362         checkIsInMultiOrPipeline();
2363         client.zremrangeByRank(key, start, stop);
2364         return client.getIntegerReply();
2365     }
2366     alias zremrangeByRank = BinaryRedis.zremrangeByRank;
2367 
2368     /**
2369      * Remove all the elements in the sorted set at key with a score between min and max (including
2370      * elements with score equal to min or max).
2371      * <p>
2372      * <b>Time complexity:</b>
2373      * <p>
2374      * O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of
2375      * elements removed by the operation
2376      * @param key
2377      * @param min
2378      * @param max
2379      * @return Integer reply, specifically the number of elements removed.
2380      */
2381     Long zremrangeByScore(string key, double min, double max) {
2382         checkIsInMultiOrPipeline();
2383         client.zremrangeByScore(key, min, max);
2384         return client.getIntegerReply();
2385     }
2386     alias zremrangeByScore = BinaryRedis.zremrangeByScore;
2387 
2388     Long zremrangeByScore(string key, string min, string max) {
2389         checkIsInMultiOrPipeline();
2390         client.zremrangeByScore(key, min, max);
2391         return client.getIntegerReply();
2392     }
2393 
2394     /**
2395      * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at
2396      * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys
2397      * and the other (optional) arguments.
2398      * <p>
2399      * As the terms imply, the {@link #zinterstore(string, string...) ZINTERSTORE} command requires an
2400      * element to be present in each of the given inputs to be inserted in the result. The
2401      * {@link #zunionstore(string, string...) ZUNIONSTORE} command inserts all elements across all
2402      * inputs.
2403      * <p>
2404      * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means
2405      * that the score of each element in the sorted set is first multiplied by this weight before
2406      * being passed to the aggregation. When this option is not given, all weights default to 1.
2407      * <p>
2408      * With the AGGREGATE option, it's possible to specify how the results of the union or
2409      * intersection are aggregated. This option defaults to SUM, where the score of an element is
2410      * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the
2411      * resulting set will contain the minimum or maximum score of an element across the inputs where
2412      * it exists.
2413      * <p>
2414      * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input
2415      * sorted sets, and M being the number of elements in the resulting sorted set
2416      * @see #zunionstore(string, string...)
2417      * @see #zunionstore(string, ZParams, string...)
2418      * @see #zinterstore(string, string...)
2419      * @see #zinterstore(string, ZParams, string...)
2420      * @param dstkey
2421      * @param sets
2422      * @return Integer reply, specifically the number of elements in the sorted set at dstkey
2423      */
2424     Long zunionstore(string dstkey, string[] sets...) {
2425         checkIsInMultiOrPipeline();
2426         client.zunionstore(dstkey, sets);
2427         return client.getIntegerReply();
2428     }
2429     alias zunionstore = BinaryRedis.zunionstore;
2430 
2431     /**
2432      * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at
2433      * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys
2434      * and the other (optional) arguments.
2435      * <p>
2436      * As the terms imply, the {@link #zinterstore(string, string...) ZINTERSTORE} command requires an
2437      * element to be present in each of the given inputs to be inserted in the result. The
2438      * {@link #zunionstore(string, string...) ZUNIONSTORE} command inserts all elements across all
2439      * inputs.
2440      * <p>
2441      * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means
2442      * that the score of each element in the sorted set is first multiplied by this weight before
2443      * being passed to the aggregation. When this option is not given, all weights default to 1.
2444      * <p>
2445      * With the AGGREGATE option, it's possible to specify how the results of the union or
2446      * intersection are aggregated. This option defaults to SUM, where the score of an element is
2447      * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the
2448      * resulting set will contain the minimum or maximum score of an element across the inputs where
2449      * it exists.
2450      * <p>
2451      * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input
2452      * sorted sets, and M being the number of elements in the resulting sorted set
2453      * @see #zunionstore(string, string...)
2454      * @see #zunionstore(string, ZParams, string...)
2455      * @see #zinterstore(string, string...)
2456      * @see #zinterstore(string, ZParams, string...)
2457      * @param dstkey
2458      * @param sets
2459      * @param params
2460      * @return Integer reply, specifically the number of elements in the sorted set at dstkey
2461      */
2462     Long zunionstore(string dstkey, ZParams params, string[] sets...) {
2463         checkIsInMultiOrPipeline();
2464         client.zunionstore(dstkey, params, sets);
2465         return client.getIntegerReply();
2466     }
2467 
2468     /**
2469      * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at
2470      * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys
2471      * and the other (optional) arguments.
2472      * <p>
2473      * As the terms imply, the {@link #zinterstore(string, string...) ZINTERSTORE} command requires an
2474      * element to be present in each of the given inputs to be inserted in the result. The
2475      * {@link #zunionstore(string, string...) ZUNIONSTORE} command inserts all elements across all
2476      * inputs.
2477      * <p>
2478      * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means
2479      * that the score of each element in the sorted set is first multiplied by this weight before
2480      * being passed to the aggregation. When this option is not given, all weights default to 1.
2481      * <p>
2482      * With the AGGREGATE option, it's possible to specify how the results of the union or
2483      * intersection are aggregated. This option defaults to SUM, where the score of an element is
2484      * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the
2485      * resulting set will contain the minimum or maximum score of an element across the inputs where
2486      * it exists.
2487      * <p>
2488      * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input
2489      * sorted sets, and M being the number of elements in the resulting sorted set
2490      * @see #zunionstore(string, string...)
2491      * @see #zunionstore(string, ZParams, string...)
2492      * @see #zinterstore(string, string...)
2493      * @see #zinterstore(string, ZParams, string...)
2494      * @param dstkey
2495      * @param sets
2496      * @return Integer reply, specifically the number of elements in the sorted set at dstkey
2497      */
2498     Long zinterstore(string dstkey, string[] sets...) {
2499         checkIsInMultiOrPipeline();
2500         client.zinterstore(dstkey, sets);
2501         return client.getIntegerReply();
2502     }
2503     alias zinterstore = BinaryRedis.zinterstore;
2504 
2505     /**
2506      * Creates a union or intersection of N sorted sets given by keys k1 through kN, and stores it at
2507      * dstkey. It is mandatory to provide the number of input keys N, before passing the input keys
2508      * and the other (optional) arguments.
2509      * <p>
2510      * As the terms imply, the {@link #zinterstore(string, string...) ZINTERSTORE} command requires an
2511      * element to be present in each of the given inputs to be inserted in the result. The
2512      * {@link #zunionstore(string, string...) ZUNIONSTORE} command inserts all elements across all
2513      * inputs.
2514      * <p>
2515      * Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means
2516      * that the score of each element in the sorted set is first multiplied by this weight before
2517      * being passed to the aggregation. When this option is not given, all weights default to 1.
2518      * <p>
2519      * With the AGGREGATE option, it's possible to specify how the results of the union or
2520      * intersection are aggregated. This option defaults to SUM, where the score of an element is
2521      * summed across the inputs where it exists. When this option is set to be either MIN or MAX, the
2522      * resulting set will contain the minimum or maximum score of an element across the inputs where
2523      * it exists.
2524      * <p>
2525      * <b>Time complexity:</b> O(N) + O(M log(M)) with N being the sum of the sizes of the input
2526      * sorted sets, and M being the number of elements in the resulting sorted set
2527      * @see #zunionstore(string, string...)
2528      * @see #zunionstore(string, ZParams, string...)
2529      * @see #zinterstore(string, string...)
2530      * @see #zinterstore(string, ZParams, string...)
2531      * @param dstkey
2532      * @param sets
2533      * @param params
2534      * @return Integer reply, specifically the number of elements in the sorted set at dstkey
2535      */
2536     Long zinterstore(string dstkey, ZParams params, string[] sets...) {
2537         checkIsInMultiOrPipeline();
2538         client.zinterstore(dstkey, params, sets);
2539         return client.getIntegerReply();
2540     }
2541 
2542     Long zlexcount(string key, string min, string max) {
2543         checkIsInMultiOrPipeline();
2544         client.zlexcount(key, min, max);
2545         return client.getIntegerReply();
2546     }
2547     alias zlexcount = BinaryRedis.zlexcount;
2548 
2549     Set!(string) zrangeByLex(string key, string min, string max) {
2550         checkIsInMultiOrPipeline();
2551         client.zrangeByLex(key, min, max);
2552         List!(string) members = client.getMultiBulkReply();
2553         return new SetFromList!string(members);
2554     }
2555     alias zrangeByLex = BinaryRedis.zrangeByLex;
2556 
2557     Set!(string) zrangeByLex(string key, string min, string max,
2558             int offset, int count) {
2559         checkIsInMultiOrPipeline();
2560         client.zrangeByLex(key, min, max, offset, count);
2561         List!(string) members = client.getMultiBulkReply();
2562         return new SetFromList!string(members);
2563     }
2564 
2565     Set!(string) zrevrangeByLex(string key, string max, string min) {
2566         checkIsInMultiOrPipeline();
2567         client.zrevrangeByLex(key, max, min);
2568         List!(string) members = client.getMultiBulkReply();
2569         return new SetFromList!string(members);
2570     }
2571     alias zrevrangeByLex = BinaryRedis.zrevrangeByLex;
2572 
2573     Set!(string) zrevrangeByLex(string key, string max, string min, int offset, int count) {
2574         checkIsInMultiOrPipeline();
2575         client.zrevrangeByLex(key, max, min, offset, count);
2576         List!(string) members = client.getMultiBulkReply();
2577         return new SetFromList!string(members);
2578     }
2579 
2580     Long zremrangeByLex(string key, string min, string max) {
2581         checkIsInMultiOrPipeline();
2582         client.zremrangeByLex(key, min, max);
2583         return client.getIntegerReply();
2584     }
2585     alias zremrangeByLex = BinaryRedis.zremrangeByLex;
2586 
2587     Long strlen(string key) {
2588         checkIsInMultiOrPipeline();
2589         client.strlen(key);
2590         return client.getIntegerReply();
2591     }
2592     alias strlen = BinaryRedis.strlen;
2593 
2594     Long lpushx(string key, string[] string...) {
2595         checkIsInMultiOrPipeline();
2596         client.lpushx(key, string);
2597         return client.getIntegerReply();
2598     }
2599     alias lpushx = BinaryRedis.lpushx;
2600 
2601     /**
2602      * Undo a {@link #expire(string, int) expire} at turning the expire key into a normal key.
2603      * <p>
2604      * Time complexity: O(1)
2605      * @param key
2606      * @return Integer reply, specifically: 1: the key is now persist. 0: the key is not persist (only
2607      *         happens when key not set).
2608      */
2609     Long persist(string key) {
2610         client.persist(key);
2611         return client.getIntegerReply();
2612     }
2613     alias persist = BinaryRedis.persist;
2614 
2615     Long rpushx(string key, string[] string...) {
2616         checkIsInMultiOrPipeline();
2617         client.rpushx(key, string);
2618         return client.getIntegerReply();
2619     }
2620     alias rpushx = BinaryRedis.rpushx;
2621 
2622     string echo(string string) {
2623         checkIsInMultiOrPipeline();
2624         client.echo(string);
2625         return client.getBulkReply();
2626     }
2627     alias echo = BinaryRedis.echo;
2628 
2629     Long linsert(string key, ListPosition where, string pivot,
2630             string value) {
2631         checkIsInMultiOrPipeline();
2632         client.linsert(key, where, pivot, value);
2633         return client.getIntegerReply();
2634     }
2635     alias linsert = BinaryRedis.linsert;
2636 
2637     /**
2638      * Pop a value from a list, push it to another list and return it; or block until one is available
2639      * @param source
2640      * @param destination
2641      * @param timeout
2642      * @return the element
2643      */
2644     string brpoplpush(string source, string destination, int timeout) {
2645         client.brpoplpush(source, destination, timeout);
2646         client.setTimeoutInfinite();
2647         try {
2648             return client.getBulkReply();
2649         } finally {
2650             client.rollbackTimeout();
2651         }
2652     }
2653     alias brpoplpush = BinaryRedis.brpoplpush;
2654 
2655     /**
2656      * Sets or clears the bit at offset in the string value stored at key
2657      * @param key
2658      * @param offset
2659      * @param value
2660      * @return
2661      */
2662     bool setbit(string key, long offset, bool value) {
2663         checkIsInMultiOrPipeline();
2664         client.setbit(key, offset, value);
2665         return client.getIntegerReply() == 1;
2666     }
2667     alias setbit = BinaryRedis.setbit;
2668 
2669 
2670     bool setbit(string key, long offset, string value) {
2671         checkIsInMultiOrPipeline();
2672         client.setbit(key, offset, value);
2673         return client.getIntegerReply() == 1;
2674     }
2675     /**
2676      * Returns the bit value at offset in the string value stored at key
2677      * @param key
2678      * @param offset
2679      * @return
2680      */
2681     bool getbit(string key, long offset) {
2682         checkIsInMultiOrPipeline();
2683         client.getbit(key, offset);
2684         return client.getIntegerReply() == 1;
2685     }
2686     alias getbit = BinaryRedis.getbit;
2687 
2688     Long setrange(string key, long offset, string value) {
2689         checkIsInMultiOrPipeline();
2690         client.setrange(key, offset, value);
2691         return client.getIntegerReply();
2692     }
2693     alias setrange = BinaryRedis.setrange;
2694 
2695     string getrange(string key, long startOffset, long endOffset) {
2696         checkIsInMultiOrPipeline();
2697         client.getrange(key, startOffset, endOffset);
2698         return client.getBulkReply();
2699     }
2700     alias getrange = BinaryRedis.getrange;
2701 
2702     Long bitpos(string key, bool value) {
2703         return bitpos(key, value, new BitPosParams());
2704     }
2705     alias bitpos = BinaryRedis.bitpos;
2706 
2707     Long bitpos(string key, bool value, BitPosParams params) {
2708         checkIsInMultiOrPipeline();
2709         client.bitpos(key, value, params);
2710         return client.getIntegerReply();
2711     }
2712 
2713     /**
2714      * Retrieve the configuration of a running Redis server. Not all the configuration parameters are
2715      * supported.
2716      * <p>
2717      * CONFIG GET returns the current configuration parameters. This sub command only accepts a single
2718      * argument, that is glob style pattern. All the configuration parameters matching this parameter
2719      * are reported as a list of key-value pairs.
2720      * <p>
2721      * <b>Example:</b>
2722      * 
2723      * <pre>
2724      * $ redis-cli config get '*'
2725      * 1. "dbfilename"
2726      * 2. "dump.rdb"
2727      * 3. "requirepass"
2728      * 4. (nil)
2729      * 5. "masterauth"
2730      * 6. (nil)
2731      * 7. "maxmemory"
2732      * 8. "0\n"
2733      * 9. "appendfsync"
2734      * 10. "everysec"
2735      * 11. "save"
2736      * 12. "3600 1 300 100 60 10000"
2737      * 
2738      * $ redis-cli config get 'm*'
2739      * 1. "masterauth"
2740      * 2. (nil)
2741      * 3. "maxmemory"
2742      * 4. "0\n"
2743      * </pre>
2744      * @param pattern
2745      * @return Bulk reply.
2746      */
2747     List!(string) configGet(string pattern) {
2748         client.configGet(pattern);
2749         return client.getMultiBulkReply();
2750     }
2751 
2752     /**
2753      * Alter the configuration of a running Redis server. Not all the configuration parameters are
2754      * supported.
2755      * <p>
2756      * The list of configuration parameters supported by CONFIG SET can be obtained issuing a
2757      * {@link #configGet(string) CONFIG GET *} command.
2758      * <p>
2759      * The configuration set using CONFIG SET is immediately loaded by the Redis server that will
2760      * start acting as specified starting from the next command.
2761      * <p>
2762      * <b>Parameters value format</b>
2763      * <p>
2764      * The value of the configuration parameter is the same as the one of the same parameter in the
2765      * Redis configuration file, with the following exceptions:
2766      * <p>
2767      * <ul>
2768      * <li>The save parameter is a list of space-separated integers. Every pair of integers specify the
2769      * time and number of changes limit to trigger a save. For instance the command CONFIG SET save
2770      * "3600 10 60 10000" will configure the server to issue a background saving of the RDB file every
2771      * 3600 seconds if there are at least 10 changes in the dataset, and every 60 seconds if there are
2772      * at least 10000 changes. To completely disable automatic snapshots just set the parameter as an
2773      * empty string.
2774      * <li>All the integer parameters representing memory are returned and accepted only using bytes
2775      * as unit.
2776      * </ul>
2777      * @param parameter
2778      * @param value
2779      * @return Status code reply
2780      */
2781     string configSet(string parameter, string value) {
2782         client.configSet(parameter, value);
2783         return client.getStatusCodeReply();
2784     }
2785 
2786     Object eval(string script, int keyCount, string[] params...) {
2787         client.setTimeoutInfinite();
2788         try {
2789             client.eval(script, keyCount, params);
2790             return getEvalResult();
2791         } finally {
2792             client.rollbackTimeout();
2793         }
2794     }
2795 
2796     void subscribe(RedisPubSub redisPubSub, string[] channels...) {
2797         client.setTimeoutInfinite();
2798         try {
2799             redisPubSub.proceed(client, channels);
2800         } finally {
2801             client.rollbackTimeout();
2802         }
2803     }
2804 
2805     Long publish(string channel, string message) {
2806         checkIsInMultiOrPipeline();
2807         connect();
2808         client.publish(channel, message);
2809         return client.getIntegerReply();
2810     }
2811 
2812     void psubscribe(RedisPubSub redisPubSub, string[] patterns...) {
2813         checkIsInMultiOrPipeline();
2814         client.setTimeoutInfinite();
2815         try {
2816             redisPubSub.proceedWithPatterns(client, patterns);
2817         } finally {
2818             client.rollbackTimeout();
2819         }
2820     }
2821 
2822     static string[] getParams(List!(string) keys, List!(string) args) {
2823         int keyCount = keys.size();
2824         int argCount = args.size();
2825 
2826         string[] params = new string[keyCount + argCount];
2827 
2828         for (int i = 0; i < keyCount; i++)
2829             params[i] = keys.get(i);
2830 
2831         for (int i = 0; i < argCount; i++)
2832             params[keyCount + i] = args.get(i);
2833 
2834         return params;
2835     }
2836 
2837     Object eval(string script, List!(string) keys, List!(string) args) {
2838         return eval(script, keys.size(), getParams(keys, args));
2839     }
2840 
2841     Object eval(string script, string[] keys, string[] args) {
2842         return eval(script, cast(int)keys.length, keys ~ args);
2843     }
2844 
2845     Object eval(string script) {
2846         return eval(script, 0);
2847     }
2848 
2849     Object evalsha(string sha1) {
2850         return evalsha(sha1, 0);
2851     }
2852 
2853     private Object getEvalResult() {
2854         return evalResult(client.getOne());
2855     }
2856 
2857     private Object evalResult(Object result) {
2858         // FIXME: Needing refactor or cleanup -@zxp at 8/5/2019, 1:45:00 PM
2859         // 
2860 
2861         version(HUNT_REDIS_DEBUG) {
2862             warningf("result's type: %s", typeid(result));
2863         }
2864         return result;
2865         // if (result instanceof const(ubyte)[]) return SafeEncoder.encode((const(ubyte)[]) result);
2866 
2867         // if (result instanceof List<?>) {
2868         //   List<?> list = (List<?>) result;
2869         //   List!(Object) listResult = new ArrayList!(Object)(list.size());
2870         //   foreach(Object bin ; list) {
2871         //     listResult.add(evalResult(bin));
2872         //   }
2873 
2874         //   return listResult;
2875         // }
2876 
2877         // return result;
2878     }
2879 
2880     Object evalsha(string sha1, List!(string) keys, List!(string) args) {
2881         return evalsha(sha1, keys.size(), getParams(keys, args));
2882     }
2883 
2884     Object evalsha(string sha1, int keyCount, string[] params...) {
2885         checkIsInMultiOrPipeline();
2886         client.evalsha(sha1, keyCount, params);
2887         return getEvalResult();
2888     }
2889 
2890 // FIXME: Needing refactor or cleanup -@zxp at 7/15/2019, 11:39:47 AM
2891 // 
2892     // override
2893     bool scriptExists(string sha1) {
2894       string[] a = new string[1];
2895       a[0] = sha1;
2896       return scriptExists(a)[0];
2897     }
2898 
2899     alias scriptExists = BinaryRedis.scriptExists;
2900 
2901     // override
2902     bool[] scriptExists(string[] sha1...) {
2903       client.scriptExists(sha1);
2904     //   List!(long) result = client.getIntegerMultiBulkReply();
2905     //   List!(bool) exists = new ArrayList!(bool)();
2906 
2907     //   foreach(long value ; result)
2908     //     exists.add(value == 1);
2909 
2910     //   return exists;
2911         implementationMissing(false);
2912         return null;
2913     }
2914 
2915     string scriptLoad(string script) {
2916         client.scriptLoad(script);
2917         return client.getBulkReply();
2918     }
2919     alias scriptLoad = BinaryRedis.scriptLoad;
2920 
2921     List!(Slowlog) slowlogGet() {
2922         client.slowlogGet();
2923         return Slowlog.from(client.getObjectMultiBulkReply());
2924     }
2925 
2926     List!(Slowlog) slowlogGet(long entries) {
2927         client.slowlogGet(entries);
2928         return Slowlog.from(client.getObjectMultiBulkReply());
2929     }
2930 
2931     Long objectRefcount(string key) {
2932         client.objectRefcount(key);
2933         return client.getIntegerReply();
2934     }
2935     alias objectRefcount = BinaryRedis.objectRefcount;
2936 
2937     string objectEncoding(string key) {
2938         client.objectEncoding(key);
2939         return client.getBulkReply();
2940     }
2941     alias objectEncoding = BinaryRedis.objectEncoding;
2942 
2943     Long objectIdletime(string key) {
2944         client.objectIdletime(key);
2945         return client.getIntegerReply();
2946     }
2947     alias objectIdletime = BinaryRedis.objectIdletime;
2948 
2949     Long bitcount(string key) {
2950         checkIsInMultiOrPipeline();
2951         client.bitcount(key);
2952         return client.getIntegerReply();
2953     }
2954     alias bitcount = BinaryRedis.bitcount;
2955 
2956     Long bitcount(string key, long start, long end) {
2957         checkIsInMultiOrPipeline();
2958         client.bitcount(key, start, end);
2959         return client.getIntegerReply();
2960     }
2961 
2962     Long bitop(BitOP op, string destKey, string[] srcKeys...) {
2963         checkIsInMultiOrPipeline();
2964         client.bitop(op, destKey, srcKeys);
2965         return client.getIntegerReply();
2966     }
2967     alias bitop = BinaryRedis.bitop;
2968 
2969     /**
2970      * <pre>
2971      * redis 127.0.0.1:26381&gt; sentinel masters
2972      * 1)  1) "name"
2973      *     2) "mymaster"
2974      *     3) "ip"
2975      *     4) "127.0.0.1"
2976      *     5) "port"
2977      *     6) "6379"
2978      *     7) "runid"
2979      *     8) "93d4d4e6e9c06d0eea36e27f31924ac26576081d"
2980      *     9) "flags"
2981      *    10) "master"
2982      *    11) "pending-commands"
2983      *    12) "0"
2984      *    13) "last-ok-ping-reply"
2985      *    14) "423"
2986      *    15) "last-ping-reply"
2987      *    16) "423"
2988      *    17) "info-refresh"
2989      *    18) "6107"
2990      *    19) "num-slaves"
2991      *    20) "1"
2992      *    21) "num-other-sentinels"
2993      *    22) "2"
2994      *    23) "quorum"
2995      *    24) "2"
2996      * 
2997      * </pre>
2998      * @return
2999      */
3000     List!(Map!(string, string)) sentinelMasters() {
3001         client.sentinel(Protocol.SENTINEL_MASTERS);
3002         List!(Object) reply = client.getObjectMultiBulkReply();
3003 
3004         List!(Map!(string, string)) masters = new ArrayList!(Map!(string, string))();
3005         // foreach(Object obj ; reply) {
3006         //   masters.add(BuilderFactory.STRING_MAP.build((List) obj));
3007         // }
3008         implementationMissing(false);
3009         return masters;
3010     }
3011 
3012     /**
3013      * <pre>
3014      * redis 127.0.0.1:26381&gt; sentinel get-master-addr-by-name mymaster
3015      * 1) "127.0.0.1"
3016      * 2) "6379"
3017      * </pre>
3018      * @param masterName
3019      * @return two elements list of strings : host and port.
3020      */
3021     List!(string) sentinelGetMasterAddrByName(string masterName) {
3022         client.sentinel(Protocol.SENTINEL_GET_MASTER_ADDR_BY_NAME, masterName);
3023         List!(Object) reply = client.getObjectMultiBulkReply();
3024         return BuilderFactory.STRING_LIST.build(cast(Object)reply);
3025     }
3026 
3027     /**
3028      * <pre>
3029      * redis 127.0.0.1:26381&gt; sentinel reset mymaster
3030      * (integer) 1
3031      * </pre>
3032      * @param pattern
3033      * @return
3034      */
3035     Long sentinelReset(string pattern) {
3036         client.sentinel(Protocol.SENTINEL_RESET, pattern);
3037         return client.getIntegerReply();
3038     }
3039 
3040     /**
3041      * <pre>
3042      * redis 127.0.0.1:26381&gt; sentinel slaves mymaster
3043      * 1)  1) "name"
3044      *     2) "127.0.0.1:6380"
3045      *     3) "ip"
3046      *     4) "127.0.0.1"
3047      *     5) "port"
3048      *     6) "6380"
3049      *     7) "runid"
3050      *     8) "d7f6c0ca7572df9d2f33713df0dbf8c72da7c039"
3051      *     9) "flags"
3052      *    10) "slave"
3053      *    11) "pending-commands"
3054      *    12) "0"
3055      *    13) "last-ok-ping-reply"
3056      *    14) "47"
3057      *    15) "last-ping-reply"
3058      *    16) "47"
3059      *    17) "info-refresh"
3060      *    18) "657"
3061      *    19) "master-link-down-time"
3062      *    20) "0"
3063      *    21) "master-link-status"
3064      *    22) "ok"
3065      *    23) "master-host"
3066      *    24) "localhost"
3067      *    25) "master-port"
3068      *    26) "6379"
3069      *    27) "slave-priority"
3070      *    28) "100"
3071      * </pre>
3072      * @param masterName
3073      * @return
3074      */
3075     
3076     List!(Map!(string, string)) sentinelSlaves(string masterName) {
3077         client.sentinel(Protocol.SENTINEL_SLAVES, masterName);
3078         List!(Object) reply = client.getObjectMultiBulkReply();
3079 
3080         List!(Map!(string, string)) slaves = new ArrayList!(Map!(string, string))();
3081         // foreach(Object obj ; reply) {
3082         //   slaves.add(BuilderFactory.STRING_MAP.build((List) obj));
3083         // }
3084         implementationMissing(false);
3085         return slaves;
3086     }
3087 
3088     string sentinelFailover(string masterName) {
3089         client.sentinel(Protocol.SENTINEL_FAILOVER, masterName);
3090         return client.getStatusCodeReply();
3091     }
3092 
3093     string sentinelMonitor(string masterName, string ip, int port, int quorum) {
3094         client.sentinel(Protocol.SENTINEL_MONITOR, masterName, ip, to!string(port),
3095             to!string(quorum));
3096         return client.getStatusCodeReply();
3097     }
3098 
3099     string sentinelRemove(string masterName) {
3100         client.sentinel(Protocol.SENTINEL_REMOVE, masterName);
3101         return client.getStatusCodeReply();
3102     }
3103 
3104     string sentinelSet(string masterName, Map!(string, string) parameterMap) {
3105         int index = 0;
3106         int paramsLength = parameterMap.size() * 2 + 2;
3107         string[] params = new string[paramsLength];
3108 
3109         params[index++] = Protocol.SENTINEL_SET;
3110         params[index++] = masterName;
3111         foreach(string key, string value ; parameterMap) {
3112             params[index++] = key;
3113             params[index++] = value;
3114         }
3115 
3116         client.sentinel(params);
3117         return client.getStatusCodeReply();
3118     }
3119 
3120     const(ubyte)[] dump(string key) {
3121         checkIsInMultiOrPipeline();
3122         client.dump(key);
3123         return client.getBinaryBulkReply();
3124     }
3125     alias dump = BinaryRedis.dump;
3126 
3127     string restore(string key, int ttl, const(ubyte)[] serializedValue) {
3128         checkIsInMultiOrPipeline();
3129         client.restore(key, ttl, serializedValue);
3130         return client.getStatusCodeReply();
3131     }
3132     alias restore = BinaryRedis.restore;
3133 
3134     string restoreReplace(string key, int ttl, const(ubyte)[] serializedValue) {
3135         checkIsInMultiOrPipeline();
3136         client.restoreReplace(key, ttl, serializedValue);
3137         return client.getStatusCodeReply();
3138     }
3139     alias restoreReplace = BinaryRedis.restoreReplace;
3140 
3141     Long pexpire(string key, long milliseconds) {
3142         checkIsInMultiOrPipeline();
3143         client.pexpire(key, milliseconds);
3144         return client.getIntegerReply();
3145     }
3146     alias pexpire = BinaryRedis.pexpire;
3147 
3148     Long pexpireAt(string key, long millisecondsTimestamp) {
3149         checkIsInMultiOrPipeline();
3150         client.pexpireAt(key, millisecondsTimestamp);
3151         return client.getIntegerReply();
3152     }
3153     alias pexpireAt = BinaryRedis.pexpireAt;
3154 
3155     Long pttl(string key) {
3156         checkIsInMultiOrPipeline();
3157         client.pttl(key);
3158         return client.getIntegerReply();
3159     }
3160     alias pttl = BinaryRedis.pttl;
3161 
3162     /**
3163      * PSETEX works exactly like {@link #setex(string, int, string)} with the sole difference that the
3164      * expire time is specified in milliseconds instead of seconds. Time complexity: O(1)
3165      * @param key
3166      * @param milliseconds
3167      * @param value
3168      * @return Status code reply
3169      */
3170     string psetex(string key, long milliseconds, string value) {
3171         checkIsInMultiOrPipeline();
3172         client.psetex(key, milliseconds, value);
3173         return client.getStatusCodeReply();
3174     }
3175     alias psetex = BinaryRedis.psetex;
3176 
3177     string clientKill(string ipPort) {
3178         checkIsInMultiOrPipeline();
3179         this.client.clientKill(ipPort);
3180         return this.client.getStatusCodeReply();
3181     }
3182 
3183     override string clientKill(string ip, int port) {
3184         return super.clientKill(ip, port);
3185     }
3186 
3187     override Long clientKill(ClientKillParams params) {
3188         return super.clientKill(params);
3189     }
3190 
3191     string clientGetname() {
3192         checkIsInMultiOrPipeline();
3193         client.clientGetname();
3194         return client.getBulkReply();
3195     }
3196 
3197     string clientList() {
3198         checkIsInMultiOrPipeline();
3199         client.clientList();
3200         return client.getBulkReply();
3201     }
3202 
3203     string clientSetname(string name) {
3204         checkIsInMultiOrPipeline();
3205         client.clientSetname(name);
3206         return client.getStatusCodeReply();
3207     }
3208 
3209     string migrate(string host, int port, string key,
3210             int destinationDb, int timeout) {
3211         checkIsInMultiOrPipeline();
3212         client.migrate(host, port, key, destinationDb, timeout);
3213         return client.getStatusCodeReply();
3214     }
3215 
3216     string migrate(string host, int port, int destinationDB,
3217             int timeout, MigrateParams params, string[] keys...) {
3218         checkIsInMultiOrPipeline();
3219         client.migrate(host, port, destinationDB, timeout, params, keys);
3220         return client.getStatusCodeReply();
3221     }
3222 
3223     ScanResult!(string) scan(string cursor) {
3224         return scan(cursor, new ScanParams());
3225     }
3226     alias scan = BinaryRedis.scan;
3227 
3228     ScanResult!(string) scan(string cursor, ScanParams params) {
3229         checkIsInMultiOrPipeline();
3230         client.scan(cursor, params);
3231         List!(Object) result = client.getObjectMultiBulkReply();
3232         // string newcursor = new string((const(ubyte)[]) result.get(0));
3233         // List!(string) results = new ArrayList!(string)();
3234         // List!(const(ubyte)[]) rawResults = (List!(const(ubyte)[])) result.get(1);
3235         // foreach(const(ubyte)[] bs ; rawResults) {
3236         //   results.add(SafeEncoder.encode(bs));
3237         // }
3238         // return new ScanResult!(string)(newcursor, results);
3239 
3240         implementationMissing();
3241         return null;    
3242     }
3243 
3244     ScanResult!(MapEntry!(string, string)) hscan(string key, string cursor) {
3245         return hscan(key, cursor, new ScanParams());
3246     }
3247     alias hscan = BinaryRedis.hscan;
3248 
3249     ScanResult!(MapEntry!(string, string)) hscan(string key, string cursor,
3250             ScanParams params) {
3251         checkIsInMultiOrPipeline();
3252         client.hscan(key, cursor, params);
3253         List!(Object) result = client.getObjectMultiBulkReply();
3254         // string newcursor = new string((const(ubyte)[]) result.get(0));
3255         // List!(MapEntry!(string, string)) results = new ArrayList!(MapEntry!(string, string))();
3256         // List!(const(ubyte)[]) rawResults = cast(List!(const(ubyte)[])) result.get(1);
3257         // Iterator!(const(ubyte)[]) iterator = rawResults.iterator();
3258         // while (iterator.hasNext()) {
3259         //   results.add(new AbstractMap.SimpleEntry!(string, string)(SafeEncoder.encode(iterator.next()),
3260         //       SafeEncoder.encode(iterator.next())));
3261         // }
3262         // return new ScanResult!(MapEntry!(string, string))(newcursor, results);
3263 
3264         implementationMissing();
3265         return null;
3266     }
3267 
3268     ScanResult!(string) sscan(string key, string cursor) {
3269         return sscan(key, cursor, new ScanParams());
3270     }
3271     alias sscan = BinaryRedis.sscan;
3272 
3273     ScanResult!(string) sscan(string key, string cursor, ScanParams params) {
3274         checkIsInMultiOrPipeline();
3275         client.sscan(key, cursor, params);
3276         List!(Object) result = client.getObjectMultiBulkReply();
3277         // string newcursor = new string((const(ubyte)[]) result.get(0));
3278         // List!(string) results = new ArrayList!(string)();
3279         // List!(const(ubyte)[]) rawResults = (List!(const(ubyte)[])) result.get(1);
3280         // foreach(const(ubyte)[] bs ; rawResults) {
3281         //   results.add(SafeEncoder.encode(bs));
3282         // }
3283         // return new ScanResult!(string)(newcursor, results);
3284         implementationMissing();
3285         return null;
3286 
3287     }
3288 
3289     ScanResult!(Tuple) zscan(string key, string cursor) {
3290         return zscan(key, cursor, new ScanParams());
3291     }
3292     alias zscan = BinaryRedis.zscan;
3293 
3294     ScanResult!(Tuple) zscan(string key, string cursor, ScanParams params) {
3295         checkIsInMultiOrPipeline();
3296         client.zscan(key, cursor, params);
3297         List!(Object) result = client.getObjectMultiBulkReply();
3298         // string newcursor = new string((const(ubyte)[]) result.get(0));
3299         // List!(Tuple) results = new ArrayList!(Tuple)();
3300         // List!(const(ubyte)[]) rawResults = (List!(const(ubyte)[])) result.get(1);
3301         // Iterator!(const(ubyte)[]) iterator = rawResults.iterator();
3302         // while (iterator.hasNext()) {
3303         //   results.add(new Tuple(iterator.next(), BuilderFactory.DOUBLE.build(iterator.next())));
3304         // }
3305         // return new ScanResult!(Tuple)(newcursor, results);
3306         implementationMissing();
3307         return null;
3308 
3309     }
3310 
3311     string clusterNodes() {
3312         checkIsInMultiOrPipeline();
3313         client.clusterNodes();
3314         return client.getBulkReply();
3315     }
3316 
3317     string readonly() {
3318         client.readonly();
3319         return client.getStatusCodeReply();
3320     }
3321 
3322     string clusterMeet(string ip, int port) {
3323         checkIsInMultiOrPipeline();
3324         client.clusterMeet(ip, port);
3325         return client.getStatusCodeReply();
3326     }
3327 
3328     string clusterReset(ClusterReset resetType) {
3329         checkIsInMultiOrPipeline();
3330         client.clusterReset(resetType);
3331         return client.getStatusCodeReply();
3332     }
3333 
3334     string clusterAddSlots(int[] slots...) {
3335         checkIsInMultiOrPipeline();
3336         client.clusterAddSlots(slots);
3337         return client.getStatusCodeReply();
3338     }
3339 
3340     string clusterDelSlots(int[] slots...) {
3341         checkIsInMultiOrPipeline();
3342         client.clusterDelSlots(slots);
3343         return client.getStatusCodeReply();
3344     }
3345 
3346     string clusterInfo() {
3347         checkIsInMultiOrPipeline();
3348         client.clusterInfo();
3349         return client.getStatusCodeReply();
3350     }
3351 
3352     List!(string) clusterGetKeysInSlot(int slot, int count) {
3353         checkIsInMultiOrPipeline();
3354         client.clusterGetKeysInSlot(slot, count);
3355         return client.getMultiBulkReply();
3356     }
3357 
3358     string clusterSetSlotNode(int slot, string nodeId) {
3359         checkIsInMultiOrPipeline();
3360         client.clusterSetSlotNode(slot, nodeId);
3361         return client.getStatusCodeReply();
3362     }
3363 
3364     string clusterSetSlotMigrating(int slot, string nodeId) {
3365         checkIsInMultiOrPipeline();
3366         client.clusterSetSlotMigrating(slot, nodeId);
3367         return client.getStatusCodeReply();
3368     }
3369 
3370     string clusterSetSlotImporting(int slot, string nodeId) {
3371         checkIsInMultiOrPipeline();
3372         client.clusterSetSlotImporting(slot, nodeId);
3373         return client.getStatusCodeReply();
3374     }
3375 
3376     string clusterSetSlotStable(int slot) {
3377         checkIsInMultiOrPipeline();
3378         client.clusterSetSlotStable(slot);
3379         return client.getStatusCodeReply();
3380     }
3381 
3382     string clusterForget(string nodeId) {
3383         checkIsInMultiOrPipeline();
3384         client.clusterForget(nodeId);
3385         return client.getStatusCodeReply();
3386     }
3387 
3388     string clusterFlushSlots() {
3389         checkIsInMultiOrPipeline();
3390         client.clusterFlushSlots();
3391         return client.getStatusCodeReply();
3392     }
3393 
3394     Long clusterKeySlot(string key) {
3395         checkIsInMultiOrPipeline();
3396         client.clusterKeySlot(key);
3397         return client.getIntegerReply();
3398     }
3399 
3400     Long clusterCountKeysInSlot(int slot) {
3401         checkIsInMultiOrPipeline();
3402         client.clusterCountKeysInSlot(slot);
3403         return client.getIntegerReply();
3404     }
3405 
3406     string clusterSaveConfig() {
3407         checkIsInMultiOrPipeline();
3408         client.clusterSaveConfig();
3409         return client.getStatusCodeReply();
3410     }
3411 
3412     string clusterReplicate(string nodeId) {
3413         checkIsInMultiOrPipeline();
3414         client.clusterReplicate(nodeId);
3415         return client.getStatusCodeReply();
3416     }
3417 
3418     List!(string) clusterSlaves(string nodeId) {
3419         checkIsInMultiOrPipeline();
3420         client.clusterSlaves(nodeId);
3421         return client.getMultiBulkReply();
3422     }
3423 
3424     string clusterFailover() {
3425         checkIsInMultiOrPipeline();
3426         client.clusterFailover();
3427         return client.getStatusCodeReply();
3428     }
3429 
3430     List!(Object) clusterSlots() {
3431         checkIsInMultiOrPipeline();
3432         client.clusterSlots();
3433         return client.getObjectMultiBulkReply();
3434     }
3435 
3436     string asking() {
3437         checkIsInMultiOrPipeline();
3438         client.asking();
3439         return client.getStatusCodeReply();
3440     }
3441 
3442     List!(string) pubsubChannels(string pattern) {
3443         checkIsInMultiOrPipeline();
3444         client.pubsubChannels(pattern);
3445         return client.getMultiBulkReply();
3446     }
3447 
3448     Long pubsubNumPat() {
3449         checkIsInMultiOrPipeline();
3450         client.pubsubNumPat();
3451         return client.getIntegerReply();
3452     }
3453 
3454     Map!(string, string) pubsubNumSub(string[] channels...) {
3455         checkIsInMultiOrPipeline();
3456         client.pubsubNumSub(channels);
3457         return BuilderFactory.PUBSUB_NUMSUB_MAP.build(cast(Object)client.getBinaryMultiBulkReply());
3458     }
3459 
3460     // override void close() {
3461     //     // if (dataSource !is null) {
3462     //     //     RedisPoolAbstract pool = this.dataSource;
3463     //     //     this.dataSource = null;
3464     //     //     if (client.isBroken()) {
3465     //     //         pool.returnBrokenResource(this);
3466     //     //     } else {
3467     //     //         pool.returnResource(this);
3468     //     //     }
3469     //     // } else {
3470     //     //     warning("The connnection has already closed!");
3471     //     //     // super.close();
3472     //     // }
3473 
3474     //     warning("The connnection has already closed!");
3475 
3476     //     super.close();
3477     // }
3478 
3479     // void setDataSource(RedisPoolAbstract redisPool) {
3480     //     this.dataSource = redisPool;
3481     // }
3482 
3483     Long pfadd(string key, string[] elements...) {
3484         checkIsInMultiOrPipeline();
3485         client.pfadd(key, elements);
3486         return client.getIntegerReply();
3487     }
3488     alias pfadd = BinaryRedis.pfadd;
3489 
3490     Long pfcount(string key) {
3491         checkIsInMultiOrPipeline();
3492         client.pfcount(key);
3493         return client.getIntegerReply();
3494     }
3495     alias pfcount = BinaryRedis.pfcount;
3496 
3497     Long pfcount(string[] keys...) {
3498         checkIsInMultiOrPipeline();
3499         client.pfcount(keys);
3500         return client.getIntegerReply();
3501     }
3502 
3503     string pfmerge(string destkey, string[] sourcekeys...) {
3504         checkIsInMultiOrPipeline();
3505         client.pfmerge(destkey, sourcekeys);
3506         return client.getStatusCodeReply();
3507     }
3508     alias pfmerge = BinaryRedis.pfmerge;
3509 
3510     List!(string) blpop(int timeout, string key) {
3511         return blpop(key, to!string(timeout));
3512     }
3513     alias blpop = BinaryRedis.blpop;
3514 
3515     List!(string) brpop(int timeout, string key) {
3516         return brpop(key, to!string(timeout));
3517     }
3518     alias brpop = BinaryRedis.brpop;
3519 
3520     Long geoadd(string key, double longitude, double latitude, string member) {
3521         checkIsInMultiOrPipeline();
3522         client.geoadd(key, longitude, latitude, member);
3523         return client.getIntegerReply();
3524     }
3525     alias geoadd = BinaryRedis.geoadd;
3526 
3527     Long geoadd(string key, Map!(string, GeoCoordinate) memberCoordinateMap) {
3528         checkIsInMultiOrPipeline();
3529         client.geoadd(key, memberCoordinateMap);
3530         return client.getIntegerReply();
3531     }
3532 
3533     Double geodist(string key, string member1, string member2) {
3534         checkIsInMultiOrPipeline();
3535         client.geodist(key, member1, member2);
3536         string dval = client.getBulkReply();
3537         return (dval !is null ? new Double(dval) : null);
3538     }
3539     alias geodist = BinaryRedis.geodist;
3540 
3541     Double geodist(string key, string member1, string member2, GeoUnit unit) {
3542         checkIsInMultiOrPipeline();
3543         client.geodist(key, member1, member2, unit);
3544         string dval = client.getBulkReply();
3545         return (dval !is null ? new Double(dval) : null);
3546     }
3547 
3548     List!(string) geohash(string key, string[] members...) {
3549         checkIsInMultiOrPipeline();
3550         client.geohash(key, members);
3551         return client.getMultiBulkReply();
3552     }
3553     alias geohash = BinaryRedis.geohash;
3554 
3555     List!(GeoCoordinate) geopos(string key, string[] members...) {
3556         checkIsInMultiOrPipeline();
3557         client.geopos(key, members);
3558         return BuilderFactory.GEO_COORDINATE_LIST.build(cast(Object)client.getObjectMultiBulkReply());
3559     }
3560     alias geopos = BinaryRedis.geopos;
3561 
3562     List!(GeoRadiusResponse) georadius(string key, double longitude, double latitude,
3563             double radius, GeoUnit unit) {
3564         checkIsInMultiOrPipeline();
3565         client.georadius(key, longitude, latitude, radius, unit);
3566         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3567     }
3568     alias georadius = BinaryRedis.georadius;
3569 
3570     List!(GeoRadiusResponse) georadiusReadonly(string key, double longitude, double latitude,
3571             double radius, GeoUnit unit) {
3572         checkIsInMultiOrPipeline();
3573         client.georadiusReadonly(key, longitude, latitude, radius, unit);
3574         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3575     }
3576     alias georadiusReadonly = BinaryRedis.georadiusReadonly;
3577 
3578     List!(GeoRadiusResponse) georadius(string key, double longitude, double latitude,
3579             double radius, GeoUnit unit, GeoRadiusParam param) {
3580         checkIsInMultiOrPipeline();
3581         client.georadius(key, longitude, latitude, radius, unit, param);
3582         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3583     }
3584     alias georadius = BinaryRedis.georadius;
3585 
3586     List!(GeoRadiusResponse) georadiusReadonly(string key, double longitude, double latitude,
3587             double radius, GeoUnit unit, GeoRadiusParam param) {
3588         checkIsInMultiOrPipeline();
3589         client.georadiusReadonly(key, longitude, latitude, radius, unit, param);
3590         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3591     }
3592 
3593     List!(GeoRadiusResponse) georadiusByMember(string key, string member, double radius,
3594             GeoUnit unit) {
3595         checkIsInMultiOrPipeline();
3596         client.georadiusByMember(key, member, radius, unit);
3597         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3598     }
3599     alias georadiusByMember = BinaryRedis.georadiusByMember;
3600 
3601     List!(GeoRadiusResponse) georadiusByMemberReadonly(string key, string member, double radius,
3602             GeoUnit unit) {
3603         checkIsInMultiOrPipeline();
3604         client.georadiusByMemberReadonly(key, member, radius, unit);
3605         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3606     }
3607     alias georadiusByMemberReadonly = BinaryRedis.georadiusByMemberReadonly;
3608 
3609     List!(GeoRadiusResponse) georadiusByMember(string key, string member, double radius,
3610             GeoUnit unit, GeoRadiusParam param) {
3611         checkIsInMultiOrPipeline();
3612         client.georadiusByMember(key, member, radius, unit, param);
3613         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3614     }
3615 
3616     List!(GeoRadiusResponse) georadiusByMemberReadonly(string key, string member, double radius,
3617             GeoUnit unit, GeoRadiusParam param) {
3618         checkIsInMultiOrPipeline();
3619         client.georadiusByMemberReadonly(key, member, radius, unit, param);
3620         return BuilderFactory.GEORADIUS_WITH_PARAMS_RESULT.build(cast(Object)client.getObjectMultiBulkReply());
3621     }
3622 
3623     string moduleLoad(string path) {
3624         client.moduleLoad(path);
3625         return client.getStatusCodeReply();
3626     }
3627 
3628     string moduleUnload(string name) {
3629         client.moduleUnload(name);
3630         return client.getStatusCodeReply();
3631     }
3632 
3633     List!(Module) moduleList() {
3634         client.moduleList();
3635         return BuilderFactory.MODULE_LIST.build(cast(Object)client.getObjectMultiBulkReply());
3636     }
3637 
3638     List!(long) bitfield(string key, string[] arguments...) {
3639         checkIsInMultiOrPipeline();
3640         client.bitfield(key, arguments);
3641         return client.getIntegerMultiBulkReply();
3642     }
3643     alias bitfield = BinaryRedis.bitfield;
3644 
3645     Long hstrlen(string key, string field) {
3646         checkIsInMultiOrPipeline();
3647         client.hstrlen(key, field);
3648         return client.getIntegerReply();
3649     }
3650     alias hstrlen = BinaryRedis.hstrlen;
3651 
3652     string memoryDoctor() {
3653         checkIsInMultiOrPipeline();
3654         client.memoryDoctor();
3655         return client.getBulkReply();
3656     }
3657             
3658     StreamEntryID xadd(string key, StreamEntryID id, Map!(string, string) hash) {
3659         return xadd(key, id, hash, long.max, false);
3660     }
3661     alias xadd = BinaryRedis.xadd;
3662     
3663     StreamEntryID xadd(string key, StreamEntryID id, Map!(string, string) hash, long maxLen, bool approximateLength) {
3664         checkIsInMultiOrPipeline();
3665         client.xadd(key, id, hash, maxLen, approximateLength);
3666         string result = client.getBulkReply();
3667         return new StreamEntryID(result);
3668     }
3669 
3670     Long xlen(string key) {
3671         checkIsInMultiOrPipeline();
3672         client.xlen(key);
3673         return client.getIntegerReply();
3674     }
3675     alias xlen = BinaryRedis.xlen;
3676 
3677     /**
3678      * {@inheritDoc}
3679      */
3680     List!(StreamEntry) xrange(string key, StreamEntryID start, StreamEntryID end, int count) {
3681         checkIsInMultiOrPipeline();
3682         client.xrange(key, start, end, count);
3683         return BuilderFactory.STREAM_ENTRY_LIST.build(cast(Object)client.getObjectMultiBulkReply());
3684     }
3685     alias xrange = BinaryRedis.xrange;
3686     
3687     /**
3688      * {@inheritDoc}
3689      */
3690     List!(StreamEntry) xrevrange(string key, StreamEntryID end, StreamEntryID start, int count) {
3691         checkIsInMultiOrPipeline();
3692         client.xrevrange(key, end, start, count);
3693         return BuilderFactory.STREAM_ENTRY_LIST.build(cast(Object)client.getObjectMultiBulkReply());
3694     }
3695     alias xrevrange = BinaryRedis.xrevrange;
3696 
3697 
3698     /**
3699      * {@inheritDoc}
3700      */
3701     List!(MapEntry!(string, List!(StreamEntry))) xread(int count, long block, MapEntry!(string, StreamEntryID)[] streams...) {
3702         checkIsInMultiOrPipeline();
3703         client.xread(count, block, streams);
3704         client.setTimeoutInfinite();
3705         
3706         try {
3707             List!(Object) streamsEntries = client.getObjectMultiBulkReply();
3708             if(streamsEntries is null) {
3709                 return new ArrayList!(MapEntry!(string, List!(StreamEntry)))();
3710             }
3711             
3712             List!(MapEntry!(string, List!(StreamEntry))) result = 
3713                 new ArrayList!(MapEntry!(string, List!(StreamEntry)))(streamsEntries.size());
3714 
3715             // foreach(Object streamObj ; streamsEntries) {
3716             //   List!(Object) stream = cast(List!(Object))streamObj;
3717             //   string streamId = SafeEncoder.encode((const(ubyte)[])stream.get(0));
3718             //   List!(StreamEntry) streamEntries = BuilderFactory.STREAM_ENTRY_LIST.build(stream.get(1));
3719             //   result.add(new AbstractMap.SimpleEntry!(string, List!(StreamEntry))(streamId, streamEntries));
3720             // }
3721             implementationMissing(false);      
3722             return result;
3723         } finally {
3724             client.rollbackTimeout();
3725         }
3726     }
3727     alias xread = BinaryRedis.xread;
3728 
3729     /**
3730      * {@inheritDoc}
3731      */
3732     Long xack(string key, string group, StreamEntryID[] ids...) {
3733         checkIsInMultiOrPipeline();
3734         client.xack(key, group, ids);
3735         return client.getIntegerReply();
3736     }
3737     alias xack = BinaryRedis.xack;
3738 
3739     string xgroupCreate(string key, string groupname, StreamEntryID id, bool makeStream) {
3740         checkIsInMultiOrPipeline();
3741         client.xgroupCreate(key, groupname, id, makeStream);
3742         return client.getStatusCodeReply();
3743     }
3744     alias xgroupCreate = BinaryRedis.xgroupCreate;
3745 
3746     string xgroupSetID(string key, string groupname, StreamEntryID id) {
3747         checkIsInMultiOrPipeline();
3748         client.xgroupSetID(key, groupname, id);
3749         return client.getStatusCodeReply();
3750     }
3751     alias xgroupSetID = BinaryRedis.xgroupSetID;
3752 
3753     Long xgroupDestroy(string key, string groupname) {
3754         checkIsInMultiOrPipeline();
3755         client.xgroupDestroy(key, groupname);
3756         return client.getIntegerReply();
3757     }
3758     alias xgroupDestroy = BinaryRedis.xgroupDestroy;
3759 
3760     string xgroupDelConsumer(string key, string groupname, string consumerName) {
3761         checkIsInMultiOrPipeline();
3762         client.xgroupDelConsumer(key, groupname, consumerName);
3763         return client.getStatusCodeReply();
3764     }
3765     alias xgroupDelConsumer = BinaryRedis.xgroupDelConsumer;
3766 
3767     Long xdel(string key, StreamEntryID[] ids...) {
3768         checkIsInMultiOrPipeline();
3769         client.xdel(key, ids);
3770         return client.getIntegerReply();
3771     }
3772     alias xdel = BinaryRedis.xdel;
3773 
3774     Long xtrim(string key, long maxLen, bool approximateLength) {
3775         checkIsInMultiOrPipeline();
3776         client.xtrim(key, maxLen, approximateLength);
3777         return client.getIntegerReply();
3778     }
3779     alias xtrim = BinaryRedis.xtrim;
3780 
3781     /**
3782      * {@inheritDoc}
3783      */
3784     List!(MapEntry!(string, List!(StreamEntry))) xreadGroup(string groupname, string consumer, int count, long block,
3785             bool noAck, MapEntry!(string, StreamEntryID)[] streams...) {
3786         checkIsInMultiOrPipeline();
3787         client.xreadGroup(groupname, consumer, count, block, noAck, streams);
3788 
3789         List!(Object) streamsEntries = client.getObjectMultiBulkReply();
3790         if(streamsEntries is null) {
3791             return null;
3792         }
3793         
3794         List!(MapEntry!(string, List!(StreamEntry))) result = 
3795             new ArrayList!(MapEntry!(string, List!(StreamEntry)))(streamsEntries.size());
3796 
3797         // foreach(Object streamObj ; streamsEntries) {
3798         //   List!(Object) stream = cast(List!(Object))streamObj;
3799           
3800         // //   string streamId = SafeEncoder.encode((const(ubyte)[])stream.get(0));
3801         // //   List!(StreamEntry) streamEntries = BuilderFactory.STREAM_ENTRY_LIST.build(stream.get(1));
3802         // //   result.add(new AbstractMap.SimpleEntry!(string, List!(StreamEntry))(streamId, streamEntries));
3803         // }
3804 
3805         foreach(Object streamObj ; streamsEntries) {
3806             List!(Object) stream = cast(List!(Object))streamObj;
3807             Object obj = stream.get(0);
3808             
3809             Bytes bytesData = cast(Bytes)obj;
3810             if(bytesData is null) {
3811                 warningf("Object: %s", typeid(obj));
3812                 return result;
3813             }
3814 
3815             const(ubyte)[] streamData = cast(const(ubyte)[]) bytesData.value;
3816             string streamId = SafeEncoder.encode(streamData);
3817 
3818             //
3819             obj = stream.get(1);
3820             List!(StreamEntry) streamEntries = BuilderFactory.STREAM_ENTRY_LIST.build(obj);
3821 
3822             result.add(new SimpleEntry!(string, List!(StreamEntry))(streamId, streamEntries));
3823         }        
3824         return result;
3825     }
3826 
3827     List!(StreamPendingEntry) xpending(string key, string groupname, StreamEntryID start, StreamEntryID end,
3828             int count, string consumername) {
3829         checkIsInMultiOrPipeline();
3830         client.xpending(key, groupname, start, end, count, consumername);
3831 
3832         // TODO handle consumername == NULL case
3833         
3834         return BuilderFactory.STREAM_PENDING_ENTRY_LIST.build(cast(Object)client.getObjectMultiBulkReply());
3835     }
3836     alias xpending = BinaryRedis.xpending;
3837 
3838     List!(StreamEntry) xclaim(string key, string group, string consumername, long minIdleTime, long newIdleTime,
3839             int retries, bool force, StreamEntryID[] ids...) {
3840         
3841         checkIsInMultiOrPipeline();
3842         client.xclaim( key, group, consumername, minIdleTime, newIdleTime, retries, force, ids);
3843         
3844         return BuilderFactory.STREAM_ENTRY_LIST.build(cast(Object)client.getObjectMultiBulkReply());
3845     }
3846     alias xclaim = BinaryRedis.xclaim;
3847 
3848     Object sendCommand(ProtocolCommand cmd, string[] args...) {
3849         client.sendCommand(cmd, args);
3850         return client.getOne();
3851     }
3852     alias sendCommand = BinaryRedis.sendCommand;
3853 
3854     override Long slowlogLen() { return super.slowlogLen(); }
3855     override string auth(string password) { 
3856         import std.range;
3857         if(password.empty) {
3858             return "empty password";
3859         }
3860         return super.auth(password); 
3861     }
3862     override string ping() { return super.ping(); }
3863     override string quit() { return super.quit(); }
3864     override string shutdown() { return super.shutdown(); }
3865     override Long dbSize() { return super.dbSize(); }
3866     override string flushDB() { return super.flushDB(); }
3867     override string flushAll() { return super.flushAll(); }
3868 
3869     override string bgrewriteaof() { return super.bgrewriteaof(); }
3870     override string bgsave() { return super.bgsave(); }
3871     override Long lastsave() { return super.lastsave(); }
3872     override string save() { return super.save(); }
3873     
3874     override string unwatch() { return super.unwatch(); }
3875     override string select(int index) { return super.select(index); }
3876     override string swapDB(int index1, int index2) { return super.swapDB(index1, index2); }
3877     override int getDB() { return super.getDB(); }
3878     override string info() { return super.info(); }
3879     override string info(string section) { return super.info(section); }
3880 
3881     override string slaveof(string host, int port) { return super.slaveof(host, port); }
3882     override string slaveofNoOne() { return super.slaveofNoOne(); }
3883     override Long waitReplicas(int replicas, long timeout) { return super.waitReplicas(replicas, timeout); }
3884 
3885     override string slowlogReset() { return super.slowlogReset(); }
3886     override string configResetStat() { return super.configResetStat(); }
3887     override string configRewrite() { return super.configRewrite(); }
3888 
3889 }