switchman 2.2.3 → 3.0.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +10 -2
  3. data/app/models/switchman/shard.rb +272 -275
  4. data/app/models/switchman/unsharded_record.rb +7 -0
  5. data/db/migrate/20130328212039_create_switchman_shards.rb +1 -1
  6. data/db/migrate/20130328224244_create_default_shard.rb +5 -5
  7. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +1 -0
  8. data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
  9. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +7 -5
  10. data/db/migrate/20190114212900_add_unique_name_indexes.rb +5 -3
  11. data/lib/switchman/action_controller/caching.rb +2 -2
  12. data/lib/switchman/active_record/abstract_adapter.rb +1 -0
  13. data/lib/switchman/active_record/association.rb +78 -89
  14. data/lib/switchman/active_record/attribute_methods.rb +58 -73
  15. data/lib/switchman/active_record/base.rb +59 -60
  16. data/lib/switchman/active_record/calculations.rb +74 -67
  17. data/lib/switchman/active_record/connection_pool.rb +14 -43
  18. data/lib/switchman/active_record/database_configurations/database_config.rb +13 -0
  19. data/lib/switchman/active_record/database_configurations.rb +34 -0
  20. data/lib/switchman/active_record/finder_methods.rb +11 -16
  21. data/lib/switchman/active_record/log_subscriber.rb +4 -5
  22. data/lib/switchman/active_record/migration.rb +7 -51
  23. data/lib/switchman/active_record/model_schema.rb +1 -1
  24. data/lib/switchman/active_record/persistence.rb +3 -14
  25. data/lib/switchman/active_record/postgresql_adapter.rb +125 -169
  26. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  27. data/lib/switchman/active_record/query_cache.rb +18 -19
  28. data/lib/switchman/active_record/query_methods.rb +168 -216
  29. data/lib/switchman/active_record/reflection.rb +7 -22
  30. data/lib/switchman/active_record/relation.rb +30 -78
  31. data/lib/switchman/active_record/spawn_methods.rb +27 -29
  32. data/lib/switchman/active_record/statement_cache.rb +18 -35
  33. data/lib/switchman/active_record/tasks/database_tasks.rb +16 -0
  34. data/lib/switchman/active_support/cache.rb +3 -5
  35. data/lib/switchman/arel.rb +13 -8
  36. data/lib/switchman/database_server.rb +121 -142
  37. data/lib/switchman/default_shard.rb +52 -16
  38. data/lib/switchman/engine.rb +62 -59
  39. data/lib/switchman/environment.rb +4 -8
  40. data/lib/switchman/errors.rb +1 -0
  41. data/lib/switchman/guard_rail/relation.rb +5 -7
  42. data/lib/switchman/guard_rail.rb +6 -19
  43. data/lib/switchman/r_spec_helper.rb +29 -37
  44. data/lib/switchman/rails.rb +14 -12
  45. data/lib/switchman/schema_cache.rb +1 -9
  46. data/lib/switchman/sharded_instrumenter.rb +1 -1
  47. data/lib/switchman/standard_error.rb +15 -3
  48. data/lib/switchman/test_helper.rb +6 -4
  49. data/lib/switchman/version.rb +1 -1
  50. data/lib/switchman.rb +3 -5
  51. data/lib/tasks/switchman.rake +55 -71
  52. metadata +88 -46
  53. data/lib/switchman/active_record/batches.rb +0 -11
  54. data/lib/switchman/active_record/connection_handler.rb +0 -190
  55. data/lib/switchman/active_record/where_clause_factory.rb +0 -36
  56. data/lib/switchman/connection_pool_proxy.rb +0 -173
@@ -17,15 +17,20 @@ module Switchman
17
17
  def shard_value
18
18
  @values[:shard]
19
19
  end
20
+
20
21
  def shard_source_value
21
22
  @values[:shard_source]
22
23
  end
24
+
23
25
  def shard_value=(value)
24
26
  raise ::ActiveRecord::ImmutableRelation if @loaded
27
+
25
28
  @values[:shard] = value
26
29
  end
30
+
27
31
  def shard_source_value=(value)
28
32
  raise ::ActiveRecord::ImmutableRelation if @loaded
33
+
29
34
  @values[:shard_source] = value
