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