redis 3.3.5 → 4.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +84 -2
  3. data/README.md +131 -76
  4. data/lib/redis.rb +912 -200
  5. data/lib/redis/client.rb +71 -29
  6. data/lib/redis/cluster.rb +291 -0
  7. data/lib/redis/cluster/command.rb +81 -0
  8. data/lib/redis/cluster/command_loader.rb +34 -0
  9. data/lib/redis/cluster/key_slot_converter.rb +72 -0
  10. data/lib/redis/cluster/node.rb +104 -0
  11. data/lib/redis/cluster/node_key.rb +31 -0
  12. data/lib/redis/cluster/node_loader.rb +37 -0
  13. data/lib/redis/cluster/option.rb +87 -0
  14. data/lib/redis/cluster/slot.rb +72 -0
  15. data/lib/redis/cluster/slot_loader.rb +50 -0
  16. data/lib/redis/connection.rb +3 -2
  17. data/lib/redis/connection/command_helper.rb +3 -8
  18. data/lib/redis/connection/hiredis.rb +3 -2
  19. data/lib/redis/connection/registry.rb +1 -0
  20. data/lib/redis/connection/ruby.rb +48 -32
  21. data/lib/redis/connection/synchrony.rb +13 -4
  22. data/lib/redis/distributed.rb +39 -15
  23. data/lib/redis/errors.rb +47 -0
  24. data/lib/redis/hash_ring.rb +21 -64
  25. data/lib/redis/pipeline.rb +54 -12
  26. data/lib/redis/subscribe.rb +1 -0
  27. data/lib/redis/version.rb +2 -1
  28. metadata +40 -198
  29. data/.gitignore +0 -16
  30. data/.travis.yml +0 -89
  31. data/.travis/Gemfile +0 -11
  32. data/.yardopts +0 -3
  33. data/Gemfile +0 -4
  34. data/Rakefile +0 -87
  35. data/benchmarking/logging.rb +0 -71
  36. data/benchmarking/pipeline.rb +0 -51
  37. data/benchmarking/speed.rb +0 -21
  38. data/benchmarking/suite.rb +0 -24
  39. data/benchmarking/worker.rb +0 -71
  40. data/examples/basic.rb +0 -15
  41. data/examples/consistency.rb +0 -114
  42. data/examples/dist_redis.rb +0 -43
  43. data/examples/incr-decr.rb +0 -17
  44. data/examples/list.rb +0 -26
  45. data/examples/pubsub.rb +0 -37
  46. data/examples/sentinel.rb +0 -41
  47. data/examples/sentinel/sentinel.conf +0 -9
  48. data/examples/sentinel/start +0 -49
  49. data/examples/sets.rb +0 -36
  50. data/examples/unicorn/config.ru +0 -3
  51. data/examples/unicorn/unicorn.rb +0 -20
  52. data/redis.gemspec +0 -44
  53. data/test/bitpos_test.rb +0 -69
  54. data/test/blocking_commands_test.rb +0 -42
  55. data/test/client_test.rb +0 -59
  56. data/test/command_map_test.rb +0 -30
  57. data/test/commands_on_hashes_test.rb +0 -21
  58. data/test/commands_on_hyper_log_log_test.rb +0 -21
  59. data/test/commands_on_lists_test.rb +0 -20
  60. data/test/commands_on_sets_test.rb +0 -77
  61. data/test/commands_on_sorted_sets_test.rb +0 -137
  62. data/test/commands_on_strings_test.rb +0 -101
  63. data/test/commands_on_value_types_test.rb +0 -133
  64. data/test/connection_handling_test.rb +0 -277
  65. data/test/connection_test.rb +0 -57
  66. data/test/db/.gitkeep +0 -0
  67. data/test/distributed_blocking_commands_test.rb +0 -46
  68. data/test/distributed_commands_on_hashes_test.rb +0 -10
  69. data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
  70. data/test/distributed_commands_on_lists_test.rb +0 -22
  71. data/test/distributed_commands_on_sets_test.rb +0 -83
  72. data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
  73. data/test/distributed_commands_on_strings_test.rb +0 -59
  74. data/test/distributed_commands_on_value_types_test.rb +0 -95
  75. data/test/distributed_commands_requiring_clustering_test.rb +0 -164
  76. data/test/distributed_connection_handling_test.rb +0 -23
  77. data/test/distributed_internals_test.rb +0 -79
  78. data/test/distributed_key_tags_test.rb +0 -52
  79. data/test/distributed_persistence_control_commands_test.rb +0 -26
  80. data/test/distributed_publish_subscribe_test.rb +0 -92
  81. data/test/distributed_remote_server_control_commands_test.rb +0 -66
  82. data/test/distributed_scripting_test.rb +0 -102
  83. data/test/distributed_sorting_test.rb +0 -20
  84. data/test/distributed_test.rb +0 -58
  85. data/test/distributed_transactions_test.rb +0 -32
  86. data/test/encoding_test.rb +0 -18
  87. data/test/error_replies_test.rb +0 -59
  88. data/test/fork_safety_test.rb +0 -65
  89. data/test/helper.rb +0 -232
  90. data/test/helper_test.rb +0 -24
  91. data/test/internals_test.rb +0 -417
  92. data/test/lint/blocking_commands.rb +0 -150
  93. data/test/lint/hashes.rb +0 -162
  94. data/test/lint/hyper_log_log.rb +0 -60
  95. data/test/lint/lists.rb +0 -143
  96. data/test/lint/sets.rb +0 -140
  97. data/test/lint/sorted_sets.rb +0 -316
  98. data/test/lint/strings.rb +0 -260
  99. data/test/lint/value_types.rb +0 -122
  100. data/test/persistence_control_commands_test.rb +0 -26
  101. data/test/pipelining_commands_test.rb +0 -242
  102. data/test/publish_subscribe_test.rb +0 -282
  103. data/test/remote_server_control_commands_test.rb +0 -118
  104. data/test/scanning_test.rb +0 -413
  105. data/test/scripting_test.rb +0 -78
  106. data/test/sentinel_command_test.rb +0 -80
  107. data/test/sentinel_test.rb +0 -255
  108. data/test/sorting_test.rb +0 -59
  109. data/test/ssl_test.rb +0 -73
  110. data/test/support/connection/hiredis.rb +0 -1
  111. data/test/support/connection/ruby.rb +0 -1
  112. data/test/support/connection/synchrony.rb +0 -17
  113. data/test/support/redis_mock.rb +0 -130
  114. data/test/support/ssl/gen_certs.sh +0 -31
  115. data/test/support/ssl/trusted-ca.crt +0 -25
  116. data/test/support/ssl/trusted-ca.key +0 -27
  117. data/test/support/ssl/trusted-cert.crt +0 -81
  118. data/test/support/ssl/trusted-cert.key +0 -28
  119. data/test/support/ssl/untrusted-ca.crt +0 -26
  120. data/test/support/ssl/untrusted-ca.key +0 -27
  121. data/test/support/ssl/untrusted-cert.crt +0 -82
  122. data/test/support/ssl/untrusted-cert.key +0 -28
  123. data/test/support/wire/synchrony.rb +0 -24
  124. data/test/support/wire/thread.rb +0 -5
  125. data/test/synchrony_driver.rb +0 -88
  126. data/test/test.conf.erb +0 -9
  127. data/test/thread_safety_test.rb +0 -62
  128. data/test/transactions_test.rb +0 -264
  129. data/test/unknown_commands_test.rb +0 -14
  130. data/test/url_param_test.rb +0 -138
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 729f56810aa501065e2fc203050363713c80ee37
4
- data.tar.gz: e7e13fb75618f794a2c742d73d6c8a72699d5e92
2
+ SHA256:
3
+ metadata.gz: 83f1f7270db68603d63e86ec43e68348cb5ccb2b4e6759642d89898566bdbaf6
4
+ data.tar.gz: 45c5bcc92629ec7d85cdc2b913e7922cd5425f2e6691891efc379aeec73026b3
5
5
  SHA512:
6
- metadata.gz: f87e76c751760b7f1feb7c859ea373c41ebf805c104c27e85177cb20a60d42b11c50db2d5a916fb7bfc78b82b13141770a851568aea922ccc1970f4e30ba1dea
7
- data.tar.gz: ac4cb236c9c9897d73ead9421e161cf1c7aac2a3c1f77c088b5c52c324cddccda6ab749441093cbcc94ea812e70232f9f9ac1c160a96fd1fbd46d87586d1d7fb
6
+ metadata.gz: 692dfc5c73c6410492589f38f279976a023f6a2ff13f7b1476806011eb387f41bed784bdeac746de5f4b990b6d22bf297b36dddc7b8e448a842241a389f50796
7
+ data.tar.gz: 55a9e305c7563f5dd7d38f50dc7b919967dbb0f6a131ebc5e1569f49f196ab458203b6594394fa9a33ea9e337b741113e781378113783683dd36b87196607b8f
data/CHANGELOG.md CHANGED
@@ -1,8 +1,90 @@
1
+ # Unreleased
2
+
3
+ # 4.1.4
4
+
5
+ * Alias `Redis#disconnect` as `#close`. See #901.
6
+ * Handle clusters with multiple slot ranges. See #894.
7
+ * Fix password authentication to a redis cluster. See #889.
8
+ * Handle recursive MOVED responses. See #882.
9
+ * Increase buffer size in the ruby connector. See #880.
10
+ * Fix thread safety of `Redis.queue`. See #878.
11
+ * Deprecate `Redis::Future#==` as it's likely to be a mistake. See #876.
12
+
13
+ # 4.1.3
14
+
15
+ * Fix the client hanging forever when connecting with SSL to a non-SSL server. See #835.
16
+
17
+ # 4.1.2
18
+
19
+ * Fix several authentication problems with sentinel. See #850 and #856.
20
+ * Explicitly drop Ruby 2.2 support.
21
+
22
+
23
+ # 4.1.1
24
+
25
+ * Fix error handling in multi blocks. See #754.
26
+ * Fix geoadd to accept arrays like georadius and georadiusbymember. See #841.
27
+ * Fix georadius command failing when long == lat. See #841.
28
+ * Fix timeout error in xread block: 0. See #837.
29
+ * Fix incompatibility issue with redis-objects. See #834.
30
+ * Properly handle Errno::EADDRNOTAVAIL on connect.
31
+ * Fix password authentication to sentinel instances. See #813.
32
+
33
+ # 4.1.0
34
+
35
+ * Add Redis Cluster support. See #716.
36
+ * Add streams support. See #799 and #811.
37
+ * Add ZPOP* support. See #812.
38
+ * Fix issues with integer-like objects as BPOP timeout
39
+
40
+ # 4.0.3
41
+
42
+ * Fix raising command error for first command in pipeline. See #788.
43
+ * Fix the gemspec to stop exposing a `build` executable. See #785.
44
+ * Add `:reconnect_delay` and `:reconnect_delay_max` options. See #778.
45
+
46
+ # 4.0.2
47
+
48
+ * Added `Redis#unlink`. See #766.
49
+
50
+ * `Redis.new` now accept a custom connector via `:connector`. See #591.
51
+
52
+ * `Redis#multi` no longer perform empty transactions. See #747.
53
+
54
+ * `Redis#hdel` now accepts hash keys as multiple arguments like `#del`. See #755.
55
+
56
+ * Allow to skip SSL verification. See #745.
57
+
58
+ * Add Geo commands: `geoadd`, `geohash`, `georadius`, `georadiusbymember`, `geopos`, `geodist`. See #730.
59
+
60
+ # 4.0.1
61
+
62
+ * `Redis::Distributed` now supports `mget` and `mapped_mget`. See #687.
63
+
64
+ * `Redis::Distributed` now supports `sscan` and `sscan_each`. See #572.
65
+
66
+ * `Redis#connection` returns a hash with connection information.
67
+ You shouldn't need to call `Redis#_client`, ever.
68
+
69
+ * `Redis#flushdb` and `Redis#flushall` now support the `:async` option. See #706.
70
+
71
+
72
+ # 4.0
73
+
74
+ * Removed `Redis.connect`. Use `Redis.new`.
75
+
76
+ * Removed `Redis#[]` and `Redis#[]=` aliases.
77
+
78
+ * Added support for `CLIENT` commands. The lower-level client can be
79
+ accessed via `Redis#_client`.
80
+
81
+ * Dropped official support for Ruby < 2.2.2.
82
+
1
83
  # 3.3.5
2
84
 
3
85
  * Fixed Ruby 1.8 compatibility after backporting `Redis#connection`. See #719.
4
86
 
5
- # 3.3.4
87
+ # 3.3.4 (yanked)
6
88
 
7
89
  * `Redis#connection` returns a hash with connection information.
8
90
  You shouldn't need to call `Redis#_client`, ever.
@@ -13,7 +95,7 @@
13
95
 
14
96
  # 3.3.2
15
97
 
16
- * Added support for SPOP with COUNT. See #628.
98
+ * Added support for `SPOP` with COUNT. See #628.
17
99
 
