redis-cluster-client 0.11.6 → 0.12.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e31ae2691d5dac09f55bffb25f1a5af9f0f300d9b71246f998d7a18ad6d6d97
4
- data.tar.gz: 7331c0ea5c04d95bb9325ad235edbede9fa872dacdcc1df2745214241600f7fa
3
+ metadata.gz: 5d90c095f5058e808331eaa9219fb52b9d4a27de639a9c54d194c68ecb253611
4
+ data.tar.gz: 8cd8092db87c7cfa42e00c6cabd30747bdea74130d8eb1e19b971b95637665fa
5
5
  SHA512:
6
- metadata.gz: f8752f06007254fd8c73716f3a93853062e8bb61d9381cdf66e56f705a4f7c391e0f2685ee2b75d97d2bd25aac815ae17997e0982b0774c78324b04637af1574
7
- data.tar.gz: f995c63ae5e11ff31a0ce4feaef8c421620244c192d32ae3522a71ca62956b362962c15a87daa8c76c50018368c2851d0c99c83b6a061fa1ef7024da3df4e6fd
6
+ metadata.gz: 4821b78b0d5766566fa3943f65271908d3e52e3f82269bd2ea0a789e571df7818b5e0275c88d8833eb7b449c7c0eb3ed672034c1caf03fac35528114f31adb73
7
+ data.tar.gz: a4bb1d4a766cd742645c61342c2822e2bd337c792fb67b9793bfbb06ec0a889185eaa8b3ed34f96591f9e57997ecfe83cc901ce65451577b7e8f13d7d23f64b3
@@ -17,7 +17,6 @@ class RedisClient
17
17
  Detail = Struct.new(
18
18
  'RedisCommand',
19
19
  :first_key_position,
20
- :last_key_position,
21
20
  :key_step,
22
21
  :write?,
23
22
  :readonly?,
@@ -25,7 +24,7 @@ class RedisClient
25
24
  )
26
25
 
27
26
  class << self
28
- def load(nodes, slow_command_timeout: -1)
27
+ def load(nodes, slow_command_timeout: -1) # rubocop:disable Metrics/AbcSize
29
28
  cmd = errors = nil
30
29
 
31
30
  nodes&.each do |node|
@@ -43,7 +42,7 @@ class RedisClient
43
42
 
44
43
  return cmd unless cmd.nil?
45
44
 
46
- raise ::RedisClient::Cluster::InitialSetupError, errors
45
+ raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors)
47
46
  end
48
47
 
49
48
  private
@@ -54,7 +53,6 @@ class RedisClient
54
53
 
