switchman 1.6.1 → 2.0.9
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 +5 -5
- data/app/models/switchman/shard.rb +746 -11
- data/db/migrate/20130328212039_create_switchman_shards.rb +3 -1
- data/db/migrate/20130328224244_create_default_shard.rb +4 -2
- data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +13 -0
- data/db/migrate/20180828183945_add_default_shard_index.rb +15 -0
- data/db/migrate/20180828192111_add_timestamps_to_shards.rb +17 -0
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +9 -0
- data/lib/switchman/action_controller/caching.rb +2 -0
- data/lib/switchman/active_record/abstract_adapter.rb +14 -4
- data/lib/switchman/active_record/association.rb +64 -37
- data/lib/switchman/active_record/attribute_methods.rb +16 -5
- data/lib/switchman/active_record/base.rb +67 -22
- data/lib/switchman/active_record/batches.rb +3 -1
- data/lib/switchman/active_record/calculations.rb +16 -21
- data/lib/switchman/active_record/connection_handler.rb +71 -79
- data/lib/switchman/active_record/connection_pool.rb +28 -23
- data/lib/switchman/active_record/finder_methods.rb +20 -29
- data/lib/switchman/active_record/log_subscriber.rb +14 -19
- data/lib/switchman/active_record/migration.rb +80 -0
- data/lib/switchman/active_record/model_schema.rb +3 -1
- data/lib/switchman/active_record/persistence.rb +9 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +166 -126
- data/lib/switchman/active_record/predicate_builder.rb +2 -0
- data/lib/switchman/active_record/query_cache.rb +22 -87
- data/lib/switchman/active_record/query_methods.rb +122 -126
- data/lib/switchman/active_record/reflection.rb +37 -20
- data/lib/switchman/active_record/relation.rb +50 -29
- data/lib/switchman/active_record/spawn_methods.rb +2 -0
- data/lib/switchman/active_record/statement_cache.rb +44 -52
- data/lib/switchman/active_record/table_definition.rb +4 -2
- data/lib/switchman/active_record/type_caster.rb +2 -0
- data/lib/switchman/active_record/where_clause_factory.rb +5 -2
- data/lib/switchman/active_support/cache.rb +18 -0
- data/lib/switchman/arel.rb +8 -25
- data/lib/switchman/call_super.rb +19 -0
- data/lib/switchman/connection_pool_proxy.rb +70 -24
- data/lib/switchman/database_server.rb +69 -59
- data/lib/switchman/default_shard.rb +3 -0
- data/lib/switchman/engine.rb +42 -40
- data/lib/switchman/environment.rb +2 -0
- data/lib/switchman/errors.rb +2 -0
- data/lib/switchman/{shackles → guard_rail}/relation.rb +7 -5
- data/lib/switchman/{shackles.rb → guard_rail.rb} +6 -4
- data/lib/switchman/open4.rb +2 -0
- data/lib/switchman/r_spec_helper.rb +14 -8
- data/lib/switchman/rails.rb +2 -0
- data/lib/switchman/schema_cache.rb +17 -0
- data/lib/switchman/sharded_instrumenter.rb +4 -2
- data/lib/switchman/standard_error.rb +4 -2
- data/lib/switchman/test_helper.rb +6 -3
- data/lib/switchman/version.rb +3 -1
- data/lib/switchman.rb +3 -1
- data/lib/tasks/switchman.rake +46 -72
- metadata +87 -41
- data/app/models/switchman/shard_internal.rb +0 -692
|
@@ -1,29 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'switchman/connection_pool_proxy'
|
|
2
4
|
|
|
3
5
|
module Switchman
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module ConnectionHandler
|
|
6
|
-
def self.make_sharing_automagic(config)
|
|
7
|
-
key = config[:adapter] == 'postgresql' ? :schema_search_path : :database
|
|
8
|
-
|
|
8
|
+
def self.make_sharing_automagic(config, shard = Shard.current)
|
|
9
9
|
# only load the shard name from the db if we have to
|
|
10
|
-
if
|
|
10
|
+
if !config[:shard_name]
|
|
11
11
|
# we may not be able to connect to this shard yet, cause it might be an empty database server
|
|
12
|
-
|
|
12
|
+
shard = shard.call if shard.is_a?(Proc)
|
|
13
|
+
shard_name = shard.name rescue nil
|
|
13
14
|
return unless shard_name
|
|
14
15
|
|
|
15
16
|
config[:shard_name] ||= shard_name
|
|
16
17
|
end
|
|
17
|
-
|
|
18
|
-
if !config[key] || config[key] == shard_name
|
|
19
|
-
# this may truncate the schema_search_path if it was not specified in database.yml
|
|
20
|
-
# but that's what our old behavior was anyway, so I guess it's okay
|
|
21
|
-
config[key] = '%{shard_name}'
|
|
22
|
-
end
|
|
23
18
|
end
|
|
24
19
|
|
|
25
|
-
def establish_connection(
|
|
26
|
-
|
|
20
|
+
def establish_connection(spec)
|
|
21
|
+
# Just skip establishing a sharded connection if sharding isn't loaded; we'll do it again later
|
|
22
|
+
# This only can happen when loading ActiveRecord::Base; after everything is loaded Shard will
|
|
23
|
+
# be defined and this will actually establish a connection
|
|
24
|
+
return unless defined?(Shard)
|
|
25
|
+
pool = super
|
|
27
26
|
|
|
28
27
|
# this is the first place that the adapter would have been required; but now we
|
|
29
28
|
# need this addition ASAP since it will be called when loading the default shard below
|
|
@@ -32,45 +31,48 @@ module Switchman
|
|
|
32
31
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
33
32
|
end
|
|
34
33
|
|
|
35
|
-
# AR3 uses the name, AR4 uses the model
|
|
36
|
-
model = case owner
|
|
37
|
-
when String
|
|
38
|
-
owner.constantize
|
|
39
|
-
when Class
|
|
40
|
-
owner
|
|
41
|
-
else
|
|
42
|
-
raise "unknown owner #{owner}"
|
|
43
|
-
end
|
|
44
|
-
pool = owner_to_pool[owner.name]
|
|
45
|
-
|
|
46
34
|
first_time = !Shard.instance_variable_get(:@default)
|
|
47
35
|
if first_time
|
|
48
36
|
# Have to cache the default shard before we insert sharding, otherwise the first access
|
|
49
37
|
# to sharding will recurse onto itself trying to access column information
|
|
50
38
|
Shard.default
|
|
51
39
|
|
|
40
|
+
config = pool.spec.config
|
|
52
41
|
# automatically change config to allow for sharing connections with simple config
|
|
53
|
-
ConnectionHandler.make_sharing_automagic(
|
|
42
|
+
ConnectionHandler.make_sharing_automagic(config)
|
|
54
43
|
ConnectionHandler.make_sharing_automagic(Shard.default.database_server.config)
|
|
55
44
|
|
|
56
|
-
::
|
|
45
|
+
if ::Rails.version < '6.0'
|
|
46
|
+
::ActiveRecord::Base.configurations[::Rails.env] = config.stringify_keys
|
|
47
|
+
else
|
|
48
|
+
# Adopted from the deprecated code that currently lives in rails proper
|
|
49
|
+
remaining_configs = ::ActiveRecord::Base.configurations.configurations.reject { |db_config| db_config.env_name == ::Rails.env }
|
|
50
|
+
new_config = ::ActiveRecord::DatabaseConfigurations.new(::Rails.env => config.stringify_keys).configurations
|
|
51
|
+
new_configs = remaining_configs + new_config
|
|
52
|
+
|
|
53
|
+
::ActiveRecord::Base.configurations = new_configs
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
# this is probably wrong now
|
|
57
|
+
Shard.default.remove_instance_variable(:@name) if Shard.default.instance_variable_defined?(:@name)
|
|
57
58
|
end
|
|
58
|
-
@shard_connection_pools ||= { [:master, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
@shard_connection_pools ||= { [:primary, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
|
|
61
|
+
|
|
62
|
+
category = pool.spec.name.to_sym
|
|
63
|
+
proxy = ConnectionPoolProxy.new(category,
|
|
61
64
|
pool,
|
|
62
65
|
@shard_connection_pools)
|
|
63
|
-
owner_to_pool[
|
|
64
|
-
class_to_pool.clear
|
|
66
|
+
owner_to_pool[pool.spec.name] = proxy
|
|
65
67
|
|
|
66
68
|
if first_time
|
|
67
|
-
if Shard.default.database_server.config[:
|
|
68
|
-
Shard.default.database_server.
|
|
69
|
+
if Shard.default.database_server.config[:prefer_secondary]
|
|
70
|
+
Shard.default.database_server.guard!
|
|
69
71
|
end
|
|
70
72
|
|
|
71
|
-
if Shard.default.is_a?(DefaultShard) && Shard.default.database_server.config[:
|
|
72
|
-
Shard.default.database_server.
|
|
73
|
-
Shard.default(true)
|
|
73
|
+
if Shard.default.is_a?(DefaultShard) && Shard.default.database_server.config[:secondary]
|
|
74
|
+
Shard.default.database_server.guard!
|
|
75
|
+
Shard.default(reload: true)
|
|
74
76
|
end
|
|
75
77
|
end
|
|
76
78
|
|
|
@@ -79,7 +81,7 @@ module Switchman
|
|
|
79
81
|
# DON'T do it if we're not the current connection handler - that means
|
|
80
82
|
# we're in the middle of switching environments, and we don't want to
|
|
81
83
|
# establish a connection with incorrect settings
|
|
82
|
-
if (
|
|
84
|
+
if [:primary, :unsharded].include?(category) && self == ::ActiveRecord::Base.connection_handler && !first_time
|
|
83
85
|
Shard.default(reload: true, with_fallback: true)
|
|
84
86
|
proxy.disconnect!
|
|
85
87
|
end
|
|
@@ -89,12 +91,15 @@ module Switchman
|
|
|
89
91
|
if Shard.default.is_a?(Shard)
|
|
90
92
|
DatabaseServer.all.each do |server|
|
|
91
93
|
next if server == Shard.default.database_server
|
|
92
|
-
|
|
93
|
-
shard
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
|
|
95
|
+
shard = nil
|
|
96
|
+
shard_proc = -> do
|
|
97
|
+
shard ||= server.shards.where(:name => nil).first
|
|
98
|
+
shard ||= Shard.new(:database_server => server)
|
|
99
|
+
shard
|
|
97
100
|
end
|
|
101
|
+
ConnectionHandler.make_sharing_automagic(server.config, shard_proc)
|
|
102
|
+
ConnectionHandler.make_sharing_automagic(proxy.current_pool.spec.config, shard_proc)
|
|
98
103
|
end
|
|
99
104
|
end
|
|
100
105
|
# we may have established some connections above trying to infer the shard's name.
|
|
@@ -106,60 +111,47 @@ module Switchman
|
|
|
106
111
|
proxy
|
|
107
112
|
end
|
|
108
113
|
|
|
109
|
-
def remove_connection(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
result
|
|
114
|
+
def remove_connection(spec_name)
|
|
115
|
+
pool = owner_to_pool[spec_name]
|
|
116
|
+
owner_to_pool[spec_name] = pool.default_pool if pool.is_a?(ConnectionPoolProxy)
|
|
117
|
+
super
|
|
114
118
|
end
|
|
115
119
|
|
|
116
|
-
def
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
owner_to_pool.fetch(owner.name) {
|
|
120
|
-
if ancestor_pool = pool_from_any_process_for(owner)
|
|
120
|
+
def retrieve_connection_pool(spec_name)
|
|
121
|
+
owner_to_pool.fetch(spec_name) do
|
|
122
|
+
if ancestor_pool = pool_from_any_process_for(spec_name)
|
|
121
123
|
# A connection was established in an ancestor process that must have
|
|
122
124
|
# subsequently forked. We can't reuse the connection, but we can copy
|
|
123
125
|
# the specification and establish a new connection with it.
|
|
124
|
-
if ancestor_pool.is_a?(ConnectionPoolProxy)
|
|
125
|
-
|
|
126
|
+
spec = if ancestor_pool.is_a?(ConnectionPoolProxy)
|
|
127
|
+
ancestor_pool.default_pool.spec
|
|
126
128
|
else
|
|
127
|
-
|
|
129
|
+
ancestor_pool.spec
|
|
130
|
+
end
|
|
131
|
+
pool = establish_connection(spec.to_hash)
|
|
132
|
+
pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
|
|
133
|
+
pool
|
|
134
|
+
elsif spec_name != "primary"
|
|
135
|
+
primary_pool = retrieve_connection_pool("primary")
|
|
136
|
+
if primary_pool.is_a?(ConnectionPoolProxy)
|
|
137
|
+
pool = ConnectionPoolProxy.new(spec_name.to_sym, primary_pool.default_pool, @shard_connection_pools)
|
|
138
|
+
pool.schema_cache.copy_references(primary_pool.schema_cache)
|
|
139
|
+
pool
|
|
140
|
+
else
|
|
141
|
+
primary_pool
|
|
128
142
|
end
|
|
129
143
|
else
|
|
130
|
-
owner_to_pool[
|
|
144
|
+
owner_to_pool[spec_name] = nil
|
|
131
145
|
end
|
|
132
|
-
}
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def retrieve_connection_pool(klass)
|
|
136
|
-
class_to_pool[klass.name] ||= begin
|
|
137
|
-
original_klass = klass
|
|
138
|
-
until pool = pool_for(klass)
|
|
139
|
-
klass = klass.superclass
|
|
140
|
-
break unless klass <= Base
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
if pool.is_a?(ConnectionPoolProxy) && pool.category != original_klass.shard_category
|
|
144
|
-
default_pool = pool.default_pool
|
|
145
|
-
pool = nil
|
|
146
|
-
class_to_pool.each_value { |p| pool = p if p.is_a?(ConnectionPoolProxy) &&
|
|
147
|
-
p.category == original_klass.shard_category &&
|
|
148
|
-
p.default_pool == default_pool }
|
|
149
|
-
pool ||= ConnectionPoolProxy.new(original_klass.shard_category, default_pool, @shard_connection_pools)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
class_to_pool[original_klass.name] = pool
|
|
153
146
|
end
|
|
154
147
|
end
|
|
155
148
|
|
|
156
149
|
def clear_idle_connections!(since_when)
|
|
157
|
-
|
|
158
|
-
connection_pools.values.each{ |pool| pool.clear_idle_connections!(since_when) }
|
|
150
|
+
connection_pool_list.each{ |pool| pool.clear_idle_connections!(since_when) }
|
|
159
151
|
end
|
|
160
152
|
|
|
161
153
|
def switchman_connection_pool_proxies
|
|
162
|
-
|
|
154
|
+
owner_to_pool.values.uniq.select{|p| p.is_a?(ConnectionPoolProxy)}
|
|
163
155
|
end
|
|
164
156
|
|
|
165
157
|
private
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'switchman/errors'
|
|
2
4
|
|
|
3
5
|
module Switchman
|
|
4
6
|
module ActiveRecord
|
|
5
7
|
module ConnectionPool
|
|
6
8
|
def shard
|
|
7
|
-
Thread.current[
|
|
9
|
+
Thread.current[tls_key] || Shard.default
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
def shard=(value)
|
|
11
|
-
Thread.current[
|
|
13
|
+
Thread.current[tls_key] = value
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def default_schema
|
|
@@ -32,19 +34,14 @@ module Switchman
|
|
|
32
34
|
conn
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
def connection
|
|
36
|
-
conn = super
|
|
37
|
+
def connection(switch_shard: true)
|
|
38
|
+
conn = super()
|
|
37
39
|
raise NonExistentShardError if shard.new_record?
|
|
38
|
-
switch_database(conn) if conn.shard != self.shard
|
|
40
|
+
switch_database(conn) if conn.shard != self.shard && switch_shard
|
|
39
41
|
conn
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
def release_connection(with_id =
|
|
43
|
-
with_id ||= if ::Rails.version >= '5'
|
|
44
|
-
Thread.current
|
|
45
|
-
else
|
|
46
|
-
current_connection_id
|
|
47
|
-
end
|
|
44
|
+
def release_connection(with_id = Thread.current)
|
|
48
45
|
super(with_id)
|
|
49
46
|
|
|
50
47
|
if spec.config[:idle_timeout]
|
|
@@ -52,6 +49,20 @@ module Switchman
|
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
|
|
52
|
+
def remove_shard!(shard)
|
|
53
|
+
synchronize do
|
|
54
|
+
# The shard might be currently active, so we need to update our own shard
|
|
55
|
+
if self.shard == shard
|
|
56
|
+
self.shard = Shard.default
|
|
57
|
+
end
|
|
58
|
+
# Update out any connections that may be using this shard
|
|
59
|
+
@connections.each do |conn|
|
|
60
|
+
# This will also update the connection's shard to the default shard
|
|
61
|
+
switch_database(conn) if conn.shard == shard
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
55
66
|
def clear_idle_connections!(since_when)
|
|
56
67
|
synchronize do
|
|
57
68
|
@connections.reject! do |conn|
|
|
@@ -75,20 +86,14 @@ module Switchman
|
|
|
75
86
|
end
|
|
76
87
|
|
|
77
88
|
spec.config[:shard_name] = self.shard.name
|
|
78
|
-
case conn.adapter_name
|
|
79
|
-
when 'MySQL', 'Mysql2'
|
|
80
|
-
conn.execute("USE #{spec.config[:database]}")
|
|
81
|
-
when 'PostgreSQL'
|
|
82
|
-
if conn.schema_search_path != spec.config[:schema_search_path]
|
|
83
|
-
conn.schema_search_path = spec.config[:schema_search_path]
|
|
84
|
-
end
|
|
85
|
-
when 'SQLite'
|
|
86
|
-
# This is an artifact of the adapter modifying the path to be an absolute path when it is instantiated; just let it slide
|
|
87
|
-
else
|
|
88
|
-
raise("Cannot switch databases on same DatabaseServer with adapter type: #{conn.adapter_name}. Limit one Shard per DatabaseServer.")
|
|
89
|
-
end
|
|
90
89
|
conn.shard = shard
|
|
91
90
|
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def tls_key
|
|
95
|
+
"#{object_id}_shard".to_sym
|
|
96
|
+
end
|
|
92
97
|
end
|
|
93
98
|
end
|
|
94
99
|
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module FinderMethods
|
|
4
|
-
def find_one(id
|
|
6
|
+
def find_one(id)
|
|
5
7
|
return super(id) unless klass.integral_id?
|
|
6
|
-
return super(id) if call_super
|
|
7
8
|
|
|
8
9
|
if shard_source_value != :implicit
|
|
9
10
|
current_shard = Shard.current(klass.shard_category)
|
|
@@ -14,7 +15,7 @@ module Switchman
|
|
|
14
15
|
# skip the shard if the object can't be on it. unless we're only looking at one shard;
|
|
15
16
|
# we might be expecting a shadow object
|
|
16
17
|
next if current_id > Shard::IDS_PER_SHARD && self.all_shards.length > 1
|
|
17
|
-
relation.
|
|
18
|
+
relation.call_super(:find_one, FinderMethods, current_id)
|
|
18
19
|
end
|
|
19
20
|
if result.is_a?(Array)
|
|
20
21
|
result = result.first
|
|
@@ -26,24 +27,18 @@ module Switchman
|
|
|
26
27
|
|
|
27
28
|
local_id, shard = Shard.local_id_for(id)
|
|
28
29
|
if shard
|
|
29
|
-
|
|
30
|
-
# find_one uses binds, so we can't depend on QueryMethods
|
|
31
|
-
# catching it
|
|
32
|
-
begin
|
|
33
|
-
old_shard_value = shard_value
|
|
34
|
-
self.shard_value = shard
|
|
35
|
-
super(local_id)
|
|
36
|
-
ensure
|
|
37
|
-
self.shard_value = old_shard_value
|
|
38
|
-
end
|
|
39
|
-
else
|
|
40
|
-
shard.activate { super(local_id) }
|
|
41
|
-
end
|
|
30
|
+
shard.activate { super(local_id) }
|
|
42
31
|
else
|
|
43
32
|
super(id)
|
|
44
33
|
end
|
|
45
34
|
end
|
|
46
35
|
|
|
36
|
+
def find_some_ordered(ids)
|
|
37
|
+
current_shard = Shard.current(klass.shard_category)
|
|
38
|
+
ids = ids.map{|id| Shard.relative_id_for(id, current_shard, current_shard)}
|
|
39
|
+
super(ids)
|
|
40
|
+
end
|
|
41
|
+
|
|
47
42
|
def find_or_instantiator_by_attributes(match, attributes, *args)
|
|
48
43
|
primary_shard.activate { super }
|
|
49
44
|
end
|
|
@@ -52,13 +47,10 @@ module Switchman
|
|
|
52
47
|
conditions = conditions.id if ::ActiveRecord::Base === conditions
|
|
53
48
|
return false if !conditions
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
join_dependency = construct_join_dependency_for_association_find
|
|
60
|
-
relation = construct_relation_for_association_find(join_dependency)
|
|
61
|
-
end
|
|
50
|
+
relation = ::Rails.version >= "5.2" ?
|
|
51
|
+
apply_join_dependency(eager_loading: false) :
|
|
52
|
+
apply_join_dependency(self, construct_join_dependency)
|
|
53
|
+
return false if ::ActiveRecord::NullRelation === relation
|
|
62
54
|
|
|
63
55
|
relation = relation.except(:select, :order).select("1 AS one").limit(1)
|
|
64
56
|
|
|
@@ -69,12 +61,11 @@ module Switchman
|
|
|
69
61
|
relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
|
|
70
62
|
end
|
|
71
63
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
raise if ::Rails.version >= '4.1' || !(::ActiveRecord::ThrowResult === $!)
|
|
64
|
+
relation.activate do |shard_rel|
|
|
65
|
+
return true if ::Rails.version >= "5.2" ?
|
|
66
|
+
connection.select_value(shard_rel.arel, "#{name} Exists") :
|
|
67
|
+
connection.select_value(shard_rel, "#{name} Exists", shard_rel.bound_attributes)
|
|
68
|
+
end
|
|
78
69
|
false
|
|
79
70
|
end
|
|
80
71
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module LogSubscriber
|
|
@@ -8,35 +10,28 @@ module Switchman
|
|
|
8
10
|
|
|
9
11
|
payload = event.payload
|
|
10
12
|
|
|
11
|
-
return if
|
|
13
|
+
return if ::ActiveRecord::LogSubscriber::IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
|
12
14
|
|
|
13
|
-
name =
|
|
15
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
|
16
|
+
name = "CACHE #{name}" if payload[:cached]
|
|
14
17
|
sql = payload[:sql].squeeze(' '.freeze)
|
|
15
18
|
binds = nil
|
|
16
19
|
shard = payload[:shard]
|
|
17
20
|
shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
|
|
18
21
|
|
|
19
22
|
unless (payload[:binds] || []).empty?
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
use_old_format = (::Rails.version < '5.1.5')
|
|
24
|
+
args = use_old_format ?
|
|
25
|
+
[payload[:binds], payload[:type_casted_binds]] :
|
|
26
|
+
[payload[:type_casted_binds]]
|
|
27
|
+
casted_params = type_casted_binds(*args)
|
|
28
|
+
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
|
29
|
+
render_bind(attr, value)
|
|
26
30
|
}.inspect
|
|
27
31
|
end
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
sql = color(sql, sql_color(sql), true)
|
|
32
|
-
else
|
|
33
|
-
if odd?
|
|
34
|
-
name = color(name, self.class::CYAN, true)
|
|
35
|
-
sql = color(sql, nil, true)
|
|
36
|
-
else
|
|
37
|
-
name = color(name, self.class::MAGENTA, true)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
33
|
+
name = colorize_payload_name(name, payload[:name])
|
|
34
|
+
sql = color(sql, sql_color(sql), true)
|
|
40
35
|
|
|
41
36
|
debug " #{name} #{sql}#{binds}#{shard}"
|
|
42
37
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Switchman
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Migration
|
|
6
|
+
module Compatibility
|
|
7
|
+
module V5_0
|
|
8
|
+
def create_table(*args, **options)
|
|
9
|
+
unless options.key?(:id)
|
|
10
|
+
options[:id] = :bigserial
|
|
11
|
+
end
|
|
12
|
+
if block_given?
|
|
13
|
+
super do |td|
|
|
14
|
+
yield td
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def connection
|
|
24
|
+
conn = super
|
|
25
|
+
if conn.shard != ::ActiveRecord::Base.connection_pool.current_pool.shard
|
|
26
|
+
::ActiveRecord::Base.connection_pool.current_pool.switch_database(conn)
|
|
27
|
+
end
|
|
28
|
+
conn
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Migrator
|
|
33
|
+
# significant change: hash shard id, not database name
|
|
34
|
+
def generate_migrator_advisory_lock_id
|
|
35
|
+
shard_name_hash = Zlib.crc32(Shard.current.name)
|
|
36
|
+
::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if ::Rails.version >= '6.0'
|
|
40
|
+
# copy/paste from Rails 6.1
|
|
41
|
+
def with_advisory_lock
|
|
42
|
+
lock_id = generate_migrator_advisory_lock_id
|
|
43
|
+
|
|
44
|
+
with_advisory_lock_connection do |connection|
|
|
45
|
+
got_lock = connection.get_advisory_lock(lock_id)
|
|
46
|
+
raise ::ActiveRecord::ConcurrentMigrationError unless got_lock
|
|
47
|
+
load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
|
|
48
|
+
yield
|
|
49
|
+
ensure
|
|
50
|
+
if got_lock && !connection.release_advisory_lock(lock_id)
|
|
51
|
+
raise ::ActiveRecord::ConcurrentMigrationError.new(
|
|
52
|
+
::ActiveRecord::ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# significant change: strip out prefer_secondary from config
|
|
59
|
+
def with_advisory_lock_connection
|
|
60
|
+
pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
|
|
61
|
+
::ActiveRecord::Base.connection_config.except(:prefer_secondary)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
pool.with_connection { |connection| yield(connection) }
|
|
65
|
+
ensure
|
|
66
|
+
pool&.disconnect!
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module MigrationContext
|
|
72
|
+
def migrations
|
|
73
|
+
return @migrations if instance_variable_defined?(:@migrations)
|
|
74
|
+
migrations_cache = Thread.current[:migrations_cache] ||= {}
|
|
75
|
+
key = Digest::MD5.hexdigest(migration_files.sort.join(','))
|
|
76
|
+
@migrations = migrations_cache[key] ||= super
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module ModelSchema
|
|
4
6
|
module ClassMethods
|
|
5
7
|
def quoted_table_name
|
|
6
8
|
@quoted_table_name ||= {}
|
|
7
|
-
@quoted_table_name[Shard.current.id] ||= connection.quote_table_name(table_name)
|
|
9
|
+
@quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
|
|
8
10
|
end
|
|
9
11
|
end
|
|
10
12
|
end
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module ActiveRecord
|
|
3
5
|
module Persistence
|
|
4
6
|
# touch reads the id attribute directly, so it's not relative to the current shard
|
|
5
|
-
def touch(
|
|
7
|
+
def touch(*, **)
|
|
6
8
|
shard.activate(self.class.shard_category) { super }
|
|
7
9
|
end
|
|
10
|
+
|
|
11
|
+
if ::Rails.version >= '5.2'
|
|
12
|
+
def update_columns(*)
|
|
13
|
+
shard.activate(self.class.shard_category) { super }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
8
16
|
end
|
|
9
17
|
end
|
|
10
18
|
end
|