switchman 3.3.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +15 -14
- 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 +4 -2
- data/lib/switchman/active_record/associations.rb +89 -49
- data/lib/switchman/active_record/attribute_methods.rb +72 -34
- data/lib/switchman/active_record/base.rb +145 -27
- data/lib/switchman/active_record/calculations.rb +96 -49
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +24 -3
- data/lib/switchman/active_record/database_configurations.rb +37 -15
- data/lib/switchman/active_record/finder_methods.rb +44 -14
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +45 -3
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +30 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +49 -20
- data/lib/switchman/active_record/query_methods.rb +93 -30
- data/lib/switchman/active_record/relation.rb +23 -12
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +26 -16
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +34 -18
- data/lib/switchman/call_super.rb +2 -8
- data/lib/switchman/database_server.rb +69 -31
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +29 -22
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +13 -0
- data/lib/switchman/guard_rail/relation.rb +1 -2
- data/lib/switchman/parallel.rb +6 -6
- data/lib/switchman/r_spec_helper.rb +12 -11
- data/lib/switchman/shard.rb +180 -68
- data/lib/switchman/sharded_instrumenter.rb +3 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +4 -0
- data/lib/switchman/test_helper.rb +2 -2
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +27 -15
- data/lib/tasks/switchman.rake +96 -60
- metadata +38 -48
|
@@ -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
|
|
@@ -84,6 +97,14 @@ module Switchman
|
|
|
84
97
|
super(other.shard(primary_shard))
|
|
85
98
|
end
|
|
86
99
|
|
|
100
|
+
# use a temp variable so that the new where clause is built before self.where_clause is read,
|
|
101
|
+
# since build_where_clause might mutate self.where_clause
|
|
102
|
+
def where!(opts, *rest)
|
|
103
|
+
new_clause = build_where_clause(opts, rest)
|
|
104
|
+
self.where_clause += new_clause
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
|
|
87
108
|
protected
|
|
88
109
|
|
|
89
110
|
def remove_nonlocal_primary_keys!
|
|
@@ -93,7 +114,7 @@ module Switchman
|
|
|
93
114
|
predicate.left.relation.klass == klass &&
|
|
94
115
|
(predicate.is_a?(::Arel::Nodes::Equality) || predicate.is_a?(::Arel::Nodes::HomogeneousIn))
|
|
95
116
|
|
|
96
|
-
value.is_a?(Integer) && value > Shard::IDS_PER_SHARD ? [] : value
|
|
117
|
+
(value.is_a?(Integer) && value > Shard::IDS_PER_SHARD) ? [] : value
|
|
97
118
|
end
|
|
98
119
|
self
|
|
99
120
|
end
|
|
@@ -104,7 +125,9 @@ module Switchman
|
|
|
104
125
|
return unless klass.integral_id?
|
|
105
126
|
|
|
106
127
|
primary_key = predicates.detect do |predicate|
|
|
107
|
-
(predicate.is_a?(::Arel::Nodes::
|
|
128
|
+
(predicate.is_a?(::Arel::Nodes::Equality) ||
|
|
129
|
+
predicate.is_a?(::Arel::Nodes::In) ||
|
|
130
|
+
predicate.is_a?(::Arel::Nodes::HomogeneousIn)) &&
|
|
108
131
|
predicate.left.is_a?(::Arel::Attributes::Attribute) &&
|
|
109
132
|
predicate.left.relation.is_a?(::Arel::Table) && predicate.left.relation.klass == klass &&
|
|
110
133
|
klass.primary_key == predicate.left.name
|
|
@@ -167,19 +190,19 @@ module Switchman
|
|
|
167
190
|
end
|
|
168
191
|
|
|
169
192
|
def sharded_foreign_key?(relation, column)
|
|
170
|
-
models_for_table(relation.
|
|
193
|
+
models_for_table(relation.name).any? { |m| m.sharded_column?(column) }
|
|
171
194
|
end
|
|
172
195
|
|
|
173
196
|
def sharded_primary_key?(relation, column)
|
|
174
197
|
column = column.to_s
|
|
175
|
-
return column ==
|
|
198
|
+
return column == "id" if relation.klass == ::ActiveRecord::Base
|
|
176
199
|
|
|
177
200
|
relation.klass.primary_key == column && relation.klass.integral_id?
|
|
178
201
|
end
|
|
179
202
|
|
|
180
203
|
def source_shard_for_foreign_key(relation, column)
|
|
181
204
|
reflection = nil
|
|
182
|
-
models_for_table(relation.
|
|
205
|
+
models_for_table(relation.name).each do |model|
|
|
183
206
|
reflection = model.send(:reflection_for_integer_attribute, column)
|
|
184
207
|
break if reflection
|
|
185
208
|
end
|
|
@@ -199,12 +222,11 @@ module Switchman
|
|
|
199
222
|
|
|
200
223
|
case opts
|
|
201
224
|
when String, Array
|
|
202
|
-
values = Hash === rest.first ? rest.first.values : rest
|
|
225
|
+
values = (Hash === rest.first) ? rest.first.values : rest
|
|
203
226
|
|
|
204
|
-
values.grep(ActiveRecord::Relation)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
rel.shard!(primary_shard) if rel.shard_source_value == :implicit && rel.primary_shard != primary_shard
|
|
227
|
+
if shard_source_value != :explicit && values.grep(ActiveRecord::Relation).first
|
|
228
|
+
raise "Sub-queries are not allowed as simple substitutions; " \
|
|
229
|
+
"please build your relation with more structured methods so that Switchman is able to introspect it."
|
|
208
230
|
end
|
|
209
231
|
|
|
210
232
|
super
|
|
@@ -254,9 +276,10 @@ module Switchman
|
|
|
254
276
|
send(clause_setter, new_clause)
|
|
255
277
|
end
|
|
256
278
|
|
|
257
|
-
def each_transposable_predicate(predicates
|
|
279
|
+
def each_transposable_predicate(predicates, &block)
|
|
258
280
|
each_predicate(predicates) do |predicate|
|
|
259
|
-
|
|
281
|
+
case predicate
|
|
282
|
+
when ::Arel::Nodes::Grouping
|
|
260
283
|
next predicate unless predicate.expr.is_a?(::Arel::Nodes::Or)
|
|
261
284
|
|
|
262
285
|
or_expr = predicate.expr
|
|
@@ -267,6 +290,40 @@ module Switchman
|
|
|
267
290
|
next predicate if new_left == old_left && new_right == old_right
|
|
268
291
|
|
|
269
292
|
next predicate.class.new predicate.expr.class.new(new_left, new_right)
|
|
293
|
+
when ::Arel::Nodes::SelectStatement
|
|
294
|
+
new_cores = predicate.cores.map do |core|
|
|
295
|
+
next core unless core.is_a?(::Arel::Nodes::SelectCore) # just in case something weird is going on
|
|
296
|
+
|
|
297
|
+
new_wheres = each_transposable_predicate(core.wheres, &block)
|
|
298
|
+
new_havings = each_transposable_predicate(core.havings, &block)
|
|
299
|
+
|
|
300
|
+
next core if core.wheres == new_wheres && core.havings == new_havings
|
|
301
|
+
|
|
302
|
+
new_core = core.clone
|
|
303
|
+
new_core.wheres = new_wheres
|
|
304
|
+
new_core.havings = new_havings
|
|
305
|
+
new_core
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
next predicate if predicate.cores == new_cores
|
|
309
|
+
|
|
310
|
+
new_node = predicate.clone
|
|
311
|
+
new_node.instance_variable_set(:@cores, new_cores)
|
|
312
|
+
next new_node
|
|
313
|
+
when ::Arel::Nodes::Not
|
|
314
|
+
old_value = predicate.expr
|
|
315
|
+
new_value = each_transposable_predicate([old_value], &block).first
|
|
316
|
+
|
|
317
|
+
next predicate if old_value == new_value
|
|
318
|
+
|
|
319
|
+
next predicate.class.new(new_value)
|
|
320
|
+
when ::Arel::Nodes::Exists
|
|
321
|
+
old_value = predicate.expressions
|
|
322
|
+
new_value = each_transposable_predicate([old_value], &block).first
|
|
323
|
+
|
|
324
|
+
next predicate if old_value == new_value
|
|
325
|
+
|
|
326
|
+
next predicate.class.new(new_value)
|
|
270
327
|
end
|
|
271
328
|
|
|
272
329
|
next predicate unless predicate.is_a?(::Arel::Nodes::Binary) || predicate.is_a?(::Arel::Nodes::HomogeneousIn)
|
|
@@ -279,54 +336,56 @@ module Switchman
|
|
|
279
336
|
end
|
|
280
337
|
end
|
|
281
338
|
|
|
282
|
-
def each_transposable_predicate_value(predicates = nil)
|
|
339
|
+
def each_transposable_predicate_value(predicates = nil, &block)
|
|
283
340
|
each_transposable_predicate(predicates) do |predicate, relation, column, type|
|
|
284
|
-
each_transposable_predicate_value_cb(predicate) do |value|
|
|
341
|
+
each_transposable_predicate_value_cb(predicate, block) do |value|
|
|
285
342
|
yield(value, predicate, relation, column, type)
|
|
286
343
|
end
|
|
287
344
|
end
|
|
288
345
|
end
|
|
289
346
|
|
|
290
|
-
def each_transposable_predicate_value_cb(node, &block)
|
|
347
|
+
def each_transposable_predicate_value_cb(node, original_block, &block)
|
|
291
348
|
case node
|
|
292
349
|
when Array
|
|
293
|
-
node.
|
|
350
|
+
node.filter_map { |val| each_transposable_predicate_value_cb(val, original_block, &block).presence }
|
|
294
351
|
when ::ActiveModel::Attribute
|
|
295
352
|
old_value = node.value_before_type_cast
|
|
296
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
|
353
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
|
297
354
|
|
|
298
|
-
old_value == new_value ? node : node.class.new(node.name, new_value, node.type)
|
|
355
|
+
(old_value == new_value) ? node : node.class.new(node.name, new_value, node.type)
|
|
299
356
|
when ::Arel::Nodes::And
|
|
300
357
|
old_value = node.children
|
|
301
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
|
358
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
|
302
359
|
|
|
303
|
-
old_value == new_value ? node : node.class.new(new_value)
|
|
360
|
+
(old_value == new_value) ? node : node.class.new(new_value)
|
|
304
361
|
when ::Arel::Nodes::BindParam
|
|
305
362
|
old_value = node.value
|
|
306
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
|
363
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
|
307
364
|
|
|
308
|
-
old_value == new_value ? node : node.class.new(new_value)
|
|
365
|
+
(old_value == new_value) ? node : node.class.new(new_value)
|
|
309
366
|
when ::Arel::Nodes::Casted
|
|
310
367
|
old_value = node.value
|
|
311
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
|
368
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
|
312
369
|
|
|
313
|
-
old_value == new_value ? node : node.class.new(new_value, node.attribute)
|
|
370
|
+
(old_value == new_value) ? node : node.class.new(new_value, node.attribute)
|
|
314
371
|
when ::Arel::Nodes::HomogeneousIn
|
|
315
372
|
old_value = node.values
|
|
316
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
|
373
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
|
317
374
|
|
|
318
375
|
# switch to a regular In, so that Relation::WhereClause#contradiction? knows about it
|
|
319
376
|
if new_value.empty?
|
|
320
|
-
klass = node.type == :in ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
|
|
377
|
+
klass = (node.type == :in) ? ::Arel::Nodes::In : ::Arel::Nodes::NotIn
|
|
321
378
|
klass.new(node.attribute, new_value)
|
|
322
379
|
else
|
|
323
|
-
old_value == new_value ? node : node.class.new(new_value, node.attribute, node.type)
|
|
380
|
+
(old_value == new_value) ? node : node.class.new(new_value, node.attribute, node.type)
|
|
324
381
|
end
|
|
325
382
|
when ::Arel::Nodes::Binary
|
|
326
383
|
old_value = node.right
|
|
327
|
-
new_value = each_transposable_predicate_value_cb(old_value, &block)
|
|
384
|
+
new_value = each_transposable_predicate_value_cb(old_value, original_block, &block)
|
|
328
385
|
|
|
329
|
-
old_value == new_value ? node : node.class.new(node.left, new_value)
|
|
386
|
+
(old_value == new_value) ? node : node.class.new(node.left, new_value)
|
|
387
|
+
when ::Arel::Nodes::SelectStatement
|
|
388
|
+
each_transposable_predicate_value([node], &original_block).first
|
|
330
389
|
else
|
|
331
390
|
yield(node)
|
|
332
391
|
end
|
|
@@ -343,6 +402,8 @@ module Switchman
|
|
|
343
402
|
Shard.current(klass.connection_class_for_self)
|
|
344
403
|
elsif type == :foreign
|
|
345
404
|
source_shard_for_foreign_key(relation, column)
|
|
405
|
+
else
|
|
406
|
+
primary_shard
|
|
346
407
|
end
|
|
347
408
|
|
|
348
409
|
transpose_predicate_value(value, current_source_shard, target_shard, type)
|
|
@@ -350,7 +411,9 @@ module Switchman
|
|
|
350
411
|
end
|
|
351
412
|
|
|
352
413
|
def transpose_predicate_value(value, current_shard, target_shard, attribute_type)
|
|
353
|
-
if value.is_a?(
|
|
414
|
+
if value.is_a?(NonTransposingValue)
|
|
415
|
+
value
|
|
416
|
+
elsif value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
|
354
417
|
value.sharded = true # mark for transposition later
|
|
355
418
|
value.primary = true if attribute_type == :primary
|
|
356
419
|
value
|
|
@@ -4,7 +4,7 @@ module Switchman
|
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module Relation
|
|
6
6
|
def self.prepended(klass)
|
|
7
|
-
klass::SINGLE_VALUE_METHODS.
|
|
7
|
+
klass::SINGLE_VALUE_METHODS.push(:shard, :shard_source)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def initialize(*, **)
|
|
@@ -44,12 +44,18 @@ module Switchman
|
|
|
44
44
|
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if ::Rails.version > "7.1.2"
|
|
48
|
+
def transaction(...)
|
|
49
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def explain(*args)
|
|
54
|
+
activate { |relation| relation.call_super(:explain, Relation, *args) }
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
def load(&block)
|
|
52
|
-
if !loaded? ||
|
|
58
|
+
if !loaded? || scheduled?
|
|
53
59
|
@records = activate { |relation| relation.send(:exec_queries, &block) }
|
|
54
60
|
@loaded = true
|
|
55
61
|
end
|
|
@@ -58,10 +64,9 @@ module Switchman
|
|
|
58
64
|
end
|
|
59
65
|
|
|
60
66
|
%I[update_all delete_all].each do |method|
|
|
61
|
-
arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
|
|
62
67
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
63
|
-
def #{method}(
|
|
64
|
-
result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation,
|
|
68
|
+
def #{method}(*args, **kwargs)
|
|
69
|
+
result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args, **kwargs) }
|
|
65
70
|
result = result.sum if result.is_a?(Array)
|
|
66
71
|
result
|
|
67
72
|
end
|
|
@@ -73,12 +78,16 @@ module Switchman
|
|
|
73
78
|
loose_mode = options[:loose] && is_integer
|
|
74
79
|
# loose_mode: if we don't care about getting exactly batch_size ids in between
|
|
75
80
|
# don't get the max - just get the min and add batch_size so we get that many _at most_
|
|
76
|
-
values = loose_mode ?
|
|
81
|
+
values = loose_mode ? "MIN(id)" : "MIN(id), MAX(id)"
|
|
77
82
|
|
|
78
83
|
batch_size = options[:batch_size].try(:to_i) || 1000
|
|
79
|
-
quoted_primary_key =
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
quoted_primary_key =
|
|
85
|
+
"#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
|
|
86
|
+
as_id = " AS id" unless primary_key == "id"
|
|
87
|
+
subquery_scope = except(:select)
|
|
88
|
+
.select("#{quoted_primary_key}#{as_id}")
|
|
89
|
+
.reorder(primary_key.to_sym)
|
|
90
|
+
.limit(loose_mode ? 1 : batch_size)
|
|
82
91
|
subquery_scope = subquery_scope.where("#{quoted_primary_key} <= ?", options[:end_at]) if options[:end_at]
|
|
83
92
|
|
|
84
93
|
first_subquery_scope = if options[:start_at]
|
|
@@ -121,7 +130,9 @@ module Switchman
|
|
|
121
130
|
relation = shard(Shard.current(klass.connection_class_for_self))
|
|
122
131
|
relation.remove_nonlocal_primary_keys!
|
|
123
132
|
# do a minimal query if possible
|
|
124
|
-
|
|
133
|
+
if limit_value && !result_count.zero? && order_values.empty?
|
|
134
|
+
relation = relation.limit(limit_value - result_count)
|
|
135
|
+
end
|
|
125
136
|
|
|
126
137
|
shard_results = relation.activate(&block)
|
|
127
138
|
|
|
@@ -17,7 +17,7 @@ module Switchman
|
|
|
17
17
|
final_shard_source_value = %i[explicit association].detect do |source_value|
|
|
18
18
|
shard_source_value == source_value || rhs.shard_source_value == source_value
|
|
19
19
|
end
|
|
20
|
-
raise
|
|
20
|
+
raise "unknown shard_source_value" unless final_shard_source_value
|
|
21
21
|
|
|
22
22
|
# have to merge shard_value
|
|
23
23
|
lhs_shard_value = all_shards
|
|
@@ -36,7 +36,7 @@ module Switchman
|
|
|
36
36
|
final_shard_source_value = %i[explicit association implicit].detect do |source_value|
|
|
37
37
|
shard_source_value == source_value || rhs.shard_source_value == source_value
|
|
38
38
|
end
|
|
39
|
-
raise
|
|
39
|
+
raise "unknown shard_source_value" unless final_shard_source_value
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
[final_shard_value, final_primary_shard, final_shard_source_value]
|
|
@@ -4,8 +4,8 @@ module Switchman
|
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
module StatementCache
|
|
6
6
|
module ClassMethods
|
|
7
|
-
def create(connection
|
|
8
|
-
relation =
|
|
7
|
+
def create(connection)
|
|
8
|
+
relation = yield ::ActiveRecord::StatementCache::Params.new
|
|
9
9
|
|
|
10
10
|
_query_builder, binds = connection.cacheable_query(self, relation.arel)
|
|
11
11
|
bind_map = ::ActiveRecord::StatementCache::BindMap.new(binds)
|
|
@@ -7,9 +7,14 @@ module Switchman
|
|
|
7
7
|
def drop(*)
|
|
8
8
|
super
|
|
9
9
|
# no really, it's gone
|
|
10
|
-
Switchman.cache.delete(
|
|
10
|
+
Switchman.cache.delete("default_shard")
|
|
11
11
|
Shard.default(reload: true)
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
def raise_for_multi_db(*)
|
|
15
|
+
# ignore; Switchman doesn't use namespaced tasks for multiple shards; it uses
|
|
16
|
+
# environment variables to filter which shards you want to target
|
|
17
|
+
end
|
|
13
18
|
end
|
|
14
19
|
end
|
|
15
20
|
end
|
|
@@ -12,31 +12,41 @@ module Switchman
|
|
|
12
12
|
# Replace the one that activerecord natively uses with a switchman-optimized one
|
|
13
13
|
::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
|
|
14
14
|
# Code adapted from the code in rails proper
|
|
15
|
-
@connection_subscriber =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
@connection_subscriber =
|
|
16
|
+
::ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
|
17
|
+
spec_name = if ::Rails.version < "7.1"
|
|
18
|
+
payload[:spec_name] if payload.key?(:spec_name)
|
|
19
|
+
elsif payload.key?(:connection_name)
|
|
20
|
+
payload[:connection_name]
|
|
21
|
+
end
|
|
22
|
+
shard = payload[:shard] if payload.key?(:shard)
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
|
|
25
|
+
begin
|
|
26
|
+
connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
|
|
27
|
+
connection.connect! if ::Rails.version >= "7.1" # eagerly validate the connection
|
|
28
|
+
rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
|
|
29
|
+
connection = nil
|
|
30
|
+
end
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
if connection
|
|
33
|
+
setup_shared_connection_pool
|
|
34
|
+
unless @fixture_connections.include?(connection)
|
|
35
|
+
connection.begin_transaction joinable: false, _lazy: false
|
|
36
|
+
connection.pool.lock_thread = true if lock_threads
|
|
37
|
+
@fixture_connections << connection
|
|
38
|
+
end
|
|
39
|
+
end
|
|
31
40
|
end
|
|
32
41
|
end
|
|
33
|
-
end
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
def enlist_fixture_connections
|
|
37
45
|
setup_shared_connection_pool
|
|
38
46
|
|
|
39
|
-
::ActiveRecord::Base.connection_handler.connection_pool_list.reject
|
|
47
|
+
::ActiveRecord::Base.connection_handler.connection_pool_list(:primary).reject do |cp|
|
|
48
|
+
FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
|
|
49
|
+
end.map(&:connection)
|
|
40
50
|
end
|
|
41
51
|
end
|
|
42
52
|
end
|
|
@@ -22,9 +22,14 @@ module Switchman
|
|
|
22
22
|
|
|
23
23
|
def lookup_store(*store_options)
|
|
24
24
|
store = super
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
25
|
+
# must use the string name, otherwise it will try to auto-load the constant
|
|
26
|
+
# and we don't want to require redis in this file (since it's not a hard dependency)
|
|
27
|
+
# rubocop:disable Style/ClassEqualityComparison
|
|
28
|
+
if store.class.name == "ActiveSupport::Cache::RedisCacheStore" &&
|
|
29
|
+
!(::ActiveSupport::Cache::RedisCacheStore <= RedisCacheStore)
|
|
30
|
+
::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
|
|
31
|
+
end
|
|
32
|
+
# rubocop:enable Style/ClassEqualityComparison
|
|
28
33
|
store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
|
|
29
34
|
store
|
|
30
35
|
end
|
|
@@ -33,7 +38,7 @@ module Switchman
|
|
|
33
38
|
module RedisCacheStore
|
|
34
39
|
def clear(namespace: nil, **)
|
|
35
40
|
# RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
|
|
36
|
-
# unfortunately, it
|
|
41
|
+
# unfortunately, it doesn't work using redis clustering because of the way redis keys are distributed
|
|
37
42
|
# fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
|
|
38
43
|
# always unset it temporarily for clear calls
|
|
39
44
|
namespace = nil # rubocop:disable Lint/ShadowedArgument
|
data/lib/switchman/arel.rb
CHANGED
|
@@ -13,38 +13,54 @@ module Switchman
|
|
|
13
13
|
# rubocop:disable Naming/MethodName
|
|
14
14
|
# rubocop:disable Naming/MethodParameterName
|
|
15
15
|
|
|
16
|
+
def visit_Arel_Nodes_Cte(o, collector)
|
|
17
|
+
collector << quote_local_table_name(o.name)
|
|
18
|
+
collector << " AS "
|
|
19
|
+
|
|
20
|
+
case o.materialized
|
|
21
|
+
when true
|
|
22
|
+
collector << "MATERIALIZED "
|
|
23
|
+
when false
|
|
24
|
+
collector << "NOT MATERIALIZED "
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
visit o.relation, collector
|
|
28
|
+
end
|
|
29
|
+
|
|
16
30
|
def visit_Arel_Nodes_TableAlias(o, collector)
|
|
17
31
|
collector = visit o.relation, collector
|
|
18
|
-
collector <<
|
|
32
|
+
collector << " "
|
|
19
33
|
collector << quote_local_table_name(o.name)
|
|
20
34
|
end
|
|
21
35
|
|
|
22
36
|
def visit_Arel_Attributes_Attribute(o, collector)
|
|
23
37
|
join_name = o.relation.table_alias || o.relation.name
|
|
24
|
-
collector << quote_local_table_name(join_name) <<
|
|
38
|
+
collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
|
|
25
39
|
end
|
|
26
40
|
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
if ::Rails.version < "7.1"
|
|
42
|
+
def visit_Arel_Nodes_HomogeneousIn(o, collector)
|
|
43
|
+
collector.preparable = false
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
collector << quote_local_table_name(o.table_name) << "." << quote_column_name(o.column_name)
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
collector << if o.type == :in
|
|
48
|
+
" IN ("
|
|
49
|
+
else
|
|
50
|
+
" NOT IN ("
|
|
51
|
+
end
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
values = o.casted_values
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
if values.empty?
|
|
56
|
+
collector << @connection.quote(nil)
|
|
57
|
+
else
|
|
58
|
+
collector.add_binds(values, o.proc_for_binds, &bind_block)
|
|
59
|
+
end
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
collector << ")"
|
|
62
|
+
collector
|
|
63
|
+
end
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
# rubocop:enable Naming/MethodName
|
data/lib/switchman/call_super.rb
CHANGED
|
@@ -12,14 +12,8 @@ module Switchman
|
|
|
12
12
|
method.super_method
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
super_method_above(method, above_module).call(*args, &block)
|
|
18
|
-
end
|
|
19
|
-
else
|
|
20
|
-
def call_super(method, above_module, *args, **kwargs, &block)
|
|
21
|
-
super_method_above(method, above_module).call(*args, **kwargs, &block)
|
|
22
|
-
end
|
|
15
|
+
def call_super(method, above_module, ...)
|
|
16
|
+
super_method_above(method, above_module).call(...)
|
|
23
17
|
end
|
|
24
18
|
end
|
|
25
19
|
end
|