redis 4.8.1 → 5.4.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/README.md +125 -162
  4. data/lib/redis/client.rb +82 -616
  5. data/lib/redis/commands/bitmaps.rb +14 -4
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +13 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +27 -23
  12. data/lib/redis/commands/lists.rb +74 -25
  13. data/lib/redis/commands/pubsub.rb +34 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +35 -40
  16. data/lib/redis/commands/sorted_sets.rb +128 -18
  17. data/lib/redis/commands/streams.rb +48 -21
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +11 -12
  21. data/lib/redis/distributed.rb +136 -72
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +47 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +77 -184
  28. metadata +10 -57
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -68
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
data/lib/redis/cluster.rb DELETED
@@ -1,315 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'errors'
4
- require_relative 'client'
5
- require_relative 'cluster/command'
6
- require_relative 'cluster/command_loader'
7
- require_relative 'cluster/key_slot_converter'
8
- require_relative 'cluster/node'
9
- require_relative 'cluster/node_key'
10
- require_relative 'cluster/node_loader'
11
- require_relative 'cluster/option'
12
- require_relative 'cluster/slot'
13
- require_relative 'cluster/slot_loader'
14
-
15
- class Redis
16
- # Redis Cluster client
17
- #
18
- # @see https://github.com/antirez/redis-rb-cluster POC implementation
19
- # @see https://redis.io/topics/cluster-spec Redis Cluster specification
20
- # @see https://redis.io/topics/cluster-tutorial Redis Cluster tutorial
21
- #
22
- # Copyright (C) 2013 Salvatore Sanfilippo <antirez@gmail.com>
23
- class Cluster
24
- def initialize(options = {})
25
- @option = Option.new(options)
26
- @node, @slot = fetch_cluster_info!(@option)
27
- @command = fetch_command_details(@node)
28
- end
29
-
30
- def id
31
- @node.map(&:id).sort.join(' ')
32
- end
33
-
34
- # db feature is disabled in cluster mode
35
- def db
36
- 0
37
- end
38
-
39
- # db feature is disabled in cluster mode
40
- def db=(_db); end
41
-
42
- def timeout
43
- @node.first.timeout
44
- end
45
-
46
- def connected?
47
- @node.any?(&:connected?)
48
- end
49
-
50
- def disconnect
51
- @node.each(&:disconnect)
52
- true
53
- end
54
-
55
- def connection_info
56
- @node.sort_by(&:id).map do |client|
57
- {
58
- host: client.host,
59
- port: client.port,
60
- db: client.db,
61
- id: client.id,
62
- location: client.location
63
- }
64
- end
65
- end
66
-
67
- def with_reconnect(val = true, &block)
68
- try_send(@node.sample, :with_reconnect, val, &block)
69
- end
70
-
71
- def call(command, &block)
72
- send_command(command, &block)
73
- end
74
-
75
- def call_loop(command, timeout = 0, &block)
76
- node = assign_node(command)
77
- try_send(node, :call_loop, command, timeout, &block)
78
- end
79
-
80
- def call_pipeline(pipeline)
81
- node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
82
- if node_keys.size > 1
83
- raise(CrossSlotPipeliningError,
84
- pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
85
- end
86
-
87
- try_send(find_node(node_keys.first), :call_pipeline, pipeline)
88
- end
89
-
90
- def call_with_timeout(command, timeout, &block)
91
- node = assign_node(command)
92
- try_send(node, :call_with_timeout, command, timeout, &block)
93
- end
94
-
95
- def call_without_timeout(command, &block)
96
- call_with_timeout(command, 0, &block)
97
- end
98
-
99
- def process(commands, &block)
100
- if commands.size == 1 &&
101
- %w[unsubscribe punsubscribe].include?(commands.first.first.to_s.downcase) &&
102
- commands.first.size == 1
103
-
104
- # Node is indeterminate. We do just a best-effort try here.
105
- @node.process_all(commands, &block)
106
- else
107
- node = assign_node(commands.first)
108
- try_send(node, :process, commands, &block)
109
- end
110
- end
111
-
112
- private
113
-
114
- def fetch_cluster_info!(option)
115
- node = Node.new(option.per_node_key)
116
- available_slots = SlotLoader.load(node)
117
- node_flags = NodeLoader.load_flags(node)
118
- option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
119
- [Node.new(option.per_node_key, node_flags, option.use_replica?),
120
- Slot.new(available_slots, node_flags, option.use_replica?)]
121
- ensure
122
- node&.each(&:disconnect)
123
- end
124
-
125
- def fetch_command_details(nodes)
126
- details = CommandLoader.load(nodes)
127
- Command.new(details)
128
- end
129
-
130
- def send_command(command, &block)
131
- cmd = command.first.to_s.downcase
132
- case cmd
133
- when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
134
- @node.call_all(command, &block).first
135
- when 'flushall', 'flushdb'
136
- @node.call_master(command, &block).first
137
- when 'wait' then @node.call_master(command, &block).reduce(:+)
138
- when 'keys' then @node.call_slave(command, &block).flatten.sort
139
- when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
140
- when 'scan' then _scan(command, &block)
141
- when 'lastsave' then @node.call_all(command, &block).sort
142
- when 'role' then @node.call_all(command, &block)
143
- when 'config' then send_config_command(command, &block)
144
- when 'client' then send_client_command(command, &block)
145
- when 'cluster' then send_cluster_command(command, &block)
146
- when 'readonly', 'readwrite', 'shutdown'
147
- raise OrchestrationCommandNotSupported, cmd
148
- when 'memory' then send_memory_command(command, &block)
149
- when 'script' then send_script_command(command, &block)
150
- when 'pubsub' then send_pubsub_command(command, &block)
151
- when 'discard', 'exec', 'multi', 'unwatch'
152
- raise AmbiguousNodeError, cmd
153
- else
154
- node = assign_node(command)
155
- try_send(node, :call, command, &block)
156
- end
157
- end
158
-
159
- def send_config_command(command, &block)
160
- case command[1].to_s.downcase
161
- when 'resetstat', 'rewrite', 'set'
162
- @node.call_all(command, &block).first
163
- else assign_node(command).call(command, &block)
164
- end
165
- end
166
-
167
- def send_memory_command(command, &block)
168
- case command[1].to_s.downcase
169
- when 'stats' then @node.call_all(command, &block)
170
- when 'purge' then @node.call_all(command, &block).first
171
- else assign_node(command).call(command, &block)
172
- end
173
- end
174
-
175
- def send_client_command(command, &block)
176
- case command[1].to_s.downcase
177
- when 'list' then @node.call_all(command, &block).flatten
178
- when 'pause', 'reply', 'setname'
179
- @node.call_all(command, &block).first
180
- else assign_node(command).call(command, &block)
181
- end
182
- end
183
-
184
- def send_cluster_command(command, &block)
185
- subcommand = command[1].to_s.downcase
186
- case subcommand
187
- when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
188
- 'reset', 'set-config-epoch', 'setslot'
189
- raise OrchestrationCommandNotSupported, 'cluster', subcommand
190
- when 'saveconfig' then @node.call_all(command, &block).first
191
- else assign_node(command).call(command, &block)
192
- end
193
- end
194
-
195
- def send_script_command(command, &block)
196
- case command[1].to_s.downcase
197
- when 'debug', 'kill'
198
- @node.call_all(command, &block).first
199
- when 'flush', 'load'
200
- @node.call_master(command, &block).first
201
- else assign_node(command).call(command, &block)
202
- end
203
- end
204
-
205
- def send_pubsub_command(command, &block)
206
- case command[1].to_s.downcase
207
- when 'channels' then @node.call_all(command, &block).flatten.uniq.sort
208
- when 'numsub'
209
- @node.call_all(command, &block).reject(&:empty?).map { |e| Hash[*e] }
210
- .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
211
- when 'numpat' then @node.call_all(command, &block).reduce(:+)
212
- else assign_node(command).call(command, &block)
213
- end
214
- end
215
-
216
- # @see https://redis.io/topics/cluster-spec#redirection-and-resharding
217
- # Redirection and resharding
218
- def try_send(node, method_name, *args, retry_count: 3, &block)
219
- node.public_send(method_name, *args, &block)
220
- rescue CommandError => err
221
- if err.message.start_with?('MOVED')
222
- raise if retry_count <= 0
223
-
224
- node = assign_redirection_node(err.message)
225
- retry_count -= 1
226
- retry
227
- elsif err.message.start_with?('ASK')
228
- raise if retry_count <= 0
229
-
230
- node = assign_asking_node(err.message)
231
- node.call(%i[asking])
232
- retry_count -= 1
233
- retry
234
- else
235
- raise
236
- end
237
- rescue CannotConnectError
238
- update_cluster_info!
239
- raise
240
- end
241
-
242
- def _scan(command, &block)
243
- input_cursor = Integer(command[1])
244
-
245
- client_index = input_cursor % 256
246
- raw_cursor = input_cursor >> 8
247
-
248
- clients = @node.scale_reading_clients
249
-
250
- client = clients[client_index]
251
- return ['0', []] unless client
252
-
253
- command[1] = raw_cursor.to_s
254
-
255
- result_cursor, result_keys = client.call(command, &block)
256
- result_cursor = Integer(result_cursor)
257
-
258
- if result_cursor == 0
259
- client_index += 1
260
- end
261
-
262
- [((result_cursor << 8) + client_index).to_s, result_keys]
263
- end
264
-
265
- def assign_redirection_node(err_msg)
266
- _, slot, node_key = err_msg.split(' ')
267
- slot = slot.to_i
268
- @slot.put(slot, node_key)
269
- find_node(node_key)
270
- end
271
-
272
- def assign_asking_node(err_msg)
273
- _, _, node_key = err_msg.split(' ')
274
- find_node(node_key)
275
- end
276
-
277
- def assign_node(command)
278
- node_key = find_node_key(command)
279
- find_node(node_key)
280
- end
281
-
282
- def find_node_key(command, primary_only: false)
283
- key = @command.extract_first_key(command)
284
- return if key.empty?
285
-
286
- slot = KeySlotConverter.convert(key)
287
- return unless @slot.exists?(slot)
288
-
289
- if @command.should_send_to_master?(command) || primary_only
290
- @slot.find_node_key_of_master(slot)
291
- else
292
- @slot.find_node_key_of_slave(slot)
293
- end
294
- end
295
-
296
- def find_node(node_key)
297
- return @node.sample if node_key.nil?
298
-
299
- @node.find_by(node_key)
300
- rescue Node::ReloadNeeded
301
- update_cluster_info!(node_key)
302
- @node.find_by(node_key)
303
- end
304
-
305
- def update_cluster_info!(node_key = nil)
306
- unless node_key.nil?
307
- host, port = NodeKey.split(node_key)
308
- @option.add_node(host, port)
309
- end
310
-
311
- @node.map(&:disconnect)
312
- @node, @slot = fetch_cluster_info!(@option)
313
- end
314
- end
315
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Redis
4
- module Connection
5
- module CommandHelper
6
- COMMAND_DELIMITER = "\r\n"
7
-
8
- def build_command(args)
9
- command = [nil]
10
-
11
- args.each do |i|
12
- if i.is_a? Array
13
- i.each do |j|
14
- j = j.to_s
15
- j = j.encoding == Encoding::BINARY ? j : j.b
16
- command << "$#{j.bytesize}"
17
- command << j
18
- end
19
- else
20
- i = i.to_s
21
- i = i.encoding == Encoding::BINARY ? i : i.b
22
- command << "$#{i.bytesize}"
23
- command << i
24
- end
25
- end
26
-
27
- command[0] = "*#{(command.length - 1) / 2}"
28
-
29
- # Trailing delimiter
30
- command << ""
31
- command.join(COMMAND_DELIMITER)
32
- end
33
-
34
- protected
35
-
36
- def encode(string)
37
- string.force_encoding(Encoding.default_external)
38
- end
39
- end
40
- end
41
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "redis/connection/registry"
4
- require "redis/errors"
5
-
6
- require "hiredis/connection"
7
- require "timeout"
8
-
9
- class Redis
10
- module Connection
11
- class Hiredis
12
- def self.connect(config)
13
- connection = ::Hiredis::Connection.new
14
- connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
15
-
16
- if config[:scheme] == "unix"
17
- connection.connect_unix(config[:path], connect_timeout)
18
- elsif config[:scheme] == "rediss" || config[:ssl]
19
- raise NotImplementedError, "SSL not supported by hiredis driver"
20
- else
21
- connection.connect(config[:host], config[:port], connect_timeout)
22
- end
23
-
24
- instance = new(connection)
25
- instance.timeout = config[:read_timeout]
26
- instance
27
- rescue Errno::ETIMEDOUT
28
- raise TimeoutError
29
- end
30
-
31
- def initialize(connection)
32
- @connection = connection
33
- end
34
-
35
- def connected?
36
- @connection&.connected?
37
- end
38
-
39
- def timeout=(timeout)
40
- # Hiredis works with microsecond timeouts
41
- @connection.timeout = Integer(timeout * 1_000_000)
42
- end
43
-
44
- def disconnect
45
- @connection.disconnect
46
- @connection = nil
47
- end
48
-
49
- def write(command)
50
- @connection.write(command.flatten(1))
51
- rescue Errno::EAGAIN
52
- raise TimeoutError
53
- end
54
-
55
- def read
56
- reply = @connection.read
57
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
58
- reply
59
- rescue Errno::EAGAIN
60
- raise TimeoutError
61
- rescue RuntimeError => err
62
- raise ProtocolError, err.message
63
- end
64
- end
65
- end
66
- end
67
-
68
- Redis::Connection.drivers << Redis::Connection::Hiredis
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Redis
4
- module Connection
5
- # Store a list of loaded connection drivers in the Connection module.
6
- # Redis::Client uses the last required driver by default, and will be aware
7
- # of the loaded connection drivers if the user chooses to override the
8
- # default connection driver.
9
- def self.drivers
10
- @drivers ||= []
11
- end
12
- end
13
- end