30
35
  end
31
36
 
@@ -35,11 +40,13 @@ module Switchman
35
40
 
36
41
  def shard!(value, source = :explicit)
37
42
  raise ArgumentError, "shard can't be nil" unless value
38
- old_primary_shard = self.primary_shard
43
+
44
+ old_primary_shard = primary_shard
39
45
  self.shard_value = value
40
46
  self.shard_source_value = source
41
- if (old_primary_shard != self.primary_shard || source == :to_a)
42
- transpose_clauses(old_primary_shard, self.primary_shard, source == :to_a)
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)
43
50
  end
44
51
  self
45
52
  end
@@ -58,7 +65,7 @@ module Switchman
58
65
  when ::ActiveRecord::Relation
59
66
  Shard.default
60
67
  when nil
61
- Shard.current(klass.shard_category)
68
+ Shard.current(klass.connection_classes)
62
69
  else
63
70
  raise ArgumentError, "invalid shard value #{shard_value}"
64
71
  end
@@ -72,118 +79,88 @@ module Switchman
72
79
  when ::ActiveRecord::Base
73
80
  shard_value.respond_to?(:associated_shards) ? shard_value.associated_shards : [shard_value.shard]
74
81
  when nil
75
- [Shard.current(klass.shard_category)]
82
+ [Shard.current(klass.connection_classes)]
76
83
  else
77
84
  shard_value
78
85
  end
79
86
  end
80
87
 
81
88
  def or(other)
82
- super(other.shard(self.primary_shard))
89
+ super(other.shard(primary_shard))
83
90
  end
84
91
 
85
92
  private
86
93
 
