redis-cluster-client 0.12.0 → 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: 2465b865cd79c78cca3a1e7e3950989cce94f6675b0497763e03e261d3fa9c6f
4
- data.tar.gz: df607279a58171aca75835b767f3bf5965256db0fcb7b540bfcb3a8f302eb638
3
+ metadata.gz: 5d90c095f5058e808331eaa9219fb52b9d4a27de639a9c54d194c68ecb253611
4
+ data.tar.gz: 8cd8092db87c7cfa42e00c6cabd30747bdea74130d8eb1e19b971b95637665fa
5
5
  SHA512:
6
- metadata.gz: bfb9031660b642bb14768dc8edc66fb15073be9ca23ddc061d279a351c497a83ff8a3aad21903e2d0a37aa6824da35761188ba4dc1fe8ae0182b54584edaa5e5
7
- data.tar.gz: 9fab63d0595bfb7dac85a07c255a41573ab8d44598605919fd4182e99cf3da585bf931abe63da9e2f470815c6ef0d34ab24a5b7f2de346ba29870fb894c556ee
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?,
@@ -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
@@ -34,11 +34,14 @@ class RedisClient
34
34
  end
35
35
 
36
36
  class ErrorCollection < Error
37
+ EMPTY_HASH = {}.freeze
38
+
39
+ private_constant :EMPTY_HASH
37
40
  attr_reader :errors
38
41
 
39
42
  def self.with_errors(errors)
40
43
  if !errors.is_a?(Hash) || errors.empty?
41
- new(errors.to_s).with_errors({})
44
+ new(errors.to_s).with_errors(EMPTY_HASH)
42
45
  else
43
46
  messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
44
47
  new(messages.join(', ')).with_errors(errors)
@@ -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'
@@ -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]
@@ -104,12 +104,6 @@ class RedisClient
104
104
  end
105
105
  end
106
106
 
107
- def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block)
108
- handle_redirection(node, nil, retry_count: retry_count) do |on_node|
109
- on_node.public_send(method, *args, **kwargs, &block)
110
- end
111
- end
112
-
113
107
  def handle_redirection(node, command, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
114
108
  yield node
115
109
  rescue ::RedisClient::CircuitBreaker::OpenCircuitError
@@ -153,9 +147,7 @@ class RedisClient
153
147
  raise
154
148
  end
155
149
 
156
- def scan(*command, seed: nil, **kwargs) # rubocop:disable Metrics/AbcSize
157
- command = @command_builder.generate(command, kwargs)
158
-
150
+ def scan(command, seed: nil) # rubocop:disable Metrics/AbcSize
159
151
  command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
160
152
  input_cursor = Integer(command[1])
161
153
 
@@ -180,6 +172,16 @@ class RedisClient
180
172
  raise
181
173
  end
182
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
+
183
185
  def assign_node(command)
184
186
  handle_node_reload_error do
185
187
  node_key = find_node_key(command)
@@ -365,17 +367,20 @@ class RedisClient
365
367
  end
366
368
  end
367
369
 
368
- MULTIPLE_KEYS_COMMAND_TO_SINGLE = {
369
- 'mget' => ['get', 1].freeze,
370
- 'mset' => ['set', 2].freeze,
371
- 'del' => ['del', 1].freeze
372
- }.freeze
373
-
374
- private_constant :MULTIPLE_KEYS_COMMAND_TO_SINGLE
375
-
376
370
  def send_multiple_keys_command(cmd, method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
377
371
  # This implementation is prioritized performance rather than readability or so.
378
- 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
379
384
 
380
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])
381
386
 
@@ -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/cluster/pipeline'
6
7
 
7
8
  class RedisClient
@@ -18,7 +19,7 @@ class RedisClient
18
19
  @router = router
19
20
  @command_builder = command_builder
20
21
  @retryable = true
21
- @pipeline = ::RedisClient::Pipeline.new(@command_builder)
22
+ @pipeline = ::RedisClient::Pipeline.new(::RedisClient::Cluster::NoopCommandBuilder)
22
23
  @pending_commands = []
23
24
  @node = node
24
25
  prepare_tx unless @node.nil?
@@ -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)
@@ -5,6 +5,7 @@ require 'redis_client'
5
5
  require 'redis_client/cluster'
6
6
  require 'redis_client/cluster/errors'
7
7
  require 'redis_client/cluster/node_key'
8
+ require 'redis_client/cluster/noop_command_builder'
8
9
  require 'redis_client/command_builder'
9
10
 
10
11
  class RedisClient
@@ -64,7 +65,7 @@ class RedisClient
64
65
  end
65
66
 
66
67
  def inspect
67
- "#<#{self.class.name} #{startup_nodes.values}>"
68
+ "#<#{self.class.name} #{startup_nodes.values.map { |v| v.reject { |k| k == :command_builder } }}>"
68
69
  end
69
70
 
70
71
  def read_timeout
@@ -187,6 +188,7 @@ class RedisClient
187
188
  def augment_client_config(config)
188
189
  config = @client_config.merge(config)
189
190
  config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
191
+ config[:command_builder] = ::RedisClient::Cluster::NoopCommandBuilder # prevent twice call
190
192
  config
191
193
  end
192
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.12.0
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-11-14 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