switchman 3.2.1 → 3.3.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: 20c8d947a29c73c09af7d1c880ef03aff2185c927d30954bb304b4592a7b4051
4
- data.tar.gz: 5973474196cc2c8f2957932a65261998de24a6be3653cedeff374c1fe1fa925f
3
+ metadata.gz: d0330d210ee4f44c8ceb3b66ba1536f5538c4a1efcd4a939d492ffa65602fca7
4
+ data.tar.gz: '0787f10b30acc34e0a5e48423c524d2db27b8b92dabd76bf567fb6631c0587c3'
5
5
  SHA512:
6
- metadata.gz: 10f59abb9c44a3b6e8ec3e87419862f5959ec8086117172d6fc044911c0efb3d96ec320fc04630e482200fbf99da4a7a8d7a291137bbf9ce019f1e7a54f5eeb0
7
- data.tar.gz: 72fc286e4f143bf9d8344aa6e4a0405161e476b23eada4fd956f2657a1911c4e93acad649e76cb17e444821060085110d12c2cfe0a5f115bf9b7ddc598fb17ea
6
+ metadata.gz: 65e0cbb8c9015940209145aa229648fa763d999399053e9046b0b4a60e1aae1ebfe419983776364e7e2460ca2e0cc0021188d286a8da6a9d9c0a74bd59d1bbc2
7
+ data.tar.gz: db938a240af2ffb146453536d42db25d38ac13ca136b757d219fef46cf99476c0916beb65c8e06e2bc48067b94736d8f88b4c13519f85d99ec2b07fc423eccc9
@@ -12,8 +12,6 @@ module Switchman
12
12
  # :explicit - explicit set on the relation
13
13
  # :association - a special value that scopes from associations use to use slightly different logic
14
14
  # for foreign key transposition
15
- # :to_a - a special value that Relation#to_a uses when querying multiple shards to
16
- # remove primary keys from conditions that aren't applicable to the current shard
17
15
  def shard_value
18
16
  @values[:shard]
19
17
  end
@@ -44,10 +42,7 @@ module Switchman
44
42
  old_primary_shard = primary_shard
45
43
  self.shard_value = value
46
44
  self.shard_source_value = source
47
- if old_primary_shard != primary_shard || source == :to_a
48
- transpose_clauses(old_primary_shard, primary_shard,
49
- remove_nonlocal_primary_keys: source == :to_a)
50
- end
45
+ transpose_predicates(nil, old_primary_shard, primary_shard) if old_primary_shard != primary_shard
51
46
  self
52
47
  end
53
48
 
@@ -89,29 +84,21 @@ module Switchman
89
84
  super(other.shard(primary_shard))
90
85
  end
91
86
 
92
- private
87
+ protected
93
88
 
