redis-cluster-client 0.16.7 → 0.17.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7b3e4681d84fa76e82207734150b10bfe7963c07f99988919cb6381bcc1be26
4
- data.tar.gz: f00dfcd28b66eb101779396fc3237b385c03f1cd845bae82fd592ae34abe9746
3
+ metadata.gz: 7df1ba6006c9d3c3e7c242e9406f6443a95aa371c7e77244b73565058f476435
4
+ data.tar.gz: 5613bf92dc543482c1bcb2e74d61096092c299ef84877b8c06da1174db82c6d4
5
5
  SHA512:
6
- metadata.gz: 4a0b9abb4d20edda5d576350d47f47fb0c181bc706d473ba39caa3faf0eae296e453ad25ee42debd4475cee32012260b1cc9753a9b382afb56392678dd4b9bbf
7
- data.tar.gz: d272558440ca03e6473153cbfd7c39be0bcb0201c643801b4ff1950b0a6d2188d787e0a2c9e01efc596d82a088c2491e9a08e7efc9827ac9cf61c01152e8f878
6
+ metadata.gz: 58e9ecaa1aecf6bfb298cba78e08d349f742439a2f5a53e19cd55bb49c8a600b4e4f47a54af1798ef0afe51cb56cf5cf292eda3bdf75e522ed180fa065e37954
7
+ data.tar.gz: c158336f83f4453e27798a2521c4e282a867db446dfa7e74e988af9e568567235b321b59386bf79ac5d0e32dee0698137755e9c416ceeb3acb150ebfc60d5208
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'redis_client/cluster/errors'
4
+
5
+ class RedisClient
6
+ class Cluster
7
+ class Router
8
+ # The routing table for the commands which shouldn't be routed by their keys.
9
+ # The built-in entries can be overridden with the `command_routings` option of the config.
10
+ class RoutingTable
11
+ BuildError = Class.new(::RedisClient::Cluster::Error)
12
+ RoutingAction = Struct.new('RedisCommandRoutingAction', :method_name, :reply_transformer, keyword_init: true)
13
+ PICK_FIRST = ->(reply) { reply.first } # rubocop:disable Style/SymbolProc
14
+ FLATTEN_STRINGS = ->(reply) { reply.flatten.sort_by(&:to_s) }
15
+ SUM_NUM = ->(reply) { reply.select { |e| e.is_a?(Integer) }.sum }
16
+ SORT_NUMBERS = ->(reply) { reply.sort_by(&:to_i) }
17
+ if Object.const_defined?(:Ractor, false) && Ractor.respond_to?(:make_shareable)
18
+ Ractor.make_shareable(PICK_FIRST)
19
+ Ractor.make_shareable(FLATTEN_STRINGS)
20
+ Ractor.make_shareable(SUM_NUM)
21
+ Ractor.make_shareable(SORT_NUMBERS)
22
+ end
23
+ DEDICATED_ACTIONS = lambda do # rubocop:disable Metrics/BlockLength
24
+ multiple_key_action = RoutingAction.new(method_name: :send_multiple_keys_command)
25
+ all_node_first_action = RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: PICK_FIRST)
26
+ primary_first_action = RoutingAction.new(method_name: :send_command_to_primaries, reply_transformer: PICK_FIRST)
27
+ not_supported_action = RoutingAction.new(method_name: :fail_not_supported_command)
28
+ keyless_action = RoutingAction.new(method_name: :fail_keyless_command)
29
+ single_node_action = RoutingAction.new(method_name: :assign_node_and_send_command)
30
+ {
31
+ 'ping' => RoutingAction.new(method_name: :send_ping_command, reply_transformer: PICK_FIRST),
32
+ 'wait' => RoutingAction.new(method_name: :send_wait_command),
33
+ 'keys' => RoutingAction.new(method_name: :send_command_to_replicas, reply_transformer: FLATTEN_STRINGS),
34
+ 'dbsize' => RoutingAction.new(method_name: :send_command_to_replicas, reply_transformer: SUM_NUM),
35
+ 'scan' => RoutingAction.new(method_name: :send_scan_command),
36
+ 'lastsave' => RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: SORT_NUMBERS),
37
+ 'role' => RoutingAction.new(method_name: :send_command_to_all_nodes),
38
+ 'config' => RoutingAction.new(method_name: :send_config_command),
39
+ 'client' => RoutingAction.new(method_name: :send_client_command),
40
+ 'cluster' => RoutingAction.new(method_name: :send_cluster_command),
41
+ 'memory' => RoutingAction.new(method_name: :send_memory_command),
42
+ 'script' => RoutingAction.new(method_name: :send_script_command),
43
+ 'pubsub' => RoutingAction.new(method_name: :send_pubsub_command),
44
+ 'watch' => RoutingAction.new(method_name: :send_watch_command),
45
+ 'mget' => multiple_key_action,
46
+ 'mset' => multiple_key_action,
47
+ 'del' => multiple_key_action,
48
+ 'acl' => all_node_first_action,
49
+ 'auth' => all_node_first_action,
50
+ 'bgrewriteaof' => all_node_first_action,
51
+ 'bgsave' => all_node_first_action,
52
+ 'quit' => all_node_first_action,
53
+ 'save' => all_node_first_action,
54
+ 'select' => all_node_first_action,
55
+ 'flushall' => primary_first_action,
56
+ 'flushdb' => primary_first_action,
57
+ # The redis 7.0 tags RANDOMKEY with `request_policy:all_shards` but without any response policy.
58
+ # It was corrected to `response_policy:special` in the redis 7.2.
59
+ # This entry keeps the historical single node routing for the redis 7.0.
60
+ 'randomkey' => single_node_action,
61
+ 'readonly' => not_supported_action,
62
+ 'readwrite' => not_supported_action,
63
+ 'shutdown' => not_supported_action,
64
+ 'discard' => keyless_action,
65
+ 'exec' => keyless_action,
66
+ 'multi' => keyless_action,
67
+ 'unwatch' => keyless_action
68
+ }.each_with_object({}) do |(k, v), acc|
69
+ acc[k] = v.freeze
70
+ acc[k.upcase] = v.freeze
71
+ end
72
+ end.call.freeze
73
+
74
+ # The routing which the command tips of the COMMAND command reply instruct.
75
+ # The `multi_shard` and the `special` request policies are out of scope.
76
+ # The `special` response policy is also out of scope because the aggregation is undefined,
77
+ # and the fan-out would change the return value of commands such as INFO.
78
+ # The `agg_min`, `agg_max` and `agg_logical_*` response policies are out of scope too:
79
+ # the only reachable command with them today is WAITAOF, whose reply is an array,
80
+ # and the aggregation of array replies is undefined. Such a command falls back to
81
+ # the single node routing until a command with a settled semantics appears.
82
+ # The entries of the DEDICATED_ACTIONS take precedence over these to keep the existing behavior.
83
+ # @see https://redis.io/docs/latest/develop/reference/command-tips/
84
+ POLICY_ACTIONS = {
85
+ 'all_shards' => {
86
+ nil => RoutingAction.new(method_name: :send_command_to_primaries).freeze,
87
+ 'all_succeeded' => RoutingAction.new(method_name: :send_command_to_primaries, reply_transformer: PICK_FIRST).freeze,
88
+ 'one_succeeded' => RoutingAction.new(method_name: :send_command_to_primaries_leniently, reply_transformer: PICK_FIRST).freeze,
89
+ 'agg_sum' => RoutingAction.new(method_name: :send_command_to_primaries, reply_transformer: SUM_NUM).freeze
90
+ }.freeze,
91
+ 'all_nodes' => {
92
+ nil => RoutingAction.new(method_name: :send_command_to_all_nodes).freeze,
93
+ 'all_succeeded' => RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: PICK_FIRST).freeze,
94
+ 'one_succeeded' => RoutingAction.new(method_name: :send_command_to_all_nodes_leniently, reply_transformer: PICK_FIRST).freeze,
95
+ 'agg_sum' => RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: SUM_NUM).freeze
96
+ }.freeze
97
+ }.freeze
98
+
99
+ private_constant :RoutingAction, :PICK_FIRST, :FLATTEN_STRINGS, :SUM_NUM, :SORT_NUMBERS,
100
+ :DEDICATED_ACTIONS, :POLICY_ACTIONS
101
+
102
+ class << self
103
+ # Builds the routing table: the built-in entries overridden by the command routings
104
+ # which the config validated and normalized. A nil routing removes the built-in entry
105
+ # of the command, so that the command follows the default resolution: the command tips
106
+ # which the server reports, or the routing by its key.
107
+ # It raises a BuildError for an unnormalized input as a defense, e.g. an unsupported policy.
108
+ def build(routings)
109
+ return DEDICATED_ACTIONS if routings.nil? || routings.empty?
110
+
111
+ routings.each_with_object(DEDICATED_ACTIONS.dup) do |(key, policies), acc|
112
+ merge_action(acc, key, fetch_action(key, policies))
113
+ end.freeze
114
+ end
115
+
116
+ def find_policy_action(request_policy, response_policy)
117
+ POLICY_ACTIONS.dig(request_policy, response_policy)
118
+ end
119
+
120
+ private
121
+
122
+ # Stores both the lowercase and the uppercase keys to avoid a per-call case conversion.
123
+ def merge_action(table, key, action)
124
+ if action.nil?
125
+ table.delete(key)
126
+ table.delete(key.upcase)
127
+ else
128
+ table[key] = action
129
+ table[key.upcase] = action
130
+ end
131
+ end
132
+
133
+ def fetch_action(key, policies)
134
+ return if policies.nil?
135
+
136
+ action = POLICY_ACTIONS.dig(policies[:request_policy], policies[:response_policy])
137
+ return action unless action.nil?
138
+
139
+ raise BuildError, "the policies of the #{key} command are unsupported: #{policies.inspect}"
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -11,106 +11,20 @@ require 'redis_client/cluster/transaction'
11
11
  require 'redis_client/cluster/optimistic_locking'
