switchman 3.0.24 → 4.2.4
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 +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/active_record/abstract_adapter.rb +11 -7
- data/lib/switchman/active_record/associations.rb +157 -50
- data/lib/switchman/active_record/attribute_methods.rb +192 -101
- data/lib/switchman/active_record/base.rb +136 -33
- data/lib/switchman/active_record/calculations.rb +91 -48
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +41 -6
- data/lib/switchman/active_record/database_configurations.rb +23 -13
- 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 -17
- 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 +32 -2
- data/lib/switchman/active_record/postgresql_adapter.rb +37 -22
- 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 +249 -142
- data/lib/switchman/active_record/reflection.rb +10 -3
- data/lib/switchman/active_record/relation.rb +103 -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 +71 -25
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +16 -25
- data/lib/switchman/call_super.rb +2 -2
- data/lib/switchman/database_server.rb +68 -34
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +36 -19
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +13 -0
- data/lib/switchman/guard_rail/relation.rb +3 -3
- data/lib/switchman/parallel.rb +6 -6
- data/lib/switchman/r_spec_helper.rb +12 -11
- data/lib/switchman/shard.rb +182 -72
- data/lib/switchman/sharded_instrumenter.rb +9 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +4 -0
- data/lib/switchman/test_helper.rb +3 -3
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +27 -15
- data/lib/tasks/switchman.rake +96 -60
- metadata +18 -168
|
@@ -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,128 +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
270
|
def table_name_matches?(from)
|
|
243
|
-
|
|
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
|
|
280
|
+
end
|
|
244
281
|
end
|
|
245
282
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
253
293
|
end
|
|
254
294
|
end
|
|
255
295
|
|
|
256
|
-
def
|
|
257
|
-
|
|
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
|
-
new_right_predicates = transpose_single_predicate(right_node, source_shard,
|
|
274
|
-
target_shard, remove_nonlocal_primary_keys: remove_nonlocal_primary_keys)
|
|
275
|
-
return predicate if new_left_predicates == left_node && new_right_predicates == right_node
|
|
276
|
-
|
|
277
|
-
return ::Arel::Nodes::Grouping.new ::Arel::Nodes::Or.new(new_left_predicates, new_right_predicates)
|
|
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
|
|
296
|
+
def each_predicate(predicates = nil, &)
|
|
297
|
+
return predicates.map(&) if predicates
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
predicate.right
|
|
303
|
-
end
|
|
299
|
+
each_predicate_cb(:having_clause, :having_clause=, &)
|
|
300
|
+
each_predicate_cb(:where_clause, :where_clause=, &)
|
|
301
|
+
end
|
|
304
302
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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)
|
|
311
368
|
end
|
|
312
369
|
|
|
313
|
-
|
|
314
|
-
predicate
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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)
|
|
372
|
+
|
|
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)
|
|
320
384
|
end
|
|
321
|
-
|
|
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, &)
|
|
395
|
+
|
|
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
|
+
|
|
322
416
|
# switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
|
|
323
|
-
if
|
|
324
|
-
klass =
|
|
325
|
-
klass.new(
|
|
417
|
+
if new_value.empty?
|
|
418
|
+
klass = (node.type == :in) ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
|
|
419
|
+
klass.new(node.attribute, new_value)
|
|
326
420
|
else
|
|
327
|
-
|
|
421
|
+
(old_value == new_value) ? node : node.class.new(new_value, node.attribute, node.type)
|
|
328
422
|
end
|
|
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
|
|
329
430
|
else
|
|
330
|
-
|
|
431
|
+
yield(node)
|
|
331
432
|
end
|
|
332
433
|
end
|
|
333
434
|
|
|
334
|
-
def
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if current_id == local_id
|
|
346
|
-
# make a new bind param
|
|
347
|
-
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)
|
|
348
446
|
else
|
|
349
|
-
|
|
447
|
+
primary_shard
|
|
350
448
|
end
|
|
351
|
-
|
|
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
|
|
352
461
|
else
|
|
353
|
-
|
|
354
|
-
local_id = [] if remove_non_local_ids && local_id.is_a?(Integer) && local_id > Shard::IDS_PER_SHARD
|
|
355
|
-
local_id
|
|
462
|
+
Shard.relative_id_for(value, current_shard, target_shard) || value
|
|
356
463
|
end
|
|
357
464
|
end
|
|
358
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
|
|