switchman 2.0.11 → 3.0.4

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 +234 -270
  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 +1 -1
  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.rb +3 -3
  12. data/lib/switchman/action_controller/caching.rb +2 -2
  13. data/lib/switchman/active_record/abstract_adapter.rb +1 -0
  14. data/lib/switchman/active_record/association.rb +78 -89
  15. data/lib/switchman/active_record/attribute_methods.rb +106 -52
  16. data/lib/switchman/active_record/base.rb +58 -59
  17. data/lib/switchman/active_record/calculations.rb +73 -66
  18. data/lib/switchman/active_record/connection_pool.rb +14 -41
  19. data/lib/switchman/active_record/database_configurations.rb +34 -0
  20. data/lib/switchman/active_record/database_configurations/database_config.rb +13 -0
  21. data/lib/switchman/active_record/finder_methods.rb +11 -16
  22. data/lib/switchman/active_record/log_subscriber.rb +4 -8
  23. data/lib/switchman/active_record/migration.rb +14 -43
  24. data/lib/switchman/active_record/model_schema.rb +1 -1
  25. data/lib/switchman/active_record/persistence.rb +4 -6
  26. data/lib/switchman/active_record/postgresql_adapter.rb +32 -160
  27. data/lib/switchman/active_record/predicate_builder.rb +1 -1
  28. data/lib/switchman/active_record/query_cache.rb +18 -19
  29. data/lib/switchman/active_record/query_methods.rb +179 -182
  30. data/lib/switchman/active_record/reflection.rb +6 -10
  31. data/lib/switchman/active_record/relation.rb +34 -29
  32. data/lib/switchman/active_record/spawn_methods.rb +27 -29
  33. data/lib/switchman/active_record/statement_cache.rb +18 -35
  34. data/lib/switchman/active_record/tasks/database_tasks.rb +16 -0
  35. data/lib/switchman/active_support/cache.rb +3 -5
  36. data/lib/switchman/arel.rb +13 -8
  37. data/lib/switchman/database_server.rb +122 -144
  38. data/lib/switchman/default_shard.rb +52 -16
  39. data/lib/switchman/engine.rb +61 -57
  40. data/lib/switchman/environment.rb +4 -8
  41. data/lib/switchman/errors.rb +1 -0
  42. data/lib/switchman/guard_rail.rb +6 -19
  43. data/lib/switchman/guard_rail/relation.rb +5 -7
  44. data/lib/switchman/r_spec_helper.rb +29 -37
  45. data/lib/switchman/rails.rb +14 -12
  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 -10
  49. data/lib/switchman/version.rb +1 -1
  50. data/lib/tasks/switchman.rake +54 -69
  51. metadata +85 -44
  52. data/lib/switchman/active_record/batches.rb +0 -11
  53. data/lib/switchman/active_record/connection_handler.rb +0 -172
  54. data/lib/switchman/active_record/where_clause_factory.rb +0 -36
  55. data/lib/switchman/connection_pool_proxy.rb +0 -173
  56. data/lib/switchman/schema_cache.rb +0 -28
@@ -23,4 +23,4 @@ module Switchman
23
23
  end
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -3,31 +3,30 @@
3
3
  module Switchman
4
4
  module ActiveRecord
5
5
  module QueryCache
6
-
7
6
  private
8
7
 
9
8
  def cache_sql(sql, name, binds)
10
9
  # have to include the shard id in the cache key because of switching dbs on the same connection
11
- sql = "#{self.shard.id}::#{sql}"
10
+ sql = "#{shard.id}::#{sql}"
12
11
  @lock.synchronize do
13
12
  result =
14
- if query_cache[sql].key?(binds)
15
- args = {
16
- sql: sql,
17
- binds: binds,
18
- name: name,
19
- connection_id: object_id,
20
- cached: true
21
- }
22
- args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
23
- ::ActiveSupport::Notifications.instrument(
24
- "sql.active_record",
25
- args
26
- )
27
- query_cache[sql][binds]
28
- else
29
- query_cache[sql][binds] = yield
30
- end
13
+ if query_cache[sql].key?(binds)
14
+ args = {
15
+ sql: sql,
16
+ binds: binds,
17
+ name: name,
18
+ connection_id: object_id,
19
+ cached: true,
20
+ type_casted_binds: -> { type_casted_binds(binds) }
21
+ }
22
+ ::ActiveSupport::Notifications.instrument(
23
+ 'sql.active_record',
24
+ args
25
+ )
26
+ query_cache[sql][binds]
27
+ else
28
+ query_cache[sql][binds] = yield
29
+ end
31
30
  result.dup
