switchman 3.2.1 → 3.3.0

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: 0730763611a9f47cf3b9a39d5e3a9969189067772c743bad82729c670d18e545
4
+ data.tar.gz: 4136ab04f9eac6c48ae4ad7a70b284845f095e3fc4153a8d5a5023f2bc5e312d
5
5
  SHA512:
6
- metadata.gz: 10f59abb9c44a3b6e8ec3e87419862f5959ec8086117172d6fc044911c0efb3d96ec320fc04630e482200fbf99da4a7a8d7a291137bbf9ce019f1e7a54f5eeb0
7
- data.tar.gz: 72fc286e4f143bf9d8344aa6e4a0405161e476b23eada4fd956f2657a1911c4e93acad649e76cb17e444821060085110d12c2cfe0a5f115bf9b7ddc598fb17ea
6
+ metadata.gz: ad391b862e5428999a5260278e767dae4a1266eb99fd2bab18619b26302542d67d50fee3277ff27711ce4eaec68fbf5428773d17b03b7d235e501baeb06a1623
7
+ data.tar.gz: 87cee001748bcea7ce0f46a4d673a2c7ac3aefb16b99f84d463fbaff3804a98cd53184dd271a3b19261f89baef91151d85728076b76484646d5be95af1f101cb
@@ -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
@@ -103,7 +103,9 @@ module Switchman
103
103
  def activate(unordered: false, &block)
104
104
  shards = all_shards
105
105
  if Array === shards && shards.length == 1
106
- if shards.first == DefaultShard || shards.first == Shard.current(klass.connection_class_for_self)
106
+ if !loaded? && shard_value != shards.first
107
+ shard(shards.first).activate(&block)
108
+ elsif shards.first == DefaultShard || shards.first == Shard.current(klass.connection_class_for_self)
107
109
  yield(self, shards.first)
108
110
  else
109
111
  shards.first.activate(klass.connection_class_for_self) { yield(self, shards.first) }
@@ -115,7 +117,8 @@ module Switchman
115
117
  # don't even query other shards if we're already past the limit
116
118
  next if limit_value && result_count >= limit_value && order_values.empty?
117
119
 
118
- relation = shard(Shard.current(klass.connection_class_for_self), :to_a)
120
+ relation = shard(Shard.current(klass.connection_class_for_self))
121
+ relation.remove_nonlocal_primary_keys!
119
122
  # do a minimal query if possible
120
123
  relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
121
124
 
@@ -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.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
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.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-11-22 00:00:00.000000000 Z
13
+ date: 2022-12-08 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord