switchman 3.4.2 → 3.6.7
Sign up to get free protection for your applications and to get access to all the features.
- 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 -16
- data/lib/switchman/active_record/attribute_methods.rb +67 -22
- data/lib/switchman/active_record/base.rb +112 -22
- data/lib/switchman/active_record/calculations.rb +93 -37
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +18 -14
- 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 +28 -9
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +22 -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 +22 -11
- 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 +68 -21
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +39 -19
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +4 -1
- data/lib/switchman/guard_rail/relation.rb +1 -2
- data/lib/switchman/parallel.rb +5 -5
- data/lib/switchman/r_spec_helper.rb +11 -11
- data/lib/switchman/shard.rb +166 -64
- data/lib/switchman/sharded_instrumenter.rb +7 -3
- 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 +117 -51
- metadata +19 -44
@@ -22,13 +22,59 @@ module Switchman
|
|
22
22
|
@integral_id
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
def transaction(**)
|
26
|
+
if self != ::ActiveRecord::Base && current_scope
|
27
|
+
current_scope.activate do
|
28
|
+
db = Shard.current(connection_class_for_self).database_server
|
29
|
+
db.unguard { super }
|
30
|
+
end
|
31
|
+
else
|
32
|
+
db = Shard.current(connection_class_for_self).database_server
|
33
|
+
db.unguard { super }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# NOTE: `returning` values are _not_ transposed back to the current shard
|
38
|
+
%w[insert_all upsert_all].each do |method|
|
26
39
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
27
|
-
def #{method}(
|
28
|
-
|
29
|
-
|
40
|
+
def #{method}(attributes, returning: nil, **)
|
41
|
+
scope = self != ::ActiveRecord::Base && current_scope
|
42
|
+
if (target_shard = scope&.primary_shard) == (current_shard = Shard.current(connection_class_for_self))
|
43
|
+
scope = nil
|
44
|
+
end
|
45
|
+
if scope
|
46
|
+
dupped = false
|
47
|
+
attributes.each_with_index do |hash, i|
|
48
|
+
if dupped || hash.any? { |k, v| sharded_column?(k) }
|
49
|
+
unless dupped
|
50
|
+
attributes = attributes.dup
|
51
|
+
dupped = true
|
52
|
+
end
|
53
|
+
attributes[i] = hash.to_h do |k, v|
|
54
|
+
if sharded_column?(k)
|
55
|
+
[k, Shard.relative_id_for(v, current_shard, target_shard)]
|
56
|
+
else
|
57
|
+
[k, v]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if scope
|
65
|
+
scope.activate do
|
30
66
|
db = Shard.current(connection_class_for_self).database_server
|
31
|
-
db.unguard { super }
|
67
|
+
result = db.unguard { super }
|
68
|
+
if result&.columns&.any? { |c| sharded_column?(c) }
|
69
|
+
transposed_rows = result.rows.map do |row|
|
70
|
+
row.map.with_index do |value, i|
|
71
|
+
sharded_column?(result.columns[i]) ? Shard.relative_id_for(value, target_shard, current_shard) : value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
result = ::ActiveRecord::Result.new(result.columns, transposed_rows, result.column_types)
|
75
|
+
end
|
76
|
+
|
77
|
+
result
|
32
78
|
end
|
33
79
|
else
|
34
80
|
db = Shard.current(connection_class_for_self).database_server
|
@@ -57,7 +103,12 @@ module Switchman
|
|
57
103
|
end
|
58
104
|
|
59
105
|
def clear_query_caches_for_current_thread
|
60
|
-
::
|
106
|
+
pools = if ::Rails.version < "7.1"
|
107
|
+
::ActiveRecord::Base.connection_handler.connection_pool_list
|
108
|
+
else
|
109
|
+
::ActiveRecord::Base.connection_handler.connection_pool_list(:all)
|
110
|
+
end
|
111
|
+
pools.each do |pool|
|
61
112
|
pool.connection(switch_shard: false).clear_query_cache if pool.active_connection?
|
62
113
|
end
|
63
114
|
end
|
@@ -67,7 +118,10 @@ module Switchman
|
|
67
118
|
end
|
68
119
|
|
69
120
|
def establish_connection(config_or_env = nil)
|
70
|
-
|
121
|
+
if config_or_env.is_a?(Symbol) && config_or_env != ::Rails.env.to_sym
|
122
|
+
raise ArgumentError,
|
123
|
+
"establish connection cannot be used on the non-current shard/role"
|
124
|
+
end
|
71
125
|
|
72
126
|
# Ensure we don't randomly surprise change the connection parms associated with a shard/role
|
73
127
|
config_or_env = nil if config_or_env == ::Rails.env.to_sym
|
@@ -75,16 +129,22 @@ module Switchman
|
|
75
129
|
config_or_env ||= if current_shard == ::Rails.env.to_sym && current_role == :primary
|
76
130
|
:primary
|
77
131
|
else
|
78
|
-
"#{current_shard}/#{current_role}"
|
132
|
+
:"#{current_shard}/#{current_role}"
|
79
133
|
end
|
80
134
|
|
81
135
|
super(config_or_env)
|
82
136
|
end
|
83
137
|
|
84
138
|
def connected_to_stack
|
85
|
-
|
139
|
+
has_own_stack = if ::Rails.version < "7.0"
|
140
|
+
Thread.current.thread_variable?(:ar_connected_to_stack)
|
141
|
+
else
|
142
|
+
::ActiveSupport::IsolatedExecutionState.key?(:active_record_connected_to_stack)
|
143
|
+
end
|
86
144
|
|
87
145
|
ret = super
|
146
|
+
return ret if has_own_stack
|
147
|
+
|
88
148
|
DatabaseServer.guard_servers
|
89
149
|
ret
|
90
150
|
end
|
@@ -96,10 +156,13 @@ module Switchman
|
|
96
156
|
sharded_role = nil
|
97
157
|
connected_to_stack.reverse_each do |hash|
|
98
158
|
shard_role = hash.dig(:shard_roles, target_shard)
|
99
|
-
|
100
|
-
|
101
|
-
|
159
|
+
unless shard_role &&
|
160
|
+
(hash[:klasses].include?(::ActiveRecord::Base) || hash[:klasses].include?(connection_class_for_self))
|
161
|
+
next
|
102
162
|
end
|
163
|
+
|
164
|
+
sharded_role = shard_role
|
165
|
+
break
|
103
166
|
end
|
104
167
|
# Allow a shard-specific role to be reverted to regular inheritance
|
105
168
|
return sharded_role if sharded_role && sharded_role != :_switchman_inherit
|
@@ -119,13 +182,15 @@ module Switchman
|
|
119
182
|
|
120
183
|
def current_switchman_shard
|
121
184
|
connected_to_stack.reverse_each do |hash|
|
122
|
-
|
185
|
+
if hash[:switchman_shard] && hash[:klasses].include?(connection_class_for_self)
|
186
|
+
return hash[:switchman_shard]
|
187
|
+
end
|
123
188
|
end
|
124
189
|
|
125
190
|
Shard.default
|
126
191
|
end
|
127
192
|
|
128
|
-
if ::Rails.version <
|
193
|
+
if ::Rails.version < "7.0"
|
129
194
|
def connection_class_for_self
|
130
195
|
connection_classes
|
131
196
|
end
|
@@ -134,6 +199,13 @@ module Switchman
|
|
134
199
|
|
135
200
|
def self.prepended(klass)
|
136
201
|
klass.singleton_class.prepend(ClassMethods)
|
202
|
+
klass.scope :non_shadow, lambda { |key = primary_key|
|
203
|
+
where(key => (QueryMethods::NonTransposingValue.new(0)..
|
204
|
+
QueryMethods::NonTransposingValue.new(Shard::IDS_PER_SHARD)))
|
205
|
+
}
|
206
|
+
klass.scope :shadow, lambda { |key = primary_key|
|
207
|
+
where(key => QueryMethods::NonTransposingValue.new(Shard::IDS_PER_SHARD)..)
|
208
|
+
}
|
137
209
|
end
|
138
210
|
|
139
211
|
def _run_initialize_callbacks
|
@@ -144,7 +216,15 @@ module Switchman
|
|
144
216
|
end
|
145
217
|
|
146
218
|
@loaded_from_shard ||= Shard.current(self.class.connection_class_for_self)
|
147
|
-
|
219
|
+
if shadow_record? && !Switchman.config[:writable_shadow_records]
|
220
|
+
@readonly = true
|
221
|
+
@readonly_from_shadow ||= true
|
222
|
+
end
|
223
|
+
super
|
224
|
+
end
|
225
|
+
|
226
|
+
def readonly!
|
227
|
+
@readonly_from_shadow = false
|
148
228
|
super
|
149
229
|
end
|
150
230
|
|
@@ -155,6 +235,10 @@ module Switchman
|
|
155
235
|
pkey > Shard::IDS_PER_SHARD
|
156
236
|
end
|
157
237
|
|
238
|
+
def canonical?
|
239
|
+
!shadow_record?
|
240
|
+
end
|
241
|
+
|
158
242
|
def save_shadow_record(new_attrs: attributes, target_shard: Shard.current)
|
159
243
|
return if target_shard == shard
|
160
244
|
|
@@ -172,13 +256,17 @@ module Switchman
|
|
172
256
|
end
|
173
257
|
|
174
258
|
def destroy_shadow_records(target_shards: [Shard.current])
|
175
|
-
raise Errors::ShadowRecordError,
|
176
|
-
|
259
|
+
raise Errors::ShadowRecordError, "Cannot be called on a shadow record." if shadow_record?
|
260
|
+
|
261
|
+
unless self.class.sharded_column?(self.class.primary_key)
|
262
|
+
raise Errors::MethodUnsupportedForUnshardedTableError,
|
263
|
+
"Cannot be called on a record belonging to an unsharded table."
|
264
|
+
end
|
177
265
|
|
178
266
|
Array(target_shards).each do |target_shard|
|
179
267
|
next if target_shard == shard
|
180
268
|
|
181
|
-
target_shard.activate { self.class.where(
|
269
|
+
target_shard.activate { self.class.where("id = ?", global_id).delete_all }
|
182
270
|
end
|
183
271
|
end
|
184
272
|
|
@@ -235,9 +323,9 @@ module Switchman
|
|
235
323
|
result
|
236
324
|
end
|
237
325
|
|
238
|
-
def transaction(
|
326
|
+
def transaction(...)
|
239
327
|
shard.activate(self.class.connection_class_for_self) do
|
240
|
-
self.class.transaction(
|
328
|
+
self.class.transaction(...)
|
241
329
|
end
|
242
330
|
end
|
243
331
|
|
@@ -270,8 +358,10 @@ module Switchman
|
|
270
358
|
|
271
359
|
def id_for_database
|
272
360
|
if self.class.sharded_primary_key?
|
273
|
-
# It's an int, so
|
274
|
-
# In theory we should do
|
361
|
+
# It's an int, so it's safe to just return it without passing it
|
362
|
+
# through anything else. In theory we should do
|
363
|
+
# `@attributes[@primary_key].type.serialize(id)`, but that seems to
|
364
|
+
# have surprising side-effects
|
275
365
|
id
|
276
366
|
else
|
277
367
|
super
|
@@ -28,7 +28,7 @@ module Switchman
|
|
28
28
|
|
29
29
|
def execute_simple_calculation(operation, column_name, distinct)
|
30
30
|
operation = operation.to_s.downcase
|
31
|
-
if operation ==
|
31
|
+
if operation == "average"
|
32
32
|
result = calculate_simple_average(column_name, distinct)
|
33
33
|
else
|
34
34
|
result = activate do |relation|
|
@@ -36,11 +36,11 @@ module Switchman
|
|
36
36
|
end
|
37
37
|
if result.is_a?(Array)
|
38
38
|
case operation
|
39
|
-
when
|
39
|
+
when "count", "sum"
|
40
40
|
result = result.sum
|
41
|
-
when
|
41
|
+
when "minimum"
|
42
42
|
result = result.min
|
43
|
-
when
|
43
|
+
when "maximum"
|
44
44
|
result = result.max
|
45
45
|
end
|
46
46
|
end
|
@@ -52,20 +52,20 @@ module Switchman
|
|
52
52
|
# See activerecord#execute_simple_calculation
|
53
53
|
relation = except(:order)
|
54
54
|
column = aggregate_column(column_name)
|
55
|
-
relation.select_values = [operation_over_aggregate_column(column,
|
56
|
-
operation_over_aggregate_column(column,
|
55
|
+
relation.select_values = [operation_over_aggregate_column(column, "average", distinct).as("average"),
|
56
|
+
operation_over_aggregate_column(column, "count", distinct).as("count")]
|
57
57
|
|
58
58
|
initial_results = relation.activate { |rel| klass.connection.select_all(rel) }
|
59
59
|
if initial_results.is_a?(Array)
|
60
60
|
initial_results.each do |r|
|
61
|
-
r[
|
62
|
-
r[
|
61
|
+
r["average"] = type_cast_calculated_value_switchman(r["average"], column_name, "average")
|
62
|
+
r["count"] = type_cast_calculated_value_switchman(r["count"], column_name, "count")
|
63
63
|
end
|
64
|
-
result = initial_results.
|
65
|
-
|
66
|
-
|
64
|
+
result = initial_results.sum { |r| r["average"] * r["count"] } / initial_results.sum do |r|
|
65
|
+
r["count"]
|
66
|
+
end
|
67
67
|
else
|
68
|
-
result = type_cast_calculated_value_switchman(initial_results.first[
|
68
|
+
result = type_cast_calculated_value_switchman(initial_results.first["average"], column_name, "average")
|
69
69
|
end
|
70
70
|
result
|
71
71
|
end
|
@@ -90,7 +90,7 @@ module Switchman
|
|
90
90
|
row[opts[:aggregate_alias]] = type_cast_calculated_value_switchman(
|
91
91
|
row[opts[:aggregate_alias]], column_name, opts[:operation]
|
92
92
|
)
|
93
|
-
row[
|
93
|
+
row["count"] = row["count"].to_i if opts[:operation] == "average"
|
94
94
|
|
95
95
|
opts[:group_columns].each do |aliaz, _type, group_column_name|
|
96
96
|
if opts[:associated] && (aliaz == opts[:group_aliases].first)
|
@@ -106,10 +106,49 @@ module Switchman
|
|
106
106
|
compact_grouped_calculation_rows(rows, opts)
|
107
107
|
end
|
108
108
|
|
109
|
+
if ::Rails.version >= "7.1"
|
110
|
+
def ids
|
111
|
+
return super unless klass.sharded_primary_key?
|
112
|
+
|
113
|
+
if loaded?
|
114
|
+
result = records.map do |record|
|
115
|
+
Shard.relative_id_for(record._read_attribute(primary_key),
|
116
|
+
record.shard,
|
117
|
+
Shard.current(klass.connection_class_for_self))
|
118
|
+
end
|
119
|
+
return @async ? Promise::Complete.new(result) : result
|
120
|
+
end
|
121
|
+
|
122
|
+
if has_include?(primary_key)
|
123
|
+
relation = apply_join_dependency.group(primary_key)
|
124
|
+
return relation.ids
|
125
|
+
end
|
126
|
+
|
127
|
+
columns = arel_columns([primary_key])
|
128
|
+
base_shard = Shard.current(klass.connection_class_for_self)
|
129
|
+
activate do |r|
|
130
|
+
relation = r.spawn
|
131
|
+
relation.select_values = columns
|
132
|
+
|
133
|
+
result = if relation.where_clause.contradiction?
|
134
|
+
::ActiveRecord::Result.empty
|
135
|
+
else
|
136
|
+
skip_query_cache_if_necessary do
|
137
|
+
klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
result.then do |res|
|
142
|
+
type_cast_pluck_values(res, columns).map { |id| Shard.relative_id_for(id, Shard.current, base_shard) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
109
148
|
private
|
110
149
|
|
111
150
|
def type_cast_calculated_value_switchman(value, column_name, operation)
|
112
|
-
if ::Rails.version <
|
151
|
+
if ::Rails.version < "7.0"
|
113
152
|
type_cast_calculated_value(value, operation) do |val|
|
114
153
|
column = aggregate_column(column_name)
|
115
154
|
type ||= column.try(:type_caster) ||
|
@@ -125,39 +164,55 @@ module Switchman
|
|
125
164
|
end
|
126
165
|
|
127
166
|
def column_name_for(field)
|
128
|
-
field.respond_to?(:name) ? field.name.to_s : field.to_s.split(
|
167
|
+
field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
|
129
168
|
end
|
130
169
|
|
131
170
|
def grouped_calculation_options(operation, column_name, distinct)
|
132
171
|
opts = { operation: operation, column_name: column_name, distinct: distinct }
|
133
172
|
|
134
|
-
|
173
|
+
# Rails 7.0.5
|
174
|
+
if defined?(::ActiveRecord::Calculations::ColumnAliasTracker)
|
175
|
+
column_alias_tracker = ::ActiveRecord::Calculations::ColumnAliasTracker.new(connection)
|
176
|
+
end
|
177
|
+
|
178
|
+
opts[:aggregate_alias] = aggregate_alias_for(operation, column_name, column_alias_tracker)
|
135
179
|
group_attrs = group_values
|
136
180
|
if group_attrs.first.respond_to?(:to_sym)
|
137
181
|
association = klass.reflect_on_association(group_attrs.first.to_sym)
|
138
|
-
|
182
|
+
# only count belongs_to associations
|
183
|
+
associated = group_attrs.size == 1 && association && association.macro == :belongs_to
|
139
184
|
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
140
185
|
else
|
141
186
|
group_fields = group_attrs
|
142
187
|
end
|
143
188
|
|
144
|
-
|
145
|
-
|
189
|
+
group_aliases = group_fields.map do |field|
|
190
|
+
field = connection.visitor.compile(field) if ::Arel.arel_node?(field)
|
191
|
+
if column_alias_tracker
|
192
|
+
column_alias_tracker.alias_for(field.to_s.downcase)
|
193
|
+
else
|
194
|
+
column_alias_for(field.to_s.downcase)
|
195
|
+
end
|
196
|
+
end
|
146
197
|
group_columns = group_aliases.zip(group_fields).map do |aliaz, field|
|
147
198
|
[aliaz, type_for(field), column_name_for(field)]
|
148
199
|
end
|
149
|
-
opts.merge!(association: association,
|
150
|
-
|
200
|
+
opts.merge!(association: association,
|
201
|
+
associated: associated,
|
202
|
+
group_aliases: group_aliases,
|
203
|
+
group_columns: group_columns,
|
151
204
|
group_fields: group_fields)
|
152
205
|
|
153
206
|
opts
|
154
207
|
end
|
155
208
|
|
156
|
-
def aggregate_alias_for(operation, column_name)
|
157
|
-
if operation ==
|
158
|
-
|
159
|
-
elsif operation ==
|
160
|
-
|
209
|
+
def aggregate_alias_for(operation, column_name, column_alias_tracker)
|
210
|
+
if operation == "count" && column_name == :all
|
211
|
+
"count_all"
|
212
|
+
elsif operation == "average"
|
213
|
+
"average"
|
214
|
+
elsif column_alias_tracker
|
215
|
+
column_alias_tracker.alias_for("#{operation} #{column_name}")
|
161
216
|
else
|
162
217
|
column_alias_for("#{operation} #{column_name}")
|
163
218
|
end
|
@@ -173,13 +228,14 @@ module Switchman
|
|
173
228
|
opts[:distinct]
|
174
229
|
).as(opts[:aggregate_alias])
|
175
230
|
]
|
176
|
-
if opts[:operation] ==
|
231
|
+
if opts[:operation] == "average"
|
177
232
|
# include count in average so we can recalculate the average
|
178
233
|
# across all shards if needed
|
179
234
|
select_values << operation_over_aggregate_column(
|
180
235
|
aggregate_column(opts[:column_name]),
|
181
|
-
|
182
|
-
|
236
|
+
"count",
|
237
|
+
opts[:distinct]
|
238
|
+
).as("count")
|
183
239
|
end
|
184
240
|
|
185
241
|
haves = having_clause.send(:predicates)
|
@@ -205,22 +261,22 @@ module Switchman
|
|
205
261
|
key = key.first if key.size == 1
|
206
262
|
value = row[opts[:aggregate_alias]]
|
207
263
|
|
208
|
-
if opts[:operation] ==
|
264
|
+
if opts[:operation] == "average"
|
209
265
|
if result.key?(key)
|
210
266
|
old_value, old_count = result[key]
|
211
|
-
new_count = old_count + row[
|
212
|
-
new_value = ((old_value * old_count) + (value * row[
|
267
|
+
new_count = old_count + row["count"]
|
268
|
+
new_value = ((old_value * old_count) + (value * row["count"])) / new_count
|
213
269
|
result[key] = [new_value, new_count]
|
214
270
|
else
|
215
|
-
result[key] = [value, row[
|
271
|
+
result[key] = [value, row["count"]]
|
216
272
|
end
|
217
273
|
elsif result.key?(key)
|
218
274
|
case opts[:operation]
|
219
|
-
when
|
275
|
+
when "count", "sum"
|
220
276
|
result[key] += value
|
221
|
-
when
|
277
|
+
when "minimum"
|
222
278
|
result[key] = value if value < result[key]
|
223
|
-
when
|
279
|
+
when "maximum"
|
224
280
|
result[key] = value if value > result[key]
|
225
281
|
end
|
226
282
|
else
|
@@ -228,7 +284,7 @@ module Switchman
|
|
228
284
|
end
|
229
285
|
end
|
230
286
|
|
231
|
-
result.transform_values!(&:first) if opts[:operation] ==
|
287
|
+
result.transform_values!(&:first) if opts[:operation] == "average"
|
232
288
|
|
233
289
|
result
|
234
290
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Switchman
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionHandler
|
6
|
+
def resolve_pool_config(config, connection_name, role, shard)
|
7
|
+
ret = super
|
8
|
+
# Make *all* pool configs use the same schema reflection
|
9
|
+
ret.schema_reflection = ConnectionHandler.global_schema_reflection
|
10
|
+
ret
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.global_schema_reflection
|
14
|
+
@global_schema_reflection ||= ::ActiveRecord::ConnectionAdapters::SchemaReflection.new(nil)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -3,22 +3,24 @@
|
|
3
3
|
module Switchman
|
4
4
|
module ActiveRecord
|
5
5
|
module ConnectionPool
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
if ::Rails.version < "7.1"
|
7
|
+
def get_schema_cache(connection)
|
8
|
+
self.schema_cache ||= SharedSchemaCache.get_schema_cache(connection)
|
9
|
+
self.schema_cache.connection = connection
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
self.schema_cache
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
# rubocop:disable Naming/AccessorMethodName override method
|
15
|
+
def set_schema_cache(cache)
|
16
|
+
schema_cache = get_schema_cache(cache.connection)
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
cache.instance_variables.each do |x|
|
19
|
+
schema_cache.instance_variable_set(x, cache.instance_variable_get(x))
|
20
|
+
end
|
19
21
|
end
|
22
|
+
# rubocop:enable Naming/AccessorMethodName override method
|
20
23
|
end
|
21
|
-
# rubocop:enable Naming/AccessorMethodName override method
|
22
24
|
|
23
25
|
def default_schema
|
24
26
|
connection unless @schemas
|
@@ -48,7 +50,9 @@ module Switchman
|
|
48
50
|
end
|
49
51
|
|
50
52
|
def switch_database(conn)
|
51
|
-
|
53
|
+
if !@schemas && conn.adapter_name == "PostgreSQL" && !current_shard.database_server.config[:shard_name]
|
54
|
+
@schemas = conn.current_schemas
|
55
|
+
end
|
52
56
|
|
53
57
|
conn.shard = current_shard
|
54
58
|
end
|
@@ -56,11 +60,11 @@ module Switchman
|
|
56
60
|
private
|
57
61
|
|
58
62
|
def current_shard
|
59
|
-
::Rails.version <
|
63
|
+
(::Rails.version < "7.0") ? connection_klass.current_switchman_shard : connection_class.current_switchman_shard
|
60
64
|
end
|
61
65
|
|
62
66
|
def tls_key
|
63
|
-
"#{object_id}_shard"
|
67
|
+
:"#{object_id}_shard"
|
64
68
|
end
|
65
69
|
end
|
66
70
|
end
|
@@ -7,14 +7,26 @@ module Switchman
|
|
7
7
|
# since all should point to the same data, even if multiple are writable
|
8
8
|
# (Picks 'primary' since it is guaranteed to exist and switchman handles activating
|
9
9
|
# deploy through other means)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
if ::Rails.version < "7.1"
|
11
|
+
def configs_for(include_replicas: false, name: nil, **)
|
12
|
+
res = super
|
13
|
+
if name && !include_replicas
|
14
|
+
return nil unless name.end_with?("primary")
|
15
|
+
elsif !include_replicas
|
16
|
+
return res.select { |config| config.name.end_with?("primary") }
|
17
|
+
end
|
18
|
+
res
|
19
|
+
end
|
20
|
+
else
|
21
|
+
def configs_for(include_hidden: false, name: nil, **)
|
22
|
+
res = super
|
23
|
+
if name && !include_hidden
|
24
|
+
return nil unless name.end_with?("primary")
|
25
|
+
elsif !include_hidden
|
26
|
+
return res.select { |config| config.name.end_with?("primary") }
|
27
|
+
end
|
28
|
+
res
|
16
29
|
end
|
17
|
-
res
|
18
30
|
end
|
19
31
|
|
20
32
|
private
|
@@ -27,21 +39,31 @@ module Switchman
|
|
27
39
|
return configs if configs.is_a?(Array)
|
28
40
|
|
29
41
|
db_configs = configs.flat_map do |env_name, config|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
42
|
+
if config.is_a?(Hash)
|
43
|
+
# It would be nice to do the auto-fallback that we want here, but we haven't
|
44
|
+
# actually done that for years (or maybe ever) and it will be a big lift to get working
|
45
|
+
roles = config.keys.select do |k|
|
46
|
+
config[k].is_a?(Hash) || (config[k].is_a?(Array) && config[k].all?(Hash))
|
47
|
+
end
|
48
|
+
base_config = config.except(*roles)
|
49
|
+
else
|
50
|
+
base_config = config
|
51
|
+
roles = []
|
52
|
+
end
|
34
53
|
|
35
54
|
name = "#{env_name}/primary"
|
36
|
-
name =
|
55
|
+
name = "primary" if env_name == default_env
|
37
56
|
base_db = build_db_config_from_raw_config(env_name, name, base_config)
|
38
57
|
[base_db] + roles.map do |role|
|
39
|
-
build_db_config_from_raw_config(
|
40
|
-
|
58
|
+
build_db_config_from_raw_config(
|
59
|
+
env_name,
|
60
|
+
"#{env_name}/#{role}",
|
61
|
+
base_config.merge(config[role].is_a?(Array) ? config[role].first : config[role])
|
62
|
+
)
|
41
63
|
end
|
42
64
|
end
|
43
65
|
|
44
|
-
db_configs << environment_url_config(default_env,
|
66
|
+
db_configs << environment_url_config(default_env, "primary", {}) unless db_configs.find(&:for_current_env?)
|
45
67
|
|
46
68
|
merge_db_environment_variables(default_env, db_configs.compact)
|
47
69
|
end
|