redis 4.1.0 → 4.6.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 +5 -5
- data/CHANGELOG.md +158 -0
- data/README.md +91 -27
- data/lib/redis/client.rb +148 -92
- data/lib/redis/cluster/command.rb +4 -6
- data/lib/redis/cluster/command_loader.rb +6 -7
- data/lib/redis/cluster/node.rb +17 -1
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +30 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/cluster.rb +46 -17
- data/lib/redis/commands/bitmaps.rb +63 -0
- data/lib/redis/commands/cluster.rb +45 -0
- data/lib/redis/commands/connection.rb +58 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +251 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +411 -0
- data/lib/redis/commands/lists.rb +289 -0
- data/lib/redis/commands/pubsub.rb +72 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +207 -0
- data/lib/redis/commands/sorted_sets.rb +804 -0
- data/lib/redis/commands/streams.rb +382 -0
- data/lib/redis/commands/strings.rb +313 -0
- data/lib/redis/commands/transactions.rb +92 -0
- data/lib/redis/commands.rb +242 -0
- data/lib/redis/connection/command_helper.rb +5 -2
- data/lib/redis/connection/hiredis.rb +7 -5
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +129 -110
- data/lib/redis/connection/synchrony.rb +17 -10
- data/lib/redis/connection.rb +3 -1
- data/lib/redis/distributed.rb +209 -70
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +139 -8
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +167 -3377
- metadata +32 -25
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
module Commands
|
5
|
+
module Transactions
|
6
|
+
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
7
|
+
#
|
8
|
+
# Using a block is optional, but is necessary for thread-safety.
|
9
|
+
#
|
10
|
+
# An `#unwatch` is automatically issued if an exception is raised within the
|
11
|
+
# block that is a subclass of StandardError and is not a ConnectionError.
|
12
|
+
#
|
13
|
+
# @example With a block
|
14
|
+
# redis.watch("key") do
|
15
|
+
# if redis.get("key") == "some value"
|
16
|
+
# redis.multi do |multi|
|
17
|
+
# multi.set("key", "other value")
|
18
|
+
# multi.incr("counter")
|
19
|
+
# end
|
20
|
+
# else
|
21
|
+
# redis.unwatch
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# # => ["OK", 6]
|
25
|
+
#
|
26
|
+
# @example Without a block
|
27
|
+
# redis.watch("key")
|
28
|
+
# # => "OK"
|
29
|
+
#
|
30
|
+
# @param [String, Array<String>] keys one or more keys to watch
|
31
|
+
# @return [Object] if using a block, returns the return value of the block
|
32
|
+
# @return [String] if not using a block, returns `OK`
|
33
|
+
#
|
34
|
+
# @see #unwatch
|
35
|
+
# @see #multi
|
36
|
+
def watch(*keys)
|
37
|
+
synchronize do |client|
|
38
|
+
res = client.call([:watch, *keys])
|
39
|
+
|
40
|
+
if block_given?
|
41
|
+
begin
|
42
|
+
yield(self)
|
43
|
+
rescue ConnectionError
|
44
|
+
raise
|
45
|
+
rescue StandardError
|
46
|
+
unwatch
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
else
|
50
|
+
res
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Forget about all watched keys.
|
56
|
+
#
|
57
|
+
# @return [String] `OK`
|
58
|
+
#
|
59
|
+
# @see #watch
|
60
|
+
# @see #multi
|
61
|
+
def unwatch
|
62
|
+
send_command([:unwatch])
|
63
|
+
end
|
64
|
+
|
65
|
+
# Execute all commands issued after MULTI.
|
66
|
+
#
|
67
|
+
# Only call this method when `#multi` was called **without** a block.
|
68
|
+
#
|
69
|
+
# @return [nil, Array<...>]
|
70
|
+
# - when commands were not executed, `nil`
|
71
|
+
# - when commands were executed, an array with their replies
|
72
|
+
#
|
73
|
+
# @see #multi
|
74
|
+
# @see #discard
|
75
|
+
def exec
|
76
|
+
send_command([:exec])
|
77
|
+
end
|
78
|
+
|
79
|
+
# Discard all commands issued after MULTI.
|
80
|
+
#
|
81
|
+
# Only call this method when `#multi` was called **without** a block.
|
82
|
+
#
|
83
|
+
# @return [String] `"OK"`
|
84
|
+
#
|
85
|
+
# @see #multi
|
86
|
+
# @see #exec
|
87
|
+
def discard
|
88
|
+
send_command([:discard])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis/commands/bitmaps"
|
4
|
+
require "redis/commands/cluster"
|
5
|
+
require "redis/commands/connection"
|
6
|
+
require "redis/commands/geo"
|
7
|
+
require "redis/commands/hashes"
|
8
|
+
require "redis/commands/hyper_log_log"
|
9
|
+
require "redis/commands/keys"
|
10
|
+
require "redis/commands/lists"
|
11
|
+
require "redis/commands/pubsub"
|
12
|
+
require "redis/commands/scripting"
|
13
|
+
require "redis/commands/server"
|
14
|
+
require "redis/commands/sets"
|
15
|
+
require "redis/commands/sorted_sets"
|
16
|
+
require "redis/commands/streams"
|
17
|
+
require "redis/commands/strings"
|
18
|
+
require "redis/commands/transactions"
|
19
|
+
|
20
|
+
class Redis
|
21
|
+
module Commands
|
22
|
+
include Bitmaps
|
23
|
+
include Cluster
|
24
|
+
include Connection
|
25
|
+
include Geo
|
26
|
+
include Hashes
|
27
|
+
include HyperLogLog
|
28
|
+
include Keys
|
29
|
+
include Lists
|
30
|
+
include Pubsub
|
31
|
+
include Scripting
|
32
|
+
include Server
|
33
|
+
include Sets
|
34
|
+
include SortedSets
|
35
|
+
include Streams
|
36
|
+
include Strings
|
37
|
+
include Transactions
|
38
|
+
|
39
|
+
# Commands returning 1 for true and 0 for false may be executed in a pipeline
|
40
|
+
# where the method call will return nil. Propagate the nil instead of falsely
|
41
|
+
# returning false.
|
42
|
+
Boolify = lambda { |value|
|
43
|
+
case value
|
44
|
+
when 1
|
45
|
+
true
|
46
|
+
when 0
|
47
|
+
false
|
48
|
+
else
|
49
|
+
value
|
50
|
+
end
|
51
|
+
}
|
52
|
+
|
53
|
+
BoolifySet = lambda { |value|
|
54
|
+
case value
|
55
|
+
when "OK"
|
56
|
+
true
|
57
|
+
when nil
|
58
|
+
false
|
59
|
+
else
|
60
|
+
value
|
61
|
+
end
|
62
|
+
}
|
63
|
+
|
64
|
+
Hashify = lambda { |value|
|
65
|
+
if value.respond_to?(:each_slice)
|
66
|
+
value.each_slice(2).to_h
|
67
|
+
else
|
68
|
+
value
|
69
|
+
end
|
70
|
+
}
|
71
|
+
|
72
|
+
Pairify = lambda { |value|
|
73
|
+
if value.respond_to?(:each_slice)
|
74
|
+
value.each_slice(2).to_a
|
75
|
+
else
|
76
|
+
value
|
77
|
+
end
|
78
|
+
}
|
79
|
+
|
80
|
+
Floatify = lambda { |value|
|
81
|
+
case value
|
82
|
+
when "inf"
|
83
|
+
Float::INFINITY
|
84
|
+
when "-inf"
|
85
|
+
-Float::INFINITY
|
86
|
+
when String
|
87
|
+
Float(value)
|
88
|
+
else
|
89
|
+
value
|
90
|
+
end
|
91
|
+
}
|
92
|
+
|
93
|
+
FloatifyPairs = lambda { |value|
|
94
|
+
return value unless value.respond_to?(:each_slice)
|
95
|
+
|
96
|
+
value.each_slice(2).map do |member, score|
|
97
|
+
[member, Floatify.call(score)]
|
98
|
+
end
|
99
|
+
}
|
100
|
+
|
101
|
+
HashifyInfo = lambda { |reply|
|
102
|
+
lines = reply.split("\r\n").grep_v(/^(#|$)/)
|
103
|
+
lines.map! { |line| line.split(':', 2) }
|
104
|
+
lines.compact!
|
105
|
+
lines.to_h
|
106
|
+
}
|
107
|
+
|
108
|
+
HashifyStreams = lambda { |reply|
|
109
|
+
case reply
|
110
|
+
when nil
|
111
|
+
{}
|
112
|
+
else
|
113
|
+
reply.map { |key, entries| [key, HashifyStreamEntries.call(entries)] }.to_h
|
114
|
+
end
|
115
|
+
}
|
116
|
+
|
117
|
+
EMPTY_STREAM_RESPONSE = [nil].freeze
|
118
|
+
private_constant :EMPTY_STREAM_RESPONSE
|
119
|
+
|
120
|
+
HashifyStreamEntries = lambda { |reply|
|
121
|
+
reply.compact.map do |entry_id, values|
|
122
|
+
[entry_id, values&.each_slice(2)&.to_h]
|
123
|
+
end
|
124
|
+
}
|
125
|
+
|
126
|
+
HashifyStreamAutoclaim = lambda { |reply|
|
127
|
+
{
|
128
|
+
'next' => reply[0],
|
129
|
+
'entries' => reply[1].map { |entry| [entry[0], entry[1].each_slice(2).to_h] }
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
HashifyStreamAutoclaimJustId = lambda { |reply|
|
134
|
+
{
|
135
|
+
'next' => reply[0],
|
136
|
+
'entries' => reply[1]
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
HashifyStreamPendings = lambda { |reply|
|
141
|
+
{
|
142
|
+
'size' => reply[0],
|
143
|
+
'min_entry_id' => reply[1],
|
144
|
+
'max_entry_id' => reply[2],
|
145
|
+
'consumers' => reply[3].nil? ? {} : reply[3].to_h
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
HashifyStreamPendingDetails = lambda { |reply|
|
150
|
+
reply.map do |arr|
|
151
|
+
{
|
152
|
+
'entry_id' => arr[0],
|
153
|
+
'consumer' => arr[1],
|
154
|
+
'elapsed' => arr[2],
|
155
|
+
'count' => arr[3]
|
156
|
+
}
|
157
|
+
end
|
158
|
+
}
|
159
|
+
|
160
|
+
HashifyClusterNodeInfo = lambda { |str|
|
161
|
+
arr = str.split(' ')
|
162
|
+
{
|
163
|
+
'node_id' => arr[0],
|
164
|
+
'ip_port' => arr[1],
|
165
|
+
'flags' => arr[2].split(','),
|
166
|
+
'master_node_id' => arr[3],
|
167
|
+
'ping_sent' => arr[4],
|
168
|
+
'pong_recv' => arr[5],
|
169
|
+
'config_epoch' => arr[6],
|
170
|
+
'link_state' => arr[7],
|
171
|
+
'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
HashifyClusterSlots = lambda { |reply|
|
176
|
+
reply.map do |arr|
|
177
|
+
first_slot, last_slot = arr[0..1]
|
178
|
+
master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
|
179
|
+
replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
|
180
|
+
{
|
181
|
+
'start_slot' => first_slot,
|
182
|
+
'end_slot' => last_slot,
|
183
|
+
'master' => master,
|
184
|
+
'replicas' => replicas
|
185
|
+
}
|
186
|
+
end
|
187
|
+
}
|
188
|
+
|
189
|
+
HashifyClusterNodes = lambda { |reply|
|
190
|
+
reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
|
191
|
+
}
|
192
|
+
|
193
|
+
HashifyClusterSlaves = lambda { |reply|
|
194
|
+
reply.map { |str| HashifyClusterNodeInfo.call(str) }
|
195
|
+
}
|
196
|
+
|
197
|
+
Noop = ->(reply) { reply }
|
198
|
+
|
199
|
+
# Sends a command to Redis and returns its reply.
|
200
|
+
#
|
201
|
+
# Replies are converted to Ruby objects according to the RESP protocol, so
|
202
|
+
# you can expect a Ruby array, integer or nil when Redis sends one. Higher
|
203
|
+
# level transformations, such as converting an array of pairs into a Ruby
|
204
|
+
# hash, are up to consumers.
|
205
|
+
#
|
206
|
+
# Redis error replies are raised as Ruby exceptions.
|
207
|
+
def call(*command)
|
208
|
+
send_command(command)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Interact with the sentinel command (masters, master, slaves, failover)
|
212
|
+
#
|
213
|
+
# @param [String] subcommand e.g. `masters`, `master`, `slaves`
|
214
|
+
# @param [Array<String>] args depends on subcommand
|
215
|
+
# @return [Array<String>, Hash<String, String>, String] depends on subcommand
|
216
|
+
def sentinel(subcommand, *args)
|
217
|
+
subcommand = subcommand.to_s.downcase
|
218
|
+
send_command([:sentinel, subcommand] + args) do |reply|
|
219
|
+
case subcommand
|
220
|
+
when "get-master-addr-by-name"
|
221
|
+
reply
|
222
|
+
else
|
223
|
+
if reply.is_a?(Array)
|
224
|
+
if reply[0].is_a?(Array)
|
225
|
+
reply.map(&Hashify)
|
226
|
+
else
|
227
|
+
Hashify.call(reply)
|
228
|
+
end
|
229
|
+
else
|
230
|
+
reply
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
def method_missing(*command) # rubocop:disable Style/MissingRespondToMissing
|
239
|
+
send_command(command)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
5
|
module CommandHelper
|
4
|
-
|
5
6
|
COMMAND_DELIMITER = "\r\n"
|
6
7
|
|
7
8
|
def build_command(args)
|
@@ -11,11 +12,13 @@ class Redis
|
|
11
12
|
if i.is_a? Array
|
12
13
|
i.each do |j|
|
13
14
|
j = j.to_s
|
15
|
+
j = j.encoding == Encoding::BINARY ? j : j.b
|
14
16
|
command << "$#{j.bytesize}"
|
15
17
|
command << j
|
16
18
|
end
|
17
19
|
else
|
18
20
|
i = i.to_s
|
21
|
+
i = i.encoding == Encoding::BINARY ? i : i.b
|
19
22
|
command << "$#{i.bytesize}"
|
20
23
|
command << i
|
21
24
|
end
|
@@ -28,7 +31,7 @@ class Redis
|
|
28
31
|
command.join(COMMAND_DELIMITER)
|
29
32
|
end
|
30
33
|
|
31
|
-
|
34
|
+
protected
|
32
35
|
|
33
36
|
def encode(string)
|
34
37
|
string.force_encoding(Encoding.default_external)
|
@@ -1,12 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis/connection/registry"
|
4
|
+
require "redis/errors"
|
5
|
+
|
3
6
|
require "hiredis/connection"
|
4
7
|
require "timeout"
|
5
8
|
|
6
9
|
class Redis
|
7
10
|
module Connection
|
8
11
|
class Hiredis
|
9
|
-
|
10
12
|
def self.connect(config)
|
11
13
|
connection = ::Hiredis::Connection.new
|
12
14
|
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
@@ -31,7 +33,7 @@ class Redis
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def connected?
|
34
|
-
@connection
|
36
|
+
@connection&.connected?
|
35
37
|
end
|
36
38
|
|
37
39
|
def timeout=(timeout)
|
@@ -57,7 +59,7 @@ class Redis
|
|
57
59
|
rescue Errno::EAGAIN
|
58
60
|
raise TimeoutError
|
59
61
|
rescue RuntimeError => err
|
60
|
-
raise ProtocolError
|
62
|
+
raise ProtocolError, err.message
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
|
-
|
4
5
|
# Store a list of loaded connection drivers in the Connection module.
|
5
6
|
# Redis::Client uses the last required driver by default, and will be aware
|
6
7
|
# of the loaded connection drivers if the user chooses to override the
|