switchman 3.4.2 → 3.6.7
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 -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
|