18
100
  * Fixed connection glitches when using SSL. See #644.
19
101
 
data/README.md CHANGED
@@ -1,46 +1,17 @@
1
- # redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link]
1
+ # redis-rb [![Build Status][travis-image]][travis-link] [![Inline docs][inchpages-image]][inchpages-link] ![](https://github.com/redis/redis-rb/workflows/Test/badge.svg?branch=master)
2
2
 
3
- [travis-image]: https://secure.travis-ci.org/redis/redis-rb.png?branch=master
4
- [travis-link]: http://travis-ci.org/redis/redis-rb
5
- [travis-home]: http://travis-ci.org/
6
- [inchpages-image]: http://inch-ci.org/github/redis/redis-rb.png
7
- [inchpages-link]: http://inch-ci.org/github/redis/redis-rb
3
+ A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still
4
+ providing an idiomatic interface.
8
5
 
9
- A Ruby client library for [Redis][redis-home].
10
-
11
- [redis-home]: http://redis.io
12
-
13
- A Ruby client that tries to match Redis' API one-to-one, while still
14
- providing an idiomatic interface. It features thread-safety, client-side
15
- sharding, pipelining, and an obsession for performance.
16
-
17
- ## Upgrading from 2.x to 3.0
18
-
19
- Please refer to the [CHANGELOG][changelog-3.0.0] for a summary of the
20
- most important changes, as well as a full list of changes.
21
-
22
- [changelog-3.0.0]: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md#300
23
6
 
24
7
  ## Getting started
25
8
 
26
- To install **redis-rb**, run the following command:
27
-
28
- ```
29
- gem install redis
30
- ```
31
-
32
- Or if you are using **bundler**, add
9
+ Install with:
33
10
 
34
11
  ```
35
- gem 'redis', '~>3.2'
12
+ $ gem install redis
36
13
  ```
37
14
 
38
- to your `Gemfile`, and run `bundle install`
39
-
40
- As of version 2.0 this client only targets Redis version 2.0 and higher.
41
- You can use an older version of this client if you need to interface
42
- with a Redis instance older than 2.0, but this is no longer supported.
43
-
44
15
  You can connect to Redis by instantiating the `Redis` class:
45
16
 
46
17
  ```ruby
@@ -54,17 +25,15 @@ listening on `localhost`, port 6379. If you need to connect to a remote
54
25
  server or a different port, try:
55
26
 
56
27
  ```ruby
57
- redis = Redis.new(:host => "10.0.1.1", :port => 6380, :db => 15)
28
+ redis = Redis.new(host: "10.0.1.1", port: 6380, db: 15)
58
29
  ```
59
30
 
60
31
  You can also specify connection options as a [`redis://` URL][redis-url]:
61
32
 
62
33
  ```ruby
63
- redis = Redis.new(:url => "redis://:p4ssw0rd@10.0.1.1:6380/15")
34
+ redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
64
35
  ```
65
36
 