12
12
  require 'redis_client/cluster/pipeline'
13
13
  require 'redis_client/cluster/error_identification'
14
+ require 'redis_client/cluster/router/routing_table'
14
15
 
15
16
  class RedisClient
16
17
  class Cluster
17
18
  class Router
18
19
  ZERO_CURSOR_FOR_SCAN = '0'
19
20
  TSF = ->(f, x) { f.nil? ? x : f.call(x) }.curry
20
- RoutingAction = Struct.new('RedisCommandRoutingAction', :method_name, :reply_transformer, keyword_init: true)
21
- PICK_FIRST = ->(reply) { reply.first } # rubocop:disable Style/SymbolProc
22
- FLATTEN_STRINGS = ->(reply) { reply.flatten.sort_by(&:to_s) }
23
- SUM_NUM = ->(reply) { reply.select { |e| e.is_a?(Integer) }.sum }
24
- SORT_NUMBERS = ->(reply) { reply.sort_by(&:to_i) }
25
- if Object.const_defined?(:Ractor, false) && Ractor.respond_to?(:make_shareable)
26
- Ractor.make_shareable(PICK_FIRST)
27
- Ractor.make_shareable(FLATTEN_STRINGS)
28
- Ractor.make_shareable(SUM_NUM)
29
- Ractor.make_shareable(SORT_NUMBERS)
30
- end
31
- DEDICATED_ACTIONS = lambda do # rubocop:disable Metrics/BlockLength
32
- multiple_key_action = RoutingAction.new(method_name: :send_multiple_keys_command)
33
- all_node_first_action = RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: PICK_FIRST)
34
- primary_first_action = RoutingAction.new(method_name: :send_command_to_primaries, reply_transformer: PICK_FIRST)
35
- not_supported_action = RoutingAction.new(method_name: :fail_not_supported_command)
36
- keyless_action = RoutingAction.new(method_name: :fail_keyless_command)
37
- single_node_action = RoutingAction.new(method_name: :assign_node_and_send_command)
38
- {
39
- 'ping' => RoutingAction.new(method_name: :send_ping_command, reply_transformer: PICK_FIRST),
40
- 'wait' => RoutingAction.new(method_name: :send_wait_command),
41
- 'keys' => RoutingAction.new(method_name: :send_command_to_replicas, reply_transformer: FLATTEN_STRINGS),
42
- 'dbsize' => RoutingAction.new(method_name: :send_command_to_replicas, reply_transformer: SUM_NUM),
43
- 'scan' => RoutingAction.new(method_name: :send_scan_command),
44
- 'lastsave' => RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: SORT_NUMBERS),
45
- 'role' => RoutingAction.new(method_name: :send_command_to_all_nodes),
46
- 'config' => RoutingAction.new(method_name: :send_config_command),
47
- 'client' => RoutingAction.new(method_name: :send_client_command),
48
- 'cluster' => RoutingAction.new(method_name: :send_cluster_command),
49
- 'memory' => RoutingAction.new(method_name: :send_memory_command),
50
- 'script' => RoutingAction.new(method_name: :send_script_command),
51
- 'pubsub' => RoutingAction.new(method_name: :send_pubsub_command),
52
- 'watch' => RoutingAction.new(method_name: :send_watch_command),
53
- 'mget' => multiple_key_action,
54
- 'mset' => multiple_key_action,
55
- 'del' => multiple_key_action,
56
- 'acl' => all_node_first_action,
57
- 'auth' => all_node_first_action,
58
- 'bgrewriteaof' => all_node_first_action,
59
- 'bgsave' => all_node_first_action,
60
- 'quit' => all_node_first_action,
61
- 'save' => all_node_first_action,
62
- 'select' => all_node_first_action,
63
- 'flushall' => primary_first_action,
64
- 'flushdb' => primary_first_action,
65
- # The redis 7.0 tags RANDOMKEY with `request_policy:all_shards` but without any response policy.
66
- # It was corrected to `response_policy:special` in the redis 7.2.
67
- # This entry keeps the historical single node routing for the redis 7.0.
68
- 'randomkey' => single_node_action,
69
- 'readonly' => not_supported_action,
70
- 'readwrite' => not_supported_action,
71
- 'shutdown' => not_supported_action,
72
- 'discard' => keyless_action,
73
- 'exec' => keyless_action,
74
- 'multi' => keyless_action,
75
- 'unwatch' => keyless_action
76
- }.each_with_object({}) do |(k, v), acc|
77
- acc[k] = v.freeze
78
- acc[k.upcase] = v.freeze
79
- end
80
- end.call.freeze
81
-
82
- # The routing which the command tips of the COMMAND command reply instruct.
83
- # The `multi_shard` and the `special` request policies are out of scope.
84
- # The `special` response policy is also out of scope because the aggregation is undefined,
85
- # and the fan-out would change the return value of commands such as INFO.
86
- # The `agg_min`, `agg_max` and `agg_logical_*` response policies are out of scope too:
87
- # the only reachable command with them today is WAITAOF, whose reply is an array,
88
- # and the aggregation of array replies is undefined. Such a command falls back to
89
- # the single node routing until a command with a settled semantics appears.
90
- # The entries of the DEDICATED_ACTIONS take precedence over these to keep the existing behavior.
91
- # @see https://redis.io/docs/latest/develop/reference/command-tips/
92
- POLICY_ACTIONS = {
93
- 'all_shards' => {
94
- nil => RoutingAction.new(method_name: :send_command_to_primaries).freeze,
95
- 'all_succeeded' => RoutingAction.new(method_name: :send_command_to_primaries, reply_transformer: PICK_FIRST).freeze,
96
- 'one_succeeded' => RoutingAction.new(method_name: :send_command_to_primaries_leniently, reply_transformer: PICK_FIRST).freeze,
97
- 'agg_sum' => RoutingAction.new(method_name: :send_command_to_primaries, reply_transformer: SUM_NUM).freeze
98
- }.freeze,
99
- 'all_nodes' => {
100
- nil => RoutingAction.new(method_name: :send_command_to_all_nodes).freeze,
101
- 'all_succeeded' => RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: PICK_FIRST).freeze,
102
- 'one_succeeded' => RoutingAction.new(method_name: :send_command_to_all_nodes_leniently, reply_transformer: PICK_FIRST).freeze,
103
- 'agg_sum' => RoutingAction.new(method_name: :send_command_to_all_nodes, reply_transformer: SUM_NUM).freeze
104
- }.freeze
105
- }.freeze
106
-
107
- private_constant :ZERO_CURSOR_FOR_SCAN, :TSF, :RoutingAction, :PICK_FIRST, :FLATTEN_STRINGS,
108
- :SUM_NUM, :SORT_NUMBERS, :DEDICATED_ACTIONS, :POLICY_ACTIONS
21
+ private_constant :ZERO_CURSOR_FOR_SCAN, :TSF
109
22
 
