redis 4.6.0 → 4.8.0
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 +21 -1
- data/README.md +15 -0
- data/lib/redis/client.rb +3 -3
- data/lib/redis/cluster/command_loader.rb +5 -5
- data/lib/redis/cluster/node_loader.rb +8 -11
- data/lib/redis/cluster/option.rb +10 -3
- data/lib/redis/cluster/slot_loader.rb +9 -12
- data/lib/redis/commands/keys.rb +52 -8
- data/lib/redis/commands/lists.rb +7 -6
- data/lib/redis/commands/sets.rb +32 -16
- data/lib/redis/commands/sorted_sets.rb +9 -1
- data/lib/redis/commands/transactions.rb +47 -0
- data/lib/redis/commands.rb +2 -4
- data/lib/redis/connection/ruby.rb +6 -0
- data/lib/redis/distributed.rb +25 -14
- data/lib/redis/errors.rb +9 -0
- data/lib/redis/pipeline.rb +33 -1
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +9 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c93b88c9766f0ba965110f61ff1e5cb3adcf378a515a9cdb7c345ef56159f98a
|
4
|
+
data.tar.gz: 6fa82d53bef3b0be6a5d192a925dfbd1e7585e66a5313605e631a1cf8585bf95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1d2b5d59615cfdc2ec756c16560b88d3f968193d7ac1c827574b775b202f3f63b9d3ab9748c89cae5d82471063a1aded9f82557b86f850b0a58a180d404ee15c
|
7
|
+
data.tar.gz: 866c1968b84397450ca8882175240c345bf2dfb3a88fa4579d78c7ce8ee581226a0eb6007ac6de638dfc654eca15bf045e6f1860c187e5d736b196cac8b721c9
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 4.8.0
|
4
|
+
|
5
|
+
* Introduce `sadd?` and `srem?` as boolean returning versions of `sadd` and `srem`.
|
6
|
+
* Deprecate `sadd` and `srem` returning a boolean when called with a single argument.
|
7
|
+
To enable the redis 5.0 behavior you can set `Redis.sadd_returns_boolean = true`.
|
8
|
+
* Deprecate passing `timeout` as a positional argument in blocking commands (`brpop`, `blop`, etc).
|
9
|
+
|
10
|
+
# 4.7.1
|
11
|
+
|
12
|
+
* Gracefully handle OpenSSL 3.0 EOF Errors (`OpenSSL::SSL::SSLError: SSL_read: unexpected eof while reading`). See #1106
|
13
|
+
This happens frequently on heroku-22.
|
14
|
+
|
15
|
+
# 4.7.0
|
16
|
+
|
17
|
+
* Support single endpoint architecture with SSL/TLS in cluster mode. See #1086.
|
18
|
+
* `zrem` and `zadd` act as noop when provided an empty list of keys. See #1097.
|
19
|
+
* Support IPv6 URLs.
|
20
|
+
* Add `Redis#with` for better compatibility with `connection_pool` usage.
|
21
|
+
* Fix the block form of `multi` called inside `pipelined`. Previously the `MUTLI/EXEC` wouldn't be sent. See #1073.
|
22
|
+
|
3
23
|
# 4.6.0
|
4
24
|
|
5
25
|
* Deprecate `Redis.current`.
|
@@ -37,7 +57,7 @@
|
|
37
57
|
* `Redis#synchronize` is now private like it should always have been.
|
38
58
|
|
39
59
|
* Add `Redis.silence_deprecations=` to turn off deprecation warnings.
|
40
|
-
If you don't wish to see warnings yet, you can set `Redis.silence_deprecations =
|
60
|
+
If you don't wish to see warnings yet, you can set `Redis.silence_deprecations = true`.
|
41
61
|
It is however heavily recommended to fix them instead when possible.
|
42
62
|
* Add `Redis.raise_deprecations=` to turn deprecation warnings into errors.
|
43
63
|
This makes it easier to identitify the source of deprecated APIs usage.
|
data/README.md
CHANGED
@@ -155,6 +155,21 @@ redis.mget('{key}1', '{key}2')
|
|
155
155
|
* The client support permanent node failures, and will reroute requests to promoted slaves.
|
156
156
|
* The client supports `MOVED` and `ASK` redirections transparently.
|
157
157
|
|
158
|
+
## Cluster mode with SSL/TLS
|
159
|
+
Since Redis can return FQDN of nodes in reply to client since `7.*` with CLUSTER commands, we can use cluster feature with SSL/TLS connection like this:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
Redis.new(cluster: %w[rediss://foo.example.com:6379])
|
163
|
+
```
|
164
|
+
|
165
|
+
On the other hand, in Redis versions prior to `6.*`, you can specify options like the following if cluster mode is enabled and client has to connect to nodes via single endpoint with SSL/TLS.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
Redis.new(cluster: %w[rediss://foo-endpoint.example.com:6379], fixed_hostname: 'foo-endpoint.example.com')
|
169
|
+
```
|
170
|
+
|
171
|
+
In case of the above architecture, if you don't pass the `fixed_hostname` option to the client and servers return IP addresses of nodes, the client may fail to verify certificates.
|
172
|
+
|
158
173
|
## Storing objects
|
159
174
|
|
160
175
|
Redis "string" types can be used to store serialized Ruby objects, for
|
data/lib/redis/client.rb
CHANGED
@@ -150,7 +150,7 @@ class Redis
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def id
|
153
|
-
@options[:id] || "
|
153
|
+
@options[:id] || "#{@options[:ssl] ? 'rediss' : @options[:scheme]}://#{location}/#{db}"
|
154
154
|
end
|
155
155
|
|
156
156
|
def location
|
@@ -302,7 +302,7 @@ class Redis
|
|
302
302
|
e2 = TimeoutError.new("Connection timed out")
|
303
303
|
e2.set_backtrace(e1.backtrace)
|
304
304
|
raise e2
|
305
|
-
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
|
305
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL, EOFError => e
|
306
306
|
raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
|
307
307
|
end
|
308
308
|
|
@@ -464,7 +464,7 @@ class Redis
|
|
464
464
|
defaults[:path] = uri.path
|
465
465
|
when "redis", "rediss"
|
466
466
|
defaults[:scheme] = uri.scheme
|
467
|
-
defaults[:host] = uri.host if uri.host
|
467
|
+
defaults[:host] = uri.host.sub(/\A\[(.*)\]\z/, '\1') if uri.host
|
468
468
|
defaults[:port] = uri.port if uri.port
|
469
469
|
defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
|
470
470
|
defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'redis/errors'
|
4
4
|
|
5
5
|
class Redis
|
6
6
|
class Cluster
|
@@ -10,15 +10,15 @@ class Redis
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
def load(nodes)
|
13
|
-
nodes.
|
13
|
+
errors = nodes.map do |node|
|
14
14
|
begin
|
15
15
|
return fetch_command_details(node)
|
16
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
17
|
-
|
16
|
+
rescue CannotConnectError, ConnectionError, CommandError => error
|
17
|
+
error
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
raise
|
21
|
+
raise InitialSetupError, errors
|
22
22
|
end
|
23
23
|
|
24
24
|
def fetch_command_details(node)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'redis/errors'
|
4
4
|
|
5
5
|
class Redis
|
6
6
|
class Cluster
|
@@ -9,16 +9,15 @@ class Redis
|
|
9
9
|
module_function
|
10
10
|
|
11
11
|
def load_flags(nodes)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
errors = nodes.map do |node|
|
13
|
+
begin
|
14
|
+
return fetch_node_info(node)
|
15
|
+
rescue CannotConnectError, ConnectionError, CommandError => error
|
16
|
+
error
|
17
|
+
end
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
20
|
+
raise InitialSetupError, errors
|
22
21
|
end
|
23
22
|
|
24
23
|
def fetch_node_info(node)
|
@@ -27,8 +26,6 @@ class Redis
|
|
27
26
|
.map { |str| str.split(' ') }
|
28
27
|
.map { |arr| [arr[1].split('@').first, (arr[2].split(',') & %w[master slave]).first] }
|
29
28
|
.to_h
|
30
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
31
|
-
{} # can retry on another node
|
32
29
|
end
|
33
30
|
|
34
31
|
private_class_method :fetch_node_info
|
data/lib/redis/cluster/option.rb
CHANGED
@@ -17,6 +17,7 @@ class Redis
|
|
17
17
|
node_addrs = options.delete(:cluster)
|
18
18
|
@node_opts = build_node_options(node_addrs)
|
19
19
|
@replica = options.delete(:replica) == true
|
20
|
+
@fixed_hostname = options.delete(:fixed_hostname)
|
20
21
|
add_common_node_option_if_needed(options, @node_opts, :scheme)
|
21
22
|
add_common_node_option_if_needed(options, @node_opts, :username)
|
22
23
|
add_common_node_option_if_needed(options, @node_opts, :password)
|
@@ -24,8 +25,12 @@ class Redis
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def per_node_key
|
27
|
-
@node_opts.map
|
28
|
-
|
28
|
+
@node_opts.map do |opt|
|
29
|
+
node_key = NodeKey.build_from_host_port(opt[:host], opt[:port])
|
30
|
+
options = @options.merge(opt)
|
31
|
+
options = options.merge(host: @fixed_hostname) if @fixed_hostname && !@fixed_hostname.empty?
|
32
|
+
[node_key, options]
|
33
|
+
end.to_h
|
29
34
|
end
|
30
35
|
|
31
36
|
def use_replica?
|
@@ -64,8 +69,10 @@ class Redis
|
|
64
69
|
raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
|
65
70
|
|
66
71
|
db = uri.path.split('/')[1]&.to_i
|
72
|
+
username = uri.user ? URI.decode_www_form_component(uri.user) : nil
|
73
|
+
password = uri.password ? URI.decode_www_form_component(uri.password) : nil
|
67
74
|
|
68
|
-
{ scheme: uri.scheme, username:
|
75
|
+
{ scheme: uri.scheme, username: username, password: password, host: uri.host, port: uri.port, db: db }
|
69
76
|
.reject { |_, v| v.nil? || v == '' }
|
70
77
|
rescue URI::InvalidURIError => err
|
71
78
|
raise InvalidClientOptionError, err.message
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require 'redis/errors'
|
4
|
+
require 'redis/cluster/node_key'
|
5
5
|
|
6
6
|
class Redis
|
7
7
|
class Cluster
|
@@ -10,16 +10,15 @@ class Redis
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
def load(nodes)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
errors = nodes.map do |node|
|
14
|
+
begin
|
15
|
+
return fetch_slot_info(node)
|
16
|
+
rescue CannotConnectError, ConnectionError, CommandError => error
|
17
|
+
error
|
18
|
+
end
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
21
|
+
raise InitialSetupError, errors
|
23
22
|
end
|
24
23
|
|
25
24
|
def fetch_slot_info(node)
|
@@ -27,8 +26,6 @@ class Redis
|
|
27
26
|
node.call(%i[cluster slots])
|
28
27
|
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
28
|
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
30
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
31
|
-
{} # can retry on another node
|
32
29
|
end
|
33
30
|
|
34
31
|
def parse_slot_info(arr, default_ip:)
|
data/lib/redis/commands/keys.rb
CHANGED
@@ -69,18 +69,40 @@ class Redis
|
|
69
69
|
#
|
70
70
|
# @param [String] key
|
71
71
|
# @param [Integer] seconds time to live
|
72
|
+
# @param [Hash] options
|
73
|
+
# - `:nx => true`: Set expiry only when the key has no expiry.
|
74
|
+
# - `:xx => true`: Set expiry only when the key has an existing expiry.
|
75
|
+
# - `:gt => true`: Set expiry only when the new expiry is greater than current one.
|
76
|
+
# - `:lt => true`: Set expiry only when the new expiry is less than current one.
|
72
77
|
# @return [Boolean] whether the timeout was set or not
|
73
|
-
def expire(key, seconds)
|
74
|
-
|
78
|
+
def expire(key, seconds, nx: nil, xx: nil, gt: nil, lt: nil)
|
79
|
+
args = [:expire, key, seconds]
|
80
|
+
args << "NX" if nx
|
81
|
+
args << "XX" if xx
|
82
|
+
args << "GT" if gt
|
83
|
+
args << "LT" if lt
|
84
|
+
|
85
|
+
send_command(args, &Boolify)
|
75
86
|
end
|
76
87
|
|
77
88
|
# Set the expiration for a key as a UNIX timestamp.
|
78
89
|
#
|
79
90
|
# @param [String] key
|
80
91
|
# @param [Integer] unix_time expiry time specified as a UNIX timestamp
|
92
|
+
# @param [Hash] options
|
93
|
+
# - `:nx => true`: Set expiry only when the key has no expiry.
|
94
|
+
# - `:xx => true`: Set expiry only when the key has an existing expiry.
|
95
|
+
# - `:gt => true`: Set expiry only when the new expiry is greater than current one.
|
96
|
+
# - `:lt => true`: Set expiry only when the new expiry is less than current one.
|
81
97
|
# @return [Boolean] whether the timeout was set or not
|
82
|
-
def expireat(key, unix_time)
|
83
|
-
|
98
|
+
def expireat(key, unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
|
99
|
+
args = [:expireat, key, unix_time]
|
100
|
+
args << "NX" if nx
|
101
|
+
args << "XX" if xx
|
102
|
+
args << "GT" if gt
|
103
|
+
args << "LT" if lt
|
104
|
+
|
105
|
+
send_command(args, &Boolify)
|
84
106
|
end
|
85
107
|
|
86
108
|
# Get the time to live (in seconds) for a key.
|
@@ -103,18 +125,40 @@ class Redis
|
|
103
125
|
#
|
104
126
|
# @param [String] key
|
105
127
|
# @param [Integer] milliseconds time to live
|
128
|
+
# @param [Hash] options
|
129
|
+
# - `:nx => true`: Set expiry only when the key has no expiry.
|
130
|
+
# - `:xx => true`: Set expiry only when the key has an existing expiry.
|
131
|
+
# - `:gt => true`: Set expiry only when the new expiry is greater than current one.
|
132
|
+
# - `:lt => true`: Set expiry only when the new expiry is less than current one.
|
106
133
|
# @return [Boolean] whether the timeout was set or not
|
107
|
-
def pexpire(key, milliseconds)
|
108
|
-
|
134
|
+
def pexpire(key, milliseconds, nx: nil, xx: nil, gt: nil, lt: nil)
|
135
|
+
args = [:pexpire, key, milliseconds]
|
136
|
+
args << "NX" if nx
|
137
|
+
args << "XX" if xx
|
138
|
+
args << "GT" if gt
|
139
|
+
args << "LT" if lt
|
140
|
+
|
141
|
+
send_command(args, &Boolify)
|
109
142
|
end
|
110
143
|
|
111
144
|
# Set the expiration for a key as number of milliseconds from UNIX Epoch.
|
112
145
|
#
|
113
146
|
# @param [String] key
|
114
147
|
# @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
|
148
|
+
# @param [Hash] options
|
149
|
+
# - `:nx => true`: Set expiry only when the key has no expiry.
|
150
|
+
# - `:xx => true`: Set expiry only when the key has an existing expiry.
|
151
|
+
# - `:gt => true`: Set expiry only when the new expiry is greater than current one.
|
152
|
+
# - `:lt => true`: Set expiry only when the new expiry is less than current one.
|
115
153
|
# @return [Boolean] whether the timeout was set or not
|
116
|
-
def pexpireat(key, ms_unix_time)
|
117
|
-
|
154
|
+
def pexpireat(key, ms_unix_time, nx: nil, xx: nil, gt: nil, lt: nil)
|
155
|
+
args = [:pexpireat, key, ms_unix_time]
|
156
|
+
args << "NX" if nx
|
157
|
+
args << "XX" if xx
|
158
|
+
args << "GT" if gt
|
159
|
+
args << "LT" if lt
|
160
|
+
|
161
|
+
send_command(args, &Boolify)
|
118
162
|
end
|
119
163
|
|
120
164
|
# Get the time to live (in milliseconds) for a key.
|
data/lib/redis/commands/lists.rb
CHANGED
@@ -254,16 +254,17 @@ class Redis
|
|
254
254
|
options = args.pop
|
255
255
|
options[:timeout]
|
256
256
|
elsif args.last.respond_to?(:to_int)
|
257
|
-
|
258
|
-
|
257
|
+
last_arg = args.pop
|
258
|
+
::Redis.deprecate!(
|
259
|
+
"Passing the timeout as a positional argument is deprecated, it should be passed as a keyword argument:\n" \
|
260
|
+
" redis.#{cmd}(#{args.map(&:inspect).join(', ')}, timeout: #{last_arg.to_int})" \
|
261
|
+
"(called from: #{caller(2, 1).first})"
|
262
|
+
)
|
263
|
+
last_arg.to_int
|
259
264
|
end
|
260
265
|
|
261
266
|
timeout ||= 0
|
262
267
|
|
263
|
-
if args.size > 1
|
264
|
-
# Issue deprecation notice in obnoxious mode...
|
265
|
-
end
|
266
|
-
|
267
268
|
keys = args.flatten
|
268
269
|
|
269
270
|
command = [cmd, keys, timeout]
|
data/lib/redis/commands/sets.rb
CHANGED
@@ -20,15 +20,23 @@ class Redis
|
|
20
20
|
# array of members is specified, holding the number of members that were
|
21
21
|
# successfully added
|
22
22
|
def sadd(key, member)
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Boolify.call(reply)
|
30
|
-
end
|
23
|
+
block = if Redis.sadd_returns_boolean && !member.is_a?(Array)
|
24
|
+
::Redis.deprecate!(
|
25
|
+
"Redis#sadd will always return an Integer in Redis 5.0.0. Use Redis#sadd? instead." \
|
26
|
+
"(called from: #{caller(1, 1).first})"
|
27
|
+
)
|
28
|
+
Boolify
|
31
29
|
end
|
30
|
+
send_command([:sadd, key, member], &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Add one or more members to a set.
|
34
|
+
#
|
35
|
+
# @param [String] key
|
36
|
+
# @param [String, Array<String>] member one member, or array of members
|
37
|
+
# @return [Boolean] Whether or not at least one member was added.
|
38
|
+
def sadd?(key, member)
|
39
|
+
send_command([:sadd, key, member], &Boolify)
|
32
40
|
end
|
33
41
|
|
34
42
|
# Remove one or more members from a set.
|
@@ -40,15 +48,23 @@ class Redis
|
|
40
48
|
# array of members is specified, holding the number of members that were
|
41
49
|
# successfully removed
|
42
50
|
def srem(key, member)
|
43
|
-
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
Boolify.call(reply)
|
50
|
-
end
|
51
|
+
block = if Redis.sadd_returns_boolean && !member.is_a?(Array)
|
52
|
+
::Redis.deprecate!(
|
53
|
+
"Redis#sadd will always return an Integer in Redis 5.0.0. Use Redis#sadd? instead." \
|
54
|
+
"(called from: #{caller(1, 1).first})"
|
55
|
+
)
|
56
|
+
Boolify
|
51
57
|
end
|
58
|
+
send_command([:srem, key, member], &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Remove one or more members from a set.
|
62
|
+
#
|
63
|
+
# @param [String] key
|
64
|
+
# @param [String, Array<String>] member one member, or array of members
|
65
|
+
# @return [Boolean] `Boolean` Whether or not a member was removed.
|
66
|
+
def srem?(key, member)
|
67
|
+
send_command([:srem, key, member], &Boolify)
|
52
68
|
end
|
53
69
|
|
54
70
|
# Remove and return one or more random member from a set.
|
@@ -60,8 +60,11 @@ class Redis
|
|
60
60
|
command << "INCR" if incr
|
61
61
|
|
62
62
|
if args.size == 1 && args[0].is_a?(Array)
|
63
|
+
members_to_add = args[0]
|
64
|
+
return 0 if members_to_add.empty?
|
65
|
+
|
63
66
|
# Variadic: return float if INCR, integer if !INCR
|
64
|
-
send_command(command +
|
67
|
+
send_command(command + members_to_add, &(incr ? Floatify : nil))
|
65
68
|
elsif args.size == 2
|
66
69
|
# Single pair: return float if INCR, boolean if !INCR
|
67
70
|
send_command(command + args, &(incr ? Floatify : Boolify))
|
@@ -102,6 +105,11 @@ class Redis
|
|
102
105
|
# - `Integer` when an array of pairs is specified, holding the number of
|
103
106
|
# members that were removed to the sorted set
|
104
107
|
def zrem(key, member)
|
108
|
+
if member.is_a?(Array)
|
109
|
+
members_to_remove = member
|
110
|
+
return 0 if members_to_remove.empty?
|
111
|
+
end
|
112
|
+
|
105
113
|
send_command([:zrem, key, member]) do |reply|
|
106
114
|
if member.is_a? Array
|
107
115
|
# Variadic: return integer
|
@@ -3,6 +3,53 @@
|
|
3
3
|
class Redis
|
4
4
|
module Commands
|
5
5
|
module Transactions
|
6
|
+
# Mark the start of a transaction block.
|
7
|
+
#
|
8
|
+
# Passing a block is optional.
|
9
|
+
#
|
10
|
+
# @example With a block
|
11
|
+
# redis.multi do |multi|
|
12
|
+
# multi.set("key", "value")
|
13
|
+
# multi.incr("counter")
|
14
|
+
# end # => ["OK", 6]
|
15
|
+
#
|
16
|
+
# @example Without a block
|
17
|
+
# redis.multi
|
18
|
+
# # => "OK"
|
19
|
+
# redis.set("key", "value")
|
20
|
+
# # => "QUEUED"
|
21
|
+
# redis.incr("counter")
|
22
|
+
# # => "QUEUED"
|
23
|
+
# redis.exec
|
24
|
+
# # => ["OK", 6]
|
25
|
+
#
|
26
|
+
# @yield [multi] the commands that are called inside this block are cached
|
27
|
+
# and written to the server upon returning from it
|
28
|
+
# @yieldparam [Redis] multi `self`
|
29
|
+
#
|
30
|
+
# @return [String, Array<...>]
|
31
|
+
# - when a block is not given, `OK`
|
32
|
+
# - when a block is given, an array with replies
|
33
|
+
#
|
34
|
+
# @see #watch
|
35
|
+
# @see #unwatch
|
36
|
+
def multi(&block) # :nodoc:
|
37
|
+
if block_given?
|
38
|
+
if block&.arity == 0
|
39
|
+
Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 5))
|
40
|
+
end
|
41
|
+
|
42
|
+
synchronize do |prior_client|
|
43
|
+
pipeline = Pipeline::Multi.new(prior_client)
|
44
|
+
pipelined_connection = PipelinedConnection.new(pipeline)
|
45
|
+
yield pipelined_connection
|
46
|
+
prior_client.call_pipeline(pipeline)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
send_command([:multi])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
6
53
|
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
7
54
|
#
|
8
55
|
# Using a block is optional, but is necessary for thread-safety.
|
data/lib/redis/commands.rb
CHANGED
@@ -384,6 +384,12 @@ class Redis
|
|
384
384
|
format_reply(reply_type, line)
|
385
385
|
rescue Errno::EAGAIN
|
386
386
|
raise TimeoutError
|
387
|
+
rescue OpenSSL::SSL::SSLError => ssl_error
|
388
|
+
if ssl_error.message.match?(/SSL_read: unexpected eof while reading/i)
|
389
|
+
raise EOFError, ssl_error.message
|
390
|
+
else
|
391
|
+
raise
|
392
|
+
end
|
387
393
|
end
|
388
394
|
|
389
395
|
def format_reply(reply_type, line)
|
data/lib/redis/distributed.rb
CHANGED
@@ -115,13 +115,13 @@ class Redis
|
|
115
115
|
end
|
116
116
|
|
117
117
|
# Set a key's time to live in seconds.
|
118
|
-
def expire(key, seconds)
|
119
|
-
node_for(key).expire(key, seconds)
|
118
|
+
def expire(key, seconds, **kwargs)
|
119
|
+
node_for(key).expire(key, seconds, **kwargs)
|
120
120
|
end
|
121
121
|
|
122
122
|
# Set the expiration for a key as a UNIX timestamp.
|
123
|
-
def expireat(key, unix_time)
|
124
|
-
node_for(key).expireat(key, unix_time)
|
123
|
+
def expireat(key, unix_time, **kwargs)
|
124
|
+
node_for(key).expireat(key, unix_time, **kwargs)
|
125
125
|
end
|
126
126
|
|
127
127
|
# Get the time to live (in seconds) for a key.
|
@@ -130,13 +130,13 @@ class Redis
|
|
130
130
|
end
|
131
131
|
|
132
132
|
# Set a key's time to live in milliseconds.
|
133
|
-
def pexpire(key, milliseconds)
|
134
|
-
node_for(key).pexpire(key, milliseconds)
|
133
|
+
def pexpire(key, milliseconds, **kwarg)
|
134
|
+
node_for(key).pexpire(key, milliseconds, **kwarg)
|
135
135
|
end
|
136
136
|
|
137
137
|
# Set the expiration for a key as number of milliseconds from UNIX Epoch.
|
138
|
-
def pexpireat(key, ms_unix_time)
|
139
|
-
node_for(key).pexpireat(key, ms_unix_time)
|
138
|
+
def pexpireat(key, ms_unix_time, **kwarg)
|
139
|
+
node_for(key).pexpireat(key, ms_unix_time, **kwarg)
|
140
140
|
end
|
141
141
|
|
142
142
|
# Get the time to live (in milliseconds) for a key.
|
@@ -464,12 +464,13 @@ class Redis
|
|
464
464
|
options = args.pop
|
465
465
|
options[:timeout]
|
466
466
|
elsif args.last.respond_to?(:to_int)
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
467
|
+
last_arg = args.pop
|
468
|
+
::Redis.deprecate!(
|
469
|
+
"Passing the timeout as a positional argument is deprecated, it should be passed as a keyword argument:\n" \
|
470
|
+
" redis.#{cmd}(#{args.map(&:inspect).join(', ')}, timeout: #{last_arg.to_int})" \
|
471
|
+
"(called from: #{caller(2, 1).first})"
|
472
|
+
)
|
473
|
+
last_arg.to_int
|
473
474
|
end
|
474
475
|
|
475
476
|
keys = args.flatten
|
@@ -543,11 +544,21 @@ class Redis
|
|
543
544
|
node_for(key).sadd(key, member)
|
544
545
|
end
|
545
546
|
|
547
|
+
# Add one or more members to a set.
|
548
|
+
def sadd?(key, member)
|
549
|
+
node_for(key).sadd?(key, member)
|
550
|
+
end
|
551
|
+
|
546
552
|
# Remove one or more members from a set.
|
547
553
|
def srem(key, member)
|
548
554
|
node_for(key).srem(key, member)
|
549
555
|
end
|
550
556
|
|
557
|
+
# Remove one or more members from a set.
|
558
|
+
def srem?(key, member)
|
559
|
+
node_for(key).srem?(key, member)
|
560
|
+
end
|
561
|
+
|
551
562
|
# Remove and return a random member from a set.
|
552
563
|
def spop(key, count = nil)
|
553
564
|
node_for(key).spop(key, count)
|
data/lib/redis/errors.rb
CHANGED
@@ -45,6 +45,15 @@ class Redis
|
|
45
45
|
end
|
46
46
|
|
47
47
|
class Cluster
|
48
|
+
# Raised when client connected to redis as cluster mode
|
49
|
+
# and failed to fetch cluster state information by commands.
|
50
|
+
class InitialSetupError < BaseError
|
51
|
+
# @param errors [Array<Redis::BaseError>]
|
52
|
+
def initialize(errors)
|
53
|
+
super("Redis client could not fetch cluster information: #{errors.map(&:message).uniq.join(',')}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
48
57
|
# Raised when client connected to redis as cluster mode
|
49
58
|
# and some cluster subcommands were called.
|
50
59
|
class OrchestrationCommandNotSupported < BaseError
|
data/lib/redis/pipeline.rb
CHANGED
@@ -22,6 +22,11 @@ class Redis
|
|
22
22
|
yield self
|
23
23
|
end
|
24
24
|
|
25
|
+
def call_pipeline(pipeline)
|
26
|
+
@pipeline.call_pipeline(pipeline)
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
25
30
|
private
|
26
31
|
|
27
32
|
def synchronize
|
@@ -69,6 +74,7 @@ class Redis
|
|
69
74
|
attr_reader :client
|
70
75
|
|
71
76
|
attr :futures
|
77
|
+
alias materialized_futures futures
|
72
78
|
|
73
79
|
def initialize(client)
|
74
80
|
@client = client.is_a?(Pipeline) ? client.client : client
|
@@ -112,7 +118,7 @@ class Redis
|
|
112
118
|
|
113
119
|
def call_pipeline(pipeline)
|
114
120
|
@shutdown = true if pipeline.shutdown?
|
115
|
-
@futures.concat(pipeline.
|
121
|
+
@futures.concat(pipeline.materialized_futures)
|
116
122
|
@db = pipeline.db
|
117
123
|
nil
|
118
124
|
end
|
@@ -169,6 +175,18 @@ class Redis
|
|
169
175
|
end
|
170
176
|
end
|
171
177
|
|
178
|
+
def materialized_futures
|
179
|
+
if empty?
|
180
|
+
[]
|
181
|
+
else
|
182
|
+
[
|
183
|
+
Future.new([:multi], nil, 0),
|
184
|
+
*futures,
|
185
|
+
MultiFuture.new(futures)
|
186
|
+
]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
172
190
|
def timeouts
|
173
191
|
if empty?
|
174
192
|
[]
|
@@ -271,4 +289,18 @@ class Redis
|
|
271
289
|
Future
|
272
290
|
end
|
273
291
|
end
|
292
|
+
|
293
|
+
class MultiFuture < Future
|
294
|
+
def initialize(futures)
|
295
|
+
@futures = futures
|
296
|
+
@command = [:exec]
|
297
|
+
end
|
298
|
+
|
299
|
+
def _set(replies)
|
300
|
+
@futures.each_with_index do |future, index|
|
301
|
+
future._set(replies[index])
|
302
|
+
end
|
303
|
+
replies
|
304
|
+
end
|
305
|
+
end
|
274
306
|
end
|
data/lib/redis/version.rb
CHANGED
data/lib/redis.rb
CHANGED
@@ -7,12 +7,13 @@ require "redis/commands"
|
|
7
7
|
class Redis
|
8
8
|
BASE_PATH = __dir__
|
9
9
|
@exists_returns_integer = true
|
10
|
+
@sadd_returns_boolean = true
|
10
11
|
|
11
12
|
Deprecated = Class.new(StandardError)
|
12
13
|
|
13
14
|
class << self
|
14
15
|
attr_reader :exists_returns_integer
|
15
|
-
attr_accessor :silence_deprecations, :raise_deprecations
|
16
|
+
attr_accessor :silence_deprecations, :raise_deprecations, :sadd_returns_boolean
|
16
17
|
|
17
18
|
def exists_returns_integer=(value)
|
18
19
|
unless value
|
@@ -37,7 +38,7 @@ class Redis
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def current
|
40
|
-
deprecate!("`Redis.current
|
41
|
+
deprecate!("`Redis.current` is deprecated and will be removed in 5.0. (called from: #{caller(1, 1).first})")
|
41
42
|
@current ||= Redis.new
|
42
43
|
end
|
43
44
|
|
@@ -74,6 +75,8 @@ class Redis
|
|
74
75
|
# @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
|
75
76
|
# @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
|
76
77
|
# @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
|
78
|
+
# @option options [String] :fixed_hostname Specify a FQDN if cluster mode enabled and
|
79
|
+
# client has to connect nodes via single endpoint with SSL/TLS
|
77
80
|
# @option options [Class] :connector Class of custom connector
|
78
81
|
#
|
79
82
|
# @return [Redis] a new client instance
|
@@ -109,6 +112,10 @@ class Redis
|
|
109
112
|
end
|
110
113
|
alias disconnect! close
|
111
114
|
|
115
|
+
def with
|
116
|
+
yield self
|
117
|
+
end
|
118
|
+
|
112
119
|
# @deprecated Queues a command for pipelining.
|
113
120
|
#
|
114
121
|
# Commands in the queue are executed with the Redis#commit method.
|
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: 4.
|
4
|
+
version: 4.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezra Zygmuntowicz
|
@@ -16,7 +16,7 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date: 2022-
|
19
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: em-synchrony
|
@@ -119,9 +119,9 @@ licenses:
|
|
119
119
|
metadata:
|
120
120
|
bug_tracker_uri: https://github.com/redis/redis-rb/issues
|
121
121
|
changelog_uri: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md
|
122
|
-
documentation_uri: https://www.rubydoc.info/gems/redis/4.
|
122
|
+
documentation_uri: https://www.rubydoc.info/gems/redis/4.8.0
|
123
123
|
homepage_uri: https://github.com/redis/redis-rb
|
124
|
-
source_code_uri: https://github.com/redis/redis-rb/tree/v4.
|
124
|
+
source_code_uri: https://github.com/redis/redis-rb/tree/v4.8.0
|
125
125
|
post_install_message:
|
126
126
|
rdoc_options: []
|
127
127
|
require_paths:
|