87
- if ::Rails.version >= '5.2'
88
- [:where, :having].each do |type|
89
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
90
- def transpose_#{type}_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
91
- unless (predicates = #{type}_clause.send(:predicates)).empty?
92
- new_predicates, _binds = transpose_predicates(predicates, source_shard,
93
- target_shard, remove_nonlocal_primary_keys)
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
94
102
  if new_predicates != predicates
95
- self.#{type}_clause = #{type}_clause.dup
96
- if new_predicates != predicates
97
- #{type}_clause.instance_variable_set(:@predicates, new_predicates)
98
- end
103
+ #{type}_clause.instance_variable_set(:@predicates, new_predicates)
99
104
  end
100
105
  end
101
106
  end
102
- RUBY
103
- end
104
- else
105
- [:where, :having].each do |type|
106
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
107
- def transpose_#{type}_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
108
- unless (predicates = #{type}_clause.send(:predicates)).empty?
109
- new_predicates, new_binds = transpose_predicates(predicates, source_shard,
110
- target_shard, remove_nonlocal_primary_keys,
111
- binds: #{type}_clause.binds,
112
- dup_binds_on_mutation: true)
113
- if new_predicates != predicates || !new_binds.equal?(#{type}_clause.binds)
114
- self.#{type}_clause = #{type}_clause.dup
115
- if new_predicates != predicates
116
- #{type}_clause.instance_variable_set(:@predicates, new_predicates)
117
- end
118
- if !new_binds.equal?(#{type}_clause.binds)
119
- #{type}_clause.instance_variable_set(:@binds, new_binds)
120
- end
121
- end
122
- end
123
- end
124
- RUBY
125
107
  end
108
+ RUBY
126
109
  end
127
110
 
128
- def transpose_clauses(source_shard, target_shard, remove_nonlocal_primary_keys = false)
129
- transpose_where_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
130
- transpose_having_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
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)
131
114
  end
132
115
 
133
- def infer_shards_from_primary_key(predicates, binds = nil)
116
+ def infer_shards_from_primary_key(predicates)
134
117
  return unless klass.integral_id?
135
118
 
136
119
  primary_key = predicates.detect do |predicate|
137
- predicate.is_a?(::Arel::Nodes::Binary) && predicate.left.is_a?(::Arel::Attributes::Attribute) &&
138
- predicate.left.relation.is_a?(::Arel::Table) && predicate.left.relation.model == klass &&
120
+ (predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)) &&
121
+ predicate.left.is_a?(::Arel::Attributes::Attribute) &&
122
+ predicate.left.relation.is_a?(::Arel::Table) && predicate.left.relation.klass == klass &&
139
123
  klass.primary_key == predicate.left.name
140
124
  end
141
- if primary_key
142
- case primary_key.right
143
- when Array
144
- id_shards = Set.new
145
- primary_key.right.each do |value|
146
- local_id, id_shard = Shard.local_id_for(value)
147
- id_shard ||= Shard.current(klass.shard_category) if local_id
148
- id_shards << id_shard if id_shard
149
- end
150
- if id_shards.empty?
151
- return
152
- elsif id_shards.length == 1
153
- id_shard = id_shards.first
154
- # prefer to not change the shard
155
- elsif id_shards.include?(primary_shard)
156
- id_shards.delete(primary_shard)
157
- self.shard_value = [primary_shard] + id_shards.to_a
158
- return
159
- else
160
- id_shards = id_shards.to_a
161
- transpose_clauses(primary_shard, id_shards.first)
162
- self.shard_value = id_shards
163
- return
164
- end
165
- when ::Arel::Nodes::BindParam
166
- if ::Rails.version >= "5.2"
167
- local_id, id_shard = Shard.local_id_for(primary_key.right.value.value_before_type_cast)
168
- id_shard ||= Shard.current(klass.shard_category) if local_id
169
- else
170
- # look for a bind param with a matching column name
171
- if binds && bind = binds.detect{|b| b&.name.to_s == klass.primary_key.to_s}
172
- unless bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
173
- local_id, id_shard = Shard.local_id_for(bind.value)
174
- id_shard ||= Shard.current(klass.shard_category) if local_id
175
- end
176
- end
177
- end
178
- else
179
- local_id, id_shard = Shard.local_id_for(primary_key.right)
180
- id_shard ||= Shard.current(klass.shard_category) if local_id
125
+ return unless primary_key
126
+
127
+ right = primary_key.is_a?(::Arel::Nodes::HomogeneousIn) ? primary_key.values : primary_key.right
128
+
129
+ case right
130
+ when Array
131
+ id_shards = Set.new
132
+ right.each do |value|
133
+ local_id, id_shard = Shard.local_id_for(value)
134
+ id_shard ||= Shard.current(klass.connection_classes) if local_id
135
+ id_shards << id_shard if id_shard
181
136
  end
137
+ return if id_shards.empty?
182
138
 
183
- return if !id_shard || id_shard == primary_shard
184
- transpose_clauses(primary_shard, id_shard)
185
- self.shard_value = id_shard
139
+ if id_shards.length == 1
140
+ id_shard = id_shards.first
141
+ # prefer to not change the shard
142
+ elsif id_shards.include?(primary_shard)
143
+ id_shards.delete(primary_shard)
144
+ self.shard_value = [primary_shard] + id_shards.to_a
145
+ return
146
+ else
147
+ id_shards = id_shards.to_a
148
+ transpose_clauses(primary_shard, id_shards.first)
149
+ self.shard_value = id_shards
150
+ return
151
+ end
152
+ when ::Arel::Nodes::BindParam
153
+ local_id, id_shard = Shard.local_id_for(right.value.value_before_type_cast)
154
+ id_shard ||= Shard.current(klass.connection_classes) if local_id
155
+ else
156
+ local_id, id_shard = Shard.local_id_for(right)
157
+ id_shard ||= Shard.current(klass.connection_classes) if local_id
186
158
  end
159
+
160
+ return if !id_shard || id_shard == primary_shard
161
+
162
+ transpose_clauses(primary_shard, id_shard)
163
+ self.shard_value = id_shard
187
164
  end
188
165
 
189
166
  def transposable_attribute_type(relation, column)
@@ -205,8 +182,9 @@ module Switchman
205
182
 
206
183
  def sharded_primary_key?(relation, column)
207
184
  column = column.to_s
208
- return column == 'id' if relation.model == ::ActiveRecord::Base
209
- relation.model.primary_key == column && relation.model.integral_id?
185
+ return column == 'id' if relation.klass == ::ActiveRecord::Base
186
+
187
+ relation.klass.primary_key == column && relation.klass.integral_id?
210
188
  end
211
189
 
212
190
  def source_shard_for_foreign_key(relation, column)
@@ -215,8 +193,9 @@ module Switchman
215
193
  reflection = model.send(:reflection_for_integer_attribute, column)
216
194
  break if reflection
217
195
  end
218
- return Shard.current(klass.shard_category) if reflection.options[:polymorphic]
219
- Shard.current(reflection.klass.shard_category)
196
+ return Shard.current(klass.connection_classes) if reflection.options[:polymorphic]
197
+
198
+ Shard.current(reflection.klass.connection_classes)
220
199
  end
221
200
 
222
201
  def relation_and_column(attribute)
@@ -225,8 +204,31 @@ module Switchman
225
204
  [attribute.relation, column]
226
205
  end
227
206
 
228
- def where_clause_factory
229
- super.tap { |factory| factory.scope = self }
207
+ def build_where_clause(opts, rest = [])
208
+ opts = sanitize_forbidden_attributes(opts)
209
+
210
+ case opts
211
+ when String, Array
212
+ values = Hash === rest.first ? rest.first.values : rest
213
+
214
+ values.grep(ActiveRecord::Relation) do |rel|
215
+ # serialize subqueries against the same shard as the outer query is currently
216
+ # targeted to run against
217
+ rel.shard!(primary_shard) if rel.shard_source_value == :implicit && rel.primary_shard != primary_shard
218
+ end
219
+
220
+ super
221
+ when Hash, ::Arel::Nodes::Node
222
+ where_clause = super
223
+
224
+ predicates = where_clause.send(:predicates)
225
+ infer_shards_from_primary_key(predicates) if shard_source_value == :implicit && shard_value.is_a?(Shard)
226
+ predicates = transpose_predicates(predicates, nil, primary_shard)
227
+ where_clause.instance_variable_set(:@predicates, predicates)
228
+ where_clause
229
+ else
230
+ super
231
+ end
230
232
  end
231
233
 
232
234
  def arel_columns(columns)
@@ -237,136 +239,86 @@ module Switchman
237
239
  connection.with_local_table_name { super }
238
240
  end
239
241
 
240
- def table_name_matches?(from)
241
- connection.with_global_table_name { super }
242
- end
243
-
244
- # semi-private
245
- public
246
242
  def transpose_predicates(predicates,
247
243
  source_shard,
248
244
  target_shard,
249
- remove_nonlocal_primary_keys = false,
250
- binds: nil,
251
- dup_binds_on_mutation: false)
252
- result = predicates.map do |predicate|
253
- if ::Rails.version >= '5.2' && predicate.is_a?(::Arel::Nodes::And)
254
- new_predicates, _binds = transpose_predicates(predicate.children, source_shard, target_shard,
255
- remove_nonlocal_primary_keys,
256
- binds: binds,
257
- dup_binds_on_mutation: dup_binds_on_mutation)
258
- next (if new_predicates == predicate.children
259
- predicate
260
- else
261
- ::Arel::Nodes::And.new(new_predicates)
262
- end)
263
- end
245
+ remove_nonlocal_primary_keys: false)
246
+ predicates.map do |predicate|
247
+ transpose_single_predicate(predicate, source_shard, target_shard,
248
+ remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
249
+ end
250
+ end
264
251
 
265
- if predicate.is_a?(::Arel::Nodes::Grouping)
266
- next predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
267
-
268
- or_expr = predicate.expr
269
- left_node = or_expr.left
270
- right_node = or_expr.right
271
- new_left_predicates, binds = transpose_predicates([left_node], source_shard, target_shard,
272
- remove_nonlocal_primary_keys,
273
- binds: binds,
274
- dup_binds_on_mutation: dup_binds_on_mutation)
275
- new_right_predicates, binds = transpose_predicates([right_node], source_shard, target_shard,
276
- remove_nonlocal_primary_keys,
277
- binds: binds,
278
- dup_binds_on_mutation: dup_binds_on_mutation)
279
-
280
- next predicate if new_left_predicates[0] == left_node && new_right_predicates[0] == right_node
281
- next ::Arel::Nodes::Grouping.new ::Arel::Nodes::Or.new(new_left_predicates[0], new_right_predicates[0])
282
- end
252
+ def transpose_single_predicate(predicate,
253
+ source_shard,
254
+ target_shard,
255
+ remove_nonlocal_primary_keys: false)
256
+ if predicate.is_a?(::Arel::Nodes::Grouping)
257
+ return predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
283
258
 
284
- next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
285
- next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
286
- relation, column = relation_and_column(predicate.left)
287
- next predicate unless (type = transposable_attribute_type(relation, column))
288
-
289
- remove = true if type == :primary &&
290
- remove_nonlocal_primary_keys &&
291
- predicate.left.relation.model == klass &&
292
- (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::In))
293
-
294
- current_source_shard =
295
- if source_shard
296
- source_shard
297
- elsif type == :primary
298
- Shard.current(klass.shard_category)
299
- elsif type == :foreign
300
- source_shard_for_foreign_key(relation, column)
301
- end
259
+ # Dang, we have an OR. OK, that means we have other epxressions below this
260
+ # level, perhaps many, that may need transposition.
261
+ # the left side and right side must each be treated as predicate lists and
262
+ # transformed in kind, if neither of them changes we can just return the grouping as is.
263
+ # hold on, it's about to get recursive...
264
+ or_expr = predicate.expr
265
+ left_node = or_expr.left
266
+ right_node = or_expr.right
267
+ new_left_predicates = transpose_single_predicate(left_node, source_shard,
268
+ target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
269
+ or_expr.instance_variable_set(:@left, new_left_predicates) if new_left_predicates != left_node
270
+ new_right_predicates = transpose_single_predicate(right_node, source_shard,
271
+ target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
272
+ or_expr.instance_variable_set(:@right, new_right_predicates) if new_right_predicates != right_node
273
+ return predicate
274
+ end
275
+ return predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
276
+ return predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
302
277
 
303
- if ::Rails.version >= "5.2"
304
- new_right_value =
305
- case predicate.right
306
- when Array
307
- predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
308
- else
309
- transpose_predicate_value(predicate.right, current_source_shard, target_shard, type, remove)
310
- end
311
- else
312
- new_right_value = case predicate.right
313
- when Array
314
- local_ids = []
315
- predicate.right.each do |value|
316
- local_id = Shard.relative_id_for(value, current_source_shard, target_shard)
317
- next unless local_id
318
- unless remove && local_id > Shard::IDS_PER_SHARD
319
- if value.is_a?(::Arel::Nodes::Casted)
320
- if local_id == value.val
321
- local_id = value
322
- elsif local_id != value
323
- local_id = value.class.new(local_id, value.attribute)
324
- end
325
- end
326
- local_ids << local_id
327
- end
328
- end
329
- local_ids
330
- when ::Arel::Nodes::BindParam
331
- # look for a bind param with a matching column name
332
- if binds && bind = binds.detect{|b| b&.name.to_s == predicate.left.name.to_s}
333
- # before we mutate, dup
334
- if dup_binds_on_mutation
335
- binds = binds.map(&:dup)
336
- dup_binds_on_mutation = false
337
- bind = binds.find { |b| b&.name.to_s == predicate.left.name.to_s }
338
- end
339
- if bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
340
- bind.value.sharded = true # mark for transposition later
341
- bind.value.primary = true if type == :primary
278
+ relation, column = relation_and_column(predicate.left)
279
+ return predicate unless (type = transposable_attribute_type(relation, column))
280
+
281
+ remove = true if type == :primary &&
282
+ remove_nonlocal_primary_keys &&
283
+ predicate.left.relation.klass == klass &&
284
+ predicate.is_a?(::Arel::Nodes::Equality)
285
+
286
+ current_source_shard =
287
+ if source_shard
288
+ source_shard
289
+ elsif type == :primary
290
+ Shard.current(klass.connection_classes)
291
+ elsif type == :foreign
292
+ source_shard_for_foreign_key(relation, column)
293
+ end
294
+
295
+ right = if predicate.is_a?(::Arel::Nodes::HomogeneousIn)
296
+ predicate.values
342
297
  else
343
- local_id = Shard.relative_id_for(bind.value, current_source_shard, target_shard)
344
- local_id = [] if remove && local_id > Shard::IDS_PER_SHARD
345
- bind.instance_variable_set(:@value, local_id)
346
- bind.instance_variable_set(:@value_for_database, nil)
298
+ predicate.right
347
299
  end
348
- end
349
- predicate.right
350
- else
351
- local_id = Shard.relative_id_for(predicate.right, current_source_shard, target_shard) || predicate.right
352
- local_id = [] if remove && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
353
- local_id
354
- end
300
+
301
+ new_right_value =
302
+ case right
303
+ when Array
304
+ right.map { |val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove) }
305
+ else
306
+ transpose_predicate_value(right, current_source_shard, target_shard, type, remove)
355
307
  end
356
- if new_right_value == predicate.right
308
+
309
+ if new_right_value == right
310
+ predicate
311
+ elsif predicate.right.is_a?(::Arel::Nodes::Casted)
312
+ if new_right_value == right.value
357
313
  predicate
358
- elsif predicate.right.is_a?(::Arel::Nodes::Casted)
359
- if new_right_value == predicate.right.val
360
- predicate
361
- else
362
- predicate.class.new(predicate.left, predicate.right.class.new(new_right_value, predicate.right.attribute))
363
- end
364
314
  else
365
- predicate.class.new(predicate.left, new_right_value)
315
+ predicate.class.new(predicate.left, right.class.new(new_right_value, right.attribute))
366
316
  end
317
+ elsif predicate.is_a?(::Arel::Nodes::HomogeneousIn)
318
+ predicate.class.new(new_right_value, predicate.attribute, predicate.type)
319
+ else
320
+ predicate.class.new(predicate.left, new_right_value)
367
321
  end
368
- result = [result, binds]
369
- result
370
322
  end
371
323
 
372
324
  def transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids)
@@ -379,12 +331,12 @@ module Switchman
379
331
  value
380
332
  else
381
333
  local_id = Shard.relative_id_for(current_id, current_shard, target_shard) || current_id
382
- return nil if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
383
- if current_id != local_id
334
+ local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
335
+ if current_id == local_id
384
336
  # make a new bind param
385
- ::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
386
- else
387
337
  value
338
+ else
339
+ ::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
388
340
  end
389
341
  end
390
342
  else
@@ -5,7 +5,7 @@ module Switchman
5
5
  module Reflection
6
6
  module AbstractReflection
7
7
  def shard(owner)
8
- if polymorphic? || klass.shard_category == owner.class.shard_category
8
+ if polymorphic? || klass.connection_classes == owner.class.connection_classes
9
9
  # polymorphic associations assume the same shard as the owning item
10
10
  owner.shard
11
11
  else
@@ -28,32 +28,17 @@ module Switchman
28
28
  # this technically belongs on AssociationReflection, but we put it on
29
29
  # ThroughReflection as well, instead of delegating to its internal
30
30
  # HasManyAssociation, losing its proper `klass`
31
- if ::Rails.version < '6.0.4'
32
- def association_scope_cache(conn, owner, &block)
33
- key = conn.prepared_statements
34
- if polymorphic?
35
- key = [key, owner._read_attribute(@foreign_type)]
36
- end
37
- key = [key, shard(owner).id].flatten
38
- @association_scope_cache[key] ||= @scope_lock.synchronize {
39
- @association_scope_cache[key] ||= (::Rails.version >= "5.2" ? ::ActiveRecord::StatementCache.create(conn, &block) : block.call)
40
- }
41
- end
42
- else
43
- def association_scope_cache(klass, owner, &block)
44
- key = self
45
- if polymorphic?
46
- key = [key, owner._read_attribute(@foreign_type)]
47
- end
48
- key = [key, shard(owner).id].flatten
49
- klass.cached_find_by_statement(key, &block)
50
- end
31
+ def association_scope_cache(klass, owner, &block)
32
+ key = self
33
+ key = [key, owner._read_attribute(@foreign_type)] if polymorphic?
34
+ key = [key, shard(owner).id].flatten
35
+ klass.cached_find_by_statement(key, &block)
51
36
  end
52
37
  end
53
38
 
54
39
  module AssociationReflection
55
40
  def join_id_for(owner)
56
- owner.send(::Rails.version >= "5.2" ? join_foreign_key : active_record_primary_key) # use sharded id values in association binds
41
+ owner.send(join_foreign_key) # use sharded id values in association binds
57
42
  end
58
43
  end
59
44
  end