switchman 2.2.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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