55
54
  acc[row[0].downcase] = ::RedisClient::Cluster::Command::Detail.new(
56
55
  first_key_position: row[3],
57
- last_key_position: row[4],
58
56
  key_step: row[5],
59
57
  write?: row[2].include?('write'),
60
58
  readonly?: row[2].include?('readonly')
@@ -71,18 +69,7 @@ class RedisClient
71
69
  i = determine_first_key_position(command)
72
70
  return EMPTY_STRING if i == 0
73
71
 
74
- (command[i].is_a?(Array) ? command[i].flatten.first : command[i]).to_s
75
- end
76
-
77
- def extract_all_keys(command)
78
- keys_start = determine_first_key_position(command)
79
- keys_end = determine_last_key_position(command, keys_start)
80
- keys_step = determine_key_step(command)
81
- return EMPTY_ARRAY if [keys_start, keys_end, keys_step].any?(&:zero?)
82
-
83
- keys_end = [keys_end, command.size - 1].min
84
- # use .. inclusive range because keys_end is a valid index.
85
- (keys_start..keys_end).step(keys_step).map { |i| command[i] }
72
+ command[i]
86
73
  end
87
74
 
88
75
  def should_send_to_primary?(command)
@@ -116,45 +103,10 @@ class RedisClient
116
103
  end
117
104
  end
118
105
 
119
- # IMPORTANT: this determines the last key position INCLUSIVE of the last key -
120
- # i.e. command[determine_last_key_position(command)] is a key.
121
- # This is in line with what Redis returns from COMMANDS.
122
- def determine_last_key_position(command, keys_start) # rubocop:disable Metrics/AbcSize
123
- case name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
124
- when 'eval', 'evalsha', 'zinterstore', 'zunionstore'
125
- # EVALSHA sha1 numkeys [key [key ...]] [arg [arg ...]]
126
- # ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
127
- command[2].to_i + 2
128
- when 'object', 'memory'
129
- # OBJECT [ENCODING | FREQ | IDLETIME | REFCOUNT] key
130
- # MEMORY USAGE key [SAMPLES count]
131
- keys_start
132
- when 'migrate'
133
- # MIGRATE host port <key | ""> destination-db timeout [COPY] [REPLACE] [AUTH password | AUTH2 username password] [KEYS key [key ...]]
134
- command[3].empty? ? (command.length - 1) : 3
135
- when 'xread', 'xreadgroup'
136
- # XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
137
- keys_start + ((command.length - keys_start) / 2) - 1
138
- else
139
- # If there is a fixed, non-variable number of keys, don't iterate past that.
140
- if @commands[name].last_key_position >= 0
141
- @commands[name].last_key_position
142
- else
143
- command.length + @commands[name].last_key_position
144
- end
145
- end
146
- end
147
-
148
- def determine_optional_key_position(command, option_name) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
149
- idx = command&.flatten&.map(&:to_s)&.map(&:downcase)&.index(option_name&.downcase)
106
+ def determine_optional_key_position(command, option_name)
107
+ idx = command.map { |e| e.to_s.downcase }.index(option_name&.downcase)
150
108
  idx.nil? ? 0 : idx + 1
151
109
  end
152
-
153
- def determine_key_step(command)
154
- name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
155
- # Some commands like EVALSHA have zero as the step in COMMANDS somehow.
156
- @commands[name].key_step == 0 ? 1 : @commands[name].key_step
157
- end
158
110
  end
159
111
  end
160
112
  end
@@ -4,51 +4,63 @@ require 'redis_client'
4
4
 
5
5
  class RedisClient
6
6
  class Cluster
7
+ class Error < ::RedisClient::Error
8
+ def with_config(config)
9
+ @config = config
10
+ self
11
+ end
12
+ end
13
+
7
14
  ERR_ARG_NORMALIZATION = ->(arg) { Array[arg].flatten.reject { |e| e.nil? || (e.respond_to?(:empty?) && e.empty?) } }
8
15
 
9
16
  private_constant :ERR_ARG_NORMALIZATION
10
17
 
11
- class InitialSetupError < ::RedisClient::Error
12
- def initialize(errors)
18
+ class InitialSetupError < Error
19
+ def self.from_errors(errors)
13
20
  msg = ERR_ARG_NORMALIZATION.call(errors).map(&:message).uniq.join(',')
14
- super("Redis client could not fetch cluster information: #{msg}")
21
+ new("Redis client could not fetch cluster information: #{msg}")
15
22
  end
16
23
  end
17
24
 
18
- class OrchestrationCommandNotSupported < ::RedisClient::Error
19
- def initialize(command)
25
+ class OrchestrationCommandNotSupported < Error
26
+ def self.from_command(command)
20
27
  str = ERR_ARG_NORMALIZATION.call(command).map(&:to_s).join(' ').upcase
21
28
  msg = "#{str} command should be used with care " \
22
29
  'only by applications orchestrating Redis Cluster, like redis-cli, ' \
23
30
  'and the command if used out of the right context can leave the cluster ' \
24
31
  'in a wrong state or cause data loss.'
25
- super(msg)
32
+ new(msg)
26
33
  end
27
34
  end
28
35
 
29
- class ErrorCollection < ::RedisClient::Error
36
+ class ErrorCollection < Error
37
+ EMPTY_HASH = {}.freeze
38
+
39
+ private_constant :EMPTY_HASH
30
40
  attr_reader :errors
31
41
 
32
- def initialize(errors)
33
- @errors = {}
42
+ def self.with_errors(errors)
34
43
  if !errors.is_a?(Hash) || errors.empty?
35
- super(errors.to_s)
36
- return
44
+ new(errors.to_s).with_errors(EMPTY_HASH)
45
+ else
46
+ messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
47
+ new(messages.join(', ')).with_errors(errors)
37
48
  end
49
+ end
38
50
 
39
- @errors = errors
40
- messages = @errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
41
- super(messages.join(', '))
51
+ def with_errors(errors)
52
+ @errors = errors if @errors.nil?
53
+ self
42
54
  end
43
55
  end
44
56
 
45
- class AmbiguousNodeError < ::RedisClient::Error
46
- def initialize(command)
47
- super("Cluster client doesn't know which node the #{command} command should be sent to.")
57
+ class AmbiguousNodeError < Error
58
+ def self.from_command(command)
59
+ new("Cluster client doesn't know which node the #{command} command should be sent to.")
48
60
  end
49
61
  end
50
62
 
51
- class NodeMightBeDown < ::RedisClient::Error
63
+ class NodeMightBeDown < Error
52
64
  def initialize(_ = '')
53
65
  super(
54
66
  'The client is trying to fetch the latest cluster state ' \
@@ -28,7 +28,7 @@ class RedisClient
28
28
  private_constant :USE_CHAR_ARRAY_SLOT, :SLOT_SIZE, :MIN_SLOT, :MAX_SLOT,
29
29
  :DEAD_FLAGS, :ROLE_FLAGS, :EMPTY_ARRAY, :EMPTY_HASH
30
30
 
31
- ReloadNeeded = Class.new(::RedisClient::Error)
31
+ ReloadNeeded = Class.new(::RedisClient::Cluster::Error)
32
32
 
33
33
  Info = Struct.new(
34
34
  'RedisClusterNode',
@@ -148,7 +148,7 @@ class RedisClient
148
148
 
149
149
  raise ReloadNeeded if errors.values.any?(::RedisClient::ConnectionError)
150
150
 
151
- raise ::RedisClient::Cluster::ErrorCollection, errors
151
+ raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
152
152
  end
153
153
 
154
154
  def clients_for_scanning(seed: nil)
@@ -267,7 +267,7 @@ class RedisClient
267
267
  result_values, errors = call_multiple_nodes(clients, method, command, args, &block)
268
268
  return result_values if errors.nil? || errors.empty?
269
269
 
270
- raise ::RedisClient::Cluster::ErrorCollection, errors
270
+ raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
271
271
  end
272
272
 
273
273
  def try_map(clients, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
@@ -334,7 +334,7 @@ class RedisClient
334
334
 
335
335
  work_group.close
336
336
 
337
- raise ::RedisClient::Cluster::InitialSetupError, errors if node_info_list.nil?
337
+ raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors) if node_info_list.nil?
338
338
 
339
339
  grouped = node_info_list.compact.group_by do |info_list|
340
340
  info_list.sort_by!(&:id)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisClient
4
+ class Cluster
5
+ module NoopCommandBuilder
6
+ module_function
7
+
8
+ def generate(args, _kwargs = nil)
9
+ args
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'redis_client'
4
4
  require 'redis_client/cluster/errors'
5
+ require 'redis_client/cluster/noop_command_builder'
5
6
  require 'redis_client/connection_mixin'
6
7
  require 'redis_client/middlewares'
7
8
  require 'redis_client/pooled'
@@ -108,13 +109,13 @@ class RedisClient
108
109
  end
109
110
  end
110
111
 
111
- ReplySizeError = Class.new(::RedisClient::Error)
112
+ ReplySizeError = Class.new(::RedisClient::Cluster::Error)
112
113
 
113
- class StaleClusterState < ::RedisClient::Error
114
+ class StaleClusterState < ::RedisClient::Cluster::Error
114
115
  attr_accessor :replies, :first_exception
115
116
  end
116
117
 
117
- class RedirectionNeeded < ::RedisClient::Error
118
+ class RedirectionNeeded < ::RedisClient::Cluster::Error
118
119
  attr_accessor :replies, :indices, :first_exception
119
120
  end
120
121
 
@@ -204,7 +205,7 @@ class RedisClient
204
205
 
205
206
  work_group.close
206
207
  @router.renew_cluster_state if cluster_state_errors
207
- raise ::RedisClient::Cluster::ErrorCollection, errors unless errors.nil?
208
+ raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors).with_config(@router.config) unless errors.nil?
208
209
 
209
210
  required_redirections&.each do |node_key, v|
210
211
  raise v.first_exception if v.first_exception
@@ -229,7 +230,7 @@ class RedisClient
229
230
 
230
231
  def append_pipeline(node_key)
231
232
  @pipelines ||= {}
232
- @pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(@command_builder)
233
+ @pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(::RedisClient::Cluster::NoopCommandBuilder)
233
234
  @pipelines[node_key].add_outer_index(@size)
234
235
  @size += 1
235
236
  @pipelines[node_key]
@@ -21,10 +21,10 @@ class RedisClient
21
21
 
22
22
  private_constant :ZERO_CURSOR_FOR_SCAN, :TSF
23
23
 
24
+ attr_reader :config
25
+
24
26
  def initialize(config, concurrent_worker, pool: nil, **kwargs)
25
- @config = config.dup
26
- @original_config = config.dup if config.connect_with_original_config
27
- @connect_with_original_config = config.connect_with_original_config
27
+ @config = config
28
28
  @concurrent_worker = concurrent_worker
29
29
  @pool = pool
30
30
  @client_kwargs = kwargs
@@ -32,6 +32,9 @@ class RedisClient
32
32
  @node.reload!
33
33
  @command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
34
34
  @command_builder = @config.command_builder
35
+ rescue ::RedisClient::Cluster::InitialSetupError => e
36
+ e.with_config(config)
37
+ raise
35
38
  end
36
39
 
37
40
  def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -58,9 +61,9 @@ class RedisClient
58
61
  when 'flushall', 'flushdb'
59
62
  @node.call_primaries(method, command, args).first.then(&TSF.call(block))
60
63
  when 'readonly', 'readwrite', 'shutdown'
61
- raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
64
+ raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(cmd).with_config(@config)
62
65
  when 'discard', 'exec', 'multi', 'unwatch'
63
- raise ::RedisClient::Cluster::AmbiguousNodeError, cmd
66
+ raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(cmd).with_config(@config)
64
67
  else
65
68
  node = assign_node(command)
66
69
  try_send(node, method, command, args, &block)
@@ -69,7 +72,7 @@ class RedisClient
69
72
  raise
70
73
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
71
74
  renew_cluster_state
72
- raise ::RedisClient::Cluster::NodeMightBeDown
75
+ raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
73
76
  rescue ::RedisClient::ConnectionError
74
77
  renew_cluster_state
75
78
  raise
@@ -77,6 +80,7 @@ class RedisClient
77
80
  renew_cluster_state if e.message.start_with?('CLUSTERDOWN')
78
81
  raise
79
82
  rescue ::RedisClient::Cluster::ErrorCollection => e
83
+ e.with_config(@config)
80
84
  raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
81
85
 
82
86
  renew_cluster_state if e.errors.values.any? do |err|
@@ -100,12 +104,6 @@ class RedisClient
100
104
  end
101
105
  end
102
106
 
103
- def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block)
104
- handle_redirection(node, nil, retry_count: retry_count) do |on_node|
105
- on_node.public_send(method, *args, **kwargs, &block)
106
- end
107
- end
108
-
109
107
  def handle_redirection(node, command, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
110
108
  yield node
111
109
  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
@@ -149,9 +147,7 @@ class RedisClient
149
147
  raise
150
148
  end
151
149
 
152
- def scan(*command, seed: nil, **kwargs) # rubocop:disable Metrics/AbcSize
153
- command = @command_builder.generate(command, kwargs)
154
-
150
+ def scan(command, seed: nil) # rubocop:disable Metrics/AbcSize
155
151
  command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
156
152
  input_cursor = Integer(command[1])
157
153
 
@@ -176,6 +172,16 @@ class RedisClient
176
172
  raise
177
173
  end
178
174
 
175
+ def scan_single_key(command, arity:, &block)
176
+ node = assign_node(command)
177
+ loop do
178
+ cursor, values = handle_redirection(node, nil, retry_count: 3) { |n| n.call_v(command) }
179
+ command[2] = cursor
180
+ arity < 2 ? values.each(&block) : values.each_slice(arity, &block)
181
+ break if cursor == ZERO_CURSOR_FOR_SCAN
182
+ end
183
+ end
184
+
179
185
  def assign_node(command)
180
186
  handle_node_reload_error do
181
187
  node_key = find_node_key(command)
@@ -189,7 +195,7 @@ class RedisClient
189
195
  node_key = primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
190
196
  if node_key.nil?
191
197
  renew_cluster_state
192
- raise ::RedisClient::Cluster::NodeMightBeDown
198
+ raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
193
199
  end
194
200
  node_key
195
201
  else
@@ -303,7 +309,7 @@ class RedisClient
303
309
  case subcommand = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
304
310
  when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
305
311
  'reset', 'set-config-epoch', 'setslot'
306
- raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, ['cluster', subcommand]
312
+ raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', subcommand]).with_config(@config)
307
313
  when 'saveconfig' then @node.call_all(method, command, args).first.then(&TSF.call(block))
308
314
  when 'getkeysinslot'
309
315
  raise ArgumentError, command.join(' ') if command.size != 4
@@ -347,7 +353,10 @@ class RedisClient
347
353
  end
348
354
 
349
355
  def send_watch_command(command)
350
- raise ::RedisClient::Cluster::Transaction::ConsistencyError, 'A block required. And you need to use the block argument as a client for the transaction.' unless block_given?
356
+ unless block_given?
357
+ msg = 'A block required. And you need to use the block argument as a client for the transaction.'
358
+ raise ::RedisClient::Cluster::Transaction::ConsistencyError.new(msg).with_config(@config)
359
+ end
351
360
 
352
361
  ::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
353
362
  transaction = ::RedisClient::Cluster::Transaction.new(
@@ -358,17 +367,20 @@ class RedisClient
358
367
  end
359
368
  end
360
369
 
361
- MULTIPLE_KEYS_COMMAND_TO_SINGLE = {
362
- 'mget' => ['get', 1].freeze,
363
- 'mset' => ['set', 2].freeze,
364
- 'del' => ['del', 1].freeze
365
- }.freeze
366
-
367
- private_constant :MULTIPLE_KEYS_COMMAND_TO_SINGLE
368
-
369
370
  def send_multiple_keys_command(cmd, method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
370
371
  # This implementation is prioritized performance rather than readability or so.
371
- single_key_cmd, keys_step = MULTIPLE_KEYS_COMMAND_TO_SINGLE.fetch(cmd)
372
+ case cmd
373
+ when 'mget'
374
+ single_key_cmd = 'get'
375
+ keys_step = 1
376
+ when 'mset'
377
+ single_key_cmd = 'set'
378
+ keys_step = 2
379
+ when 'del'
380
+ single_key_cmd = 'del'
381
+ keys_step = 1
382
+ else raise NotImplementedError, cmd
383
+ end
372
384
 
373
385
  return try_send(assign_node(command), method, command, args, &block) if command.size <= keys_step + 1 || ::RedisClient::Cluster::KeySlotConverter.hash_tag_included?(command[1])
374
386
 
@@ -401,7 +413,7 @@ class RedisClient
401
413
  def handle_node_reload_error(retry_count: 1)
402
414
  yield
403
415
  rescue ::RedisClient::Cluster::Node::ReloadNeeded
404
- raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
416
+ raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config) if retry_count <= 0
405
417
 
406
418
  retry_count -= 1
407
419
  renew_cluster_state
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'redis_client'
4
+ require 'redis_client/cluster/errors'
5
+ require 'redis_client/cluster/noop_command_builder'
4
6
  require 'redis_client/cluster/pipeline'
5
7
 
6
8
  class RedisClient
7
9
  class Cluster
8
10
  class Transaction
9
- ConsistencyError = Class.new(::RedisClient::Error)
11
+ ConsistencyError = Class.new(::RedisClient::Cluster::Error)
10
12
 
11
13
  MAX_REDIRECTION = 2
12
14
  EMPTY_ARRAY = [].freeze
@@ -17,7 +19,7 @@ class RedisClient
17
19
  @router = router
18
20
  @command_builder = command_builder
19
21
  @retryable = true
20
- @pipeline = ::RedisClient::Pipeline.new(@command_builder)
22
+ @pipeline = ::RedisClient::Pipeline.new(::RedisClient::Cluster::NoopCommandBuilder)
21
23
  @pending_commands = []
22
24
  @node = node
23
25
  prepare_tx unless @node.nil?
@@ -67,7 +69,7 @@ class RedisClient
67
69
  @pending_commands.each(&:call)
68
70
 
69
71
  return EMPTY_ARRAY if @pipeline._empty?
70
- raise ConsistencyError, "couldn't determine the node: #{@pipeline._commands}" if @node.nil?
72
+ raise ConsistencyError.new("couldn't determine the node: #{@pipeline._commands}").with_config(@router.config) if @node.nil?
71
73
 
72
74
  commit
73
75
  end
@@ -163,7 +165,7 @@ class RedisClient
163
165
 
164
166
  def handle_command_error!(err, redirect:) # rubocop:disable Metrics/AbcSize
165
167
  if err.message.start_with?('CROSSSLOT')
166
- raise ConsistencyError, "#{err.message}: #{err.command}"
168
+ raise ConsistencyError.new("#{err.message}: #{err.command}").with_config(@router.config)
167
169
  elsif err.message.start_with?('MOVED')
168
170
  node = @router.assign_redirection_node(err.message)
169
171
  send_transaction(node, redirect: redirect - 1)
@@ -183,7 +185,7 @@ class RedisClient
183
185
  return if slots.size == 1 && @watching_slot.nil?
184
186
  return if slots.size == 1 && @watching_slot == slots.first
185
187
 
186
- raise(ConsistencyError, "the transaction should be executed to a slot in a node: #{commands}")
188
+ raise ConsistencyError.new("the transaction should be executed to a slot in a node: #{commands}").with_config(@router.config)
187
189
  end
188
190
 
189
191
  def try_asking(node)
@@ -62,30 +62,37 @@ class RedisClient
62
62
  end
63
63
 
64
64
  def scan(*args, **kwargs, &block)
65
- raise ArgumentError, 'block required' unless block
65
+ return to_enum(__callee__, *args, **kwargs) unless block_given?
66
66
 
67
+ command = @command_builder.generate(['SCAN', ZERO_CURSOR_FOR_SCAN] + args, kwargs)
67
68
  seed = Random.new_seed
68
- cursor = ZERO_CURSOR_FOR_SCAN
69
69
  loop do
70
- cursor, keys = router.scan('SCAN', cursor, *args, seed: seed, **kwargs)
70
+ cursor, keys = router.scan(command, seed: seed)
71
+ command[1] = cursor
71
72
  keys.each(&block)
72
73
  break if cursor == ZERO_CURSOR_FOR_SCAN
73
74
  end
74
75
  end
75
76
 
76
77
  def sscan(key, *args, **kwargs, &block)
77
- node = router.assign_node(['SSCAN', key])
78
- router.try_delegate(node, :sscan, key, *args, **kwargs, &block)
78
+ return to_enum(__callee__, key, *args, **kwargs) unless block_given?
79
+
80
+ command = @command_builder.generate(['SSCAN', key, ZERO_CURSOR_FOR_SCAN] + args, kwargs)
81
+ router.scan_single_key(command, arity: 1, &block)
79
82
  end
80
83
 
81
84
  def hscan(key, *args, **kwargs, &block)
82
- node = router.assign_node(['HSCAN', key])
83
- router.try_delegate(node, :hscan, key, *args, **kwargs, &block)
85
+ return to_enum(__callee__, key, *args, **kwargs) unless block_given?
86
+
87
+ command = @command_builder.generate(['HSCAN', key, ZERO_CURSOR_FOR_SCAN] + args, kwargs)
88
+ router.scan_single_key(command, arity: 2, &block)
84
89
  end
85
90
 
86
91
  def zscan(key, *args, **kwargs, &block)
87
- node = router.assign_node(['ZSCAN', key])
88
- router.try_delegate(node, :zscan, key, *args, **kwargs, &block)
92
+ return to_enum(__callee__, key, *args, **kwargs) unless block_given?
93
+
94
+ command = @command_builder.generate(['ZSCAN', key, ZERO_CURSOR_FOR_SCAN] + args, kwargs)
95
+ router.scan_single_key(command, arity: 2, &block)
89
96
  end
90
97
 
91
98
  def pipelined(exception: true)
@@ -3,7 +3,9 @@
3
3
  require 'uri'
4
4
  require 'redis_client'
5
5
  require 'redis_client/cluster'
6
+ require 'redis_client/cluster/errors'
6
7
  require 'redis_client/cluster/node_key'
8
+ require 'redis_client/cluster/noop_command_builder'
7
9
  require 'redis_client/command_builder'
8
10
 
9
11
  class RedisClient
@@ -27,10 +29,10 @@ class RedisClient
27
29
  :VALID_SCHEMES, :VALID_NODES_KEYS, :MERGE_CONFIG_KEYS, :IGNORE_GENERIC_CONFIG_KEYS,
28
30
  :MAX_WORKERS, :SLOW_COMMAND_TIMEOUT, :MAX_STARTUP_SAMPLE
29
31
 
30
- InvalidClientConfigError = Class.new(::RedisClient::Error)
32
+ InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
31
33
 
32
34
  attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
33
- :connect_with_original_config, :startup_nodes, :max_startup_sample
35
+ :connect_with_original_config, :startup_nodes, :max_startup_sample, :id
34
36
 
35
37
  def initialize( # rubocop:disable Metrics/ParameterLists
36
38
  nodes: DEFAULT_NODES,
@@ -59,10 +61,11 @@ class RedisClient
59
61
  @client_implementation = client_implementation
60
62
  @slow_command_timeout = slow_command_timeout
61
63
  @max_startup_sample = max_startup_sample
64
+ @id = client_config[:id]
62
65
  end
63
66
 
64
67
  def inspect
65
- "#<#{self.class.name} #{startup_nodes.values}>"
68
+ "#<#{self.class.name} #{startup_nodes.values.map { |v| v.reject { |k| k == :command_builder } }}>"
66
69
  end
67
70
 
68
71
  def read_timeout
@@ -92,6 +95,18 @@ class RedisClient
92
95
  augment_client_config(config)
93
96
  end
94
97
 
98
+ def resolved?
99
+ true
100
+ end
101
+
102
+ def sentinel?
103
+ false
104
+ end
105
+
106
+ def server_url
107
+ nil
108
+ end
109
+
95
110
  private
96
111
 
97
112
  def merge_concurrency_option(option)
@@ -173,6 +188,7 @@ class RedisClient
173
188
  def augment_client_config(config)
174
189
  config = @client_config.merge(config)
175
190
  config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
191
+ config[:command_builder] = ::RedisClient::Cluster::NoopCommandBuilder # prevent twice call
176
192
  config
177
193
  end
178
194
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.6
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-17 00:00:00.000000000 Z
11
+ date: 2024-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -48,6 +48,7 @@ files:
48
48
  - lib/redis_client/cluster/node/random_replica.rb
49
49
  - lib/redis_client/cluster/node/random_replica_or_primary.rb
50
50
  - lib/redis_client/cluster/node_key.rb
51
+ - lib/redis_client/cluster/noop_command_builder.rb
51
52
  - lib/redis_client/cluster/normalized_cmd_name.rb
52
53
  - lib/redis_client/cluster/optimistic_locking.rb
53
54
  - lib/redis_client/cluster/pipeline.rb
@@ -77,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
78
  - !ruby/object:Gem::Version
78
79
  version: '0'
79
80
  requirements: []
80
- rubygems_version: 3.5.16
81
+ rubygems_version: 3.5.22
81
82
  signing_key:
82
83
  specification_version: 4
83
84
  summary: A Redis cluster client for Ruby