redis-cluster-client 0.11.6 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
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