redis 4.2.5 → 5.0.7

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