32
31
  end
33
32
  end
@@ -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,114 +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
 
88
+ def or(other)
89
+ super(other.shard(primary_shard))
90
+ end
91
+
81
92
  private
82
93
 
83
- if ::Rails.version >= '5.2'
84
- [:where, :having].each do |type|
85
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
86
- def transpose_#{type}_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
87
- unless (predicates = #{type}_clause.send(:predicates)).empty?
88
- new_predicates, _binds = transpose_predicates(predicates, source_shard,
89
- 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
90
102
  if new_predicates != predicates
91
- self.#{type}_clause = #{type}_clause.dup
92
- if new_predicates != predicates
93
- #{type}_clause.instance_variable_set(:@predicates, new_predicates)
94
- end
103
+ #{type}_clause.instance_variable_set(:@predicates, new_predicates)
95
104
  end
96
105
  end
97
106
  end
98
- RUBY
99
- end
100
- else
101
- [:where, :having].each do |type|
102
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
103
- def transpose_#{type}_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
104
- unless (predicates = #{type}_clause.send(:predicates)).empty?
105
- new_predicates, new_binds = transpose_predicates(predicates, source_shard,
106
- target_shard, remove_nonlocal_primary_keys,
107
- binds: #{type}_clause.binds,
108
- dup_binds_on_mutation: true)
109
- if new_predicates != predicates || !new_binds.equal?(#{type}_clause.binds)
110
- self.#{type}_clause = #{type}_clause.dup
111
- if new_predicates != predicates
112
- #{type}_clause.instance_variable_set(:@predicates, new_predicates)
113
- end
114
- if !new_binds.equal?(#{type}_clause.binds)
115
- #{type}_clause.instance_variable_set(:@binds, new_binds)
116
- end
117
- end
118
- end
119
- end
120
- RUBY
121
107
  end
108
+ RUBY
122
109
  end
123
110
 
124
- def transpose_clauses(source_shard, target_shard, remove_nonlocal_primary_keys = false)
125
- transpose_where_clauses(source_shard, target_shard, remove_nonlocal_primary_keys)
126
- 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)
127
114
  end
128
115
 
129
- def infer_shards_from_primary_key(predicates, binds = nil)
116
+ def infer_shards_from_primary_key(predicates)
130
117
  return unless klass.integral_id?
131
118
 
132
119
  primary_key = predicates.detect do |predicate|
133
- predicate.is_a?(::Arel::Nodes::Binary) && predicate.left.is_a?(::Arel::Attributes::Attribute) &&
134
- 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 &&
135
123
  klass.primary_key == predicate.left.name
136
124
  end
137
- if primary_key
138
- case primary_key.right
139
- when Array
140
- id_shards = Set.new
141
- primary_key.right.each do |value|
142
- local_id, id_shard = Shard.local_id_for(value)
143
- id_shard ||= Shard.current(klass.shard_category) if local_id
144
- id_shards << id_shard if id_shard
145
- end
146
- if id_shards.empty?
147
- return
148
- elsif id_shards.length == 1
149
- id_shard = id_shards.first
150
- # prefer to not change the shard
151
- elsif id_shards.include?(primary_shard)
152
- id_shards.delete(primary_shard)
153
- self.shard_value = [primary_shard] + id_shards.to_a
154
- return
155
- else
156
- id_shards = id_shards.to_a
157
- transpose_clauses(primary_shard, id_shards.first)
158
- self.shard_value = id_shards
159
- return
160
- end
161
- when ::Arel::Nodes::BindParam
162
- if ::Rails.version >= "5.2"
163
- local_id, id_shard = Shard.local_id_for(primary_key.right.value.value_before_type_cast)
164
- id_shard ||= Shard.current(klass.shard_category) if local_id
165
- else
166
- # look for a bind param with a matching column name
167
- if binds && bind = binds.detect{|b| b&.name.to_s == klass.primary_key.to_s}
168
- unless bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
169
- local_id, id_shard = Shard.local_id_for(bind.value)
170
- id_shard ||= Shard.current(klass.shard_category) if local_id
171
- end
172
- end
173
- end
174
- else
175
- local_id, id_shard = Shard.local_id_for(primary_key.right)
176
- 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
177
136
  end
137
+ return if id_shards.empty?
178
138
 
179
- return if !id_shard || id_shard == primary_shard
180
- transpose_clauses(primary_shard, id_shard)
181
- 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
182
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
183
164
  end
184
165
 
185
166
  def transposable_attribute_type(relation, column)
@@ -201,8 +182,9 @@ module Switchman
201
182
 
202
183
  def sharded_primary_key?(relation, column)
203
184
  column = column.to_s
204
- return column == 'id' if relation.model == ::ActiveRecord::Base
205
- 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?
206
188
  end
207
189
 
208
190
  def source_shard_for_foreign_key(relation, column)
@@ -211,8 +193,9 @@ module Switchman
211
193
  reflection = model.send(:reflection_for_integer_attribute, column)
212
194
  break if reflection
213
195
  end
214
- return Shard.current(klass.shard_category) if reflection.options[:polymorphic]
215
- 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)
216
199
  end
217
200
 
218
201
  def relation_and_column(attribute)
@@ -221,8 +204,31 @@ module Switchman
221
204
  [attribute.relation, column]
222
205
  end
223
206
 
224
- def where_clause_factory
225
- 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
226
232
  end
227
233
 
228
234
  def arel_columns(columns)
@@ -237,101 +243,92 @@ module Switchman
237
243
  connection.with_global_table_name { super }
238
244
  end
239
245
 
240
- # semi-private
241
- public
242
246
  def transpose_predicates(predicates,
243
247
  source_shard,
244
248
  target_shard,
245
- remove_nonlocal_primary_keys = false,
246
- binds: nil,
247
- dup_binds_on_mutation: false)
248
- result = predicates.map do |predicate|
249
- next predicate unless predicate.is_a?(::Arel::Nodes::Binary)
250
- next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
251
- relation, column = relation_and_column(predicate.left)
252
- next predicate unless (type = transposable_attribute_type(relation, column))
253
-
254
- remove = true if type == :primary &&
255
- remove_nonlocal_primary_keys &&
256
- predicate.left.relation.model == klass &&
257
- (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::In))
258
-
259
- current_source_shard =
260
- if source_shard
261
- source_shard
262
- elsif type == :primary
263
- Shard.current(klass.shard_category)
264
- elsif type == :foreign
265
- source_shard_for_foreign_key(relation, column)
266
- end
249
+ remove_nonlocal_primary_keys: false)
250
+ predicates.map do |predicate|
251
+ transpose_single_predicate(predicate, source_shard, target_shard,
252
+ remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
253
+ end
254
+ end
267
255
 
268
- if ::Rails.version >= "5.2"
269
- new_right_value =
270
- case predicate.right
271
- when Array
272
- predicate.right.map {|val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
273
- else
274
- transpose_predicate_value(predicate.right, current_source_shard, target_shard, type, remove)
275
- end
276
- else
277
- new_right_value = case predicate.right
278
- when Array
279
- local_ids = []
280
- predicate.right.each do |value|
281
- local_id = Shard.relative_id_for(value, current_source_shard, target_shard)
282
- next unless local_id
283
- unless remove && local_id > Shard::IDS_PER_SHARD
284
- if value.is_a?(::Arel::Nodes::Casted)
285
- if local_id == value.val
286
- local_id = value
287
- elsif local_id != value
288
- local_id = value.class.new(local_id, value.attribute)
289
- end
290
- end
291
- local_ids << local_id
292
- end
293
- end
294
- local_ids
295
- when ::Arel::Nodes::BindParam
296
- # look for a bind param with a matching column name
297
- if binds && bind = binds.detect{|b| b&.name.to_s == predicate.left.name.to_s}
298
- # before we mutate, dup
299
- if dup_binds_on_mutation
300
- binds = binds.map(&:dup)
301
- dup_binds_on_mutation = false
302
- bind = binds.find { |b| b&.name.to_s == predicate.left.name.to_s }
303
- end
304
- if bind.value.is_a?(::ActiveRecord::StatementCache::Substitute)
305
- bind.value.sharded = true # mark for transposition later
306
- bind.value.primary = true if type == :primary
256
+ def transpose_single_predicate(predicate,
257
+ source_shard,
258
+ target_shard,
259
+ remove_nonlocal_primary_keys: false)
260
+ if predicate.is_a?(::Arel::Nodes::Grouping)
261
+ return predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
262
+
263
+ # Dang, we have an OR. OK, that means we have other epxressions below this
264
+ # level, perhaps many, that may need transposition.
265
+ # the left side and right side must each be treated as predicate lists and
266
+ # transformed in kind, if neither of them changes we can just return the grouping as is.
267
+ # hold on, it's about to get recursive...
268
+ or_expr = predicate.expr
269
+ left_node = or_expr.left
270
+ right_node = or_expr.right
271
+ new_left_predicates = transpose_single_predicate(left_node, source_shard,
272
+ target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
273
+ or_expr.instance_variable_set(:@left, new_left_predicates) if new_left_predicates != left_node
274
+ new_right_predicates = transpose_single_predicate(right_node, source_shard,
275
+ target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
276
+ or_expr.instance_variable_set(:@right, new_right_predicates) if new_right_predicates != right_node
277
+ return predicate
278
+ end
279
+ return predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
280
+ return predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
281
+
282
+ relation, column = relation_and_column(predicate.left)
283
+ return predicate unless (type = transposable_attribute_type(relation, column))
284
+
285
+ remove = true if type == :primary &&
286
+ remove_nonlocal_primary_keys &&
287
+ predicate.left.relation.klass == klass &&
288
+ (predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
289
+
290
+ current_source_shard =
291
+ if source_shard
292
+ source_shard
293
+ elsif type == :primary
294
+ Shard.current(klass.connection_classes)
295
+ elsif type == :foreign
296
+ source_shard_for_foreign_key(relation, column)
297
+ end
298
+
299
+ right = if predicate.is_a?(::Arel::Nodes::HomogeneousIn)
300
+ predicate.values
307
301
  else
308
- local_id = Shard.relative_id_for(bind.value, current_source_shard, target_shard)
309
- local_id = [] if remove && local_id > Shard::IDS_PER_SHARD
310
- bind.instance_variable_set(:@value, local_id)
311
- bind.instance_variable_set(:@value_for_database, nil)
302
+ predicate.right
312
303
  end
313
- end
314
- predicate.right
315
- else
316
- local_id = Shard.relative_id_for(predicate.right, current_source_shard, target_shard) || predicate.right
317
- local_id = [] if remove && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
318
- local_id
319
- end
304
+
305
+ new_right_value =
306
+ case right
307
+ when Array
308
+ right.map { |val| transpose_predicate_value(val, current_source_shard, target_shard, type, remove).presence }.compact
309
+ else
310
+ transpose_predicate_value(right, current_source_shard, target_shard, type, remove)
320
311
  end
321
- if new_right_value == predicate.right
312
+
313
+ if new_right_value == right
314
+ predicate
315
+ elsif predicate.right.is_a?(::Arel::Nodes::Casted)
316
+ if new_right_value == right.value
322
317
  predicate
323
- elsif predicate.right.is_a?(::Arel::Nodes::Casted)
324
- if new_right_value == predicate.right.val
325
- predicate
326
- else
327
- predicate.class.new(predicate.left, predicate.right.class.new(new_right_value, predicate.right.attribute))
328
- end
329
318
  else
330
- predicate.class.new(predicate.left, new_right_value)
319
+ predicate.class.new(predicate.left, right.class.new(new_right_value, right.attribute))
320
+ end
321
+ elsif predicate.is_a?(::Arel::Nodes::HomogeneousIn)
322
+ # switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
323
+ if new_right_value.empty?
324
+ klass = predicate.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
325
+ klass.new(predicate.attribute, new_right_value)
326
+ else
327
+ predicate.class.new(new_right_value, predicate.attribute, predicate.type)
331
328
  end
329
+ else
330
+ predicate.class.new(predicate.left, new_right_value)
332
331
  end
333
- result = [result, binds]
334
- result
335
332
  end
336
333
 
337
334
  def transpose_predicate_value(value, current_shard, target_shard, attribute_type, remove_non_local_ids)
@@ -344,12 +341,12 @@ module Switchman
344
341
  value
345
342
  else
346
343
  local_id = Shard.relative_id_for(current_id, current_shard, target_shard) || current_id
347
- return nil if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
348
- if current_id != local_id
344
+ local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
345
+ if current_id == local_id
349
346
  # make a new bind param
350
- ::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
351
- else
352
347
  value
348
+ else
349
+ ::Arel::Nodes::BindParam.new(query_att.class.new(query_att.name, local_id, query_att.type))
353
350
  end
354
351
  end
355
352
  else