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