94
- %i[where having].each do |type|
95
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
96
- def transpose_#{type}_clauses(source_shard, target_shard, remove_nonlocal_primary_keys:)
97
- unless (predicates = #{type}_clause.send(:predicates)).empty?
98
- new_predicates = transpose_predicates(predicates, source_shard,
99
- target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
100
- if new_predicates != predicates
101
- self.#{type}_clause = #{type}_clause.dup
102
- if new_predicates != predicates
103
- #{type}_clause.instance_variable_set(:@predicates, new_predicates)
104
- end
105
- end
106
- end
89
+ def remove_nonlocal_primary_keys!
90
+ each_transposable_predicate_value do |value, predicate, _relation, _column, type|
91
+ next value unless
92
+ type == :primary &&
93
+ predicate.left.relation.klass == klass &&
94
+ (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
95
+
96
+ value.is_a?(Integer) && value > Shard::IDS_PER_SHARD ? [] : value
107
97
  end
108
- RUBY
98
+ self
109
99
  end
110
100
 
111
- def transpose_clauses(source_shard, target_shard, remove_nonlocal_primary_keys: false)
112
- transpose_where_clauses(source_shard, target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
113
- transpose_having_clauses(source_shard, target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
114
- end
101
+ private
115
102
 
116
103
  def infer_shards_from_primary_key(predicates)
117
104
  return unless klass.integral_id?
@@ -145,7 +132,7 @@ module Switchman
145
132
  return
146
133
  else
147
134
  id_shards = id_shards.to_a
148
- transpose_clauses(primary_shard, id_shards.first)
135
+ transpose_predicates(nil, primary_shard, id_shards.first)
149
136
  self.shard_value = id_shards
150
137
  return
151
138
  end
@@ -162,7 +149,7 @@ module Switchman
162
149
 
163
150
  return if !id_shard || id_shard == primary_shard
164
151
 
165
- transpose_clauses(primary_shard, id_shard)
152
+ transpose_predicates(nil, primary_shard, id_shard)
166
153
  self.shard_value = id_shard
167
154
  end
168
155
 
@@ -246,122 +233,129 @@ module Switchman
246
233
  connection.with_global_table_name { super }
247
234
  end
248
235
 
249
- def transpose_predicates(predicates,
250
- source_shard,
251
- target_shard,
252
- remove_nonlocal_primary_keys: false)
253
- predicates.map do |predicate|
254
- transpose_single_predicate(predicate, source_shard, target_shard,
255
- remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
256
- end
236
+ def each_predicate(predicates = nil, &block)
237
+ return predicates.map(&block) if predicates
238
+
239
+ each_predicate_cb(:having_clause, :having_clause=, &block)
240
+ each_predicate_cb(:where_clause, :where_clause=, &block)
257
241
  end
258
242
 
259
- def transpose_single_predicate(predicate,
260
- source_shard,
261
- target_shard,
262
- remove_nonlocal_primary_keys: false)
263
- if predicate.is_a?(::Arel::Nodes::Grouping)
264
- return predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
265
-
266
- # Dang, we have an OR. OK, that means we have other epxressions below this
267
- # level, perhaps many, that may need transposition.
268
- # the left side and right side must each be treated as predicate lists and
269
- # transformed in kind, if neither of them changes we can just return the grouping as is.
270
- # hold on, it's about to get recursive...
271
- or_expr = predicate.expr
272
- left_node = or_expr.left
273
- right_node = or_expr.right
274
- new_left_predicates = transpose_single_predicate(left_node, source_shard,
275
- target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
276
- new_right_predicates = transpose_single_predicate(right_node, source_shard,
277
- target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
278
- return predicate if new_left_predicates == left_node && new_right_predicates == right_node
279
-
280
- return ::Arel::Nodes::Grouping.new ::Arel::Nodes::Or.new(new_left_predicates, new_right_predicates)
281
- end
282
- return predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
283
- return predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
284
-
285
- relation, column = relation_and_column(predicate.left)
286
- return predicate unless (type = transposable_attribute_type(relation, column))
287
-
288
- remove = true if type == :primary &&
289
- remove_nonlocal_primary_keys &&
290
- predicate.left.relation.klass == klass &&
291
- (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
292
-
293
- current_source_shard =
294
- if source_shard
295
- source_shard
296
- elsif type == :primary
297
- Shard.current(klass.connection_class_for_self)
298
- elsif type == :foreign
299
- source_shard_for_foreign_key(relation, column)
300
- end
243
+ def each_predicate_cb(clause_getter, clause_setter, &block)
244
+ old_clause = send(clause_getter)
245
+ old_predicates = old_clause.send(:predicates)
246
+ return if old_predicates.empty?
301
247
 
302
- right = if predicate.is_a?(::Arel::Nodes::HomogeneousIn)
303
- predicate.values
304
- else
305
- predicate.right
306
- end
248
+ new_predicates = old_predicates.map(&block)
249
+ return if new_predicates == old_predicates
307
250
 
308
- new_right_value =
309
- case right
310
- when Array
311
- right.map { |val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
312
- else
313
- transpose_predicate_value(right, current_source_shard, target_shard, type, remove)
251
+ new_clause = old_clause.dup
252
+ new_clause.instance_variable_set(:@predicates, new_predicates)
253
+
254
+ send(clause_setter, new_clause)
255
+ end
256
+
257
+ def each_transposable_predicate(predicates = nil, &block)
258
+ each_predicate(predicates) do |predicate|
259
+ if predicate.is_a?(::Arel::Nodes::Grouping)
260
+ next predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
261
+
262
+ or_expr = predicate.expr
263
+ old_left = or_expr.left
264
+ old_right = or_expr.right
265
+ new_left, new_right = each_transposable_predicate([old_left, old_right], &block)
266
+
267
+ next predicate if new_left == old_left && new_right == old_right
268
+
269
+ next predicate.class.new predicate.expr.class.new(new_left, new_right)
314
270
  end
315
271
 
316
- if new_right_value == right
317
- predicate
318
- elsif predicate.right.is_a?(::Arel::Nodes::Casted)
319
- if new_right_value == right.value
320
- predicate
321
- else
322
- predicate.class.new(predicate.left, right.class.new(new_right_value, right.attribute))
272
+ next predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
273
+ next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
274
+
275
+ relation, column = relation_and_column(predicate.left)
276
+ next predicate unless (type = transposable_attribute_type(relation, column))
277
+
278
+ yield(predicate, relation, column, type)
279
+ end
280
+ end
281
+
282
+ def each_transposable_predicate_value(predicates = nil)
283
+ each_transposable_predicate(predicates) do |predicate, relation, column, type|
284
+ each_transposable_predicate_value_cb(predicate) do |value|
285
+ yield(value, predicate, relation, column, type)
323
286
  end
324
- elsif predicate.is_a?(::Arel::Nodes::HomogeneousIn)
287
+ end
288
+ end
289
+
290
+ def each_transposable_predicate_value_cb(node, &block)
291
+ case node
292
+ when Array
293
+ node.map { |val| each_transposable_predicate_value_cb(val, &block).presence }.compact
294
+ when ::ActiveModel::Attribute
295
+ old_value = node.value_before_type_cast
296
+ new_value = each_transposable_predicate_value_cb(old_value, &block)
297
+
298
+ old_value == new_value ? node : node.class.new(node.name, new_value, node.type)
299
+ when ::Arel::Nodes::And
300
+ old_value = node.children
301
+ new_value = each_transposable_predicate_value_cb(old_value, &block)
302
+
303
+ old_value == new_value ? node : node.class.new(new_value)
304
+ when ::Arel::Nodes::BindParam
305
+ old_value = node.value
306
+ new_value = each_transposable_predicate_value_cb(old_value, &block)
307
+
308
+ old_value == new_value ? node : node.class.new(new_value)
309
+ when ::Arel::Nodes::Casted
310
+ old_value = node.value
311
+ new_value = each_transposable_predicate_value_cb(old_value, &block)
312
+
313
+ old_value == new_value ? node : node.class.new(new_value, node.attribute)
314
+ when ::Arel::Nodes::HomogeneousIn
315
+ old_value = node.values
316
+ new_value = each_transposable_predicate_value_cb(old_value, &block)
317
+
325
318
  # switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
326
- if new_right_value.empty?
327
- klass = predicate.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
328
- klass.new(predicate.attribute, new_right_value)
319
+ if new_value.empty?
320
+ klass = node.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
321
+ klass.new(node.attribute, new_value)
329
322
  else
330
- predicate.class.new(new_right_value, predicate.attribute, predicate.type)
323
+ old_value == new_value ? node : node.class.new(new_value, node.attribute, node.type)
331
324
  end
325
+ when ::Arel::Nodes::Binary
326
+ old_value = node.right
327
+ new_value = each_transposable_predicate_value_cb(old_value, &block)
328
+
329
+ old_value == new_value ? node : node.class.new(node.left, new_value)
332
330
  else
333
- predicate.class.new(predicate.left, new_right_value)
331
+ yield(node)
334
332
  end
335
333
  end
336
334
 
337
- def transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids)
338
- case value
339
- when ::Arel::Nodes::BindParam, ::ActiveModel::Attribute
340
- query_att = value.is_a?(::ActiveModel::Attribute) ? value : value.value
341
- current_id = query_att.value_before_type_cast
342
- if current_id.is_a?(::ActiveRecord::StatementCache::Substitute)
343
- current_id.sharded = true # mark for transposition later
344
- current_id.primary = true if attribute_type == :primary
345
- value
346
- else
347
- local_id = Shard.relative_id_for(current_id, current_shard, target_shard) || current_id
348
- local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
349
- if current_id == local_id
350
- # make a new bind param
351
- value
352
- else
353
- new_att = query_att.class.new(query_att.name, local_id, query_att.type)
354
- if value.is_a?(::ActiveModel::Attribute)
355
- new_att
356
- else
357
- ::Arel::Nodes::BindParam.new(new_att)
358
- end
335
+ def transpose_predicates(predicates,
336
+ source_shard,
337
+ target_shard)
338
+ each_transposable_predicate_value(predicates) do |value, _predicate, relation, column, type|
339
+ current_source_shard =
340
+ if source_shard
341
+ source_shard
342
+ elsif type == :primary
343
+ Shard.current(klass.connection_class_for_self)
344
+ elsif type == :foreign
345
+ source_shard_for_foreign_key(relation, column)
359
346
  end
360
- end
347
+
348
+ transpose_predicate_value(value, current_source_shard, target_shard, type)
349
+ end
350
+ end
351
+
352
+ def transpose_predicate_value(value, current_shard, target_shard, attribute_type)
353
+ if value.is_a?(::ActiveRecord::StatementCache::Substitute)
354
+ value.sharded = true # mark for transposition later
355
+ value.primary = true if attribute_type == :primary
356
+ value
361
357
  else
362
- local_id = Shard.relative_id_for(value, current_shard, target_shard) || value
363
- local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
364
- local_id
358
+ Shard.relative_id_for(value, current_shard, target_shard) || value
365
359
  end
366
360
  end
367
361
  end
@@ -58,9 +58,10 @@ module Switchman
58
58
  end
59
59
 
60
60
  %I[update_all delete_all].each do |method|
61
+ arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
61
62
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
62
- def #{method}(*args)
63
- result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
63
+ def #{method}(#{arg_params})
64
+ result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, #{arg_params}) }
64
65
  result = result.sum if result.is_a?(Array)
65
66
  result
66
67
  end
@@ -103,7 +104,9 @@ module Switchman
103
104
  def activate(unordered: false, &block)
104
105
  shards = all_shards
105
106
  if Array === shards && shards.length == 1
106
- if shards.first == DefaultShard || shards.first == Shard.current(klass.connection_class_for_self)
107
+ if !loaded? && shard_value != shards.first
108
+ shard(shards.first).activate(&block)
109
+ elsif shards.first == DefaultShard || shards.first == Shard.current(klass.connection_class_for_self)
107
110
  yield(self, shards.first)
108
111
  else
109
112
  shards.first.activate(klass.connection_class_for_self) { yield(self, shards.first) }
@@ -115,7 +118,8 @@ module Switchman
115
118
  # don't even query other shards if we're already past the limit
116
119
  next if limit_value && result_count >= limit_value && order_values.empty?
117
120
 
118
- relation = shard(Shard.current(klass.connection_class_for_self), :to_a)
121
+ relation = shard(Shard.current(klass.connection_class_for_self))
122
+ relation.remove_nonlocal_primary_keys!
119
123
  # do a minimal query if possible
120
124
  relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
121
125
 
@@ -12,8 +12,14 @@ module Switchman
12
12
  method.super_method
13
13
  end
14
14
 
15
- def call_super(method, above_module, *args, &block)
16
- super_method_above(method, above_module).call(*args, &block)
15
+ if RUBY_VERSION <= '2.8'
16
+ def call_super(method, above_module, *args, &block)
17
+ super_method_above(method, above_module).call(*args, &block)
18
+ end
19
+ else
20
+ def call_super(method, above_module, *args, **kwargs, &block)
21
+ super_method_above(method, above_module).call(*args, **kwargs, &block)
22
+ end
17
23
  end
18
24
  end
19
25
  end
@@ -13,8 +13,9 @@ module Switchman
13
13
  end
14
14
 
15
15
  %w[update_all delete_all].each do |method|
16
+ arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
16
17
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
17
- def #{method}(*args)
18
+ def #{method}(#{arg_params})
18
19
  db = Shard.current(connection_class_for_self).database_server
19
20
  db.unguard { super }
20
21
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = '3.2.1'
4
+ VERSION = '3.3.1'
5
5
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  - James Williams
9
9
  - Jacob Fugal
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-11-22 00:00:00.000000000 Z
13
+ date: 2022-12-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -300,7 +300,7 @@ licenses:
300
300
  - MIT
301
301
  metadata:
302
302
  rubygems_mfa_required: 'true'
303
- post_install_message:
303
+ post_install_message:
304
304
  rdoc_options: []
305
305
  require_paths:
306
306
  - lib
@@ -315,8 +315,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
315
315
  - !ruby/object:Gem::Version
316
316
  version: '0'
317
317
  requirements: []
318
- rubygems_version: 3.1.6
319
- signing_key:
318
+ rubygems_version: 3.3.7
319
+ signing_key:
320
320
  specification_version: 4
321
321
  summary: Rails sharding magic
322
322
  test_files: []