110
23
  attr_reader :config
111
24
 
112
25
  def initialize(config, concurrent_worker, pool: nil, **kwargs)
113
26
  @config = config
27
+ @dedicated_actions = RoutingTable.build(config.command_routings)
114
28
  @concurrent_worker = concurrent_worker
115
29
  @pool = pool
116
30
  @client_kwargs = kwargs
@@ -124,7 +38,7 @@ class RedisClient
124
38
  end
125
39
 
126
40
  def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
127
- action = DEDICATED_ACTIONS[command.first]
41
+ action = @dedicated_actions[command.first]
128
42
  if action.nil?
129
43
  cmd_spec = @command.get_spec(command)
130
44
  action = find_policy_action(cmd_spec)
@@ -350,7 +264,7 @@ class RedisClient
350
264
  request_policy = cmd_spec.request_policy
351
265
  return if request_policy.nil?
352
266
 
353
- POLICY_ACTIONS.dig(request_policy, cmd_spec.response_policy)
267
+ RoutingTable.find_policy_action(request_policy, cmd_spec.response_policy)
354
268
  end
355
269
 
356
270
  def send_command_to_all_nodes(method, command, args, &block)
@@ -6,6 +6,7 @@ require 'redis_client/cluster'
6
6
  require 'redis_client/cluster/errors'
