switchman 3.0.2 → 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 +92 -49
- 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 +251 -140
- data/lib/switchman/active_record/reflection.rb +10 -3
- data/lib/switchman/active_record/relation.rb +110 -35
- 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 +44 -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
|
@@ -4,18 +4,18 @@ 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(*, **)
|
|
11
11
|
super
|
|
12
|
-
self.shard_value = Shard.current(klass ? klass.
|
|
12
|
+
self.shard_value = Shard.current(klass ? klass.connection_class_for_self : :primary) unless shard_value
|
|
13
13
|
self.shard_source_value = :implicit unless shard_source_value
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def clone
|
|
17
17
|
result = super
|
|
18
|
-
result.shard_value = Shard.current(klass ? klass.
|
|
18
|
+
result.shard_value = Shard.current(klass ? klass.connection_class_for_self : :primary) unless shard_value
|
|
19
19
|
result
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -28,59 +28,119 @@ module Switchman
|
|
|
28
28
|
relation
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def new(*, &
|
|
32
|
-
primary_shard.activate(klass.
|
|
31
|
+
def new(*, &)
|
|
32
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def create(*, &
|
|
36
|
-
primary_shard.activate(klass.
|
|
35
|
+
def create(*, &)
|
|
36
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def create!(*, &
|
|
40
|
-
primary_shard.activate(klass.
|
|
39
|
+
def create!(*, &)
|
|
40
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def to_sql
|
|
44
|
-
primary_shard.activate(klass.
|
|
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
|
|
49
51
|
end
|
|
50
52
|
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
+
def explain(*args)
|
|
54
|
+
activate { |relation| relation.call_super(:explain, Relation, *args) }
|
|
55
|
+
end
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@records = results
|
|
57
|
+
def load(&block)
|
|
58
|
+
if !loaded? || scheduled?
|
|
59
|
+
@records = activate { |relation| relation.send(:exec_queries, &block) }
|
|
58
60
|
@loaded = true
|
|
59
61
|
end
|
|
60
|
-
|
|
62
|
+
|
|
63
|
+
self
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
%I[update_all delete_all].each do |method|
|
|
64
67
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
65
|
-
def #{method}(*args)
|
|
66
|
-
result = self.activate { |relation| relation.call_super(#{method.inspect}, Relation, *args) }
|
|
68
|
+
def #{method}(*args, **kwargs)
|
|
69
|
+
result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args, **kwargs) }
|
|
67
70
|
result = result.sum if result.is_a?(Array)
|
|
68
71
|
result
|
|
69
72
|
end
|
|
70
73
|
RUBY
|
|
71
74
|
end
|
|
72
75
|
|
|
76
|
+
# https://github.com/rails/rails/commit/ed2c15b52450ff927a05629f031376f25b670335
|
|
77
|
+
# once the minimum version is Rails 7.2, we can drop this separate module
|
|
78
|
+
module InsertUpsertAll
|
|
79
|
+
%w[insert_all upsert_all].each do |method|
|
|
80
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
81
|
+
def #{method}(attributes, returning: nil, **)
|
|
82
|
+
scope = self != ::ActiveRecord::Base && current_scope
|
|
83
|
+
if (target_shard = scope&.primary_shard) == (current_shard = Shard.current(connection_class_for_self))
|
|
84
|
+
scope = nil
|
|
85
|
+
end
|
|
86
|
+
if scope
|
|
87
|
+
dupped = false
|
|
88
|
+
attributes.each_with_index do |hash, i|
|
|
89
|
+
if dupped || hash.any? { |k, v| sharded_column?(k) }
|
|
90
|
+
unless dupped
|
|
91
|
+
attributes = attributes.dup
|
|
92
|
+
dupped = true
|
|
93
|
+
end
|
|
94
|
+
attributes[i] = hash.to_h do |k, v|
|
|
95
|
+
if sharded_column?(k)
|
|
96
|
+
[k, Shard.relative_id_for(v, current_shard, target_shard)]
|
|
97
|
+
else
|
|
98
|
+
[k, v]
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if scope
|
|
106
|
+
scope.activate do
|
|
107
|
+
db = Shard.current(connection_class_for_self).database_server
|
|
108
|
+
result = db.unguard { super }
|
|
109
|
+
if result&.columns&.any? { |c| sharded_column?(c) }
|
|
110
|
+
transposed_rows = result.rows.map do |row|
|
|
111
|
+
row.map.with_index do |value, i|
|
|
112
|
+
sharded_column?(result.columns[i]) ? Shard.relative_id_for(value, target_shard, current_shard) : value
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
result = ::ActiveRecord::Result.new(result.columns, transposed_rows, result.column_types)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
db = Shard.current(connection_class_for_self).database_server
|
|
122
|
+
db.unguard { super }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
RUBY
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
73
129
|
def find_ids_in_ranges(options = {})
|
|
74
130
|
is_integer = columns_hash[primary_key.to_s].type == :integer
|
|
75
131
|
loose_mode = options[:loose] && is_integer
|
|
76
132
|
# loose_mode: if we don't care about getting exactly batch_size ids in between
|
|
77
133
|
# don't get the max - just get the min and add batch_size so we get that many _at most_
|
|
78
|
-
values = loose_mode ?
|
|
134
|
+
values = loose_mode ? "MIN(id)" : "MIN(id), MAX(id)"
|
|
79
135
|
|
|
80
136
|
batch_size = options[:batch_size].try(:to_i) || 1000
|
|
81
|
-
quoted_primary_key =
|
|
82
|
-
|
|
83
|
-
|
|
137
|
+
quoted_primary_key =
|
|
138
|
+
"#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
|
|
139
|
+
as_id = " AS id" unless primary_key == "id"
|
|
140
|
+
subquery_scope = except(:select)
|
|
141
|
+
.select("#{quoted_primary_key}#{as_id}")
|
|
142
|
+
.reorder(primary_key.to_sym)
|
|
143
|
+
.limit(loose_mode ? 1 : batch_size)
|
|
84
144
|
subquery_scope = subquery_scope.where("#{quoted_primary_key} <= ?", options[:end_at]) if options[:end_at]
|
|
85
145
|
|
|
86
146
|
first_subquery_scope = if options[:start_at]
|
|
@@ -94,7 +154,7 @@ module Switchman
|
|
|
94
154
|
|
|
95
155
|
while ids.first.present?
|
|
96
156
|
ids.map!(&:to_i) if is_integer
|
|
97
|
-
ids << ids.first + batch_size if loose_mode
|
|
157
|
+
ids << (ids.first + batch_size) if loose_mode
|
|
98
158
|
|
|
99
159
|
yield(*ids)
|
|
100
160
|
last_value = ids.last
|
|
@@ -103,28 +163,39 @@ module Switchman
|
|
|
103
163
|
end
|
|
104
164
|
end
|
|
105
165
|
|
|
106
|
-
def activate(&block)
|
|
166
|
+
def activate(count: false, unordered: false, &block)
|
|
107
167
|
shards = all_shards
|
|
108
168
|
if Array === shards && shards.length == 1
|
|
109
|
-
if
|
|
169
|
+
if !loaded? && shard_value != shards.first
|
|
170
|
+
shard(shards.first).activate(&block)
|
|
171
|
+
elsif shards.first == DefaultShard || shards.first == Shard.current(klass.connection_class_for_self)
|
|
110
172
|
yield(self, shards.first)
|
|
111
173
|
else
|
|
112
|
-
shards.first.activate(klass.
|
|
174
|
+
shards.first.activate(klass.connection_class_for_self) { yield(self, shards.first) }
|
|
113
175
|
end
|
|
114
176
|
else
|
|
115
177
|
result_count = 0
|
|
116
178
|
can_order = false
|
|
117
|
-
result = Shard.with_each_shard(shards, [klass.
|
|
179
|
+
result = Shard.with_each_shard(shards, [klass.connection_class_for_self]) do
|
|
118
180
|
# don't even query other shards if we're already past the limit
|
|
119
181
|
next if limit_value && result_count >= limit_value && order_values.empty?
|
|
120
182
|
|
|
121
|
-
relation = shard(Shard.current(klass.
|
|
183
|
+
relation = shard(Shard.current(klass.connection_class_for_self))
|
|
184
|
+
relation.remove_nonlocal_primary_keys!
|
|
122
185
|
# do a minimal query if possible
|
|
123
|
-
|
|
186
|
+
if limit_value && !result_count.zero? && order_values.empty?
|
|
187
|
+
relation = relation.limit(limit_value - result_count)
|
|
188
|
+
end
|
|
124
189
|
|
|
125
190
|
shard_results = relation.activate(&block)
|
|
126
191
|
|
|
127
|
-
if shard_results.present?
|
|
192
|
+
if shard_results.present? && count
|
|
193
|
+
unless shard_results.is_a?(Integer)
|
|
194
|
+
raise "expected integer result for count, got #{shard_results.class.name}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
result_count += shard_results
|
|
198
|
+
elsif shard_results.present? && !unordered
|
|
128
199
|
can_order ||= can_order_cross_shard_results? unless order_values.empty?
|
|
129
200
|
raise OrderOnMultiShardQuery if !can_order && !order_values.empty? && result_count.positive?
|
|
130
201
|
|
|
@@ -148,8 +219,12 @@ module Switchman
|
|
|
148
219
|
results.sort! do |l, r|
|
|
149
220
|
result = 0
|
|
150
221
|
order_values.each do |ov|
|
|
151
|
-
|
|
152
|
-
|
|
222
|
+
if l.is_a?(::ActiveRecord::Base)
|
|
223
|
+
a = l.attribute(ov.expr.name)
|
|
224
|
+
b = r.attribute(ov.expr.name)
|
|
225
|
+
else
|
|
226
|
+
a, b = l, r
|
|
227
|
+
end
|
|
153
228
|
next if a == b
|
|
154
229
|
|
|
155
230
|
if a.nil? || b.nil?
|
|
@@ -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]
|
|
@@ -62,16 +62,12 @@ module Switchman
|
|
|
62
62
|
if primary_shard != final_primary_shard && rhs.primary_shard != final_primary_shard
|
|
63
63
|
shard!(final_primary_shard)
|
|
64
64
|
rhs = rhs.shard(final_primary_shard)
|
|
65
|
-
super(rhs)
|
|
66
65
|
elsif primary_shard != final_primary_shard
|
|
67
66
|
shard!(final_primary_shard)
|
|
68
|
-
super(rhs)
|
|
69
67
|
elsif rhs.primary_shard != final_primary_shard
|
|
70
68
|
rhs = rhs.shard(final_primary_shard)
|
|
71
|
-
super(rhs)
|
|
72
|
-
else
|
|
73
|
-
super
|
|
74
69
|
end
|
|
70
|
+
super
|
|
75
71
|
|
|
76
72
|
self.shard_value = final_shard_value
|
|
77
73
|
self.shard_source_value = 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)
|
|
@@ -25,22 +25,22 @@ module Switchman
|
|
|
25
25
|
# we can make some assumptions about the shard source
|
|
26
26
|
# (e.g. infer from the primary key or use the current shard)
|
|
27
27
|
|
|
28
|
-
def execute(*args)
|
|
28
|
+
def execute(*args, &block)
|
|
29
29
|
params, connection = args
|
|
30
30
|
klass = @klass
|
|
31
31
|
target_shard = nil
|
|
32
|
-
if (primary_index = bind_map.primary_value_index)
|
|
32
|
+
if (primary_index = @bind_map.primary_value_index)
|
|
33
33
|
primary_value = params[primary_index]
|
|
34
34
|
target_shard = Shard.local_id_for(primary_value)[1]
|
|
35
35
|
end
|
|
36
|
-
current_shard = Shard.current(klass.
|
|
36
|
+
current_shard = Shard.current(klass.connection_class_for_self)
|
|
37
37
|
target_shard ||= current_shard
|
|
38
38
|
|
|
39
|
-
bind_values = bind_map.bind(params, current_shard, target_shard)
|
|
39
|
+
bind_values = @bind_map.bind(params, current_shard, target_shard)
|
|
40
40
|
|
|
41
|
-
target_shard.activate(klass.
|
|
41
|
+
target_shard.activate(klass.connection_class_for_self) do
|
|
42
42
|
sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
|
|
43
|
-
klass.find_by_sql(sql, bind_values)
|
|
43
|
+
klass.find_by_sql(sql, bind_values, &block)
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -66,7 +66,11 @@ module Switchman
|
|
|
66
66
|
|
|
67
67
|
def primary_value_index
|
|
68
68
|
primary_ba_index = @bound_attributes.index do |ba|
|
|
69
|
-
ba.is_a?(::ActiveRecord::
|
|
69
|
+
if ba.value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
|
70
|
+
ba.is_a?(::ActiveRecord::Relation::QueryAttribute) && ba.value.primary
|
|
71
|
+
else
|
|
72
|
+
false
|
|
73
|
+
end
|
|
70
74
|
end
|
|
71
75
|
@indexes.index(primary_ba_index) if primary_ba_index
|
|
72
76
|
end
|
|
@@ -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
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Switchman
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module TestFixtures
|
|
6
|
+
FORBIDDEN_DB_ENVS = %i[development production].freeze
|
|
7
|
+
|
|
8
|
+
if ::Rails.version < "7.2"
|
|
9
|
+
def setup_fixtures(config = ::ActiveRecord::Base)
|
|
10
|
+
super
|
|
11
|
+
return unless run_in_transaction?
|
|
12
|
+
|
|
13
|
+
# Replace the one that activerecord natively uses with a switchman-optimized one
|
|
14
|
+
::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
|
|
15
|
+
# Code adapted from the code in rails proper
|
|
16
|
+
@connection_subscriber =
|
|
17
|
+
::ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
|
18
|
+
spec_name = (payload[:connection_name] if payload.key?(:connection_name))
|
|
19
|
+
shard = payload[:shard] if payload.key?(:shard)
|
|
20
|
+
|
|
21
|
+
if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
|
|
22
|
+
begin
|
|
23
|
+
connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard:)
|
|
24
|
+
connection.connect! # eagerly validate the connection
|
|
25
|
+
rescue ::ActiveRecord::ConnectionNotEstablished
|
|
26
|
+
connection = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if connection
|
|
30
|
+
setup_shared_connection_pool
|
|
31
|
+
unless @fixture_connections.include?(connection)
|
|
32
|
+
connection.begin_transaction joinable: false, _lazy: false
|
|
33
|
+
connection.pool.lock_thread = true if lock_threads
|
|
34
|
+
@fixture_connections << connection
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def enlist_fixture_connections
|
|
42
|
+
setup_shared_connection_pool
|
|
43
|
+
|
|
44
|
+
::ActiveRecord::Base.connection_handler.connection_pool_list(:primary).reject do |cp|
|
|
45
|
+
FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
|
|
46
|
+
end.map(&:connection)
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
def setup_transactional_fixtures
|
|
50
|
+
setup_shared_connection_pool
|
|
51
|
+
|
|
52
|
+
# Begin transactions for connections already established
|
|
53
|
+
# INST: :writing -> :primary
|
|
54
|
+
@fixture_connection_pools = ::ActiveRecord::Base.connection_handler.connection_pool_list(:primary)
|
|
55
|
+
# INST: filter by FORBIDDEN_DB_ENVS
|
|
56
|
+
@fixture_connection_pools = @fixture_connection_pools.reject do |cp|
|
|
57
|
+
FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@fixture_connection_pools.each do |pool|
|
|
61
|
+
pool.pin_connection!(lock_threads)
|
|
62
|
+
pool.lease_connection
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# When connections are established in the future, begin a transaction too
|
|
66
|
+
@connection_subscriber = ::ActiveSupport::Notifications
|
|
67
|
+
.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
|
68
|
+
connection_name = payload[:connection_name] if payload.key?(:connection_name)
|
|
69
|
+
shard = payload[:shard] if payload.key?(:shard)
|
|
70
|
+
|
|
71
|
+
# INST: filter by FORBIDDEN_DB_ENVS
|
|
72
|
+
if connection_name && !FORBIDDEN_DB_ENVS.include?(shard)
|
|
73
|
+
pool = ::ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard:)
|
|
74
|
+
if pool
|
|
75
|
+
setup_shared_connection_pool
|
|
76
|
+
|
|
77
|
+
unless @fixture_connection_pools.include?(pool)
|
|
78
|
+
pool.pin_connection!(lock_threads)
|
|
79
|
+
pool.lease_connection
|
|
80
|
+
@fixture_connection_pools << pool
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -4,11 +4,32 @@ module Switchman
|
|
|
4
4
|
module ActiveSupport
|
|
5
5
|
module Cache
|
|
6
6
|
module ClassMethods
|
|
7
|
+
def lookup_stores(cache_store_config)
|
|
8
|
+
result = {}
|
|
9
|
+
cache_store_config.each do |key, value|
|
|
10
|
+
next if value.is_a?(String)
|
|
11
|
+
|
|
12
|
+
result[key] = ::ActiveSupport::Cache.lookup_store(value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
cache_store_config.each do |key, value| # rubocop:disable Style/CombinableLoops
|
|
16
|
+
next unless value.is_a?(String)
|
|
17
|
+
|
|
18
|
+
result[key] = result[value]
|
|
19
|
+
end
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
7
23
|
def lookup_store(*store_options)
|
|
8
24
|
store = super
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
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
|
|
12
33
|
store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
|
|
13
34
|
store
|
|
14
35
|
end
|
|
@@ -17,7 +38,7 @@ module Switchman
|
|
|
17
38
|
module RedisCacheStore
|
|
18
39
|
def clear(namespace: nil, **)
|
|
19
40
|
# RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
|
|
20
|
-
# unfortunately, it
|
|
41
|
+
# unfortunately, it doesn't work using redis clustering because of the way redis keys are distributed
|
|
21
42
|
# fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
|
|
22
43
|
# always unset it temporarily for clear calls
|
|
23
44
|
namespace = nil # rubocop:disable Lint/ShadowedArgument
|
data/lib/switchman/arel.rb
CHANGED
|
@@ -11,22 +11,35 @@ module Switchman
|
|
|
11
11
|
module Visitors
|
|
12
12
|
module ToSql
|
|
13
13
|
# rubocop:disable Naming/MethodName
|
|
14
|
+
# rubocop:disable Naming/MethodParameterName
|
|
14
15
|
|
|
15
|
-
def
|
|
16
|
-
|
|
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
|
+
|
|
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
|
-
def visit_Arel_Attributes_Attribute(
|
|
23
|
-
o = args.first
|
|
36
|
+
def visit_Arel_Attributes_Attribute(o, collector)
|
|
24
37
|
join_name = o.relation.table_alias || o.relation.name
|
|
25
|
-
|
|
26
|
-
args.last << result
|
|
38
|
+
collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
|
|
27
39
|
end
|
|
28
40
|
|
|
29
41
|
# rubocop:enable Naming/MethodName
|
|
42
|
+
# rubocop:enable Naming/MethodParameterName
|
|
30
43
|
|
|
31
44
|
def quote_local_table_name(name)
|
|
32
45
|
return name if ::Arel::Nodes::SqlLiteral === name
|
data/lib/switchman/call_super.rb
CHANGED
|
@@ -12,8 +12,8 @@ module Switchman
|
|
|
12
12
|
method.super_method
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def call_super(method, above_module,
|
|
16
|
-
super_method_above(method, above_module).call(
|
|
15
|
+
def call_super(method, above_module, ...)
|
|
16
|
+
super_method_above(method, above_module).call(...)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|