switchman 3.0.1 → 4.2.5
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 +4 -4
- data/Rakefile +16 -15
- data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/action_controller/caching.rb +2 -2
- data/lib/switchman/active_record/abstract_adapter.rb +11 -18
- data/lib/switchman/active_record/associations.rb +315 -0
- data/lib/switchman/active_record/attribute_methods.rb +191 -79
- data/lib/switchman/active_record/base.rb +204 -50
- data/lib/switchman/active_record/calculations.rb +93 -50
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +47 -34
- data/lib/switchman/active_record/database_configurations.rb +32 -6
- data/lib/switchman/active_record/finder_methods.rb +22 -16
- data/lib/switchman/active_record/log_subscriber.rb +3 -6
- data/lib/switchman/active_record/migration.rb +42 -14
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +37 -2
- data/lib/switchman/active_record/postgresql_adapter.rb +39 -20
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +26 -17
- data/lib/switchman/active_record/query_methods.rb +252 -135
- data/lib/switchman/active_record/reflection.rb +10 -3
- data/lib/switchman/active_record/relation.rb +154 -32
- data/lib/switchman/active_record/spawn_methods.rb +3 -7
- data/lib/switchman/active_record/statement_cache.rb +13 -9
- data/lib/switchman/active_record/table_definition.rb +1 -1
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +89 -0
- data/lib/switchman/active_support/cache.rb +25 -4
- data/lib/switchman/arel.rb +20 -7
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +123 -83
- data/lib/switchman/default_shard.rb +14 -5
- data/lib/switchman/engine.rb +85 -131
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +17 -2
- data/lib/switchman/guard_rail/relation.rb +7 -10
- data/lib/switchman/guard_rail.rb +5 -0
- data/lib/switchman/parallel.rb +68 -0
- data/lib/switchman/r_spec_helper.rb +17 -28
- data/lib/switchman/rails.rb +1 -4
- data/{app/models → lib}/switchman/shard.rb +229 -246
- data/lib/switchman/sharded_instrumenter.rb +9 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +15 -12
- data/lib/switchman/test_helper.rb +3 -3
- data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +46 -12
- data/lib/tasks/switchman.rake +101 -54
- metadata +34 -176
- data/lib/switchman/active_record/association.rb +0 -206
- data/lib/switchman/open4.rb +0 -80
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
module Switchman
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module QueryMethods
|
|
6
|
+
# Use this class to prevent a value from getting transposed across shards
|
|
7
|
+
class NonTransposingValue < SimpleDelegator
|
|
8
|
+
def class
|
|
9
|
+
__getobj__.class
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def is_a?(other)
|
|
13
|
+
return true if other == NonTransposingValue
|
|
14
|
+
|
|
15
|
+
__getobj__.is_a?(other)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
6
19
|
# shard_value is one of:
|
|
7
20
|
# A shard
|
|
8
21
|
# An array or relation of shards
|
|
@@ -12,8 +25,6 @@ module Switchman
|
|
|
12
25
|
# :explicit - explicit set on the relation
|
|
13
26
|
# :association - a special value that scopes from associations use to use slightly different logic
|
|
14
27
|
# 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
28
|
def shard_value
|
|
18
29
|
@values[:shard]
|
|
19
30
|
end
|
|
@@ -23,13 +34,29 @@ module Switchman
|
|
|
23
34
|
end
|
|
24
35
|
|
|
25
36
|
def shard_value=(value)
|
|
26
|
-
|
|
37
|
+
if @loaded
|
|
38
|
+
error_class = if ::Rails.version < "7.2"
|
|
39
|
+
::ActiveRecord::ImmutableRelation
|
|
40
|
+
else
|
|
41
|
+
::ActiveRecord::UnmodifiableRelation
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
raise error_class
|
|
45
|
+
end
|
|
27
46
|
|
|
28
47
|
@values[:shard] = value
|
|
29
48
|
end
|
|
30
49
|
|
|
31
50
|
def shard_source_value=(value)
|
|
32
|
-
|
|
51
|
+
if @loaded
|
|
52
|
+
error_class = if ::Rails.version < "7.2"
|
|
53
|
+
::ActiveRecord::ImmutableRelation
|
|
54
|
+
else
|
|
55
|
+
::ActiveRecord::UnmodifiableRelation
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
raise error_class
|
|
59
|
+
end
|
|
33
60
|
|
|
34
61
|
@values[:shard_source] = value
|
|
35
62
|
end
|
|
@@ -44,10 +71,7 @@ module Switchman
|
|
|
44
71
|
old_primary_shard = primary_shard
|
|
45
72
|
self.shard_value = value
|
|
46
73
|
self.shard_source_value = source
|
|
47
|
-
|
|
48
|
-
transpose_clauses(old_primary_shard, primary_shard,
|
|
49
|
-
remove_nonlocal_primary_keys: source == :to_a)
|
|
50
|
-
end
|
|
74
|
+
transpose_predicates(nil, old_primary_shard, primary_shard) if old_primary_shard != primary_shard
|
|
51
75
|
self
|
|
52
76
|
end
|
|
53
77
|
|
|
@@ -65,7 +89,7 @@ module Switchman
|
|
|
65
89
|
when ::ActiveRecord::Relation
|
|
66
90
|
Shard.default
|
|
67
91
|
when nil
|
|
68
|
-
Shard.current(klass.
|
|
92
|
+
Shard.current(klass.connection_class_for_self)
|
|
69
93
|
else
|
|
70
94
|
raise ArgumentError, "invalid shard value #{shard_value}"
|
|
71
95
|
end
|
|
@@ -79,7 +103,7 @@ module Switchman
|
|
|
79
103
|
when ::ActiveRecord::Base
|
|
80
104
|
shard_value.respond_to?(:associated_shards) ? shard_value.associated_shards : [shard_value.shard]
|
|
81
105
|
when nil
|
|
82
|
-
[Shard.current(klass.
|
|
106
|
+
[Shard.current(klass.connection_class_for_self)]
|
|
83
107
|
else
|
|
84
108
|
shard_value
|
|
85
109
|
end
|
|
@@ -89,35 +113,41 @@ module Switchman
|
|
|
89
113
|
super(other.shard(primary_shard))
|
|
90
114
|
end
|
|
91
115
|
|
|
92
|
-
|
|
116
|
+
# use a temp variable so that the new where clause is built before self.where_clause is read,
|
|
117
|
+
# since build_where_clause might mutate self.where_clause
|
|
118
|
+
def where!(opts, *rest)
|
|
119
|
+
new_clause = build_where_clause(opts, rest)
|
|
120
|
+
self.where_clause += new_clause
|
|
121
|
+
self
|
|
122
|
+
end
|
|
93
123
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
107
|
-
end
|
|
108
|
-
RUBY
|
|
124
|
+
protected
|
|
125
|
+
|
|
126
|
+
def arel_columns(columns)
|
|
127
|
+
connection.with_local_table_name { super }
|
|
109
128
|
end
|
|
110
129
|
|
|
111
|
-
def
|
|
112
|
-
|
|
113
|
-
|
|
130
|
+
def remove_nonlocal_primary_keys!
|
|
131
|
+
each_transposable_predicate_value do |value, predicate, _relation, _column, type|
|
|
132
|
+
next value unless
|
|
133
|
+
type == :primary &&
|
|
134
|
+
predicate.left.relation.klass == klass &&
|
|
135
|
+
(predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
|
|
136
|
+
|
|
137
|
+
(value.is_a?(Integer) && value > Shard::IDS_PER_SHARD) ? [] : value
|
|
138
|
+
end
|
|
139
|
+
self
|
|
114
140
|
end
|
|
115
141
|
|
|
142
|
+
private
|
|
143
|
+
|
|
116
144
|
def infer_shards_from_primary_key(predicates)
|
|
117
145
|
return unless klass.integral_id?
|
|
118
146
|
|
|
119
147
|
primary_key = predicates.detect do |predicate|
|
|
120
|
-
(predicate.is_a?(::Arel::Nodes::
|
|
148
|
+
(predicate.is_a?(::Arel::Nodes::Equality) ||
|
|
149
|
+
predicate.is_a?(::Arel::Nodes::In) ||
|
|
150
|
+
predicate.is_a?(::Arel::Nodes::HomogeneousIn)) &&
|
|
121
151
|
predicate.left.is_a?(::Arel::Attributes::Attribute) &&
|
|
122
152
|
predicate.left.relation.is_a?(::Arel::Table) && predicate.left.relation.klass == klass &&
|
|
123
153
|
klass.primary_key == predicate.left.name
|
|
@@ -131,7 +161,7 @@ module Switchman
|
|
|
131
161
|
id_shards = Set.new
|
|
132
162
|
right.each do |value|
|
|
133
163
|
local_id, id_shard = Shard.local_id_for(value)
|
|
134
|
-
id_shard ||= Shard.current(klass.
|
|
164
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
135
165
|
id_shards << id_shard if id_shard
|
|
136
166
|
end
|
|
137
167
|
return if id_shards.empty?
|
|
@@ -145,21 +175,24 @@ module Switchman
|
|
|
145
175
|
return
|
|
146
176
|
else
|
|
147
177
|
id_shards = id_shards.to_a
|
|
148
|
-
|
|
178
|
+
transpose_predicates(nil, primary_shard, id_shards.first)
|
|
149
179
|
self.shard_value = id_shards
|
|
150
180
|
return
|
|
151
181
|
end
|
|
152
182
|
when ::Arel::Nodes::BindParam
|
|
153
183
|
local_id, id_shard = Shard.local_id_for(right.value.value_before_type_cast)
|
|
154
|
-
id_shard ||= Shard.current(klass.
|
|
184
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
185
|
+
when ::ActiveModel::Attribute
|
|
186
|
+
local_id, id_shard = Shard.local_id_for(right.value_before_type_cast)
|
|
187
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
155
188
|
else
|
|
156
189
|
local_id, id_shard = Shard.local_id_for(right)
|
|
157
|
-
id_shard ||= Shard.current(klass.
|
|
190
|
+
id_shard ||= Shard.current(klass.connection_class_for_self) if local_id
|
|
158
191
|
end
|
|
159
192
|
|
|
160
193
|
return if !id_shard || id_shard == primary_shard
|
|
161
194
|
|
|
162
|
-
|
|
195
|
+
transpose_predicates(nil, primary_shard, id_shard)
|
|
163
196
|
self.shard_value = id_shard
|
|
164
197
|
end
|
|
165
198
|
|
|
@@ -177,25 +210,25 @@ module Switchman
|
|
|
177
210
|
end
|
|
178
211
|
|
|
179
212
|
def sharded_foreign_key?(relation, column)
|
|
180
|
-
models_for_table(relation.
|
|
213
|
+
models_for_table(relation.name).any? { |m| m.sharded_column?(column) }
|
|
181
214
|
end
|
|
182
215
|
|
|
183
216
|
def sharded_primary_key?(relation, column)
|
|
184
217
|
column = column.to_s
|
|
185
|
-
return column ==
|
|
218
|
+
return column == "id" if relation.klass == ::ActiveRecord::Base
|
|
186
219
|
|
|
187
220
|
relation.klass.primary_key == column && relation.klass.integral_id?
|
|
188
221
|
end
|
|
189
222
|
|
|
190
223
|
def source_shard_for_foreign_key(relation, column)
|
|
191
224
|
reflection = nil
|
|
192
|
-
models_for_table(relation.
|
|
225
|
+
models_for_table(relation.name).each do |model|
|
|
193
226
|
reflection = model.send(:reflection_for_integer_attribute, column)
|
|
194
227
|
break if reflection
|
|
195
228
|
end
|
|
196
|
-
return Shard.current(klass.
|
|
229
|
+
return Shard.current(klass.connection_class_for_self) if reflection.options[:polymorphic]
|
|
197
230
|
|
|
198
|
-
Shard.current(reflection.klass.
|
|
231
|
+
Shard.current(reflection.klass.connection_class_for_self)
|
|
199
232
|
end
|
|
200
233
|
|
|
201
234
|
def relation_and_column(attribute)
|
|
@@ -209,12 +242,11 @@ module Switchman
|
|
|
209
242
|
|
|
210
243
|
case opts
|
|
211
244
|
when String, Array
|
|
212
|
-
values = Hash === rest.first ? rest.first.values : rest
|
|
245
|
+
values = (Hash === rest.first) ? rest.first.values : rest
|
|
213
246
|
|
|
214
|
-
values.grep(ActiveRecord::Relation)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
rel.shard!(primary_shard) if rel.shard_source_value == :implicit && rel.primary_shard != primary_shard
|
|
247
|
+
if shard_source_value != :explicit && values.grep(ActiveRecord::Relation).first
|
|
248
|
+
raise "Sub-queries are not allowed as simple substitutions; " \
|
|
249
|
+
"please build your relation with more structured methods so that Switchman is able to introspect it."
|
|
218
250
|
end
|
|
219
251
|
|
|
220
252
|
super
|
|
@@ -231,118 +263,203 @@ module Switchman
|
|
|
231
263
|
end
|
|
232
264
|
end
|
|
233
265
|
|
|
234
|
-
def arel_columns(columns)
|
|
235
|
-
connection.with_local_table_name { super }
|
|
236
|
-
end
|
|
237
|
-
|
|
238
266
|
def arel_column(columns)
|
|
239
267
|
connection.with_local_table_name { super }
|
|
240
268
|
end
|
|
241
269
|
|
|
242
|
-
def
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
270
|
+
def table_name_matches?(from)
|
|
271
|
+
if ::Rails.version < "7.2"
|
|
272
|
+
connection.with_global_table_name { super }
|
|
273
|
+
else
|
|
274
|
+
connection.with_global_table_name do
|
|
275
|
+
table_name = Regexp.escape(table.name)
|
|
276
|
+
# INST: adapter_class -> connection
|
|
277
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
|
278
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
|
279
|
+
end
|
|
249
280
|
end
|
|
250
281
|
end
|
|
251
282
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
283
|
+
unless ::Rails.version < "7.2"
|
|
284
|
+
def order_column(field)
|
|
285
|
+
arel_column(field) do |attr_name|
|
|
286
|
+
if attr_name == "count" && !group_values.empty?
|
|
287
|
+
table[attr_name]
|
|
288
|
+
else
|
|
289
|
+
# INST: adapter_class -> connection
|
|
290
|
+
::Arel.sql(connection.quote_table_name(attr_name), retryable: true)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
274
293
|
end
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def each_predicate(predicates = nil, &)
|
|
297
|
+
return predicates.map(&) if predicates
|
|
298
|
+
|
|
299
|
+
each_predicate_cb(:having_clause, :having_clause=, &)
|
|
300
|
+
each_predicate_cb(:where_clause, :where_clause=, &)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def each_predicate_cb(clause_getter, clause_setter, &)
|
|
304
|
+
old_clause = send(clause_getter)
|
|
305
|
+
old_predicates = old_clause.send(:predicates)
|
|
306
|
+
return if old_predicates.empty?
|
|
307
|
+
|
|
308
|
+
new_predicates = old_predicates.map(&)
|
|
309
|
+
return if new_predicates == old_predicates
|
|
310
|
+
|
|
311
|
+
new_clause = old_clause.dup
|
|
312
|
+
new_clause.instance_variable_set(:@predicates, new_predicates)
|
|
313
|
+
|
|
314
|
+
send(clause_setter, new_clause)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def each_transposable_predicate(predicates, &block)
|
|
318
|
+
each_predicate(predicates) do |predicate|
|
|
319
|
+
case predicate
|
|
320
|
+
when ::Arel::Nodes::Grouping
|
|
321
|
+
next predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
|
|
322
|
+
|
|
323
|
+
or_expr = predicate.expr
|
|
324
|
+
old_left = or_expr.left
|
|
325
|
+
old_right = or_expr.right
|
|
326
|
+
new_left, new_right = each_transposable_predicate([old_left, old_right], &block)
|
|
327
|
+
|
|
328
|
+
next predicate if new_left == old_left && new_right == old_right
|
|
329
|
+
|
|
330
|
+
next predicate.class.new predicate.expr.class.new(new_left, new_right) if ::Rails.version < "7.2"
|
|
331
|
+
|
|
332
|
+
next predicate.class.new predicate.expr.class.new([new_left, new_right])
|
|
333
|
+
|
|
334
|
+
when ::Arel::Nodes::SelectStatement
|
|
335
|
+
new_cores = predicate.cores.map do |core|
|
|
336
|
+
next core unless core.is_a?(::Arel::Nodes::SelectCore) # just in case something weird is going on
|
|
337
|
+
|
|
338
|
+
new_wheres = each_transposable_predicate(core.wheres, &block)
|
|
339
|
+
new_havings = each_transposable_predicate(core.havings, &block)
|
|
340
|
+
|
|
341
|
+
next core if core.wheres == new_wheres && core.havings == new_havings
|
|
342
|
+
|
|
343
|
+
new_core = core.clone
|
|
344
|
+
new_core.wheres = new_wheres
|
|
345
|
+
new_core.havings = new_havings
|
|
346
|
+
new_core
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
next predicate if predicate.cores == new_cores
|
|
350
|
+
|
|
351
|
+
new_node = predicate.clone
|
|
352
|
+
new_node.instance_variable_set(:@cores, new_cores)
|
|
353
|
+
next new_node
|
|
354
|
+
when ::Arel::Nodes::Not
|
|
355
|
+
old_value = predicate.expr
|
|
356
|
+
new_value = each_transposable_predicate([old_value], &block).first
|
|
357
|
+
|
|
358
|
+
next predicate if old_value == new_value
|
|
359
|
+
|
|
360
|
+
next predicate.class.new(new_value)
|
|
361
|
+
when ::Arel::Nodes::Exists
|
|
362
|
+
old_value = predicate.expressions
|
|
363
|
+
new_value = each_transposable_predicate([old_value], &block).first
|
|
364
|
+
|
|
365
|
+
next predicate if old_value == new_value
|
|
366
|
+
|
|
367
|
+
next predicate.class.new(new_value)
|
|
293
368
|
end
|
|
294
369
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
else
|
|
298
|
-
predicate.right
|
|
299
|
-
end
|
|
370
|
+
next predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
|
|
371
|
+
next predicate unless predicate.left.is_a?(::Arel::Attributes::Attribute)
|
|
300
372
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
373
|
+
relation, column = relation_and_column(predicate.left)
|
|
374
|
+
next predicate unless (type = transposable_attribute_type(relation, column))
|
|
375
|
+
|
|
376
|
+
yield(predicate, relation, column, type)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def each_transposable_predicate_value(predicates = nil, &block)
|
|
381
|
+
each_transposable_predicate(predicates) do |predicate, relation, column, type|
|
|
382
|
+
each_transposable_predicate_value_cb(predicate, block) do |value|
|
|
383
|
+
yield(value, predicate, relation, column, type)
|
|
307
384
|
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def each_transposable_predicate_value_cb(node, original_block, &)
|
|
389
|
+
case node
|
|
390
|
+
when Array
|
|
391
|
+
node.filter_map { |val| each_transposable_predicate_value_cb(val, original_block, &).presence }
|
|
392
|
+
when ::ActiveModel::Attribute
|
|
393
|
+
old_value = node.value_before_type_cast
|
|
394
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
|
|
308
395
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
396
|
+
(old_value == new_value) ? node : node.class.new(node.name, new_value, node.type)
|
|
397
|
+
when ::Arel::Nodes::And
|
|
398
|
+
old_value = node.children
|
|
399
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
|
|
400
|
+
|
|
401
|
+
(old_value == new_value) ? node : node.class.new(new_value)
|
|
402
|
+
when ::Arel::Nodes::BindParam
|
|
403
|
+
old_value = node.value
|
|
404
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
|
|
405
|
+
|
|
406
|
+
(old_value == new_value) ? node : node.class.new(new_value)
|
|
407
|
+
when ::Arel::Nodes::Casted
|
|
408
|
+
old_value = node.value
|
|
409
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
|
|
410
|
+
|
|
411
|
+
(old_value == new_value) ? node : node.class.new(new_value, node.attribute)
|
|
412
|
+
when ::Arel::Nodes::HomogeneousIn
|
|
413
|
+
old_value = node.values
|
|
414
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
|
|
415
|
+
|
|
416
|
+
# switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
|
|
417
|
+
if new_value.empty?
|
|
418
|
+
klass = (node.type == :in) ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
|
|
419
|
+
klass.new(node.attribute, new_value)
|
|
314
420
|
else
|
|
315
|
-
|
|
421
|
+
(old_value == new_value) ? node : node.class.new(new_value, node.attribute, node.type)
|
|
316
422
|
end
|
|
317
|
-
|
|
318
|
-
|
|
423
|
+
when ::Arel::Nodes::Binary
|
|
424
|
+
old_value = node.right
|
|
425
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &)
|
|
426
|
+
|
|
427
|
+
(old_value == new_value) ? node : node.class.new(node.left, new_value)
|
|
428
|
+
when ::Arel::Nodes::SelectStatement
|
|
429
|
+
each_transposable_predicate_value([node], &original_block).first
|
|
319
430
|
else
|
|
320
|
-
|
|
431
|
+
yield(node)
|
|
321
432
|
end
|
|
322
433
|
end
|
|
323
434
|
|
|
324
|
-
def
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if current_id == local_id
|
|
336
|
-
# make a new bind param
|
|
337
|
-
value
|
|
435
|
+
def transpose_predicates(predicates,
|
|
436
|
+
source_shard,
|
|
437
|
+
target_shard)
|
|
438
|
+
each_transposable_predicate_value(predicates) do |value, _predicate, relation, column, type|
|
|
439
|
+
current_source_shard =
|
|
440
|
+
if source_shard
|
|
441
|
+
source_shard
|
|
442
|
+
elsif type == :primary
|
|
443
|
+
Shard.current(klass.connection_class_for_self)
|
|
444
|
+
elsif type == :foreign
|
|
445
|
+
source_shard_for_foreign_key(relation, column)
|
|
338
446
|
else
|
|
339
|
-
|
|
447
|
+
primary_shard
|
|
340
448
|
end
|
|
341
|
-
|
|
449
|
+
|
|
450
|
+
transpose_predicate_value(value, current_source_shard, target_shard, type)
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def transpose_predicate_value(value, current_shard, target_shard, attribute_type)
|
|
455
|
+
if value.is_a?(NonTransposingValue)
|
|
456
|
+
value
|
|
457
|
+
elsif value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
|
458
|
+
value.sharded = true # mark for transposition later
|
|
459
|
+
value.primary = true if attribute_type == :primary
|
|
460
|
+
value
|
|
342
461
|
else
|
|
343
|
-
|
|
344
|
-
local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
|
|
345
|
-
local_id
|
|
462
|
+
Shard.relative_id_for(value, current_shard, target_shard) || value
|
|
346
463
|
end
|
|
347
464
|
end
|
|
348
465
|
end
|
|
@@ -5,7 +5,7 @@ module Switchman
|
|
|
5
5
|
module Reflection
|
|
6
6
|
module AbstractReflection
|
|
7
7
|
def shard(owner)
|
|
8
|
-
if polymorphic? || klass.
|
|
8
|
+
if polymorphic? || klass.connection_class_for_self == owner.class.connection_class_for_self
|
|
9
9
|
# polymorphic associations assume the same shard as the owning item
|
|
10
10
|
owner.shard
|
|
11
11
|
else
|
|
@@ -28,11 +28,18 @@ 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
|
-
def association_scope_cache(klass, owner, &
|
|
31
|
+
def association_scope_cache(klass, owner, &)
|
|
32
32
|
key = self
|
|
33
33
|
key = [key, owner._read_attribute(@foreign_type)] if polymorphic?
|
|
34
34
|
key = [key, shard(owner).id].flatten
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
if ::Rails.version < "7.2"
|
|
37
|
+
klass.cached_find_by_statement(key, &)
|
|
38
|
+
else
|
|
39
|
+
klass.with_connection do |connection|
|
|
40
|
+
klass.cached_find_by_statement(connection, key, &)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
36
43
|
end
|
|
37
44
|
end
|
|
38
45
|
|