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 +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +66 -17
- data/lib/redis/client.rb +33 -30
- data/lib/redis/commands/bitmaps.rb +10 -3
- data/lib/redis/commands/hashes.rb +4 -0
- data/lib/redis/commands/keys.rb +22 -0
- data/lib/redis/commands/lists.rb +54 -0
- data/lib/redis/commands/pubsub.rb +27 -0
- data/lib/redis/commands/sets.rb +4 -0
- data/lib/redis/commands/sorted_sets.rb +110 -6
- data/lib/redis/commands/streams.rb +10 -3
- data/lib/redis/commands.rb +7 -5
- data/lib/redis/distributed.rb +50 -8
- data/lib/redis/errors.rb +5 -0
- data/lib/redis/pipeline.rb +13 -9
- data/lib/redis/subscribe.rb +29 -1
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +9 -19
- metadata +8 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d76a0d8a8a0361f991dc110219ffc77c12503148f9ece9586dd274321283265
|
4
|
+
data.tar.gz: fab1b1d4d3a5e22d2d952c98648a1412239f117a2c53bcbdf20ea7c43f456faa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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][
|
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
|
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
|
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(
|
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
|
-
|
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
|
127
|
-
{ host: '127.0.0.1', port: 26381
|
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
|
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
|
-
[
|
398
|
-
[
|
399
|
-
[redis-commands]:
|
400
|
-
[redis-home]:
|
401
|
-
[redis-url]:
|
402
|
-
[gh-actions-image]:
|
403
|
-
[gh-actions-link]:
|
404
|
-
[rubydoc]:
|
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
|
-
|
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
|
|
data/lib/redis/commands/keys.rb
CHANGED
@@ -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)
|
data/lib/redis/commands/lists.rb
CHANGED
@@ -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
|
data/lib/redis/commands/sets.rb
CHANGED
@@ -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
|
-
#
|
394
|
-
|
395
|
-
|
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
|
-
#
|
404
|
-
|
405
|
-
|
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 [
|
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)
|
data/lib/redis/commands.rb
CHANGED
@@ -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
|
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)
|
data/lib/redis/distributed.rb
CHANGED
@@ -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
|
data/lib/redis/pipeline.rb
CHANGED
@@ -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::
|
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.
|
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
|
data/lib/redis/subscribe.rb
CHANGED
@@ -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
|
-
|
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
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 (
|
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.
|
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:
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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: []
|