66
- [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
67
-
68
37
  By default, the client will try to read the `REDIS_URL` environment variable
69
38
  and use that as URL to connect to. The above statement is therefore equivalent
70
39
  to setting this environment variable and calling `Redis.new` without arguments.
@@ -72,13 +41,13 @@ to setting this environment variable and calling `Redis.new` without arguments.
72
41
  To connect to Redis listening on a Unix socket, try:
73
42
 
74
43
  ```ruby
75
- redis = Redis.new(:path => "/tmp/redis.sock")
44
+ redis = Redis.new(path: "/tmp/redis.sock")
76
45
  ```
77
46
 
78
47
  To connect to a password protected Redis instance, use:
79
48
 
80
49
  ```ruby
81
- redis = Redis.new(:password => "mysecret")
50
+ redis = Redis.new(password: "mysecret")
82
51
  ```
83
52
 
84
53
  The Redis class exports methods that are named identical to the commands
@@ -86,8 +55,6 @@ they execute. The arguments these methods accept are often identical to
86
55
  the arguments specified on the [Redis website][redis-commands]. For
87
56
  instance, the `SET` and `GET` commands can be called like this:
88
57
 
89
- [redis-commands]: http://redis.io/commands
90
-
91
58
  ```ruby
92
59
  redis.set("mykey", "hello world")
93
60
  # => "OK"
@@ -96,24 +63,22 @@ redis.get("mykey")
96
63
  # => "hello world"
97
64
  ```
98
65
 
99
- All commands, their arguments and return values are documented, and
100
- available on [rdoc.info][rdoc].
101
-
102
- [rdoc]: http://rdoc.info/github/redis/redis-rb/
66
+ All commands, their arguments, and return values are documented and
67
+ available on [RubyDoc.info][rubydoc].
103
68
 
104
69
  ## Sentinel support
105
70
 
106
- The client is able to perform automatic failovers by using [Redis
71
+ The client is able to perform automatic failover by using [Redis
107
72
  Sentinel](http://redis.io/topics/sentinel). Make sure to run Redis 2.8+
108
73
  if you want to use this feature.
109
74
 
110
75
  To connect using Sentinel, use:
111
76
 
112
77
  ```ruby
113
- SENTINELS = [{:host => "127.0.0.1", :port => 26380},
114
- {:host => "127.0.0.1", :port => 26381}]
78
+ SENTINELS = [{ host: "127.0.0.1", port: 26380 },
79
+ { host: "127.0.0.1", port: 26381 }]
115
80
 
116
- redis = Redis.new(:url => "redis://mymaster", :sentinels => SENTINELS, :role => :master)
81
+ redis = Redis.new(url: "redis://mymaster", sentinels: SENTINELS, role: :master)
117
82
  ```
118
83
 
119
84
  * The master name identifies a group of Redis instances composed of a master
@@ -130,6 +95,56 @@ but a few so that if one is down the client will try the next one. The client
130
95
  is able to remember the last Sentinel that was able to reply correctly and will
131
96
  use it for the next requests.
132
97
 
98
+ If you want to [authenticate](https://redis.io/topics/sentinel#configuring-sentinel-instances-with-authentication) Sentinel itself, you must specify the `password` option per instance.
99
+
100
+ ```ruby
101
+ SENTINELS = [{ host: '127.0.0.1', port: 26380, password: 'mysecret' },
102
+ { host: '127.0.0.1', port: 26381, password: 'mysecret' }]
103
+
104
+ redis = Redis.new(host: 'mymaster', sentinels: SENTINELS, role: :master)
105
+ ```
106
+
107
+ ## Cluster support
108
+
109
+ `redis-rb` supports [clustering](https://redis.io/topics/cluster-spec).
110
+
111
+ ```ruby
112
+ # Nodes can be passed to the client as an array of connection URLs.
113
+ nodes = (7000..7005).map { |port| "redis://127.0.0.1:#{port}" }
114
+ redis = Redis.new(cluster: nodes)
115
+
116
+ # You can also specify the options as a Hash. The options are the same as for a single server connection.
117
+ (7000..7005).map { |port| { host: '127.0.0.1', port: port } }
118
+ ```
119
+
120
+ You can also specify only a subset of the nodes, and the client will discover the missing ones using the [CLUSTER NODES](https://redis.io/commands/cluster-nodes) command.
121
+
122
+ ```ruby
123
+ Redis.new(cluster: %w[redis://127.0.0.1:7000])
124
+ ```
125
+
126
+ If you want [the connection to be able to read from any replica](https://redis.io/commands/readonly), you must pass the `replica: true`. Note that this connection won't be usable to write keys.
127
+
128
+ ```ruby
129
+ Redis.new(cluster: nodes, replica: true)
130
+ ```
131
+
132
+ The calling code is responsible for [avoiding cross slot commands](https://redis.io/topics/cluster-spec#keys-distribution-model).
133
+
134
+ ```ruby
135
+ redis = Redis.new(cluster: %w[redis://127.0.0.1:7000])
136
+
137
+ redis.mget('key1', 'key2')
138
+ #=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)
139
+
140
+ redis.mget('{key}1', '{key}2')
141
+ #=> [nil, nil]
142
+ ```
143
+
144
+ * The client automatically reconnects after a failover occurred, but the caller is responsible for handling errors while it is happening.
145
+ * The client support permanent node failures, and will reroute requests to promoted slaves.
146
+ * The client supports `MOVED` and `ASK` redirections transparently.
147
+
133
148
  ## Storing objects
134
149
 
135
150
  Redis only stores strings as values. If you want to store an object, you
@@ -211,7 +226,7 @@ it can't connect to the server a `Redis::CannotConnectError` error will be raise
211
226
  ```ruby
212
227
  begin
213
228
  redis.ping
214
- rescue Exception => e
229
+ rescue StandardError => e
215
230
  e.inspect
216
231
  # => #<Redis::CannotConnectError: Timed out connecting to Redis on 10.0.1.1:6380>
217
232
 
@@ -255,6 +270,51 @@ end
255
270
 
256
271
  If no message is received after 5 seconds, the client will unsubscribe.
257
272
 
273
+ ## Reconnections
274
+
275
+ The client allows you to configure how many `reconnect_attempts` it should
276
+ complete before declaring a connection as failed. Furthermore, you may want
277
+ to control the maximum duration between reconnection attempts with
278
+ `reconnect_delay` and `reconnect_delay_max`.
279
+
280
+ ```ruby
281
+ Redis.new(
282
+ :reconnect_attempts => 10,
283
+ :reconnect_delay => 1.5,
284
+ :reconnect_delay_max => 10.0,
285
+ )
286
+ ```
287
+
288
+ The delay values are specified in seconds. With the above configuration, the
289
+ client would attempt 10 reconnections, exponentially increasing the duration
290
+ between each attempt but it never waits longer than `reconnect_delay_max`.
291
+
292
+ This is the retry algorithm:
293
+
294
+ ```ruby
295
+ attempt_wait_time = [(reconnect_delay * 2**(attempt-1)), reconnect_delay_max].min
296
+ ```
297
+
298
+ **By default**, this gem will only **retry a connection once** and then fail, but with the
299
+ above configuration the reconnection attempt would look like this:
300
+
301
+ #|Attempt wait time|Total wait time
302
+ :-:|:-:|:-:
303
+ 1|1.5s|1.5s
304
+ 2|3.0s|4.5s
305
+ 3|6.0s|10.5s
306
+ 4|10.0s|20.5s
307
+ 5|10.0s|30.5s
308
+ 6|10.0s|40.5s
309
+ 7|10.0s|50.5s
310
+ 8|10.0s|60.5s
311
+ 9|10.0s|70.5s
312
+ 10|10.0s|80.5s
313
+
314
+ So if the reconnection attempt #10 succeeds 70 seconds have elapsed trying
315
+ to reconnect, this is likely fine in long-running background processes, but if
316
+ you use Redis to drive your website you might want to have a lower
317
+ `reconnect_delay_max` or have less `reconnect_attempts`.
258
318
 
259
319
  ## SSL/TLS Support
260
320
 
@@ -374,37 +434,32 @@ redis = Redis.new(:driver => :synchrony)
374
434
 
375
435
  ## Testing
376
436
 
377
- This library is tested using [Travis][travis-home], where it is tested
378
- against the following interpreters and drivers:
437
+ This library is tested against recent Ruby and Redis versions.
438
+ Check [Travis][travis-link] for the exact versions supported.
379
439
 
380
- * MRI 1.8.7 (drivers: ruby, hiredis)
381
- * MRI 1.9.3 (drivers: ruby, hiredis, synchrony)
382
- * MRI 2.0 (drivers: ruby, hiredis, synchrony)
383
- * MRI 2.1 (drivers: ruby, hiredis, synchrony)
384
- * MRI 2.2 (drivers: ruby, hiredis, synchrony)
385
- * MRI 2.3 (drivers: ruby, hiredis, synchrony)
386
- * JRuby 1.7 (1.8 mode) (drivers: ruby)
387
- * JRuby 1.7 (1.9 mode) (drivers: ruby)
440
+ ## See Also
441
+
442
+ - [async-redis](https://github.com/socketry/async-redis) — An [async](https://github.com/socketry/async) compatible Redis client.
388
443
 
389
444
  ## Contributors
390
445
 
391
- (ordered chronologically with more than 5 commits, see `git shortlog -sn` for
392
- all contributors)
393
-
394
- * Ezra Zygmuntowicz
395
- * Taylor Weibley
396
- * Matthew Clark
397
- * Brian McKinney
398
- * Luca Guidi
399
- * Salvatore Sanfilippo
400
- * Chris Wanstrath
401
- * Damian Janowski
402
- * Michel Martens
403
- * Nick Quaranto
404
- * Pieter Noordhuis
405
- * Ilya Grigorik
446
+ Several people contributed to redis-rb, but we would like to especially
447
+ mention Ezra Zygmuntowicz. Ezra introduced the Ruby community to many
448
+ new cool technologies, like Redis. He wrote the first version of this
449
+ client and evangelized Redis in Rubyland. Thank you, Ezra.
406
450
 
407
451
  ## Contributing
408
452
 
409
453
  [Fork the project](https://github.com/redis/redis-rb) and send pull
410
454
  requests. You can also ask for help at `#redis-rb` on Freenode.
455
+
456
+
457
+ [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
458
+ [inchpages-link]: https://inch-ci.org/github/redis/redis-rb
459
+ [redis-commands]: https://redis.io/commands
460
+ [redis-home]: https://redis.io
461
+ [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
462
+ [travis-home]: https://travis-ci.org/
463
+ [travis-image]: https://secure.travis-ci.org/redis/redis-rb.svg?branch=master
464
+ [travis-link]: https://travis-ci.org/redis/redis-rb
465
+ [rubydoc]: http://www.rubydoc.info/gems/redis
data/lib/redis.rb CHANGED
@@ -1,21 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "monitor"
2
- require "redis/errors"
4
+ require_relative "redis/errors"
3
5
 
4
6
  class Redis
5
-
6
- def self.deprecate(message, trace = caller[0])
7
- $stderr.puts "\n#{message} (in #{trace})"
8
- end
9
-
10
- attr :client
11
-
12
- # @deprecated The preferred way to create a new client object is using `#new`.
13
- # This method does not actually establish a connection to Redis,
14
- # in contrary to what you might expect.
15
- def self.connect(options = {})
16
- new(options)
17
- end
18
-
19
7
  def self.current
20
8
  @current ||= Redis.new
21
9
  end
@@ -31,24 +19,29 @@ class Redis
31
19
  # @param [Hash] options
32
20
  # @option options [String] :url (value of the environment variable REDIS_URL) a Redis URL, for a TCP connection: `redis://:[password]@[hostname]:[port]/[db]` (password, port and database are optional), for a unix socket connection: `unix://[path to Redis socket]`. This overrides all other options.
33
21
  # @option options [String] :host ("127.0.0.1") server hostname
34
- # @option options [Fixnum] :port (6379) server port
22
+ # @option options [Integer] :port (6379) server port
35
23
  # @option options [String] :path path to server socket (overrides host and port)
36
24
  # @option options [Float] :timeout (5.0) timeout in seconds
37
25
  # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
38
26
  # @option options [String] :password Password to authenticate against server
39
- # @option options [Fixnum] :db (0) Database to select after initial connect
27
+ # @option options [Integer] :db (0) Database to select after initial connect
40
28
  # @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`, `:synchrony`
41
29
  # @option options [String] :id ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`
42
- # @option options [Hash, Fixnum] :tcp_keepalive Keepalive values, if Fixnum `intvl` and `probe` are calculated based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Fixnum
43
- # @option options [Fixnum] :reconnect_attempts Number of attempts trying to connect
30
+ # @option options [Hash, Integer] :tcp_keepalive Keepalive values, if Integer `intvl` and `probe` are calculated based on the value, if Hash `time`, `intvl` and `probes` can be specified as a Integer
31
+ # @option options [Integer] :reconnect_attempts Number of attempts trying to connect
44
32
  # @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
45
33
  # @option options [Array] :sentinels List of sentinels to contact
46
34
  # @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
35
+ # @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
36
+ # @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
37
+ # @option options [Class] :connector Class of custom connector
47
38
  #
48
39
  # @return [Redis] a new client instance
49
40
  def initialize(options = {})
50
41
  @options = options.dup
51
- @original_client = @client = Client.new(options)
42
+ @cluster_mode = options.key?(:cluster)
43
+ client = @cluster_mode ? Cluster : Client
44
+ @original_client = @client = client.new(options)
52
45
  @queue = Hash.new { |h, k| h[k] = [] }
53
46
 
54
47
  super() # Monitor#initialize
@@ -102,7 +95,9 @@ class Redis
102
95
  # See http://redis.io/topics/pipelining for more details.
103
96
  #
104
97
  def queue(*command)
105
- @queue[Thread.current.object_id] << command
98
+ synchronize do
99
+ @queue[Thread.current.object_id] << command
100
+ end
106
101
  end
107
102
 
108
103
  # Sends all commands in the queue.
@@ -112,13 +107,22 @@ class Redis
112
107
  def commit
113
108
  synchronize do |client|
114
109
  begin
115
- client.call_pipelined(@queue[Thread.current.object_id])
110
+ pipeline = Pipeline.new(client)
111
+ @queue[Thread.current.object_id].each do |command|
112
+ pipeline.call(command)
113
+ end
114
+
115
+ client.call_pipelined(pipeline)
116
116
  ensure
117
117
  @queue.delete(Thread.current.object_id)
118
118
  end
119
119
  end
120
120
  end
121
121
 
122
+ def _client
123
+ @client
124
+ end
125
+
122
126
  # Authenticate to the server.
123
127
  #
124
128
  # @param [String] password must match the password specified in the
@@ -132,7 +136,7 @@ class Redis
132
136
 
133
137
  # Change the selected database for the current connection.
134
138
  #
135
- # @param [Fixnum] db zero-based index of the DB to use (0 to 15)
139
+ # @param [Integer] db zero-based index of the DB to use (0 to 15)
136
140
  # @return [String] `OK`
137
141
  def select(db)
138
142
  synchronize do |client|
@@ -143,10 +147,11 @@ class Redis
143
147
 
144
148
  # Ping the server.
145
149
  #
150
+ # @param [optional, String] message
146
151
  # @return [String] `PONG`
147
- def ping
152
+ def ping(message = nil)
148
153
  synchronize do |client|
149
- client.call([:ping])
154
+ client.call([:ping, message].compact)
150
155
  end
151
156
  end
152
157
 
@@ -209,9 +214,28 @@ class Redis
209
214
  end
210
215
  end
211
216
 
217
+ # Manage client connections.
218
+ #
219
+ # @param [String, Symbol] subcommand e.g. `kill`, `list`, `getname`, `setname`
220
+ # @return [String, Hash] depends on subcommand
221
+ def client(subcommand = nil, *args)
222
+ synchronize do |client|
223
+ client.call([:client, subcommand] + args) do |reply|
224
+ if subcommand.to_s == "list"
225
+ reply.lines.map do |line|
226
+ entries = line.chomp.split(/[ =]/)
227
+ Hash[entries.each_slice(2).to_a]
228
+ end
229
+ else
230
+ reply
231
+ end
232
+ end
233
+ end
234
+ end
235
+
212
236
  # Return the number of keys in the selected database.
213
237
  #
214
- # @return [Fixnum]
238
+ # @return [Integer]
215
239
  def dbsize
216
240
  synchronize do |client|
217
241
  client.call([:dbsize])
@@ -226,19 +250,31 @@ class Redis
226
250
 
227
251
  # Remove all keys from all databases.
228
252
  #
253
+ # @param [Hash] options
254
+ # - `:async => Boolean`: async flush (default: false)
229
255
  # @return [String] `OK`
230
- def flushall
256
+ def flushall(options = nil)
231
257
  synchronize do |client|
232
- client.call([:flushall])
258
+ if options && options[:async]
259
+ client.call([:flushall, :async])
260
+ else
261
+ client.call([:flushall])
262
+ end
233
263
  end
234
264
  end
235
265
 
236
266
  # Remove all keys from the current database.
237
267
  #
268
+ # @param [Hash] options
269
+ # - `:async => Boolean`: async flush (default: false)
238
270
  # @return [String] `OK`
239
- def flushdb
271
+ def flushdb(options = nil)
240
272
  synchronize do |client|
241
- client.call([:flushdb])
273
+ if options && options[:async]
274
+ client.call([:flushdb, :async])
275
+ else
276
+ client.call([:flushdb])
277
+ end
242
278
  end
243
279
  end
244
280
 
@@ -250,9 +286,7 @@ class Redis
250
286
  synchronize do |client|
251
287
  client.call([:info, cmd].compact) do |reply|
252
288
  if reply.kind_of?(String)
253
- reply = Hash[reply.split("\r\n").map do |line|
254
- line.split(":", 2) unless line =~ /^(#|$)/
255
- end.compact]
289
+ reply = HashifyInfo.call(reply)
256
290
 
257
291
  if cmd && cmd.to_s == "commandstats"
258
292
  # Extract nested hashes for INFO COMMANDSTATS
@@ -270,7 +304,7 @@ class Redis
270
304
 
271
305
  # Get the UNIX time stamp of the last successful save to disk.
272
306
  #
273
- # @return [Fixnum]
307
+ # @return [Integer]
274
308
  def lastsave
275
309
  synchronize do |client|
276
310
  client.call([:lastsave])
@@ -322,8 +356,8 @@ class Redis
322
356
  # Interact with the slowlog (get, len, reset)
323
357
  #
324
358
  # @param [String] subcommand e.g. `get`, `len`, `reset`
325
- # @param [Fixnum] length maximum number of entries to return
326
- # @return [Array<String>, Fixnum, String] depends on subcommand
359
+ # @param [Integer] length maximum number of entries to return
360
+ # @return [Array<String>, Integer, String] depends on subcommand
327
361
  def slowlog(subcommand, length=nil)
328
362
  synchronize do |client|
329
363
  args = [:slowlog, subcommand]
@@ -344,7 +378,7 @@ class Redis
344
378
  # @example
345
379
  # r.time # => [ 1333093196, 606806 ]
346
380
  #
347
- # @return [Array<Fixnum>] tuple of seconds since UNIX epoch and
381
+ # @return [Array<Integer>] tuple of seconds since UNIX epoch and
348
382
  # microseconds in the current second
349
383
  def time
350
384
  synchronize do |client|
@@ -367,7 +401,7 @@ class Redis
367
401
  # Set a key's time to live in seconds.
368
402
  #
369
403
  # @param [String] key
370
- # @param [Fixnum] seconds time to live
404
+ # @param [Integer] seconds time to live
371
405
  # @return [Boolean] whether the timeout was set or not
372
406
  def expire(key, seconds)
373
407
  synchronize do |client|
@@ -378,7 +412,7 @@ class Redis
378
412
  # Set the expiration for a key as a UNIX timestamp.
379
413
  #
380
414
  # @param [String] key
381
- # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp
415
+ # @param [Integer] unix_time expiry time specified as a UNIX timestamp
382
416
  # @return [Boolean] whether the timeout was set or not
383
417
  def expireat(key, unix_time)
384
418
  synchronize do |client|
@@ -389,7 +423,7 @@ class Redis
389
423
  # Get the time to live (in seconds) for a key.
390
424
  #
391
425
  # @param [String] key
392
- # @return [Fixnum] remaining time to live in seconds.
426
+ # @return [Integer] remaining time to live in seconds.
393
427
  #
394
428
  # In Redis 2.6 or older the command returns -1 if the key does not exist or if
395
429
  # the key exist but has no associated expire.
@@ -407,7 +441,7 @@ class Redis
407
441
  # Set a key's time to live in milliseconds.
408
442
  #
409
443
  # @param [String] key
410
- # @param [Fixnum] milliseconds time to live
444
+ # @param [Integer] milliseconds time to live
411
445
  # @return [Boolean] whether the timeout was set or not
412
446
  def pexpire(key, milliseconds)
413
447
  synchronize do |client|
@@ -418,7 +452,7 @@ class Redis
418
452
  # Set the expiration for a key as number of milliseconds from UNIX Epoch.
419
453
  #
420
454
  # @param [String] key
421
- # @param [Fixnum] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
455
+ # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
422
456
  # @return [Boolean] whether the timeout was set or not
423
457
  def pexpireat(key, ms_unix_time)
424
458
  synchronize do |client|
@@ -429,7 +463,7 @@ class Redis
429
463
  # Get the time to live (in milliseconds) for a key.
430
464
  #
431
465
  # @param [String] key
432
- # @return [Fixnum] remaining time to live in milliseconds
466
+ # @return [Integer] remaining time to live in milliseconds
433
467
  # In Redis 2.6 or older the command returns -1 if the key does not exist or if
434
468
  # the key exist but has no associated expire.
435
469
  #
@@ -458,43 +492,64 @@ class Redis
458
492
  # @param [String] key
459
493
  # @param [String] ttl
460
494
  # @param [String] serialized_value
495
+ # @param [Hash] options
496
+ # - `:replace => Boolean`: if false, raises an error if key already exists
497
+ # @raise [Redis::CommandError]
461
498
  # @return [String] `"OK"`
462
- def restore(key, ttl, serialized_value)
499
+ def restore(key, ttl, serialized_value, options = {})
500
+ args = [:restore, key, ttl, serialized_value]
501
+ args << 'REPLACE' if options[:replace]
502
+
463
503
  synchronize do |client|
464
- client.call([:restore, key, ttl, serialized_value])
504
+ client.call(args)
465
505
  end
466
506
  end
467
507
 
468
508
  # Transfer a key from the connected instance to another instance.
469
509
  #
470
- # @param [String] key
510
+ # @param [String, Array<String>] key
471
511
  # @param [Hash] options
472
512
  # - `:host => String`: host of instance to migrate to
473
513
  # - `:port => Integer`: port of instance to migrate to
474
514
  # - `:db => Integer`: database to migrate to (default: same as source)
475
515
  # - `:timeout => Integer`: timeout (default: same as connection timeout)
516
+ # - `:copy => Boolean`: Do not remove the key from the local instance.
517
+ # - `:replace => Boolean`: Replace existing key on the remote instance.
476
518
  # @return [String] `"OK"`
477
519
  def migrate(key, options)
478
- host = options[:host] || raise(RuntimeError, ":host not specified")
479
- port = options[:port] || raise(RuntimeError, ":port not specified")
480
- db = (options[:db] || client.db).to_i
481
- timeout = (options[:timeout] || client.timeout).to_i
520
+ args = [:migrate]
521
+ args << (options[:host] || raise(':host not specified'))
522
+ args << (options[:port] || raise(':port not specified'))
523
+ args << (key.is_a?(String) ? key : '')
524
+ args << (options[:db] || @client.db).to_i
525
+ args << (options[:timeout] || @client.timeout).to_i
526
+ args << 'COPY' if options[:copy]
527
+ args << 'REPLACE' if options[:replace]
528
+ args += ['KEYS', *key] if key.is_a?(Array)
482
529
 
483
- synchronize do |client|
484
- client.call([:migrate, host, port, key, db, timeout])
485
- end
530
+ synchronize { |client| client.call(args) }
486
531
  end
487
532
 
488
533
  # Delete one or more keys.
489
534
  #
490
535
  # @param [String, Array<String>] keys
491
- # @return [Fixnum] number of keys that were deleted
536
+ # @return [Integer] number of keys that were deleted
492
537
  def del(*keys)
493
538
  synchronize do |client|
494
539
  client.call([:del] + keys)
495
540
  end
496
541
  end
497
542
 
543
+ # Unlink one or more keys.
544
+ #
545
+ # @param [String, Array<String>] keys
546
+ # @return [Integer] number of keys that were unlinked
547
+ def unlink(*keys)
548
+ synchronize do |client|
549
+ client.call([:unlink] + keys)
550
+ end
551
+ end
552
+
498
553
  # Determine if a key exists.
499
554
  #
500
555
  # @param [String] key
@@ -538,7 +593,7 @@ class Redis
538
593
  # # => "bar"
539
594
  #
540
595
  # @param [String] key
541
- # @param [Fixnum] db
596
+ # @param [Integer] db
542
597
  # @return [Boolean] whether the key was moved or not
543
598
  def move(key, db)
544
599
  synchronize do |client|
@@ -602,7 +657,7 @@ class Redis
602
657
  # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
603
658
  # - `:store => String`: key to store the result at
604
659
  #
605
- # @return [Array<String>, Array<Array<String>>, Fixnum]
660
+ # @return [Array<String>, Array<Array<String>>, Integer]
606
661
  # - when `:get` is not specified, or holds a single element, an array of elements
607
662
  # - when `:get` is specified, and holds more than one element, an array of
608
663
  # elements where every element is an array with the result for every
@@ -656,7 +711,7 @@ class Redis
656
711
  # # => 4
657
712
  #
658
713
  # @param [String] key
659
- # @return [Fixnum] value after decrementing it
714
+ # @return [Integer] value after decrementing it
660
715
  def decr(key)
661
716
  synchronize do |client|
662
717
  client.call([:decr, key])
@@ -670,8 +725,8 @@ class Redis
670
725
  # # => 0
671
726
  #
672
727
  # @param [String] key
673
- # @param [Fixnum] decrement
674
- # @return [Fixnum] value after decrementing it
728
+ # @param [Integer] decrement
729
+ # @return [Integer] value after decrementing it
675
730
  def decrby(key, decrement)
676
731
  synchronize do |client|
677
732
  client.call([:decrby, key, decrement])
@@ -685,7 +740,7 @@ class Redis
685
740
  # # => 6
686
741
  #
687
742
  # @param [String] key
688
- # @return [Fixnum] value after incrementing it
743
+ # @return [Integer] value after incrementing it
689
744
  def incr(key)
690
745
  synchronize do |client|
691
746
  client.call([:incr, key])
@@ -699,8 +754,8 @@ class Redis
699
754
  # # => 10
700
755
  #
701
756
  # @param [String] key
702
- # @param [Fixnum] increment
703
- # @return [Fixnum] value after incrementing it
757
+ # @param [Integer] increment
758
+ # @return [Integer] value after incrementing it
704
759
  def incrby(key, increment)
705
760
  synchronize do |client|
706
761
  client.call([:incrby, key, increment])
@@ -727,8 +782,8 @@ class Redis
727
782
  # @param [String] key
728
783
  # @param [String] value
729
784
  # @param [Hash] options
730
- # - `:ex => Fixnum`: Set the specified expire time, in seconds.
731
- # - `:px => Fixnum`: Set the specified expire time, in milliseconds.
785
+ # - `:ex => Integer`: Set the specified expire time, in seconds.
786
+ # - `:px => Integer`: Set the specified expire time, in milliseconds.
732
787
  # - `:nx => true`: Only set the key if it does not already exist.
733
788
  # - `:xx => true`: Only set the key if it already exist.
734
789
  # @return [String, Boolean] `"OK"` or true, false if `:nx => true` or `:xx => true`
@@ -756,12 +811,10 @@ class Redis
756
811
  end
757
812
  end
758
813
 
759
- alias :[]= :set
760
-
761
814
  # Set the time to live in seconds of a key.
762
815
  #
763
816
  # @param [String] key
764
- # @param [Fixnum] ttl
817
+ # @param [Integer] ttl
765
818
  # @param [String] value
766
819
  # @return [String] `"OK"`
767
820
  def setex(key, ttl, value)
@@ -773,7 +826,7 @@ class Redis
773
826
  # Set the time to live in milliseconds of a key.
774
827
  #
775
828
  # @param [String] key
776
- # @param [Fixnum] ttl
829
+ # @param [Integer] ttl
777
830
  # @param [String] value
778
831
  # @return [String] `"OK"`
779
832
  def psetex(key, ttl, value)
@@ -863,12 +916,10 @@ class Redis
863
916
  end
864
917
  end
865
918
 
866
- alias :[] :get
867
-
868
919
  # Get the values of all the given keys.
869
920
  #
870
921
  # @example
871
- # redis.mget("key1", "key1")
922
+ # redis.mget("key1", "key2")
872
923
  # # => ["v1", "v2"]
873
924
  #
874
925
  # @param [Array<String>] keys
@@ -904,9 +955,9 @@ class Redis
904
955
  # Overwrite part of a string at key starting at the specified offset.
905
956
  #
906
957
  # @param [String] key
907
- # @param [Fixnum] offset byte offset
958
+ # @param [Integer] offset byte offset
908
959
  # @param [String] value
909
- # @return [Fixnum] length of the string after it was modified
960
+ # @return [Integer] length of the string after it was modified
910
961
  def setrange(key, offset, value)
911
962
  synchronize do |client|
912
963
  client.call([:setrange, key, offset, value.to_s])
@@ -916,10 +967,10 @@ class Redis
916
967
  # Get a substring of the string stored at a key.
917
968
  #
918
969
  # @param [String] key
919
- # @param [Fixnum] start zero-based start offset
920
- # @param [Fixnum] stop zero-based end offset. Use -1 for representing
970
+ # @param [Integer] start zero-based start offset
971
+ # @param [Integer] stop zero-based end offset. Use -1 for representing
921
972
  # the end of the string
922
- # @return [Fixnum] `0` or `1`
973
+ # @return [Integer] `0` or `1`
923
974
  def getrange(key, start, stop)
924
975
  synchronize do |client|
925
976
  client.call([:getrange, key, start, stop])
@@ -929,9 +980,9 @@ class Redis
929
980
  # Sets or clears the bit at offset in the string value stored at key.
930
981
  #
931
982
  # @param [String] key
932
- # @param [Fixnum] offset bit offset
933
- # @param [Fixnum] value bit value `0` or `1`
934
- # @return [Fixnum] the original bit value stored at `offset`
983
+ # @param [Integer] offset bit offset
984
+ # @param [Integer] value bit value `0` or `1`
985
+ # @return [Integer] the original bit value stored at `offset`
935
986
  def setbit(key, offset, value)
936
987
  synchronize do |client|
937
988
  client.call([:setbit, key, offset, value])
@@ -941,8 +992,8 @@ class Redis
941
992
  # Returns the bit value at offset in the string value stored at key.
942
993
  #
943
994
  # @param [String] key
944
- # @param [Fixnum] offset bit offset
945
- # @return [Fixnum] `0` or `1`
995
+ # @param [Integer] offset bit offset
996
+ # @return [Integer] `0` or `1`
946
997
  def getbit(key, offset)
947
998
  synchronize do |client|
948
999
  client.call([:getbit, key, offset])
@@ -953,7 +1004,7 @@ class Redis
953
1004
  #
954
1005
  # @param [String] key
955
1006
  # @param [String] value value to append
956
- # @return [Fixnum] length of the string after appending
1007
+ # @return [Integer] length of the string after appending
957
1008
  def append(key, value)
958
1009
  synchronize do |client|
959
1010
  client.call([:append, key, value])
@@ -963,9 +1014,9 @@ class Redis
963
1014
  # Count the number of set bits in a range of the string value stored at key.
964
1015
  #
965
1016
  # @param [String] key
966
- # @param [Fixnum] start start index
967
- # @param [Fixnum] stop stop index
968
- # @return [Fixnum] the number of bits set to 1
1017
+ # @param [Integer] start start index
1018
+ # @param [Integer] stop stop index
1019
+ # @return [Integer] the number of bits set to 1
969
1020
  def bitcount(key, start = 0, stop = -1)
970
1021
  synchronize do |client|
971
1022
  client.call([:bitcount, key, start, stop])
@@ -977,7 +1028,7 @@ class Redis
977
1028
  # @param [String] operation e.g. `and`, `or`, `xor`, `not`
978
1029
  # @param [String] destkey destination key
979
1030
  # @param [String, Array<String>] keys one or more source keys to perform `operation`
980
- # @return [Fixnum] the length of the string stored in `destkey`
1031
+ # @return [Integer] the length of the string stored in `destkey`
981
1032
  def bitop(operation, destkey, *keys)
982
1033
  synchronize do |client|
983
1034
  client.call([:bitop, operation, destkey] + keys)
@@ -987,10 +1038,10 @@ class Redis
987
1038
  # Return the position of the first bit set to 1 or 0 in a string.
988
1039
  #
989
1040
  # @param [String] key
990
- # @param [Fixnum] bit whether to look for the first 1 or 0 bit
991
- # @param [Fixnum] start start index
992
- # @param [Fixnum] stop stop index
993
- # @return [Fixnum] the position of the first 1/0 bit.
1041
+ # @param [Integer] bit whether to look for the first 1 or 0 bit
1042
+ # @param [Integer] start start index
1043
+ # @param [Integer] stop stop index
1044
+ # @return [Integer] the position of the first 1/0 bit.
994
1045
  # -1 if looking for 1 and it is not found or start and stop are given.
995
1046
  def bitpos(key, bit, start=nil, stop=nil)
996
1047
  if stop and not start
@@ -1020,7 +1071,7 @@ class Redis
1020
1071
  # Get the length of the value stored in a key.
1021
1072
  #
1022
1073
  # @param [String] key
1023
- # @return [Fixnum] the length of the value stored in the key, or 0
1074
+ # @return [Integer] the length of the value stored in the key, or 0
1024
1075
  # if the key does not exist
1025
1076
  def strlen(key)
1026
1077
  synchronize do |client|
@@ -1031,7 +1082,7 @@ class Redis
1031
1082
  # Get the length of a list.
1032
1083
  #
1033
1084
  # @param [String] key
1034
- # @return [Fixnum]
1085
+ # @return [Integer]
1035
1086
  def llen(key)
1036
1087
  synchronize do |client|
1037
1088
  client.call([:llen, key])
@@ -1041,8 +1092,8 @@ class Redis
1041
1092
  # Prepend one or more values to a list, creating the list if it doesn't exist
1042
1093
  #
1043
1094
  # @param [String] key
1044
- # @param [String, Array] value string value, or array of string values to push
1045
- # @return [Fixnum] the length of the list after the push operation
1095
+ # @param [String, Array<String>] value string value, or array of string values to push
1096
+ # @return [Integer] the length of the list after the push operation
1046
1097
  def lpush(key, value)
1047
1098
  synchronize do |client|
1048
1099
  client.call([:lpush, key, value])
@@ -1053,7 +1104,7 @@ class Redis
1053
1104
  #
1054
1105
  # @param [String] key
1055
1106
  # @param [String] value
1056
- # @return [Fixnum] the length of the list after the push operation
1107
+ # @return [Integer] the length of the list after the push operation
1057
1108
  def lpushx(key, value)
1058
1109
  synchronize do |client|
1059
1110
  client.call([:lpushx, key, value])
@@ -1063,8 +1114,8 @@ class Redis
1063
1114
  # Append one or more values to a list, creating the list if it doesn't exist
1064
1115
  #
1065
1116
  # @param [String] key
1066
- # @param [String] value
1067
- # @return [Fixnum] the length of the list after the push operation
1117
+ # @param [String, Array<String>] value string value, or array of string values to push
1118
+ # @return [Integer] the length of the list after the push operation
1068
1119
  def rpush(key, value)
1069
1120
  synchronize do |client|
1070
1121
  client.call([:rpush, key, value])
@@ -1075,7 +1126,7 @@ class Redis
1075
1126
  #
1076
1127
  # @param [String] key
1077
1128
  # @param [String] value
1078
- # @return [Fixnum] the length of the list after the push operation
1129
+ # @return [Integer] the length of the list after the push operation
1079
1130
  def rpushx(key, value)
1080
1131
  synchronize do |client|
1081
1132
  client.call([:rpushx, key, value])
@@ -1113,28 +1164,27 @@ class Redis
1113
1164
  end
1114
1165
  end
1115
1166
 
1116
- def _bpop(cmd, args)
1117
- options = {}
1118
-
1119
- case args.last
1120
- when Hash
1167
+ def _bpop(cmd, args, &blk)
1168
+ timeout = if args.last.is_a?(Hash)
1121
1169
  options = args.pop
1122
- when Integer
1170
+ options[:timeout]
1171
+ elsif args.last.respond_to?(:to_int)
1123
1172
  # Issue deprecation notice in obnoxious mode...
1124
- options[:timeout] = args.pop
1173
+ args.pop.to_int
1125
1174
  end
1126
1175
 
1176
+ timeout ||= 0
1177
+
1127
1178
  if args.size > 1
1128
1179
  # Issue deprecation notice in obnoxious mode...
1129
1180
  end
1130
1181
 
1131
1182
  keys = args.flatten
1132
- timeout = options[:timeout] || 0
1133
1183
 
1134
1184
  synchronize do |client|
1135
1185
  command = [cmd, keys, timeout]
1136
1186
  timeout += client.timeout if timeout > 0
1137
- client.call_with_timeout(command, timeout)
1187
+ client.call_with_timeout(command, timeout, &blk)
1138
1188
  end
1139
1189
  end
1140
1190
 
@@ -1154,7 +1204,7 @@ class Redis
1154
1204
  # @param [String, Array<String>] keys one or more keys to perform the
1155
1205
  # blocking pop on
1156
1206
  # @param [Hash] options
1157
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1207
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1158
1208
  #
1159
1209
  # @return [nil, [String, String]]
1160
1210
  # - `nil` when the operation timed out
@@ -1168,7 +1218,7 @@ class Redis
1168
1218
  # @param [String, Array<String>] keys one or more keys to perform the
1169
1219
  # blocking pop on
1170
1220
  # @param [Hash] options
1171
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1221
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1172
1222
  #
1173
1223
  # @return [nil, [String, String]]
1174
1224
  # - `nil` when the operation timed out
@@ -1185,7 +1235,7 @@ class Redis
1185
1235
  # @param [String] source source key
1186
1236
  # @param [String] destination destination key
1187
1237
  # @param [Hash] options
1188
- # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout
1238
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
1189
1239
  #
1190
1240
  # @return [nil, String]
1191
1241
  # - `nil` when the operation timed out
@@ -1209,7 +1259,7 @@ class Redis
1209
1259
  # Get an element from a list by its index.
1210
1260
  #
1211
1261
  # @param [String] key
1212
- # @param [Fixnum] index
1262
+ # @param [Integer] index
1213
1263
  # @return [String]
1214
1264
  def lindex(key, index)
1215
1265
  synchronize do |client|
@@ -1223,7 +1273,7 @@ class Redis
1223
1273
  # @param [String, Symbol] where `BEFORE` or `AFTER`
1224
1274
  # @param [String] pivot reference element
1225
1275
  # @param [String] value
1226
- # @return [Fixnum] length of the list after the insert operation, or `-1`
1276
+ # @return [Integer] length of the list after the insert operation, or `-1`
1227
1277
  # when the element `pivot` was not found
1228
1278
  def linsert(key, where, pivot, value)
1229
1279
  synchronize do |client|
@@ -1234,8 +1284,8 @@ class Redis
1234
1284
  # Get a range of elements from a list.
1235
1285
  #
1236
1286
  # @param [String] key
1237
- # @param [Fixnum] start start index
1238
- # @param [Fixnum] stop stop index
1287
+ # @param [Integer] start start index
1288
+ # @param [Integer] stop stop index
1239
1289
  # @return [Array<String>]
1240
1290
  def lrange(key, start, stop)
1241
1291
  synchronize do |client|
@@ -1246,12 +1296,12 @@ class Redis
1246
1296
  # Remove elements from a list.
1247
1297
  #
1248
1298
  # @param [String] key
1249
- # @param [Fixnum] count number of elements to remove. Use a positive
1299
+ # @param [Integer] count number of elements to remove. Use a positive
1250
1300
  # value to remove the first `count` occurrences of `value`. A negative
1251
1301
  # value to remove the last `count` occurrences of `value`. Or zero, to
1252
1302
  # remove all occurrences of `value` from the list.
1253
1303
  # @param [String] value
1254
- # @return [Fixnum] the number of removed elements
1304
+ # @return [Integer] the number of removed elements
1255
1305
  def lrem(key, count, value)
1256
1306
  synchronize do |client|
1257
1307
  client.call([:lrem, key, count, value])
@@ -1261,7 +1311,7 @@ class Redis
1261
1311
  # Set the value of an element in a list by its index.
1262
1312
  #
1263
1313
  # @param [String] key
1264
- # @param [Fixnum] index
1314
+ # @param [Integer] index
1265
1315
  # @param [String] value
1266
1316
  # @return [String] `OK`
1267
1317
  def lset(key, index, value)
@@ -1273,8 +1323,8 @@ class Redis
1273
1323
  # Trim a list to the specified range.
1274
1324
  #
1275
1325
  # @param [String] key
1276
- # @param [Fixnum] start start index
1277
- # @param [Fixnum] stop stop index
1326
+ # @param [Integer] start start index
1327
+ # @param [Integer] stop stop index
1278
1328
  # @return [String] `OK`
1279
1329
  def ltrim(key, start, stop)
1280
1330
  synchronize do |client|
@@ -1285,7 +1335,7 @@ class Redis
1285
1335
  # Get the number of members in a set.
1286
1336
  #
1287
1337
  # @param [String] key
1288
- # @return [Fixnum]
1338
+ # @return [Integer]
1289
1339
  def scard(key)
1290
1340
  synchronize do |client|
1291
1341
  client.call([:scard, key])
@@ -1296,8 +1346,8 @@ class Redis
1296
1346
  #
1297
1347
  # @param [String] key
1298
1348
  # @param [String, Array<String>] member one member, or array of members
1299
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1300
- # holding whether or not adding the member succeeded, or `Fixnum` when an
1349
+ # @return [Boolean, Integer] `Boolean` when a single member is specified,
1350
+ # holding whether or not adding the member succeeded, or `Integer` when an
1301
1351
  # array of members is specified, holding the number of members that were
1302
1352
  # successfully added
1303
1353
  def sadd(key, member)
@@ -1318,8 +1368,8 @@ class Redis
1318
1368
  #
1319
1369
  # @param [String] key
1320
1370
  # @param [String, Array<String>] member one member, or array of members
1321
- # @return [Boolean, Fixnum] `Boolean` when a single member is specified,
1322
- # holding whether or not removing the member succeeded, or `Fixnum` when an
1371
+ # @return [Boolean, Integer] `Boolean` when a single member is specified,
1372
+ # holding whether or not removing the member succeeded, or `Integer` when an
1323
1373
  # array of members is specified, holding the number of members that were
1324
1374
  # successfully removed
1325
1375
  def srem(key, member)
@@ -1340,7 +1390,7 @@ class Redis
1340
1390
  #
1341
1391
  # @param [String] key
1342
1392
  # @return [String]
1343
- # @param [Fixnum] count
1393
+ # @param [Integer] count
1344
1394
  def spop(key, count = nil)
1345
1395
  synchronize do |client|
1346
1396
  if count.nil?
@@ -1354,7 +1404,7 @@ class Redis
1354
1404
  # Get one or more random members from a set.
1355
1405
  #
1356
1406
  # @param [String] key
1357
- # @param [Fixnum] count
1407
+ # @param [Integer] count
1358
1408
  # @return [String]
1359
1409
  def srandmember(key, count = nil)
1360
1410
  synchronize do |client|
@@ -1413,7 +1463,7 @@ class Redis
1413
1463
  #
1414
1464
  # @param [String] destination destination key
1415
1465
  # @param [String, Array<String>] keys keys pointing to sets to subtract
1416
- # @return [Fixnum] number of elements in the resulting set
1466
+ # @return [Integer] number of elements in the resulting set
1417
1467
  def sdiffstore(destination, *keys)
1418
1468
  synchronize do |client|
1419
1469
  client.call([:sdiffstore, destination] + keys)
@@ -1434,7 +1484,7 @@ class Redis
1434
1484
  #
1435
1485
  # @param [String] destination destination key
1436
1486
  # @param [String, Array<String>] keys keys pointing to sets to intersect
1437
- # @return [Fixnum] number of elements in the resulting set
1487
+ # @return [Integer] number of elements in the resulting set
1438
1488
  def sinterstore(destination, *keys)
1439
1489
  synchronize do |client|
1440
1490
  client.call([:sinterstore, destination] + keys)
@@ -1455,7 +1505,7 @@ class Redis
1455
1505
  #
1456
1506
  # @param [String] destination destination key
1457
1507
  # @param [String, Array<String>] keys keys pointing to sets to unify
1458
- # @return [Fixnum] number of elements in the resulting set
1508
+ # @return [Integer] number of elements in the resulting set
1459
1509
  def sunionstore(destination, *keys)
1460
1510
  synchronize do |client|
1461
1511
  client.call([:sunionstore, destination] + keys)
@@ -1469,7 +1519,7 @@ class Redis
1469
1519
  # # => 4
1470
1520
  #
1471
1521
  # @param [String] key
1472
- # @return [Fixnum]
1522
+ # @return [Integer]
1473
1523
  def zcard(key)
1474
1524
  synchronize do |client|
1475
1525
  client.call([:zcard, key])
@@ -1500,10 +1550,10 @@ class Redis
1500
1550
  # - `:incr => true`: When this option is specified ZADD acts like
1501
1551
  # ZINCRBY; only one score-element pair can be specified in this mode
1502
1552
  #
1503
- # @return [Boolean, Fixnum, Float]
1553
+ # @return [Boolean, Integer, Float]
1504
1554
  # - `Boolean` when a single pair is specified, holding whether or not it was
1505
1555
  # **added** to the sorted set.
1506
- # - `Fixnum` when an array of pairs is specified, holding the number of
1556
+ # - `Integer` when an array of pairs is specified, holding the number of
1507
1557
  # pairs that were **added** to the sorted set.
1508
1558
  # - `Float` when option :incr is specified, holding the score of the member
1509
1559
  # after incrementing it.
@@ -1566,10 +1616,10 @@ class Redis
1566
1616
  # - a single member
1567
1617
  # - an array of members
1568
1618
  #
1569
- # @return [Boolean, Fixnum]
1619
+ # @return [Boolean, Integer]
1570
1620
  # - `Boolean` when a single member is specified, holding whether or not it
1571
1621
  # was removed from the sorted set
1572
- # - `Fixnum` when an array of pairs is specified, holding the number of
1622
+ # - `Integer` when an array of pairs is specified, holding the number of
1573
1623
  # members that were removed to the sorted set
1574
1624
  def zrem(key, member)
1575
1625
  synchronize do |client|
@@ -1585,6 +1635,90 @@ class Redis
1585
1635
  end
1586
1636
  end
1587
1637
 
1638
+ # Removes and returns up to count members with the highest scores in the sorted set stored at key.
1639
+ #
1640
+ # @example Popping a member
1641
+ # redis.zpopmax('zset')
1642
+ # #=> ['b', 2.0]
1643
+ # @example With count option
1644
+ # redis.zpopmax('zset', 2)
1645
+ # #=> [['b', 2.0], ['a', 1.0]]
1646
+ #
1647
+ # @params key [String] a key of the sorted set
1648
+ # @params count [Integer] a number of members
1649
+ #
1650
+ # @return [Array<String, Float>] element and score pair if count is not specified
1651
+ # @return [Array<Array<String, Float>>] list of popped elements and scores
1652
+ def zpopmax(key, count = nil)
1653
+ synchronize do |client|
1654
+ members = client.call([:zpopmax, key, count].compact, &FloatifyPairs)
1655
+ count.to_i > 1 ? members : members.first
1656
+ end
1657
+ end
1658
+
1659
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at key.
1660
+ #
1661
+ # @example Popping a member
1662
+ # redis.zpopmin('zset')
1663
+ # #=> ['a', 1.0]
1664
+ # @example With count option
1665
+ # redis.zpopmin('zset', 2)
1666
+ # #=> [['a', 1.0], ['b', 2.0]]
1667
+ #
1668
+ # @params key [String] a key of the sorted set
1669
+ # @params count [Integer] a number of members
1670
+ #
1671
+ # @return [Array<String, Float>] element and score pair if count is not specified
1672
+ # @return [Array<Array<String, Float>>] list of popped elements and scores
1673
+ def zpopmin(key, count = nil)
1674
+ synchronize do |client|
1675
+ members = client.call([:zpopmin, key, count].compact, &FloatifyPairs)
1676
+ count.to_i > 1 ? members : members.first
1677
+ end
1678
+ end
1679
+
1680
+ # Removes and returns up to count members with the highest scores in the sorted set stored at keys,
1681
+ # or block until one is available.
1682
+ #
1683
+ # @example Popping a member from a sorted set
1684
+ # redis.bzpopmax('zset', 1)
1685
+ # #=> ['zset', 'b', 2.0]
1686
+ # @example Popping a member from multiple sorted sets
1687
+ # redis.bzpopmax('zset1', 'zset2', 1)
1688
+ # #=> ['zset1', 'b', 2.0]
1689
+ #
1690
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
1691
+ # @params timeout [Integer] the maximum number of seconds to block
1692
+ #
1693
+ # @return [Array<String, String, Float>] a touple of key, member and score
1694
+ # @return [nil] when no element could be popped and the timeout expired
1695
+ def bzpopmax(*args)
1696
+ _bpop(:bzpopmax, args) do |reply|
1697
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
1698
+ end
1699
+ end
1700
+
1701
+ # Removes and returns up to count members with the lowest scores in the sorted set stored at keys,
1702
+ # or block until one is available.
1703
+ #
1704
+ # @example Popping a member from a sorted set
1705
+ # redis.bzpopmin('zset', 1)
1706
+ # #=> ['zset', 'a', 1.0]
1707
+ # @example Popping a member from multiple sorted sets
1708
+ # redis.bzpopmin('zset1', 'zset2', 1)
1709
+ # #=> ['zset1', 'a', 1.0]
1710
+ #
1711
+ # @params keys [Array<String>] one or multiple keys of the sorted sets
1712
+ # @params timeout [Integer] the maximum number of seconds to block
1713
+ #
1714
+ # @return [Array<String, String, Float>] a touple of key, member and score
1715
+ # @return [nil] when no element could be popped and the timeout expired
1716
+ def bzpopmin(*args)
1717
+ _bpop(:bzpopmin, args) do |reply|
1718
+ reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
1719
+ end
1720
+ end
1721
+
1588
1722
  # Get the score associated with the given member in a sorted set.
1589
1723
  #
1590
1724
  # @example Get the score for member "a"
@@ -1610,8 +1744,8 @@ class Redis
1610
1744
  # # => [["a", 32.0], ["b", 64.0]]
1611
1745
  #
1612
1746
  # @param [String] key
1613
- # @param [Fixnum] start start index
1614
- # @param [Fixnum] stop stop index
1747
+ # @param [Integer] start start index
1748
+ # @param [Integer] stop stop index
1615
1749
  # @param [Hash] options
1616
1750
  # - `:with_scores => true`: include scores in output
1617
1751
  #
@@ -1663,7 +1797,7 @@ class Redis
1663
1797
  #
1664
1798
  # @param [String] key
1665
1799
  # @param [String] member
1666
- # @return [Fixnum]
1800
+ # @return [Integer]
1667
1801
  def zrank(key, member)
1668
1802
  synchronize do |client|
1669
1803
  client.call([:zrank, key, member])
@@ -1675,7 +1809,7 @@ class Redis
1675
1809
  #
1676
1810
  # @param [String] key
1677
1811
  # @param [String] member
1678
- # @return [Fixnum]
1812
+ # @return [Integer]
1679
1813
  def zrevrank(key, member)
1680
1814
  synchronize do |client|
1681
1815
  client.call([:zrevrank, key, member])
@@ -1692,15 +1826,39 @@ class Redis
1692
1826
  # # => 5
1693
1827
  #
1694
1828
  # @param [String] key
1695
- # @param [Fixnum] start start index
1696
- # @param [Fixnum] stop stop index
1697
- # @return [Fixnum] number of members that were removed
1829
+ # @param [Integer] start start index
1830
+ # @param [Integer] stop stop index
1831
+ # @return [Integer] number of members that were removed
1698
1832
  def zremrangebyrank(key, start, stop)
1699
1833
  synchronize do |client|
1700
1834
  client.call([:zremrangebyrank, key, start, stop])
1701
1835
  end
1702
1836
  end
1703
1837
 
1838
+ # Count the members, with the same score in a sorted set, within the given lexicographical range.
1839
+ #
1840
+ # @example Count members matching a
1841
+ # redis.zlexcount("zset", "[a", "[a\xff")
1842
+ # # => 1
1843
+ # @example Count members matching a-z
1844
+ # redis.zlexcount("zset", "[a", "[z\xff")
1845
+ # # => 26
1846
+ #
1847
+ # @param [String] key
1848
+ # @param [String] min
1849
+ # - inclusive minimum is specified by prefixing `(`
1850
+ # - exclusive minimum is specified by prefixing `[`
1851
+ # @param [String] max
1852
+ # - inclusive maximum is specified by prefixing `(`
1853
+ # - exclusive maximum is specified by prefixing `[`
1854
+ #
1855
+ # @return [Integer] number of members within the specified lexicographical range
1856
+ def zlexcount(key, min, max)
1857
+ synchronize do |client|
1858
+ client.call([:zlexcount, key, min, max])
1859
+ end
1860
+ end
1861
+
1704
1862
  # Return a range of members with the same score in a sorted set, by lexicographical ordering
1705
1863
  #
1706
1864
  # @example Retrieve members matching a
@@ -1848,7 +2006,7 @@ class Redis
1848
2006
  # @param [String] max
1849
2007
  # - inclusive maximum score is specified verbatim
1850
2008
  # - exclusive maximum score is specified by prefixing `(`
1851
- # @return [Fixnum] number of members that were removed
2009
+ # @return [Integer] number of members that were removed
1852
2010
  def zremrangebyscore(key, min, max)
1853
2011
  synchronize do |client|
1854
2012
  client.call([:zremrangebyscore, key, min, max])
@@ -1871,7 +2029,7 @@ class Redis
1871
2029
  # @param [String] max
1872
2030
  # - inclusive maximum score is specified verbatim
1873
2031
  # - exclusive maximum score is specified by prefixing `(`
1874
- # @return [Fixnum] number of members in within the specified range
2032
+ # @return [Integer] number of members in within the specified range
1875
2033
  def zcount(key, min, max)
1876
2034
  synchronize do |client|
1877
2035
  client.call([:zcount, key, min, max])
@@ -1891,7 +2049,7 @@ class Redis
1891
2049
  # - `:weights => [Float, Float, ...]`: weights to associate with source
1892
2050
  # sorted sets
1893
2051
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1894
- # @return [Fixnum] number of elements in the resulting sorted set
2052
+ # @return [Integer] number of elements in the resulting sorted set
1895
2053
  def zinterstore(destination, keys, options = {})
1896
2054
  args = []
1897
2055
 
@@ -1918,7 +2076,7 @@ class Redis
1918
2076
  # - `:weights => [Float, Float, ...]`: weights to associate with source
1919
2077
  # sorted sets
1920
2078
  # - `:aggregate => String`: aggregate function to use (sum, min, max, ...)
1921
- # @return [Fixnum] number of elements in the resulting sorted set
2079
+ # @return [Integer] number of elements in the resulting sorted set
1922
2080
  def zunionstore(destination, keys, options = {})
1923
2081
  args = []
1924
2082
 
@@ -1936,7 +2094,7 @@ class Redis
1936
2094
  # Get the number of fields in a hash.
1937
2095
  #
1938
2096
  # @param [String] key
1939
- # @return [Fixnum] number of fields in the hash
2097
+ # @return [Integer] number of fields in the hash
1940
2098
  def hlen(key)
1941
2099
  synchronize do |client|
1942
2100
  client.call([:hlen, key])
@@ -2052,10 +2210,10 @@ class Redis
2052
2210
  #
2053
2211
  # @param [String] key
2054
2212
  # @param [String, Array<String>] field
2055
- # @return [Fixnum] the number of fields that were removed from the hash
2056
- def hdel(key, field)
2213
+ # @return [Integer] the number of fields that were removed from the hash
2214
+ def hdel(key, *fields)
2057
2215
  synchronize do |client|
2058
- client.call([:hdel, key, field])
2216
+ client.call([:hdel, key, *fields])
2059
2217
  end
2060
2218
  end
2061
2219
 
@@ -2074,8 +2232,8 @@ class Redis
2074
2232
  #
2075
2233
  # @param [String] key
2076
2234
  # @param [String] field
2077
- # @param [Fixnum] increment
2078
- # @return [Fixnum] value of the field after incrementing it
2235
+ # @param [Integer] increment
2236
+ # @return [Integer] value of the field after incrementing it
2079
2237
  def hincrby(key, field, increment)
2080
2238
  synchronize do |client|
2081
2239
  client.call([:hincrby, key, field, increment])
@@ -2253,7 +2411,8 @@ class Redis
2253
2411
  def pipelined
2254
2412
  synchronize do |client|
2255
2413
  begin
2256
- original, @client = @client, Pipeline.new
2414
+ pipeline = Pipeline.new(@client)
2415
+ original, @client = @client, pipeline
2257
2416
  yield(self)
2258
2417
  original.call_pipeline(@client)
2259
2418
  ensure
@@ -2298,7 +2457,7 @@ class Redis
2298
2457
  client.call([:multi])
2299
2458
  else
2300
2459
  begin
2301
- pipeline = Pipeline::Multi.new
2460
+ pipeline = Pipeline::Multi.new(@client)
2302
2461
  original, @client = @client, pipeline
2303
2462
  yield(self)
2304
2463
  original.call_pipeline(pipeline)
@@ -2642,7 +2801,7 @@ class Redis
2642
2801
  # union of the HyperLogLogs contained in the keys.
2643
2802
  #
2644
2803
  # @param [String, Array<String>] keys
2645
- # @return [Fixnum]
2804
+ # @return [Integer]
2646
2805
  def pfcount(*keys)
2647
2806
  synchronize do |client|
2648
2807
  client.call([:pfcount] + keys)
@@ -2661,6 +2820,407 @@ class Redis
2661
2820
  end
2662
2821
  end
2663
2822
 
2823
+ # Adds the specified geospatial items (latitude, longitude, name) to the specified key
2824
+ #
2825
+ # @param [String] key
2826
+ # @param [Array] member arguemnts for member or members: longitude, latitude, name
2827
+ # @return [Integer] number of elements added to the sorted set
2828
+ def geoadd(key, *member)
2829
+ synchronize do |client|
2830
+ client.call([:geoadd, key, *member])
2831
+ end
2832
+ end
2833
+
2834
+ # Returns geohash string representing position for specified members of the specified key.
2835
+ #
2836
+ # @param [String] key
2837
+ # @param [String, Array<String>] member one member or array of members
2838
+ # @return [Array<String, nil>] returns array containg geohash string if member is present, nil otherwise
2839
+ def geohash(key, member)
2840
+ synchronize do |client|
2841
+ client.call([:geohash, key, member])
2842
+ end
2843
+ end
2844
+
2845
+
2846
+ # Query a sorted set representing a geospatial index to fetch members matching a
2847
+ # given maximum distance from a point
2848
+ #
2849
+ # @param [Array] args key, longitude, latitude, radius, unit(m|km|ft|mi)
2850
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
2851
+ # @param [Integer] count limit the results to the first N matching items
2852
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2853
+ # @return [Array<String>] may be changed with `options`
2854
+
2855
+ def georadius(*args, **geoptions)
2856
+ geoarguments = _geoarguments(*args, **geoptions)
2857
+
2858
+ synchronize do |client|
2859
+ client.call([:georadius, *geoarguments])
2860
+ end
2861
+ end
2862
+
2863
+ # Query a sorted set representing a geospatial index to fetch members matching a
2864
+ # given maximum distance from an already existing member
2865
+ #
2866
+ # @param [Array] args key, member, radius, unit(m|km|ft|mi)
2867
+ # @param ['asc', 'desc'] sort sort returned items from the nearest to the farthest or the farthest to the nearest relative to the center
2868
+ # @param [Integer] count limit the results to the first N matching items
2869
+ # @param ['WITHDIST', 'WITHCOORD', 'WITHHASH'] options to return additional information
2870
+ # @return [Array<String>] may be changed with `options`
2871
+
2872
+ def georadiusbymember(*args, **geoptions)
2873
+ geoarguments = _geoarguments(*args, **geoptions)
2874
+
2875
+ synchronize do |client|
2876
+ client.call([:georadiusbymember, *geoarguments])
2877
+ end
2878
+ end
2879
+
2880
+ # Returns longitude and latitude of members of a geospatial index
2881
+ #
2882
+ # @param [String] key
2883
+ # @param [String, Array<String>] member one member or array of members
2884
+ # @return [Array<Array<String>, nil>] returns array of elements, where each element is either array of longitude and latitude or nil
2885
+ def geopos(key, member)
2886
+ synchronize do |client|
2887
+ client.call([:geopos, key, member])
2888
+ end
2889
+ end
2890
+
2891
+ # Returns the distance between two members of a geospatial index
2892
+ #
2893
+ # @param [String ]key
2894
+ # @param [Array<String>] members
2895
+ # @param ['m', 'km', 'mi', 'ft'] unit
2896
+ # @return [String, nil] returns distance in spefied unit if both members present, nil otherwise.
2897
+ def geodist(key, member1, member2, unit = 'm')
2898
+ synchronize do |client|
2899
+ client.call([:geodist, key, member1, member2, unit])
2900
+ end
2901
+ end
2902
+
2903
+ # Returns the stream information each subcommand.
2904
+ #
2905
+ # @example stream
2906
+ # redis.xinfo(:stream, 'mystream')
2907
+ # @example groups
2908
+ # redis.xinfo(:groups, 'mystream')
2909
+ # @example consumers
2910
+ # redis.xinfo(:consumers, 'mystream', 'mygroup')
2911
+ #
2912
+ # @param subcommand [String] e.g. `stream` `groups` `consumers`
2913
+ # @param key [String] the stream key
2914
+ # @param group [String] the consumer group name, required if subcommand is `consumers`
2915
+ #
2916
+ # @return [Hash] information of the stream if subcommand is `stream`
2917
+ # @return [Array<Hash>] information of the consumer groups if subcommand is `groups`
2918
+ # @return [Array<Hash>] information of the consumers if subcommand is `consumers`
2919
+ def xinfo(subcommand, key, group = nil)
2920
+ args = [:xinfo, subcommand, key, group].compact
2921
+ synchronize do |client|
2922
+ client.call(args) do |reply|
2923
+ case subcommand.to_s.downcase
2924
+ when 'stream' then Hashify.call(reply)
2925
+ when 'groups', 'consumers' then reply.map { |arr| Hashify.call(arr) }
2926
+ else reply
2927
+ end
2928
+ end
2929
+ end
2930
+ end
2931
+
2932
+ # Add new entry to the stream.
2933
+ #
2934
+ # @example Without options
2935
+ # redis.xadd('mystream', f1: 'v1', f2: 'v2')
2936
+ # @example With options
2937
+ # redis.xadd('mystream', { f1: 'v1', f2: 'v2' }, id: '0-0', maxlen: 1000, approximate: true)
2938
+ #
2939
+ # @param key [String] the stream key
2940
+ # @param entry [Hash] one or multiple field-value pairs
2941
+ # @param opts [Hash] several options for `XADD` command
2942
+ #
2943
+ # @option opts [String] :id the entry id, default value is `*`, it means auto generation
2944
+ # @option opts [Integer] :maxlen max length of entries
2945
+ # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
2946
+ #
2947
+ # @return [String] the entry id
2948
+ def xadd(key, entry, opts = {})
2949
+ args = [:xadd, key]
2950
+ args.concat(['MAXLEN', (opts[:approximate] ? '~' : nil), opts[:maxlen]].compact) if opts[:maxlen]
2951
+ args << (opts[:id] || '*')
2952
+ args.concat(entry.to_a.flatten)
2953
+ synchronize { |client| client.call(args) }
2954
+ end
2955
+
2956
+ # Trims older entries of the stream if needed.
2957
+ #
2958
+ # @example Without options
2959
+ # redis.xtrim('mystream', 1000)
2960
+ # @example With options
2961
+ # redis.xtrim('mystream', 1000, approximate: true)
2962
+ #
2963
+ # @param key [String] the stream key
2964
+ # @param mexlen [Integer] max length of entries
2965
+ # @param approximate [Boolean] whether to add `~` modifier of maxlen or not
2966
+ #
2967
+ # @return [Integer] the number of entries actually deleted
2968
+ def xtrim(key, maxlen, approximate: false)
2969
+ args = [:xtrim, key, 'MAXLEN', (approximate ? '~' : nil), maxlen].compact
2970
+ synchronize { |client| client.call(args) }
2971
+ end
2972
+
2973
+ # Delete entries by entry ids.
2974
+ #
2975
+ # @example With splatted entry ids
2976
+ # redis.xdel('mystream', '0-1', '0-2')
2977
+ # @example With arrayed entry ids
2978
+ # redis.xdel('mystream', ['0-1', '0-2'])
2979
+ #
2980
+ # @param key [String] the stream key
2981
+ # @param ids [Array<String>] one or multiple entry ids
2982
+ #
2983
+ # @return [Integer] the number of entries actually deleted
2984
+ def xdel(key, *ids)
2985
+ args = [:xdel, key].concat(ids.flatten)
2986
+ synchronize { |client| client.call(args) }
2987
+ end
2988
+
2989
+ # Fetches entries of the stream in ascending order.
2990
+ #
2991
+ # @example Without options
2992
+ # redis.xrange('mystream')
2993
+ # @example With a specific start
2994
+ # redis.xrange('mystream', '0-1')
2995
+ # @example With a specific start and end
2996
+ # redis.xrange('mystream', '0-1', '0-3')
2997
+ # @example With count options
2998
+ # redis.xrange('mystream', count: 10)
2999
+ #
3000
+ # @param key [String] the stream key
3001
+ # @param start [String] first entry id of range, default value is `-`
3002
+ # @param end [String] last entry id of range, default value is `+`
3003
+ # @param count [Integer] the number of entries as limit
3004
+ #
3005
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
3006
+ def xrange(key, start = '-', _end = '+', count: nil)
3007
+ args = [:xrange, key, start, _end]
3008
+ args.concat(['COUNT', count]) if count
3009
+ synchronize { |client| client.call(args, &HashifyStreamEntries) }
3010
+ end
3011
+
3012
+ # Fetches entries of the stream in descending order.
3013
+ #
3014
+ # @example Without options
3015
+ # redis.xrevrange('mystream')
3016
+ # @example With a specific end
3017
+ # redis.xrevrange('mystream', '0-3')
3018
+ # @example With a specific end and start
3019
+ # redis.xrevrange('mystream', '0-3', '0-1')
3020
+ # @example With count options
3021
+ # redis.xrevrange('mystream', count: 10)
3022
+ #
3023
+ # @param key [String] the stream key
3024
+ # @param end [String] first entry id of range, default value is `+`
3025
+ # @param start [String] last entry id of range, default value is `-`
3026
+ # @params count [Integer] the number of entries as limit
3027
+ #
3028
+ # @return [Array<Array<String, Hash>>] the ids and entries pairs
3029
+ def xrevrange(key, _end = '+', start = '-', count: nil)
3030
+ args = [:xrevrange, key, _end, start]
3031
+ args.concat(['COUNT', count]) if count
3032
+ synchronize { |client| client.call(args, &HashifyStreamEntries) }
3033
+ end
3034
+
3035
+ # Returns the number of entries inside a stream.
3036
+ #
3037
+ # @example With key
3038
+ # redis.xlen('mystream')
3039
+ #
3040
+ # @param key [String] the stream key
3041
+ #
3042
+ # @return [Integer] the number of entries
3043
+ def xlen(key)
3044
+ synchronize { |client| client.call([:xlen, key]) }
3045
+ end
3046
+
3047
+ # Fetches entries from one or multiple streams. Optionally blocking.
3048
+ #
3049
+ # @example With a key
3050
+ # redis.xread('mystream', '0-0')
3051
+ # @example With multiple keys
3052
+ # redis.xread(%w[mystream1 mystream2], %w[0-0 0-0])
3053
+ # @example With count option
3054
+ # redis.xread('mystream', '0-0', count: 2)
3055
+ # @example With block option
3056
+ # redis.xread('mystream', '$', block: 1000)
3057
+ #
3058
+ # @param keys [Array<String>] one or multiple stream keys
3059
+ # @param ids [Array<String>] one or multiple entry ids
3060
+ # @param count [Integer] the number of entries as limit per stream
3061
+ # @param block [Integer] the number of milliseconds as blocking timeout
3062
+ #
3063
+ # @return [Hash{String => Hash{String => Hash}}] the entries
3064
+ def xread(keys, ids, count: nil, block: nil)
3065
+ args = [:xread]
3066
+ args << 'COUNT' << count if count
3067
+ args << 'BLOCK' << block.to_i if block
3068
+ _xread(args, keys, ids, block)
3069
+ end
3070
+
3071
+ # Manages the consumer group of the stream.
3072
+ #
3073
+ # @example With `create` subcommand
3074
+ # redis.xgroup(:create, 'mystream', 'mygroup', '$')
3075
+ # @example With `setid` subcommand
3076
+ # redis.xgroup(:setid, 'mystream', 'mygroup', '$')
3077
+ # @example With `destroy` subcommand
3078
+ # redis.xgroup(:destroy, 'mystream', 'mygroup')
3079
+ # @example With `delconsumer` subcommand
3080
+ # redis.xgroup(:delconsumer, 'mystream', 'mygroup', 'consumer1')
3081
+ #
3082
+ # @param subcommand [String] `create` `setid` `destroy` `delconsumer`
3083
+ # @param key [String] the stream key
3084
+ # @param group [String] the consumer group name
3085
+ # @param id_or_consumer [String]
3086
+ # * the entry id or `$`, required if subcommand is `create` or `setid`
3087
+ # * the consumer name, required if subcommand is `delconsumer`
3088
+ # @param mkstream [Boolean] whether to create an empty stream automatically or not
3089
+ #
3090
+ # @return [String] `OK` if subcommand is `create` or `setid`
3091
+ # @return [Integer] effected count if subcommand is `destroy` or `delconsumer`
3092
+ def xgroup(subcommand, key, group, id_or_consumer = nil, mkstream: false)
3093
+ args = [:xgroup, subcommand, key, group, id_or_consumer, (mkstream ? 'MKSTREAM' : nil)].compact
3094
+ synchronize { |client| client.call(args) }
3095
+ end
3096
+
3097
+ # Fetches a subset of the entries from one or multiple streams related with the consumer group.
3098
+ # Optionally blocking.
3099
+ #
3100
+ # @example With a key
3101
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>')
3102
+ # @example With multiple keys
3103
+ # redis.xreadgroup('mygroup', 'consumer1', %w[mystream1 mystream2], %w[> >])
3104
+ # @example With count option
3105
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', count: 2)
3106
+ # @example With block option
3107
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', block: 1000)
3108
+ # @example With noack option
3109
+ # redis.xreadgroup('mygroup', 'consumer1', 'mystream', '>', noack: true)
3110
+ #
3111
+ # @param group [String] the consumer group name
3112
+ # @param consumer [String] the consumer name
3113
+ # @param keys [Array<String>] one or multiple stream keys
3114
+ # @param ids [Array<String>] one or multiple entry ids
3115
+ # @param opts [Hash] several options for `XREADGROUP` command
3116
+ #
3117
+ # @option opts [Integer] :count the number of entries as limit
3118
+ # @option opts [Integer] :block the number of milliseconds as blocking timeout
3119
+ # @option opts [Boolean] :noack whether message loss is acceptable or not
3120
+ #
3121
+ # @return [Hash{String => Hash{String => Hash}}] the entries
3122
+ def xreadgroup(group, consumer, keys, ids, opts = {})
3123
+ args = [:xreadgroup, 'GROUP', group, consumer]
3124
+ args << 'COUNT' << opts[:count] if opts[:count]
3125
+ args << 'BLOCK' << opts[:block].to_i if opts[:block]
3126
+ args << 'NOACK' if opts[:noack]
3127
+ _xread(args, keys, ids, opts[:block])
3128
+ end
3129
+
3130
+ # Removes one or multiple entries from the pending entries list of a stream consumer group.
3131
+ #
3132
+ # @example With a entry id
3133
+ # redis.xack('mystream', 'mygroup', '1526569495631-0')
3134
+ # @example With splatted entry ids
3135
+ # redis.xack('mystream', 'mygroup', '0-1', '0-2')
3136
+ # @example With arrayed entry ids
3137
+ # redis.xack('mystream', 'mygroup', %w[0-1 0-2])
3138
+ #
3139
+ # @param key [String] the stream key
3140
+ # @param group [String] the consumer group name
3141
+ # @param ids [Array<String>] one or multiple entry ids
3142
+ #
3143
+ # @return [Integer] the number of entries successfully acknowledged
3144
+ def xack(key, group, *ids)
3145
+ args = [:xack, key, group].concat(ids.flatten)
3146
+ synchronize { |client| client.call(args) }
3147
+ end
3148
+
3149
+ # Changes the ownership of a pending entry
3150
+ #
3151
+ # @example With splatted entry ids
3152
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, '0-1', '0-2')
3153
+ # @example With arrayed entry ids
3154
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2])
3155
+ # @example With idle option
3156
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], idle: 1000)
3157
+ # @example With time option
3158
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], time: 1542866959000)
3159
+ # @example With retrycount option
3160
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], retrycount: 10)
3161
+ # @example With force option
3162
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], force: true)
3163
+ # @example With justid option
3164
+ # redis.xclaim('mystream', 'mygroup', 'consumer1', 3600000, %w[0-1 0-2], justid: true)
3165
+ #
3166
+ # @param key [String] the stream key
3167
+ # @param group [String] the consumer group name
3168
+ # @param consumer [String] the consumer name
3169
+ # @param min_idle_time [Integer] the number of milliseconds
3170
+ # @param ids [Array<String>] one or multiple entry ids
3171
+ # @param opts [Hash] several options for `XCLAIM` command
3172
+ #
3173
+ # @option opts [Integer] :idle the number of milliseconds as last time it was delivered of the entry
3174
+ # @option opts [Integer] :time the number of milliseconds as a specific Unix Epoch time
3175
+ # @option opts [Integer] :retrycount the number of retry counter
3176
+ # @option opts [Boolean] :force whether to create the pending entry to the pending entries list or not
3177
+ # @option opts [Boolean] :justid whether to fetch just an array of entry ids or not
3178
+ #
3179
+ # @return [Hash{String => Hash}] the entries successfully claimed
3180
+ # @return [Array<String>] the entry ids successfully claimed if justid option is `true`
3181
+ def xclaim(key, group, consumer, min_idle_time, *ids, **opts)
3182
+ args = [:xclaim, key, group, consumer, min_idle_time].concat(ids.flatten)
3183
+ args.concat(['IDLE', opts[:idle].to_i]) if opts[:idle]
3184
+ args.concat(['TIME', opts[:time].to_i]) if opts[:time]
3185
+ args.concat(['RETRYCOUNT', opts[:retrycount]]) if opts[:retrycount]
3186
+ args << 'FORCE' if opts[:force]
3187
+ args << 'JUSTID' if opts[:justid]
3188
+ blk = opts[:justid] ? Noop : HashifyStreamEntries
3189
+ synchronize { |client| client.call(args, &blk) }
3190
+ end
3191
+
3192
+ # Fetches not acknowledging pending entries
3193
+ #
3194
+ # @example With key and group
3195
+ # redis.xpending('mystream', 'mygroup')
3196
+ # @example With range options
3197
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10)
3198
+ # @example With range and consumer options
3199
+ # redis.xpending('mystream', 'mygroup', '-', '+', 10, 'consumer1')
3200
+ #
3201
+ # @param key [String] the stream key
3202
+ # @param group [String] the consumer group name
3203
+ # @param start [String] start first entry id of range
3204
+ # @param end [String] end last entry id of range
3205
+ # @param count [Integer] count the number of entries as limit
3206
+ # @param consumer [String] the consumer name
3207
+ #
3208
+ # @return [Hash] the summary of pending entries
3209
+ # @return [Array<Hash>] the pending entries details if options were specified
3210
+ def xpending(key, group, *args)
3211
+ command_args = [:xpending, key, group]
3212
+ case args.size
3213
+ when 0, 3, 4
3214
+ command_args.concat(args)
3215
+ else
3216
+ raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 2, 5 or 6)"
3217
+ end
3218
+
3219
+ summary_needed = args.empty?
3220
+ blk = summary_needed ? HashifyStreamPendings : HashifyStreamPendingDetails
3221
+ synchronize { |client| client.call(command_args, &blk) }
3222
+ end
3223
+
2664
3224
  # Interact with the sentinel command (masters, master, slaves, failover)
