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
@@ -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
|