7
7
  require 'redis_client/cluster/node_key'
8
8
  require 'redis_client/cluster/noop_command_builder'
9
+ require 'redis_client/cluster/router/routing_table'
9
10
  require 'redis_client/command_builder'
10
11
 
11
12
  class RedisClient
@@ -25,10 +26,18 @@ class RedisClient
25
26
  SLOW_COMMAND_TIMEOUT = Float(ENV.fetch('REDIS_CLIENT_SLOW_COMMAND_TIMEOUT', -1))
26
27
  # Controls the balance between startup load and stability during initialization or cluster state changes.
27
28
  MAX_STARTUP_SAMPLE = Integer(ENV.fetch('REDIS_CLIENT_MAX_STARTUP_SAMPLE', 3))
29
+ VALID_COMMAND_ROUTING_KEYS = %i[request_policy response_policy].freeze
30
+ # Routing these could wedge the state of the connections in the pool,
31
+ # e.g. leave them inside a MULTI or a subscription.
32
+ UNSAFE_COMMAND_ROUTINGS = %w[
33
+ multi exec discard watch unwatch
34
+ subscribe unsubscribe psubscribe punsubscribe ssubscribe sunsubscribe
35
+ ].freeze
28
36
 
29
37
  private_constant :DEFAULT_HOST, :DEFAULT_PORT, :DEFAULT_SCHEME, :SECURE_SCHEME, :DEFAULT_NODES,
