redis 5.0.6 → 5.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b000349da8b2f7ae8ed14909175306d2c719a9873bf1dabe05c4719ae96d0da
4
- data.tar.gz: fdcda2f50f9d33265f7ba24d3f29a2e078fabe5240e1eaf01efae51e0b83eb0b
3
+ metadata.gz: 5d76a0d8a8a0361f991dc110219ffc77c12503148f9ece9586dd274321283265
4
+ data.tar.gz: fab1b1d4d3a5e22d2d952c98648a1412239f117a2c53bcbdf20ea7c43f456faa
5
5
  SHA512:
6
- metadata.gz: 990972ebefe548f952cbf630708298f52c1856fa5a142a341f2548bd9173f03f0106d6b745c7e39cc5baa9f084d5fea5e4c0bf42cf1d42df94e404f3558d7816
7
- data.tar.gz: e52615e0d5b7ca9d554ffc540fdfec524d2aed74ac1c7fc3c727d3096504aa81ff43a85b9e2ce7b21da87edf21ca5f92197171ff1b1c62fa85c7551839e9df83
6
+ metadata.gz: 042f0aa785647bb0e6f73ff50722ba5dcf440e3f5e817c7eec030b876b7d34ca8be42a7a5a5a1767eba66b7424a8d2e74fe8e7b9e0362eedefe4fb58878cdb57
7
+ data.tar.gz: 5bc22d99f3a77929f35c02072be18d663ca127c77055afadd55455d3f571f61bbd70853d6fd4414e0e11f601c20d68872e8f097fce109341e5bda39f6aadb9fe
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Unreleased
2
2
 
3
+ # 5.4.1
4
+
5
+ - Properly handle NOSCRIPT errors.
6
+
7
+ # 5.4.0
8
+
9
+ - Fix `blmpop` method to actually use `BLMPOP`, it was mistakenly issuing `LMPOP` commands.
10
+ - `xadd` now accepts a `minid:` argument.
11
+ - `zrank` and `zrevrank` now accepts `with_score:` argument.
12
+ - `Redis#call` now accept a block, allowing to use `Redis` instances where `RedisClient` is expected.
13
+
14
+ # 5.3.0
15
+
16
+ - Fix the return type of `hgetall` when used inside a `multi` transaction which is itself inside a pipeline.
17
+
18
+ # 5.2.0
19
+
20
+ - Now require Ruby 2.6 because `redis-client` does.
21
+ - Eagerly close subscribed connection when using `subscribe_with_timeout`. See #1259.
22
+ - Add `exception` flag in `pipelined` allowing failed commands to be returned in the result array when set to `false`.
23
+
24
+ # 5.1.0
25
+
26
+ - `multi` now accept a `watch` keyword argument like `redis-client`. See #1236.
27
+ - `bitcount` and `bitpos` now accept a `scale:` argument on Redis 7+. See #1242
28
+ - Added `expiretime` and `pexpiretime`. See #1248.
29
+
30
+ # 5.0.8
31
+
32
+ - Fix `Redis#without_reconnect` for sentinel clients. Fix #1212.
33
+ - Add `sentinel_username`, `sentinel_password` for sentinel clients. Bump `redis-client` to `>=0.17.0`. See #1213
34
+
35
+ # 5.0.7
36
+
37
+ - Fix compatibility with `redis-client 0.15.0` when using Redis Sentinel. Fix #1209.
38
+
3
39
  # 5.0.6
4
40
 
5
41
  - Wait for an extra `config.read_timeout` in blocking commands rather than an arbitrary 100ms. See #1175.
@@ -27,6 +63,7 @@
27
63
 
28
64
  # 5.0.0
29
65
 
66
+ - Default client timeout decreased from 5 seconds to 1 second.
30
67
  - Eagerly and strictly cast Integer and Float parameters.
31
68
  - Allow to call `subscribe`, `unsubscribe`, `psubscribe` and `punsubscribe` from a subscribed client. See #1131.
32
69
  - Use `MD5` for hashing server nodes in `Redis::Distributed`. This should improve keys distribution among servers. See #1089.
@@ -49,6 +86,10 @@
49
86
  - Removed the `synchrony` driver.
50
87
  - Removed `Redis.exists_returns_integer`, it's now always enabled.
51
88
 
89
+ # 4.8.1
90
+
91
+ * Automatically reconnect after fork regardless of `reconnect_attempts`
92
+
52
93
  # 4.8.0
53
94
 
54
95
  * Introduce `sadd?` and `srem?` as boolean returning versions of `sadd` and `srem`.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # redis-rb [![Build Status][gh-actions-image]][gh-actions-link] [![Inline docs][inchpages-image]][inchpages-link]
1
+ # redis-rb [![Build Status][gh-actions-image]][gh-actions-link] [![Inline docs][rdoc-master-image]][rdoc-master-link]
2
2
 
3
3
  A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still providing an idiomatic interface.
4
4
 
@@ -34,7 +34,7 @@ You can also specify connection options as a [`redis://` URL][redis-url]:
34
34
  redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
35
35
  ```
36
36
 
37
- The client expects passwords with special chracters to be URL-encoded (i.e.
37
+ The client expects passwords with special characters to be URL-encoded (i.e.
38
38
  `CGI.escape(password)`).
39
39
 
40
40
  To connect to Redis listening on a Unix socket, try:
@@ -77,7 +77,7 @@ The client does not provide connection pooling. Each `Redis` instance
77
77
  has one and only one connection to the server, and use of this connection
78
78
  is protected by a mutex.
79
79
 
80
- As such it is heavilly recommended to use the [`connection_pool` gem](https://github.com/mperham/connection_pool), e.g.:
80
+ As such it is heavily recommended to use the [`connection_pool` gem](https://github.com/mperham/connection_pool), e.g.:
81
81
 
82
82
  ```ruby
83
83
  module MyApp
@@ -103,7 +103,7 @@ To connect using Sentinel, use:
103
103
  SENTINELS = [{ host: "127.0.0.1", port: 26380 },
104
104
  { host: "127.0.0.1", port: 26381 }]
105
105
 
