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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "securerandom"
|
|
4
4
|
|
|
5
5
|
module Switchman
|
|
6
6
|
class DatabaseServer
|
|
@@ -8,24 +8,25 @@ module Switchman
|
|
|
8
8
|
|
|
9
9
|
class << self
|
|
10
10
|
attr_accessor :creating_new_shard
|
|
11
|
+
attr_reader :all_roles
|
|
12
|
+
|
|
13
|
+
include Enumerable
|
|
14
|
+
|
|
15
|
+
delegate :each, to: :all
|
|
11
16
|
|
|
12
17
|
def all
|
|
13
18
|
database_servers.values
|
|
14
19
|
end
|
|
15
20
|
|
|
16
|
-
def all_roles
|
|
17
|
-
@all_roles ||= all.map(&:roles).flatten.uniq
|
|
18
|
-
end
|
|
19
|
-
|
|
20
21
|
def find(id_or_all)
|
|
21
22
|
return all if id_or_all == :all
|
|
22
|
-
return id_or_all.
|
|
23
|
+
return id_or_all.filter_map { |id| database_servers[id || ::Rails.env] }.uniq if id_or_all.is_a?(Array)
|
|
23
24
|
|
|
24
25
|
database_servers[id_or_all || ::Rails.env]
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def create(settings = {})
|
|
28
|
-
raise
|
|
29
|
+
raise "database servers should be set up in database.yml" unless ::Rails.env.test?
|
|
29
30
|
|
|
30
31
|
id = settings[:id]
|
|
31
32
|
unless id
|
|
@@ -38,7 +39,7 @@ module Switchman
|
|
|
38
39
|
database_servers[server.id] = server
|
|
39
40
|
::ActiveRecord::Base.configurations.configurations <<
|
|
40
41
|
::ActiveRecord::DatabaseConfigurations::HashConfig.new(::Rails.env, "#{server.id}/primary", settings)
|
|
41
|
-
Shard.send(:
|
|
42
|
+
Shard.send(:configure_connects_to)
|
|
42
43
|
server
|
|
43
44
|
end
|
|
44
45
|
|
|
@@ -49,31 +50,48 @@ module Switchman
|
|
|
49
50
|
servers[rand(servers.length)]
|
|
50
51
|
end
|
|
51
52
|
|
|
53
|
+
def guard_servers
|
|
54
|
+
all.each { |db| db.guard! if db.config[:prefer_secondary] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def regions
|
|
58
|
+
@regions ||= all.filter_map(&:region).uniq.sort
|
|
59
|
+
end
|
|
60
|
+
|
|
52
61
|
private
|
|
53
62
|
|
|
54
63
|
def reference_role(role)
|
|
55
64
|
return if all_roles.include?(role)
|
|
56
65
|
|
|
57
66
|
@all_roles << role
|
|
58
|
-
Shard.send(:
|
|
67
|
+
Shard.send(:configure_connects_to)
|
|
59
68
|
end
|
|
60
69
|
|
|
61
70
|
def database_servers
|
|
62
|
-
|
|
71
|
+
if !@database_servers || @database_servers.empty?
|
|
63
72
|
@database_servers = {}.with_indifferent_access
|
|
73
|
+
roles = []
|
|
64
74
|
::ActiveRecord::Base.configurations.configurations.each do |config|
|
|
65
|
-
if config.name.include?(
|
|
66
|
-
name, role = config.name.split(
|
|
75
|
+
if config.name.include?("/")
|
|
76
|
+
name, role = config.name.split("/")
|
|
67
77
|
else
|
|
68
78
|
name, role = config.env_name, config.name
|
|
69
79
|
end
|
|
80
|
+
role = role.to_sym
|
|
70
81
|
|
|
71
|
-
|
|
82
|
+
roles << role
|
|
83
|
+
if role == :primary
|
|
72
84
|
@database_servers[name] = DatabaseServer.new(config.env_name, config.configuration_hash)
|
|
73
85
|
else
|
|
74
86
|
@database_servers[name].roles << role
|
|
75
87
|
end
|
|
76
88
|
end
|
|
89
|
+
# Do this after so that all database servers for all roles are established and we won't prematurely
|
|
90
|
+
# configure a connection for the wrong role
|
|
91
|
+
@all_roles = roles.uniq
|
|
92
|
+
return @database_servers if @database_servers.empty?
|
|
93
|
+
|
|
94
|
+
Shard.send(:configure_connects_to)
|
|
77
95
|
end
|
|
78
96
|
@database_servers
|
|
79
97
|
end
|
|
@@ -89,21 +107,22 @@ module Switchman
|
|
|
89
107
|
end
|
|
90
108
|
|
|
91
109
|
def connects_to_hash
|
|
92
|
-
self.class.all_roles.
|
|
110
|
+
self.class.all_roles.to_h do |role|
|
|
93
111
|
config_role = role
|
|
94
112
|
config_role = :primary unless roles.include?(role)
|
|
95
113
|
config_name = :"#{id}/#{config_role}"
|
|
96
114
|
config_name = :primary if id == ::Rails.env && config_role == :primary
|
|
97
115
|
[role.to_sym, config_name]
|
|
98
|
-
end
|
|
116
|
+
end
|
|
99
117
|
end
|
|
100
118
|
|
|
101
119
|
def destroy
|
|
102
120
|
self.class.send(:database_servers).delete(id) if id
|
|
103
121
|
Shard.sharded_models.each do |klass|
|
|
104
122
|
self.class.all_roles.each do |role|
|
|
105
|
-
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
|
106
|
-
|
|
123
|
+
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
|
124
|
+
role:,
|
|
125
|
+
shard: id.to_sym)
|
|
107
126
|
end
|
|
108
127
|
end
|
|
109
128
|
end
|
|
@@ -129,32 +148,57 @@ module Switchman
|
|
|
129
148
|
end
|
|
130
149
|
end
|
|
131
150
|
|
|
132
|
-
def
|
|
133
|
-
|
|
151
|
+
def region
|
|
152
|
+
config[:region]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# @param region [String, Array<String>] the region(s) to check against
|
|
156
|
+
# @return true if the database server doesn't have a region, or it
|
|
157
|
+
# matches the specified region
|
|
158
|
+
def in_region?(region)
|
|
159
|
+
!self.region || (region.is_a?(Array) ? region.include?(self.region) : self.region == region)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @return true if the database server doesn't have a region, Switchman is
|
|
163
|
+
# not configured with a region, or the database server's region matches
|
|
164
|
+
# Switchman's current region
|
|
165
|
+
def in_current_region?
|
|
166
|
+
unless instance_variable_defined?(:@in_current_region)
|
|
167
|
+
@in_current_region = !region ||
|
|
168
|
+
!Switchman.region ||
|
|
169
|
+
region == Switchman.region
|
|
170
|
+
end
|
|
171
|
+
@in_current_region
|
|
134
172
|
end
|
|
135
173
|
|
|
136
174
|
# locks this db to a specific environment, except for
|
|
137
175
|
# when doing writes (then it falls back to the current
|
|
138
176
|
# value of GuardRail.environment)
|
|
139
177
|
def guard!(environment = :secondary)
|
|
140
|
-
|
|
178
|
+
DatabaseServer.send(:reference_role, environment)
|
|
179
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
|
|
180
|
+
klasses: [::ActiveRecord::Base] }
|
|
141
181
|
end
|
|
142
182
|
|
|
143
183
|
def unguard!
|
|
144
|
-
|
|
184
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
|
|
185
|
+
klasses: [::ActiveRecord::Base] }
|
|
145
186
|
end
|
|
146
187
|
|
|
147
188
|
def unguard
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
189
|
+
return yield unless ::ActiveRecord::Base.role_overriden?(id.to_sym)
|
|
190
|
+
|
|
191
|
+
begin
|
|
192
|
+
unguard!
|
|
193
|
+
yield
|
|
194
|
+
ensure
|
|
195
|
+
::ActiveRecord::Base.connected_to_stack.pop
|
|
196
|
+
end
|
|
153
197
|
end
|
|
154
198
|
|
|
155
199
|
def shards
|
|
156
200
|
if id == ::Rails.env
|
|
157
|
-
Shard.where(
|
|
201
|
+
Shard.where("database_server_id IS NULL OR database_server_id=?", id)
|
|
158
202
|
else
|
|
159
203
|
Shard.where(database_server_id: id)
|
|
160
204
|
end
|
|
@@ -174,70 +218,66 @@ module Switchman
|
|
|
174
218
|
if config_create_statement
|
|
175
219
|
create_commands = Array(config_create_statement).dup
|
|
176
220
|
create_statement = lambda {
|
|
177
|
-
create_commands.map { |statement| format(statement, name
|
|
221
|
+
create_commands.map { |statement| format(statement, name:, password:) }
|
|
178
222
|
}
|
|
179
223
|
end
|
|
180
224
|
|
|
181
225
|
id ||= begin
|
|
182
|
-
id_seq = Shard.connection.quote(Shard.connection.quote_table_name(
|
|
226
|
+
id_seq = Shard.connection.quote(Shard.connection.quote_table_name("switchman_shards_id_seq"))
|
|
183
227
|
next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
|
|
184
228
|
next_id.to_i
|
|
185
229
|
end
|
|
186
230
|
|
|
187
231
|
name ||= "#{config[:database]}_shard_#{id}"
|
|
188
232
|
|
|
233
|
+
schema_already_existed = false
|
|
234
|
+
shard = nil
|
|
189
235
|
Shard.connection.transaction do
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
raise 'This schema already exists; cannot overwrite'
|
|
203
|
-
end
|
|
204
|
-
Array(create_statement.call).each do |stmt|
|
|
205
|
-
::ActiveRecord::Base.connection.execute(stmt)
|
|
206
|
-
end
|
|
236
|
+
self.class.creating_new_shard = true
|
|
237
|
+
DatabaseServer.send(:reference_role, :deploy)
|
|
238
|
+
::ActiveRecord::Base.connected_to(shard: self.id.to_sym, role: :deploy) do
|
|
239
|
+
shard = Shard.create!(id:,
|
|
240
|
+
name:,
|
|
241
|
+
database_server_id: self.id)
|
|
242
|
+
if create_statement
|
|
243
|
+
if ::ActiveRecord::Base.connection.select_value(
|
|
244
|
+
"SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"
|
|
245
|
+
)
|
|
246
|
+
schema_already_existed = true
|
|
247
|
+
raise "This schema already exists; cannot overwrite"
|
|
207
248
|
end
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
end
|
|
249
|
+
Array(create_statement.call).each do |stmt|
|
|
250
|
+
::ActiveRecord::Base.connection.execute(stmt)
|
|
211
251
|
end
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
::ActiveRecord::Base.descendants.reject do |m|
|
|
224
|
-
m <= UnshardedRecord || !m.table_exists?
|
|
225
|
-
end.each(&:define_attribute_methods)
|
|
252
|
+
end
|
|
253
|
+
if config[:adapter] == "postgresql"
|
|
254
|
+
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {}
|
|
255
|
+
end
|
|
256
|
+
old_verbose = ::ActiveRecord::Migration.verbose
|
|
257
|
+
::ActiveRecord::Migration.verbose = false
|
|
258
|
+
|
|
259
|
+
unless schema == false
|
|
260
|
+
shard.activate do
|
|
261
|
+
::ActiveRecord::Base.connection.transaction(requires_new: true) do
|
|
262
|
+
::ActiveRecord::MigrationContext.new(::ActiveRecord::Migrator.migrations_paths).migrate
|
|
226
263
|
end
|
|
264
|
+
|
|
265
|
+
::ActiveRecord::Base.descendants.reject do |m|
|
|
266
|
+
m <= UnshardedRecord || !m.table_exists?
|
|
267
|
+
end.each(&:define_attribute_methods)
|
|
227
268
|
end
|
|
228
|
-
ensure
|
|
229
|
-
::ActiveRecord::Migration.verbose = old_verbose
|
|
230
|
-
::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
|
|
231
269
|
end
|
|
232
|
-
shard
|
|
233
|
-
rescue
|
|
234
|
-
shard.destroy
|
|
235
|
-
shard.drop_database rescue nil unless schema_already_existed
|
|
236
|
-
reset_column_information unless schema == false rescue nil
|
|
237
|
-
raise
|
|
238
270
|
ensure
|
|
239
|
-
|
|
271
|
+
::ActiveRecord::Migration.verbose = old_verbose
|
|
272
|
+
::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
|
|
240
273
|
end
|
|
274
|
+
shard
|
|
275
|
+
rescue
|
|
276
|
+
shard&.destroy
|
|
277
|
+
shard&.drop_database rescue nil unless schema_already_existed
|
|
278
|
+
raise
|
|
279
|
+
ensure
|
|
280
|
+
self.class.creating_new_shard = false
|
|
241
281
|
end
|
|
242
282
|
end
|
|
243
283
|
|
|
@@ -259,18 +299,18 @@ module Switchman
|
|
|
259
299
|
end
|
|
260
300
|
|
|
261
301
|
def primary_shard
|
|
262
|
-
unless
|
|
263
|
-
# if sharding isn't fully set up yet, we may not be able to query the shards table
|
|
264
|
-
@primary_shard = Shard.default if Shard.default.database_server == self
|
|
265
|
-
@primary_shard ||= shards.where(name: nil).first
|
|
266
|
-
end
|
|
267
|
-
@primary_shard
|
|
268
|
-
end
|
|
302
|
+
return nil unless primary_shard_id
|
|
269
303
|
|
|
270
|
-
|
|
304
|
+
Shard.lookup(primary_shard_id)
|
|
305
|
+
end
|
|
271
306
|
|
|
272
|
-
def
|
|
273
|
-
|
|
307
|
+
def primary_shard_id
|
|
308
|
+
unless instance_variable_defined?(:@primary_shard_id)
|
|
309
|
+
# if sharding isn't fully set up yet, we may not be able to query the shards table
|
|
310
|
+
@primary_shard_id = Shard.default.id if Shard.default.database_server == self
|
|
311
|
+
@primary_shard_id ||= shards.where(name: nil).first&.id
|
|
312
|
+
end
|
|
313
|
+
@primary_shard_id
|
|
274
314
|
end
|
|
275
315
|
end
|
|
276
316
|
end
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'switchman/database_server'
|
|
4
|
-
|
|
5
3
|
module Switchman
|
|
6
4
|
class DefaultShard
|
|
7
5
|
def id
|
|
8
|
-
|
|
6
|
+
"default"
|
|
9
7
|
end
|
|
10
|
-
|
|
8
|
+
alias_method :cache_key, :id
|
|
9
|
+
|
|
11
10
|
def activate(*_classes)
|
|
12
11
|
yield
|
|
13
12
|
end
|
|
@@ -59,8 +58,18 @@ module Switchman
|
|
|
59
58
|
self
|
|
60
59
|
end
|
|
61
60
|
|
|
61
|
+
def region; end
|
|
62
|
+
|
|
63
|
+
def in_region?(_region)
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def in_current_region?
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
62
71
|
def _dump(_depth)
|
|
63
|
-
|
|
72
|
+
""
|
|
64
73
|
end
|
|
65
74
|
|
|
66
75
|
def self._load(_str)
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -4,105 +4,22 @@ module Switchman
|
|
|
4
4
|
class Engine < ::Rails::Engine
|
|
5
5
|
isolate_namespace Switchman
|
|
6
6
|
|
|
7
|
-
# enable Rails 6.1 style connection handling
|
|
8
|
-
config.active_record.legacy_connection_handling = false
|
|
9
7
|
config.active_record.writing_role = :primary
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
next if value.is_a?(String)
|
|
17
|
-
|
|
18
|
-
result[key] = ::ActiveSupport::Cache.lookup_store(value)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
cache_store_config.each do |key, value| # rubocop:disable Style/CombinableLoops
|
|
22
|
-
next unless value.is_a?(String)
|
|
23
|
-
|
|
24
|
-
result[key] = result[value]
|
|
25
|
-
end
|
|
26
|
-
result
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
initializer 'switchman.initialize_cache', before: 'initialize_cache' do
|
|
30
|
-
require 'switchman/active_support/cache'
|
|
31
|
-
::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
|
|
32
|
-
|
|
33
|
-
# if we haven't already setup our cache map out-of-band, set it up from
|
|
34
|
-
# config.cache_store now. behaves similarly to Rails' default
|
|
35
|
-
# initialize_cache initializer, but for each value in the map, rather
|
|
36
|
-
# than just Rails.cache. if config.cache_store is a flat value, uses it
|
|
37
|
-
# to fill just the Rails.env entry in the cache map.
|
|
38
|
-
unless Switchman.config[:cache_map].present?
|
|
39
|
-
cache_store_config = ::Rails.configuration.cache_store
|
|
40
|
-
cache_store_config = { ::Rails.env => cache_store_config } unless cache_store_config.is_a?(Hash)
|
|
41
|
-
|
|
42
|
-
Switchman.config[:cache_map] = Engine.lookup_stores(cache_store_config)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# if the configured cache map (either from before, or as populated from
|
|
46
|
-
# config.cache_store) didn't have an entry for Rails.env, add one using
|
|
47
|
-
# lookup_store(nil); matches the behavior of Rails' default
|
|
48
|
-
# initialize_cache initializer when config.cache_store is nil.
|
|
49
|
-
unless Switchman.config[:cache_map].key?(::Rails.env)
|
|
50
|
-
value = ::ActiveSupport::Cache.lookup_store(nil)
|
|
51
|
-
Switchman.config[:cache_map][::Rails.env] = value
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
middlewares = Switchman.config[:cache_map].values.map do |store|
|
|
55
|
-
store.middleware if store.respond_to?(:middleware)
|
|
56
|
-
end.compact.uniq
|
|
57
|
-
middlewares.each do |middleware|
|
|
58
|
-
config.middleware.insert_before('Rack::Runtime', middleware)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# prevent :initialize_cache from trying to (or needing to) set
|
|
62
|
-
# Rails.cache. once our switchman.extend_ar initializer (below) runs
|
|
63
|
-
# Rails.cache will be overridden to pull appropriate values from the
|
|
64
|
-
# cache map, but between now and then, Rails.cache should return the
|
|
65
|
-
# Rails.env entry in the cache map.
|
|
66
|
-
::Rails.cache = Switchman.config[:cache_map][::Rails.env]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
initializer 'switchman.extend_ar', before: 'active_record.initialize_database' do
|
|
11
|
+
initializer "switchman.active_record_patch",
|
|
12
|
+
before: "active_record.initialize_database",
|
|
13
|
+
after: :setup_once_autoloader do
|
|
70
14
|
::ActiveSupport.on_load(:active_record) do
|
|
71
|
-
|
|
72
|
-
require
|
|
73
|
-
require 'switchman/active_record/attribute_methods'
|
|
74
|
-
require 'switchman/active_record/base'
|
|
75
|
-
require 'switchman/active_record/calculations'
|
|
76
|
-
require 'switchman/active_record/connection_pool'
|
|
77
|
-
require 'switchman/active_record/database_configurations'
|
|
78
|
-
require 'switchman/active_record/database_configurations/database_config'
|
|
79
|
-
require 'switchman/active_record/finder_methods'
|
|
80
|
-
require 'switchman/active_record/log_subscriber'
|
|
81
|
-
require 'switchman/active_record/migration'
|
|
82
|
-
require 'switchman/active_record/model_schema'
|
|
83
|
-
require 'switchman/active_record/persistence'
|
|
84
|
-
require 'switchman/active_record/predicate_builder'
|
|
85
|
-
require 'switchman/active_record/query_cache'
|
|
86
|
-
require 'switchman/active_record/query_methods'
|
|
87
|
-
require 'switchman/active_record/reflection'
|
|
88
|
-
require 'switchman/active_record/relation'
|
|
89
|
-
require 'switchman/active_record/spawn_methods'
|
|
90
|
-
require 'switchman/active_record/statement_cache'
|
|
91
|
-
require 'switchman/active_record/tasks/database_tasks'
|
|
92
|
-
require 'switchman/active_record/type_caster'
|
|
93
|
-
require 'switchman/arel'
|
|
94
|
-
require 'switchman/call_super'
|
|
95
|
-
require 'switchman/rails'
|
|
96
|
-
require 'switchman/guard_rail/relation'
|
|
97
|
-
require 'switchman/standard_error'
|
|
98
|
-
|
|
99
|
-
::StandardError.include(StandardError)
|
|
15
|
+
# Switchman requires postgres, so just always load the pg adapter
|
|
16
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
|
100
17
|
|
|
101
18
|
self.default_shard = ::Rails.env.to_sym
|
|
102
19
|
self.default_role = :primary
|
|
103
20
|
|
|
104
|
-
|
|
105
|
-
|
|
21
|
+
prepend ActiveRecord::Base
|
|
22
|
+
prepend ActiveRecord::AttributeMethods
|
|
106
23
|
include ActiveRecord::Persistence
|
|
107
24
|
singleton_class.prepend ActiveRecord::ModelSchema::ClassMethods
|
|
108
25
|
|
|
@@ -111,25 +28,40 @@ module Switchman
|
|
|
111
28
|
::ActiveRecord::StatementCache::BindMap.prepend(ActiveRecord::StatementCache::BindMap)
|
|
112
29
|
::ActiveRecord::StatementCache::Substitute.send(:attr_accessor, :primary, :sharded)
|
|
113
30
|
|
|
114
|
-
::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
|
|
115
|
-
::ActiveRecord::Associations::HasOneAssociation.prepend(ActiveRecord::ForeignAssociation)
|
|
116
|
-
::ActiveRecord::Associations::HasManyAssociation.prepend(ActiveRecord::ForeignAssociation)
|
|
31
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::Associations::CollectionAssociation)
|
|
32
|
+
::ActiveRecord::Associations::HasOneAssociation.prepend(ActiveRecord::Associations::ForeignAssociation)
|
|
33
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(ActiveRecord::Associations::ForeignAssociation)
|
|
117
34
|
|
|
118
35
|
::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
|
|
119
36
|
|
|
120
|
-
prepend(ActiveRecord::AutosaveAssociation)
|
|
37
|
+
prepend(ActiveRecord::Associations::AutosaveAssociation)
|
|
121
38
|
|
|
122
|
-
::ActiveRecord::Associations::Association.prepend(ActiveRecord::Association)
|
|
123
|
-
::ActiveRecord::Associations::BelongsToAssociation.prepend(ActiveRecord::BelongsToAssociation)
|
|
124
|
-
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::CollectionProxy)
|
|
39
|
+
::ActiveRecord::Associations::Association.prepend(ActiveRecord::Associations::Association)
|
|
40
|
+
::ActiveRecord::Associations::BelongsToAssociation.prepend(ActiveRecord::Associations::BelongsToAssociation)
|
|
41
|
+
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::Associations::CollectionProxy)
|
|
125
42
|
|
|
126
|
-
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Preloader::Association)
|
|
43
|
+
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Associations::Preloader::Association)
|
|
44
|
+
::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
|
|
45
|
+
ActiveRecord::Associations::Preloader::Association::LoaderRecords
|
|
46
|
+
)
|
|
127
47
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
|
|
48
|
+
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
128
49
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
129
50
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
51
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
52
|
+
# https://github.com/rails/rails/commit/0016280f4fde55d96738887093dc333aae0d107b
|
|
53
|
+
if ::Rails.version < "7.2"
|
|
54
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter::ClassMethods)
|
|
55
|
+
else
|
|
56
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.singleton_class.prepend(
|
|
57
|
+
ActiveRecord::PostgreSQLAdapter::ClassMethods
|
|
58
|
+
)
|
|
59
|
+
end
|
|
130
60
|
|
|
131
61
|
::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
|
|
132
|
-
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
62
|
+
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
|
63
|
+
ActiveRecord::DatabaseConfigurations::DatabaseConfig
|
|
64
|
+
)
|
|
133
65
|
|
|
134
66
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
135
67
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
@@ -137,6 +69,11 @@ module Switchman
|
|
|
137
69
|
::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext)
|
|
138
70
|
::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
|
|
139
71
|
|
|
72
|
+
if ::Rails.version > "7.1.3"
|
|
73
|
+
::ActiveRecord::PendingMigrationConnection.singleton_class
|
|
74
|
+
.include(ActiveRecord::PendingMigrationConnection::ClassMethods)
|
|
75
|
+
end
|
|
76
|
+
|
|
140
77
|
::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
|
|
141
78
|
::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
142
79
|
::ActiveRecord::Reflection::ThroughReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
@@ -146,64 +83,81 @@ module Switchman
|
|
|
146
83
|
::ActiveRecord::Relation.include(ActiveRecord::QueryMethods)
|
|
147
84
|
::ActiveRecord::Relation.prepend(GuardRail::Relation)
|
|
148
85
|
::ActiveRecord::Relation.prepend(ActiveRecord::Relation)
|
|
86
|
+
::ActiveRecord::Relation.prepend(ActiveRecord::Relation::InsertUpsertAll) if ::Rails.version >= "7.2"
|
|
149
87
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
|
150
88
|
::ActiveRecord::Relation.include(CallSuper)
|
|
151
89
|
|
|
152
|
-
::ActiveRecord::PredicateBuilder::
|
|
90
|
+
::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(
|
|
91
|
+
ActiveRecord::PredicateBuilder::PolymorphicArrayValue
|
|
92
|
+
)
|
|
153
93
|
|
|
154
94
|
::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
|
|
155
95
|
|
|
96
|
+
::ActiveRecord::TestFixtures.prepend(ActiveRecord::TestFixtures)
|
|
97
|
+
|
|
156
98
|
::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
|
|
157
99
|
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
|
158
100
|
|
|
159
|
-
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
160
|
-
|
|
161
101
|
::Arel::Table.prepend(Arel::Table)
|
|
162
102
|
::Arel::Visitors::ToSql.prepend(Arel::Visitors::ToSql)
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def self.foreign_key_check(name, type, limit: nil)
|
|
167
|
-
puts "WARNING: All foreign keys need to be 8-byte integers. #{name} looks like a foreign key. If so, please add the option: `:limit => 8`" if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
|
|
168
|
-
end
|
|
169
103
|
|
|
170
|
-
initializer 'switchman.extend_connection_adapters', after: 'active_record.initialize_database' do
|
|
171
|
-
::ActiveSupport.on_load(:active_record) do
|
|
172
104
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.descendants.each do |klass|
|
|
173
105
|
klass.prepend(ActiveRecord::AbstractAdapter::ForeignKeyCheck)
|
|
174
106
|
end
|
|
175
107
|
|
|
176
|
-
require 'switchman/active_record/table_definition'
|
|
177
108
|
::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
|
|
178
|
-
|
|
179
|
-
if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
|
180
|
-
require 'switchman/active_record/postgresql_adapter'
|
|
181
|
-
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
Shard.send(:initialize_sharding)
|
|
185
109
|
end
|
|
110
|
+
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
|
111
|
+
# initializers can go try to load Switchman::Shard or we get a loop
|
|
112
|
+
::ActiveRecord::Base
|
|
186
113
|
end
|
|
187
114
|
|
|
188
|
-
initializer
|
|
189
|
-
::ActiveSupport.on_load(:
|
|
190
|
-
|
|
191
|
-
require 'active_record/base'
|
|
115
|
+
initializer "switchman.error_patch", after: "active_record.initialize_database" do
|
|
116
|
+
::ActiveSupport.on_load(:active_record) do
|
|
117
|
+
::StandardError.include(StandardError)
|
|
192
118
|
end
|
|
193
119
|
end
|
|
194
120
|
|
|
195
|
-
initializer
|
|
196
|
-
::ActiveSupport.
|
|
197
|
-
|
|
121
|
+
initializer "switchman.initialize_cache", before: :initialize_cache, after: "active_record.initialize_database" do
|
|
122
|
+
::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
|
|
123
|
+
|
|
124
|
+
# if we haven't already setup our cache map out-of-band, set it up from
|
|
125
|
+
# config.cache_store now. behaves similarly to Rails' default
|
|
126
|
+
# initialize_cache initializer, but for each value in the map, rather
|
|
127
|
+
# than just Rails.cache. if config.cache_store is a flat value, uses it
|
|
128
|
+
# to fill just the Rails.env entry in the cache map.
|
|
129
|
+
unless Switchman.config[:cache_map].present?
|
|
130
|
+
cache_store_config = ::Rails.configuration.cache_store
|
|
131
|
+
cache_store_config = { ::Rails.env => cache_store_config } unless cache_store_config.is_a?(Hash)
|
|
198
132
|
|
|
199
|
-
::
|
|
133
|
+
Switchman.config[:cache_map] = ::ActiveSupport::Cache.lookup_stores(cache_store_config)
|
|
200
134
|
end
|
|
201
|
-
end
|
|
202
135
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
136
|
+
# if the configured cache map (either from before, or as populated from
|
|
137
|
+
# config.cache_store) didn't have an entry for Rails.env, add one using
|
|
138
|
+
# lookup_store(nil); matches the behavior of Rails' default
|
|
139
|
+
# initialize_cache initializer when config.cache_store is nil.
|
|
140
|
+
unless Switchman.config[:cache_map].key?(::Rails.env)
|
|
141
|
+
value = ::ActiveSupport::Cache.lookup_store(nil)
|
|
142
|
+
Switchman.config[:cache_map][::Rails.env] = value
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
middlewares = Switchman.config[:cache_map].values.filter_map do |store|
|
|
146
|
+
store.middleware if store.respond_to?(:middleware)
|
|
147
|
+
end.uniq
|
|
148
|
+
middlewares.each do |middleware|
|
|
149
|
+
config.middleware.insert_before("Rack::Runtime", middleware)
|
|
150
|
+
end
|
|
206
151
|
|
|
152
|
+
# prevent :initialize_cache from trying to (or needing to) set
|
|
153
|
+
# Rails.cache. once our switchman.extend_ar initializer (below) runs
|
|
154
|
+
# Rails.cache will be overridden to pull appropriate values from the
|
|
155
|
+
# cache map, but between now and then, Rails.cache should return the
|
|
156
|
+
# Rails.env entry in the cache map.
|
|
157
|
+
::Rails.cache = Switchman.config[:cache_map][::Rails.env]
|
|
158
|
+
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
159
|
+
|
|
160
|
+
::ActiveSupport.on_load(:action_controller) do
|
|
207
161
|
::ActionController::Base.include(ActionController::Caching)
|
|
208
162
|
end
|
|
209
163
|
end
|