30
38
  :VALID_SCHEMES, :VALID_NODES_KEYS, :MERGE_CONFIG_KEYS, :IGNORE_GENERIC_CONFIG_KEYS,
31
- :MAX_WORKERS, :SLOW_COMMAND_TIMEOUT, :MAX_STARTUP_SAMPLE
39
+ :MAX_WORKERS, :SLOW_COMMAND_TIMEOUT, :MAX_STARTUP_SAMPLE,
40
+ :VALID_COMMAND_ROUTING_KEYS, :UNSAFE_COMMAND_ROUTINGS
32
41
 
33
42
  InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
34
43
 
@@ -39,9 +48,9 @@ class RedisClient
39
48
  private_constant :SENSITIVE_INSPECT_KEYS, :INSPECT_REDACTED_KEYS, :INSPECT_PLACEHOLDER
40
49
 
41
50
  attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
42
- :connect_with_original_config, :startup_nodes, :max_startup_sample, :id
51
+ :connect_with_original_config, :startup_nodes, :max_startup_sample, :id, :command_routings
43
52
 
44
- def initialize( # rubocop:disable Metrics/ParameterLists
53
+ def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize
45
54
  nodes: DEFAULT_NODES,
46
55
  replica: false,
47
56
  replica_affinity: :random,
@@ -52,6 +61,7 @@ class RedisClient
52
61
  slow_command_timeout: SLOW_COMMAND_TIMEOUT,
53
62
  command_builder: ::RedisClient::CommandBuilder,
54
63
  max_startup_sample: MAX_STARTUP_SAMPLE,
64
+ command_routings: nil,
55
65
  **client_config
56
66
  )
