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
@@ -44,12 +44,18 @@ module Switchman
|
|
44
44
|
primary_shard.activate(klass.connection_class_for_self) { super }
|
45
45
|
end
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
if ::Rails.version > "7.1.2"
|
48
|
+
def transaction(...)
|
49
|
+
primary_shard.activate(klass.connection_class_for_self) { super }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def explain(*args)
|
54
|
+
activate { |relation| relation.call_super(:explain, Relation, *args) }
|
49
55
|
end
|
50
56
|
|
51
57
|
def load(&block)
|
52
|
-
if !loaded? || (::Rails.version >=
|
58
|
+
if !loaded? || (::Rails.version >= "7.0" && scheduled?)
|
53
59
|
@records = activate { |relation| relation.send(:exec_queries, &block) }
|
54
60
|
@loaded = true
|
55
61
|
end
|
@@ -58,10 +64,9 @@ module Switchman
|
|
58
64
|
end
|
59
65
|
|
60
66
|
%I[update_all delete_all].each do |method|
|
61
|
-
arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
|
62
67
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
63
|
-
def #{method}(
|
64
|
-
result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation,
|
68
|
+
def #{method}(*args, **kwargs)
|
69
|
+
result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args, **kwargs) }
|
65
70
|
result = result.sum if result.is_a?(Array)
|
66
71
|
result
|
67
72
|
end
|
@@ -73,12 +78,16 @@ module Switchman
|
|
73
78
|
loose_mode = options[:loose] && is_integer
|
74
79
|
# loose_mode: if we don't care about getting exactly batch_size ids in between
|
75
80
|
# don't get the max - just get the min and add batch_size so we get that many _at most_
|
76
|
-
values = loose_mode ?
|
81
|
+
values = loose_mode ? "MIN(id)" : "MIN(id), MAX(id)"
|
77
82
|
|
78
83
|
batch_size = options[:batch_size].try(:to_i) || 1000
|
79
|
-
quoted_primary_key =
|
80
|
-
|
81
|
-
|
84
|
+
quoted_primary_key =
|
85
|
+
"#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
|
86
|
+
as_id = " AS id" unless primary_key == "id"
|
87
|
+
subquery_scope = except(:select)
|
88
|
+
.select("#{quoted_primary_key}#{as_id}")
|
89
|
+
.reorder(primary_key.to_sym)
|
90
|
+
.limit(loose_mode ? 1 : batch_size)
|
82
91
|
subquery_scope = subquery_scope.where("#{quoted_primary_key} <= ?", options[:end_at]) if options[:end_at]
|
83
92
|
|
84
93
|
first_subquery_scope = if options[:start_at]
|
@@ -121,7 +130,9 @@ module Switchman
|
|
121
130
|
relation = shard(Shard.current(klass.connection_class_for_self))
|
122
131
|
relation.remove_nonlocal_primary_keys!
|
123
132
|
# do a minimal query if possible
|
124
|
-
|
133
|
+
if limit_value && !result_count.zero? && order_values.empty?
|
134
|
+
relation = relation.limit(limit_value - result_count)
|
135
|
+
end
|
125
136
|
|
126
137
|
shard_results = relation.activate(&block)
|
127
138
|
|
@@ -17,7 +17,7 @@ module Switchman
|
|
17
17
|
final_shard_source_value = %i[explicit association].detect do |source_value|
|
18
18
|
shard_source_value == source_value || rhs.shard_source_value == source_value
|
19
19
|
end
|
20
|
-
raise
|
20
|
+
raise "unknown shard_source_value" unless final_shard_source_value
|
21
21
|
|
22
22
|
# have to merge shard_value
|
23
23
|
lhs_shard_value = all_shards
|
@@ -36,7 +36,7 @@ module Switchman
|
|
36
36
|
final_shard_source_value = %i[explicit association implicit].detect do |source_value|
|
37
37
|
shard_source_value == source_value || rhs.shard_source_value == source_value
|
38
38
|
end
|
39
|
-
raise
|
39
|
+
raise "unknown shard_source_value" unless final_shard_source_value
|
40
40
|
end
|
41
41
|
|
42
42
|
[final_shard_value, final_primary_shard, final_shard_source_value]
|
@@ -4,8 +4,8 @@ module Switchman
|
|
4
4
|
module ActiveRecord
|
5
5
|
module StatementCache
|
6
6
|
module ClassMethods
|
7
|
-
def create(connection
|
8
|
-
relation =
|
7
|
+
def create(connection)
|
8
|
+
relation = yield ::ActiveRecord::StatementCache::Params.new
|
9
9
|
|
10
10
|
_query_builder, binds = connection.cacheable_query(self, relation.arel)
|
11
11
|
bind_map = ::ActiveRecord::StatementCache::BindMap.new(binds)
|
@@ -7,9 +7,14 @@ module Switchman
|
|
7
7
|
def drop(*)
|
8
8
|
super
|
9
9
|
# no really, it's gone
|
10
|
-
Switchman.cache.delete(
|
10
|
+
Switchman.cache.delete("default_shard")
|
11
11
|
Shard.default(reload: true)
|
12
12
|
end
|
13
|
+
|
14
|
+
def raise_for_multi_db(*)
|
15
|
+
# ignore; Switchman doesn't use namespaced tasks for multiple shards; it uses
|
16
|
+
# environment variables to filter which shards you want to target
|
17
|
+
end
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
@@ -12,31 +12,41 @@ module Switchman
|
|
12
12
|
# Replace the one that activerecord natively uses with a switchman-optimized one
|
13
13
|
::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
|
14
14
|
# Code adapted from the code in rails proper
|
15
|
-
@connection_subscriber =
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
@connection_subscriber =
|
16
|
+
::ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
17
|
+
spec_name = if ::Rails.version < "7.1"
|
18
|
+
payload[:spec_name] if payload.key?(:spec_name)
|
19
|
+
elsif payload.key?(:connection_name)
|
20
|
+
payload[:connection_name]
|
21
|
+
end
|
22
|
+
shard = payload[:shard] if payload.key?(:shard)
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
|
25
|
+
begin
|
26
|
+
connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
|
27
|
+
connection.connect! if ::Rails.version >= "7.1" # eagerly validate the connection
|
28
|
+
rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
|
29
|
+
connection = nil
|
30
|
+
end
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
32
|
+
if connection
|
33
|
+
setup_shared_connection_pool
|
34
|
+
unless @fixture_connections.include?(connection)
|
35
|
+
connection.begin_transaction joinable: false, _lazy: false
|
36
|
+
connection.pool.lock_thread = true if lock_threads
|
37
|
+
@fixture_connections << connection
|
38
|
+
end
|
39
|
+
end
|
31
40
|
end
|
32
41
|
end
|
33
|
-
end
|
34
42
|
end
|
35
43
|
|
36
44
|
def enlist_fixture_connections
|
37
45
|
setup_shared_connection_pool
|
38
46
|
|
39
|
-
::ActiveRecord::Base.connection_handler.connection_pool_list.reject
|
47
|
+
::ActiveRecord::Base.connection_handler.connection_pool_list(:primary).reject do |cp|
|
48
|
+
FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
|
49
|
+
end.map(&:connection)
|
40
50
|
end
|
41
51
|
end
|
42
52
|
end
|
@@ -22,9 +22,14 @@ module Switchman
|
|
22
22
|
|
23
23
|
def lookup_store(*store_options)
|
24
24
|
store = super
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
25
|
+
# must use the string name, otherwise it will try to auto-load the constant
|
26
|
+
# and we don't want to require redis in this file (since it's not a hard dependency)
|
27
|
+
# rubocop:disable Style/ClassEqualityComparison
|
28
|
+
if store.class.name == "ActiveSupport::Cache::RedisCacheStore" &&
|
29
|
+
!(::ActiveSupport::Cache::RedisCacheStore <= RedisCacheStore)
|
30
|
+
::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
|
31
|
+
end
|
32
|
+
# rubocop:enable Style/ClassEqualityComparison
|
28
33
|
store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
|
29
34
|
store
|
30
35
|
end
|
@@ -33,7 +38,7 @@ module Switchman
|
|
33
38
|
module RedisCacheStore
|
34
39
|
def clear(namespace: nil, **)
|
35
40
|
# RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
|
36
|
-
# unfortunately, it
|
41
|
+
# unfortunately, it doesn't work using redis clustering because of the way redis keys are distributed
|
37
42
|
# fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
|
38
43
|
# always unset it temporarily for clear calls
|
39
44
|
namespace = nil # rubocop:disable Lint/ShadowedArgument
|
data/lib/switchman/arel.rb
CHANGED
@@ -13,38 +13,54 @@ module Switchman
|
|
13
13
|
# rubocop:disable Naming/MethodName
|
14
14
|
# rubocop:disable Naming/MethodParameterName
|
15
15
|
|
16
|
+
def visit_Arel_Nodes_Cte(o, collector)
|
17
|
+
collector << quote_local_table_name(o.name)
|
18
|
+
collector << " AS "
|
19
|
+
|
20
|
+
case o.materialized
|
21
|
+
when true
|
22
|
+
collector << "MATERIALIZED "
|
23
|
+
when false
|
24
|
+
collector << "NOT MATERIALIZED "
|
25
|
+
end
|
26
|
+
|
27
|
+
visit o.relation, collector
|
28
|
+
end
|
29
|
+
|
16
30
|
def visit_Arel_Nodes_TableAlias(o, collector)
|
17
31
|
collector = visit o.relation, collector
|
18
|
-
collector <<
|
32
|
+
collector << " "
|
19
33
|
collector << quote_local_table_name(o.name)
|
20
34
|
end
|
21
35
|
|
22
36
|
def visit_Arel_Attributes_Attribute(o, collector)
|
23
37
|
join_name = o.relation.table_alias || o.relation.name
|
24
|
-
collector << quote_local_table_name(join_name) <<
|
38
|
+
collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
|
25
39
|
end
|
26
40
|
|
27
|
-
|
28
|
-
|
41
|
+
if ::Rails.version < "7.1"
|
42
|
+
def visit_Arel_Nodes_HomogeneousIn(o, collector)
|
43
|
+
collector.preparable = false
|
29
44
|
|
30
|
-
|
45
|
+
collector << quote_local_table_name(o.table_name) << "." << quote_column_name(o.column_name)
|
31
46
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
47
|
+
collector << if o.type == :in
|
48
|
+
" IN ("
|
49
|
+
else
|
50
|
+
" NOT IN ("
|
51
|
+
end
|
37
52
|
|
38
|
-
|
53
|
+
values = o.casted_values
|
39
54
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
55
|
+
if values.empty?
|
56
|
+
collector << @connection.quote(nil)
|
57
|
+
else
|
58
|
+
collector.add_binds(values, o.proc_for_binds, &bind_block)
|
59
|
+
end
|
45
60
|
|
46
|
-
|
47
|
-
|
61
|
+
collector << ")"
|
62
|
+
collector
|
63
|
+
end
|
48
64
|
end
|
49
65
|
|
50
66
|
# rubocop:enable Naming/MethodName
|
data/lib/switchman/call_super.rb
CHANGED
@@ -12,14 +12,8 @@ module Switchman
|
|
12
12
|
method.super_method
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
super_method_above(method, above_module).call(*args, &block)
|
18
|
-
end
|
19
|
-
else
|
20
|
-
def call_super(method, above_module, *args, **kwargs, &block)
|
21
|
-
super_method_above(method, above_module).call(*args, **kwargs, &block)
|
22
|
-
end
|
15
|
+
def call_super(method, above_module, ...)
|
16
|
+
super_method_above(method, above_module).call(...)
|
23
17
|
end
|
24
18
|
end
|
25
19
|
end
|
@@ -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
|
@@ -10,19 +10,23 @@ module Switchman
|
|
10
10
|
attr_accessor :creating_new_shard
|
11
11
|
attr_reader :all_roles
|
12
12
|
|
13
|
+
include Enumerable
|
14
|
+
|
15
|
+
delegate :each, to: :all
|
16
|
+
|
13
17
|
def all
|
14
18
|
database_servers.values
|
15
19
|
end
|
16
20
|
|
17
21
|
def find(id_or_all)
|
18
22
|
return all if id_or_all == :all
|
19
|
-
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)
|
20
24
|
|
21
25
|
database_servers[id_or_all || ::Rails.env]
|
22
26
|
end
|
23
27
|
|
24
28
|
def create(settings = {})
|
25
|
-
raise
|
29
|
+
raise "database servers should be set up in database.yml" unless ::Rails.env.test?
|
26
30
|
|
27
31
|
id = settings[:id]
|
28
32
|
unless id
|
@@ -50,6 +54,10 @@ module Switchman
|
|
50
54
|
all.each { |db| db.guard! if db.config[:prefer_secondary] }
|
51
55
|
end
|
52
56
|
|
57
|
+
def regions
|
58
|
+
@regions ||= all.filter_map(&:region).uniq.sort
|
59
|
+
end
|
60
|
+
|
53
61
|
private
|
54
62
|
|
55
63
|
def reference_role(role)
|
@@ -64,8 +72,8 @@ module Switchman
|
|
64
72
|
@database_servers = {}.with_indifferent_access
|
65
73
|
roles = []
|
66
74
|
::ActiveRecord::Base.configurations.configurations.each do |config|
|
67
|
-
if config.name.include?(
|
68
|
-
name, role = config.name.split(
|
75
|
+
if config.name.include?("/")
|
76
|
+
name, role = config.name.split("/")
|
69
77
|
else
|
70
78
|
name, role = config.env_name, config.name
|
71
79
|
end
|
@@ -81,6 +89,8 @@ module Switchman
|
|
81
89
|
# Do this after so that all database servers for all roles are established and we won't prematurely
|
82
90
|
# configure a connection for the wrong role
|
83
91
|
@all_roles = roles.uniq
|
92
|
+
return @database_servers if @database_servers.empty?
|
93
|
+
|
84
94
|
Shard.send(:configure_connects_to)
|
85
95
|
end
|
86
96
|
@database_servers
|
@@ -110,8 +120,9 @@ module Switchman
|
|
110
120
|
self.class.send(:database_servers).delete(id) if id
|
111
121
|
Shard.sharded_models.each do |klass|
|
112
122
|
self.class.all_roles.each do |role|
|
113
|
-
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
114
|
-
|
123
|
+
klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
|
124
|
+
role: role,
|
125
|
+
shard: id.to_sym)
|
115
126
|
end
|
116
127
|
end
|
117
128
|
end
|
@@ -137,16 +148,41 @@ module Switchman
|
|
137
148
|
end
|
138
149
|
end
|
139
150
|
|
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
|
172
|
+
end
|
173
|
+
|
140
174
|
# locks this db to a specific environment, except for
|
141
175
|
# when doing writes (then it falls back to the current
|
142
176
|
# value of GuardRail.environment)
|
143
177
|
def guard!(environment = :secondary)
|
144
178
|
DatabaseServer.send(:reference_role, environment)
|
145
|
-
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
|
179
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
|
180
|
+
klasses: [::ActiveRecord::Base] }
|
146
181
|
end
|
147
182
|
|
148
183
|
def unguard!
|
149
|
-
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
|
184
|
+
::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
|
185
|
+
klasses: [::ActiveRecord::Base] }
|
150
186
|
end
|
151
187
|
|
152
188
|
def unguard
|
@@ -162,7 +198,7 @@ module Switchman
|
|
162
198
|
|
163
199
|
def shards
|
164
200
|
if id == ::Rails.env
|
165
|
-
Shard.where(
|
201
|
+
Shard.where("database_server_id IS NULL OR database_server_id=?", id)
|
166
202
|
else
|
167
203
|
Shard.where(database_server_id: id)
|
168
204
|
end
|
@@ -187,7 +223,7 @@ module Switchman
|
|
187
223
|
end
|
188
224
|
|
189
225
|
id ||= begin
|
190
|
-
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"))
|
191
227
|
next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
|
192
228
|
next_id.to_i
|
193
229
|
end
|
@@ -204,17 +240,18 @@ module Switchman
|
|
204
240
|
name: name,
|
205
241
|
database_server_id: self.id)
|
206
242
|
if create_statement
|
207
|
-
if ::ActiveRecord::Base.connection.select_value(
|
243
|
+
if ::ActiveRecord::Base.connection.select_value(
|
244
|
+
"SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"
|
245
|
+
)
|
208
246
|
schema_already_existed = true
|
209
|
-
raise
|
247
|
+
raise "This schema already exists; cannot overwrite"
|
210
248
|
end
|
211
249
|
Array(create_statement.call).each do |stmt|
|
212
250
|
::ActiveRecord::Base.connection.execute(stmt)
|
213
251
|
end
|
214
252
|
end
|
215
|
-
if config[:adapter] ==
|
216
|
-
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor
|
217
|
-
end
|
253
|
+
if config[:adapter] == "postgresql"
|
254
|
+
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {}
|
218
255
|
end
|
219
256
|
old_verbose = ::ActiveRecord::Migration.verbose
|
220
257
|
::ActiveRecord::Migration.verbose = false
|
@@ -222,7 +259,11 @@ module Switchman
|
|
222
259
|
unless schema == false
|
223
260
|
shard.activate do
|
224
261
|
::ActiveRecord::Base.connection.transaction(requires_new: true) do
|
225
|
-
::
|
262
|
+
if ::Rails.version < "7.1"
|
263
|
+
::ActiveRecord::Base.connection.migration_context.migrate
|
264
|
+
else
|
265
|
+
::ActiveRecord::MigrationContext.new(::ActiveRecord::Migrator.migrations_paths).migrate
|
266
|
+
end
|
226
267
|
end
|
227
268
|
|
228
269
|
::ActiveRecord::Base.descendants.reject do |m|
|
@@ -262,12 +303,18 @@ module Switchman
|
|
262
303
|
end
|
263
304
|
|
264
305
|
def primary_shard
|
265
|
-
unless
|
306
|
+
return nil unless primary_shard_id
|
307
|
+
|
308
|
+
Shard.lookup(primary_shard_id)
|
309
|
+
end
|
310
|
+
|
311
|
+
def primary_shard_id
|
312
|
+
unless instance_variable_defined?(:@primary_shard_id)
|
266
313
|
# if sharding isn't fully set up yet, we may not be able to query the shards table
|
267
|
-
@
|
268
|
-
@
|
314
|
+
@primary_shard_id = Shard.default.id if Shard.default.database_server == self
|
315
|
+
@primary_shard_id ||= shards.where(name: nil).first&.id
|
269
316
|
end
|
270
|
-
@
|
317
|
+
@primary_shard_id
|
271
318
|
end
|
272
319
|
end
|
273
320
|
end
|
@@ -3,9 +3,10 @@
|
|
3
3
|
module Switchman
|
4
4
|
class DefaultShard
|
5
5
|
def id
|
6
|
-
|
6
|
+
"default"
|
7
7
|
end
|
8
|
-
|
8
|
+
alias_method :cache_key, :id
|
9
|
+
|
9
10
|
def activate(*_classes)
|
10
11
|
yield
|
11
12
|
end
|
@@ -57,8 +58,18 @@ module Switchman
|
|
57
58
|
self
|
58
59
|
end
|
59
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
|
+
|
60
71
|
def _dump(_depth)
|
61
|
-
|
72
|
+
""
|
62
73
|
end
|
63
74
|
|
64
75
|
def self._load(_str)
|
data/lib/switchman/engine.rb
CHANGED
@@ -5,24 +5,28 @@ module Switchman
|
|
5
5
|
isolate_namespace Switchman
|
6
6
|
|
7
7
|
# enable Rails 6.1 style connection handling
|
8
|
-
config.active_record.legacy_connection_handling = false
|
8
|
+
config.active_record.legacy_connection_handling = false if ::Rails.version < "7.1"
|
9
9
|
config.active_record.writing_role = :primary
|
10
10
|
|
11
11
|
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
12
12
|
|
13
|
-
# after :initialize_dependency_mechanism to ensure autoloading is
|
14
|
-
#
|
15
|
-
|
16
|
-
|
13
|
+
# after :initialize_dependency_mechanism to ensure autoloading is
|
14
|
+
# configured for any downstream initializers that care. In rails 7.0 we
|
15
|
+
# should be able to just use an explicit after on configuring the once
|
16
|
+
# autoloaders and not need to go monkey around with initializer order
|
17
|
+
if ::Rails.version < "7.0"
|
18
|
+
initialize_dependency_mechanism = ::Rails::Application::Bootstrap.initializers.find do |i|
|
19
|
+
i.name == :initialize_dependency_mechanism
|
20
|
+
end
|
17
21
|
initialize_dependency_mechanism.instance_variable_get(:@options)[:after] = :set_autoload_paths
|
18
22
|
end
|
19
23
|
|
20
|
-
initializer
|
21
|
-
before:
|
22
|
-
after: (::Rails.version <
|
24
|
+
initializer "switchman.active_record_patch",
|
25
|
+
before: "active_record.initialize_database",
|
26
|
+
after: ((::Rails.version < "7.0") ? :initialize_dependency_mechanism : :setup_once_autoloader) do
|
23
27
|
::ActiveSupport.on_load(:active_record) do
|
24
28
|
# Switchman requires postgres, so just always load the pg adapter
|
25
|
-
require
|
29
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
26
30
|
|
27
31
|
self.default_shard = ::Rails.env.to_sym
|
28
32
|
self.default_role = :primary
|
@@ -50,14 +54,23 @@ module Switchman
|
|
50
54
|
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::Associations::CollectionProxy)
|
51
55
|
|
52
56
|
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Associations::Preloader::Association)
|
53
|
-
|
57
|
+
unless ::Rails.version < "7.0"
|
58
|
+
::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
|
59
|
+
ActiveRecord::Associations::Preloader::Association::LoaderRecords
|
60
|
+
)
|
61
|
+
end
|
54
62
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
|
63
|
+
unless ::Rails.version < "7.1"
|
64
|
+
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
65
|
+
end
|
55
66
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
56
67
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
57
68
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
58
69
|
|
59
70
|
::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
|
60
|
-
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
71
|
+
::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
|
72
|
+
ActiveRecord::DatabaseConfigurations::DatabaseConfig
|
73
|
+
)
|
61
74
|
|
62
75
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
63
76
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
@@ -65,6 +78,11 @@ module Switchman
|
|
65
78
|
::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext)
|
66
79
|
::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
|
67
80
|
|
81
|
+
if ::Rails.version > "7.1.3"
|
82
|
+
::ActiveRecord::PendingMigrationConnection.singleton_class
|
83
|
+
.include(ActiveRecord::PendingMigrationConnection::ClassMethods)
|
84
|
+
end
|
85
|
+
|
68
86
|
::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
|
69
87
|
::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
70
88
|
::ActiveRecord::Reflection::ThroughReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
@@ -77,8 +95,9 @@ module Switchman
|
|
77
95
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
78
96
|
::ActiveRecord::Relation.include(CallSuper)
|
79
97
|
|
80
|
-
::ActiveRecord::PredicateBuilder::
|
81
|
-
|
98
|
+
::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(
|
99
|
+
ActiveRecord::PredicateBuilder::PolymorphicArrayValue
|
100
|
+
)
|
82
101
|
|
83
102
|
::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
|
84
103
|
|
@@ -96,17 +115,18 @@ module Switchman
|
|
96
115
|
|
97
116
|
::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
|
98
117
|
end
|
99
|
-
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
118
|
+
# Ensure that ActiveRecord::Base is always loaded before any app-level
|
119
|
+
# initializers can go try to load Switchman::Shard or we get a loop
|
100
120
|
::ActiveRecord::Base
|
101
121
|
end
|
102
122
|
|
103
|
-
initializer
|
123
|
+
initializer "switchman.error_patch", after: "active_record.initialize_database" do
|
104
124
|
::ActiveSupport.on_load(:active_record) do
|
105
125
|
::StandardError.include(StandardError)
|
106
126
|
end
|
107
127
|
end
|
108
128
|
|
109
|
-
initializer
|
129
|
+
initializer "switchman.initialize_cache", before: :initialize_cache, after: "active_record.initialize_database" do
|
110
130
|
::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
|
111
131
|
|
112
132
|
# if we haven't already setup our cache map out-of-band, set it up from
|
@@ -130,11 +150,11 @@ module Switchman
|
|
130
150
|
Switchman.config[:cache_map][::Rails.env] = value
|
131
151
|
end
|
132
152
|
|
133
|
-
middlewares = Switchman.config[:cache_map].values.
|
153
|
+
middlewares = Switchman.config[:cache_map].values.filter_map do |store|
|
134
154
|
store.middleware if store.respond_to?(:middleware)
|
135
|
-
end.
|
155
|
+
end.uniq
|
136
156
|
middlewares.each do |middleware|
|
137
|
-
config.middleware.insert_before(
|
157
|
+
config.middleware.insert_before("Rack::Runtime", middleware)
|
138
158
|
end
|
139
159
|
|
140
160
|
# prevent :initialize_cache from trying to (or needing to) set
|