106
- redis = Redis.new(url: "redis://mymaster", sentinels: SENTINELS, role: :master)
106
+ redis = Redis.new(name: "mymaster", sentinels: SENTINELS, role: :master)
107
107
  ```
108
108
 
109
109
  * The master name identifies a group of Redis instances composed of a master
@@ -120,13 +120,39 @@ but a few so that if one is down the client will try the next one. The client
120
120
  is able to remember the last Sentinel that was able to reply correctly and will
121
121
  use it for the next requests.
122
122
 
123
- 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.
123
+ To [authenticate](https://redis.io/docs/management/sentinel/#configuring-sentinel-instances-with-authentication) Sentinel itself, you can specify the `sentinel_username` and `sentinel_password`. Exclude the `sentinel_username` option if you're using password-only authentication.
124
124
 
125
125
  ```ruby
126
- SENTINELS = [{ host: '127.0.0.1', port: 26380, password: 'mysecret' },
127
- { host: '127.0.0.1', port: 26381, password: 'mysecret' }]
126
+ SENTINELS = [{ host: '127.0.0.1', port: 26380},
127
+ { host: '127.0.0.1', port: 26381}]
128
128
 
129
- redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master)
129
+ redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, sentinel_username: 'appuser', sentinel_password: 'mysecret', role: :master)
130
+ ```
131
+
132
+ If you specify a username and/or password at the top level for your main Redis instance, Sentinel *will not* using thouse credentials
133
+
134
+ ```ruby
135
+ # Use 'mysecret' to authenticate against the mymaster instance, but skip authentication for the sentinels:
136
+ SENTINELS = [{ host: '127.0.0.1', port: 26380 },
137
+ { host: '127.0.0.1', port: 26381 }]
138
+
139
+ redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret')
140
+ ```
141
+
142
+ So you have to provide Sentinel credential and Redis explicitly even they are the same
143
+
144
+ ```ruby
145
+ # Use 'mysecret' to authenticate against the mymaster instance and sentinel
146
+ SENTINELS = [{ host: '127.0.0.1', port: 26380 },
147
+ { host: '127.0.0.1', port: 26381 }]
148
+
149
+ redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master, password: 'mysecret', sentinel_password: 'mysecret')
150
+ ```
151
+
152
+ Also the `name`, `password`, `username` and `db` for Redis instance can be passed as an url:
153
+
154
+ ```ruby
155
+ redis = Redis.new(url: "redis://appuser:mysecret@mymaster/10", sentinels: SENTINELS, role: :master)
130
156
  ```
131
157
 
132
158
  ## Cluster support
@@ -165,6 +191,28 @@ end
165
191
  # => ["OK"]
166
192
  ```
167
193
 
194
+ ### Exception management
195
+
196
+ The `exception` flag in the `#pipelined` is a feature that modifies the pipeline execution behavior. When set
197
+ to `false`, it doesn't raise an exception when a command error occurs. Instead, it allows the pipeline to execute all
198
+ commands, and any failed command will be available in the returned array. (Defaults to `true`)
199
+
200
+ ```ruby
201
+ results = redis.pipelined(exception: false) do |pipeline|
202
+ pipeline.set('key1', 'value1')
203
+ pipeline.lpush('key1', 'something') # This will fail
204
+ pipeline.set('key2', 'value2')
205
+ end
206
+ # results => ["OK", #<RedisClient::WrongTypeError: WRONGTYPE Operation against a key holding the wrong kind of value>, "OK"]
207
+
208
+ results.each do |result|
209
+ if result.is_a?(Redis::CommandError)
210
+ # Do something with the failed result
211
+ end
212
+ end
213
+ ```
214
+
215
+
168
216
  ### Executing commands atomically
169
217
 
170
218
  You can use `MULTI/EXEC` to run a number of commands in an atomic
@@ -225,6 +273,7 @@ See lib/redis/errors.rb for information about what exceptions are possible.
225
273
  ## Timeouts
226
274
 
227
275
  The client allows you to configure connect, read, and write timeouts.
276
+ Starting in version 5.0, the default for each is 1. Before that, it was 5.
228
277
  Passing a single `timeout` option will set all three values:
229
278
 
230
279
  ```ruby
@@ -357,7 +406,7 @@ gem "hiredis-client"
357
406
  ```
358
407
 
359
408
  If your application doesn't call `Bundler.require`, you may have
360
- to require it explictly:
409
+ to require it explicitly:
361
410
 