57
67
  @replica = true & replica
@@ -67,6 +77,7 @@ class RedisClient
67
77
  @client_implementation = client_implementation
68
78
  @slow_command_timeout = slow_command_timeout
69
79
  @max_startup_sample = max_startup_sample
80
+ @command_routings = normalize_command_routings(command_routings)
70
81
  @id = client_config[:id]
71
82
  end
72
83
 
@@ -197,6 +208,52 @@ class RedisClient
197
208
  raise InvalidClientConfigError, e.message
198
209
  end
199
210
 
211
+ def normalize_command_routings(routings)
212
+ return if routings.nil? || (routings.is_a?(Hash) && routings.empty?)
213
+ raise InvalidClientConfigError, "`command_routings` option must be a Hash: #{routings.class}" unless routings.is_a?(Hash)
214
+
215
+ routings.each_with_object({}) do |(name, value), acc|
216
+ acc[normalize_command_routing_name(name)] = normalize_command_routing_value(name, value)
217
+ end.freeze
218
+ end
219
+
220
+ def normalize_command_routing_name(name)
221
+ raise InvalidClientConfigError, "`command_routings` option includes an invalid command name: #{name.inspect}" unless name.is_a?(String) || name.is_a?(Symbol)
222
+
223
+ key = name.to_s.downcase
224
+ raise InvalidClientConfigError, '`command_routings` option includes an empty command name' if key.empty?
225
+ raise InvalidClientConfigError, "`command_routings` option can route a command, not a subcommand: #{name.inspect}" if key.match?(/\s/)
226
+ raise InvalidClientConfigError, "`command_routings` option can't route the command which changes the state of a connection: #{key}" if UNSAFE_COMMAND_ROUTINGS.include?(key)
227
+
228
+ -key
229
+ end
230
+
231
+ def normalize_command_routing_value(name, value)
232
+ return if value.nil? || (value.is_a?(Hash) && value.empty?)
233
+ raise InvalidClientConfigError, "`command_routings` option includes an invalid routing of #{name}: #{value.inspect}" unless value.is_a?(Hash)
234
+
235
+ normalize_command_routing_policies(name, value.transform_keys { |k| k.respond_to?(:to_sym) ? k.to_sym : k })
236
+ end
237
+
238
+ def normalize_command_routing_policies(name, policies)
239
+ unknown_keys = policies.keys - VALID_COMMAND_ROUTING_KEYS
240
+ raise InvalidClientConfigError, "`command_routings` option includes unknown keys of #{name}: #{unknown_keys.join(', ')}" unless unknown_keys.empty?
241
+ raise InvalidClientConfigError, "`command_routings` option needs the request_policy key of #{name}" if policies[:request_policy].nil?
242
+
243
+ build_command_routing_policies(name, policies[:request_policy], policies[:response_policy])
244
+ end
245
+
246
+ def build_command_routing_policies(name, request_policy, response_policy)
247
+ request_policy = -request_policy.to_s
248
+ response_policy = response_policy.nil? ? nil : -response_policy.to_s
249
+ if ::RedisClient::Cluster::Router::RoutingTable.find_policy_action(request_policy, response_policy).nil?
250
+ raise InvalidClientConfigError,
251
+ "`command_routings` option includes unsupported policies of #{name}: request_policy=#{request_policy}, response_policy=#{response_policy.inspect}"
252
+ end
253
+
254
+ { request_policy: request_policy, response_policy: response_policy }.freeze
255
+ end
256
+
200
257
  def merge_generic_config(client_config, node_configs)
201
258
  cfg = node_configs.first || {}
202
259
  client_config.reject { |k, _| IGNORE_GENERIC_CONFIG_KEYS.include?(k) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-cluster-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.7
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taishi Kasuga
@@ -51,6 +51,7 @@ files:
51
51
  - lib/redis_client/cluster/pipeline.rb
52
52
  - lib/redis_client/cluster/pub_sub.rb
53
53
  - lib/redis_client/cluster/router.rb
54
+ - lib/redis_client/cluster/router/routing_table.rb
54
55
  - lib/redis_client/cluster/transaction.rb
55
56
  - lib/redis_client/cluster_config.rb
56
57
  - lib/redis_cluster_client.rb