2665
3225
  #
2666
3226
  # @param [String] subcommand e.g. `masters`, `master`, `slaves`
@@ -2688,6 +3248,41 @@ class Redis
2688
3248
  end
2689
3249
  end
2690
3250
 
3251
+ # Sends `CLUSTER *` command to random node and returns its reply.
3252
+ #
3253
+ # @see https://redis.io/commands#cluster Reference of cluster command
3254
+ #
3255
+ # @param subcommand [String, Symbol] the subcommand of cluster command
3256
+ # e.g. `:slots`, `:nodes`, `:slaves`, `:info`
3257
+ #
3258
+ # @return [Object] depends on the subcommand
3259
+ def cluster(subcommand, *args)
3260
+ subcommand = subcommand.to_s.downcase
3261
+ block = case subcommand
3262
+ when 'slots' then HashifyClusterSlots
3263
+ when 'nodes' then HashifyClusterNodes
3264
+ when 'slaves' then HashifyClusterSlaves
3265
+ when 'info' then HashifyInfo
3266
+ else Noop
3267
+ end
3268
+
3269
+ # @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
3270
+ block = Noop unless @cluster_mode
3271
+
3272
+ synchronize do |client|
3273
+ client.call([:cluster, subcommand] + args, &block)
3274
+ end
3275
+ end
3276
+
3277
+ # Sends `ASKING` command to random node and returns its reply.
3278
+ #
3279
+ # @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
3280
+ #
3281
+ # @return [String] `'OK'`
3282
+ def asking
3283
+ synchronize { |client| client.call(%i[asking]) }
3284
+ end
3285
+
2691
3286
  def id