362
411
  ```ruby
363
412
  require "hiredis-client"
@@ -394,11 +443,11 @@ client and evangelized Redis in Rubyland. Thank you, Ezra.
394
443
  requests.
395
444
 
396
445
 
397
- [inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
398
- [inchpages-link]: https://inch-ci.org/github/redis/redis-rb
399
- [redis-commands]: https://redis.io/commands
400
- [redis-home]: https://redis.io
401
- [redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
402
- [gh-actions-image]: https://github.com/redis/redis-rb/workflows/Test/badge.svg
403
- [gh-actions-link]: https://github.com/redis/redis-rb/actions
404
- [rubydoc]: http://www.rubydoc.info/gems/redis
446
+ [rdoc-master-image]: https://img.shields.io/badge/docs-rdoc.info-blue.svg
447
+ [rdoc-master-link]: https://rubydoc.info/github/redis/redis-rb
448
+ [redis-commands]: https://redis.io/commands
449
+ [redis-home]: https://redis.io
450
+ [redis-url]: https://www.iana.org/assignments/uri-schemes/prov/redis
451
+ [gh-actions-image]: https://github.com/redis/redis-rb/workflows/Test/badge.svg
452
+ [gh-actions-link]: https://github.com/redis/redis-rb/actions
453
+ [rubydoc]: https://rubydoc.info/gems/redis
data/lib/redis/client.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'redis-client'
4
-
5
3
  class Redis
6
4
  class Client < ::RedisClient
7
5
  ERROR_MAPPING = {
@@ -17,6 +15,9 @@ class Redis
17
15
  RedisClient::ProtocolError => Redis::ProtocolError,
18
16
  RedisClient::OutOfMemoryError => Redis::OutOfMemoryError,
19
17
  }
18
+ if defined?(RedisClient::NoScriptError)
19
+ ERROR_MAPPING[RedisClient::NoScriptError] = Redis::NoScriptError
20
+ end
20
21
 
21
22
  class << self
22
23
  def config(**kwargs)
@@ -24,7 +25,24 @@ class Redis
24
25
  end
25
26
 
26
27
  def sentinel(**kwargs)
27
- super(protocol: 2, **kwargs)
28
+ super(protocol: 2, **kwargs, client_implementation: ::RedisClient)
29
+ end
30
+
31
+ def translate_error!(error, mapping: ERROR_MAPPING)
32
+ redis_error = translate_error_class(error.class, mapping: mapping)
33
+ raise redis_error, error.message, error.backtrace
34
+ end
35
+
36
+ private
37
+
38
+ def translate_error_class(error_class, mapping: ERROR_MAPPING)
39
+ mapping.fetch(error_class)
40
+ rescue IndexError
41
+ if (client_error = error_class.ancestors.find { |a| mapping[a] })
42
+ mapping[error_class] = mapping[client_error]
43
+ else
44
+ raise
45
+ end
28
46
  end
29
47
  end
30
48
 
@@ -69,10 +87,16 @@ class Redis
69
87
  undef_method :call_once_v
70
88
  undef_method :blocking_call
71
89
 
90
+ def ensure_connected(retryable: true, &block)
91
+ super(retryable: retryable, &block)
92
+ rescue ::RedisClient::Error => error
93
+ Client.translate_error!(error)
94
+ end
95
+
72
96
  def call_v(command, &block)
73
97
  super(command, &block)
74
98
  rescue ::RedisClient::Error => error
75
- translate_error!(error)
99
+ Client.translate_error!(error)
76
100
  end
77
101
 
78
102
  def blocking_call_v(timeout, command, &block)
@@ -85,44 +109,23 @@ class Redis
85
109
 
86
110
  super(timeout, command, &block)
87
111
  rescue ::RedisClient::Error => error
88
- translate_error!(error)
112
+ Client.translate_error!(error)
89
113
  end
90
114
 
91
- def pipelined
115
+ def pipelined(exception: true)
92
116
  super
93
117
  rescue ::RedisClient::Error => error
94
- translate_error!(error)
118
+ Client.translate_error!(error)
95
119
  end
96
120
 
97
- def multi
121
+ def multi(watch: nil)
98
122
  super
99
123
  rescue ::RedisClient::Error => error
100
- translate_error!(error)
101
- end
102
-
103
- def disable_reconnection(&block)
104
- ensure_connected(retryable: false, &block)
124
+ Client.translate_error!(error)
105
125
  end
106
126
 
107
127
  def inherit_socket!
108
128
  @inherit_socket = true
109
129
  end
110
-
111
- private
112
-
113
- def translate_error!(error)
114
- redis_error = translate_error_class(error.class)
115
- raise redis_error, error.message, error.backtrace
116
- end
117
-
118
- def translate_error_class(error_class)
119
- ERROR_MAPPING.fetch(error_class)
120
- rescue IndexError
121
- if (client_error = error_class.ancestors.find { |a| ERROR_MAPPING[a] })
122
- ERROR_MAPPING[error_class] = ERROR_MAPPING[client_error]
123
- else
124
- raise
125
- end
126
- end
127
130
  end
128
131
  end
@@ -27,9 +27,13 @@ class Redis
27
27
  # @param [String] key
28
28
  # @param [Integer] start start index
29
29
  # @param [Integer] stop stop index
30
+ # @param [String, Symbol] scale the scale of the offset range
31
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
30
32
  # @return [Integer] the number of bits set to 1
31
- def bitcount(key, start = 0, stop = -1)
32
- send_command([:bitcount, key, start, stop])
33
+ def bitcount(key, start = 0, stop = -1, scale: nil)
34
+ command = [:bitcount, key, start, stop]
35
+ command << scale if scale
36
+ send_command(command)
33
37
  end
34
38
 
35
39
  # Perform a bitwise operation between strings and store the resulting string in a key.
@@ -51,14 +55,17 @@ class Redis
51
55
  # @param [Integer] bit whether to look for the first 1 or 0 bit
52
56
  # @param [Integer] start start index
53
57
  # @param [Integer] stop stop index
58
+ # @param [String, Symbol] scale the scale of the offset range
59
+ # e.g. 'BYTE' - interpreted as a range of bytes, 'BIT' - interpreted as a range of bits
54
60
  # @return [Integer] the position of the first 1/0 bit.
55
61
  # -1 if looking for 1 and it is not found or start and stop are given.
56
- def bitpos(key, bit, start = nil, stop = nil)
62
+ def bitpos(key, bit, start = nil, stop = nil, scale: nil)
57
63
  raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
58
64
 
59
65
  command = [:bitpos, key, bit]
60
66
  command << start if start
61
67
  command << stop if stop
68
+ command << scale if scale
62
69
  send_command(command)
63
70
  end
64
71
  end
@@ -222,6 +222,8 @@ class Redis
222
222
  # - `:count => Integer`: return count keys at most per iteration
223
223
  #
224
224
  # @return [String, Array<[String, String]>] the next cursor and all found keys
225
+ #
226
+ # See the [Redis Server HSCAN documentation](https://redis.io/docs/latest/commands/hscan/) for further details
225
227
  def hscan(key, cursor, **options)
226
228
  _scan(:hscan, cursor, [key], **options) do |reply|
227
229
  [reply[0], reply[1].each_slice(2).to_a]
@@ -239,6 +241,8 @@ class Redis
239
241
  # - `:count => Integer`: return count keys at most per iteration
240
242
  #
241
243
  # @return [Enumerator] an enumerator for all found keys
244
+ #
245
+ # See the [Redis Server HSCAN documentation](https://redis.io/docs/latest/commands/hscan/) for further details
242
246
  def hscan_each(key, **options, &block)
243
247
  return to_enum(:hscan_each, key, **options) unless block_given?
244
248
 
@@ -22,6 +22,8 @@ class Redis
22
22
  # - `:type => String`: return keys only of the given type
23
23
  #
24
24
  # @return [String, Array<String>] the next cursor and all found keys
25
+ #
26
+ # See the [Redis Server SCAN documentation](https://redis.io/docs/latest/commands/scan/) for further details
25
27
  def scan(cursor, **options)
26
28
  _scan(:scan, cursor, [], **options)
27
29
  end
@@ -46,6 +48,8 @@ class Redis
46
48
  # - `:type => String`: return keys only of the given type
47
49
  #
48
50
  # @return [Enumerator] an enumerator for all found keys
51
+ #
52
+ # See the [Redis Server SCAN documentation](https://redis.io/docs/latest/commands/scan/) for further details
49
53
  def scan_each(**options, &block)
50
54
  return to_enum(:scan_each, **options) unless block_given?
51
55
 
@@ -105,6 +109,14 @@ class Redis
105
109
  send_command(args, &Boolify)
106
110
  end
107
111
 
112
+ # Get a key's expiry time specified as number of seconds from UNIX Epoch
113
+ #
114
+ # @param [String] key
115
+ # @return [Integer] expiry time specified as number of seconds from UNIX Epoch
116
+ def expiretime(key)
117
+ send_command([:expiretime, key])
118
+ end
119
+
108
120
  # Get the time to live (in seconds) for a key.
109
121
  #
110
122
  # @param [String] key
@@ -161,6 +173,14 @@ class Redis
161
173
  send_command(args, &Boolify)
162
174
  end
163
175
 
176
+ # Get a key's expiry time specified as number of milliseconds from UNIX Epoch
177
+ #
178
+ # @param [String] key
179
+ # @return [Integer] expiry time specified as number of milliseconds from UNIX Epoch
180
+ def pexpiretime(key)
181
+ send_command([:pexpiretime, key])
182
+ end
183
+
164
184
  # Get the time to live (in milliseconds) for a key.
165
185
  #
166
186
  # @param [String] key
@@ -266,6 +286,8 @@ class Redis
266
286
  #
267
287
  # @param [String] pattern
268
288
  # @return [Array<String>]
289
+ #
290
+ # See the [Redis Server KEYS documentation](https://redis.io/docs/latest/commands/keys/) for further details
269
291
  def keys(pattern = "*")
270
292
  send_command([:keys, pattern]) do |reply|
271
293
  if reply.is_a?(String)
@@ -183,6 +183,60 @@ class Redis
183
183
  send_blocking_command(command, timeout)
184
184
  end
185
185
 
186
+ # Pops one or more elements from the first non-empty list key from the list
187
+ # of provided key names. If lists are empty, blocks until timeout has passed.
188
+ #
189
+ # @example Popping a element
190
+ # redis.blmpop(1.0, 'list')
191
+ # #=> ['list', ['a']]
192
+ # @example With count option
193
+ # redis.blmpop(1.0, 'list', count: 2)
194
+ # #=> ['list', ['a', 'b']]
195
+ #
196
+ # @params timeout [Float] a float value specifying the maximum number of seconds to block) elapses.
197
+ # A timeout of zero can be used to block indefinitely.
198
+ # @params key [String, Array<String>] one or more keys with lists
199
+ # @params modifier [String]
200
+ # - when `"LEFT"` - the elements popped are those from the left of the list
201
+ # - when `"RIGHT"` - the elements popped are those from the right of the list
202
+ # @params count [Integer] a number of elements to pop
203
+ #
204
+ # @return [Array<String, Array<String, Float>>] list of popped elements or nil
205
+ def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
206
+ raise ArgumentError, "Pick either LEFT or RIGHT" unless modifier == "LEFT" || modifier == "RIGHT"
207
+
208
+ args = [:blmpop, timeout, keys.size, *keys, modifier]
209
+ args << "COUNT" << Integer(count) if count
210
+
211
+ send_blocking_command(args, timeout)
212
+ end
213
+
214
+ # Pops one or more elements from the first non-empty list key from the list
215
+ # of provided key names.
216
+ #
217
+ # @example Popping a element
218
+ # redis.lmpop('list')
219
+ # #=> ['list', ['a']]
220
+ # @example With count option
221
+ # redis.lmpop('list', count: 2)
222
+ # #=> ['list', ['a', 'b']]
223
+ #
224
+ # @params key [String, Array<String>] one or more keys with lists
225
+ # @params modifier [String]
226
+ # - when `"LEFT"` - the elements popped are those from the left of the list
227
+ # - when `"RIGHT"` - the elements popped are those from the right of the list
228
+ # @params count [Integer] a number of elements to pop
229
+ #
230
+ # @return [Array<String, Array<String, Float>>] list of popped elements or nil
231
+ def lmpop(*keys, modifier: "LEFT", count: nil)
232
+ raise ArgumentError, "Pick either LEFT or RIGHT" unless modifier == "LEFT" || modifier == "RIGHT"
233
+
234
+ args = [:lmpop, keys.size, *keys, modifier]
235
+ args << "COUNT" << Integer(count) if count
236
+
237
+ send_command(args)
238
+ end
239
+
186
240
  # Get an element from a list by its index.
187
241
  #
188
242
  # @param [String] key
@@ -29,17 +29,23 @@ class Redis
29
29
  end
30
30
 
31
31
  # Listen for messages published to channels matching the given patterns.
32
+ # See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
33
+ # for further details
32
34
  def psubscribe(*channels, &block)
33
35
  _subscription(:psubscribe, 0, channels, block)
34
36
  end
35
37
 
36
38
  # Listen for messages published to channels matching the given patterns.
37
39
  # Throw a timeout error if there is no messages for a timeout period.
40
+ # See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
41
+ # for further details
38
42
  def psubscribe_with_timeout(timeout, *channels, &block)
39
43
  _subscription(:psubscribe_with_timeout, timeout, channels, block)
40
44
  end
41
45
 
42
46
  # Stop listening for messages posted to channels matching the given patterns.
47
+ # See the [Redis Server PUNSUBSCRIBE documentation](https://redis.io/docs/latest/commands/punsubscribe/)
48
+ # for further details
43
49
  def punsubscribe(*channels)
44
50
  _subscription(:punsubscribe, 0, channels, nil)
45
51
  end
@@ -49,6 +55,27 @@ class Redis
49
55
  def pubsub(subcommand, *args)
50
56
  send_command([:pubsub, subcommand] + args)
51
57
  end
58
+
59
+ # Post a message to a channel in a shard.
60
+ def spublish(channel, message)
61
+ send_command([:spublish, channel, message])
62
+ end
63
+
64
+ # Listen for messages published to the given channels in a shard.
65
+ def ssubscribe(*channels, &block)
66
+ _subscription(:ssubscribe, 0, channels, block)
67
+ end
68
+
69
+ # Listen for messages published to the given channels in a shard.
70
+ # Throw a timeout error if there is no messages for a timeout period.
71
+ def ssubscribe_with_timeout(timeout, *channels, &block)
72
+ _subscription(:ssubscribe_with_timeout, timeout, channels, block)
73
+ end
74
+
75
+ # Stop listening for messages posted to the given channels in a shard.
76
+ def sunsubscribe(*channels)
77
+ _subscription(:sunsubscribe, 0, channels, nil)
78
+ end
52
79
  end
53
80
  end
54
81
  end
@@ -184,6 +184,8 @@ class Redis
184
184
  # - `:count => Integer`: return count keys at most per iteration
185
185
  #
186
186
  # @return [String, Array<String>] the next cursor and all found members
187
+ #
188
+ # See the [Redis Server SSCAN documentation](https://redis.io/docs/latest/commands/sscan/) for further details
187
189
  def sscan(key, cursor, **options)
188
190
  _scan(:sscan, cursor, [key], **options)
189
191
  end
@@ -199,6 +201,8 @@ class Redis
199
201
  # - `:count => Integer`: return count keys at most per iteration
200
202
  #
201
203
  # @return [Enumerator] an enumerator for all keys in the set
204
+ #
205
+ # See the [Redis Server SSCAN documentation](https://redis.io/docs/latest/commands/sscan/) for further details
202
206
  def sscan_each(key, **options, &block)
203
207
  return to_enum(:sscan_each, key, **options) unless block_given?
204
208
 
@@ -167,6 +167,72 @@ class Redis
167
167
  end
168
168
  end
169
169
 
170
+ # Removes and returns up to count members with scores in the sorted set stored at key.
171
+ #
172
+ # @example Popping a member
173
+ # redis.bzmpop('zset')
174
+ # #=> ['zset', ['a', 1.0]]
175
+ # @example With count option
176
+ # redis.bzmpop('zset', count: 2)
177
+ # #=> ['zset', [['a', 1.0], ['b', 2.0]]
178
+ #
179
+ # @params timeout [Float] a float value specifying the maximum number of seconds to block) elapses.
180
+ # A timeout of zero can be used to block indefinitely.
181
+ # @params key [String, Array<String>] one or more keys with sorted sets
182
+ # @params modifier [String]
183
+ # - when `"MIN"` - the elements popped are those with lowest scores
184
+ # - when `"MAX"` - the elements popped are those with the highest scores
185
+ # @params count [Integer] a number of members to pop
186
+ #
187
+ # @return [Array<String, Array<String, Float>>] list of popped elements and scores
188
+ def bzmpop(timeout, *keys, modifier: "MIN", count: nil)
189
+ raise ArgumentError, "Pick either MIN or MAX" unless modifier == "MIN" || modifier == "MAX"
190
+
191
+ args = [:bzmpop, timeout, keys.size, *keys, modifier]
192
+ args << "COUNT" << Integer(count) if count
193
+
194
+ send_blocking_command(args, timeout) do |response|
195
+ response&.map do |entry|
196
+ case entry
197
+ when String then entry
198
+ when Array then entry.map { |pair| FloatifyPairs.call(pair) }.flatten(1)
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ # Removes and returns up to count members with scores in the sorted set stored at key.
205
+ #
206
+ # @example Popping a member
207
+ # redis.zmpop('zset')
208
+ # #=> ['zset', ['a', 1.0]]
209
+ # @example With count option
210
+ # redis.zmpop('zset', count: 2)
211
+ # #=> ['zset', [['a', 1.0], ['b', 2.0]]
212
+ #
213
+ # @params key [String, Array<String>] one or more keys with sorted sets
214
+ # @params modifier [String]
215
+ # - when `"MIN"` - the elements popped are those with lowest scores
216
+ # - when `"MAX"` - the elements popped are those with the highest scores
217
+ # @params count [Integer] a number of members to pop
218
+ #
219
+ # @return [Array<String, Array<String, Float>>] list of popped elements and scores
220
+ def zmpop(*keys, modifier: "MIN", count: nil)
221
+ raise ArgumentError, "Pick either MIN or MAX" unless modifier == "MIN" || modifier == "MAX"
222
+
223
+ args = [:zmpop, keys.size, *keys, modifier]
224
+ args << "COUNT" << Integer(count) if count
225
+
226
+ send_command(args) do |response|
227
+ response&.map do |entry|
228
+ case entry
229
+ when String then entry
230
+ when Array then entry.map { |pair| FloatifyPairs.call(pair) }.flatten(1)
231
+ end
232
+ end
233
+ end
234
+ end
235
+
170
236
  # Removes and returns up to count members with the highest scores in the sorted set stored at keys,
171
237
  # or block until one is available.
172
238
  #
@@ -388,21 +454,55 @@ class Redis
388
454
 
389
455
  # Determine the index of a member in a sorted set.
390
456
  #
457
+ # @example Retrieve member rank
458
+ # redis.zrank("zset", "a")
459
+ # # => 3
460
+ # @example Retrieve member rank with their score
461
+ # redis.zrank("zset", "a", :with_score => true)
462
+ # # => [3, 32.0]
463
+ #
391
464
  # @param [String] key
392
465
  # @param [String] member
393
- # @return [Integer]
394
- def zrank(key, member)
395
- send_command([:zrank, key, member])
466
+ #
467
+ # @return [Integer, [Integer, Float]]
468
+ # - when `:with_score` is not specified, an Integer
469
+ # - when `:with_score` is specified, a `[rank, score]` pair
470
+ def zrank(key, member, withscore: false, with_score: withscore)
471
+ args = [:zrank, key, member]
472
+
473
+ if with_score
474
+ args << "WITHSCORE"
475
+ block = FloatifyPair
476
+ end
477
+
478
+ send_command(args, &block)
396
479
  end
397
480
 
398
481
  # Determine the index of a member in a sorted set, with scores ordered from
399
482
  # high to low.
400
483
  #
484
+ # @example Retrieve member rank
485
+ # redis.zrevrank("zset", "a")
486
+ # # => 3
487
+ # @example Retrieve member rank with their score
488
+ # redis.zrevrank("zset", "a", :with_score => true)
489
+ # # => [3, 32.0]
490
+ #
401
491
  # @param [String] key
402
492
  # @param [String] member
403
- # @return [Integer]
404
- def zrevrank(key, member)
405
- send_command([:zrevrank, key, member])
493
+ #
494
+ # @return [Integer, [Integer, Float]]
495
+ # - when `:with_score` is not specified, an Integer
496
+ # - when `:with_score` is specified, a `[rank, score]` pair
497
+ def zrevrank(key, member, withscore: false, with_score: withscore)
498
+ args = [:zrevrank, key, member]
499
+
500
+ if with_score
501
+ args << "WITHSCORE"
502
+ block = FloatifyPair
503
+ end
504
+
505
+ send_command(args, &block)
406
506
  end
407
507
 
408
508
  # Remove all members in a sorted set within the given indexes.
@@ -751,6 +851,8 @@ class Redis
751
851
  #
752
852
  # @return [String, Array<[String, Float]>] the next cursor and all found
753
853
  # members and scores
854
+ #
855
+ # See the [Redis Server ZSCAN documentation](https://redis.io/docs/latest/commands/zscan/) for further details
754
856
  def zscan(key, cursor, **options)
755
857
  _scan(:zscan, cursor, [key], **options) do |reply|
756
858
  [reply[0], FloatifyPairs.call(reply[1])]
@@ -768,6 +870,8 @@ class Redis
768
870
  # - `:count => Integer`: return count keys at most per iteration
769
871
  #
770
872
  # @return [Enumerator] an enumerator for all found scores and members
873
+ #
874
+ # See the [Redis Server ZSCAN documentation](https://redis.io/docs/latest/commands/zscan/) for further details
771
875
  def zscan_each(key, **options, &block)
772
876
  return to_enum(:zscan_each, key, **options) unless block_given?
773
877
 
@@ -41,18 +41,25 @@ class Redis
41
41
  # @param opts [Hash] several options for `XADD` command
42
42
  #
43
43
  # @option opts [String] :id the entry id, default value is `*`, it means auto generation
44
- # @option opts [Integer] :maxlen max length of entries
45
- # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen or not
44
+ # @option opts [Integer] :maxlen max length of entries to keep
45
+ # @option opts [Integer] :minid min id of entries to keep
46
+ # @option opts [Boolean] :approximate whether to add `~` modifier of maxlen/minid or not
46
47
  # @option opts [Boolean] :nomkstream whether to add NOMKSTREAM, default is not to add
47
48
  #
48
49
  # @return [String] the entry id
49
- def xadd(key, entry, approximate: nil, maxlen: nil, nomkstream: nil, id: '*')
50
+ def xadd(key, entry, approximate: nil, maxlen: nil, minid: nil, nomkstream: nil, id: '*')
50
51
  args = [:xadd, key]
51
52
  args << 'NOMKSTREAM' if nomkstream
52
53
  if maxlen
54
+ raise ArgumentError, "can't supply both maxlen and minid" if minid
55
+
53
56
  args << "MAXLEN"
54
57
  args << "~" if approximate
55
58
  args << maxlen
59
+ elsif minid
60
+ args << "MINID"
61
+ args << "~" if approximate
62
+ args << minid
56
63
  end
57
64
  args << id
58
65
  args.concat(entry.flatten)
@@ -83,12 +83,14 @@ class Redis
83
83
  end
84
84
  }
85
85
 
86
+ FloatifyPair = lambda { |(first, score)|
87
+ [first, Floatify.call(score)]
88
+ }
89
+
86
90
  FloatifyPairs = lambda { |value|
87
91
  return value unless value.respond_to?(:each_slice)
88
92
 
89
- value.each_slice(2).map do |member, score|
90
- [member, Floatify.call(score)]
91
- end
93
+ value.each_slice(2).map(&FloatifyPair)
92
94
  }
93
95
 
94
96
  HashifyInfo = lambda { |reply|
@@ -199,8 +201,8 @@ class Redis
199
201
  # hash, are up to consumers.
200
202
  #
201
203
  # Redis error replies are raised as Ruby exceptions.
202
- def call(*command)
203
- send_command(command)
204
+ def call(*command, &block)
205
+ send_command(command, &block)
204
206
  end
205
207
 
206
208
  # Interact with the sentinel command (masters, master, slaves, failover)
@@ -130,6 +130,11 @@ class Redis
130
130
  node_for(key).expireat(key, unix_time, **kwargs)
131
131
  end
132
132
 
133
+ # Get the expiration for a key as a UNIX timestamp.
134
+ def expiretime(key)
135
+ node_for(key).expiretime(key)
136
+ end
137
+
133
138
  # Get the time to live (in seconds) for a key.
134
139
  def ttl(key)
135
140
  node_for(key).ttl(key)
@@ -145,6 +150,11 @@ class Redis
145
150
  node_for(key).pexpireat(key, ms_unix_time, **kwarg)
146
151
  end
147
152
 
153
+ # Get the expiration for a key as number of milliseconds from UNIX Epoch.
154
+ def pexpiretime(key)
155
+ node_for(key).pexpiretime(key)
156
+ end
157
+
148
158
  # Get the time to live (in milliseconds) for a key.
149
159
  def pttl(key)
150
160
  node_for(key).pttl(key)
@@ -370,8 +380,8 @@ class Redis
370
380
  end
371
381
 
372
382
  # Count the number of set bits in a range of the string value stored at key.
373
- def bitcount(key, start = 0, stop = -1)
374
- node_for(key).bitcount(key, start, stop)
383
+ def bitcount(key, start = 0, stop = -1, scale: nil)
384
+ node_for(key).bitcount(key, start, stop, scale: scale)
375
385
  end
376
386
 
377
387
  # Perform a bitwise operation between strings and store the resulting string in a key.
@@ -383,8 +393,8 @@ class Redis
383
393
  end
384
394
 
385
395
  # Return the position of the first bit set to 1 or 0 in a string.
386
- def bitpos(key, bit, start = nil, stop = nil)
387
- node_for(key).bitpos(key, bit, start, stop)
396
+ def bitpos(key, bit, start = nil, stop = nil, scale: nil)
397
+ node_for(key).bitpos(key, bit, start, stop, scale: scale)
388
398
  end
389
399
 
390
400
  # Set the string value of a key and return its old value.
@@ -542,6 +552,20 @@ class Redis
542
552
  node_for(key).ltrim(key, start, stop)
543
553
  end
544
554
 
555
+ # Iterate over keys, blocking and removing elements from the first non empty liist found.
556
+ def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
557
+ ensure_same_node(:blmpop, keys) do |node|
558
+ node.blmpop(timeout, *keys, modifier: modifier, count: count)
559
+ end
560
+ end
561
+
562
+ # Iterate over keys, removing elements from the first non list found.
563
+ def lmpop(*keys, modifier: "LEFT", count: nil)
564
+ ensure_same_node(:lmpop, keys) do |node|
565
+ node.lmpop(*keys, modifier: modifier, count: count)
566
+ end
567
+ end
568
+
545
569
  # Get the number of members in a set.
546
570
  def scard(key)
547
571
  node_for(key).scard(key)
@@ -694,6 +718,20 @@ class Redis
694
718
  node_for(key).zmscore(key, *members)
695
719
  end
696
720
 
721
+ # Iterate over keys, blocking and removing members from the first non empty sorted set found.
722
+ def bzmpop(timeout, *keys, modifier: "MIN", count: nil)
723
+ ensure_same_node(:bzmpop, keys) do |node|
724
+ node.bzmpop(timeout, *keys, modifier: modifier, count: count)
725
+ end
726
+ end
727
+
728
+ # Iterate over keys, removing members from the first non empty sorted set found.
729
+ def zmpop(*keys, modifier: "MIN", count: nil)
730
+ ensure_same_node(:zmpop, keys) do |node|
731
+ node.zmpop(*keys, modifier: modifier, count: count)
732
+ end
733
+ end
734
+
697
735
  # Return a range of members in a sorted set, by index, score or lexicographical ordering.
698
736
  def zrange(key, start, stop, **options)
699
737
  node_for(key).zrange(key, start, stop, **options)
@@ -714,14 +752,14 @@ class Redis
714
752
  end
715
753
 
716
754
  # Determine the index of a member in a sorted set.
717
- def zrank(key, member)
718
- node_for(key).zrank(key, member)
755
+ def zrank(key, member, **options)
756
+ node_for(key).zrank(key, member, **options)
719
757
  end
720
758
 
721
759
  # Determine the index of a member in a sorted set, with scores ordered from
722
760
  # high to low.
723
- def zrevrank(key, member)
724
- node_for(key).zrevrank(key, member)
761
+ def zrevrank(key, member, **options)
762
+ node_for(key).zrevrank(key, member, **options)
725
763
  end
726
764
 
727
765
  # Remove all members in a sorted set within the given indexes.
@@ -910,12 +948,16 @@ class Redis
910
948
  end
911
949
 
912
950
  # Listen for messages published to channels matching the given patterns.
951
+ # See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
952
+ # for further details
913
953
  def psubscribe(*channels, &block)
914
954
  raise NotImplementedError
915
955
  end
916
956
 
917
957
  # Stop listening for messages posted to channels matching the given
918
958
  # patterns.
959
+ # See the [Redis Server PUNSUBSCRIBE documentation](https://redis.io/docs/latest/commands/punsubscribe/)
960
+ # for further details
919
961
  def punsubscribe(*channels)
920
962
  raise NotImplementedError
921
963
  end
data/lib/redis/errors.rb CHANGED
@@ -29,6 +29,11 @@ class Redis
29
29
  class OutOfMemoryError < CommandError
30
30
  end
31
31
 
32
+ if defined?(RedisClient::NoScriptError)
33
+ class NoScriptError < CommandError
34
+ end
35
+ end
36
+
32
37
  # Base error for connection related errors.
33
38
  class BaseConnectionError < BaseError
34
39
  end
@@ -6,9 +6,10 @@ class Redis
6
6
  class PipelinedConnection
7
7
  attr_accessor :db
8
8
 
9
- def initialize(pipeline, futures = [])
9
+ def initialize(pipeline, futures = [], exception: true)
10
10
  @pipeline = pipeline
11
11
  @futures = futures
12
+ @exception = exception
12
13
  end
13
14
 
14
15
  include Commands
@@ -37,7 +38,7 @@ class Redis
37
38
  end
38
39
 
39
40
  def send_command(command, &block)
40
- future = Future.new(command, block)
41
+ future = Future.new(command, block, @exception)
41
42
  @pipeline.call_v(command) do |result|
42
43
  future._set(result)
43
44
  end
@@ -46,7 +47,7 @@ class Redis
46
47
  end
47
48
 
48
49
  def send_blocking_command(command, timeout, &block)
49
- future = Future.new(command, block)
50
+ future = Future.new(command, block, @exception)
50
51
  @pipeline.blocking_call_v(timeout, command) do |result|
51
52
  future._set(result)
52
53
  end
@@ -57,7 +58,7 @@ class Redis
57
58
 
58
59
  class MultiConnection < PipelinedConnection
59
60
  def multi
60
- raise Redis::Error, "Can't nest multi transaction"
61
+ raise Redis::BaseError, "Can't nest multi transaction"
61
62
  end
62
63
 
63
64
  private
@@ -79,10 +80,11 @@ class Redis
79
80
  class Future < BasicObject
80
81
  FutureNotReady = ::Redis::FutureNotReady.new
81
82
 
82
- def initialize(command, coerce)
83
+ def initialize(command, coerce, exception)
83
84
  @command = command
84
85
  @object = FutureNotReady
85
86
  @coerce = coerce
87
+ @exception = exception
86
88
  end
87
89
 
88
90
  def inspect
@@ -95,7 +97,7 @@ class Redis
95
97
  end
96
98
 
97
99
  def value
98
- ::Kernel.raise(@object) if @object.is_a?(::StandardError)
100
+ ::Kernel.raise(@object) if @exception && @object.is_a?(::StandardError)
99
101
  @object
100
102
  end
101
103
 
@@ -116,12 +118,14 @@ class Redis
116
118
  end
117
119
 
118
120
  def _set(replies)
119
- if replies
120
- @futures.each_with_index do |future, index|
121
+ @object = if replies
122
+ @futures.map.with_index do |future, index|
121
123
  future._set(replies[index])
124
+ future.value
122
125
  end
126
+ else
127
+ replies
123
128
  end
124
- @object = replies
125
129
  end
126
130
  end
127
131
  end
@@ -29,6 +29,14 @@ class Redis
29
29
  subscription("psubscribe", "punsubscribe", channels, block, timeout)
30
30
  end
31
31
 
32
+ def ssubscribe(*channels, &block)
33
+ subscription("ssubscribe", "sunsubscribe", channels, block)
34
+ end
35
+
36
+ def ssubscribe_with_timeout(timeout, *channels, &block)
37
+ subscription("ssubscribe", "sunsubscribe", channels, block, timeout)
38
+ end
39
+
32
40
  def unsubscribe(*channels)
33
41
  call_v([:unsubscribe, *channels])
34
42
  end
@@ -37,6 +45,10 @@ class Redis
37
45
  call_v([:punsubscribe, *channels])
38
46
  end
39
47
 
48
+ def sunsubscribe(*channels)
49
+ call_v([:sunsubscribe, *channels])
50
+ end
51
+
40
52
  def close
41
53
  @client.close
42
54
  end
@@ -46,7 +58,11 @@ class Redis
46
58
  def subscription(start, stop, channels, block, timeout = 0)
47
59
  sub = Subscription.new(&block)
48
60
 
49
- call_v([start, *channels])
61
+ case start
62
+ when "ssubscribe" then channels.each { |c| call_v([start, c]) } # avoid cross-slot keys
63
+ else call_v([start, *channels])
64
+ end
65
+
50
66
  while event = @client.next_event(timeout)
51
67
  if event.is_a?(::RedisClient::CommandError)
52
68
  raise Client::ERROR_MAPPING.fetch(event.class), event.message
@@ -94,5 +110,17 @@ class Redis
94
110
  def pmessage(&block)
95
111
  @callbacks["pmessage"] = block
96
112
  end
113
+
114
+ def ssubscribe(&block)
115
+ @callbacks["ssubscribe"] = block
116
+ end
117
+
118
+ def sunsubscribe(&block)
119
+ @callbacks["sunsubscribe"] = block
120
+ end
121
+
122
+ def smessage(&block)
123
+ @callbacks["smessage"] = block
124
+ end
97
125
  end
98
126
  end
data/lib/redis/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Redis
4
- VERSION = '5.0.6'
4
+ VERSION = '5.4.1'
5
5
  end
data/lib/redis.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "redis-client"
4
+
3
5
  require "monitor"
4
6
  require "redis/errors"
5
7
  require "redis/commands"
@@ -45,7 +47,7 @@ class Redis
45
47
  # @option options [String] :host ("127.0.0.1") server hostname
46
48
  # @option options [Integer] :port (6379) server port
47
49
  # @option options [String] :path path to server socket (overrides host and port)
48
- # @option options [Float] :timeout (5.0) timeout in seconds
50
+ # @option options [Float] :timeout (1.0) timeout in seconds
49
51
  # @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
50
52
  # @option options [String] :username Username to authenticate against server
51
53
  # @option options [String] :password Password to authenticate against server
@@ -99,10 +101,10 @@ class Redis
99
101
  @client
100
102
  end
101
103
 
102
- def pipelined
104
+ def pipelined(exception: true)
103
105
  synchronize do |client|
104
- client.pipelined do |raw_pipeline|
105
- yield PipelinedConnection.new(raw_pipeline)
106
+ client.pipelined(exception: exception) do |raw_pipeline|
107
+ yield PipelinedConnection.new(raw_pipeline, exception: exception)
106
108
  end
107
109
  end
108
110
  end
@@ -137,21 +139,6 @@ class Redis
137
139
  end
138
140
 
139
141
  if options.key?(:sentinels)
140
- if url = options.delete(:url)
141
- uri = URI.parse(url)
142
- if !options.key?(:name) && uri.host
143
- options[:name] = uri.host
144
- end
145
-
146
- if !options.key?(:password) && uri.password && !uri.password.empty?
147
- options[:password] = uri.password
148
- end
149
-
150
- if !options.key?(:username) && uri.user && !uri.user.empty?
151
- options[:username] = uri.user
152
- end
153
- end
154
-
155
142
  Client.sentinel(**options).new_client
156
143
  else
157
144
  Client.config(**options).new_client
@@ -166,6 +153,8 @@ class Redis
166
153
  @monitor.synchronize do
167
154
  @client.call_v(command, &block)
168
155
  end
156
+ rescue ::RedisClient::Error => error
157
+ Client.translate_error!(error)
169
158
  end
170
159
 
171
160
  def send_blocking_command(command, timeout, &block)
@@ -188,6 +177,7 @@ class Redis
188
177
  @subscription_client.send(method, *channels, &block)
189
178
  end
190
179
  ensure
180
+ @subscription_client&.close
191
181
  @subscription_client = nil
192
182
  end
193
183
  else
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.6
4
+ version: 5.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ezra Zygmuntowicz
@@ -13,10 +13,9 @@ authors:
13
13
  - Michel Martens
14
14
  - Damian Janowski
15
15
  - Pieter Noordhuis
16
- autorequire:
17
16
  bindir: bin
18
17
  cert_chain: []
19
- date: 2023-01-16 00:00:00.000000000 Z
18
+ date: 2025-07-17 00:00:00.000000000 Z
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
22
21
  name: redis-client
@@ -24,14 +23,14 @@ dependencies:
24
23
  requirements:
25
24
  - - ">="
26
25
  - !ruby/object:Gem::Version
27
- version: 0.9.0
26
+ version: 0.22.0
28
27
  type: :runtime
29
28
  prerelease: false
30
29
  version_requirements: !ruby/object:Gem::Requirement
31
30
  requirements:
32
31
  - - ">="
33
32
  - !ruby/object:Gem::Version
34
- version: 0.9.0
33
+ version: 0.22.0
35
34
  description: |2
36
35
  A Ruby client that tries to match Redis' API one-to-one, while still
37
36
  providing an idiomatic interface.
@@ -75,10 +74,9 @@ licenses:
75
74
  metadata:
76
75
  bug_tracker_uri: https://github.com/redis/redis-rb/issues
77
76
  changelog_uri: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md
78
- documentation_uri: https://www.rubydoc.info/gems/redis/5.0.6
77
+ documentation_uri: https://www.rubydoc.info/gems/redis/5.4.1
79
78
  homepage_uri: https://github.com/redis/redis-rb
80
- source_code_uri: https://github.com/redis/redis-rb/tree/v5.0.6
81
- post_install_message:
79
+ source_code_uri: https://github.com/redis/redis-rb/tree/v5.4.1
82
80
  rdoc_options: []
83
81
  require_paths:
84
82
  - lib
@@ -86,15 +84,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
84
  requirements:
87
85
  - - ">="
88
86
  - !ruby/object:Gem::Version
89
- version: 2.5.0
87
+ version: 2.6.0
90
88
  required_rubygems_version: !ruby/object:Gem::Requirement
91
89
  requirements:
92
90
  - - ">="
93
91
  - !ruby/object:Gem::Version
94
92
  version: '0'
95
93
  requirements: []
96
- rubygems_version: 3.1.2
97
- signing_key:
94
+ rubygems_version: 3.6.2
98
95
  specification_version: 4
99
96
  summary: A Ruby client library for Redis
100
97
  test_files: []