switchman 3.2.1 → 3.3.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: 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