2692
3287
  @original_client.id
2693
3288
  end
@@ -2701,12 +3296,14 @@ class Redis
2701
3296
  end
2702
3297
 
2703
3298
  def connection
3299
+ return @original_client.connection_info if @cluster_mode
3300
+
2704
3301
  {
2705
- :host => @original_client.host,
2706
- :port => @original_client.port,
2707
- :db => @original_client.db,
2708
- :id => @original_client.id,
2709
- :location => @original_client.location
3302
+ host: @original_client.host,
3303
+ port: @original_client.port,
3304
+ db: @original_client.db,
3305
+ id: @original_client.id,
3306
+ location: @original_client.location
2710
3307
  }
2711
3308
  end
2712
3309
 
@@ -2721,48 +3318,144 @@ private
2721
3318
  # Commands returning 1 for true and 0 for false may be executed in a pipeline
2722
3319
  # where the method call will return nil. Propagate the nil instead of falsely
2723
3320
  # returning false.
2724
- Boolify =
2725
- lambda { |value|
2726
- value == 1 if value
2727
- }
3321
+ Boolify = lambda { |value|
3322
+ case value
3323
+ when 1
3324
+ true
3325
+ when 0
3326
+ false
3327
+ else
3328
+ value
3329
+ end
3330
+ }
2728
3331
 
2729
- BoolifySet =
2730
- lambda { |value|
2731
- if value && "OK" == value
2732
- true
2733
- else
2734
- false
2735
- end
2736
- }
3332
+ BoolifySet = lambda { |value|
3333
+ case value
3334
+ when "OK"
3335
+ true
3336
+ when nil
3337
+ false
3338
+ else
3339
+ value
3340
+ end
3341
+ }
2737
3342
 
2738
- Hashify =
2739
- lambda { |array|
2740
- hash = Hash.new
2741
- array.each_slice(2) do |field, value|
2742
- hash[field] = value
2743
- end
2744
- hash
2745
- }
3343
+ Hashify = lambda { |value|
3344
+ if value.respond_to?(:each_slice)
3345
+ value.each_slice(2).to_h
3346
+ else
3347
+ value
3348
+ end
3349
+ }
3350
+
3351
+ Floatify = lambda { |value|
3352
+ case value
3353
+ when "inf"
3354
+ Float::INFINITY
3355
+ when "-inf"
3356
+ -Float::INFINITY
3357
+ when String
3358
+ Float(value)
3359
+ else
3360
+ value
3361
+ end
3362
+ }
2746
3363
 
2747
- Floatify =
2748
- lambda { |str|
2749
- if str
2750
- if (inf = str.match(/^(-)?inf/i))
2751
- (inf[1] ? -1.0 : 1.0) / 0.0
2752
- else
2753
- Float(str)
2754
- end
2755
- end
3364
+ FloatifyPairs = lambda { |value|
3365
+ return value unless value.respond_to?(:each_slice)
3366
+
3367
+ value.each_slice(2).map do |member, score|
3368
+ [member, Floatify.call(score)]
3369
+ end
3370
+ }
3371
+
3372
+ HashifyInfo = lambda { |reply|
3373
+ lines = reply.split("\r\n").grep_v(/^(#|$)/)
3374
+ lines.map! { |line| line.split(':', 2) }
3375
+ lines.compact!
3376
+ lines.to_h
3377
+ }
3378
+
3379
+ HashifyStreams = lambda { |reply|
3380
+ case reply
3381
+ when nil
3382
+ {}
3383
+ else
3384
+ reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
3385
+ end
3386
+ }
3387
+
3388
+ HashifyStreamEntries = lambda { |reply|
3389
+ reply.map do |entry_id, values|
3390
+ [entry_id, values.each_slice(2).to_h]
3391
+ end
3392
+ }
3393
+
3394
+ HashifyStreamPendings = lambda { |reply|
3395
+ {
3396
+ 'size' => reply[0],
3397
+ 'min_entry_id' => reply[1],
3398
+ 'max_entry_id' => reply[2],
3399
+ 'consumers' => reply[3].nil? ? {} : reply[3].to_h
2756
3400
  }
3401
+ }
2757
3402
 
2758
- FloatifyPairs =
2759
- lambda { |array|
2760
- if array
2761
- array.each_slice(2).map do |member, score|
2762
- [member, Floatify.call(score)]
2763
- end
2764
- end
3403
+ HashifyStreamPendingDetails = lambda { |reply|
3404
+ reply.map do |arr|
3405
+ {
3406
+ 'entry_id' => arr[0],
3407
+ 'consumer' => arr[1],
3408
+ 'elapsed' => arr[2],
3409
+ 'count' => arr[3]
3410
+ }
3411
+ end
3412
+ }
3413
+
3414
+ HashifyClusterNodeInfo = lambda { |str|
3415
+ arr = str.split(' ')
3416
+ {
3417
+ 'node_id' => arr[0],
3418
+ 'ip_port' => arr[1],
3419
+ 'flags' => arr[2].split(','),
3420
+ 'master_node_id' => arr[3],
3421
+ 'ping_sent' => arr[4],
3422
+ 'pong_recv' => arr[5],
3423
+ 'config_epoch' => arr[6],
3424
+ 'link_state' => arr[7],
3425
+ 'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
2765
3426
  }
3427
+ }
3428
+
3429
+ HashifyClusterSlots = lambda { |reply|
3430
+ reply.map do |arr|
3431
+ first_slot, last_slot = arr[0..1]
3432
+ master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
3433
+ replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
3434
+ {
3435
+ 'start_slot' => first_slot,
3436
+ 'end_slot' => last_slot,
3437
+ 'master' => master,
3438
+ 'replicas' => replicas
3439
+ }
3440
+ end
3441
+ }
3442
+
3443
+ HashifyClusterNodes = lambda { |reply|
3444
+ reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
3445
+ }
3446
+
3447
+ HashifyClusterSlaves = lambda { |reply|
3448
+ reply.map { |str| HashifyClusterNodeInfo.call(str) }
3449
+ }
3450
+
3451
+ Noop = ->(reply) { reply }
3452
+
3453
+ def _geoarguments(*args, options: nil, sort: nil, count: nil)
3454
+ args.push sort if sort
3455
+ args.push 'count', count if count
3456
+ args.push options if options
3457
+ args
3458
+ end
2766
3459
 
2767
3460
  def _subscription(method, timeout, channels, block)
2768
3461
  return @client.call([method] + channels) if subscribed?
@@ -2779,10 +3472,29 @@ private
2779
3472
  end
2780
3473
  end
2781
3474
 
3475
+ def _xread(args, keys, ids, blocking_timeout_msec)
3476
+ keys = keys.is_a?(Array) ? keys : [keys]
3477
+ ids = ids.is_a?(Array) ? ids : [ids]
3478
+ args << 'STREAMS'
3479
+ args.concat(keys)
3480
+ args.concat(ids)
3481
+
3482
+ synchronize do |client|
3483
+ if blocking_timeout_msec.nil?
3484
+ client.call(args, &HashifyStreams)
3485
+ elsif blocking_timeout_msec.to_f.zero?
3486
+ client.call_without_timeout(args, &HashifyStreams)
3487
+ else
3488
+ timeout = client.timeout.to_f + blocking_timeout_msec.to_f / 1000.0
3489
+ client.call_with_timeout(args, timeout, &HashifyStreams)
3490
+ end
3491
+ end
3492
+ end
2782
3493
  end
2783
3494
 
2784
- require "redis/version"
2785
- require "redis/connection"
2786
- require "redis/client"
2787
- require "redis/pipeline"
2788
- require "redis/subscribe"
3495
+ require_relative "redis/version"
3496
+ require_relative "redis/connection"
3497
+ require_relative "redis/client"
3498
+ require_relative "redis/cluster"
3499
+ require_relative "redis/pipeline"
3500
+ require_relative "redis/subscribe"