switchman 1.14.4 → 1.15.0
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/app/models/switchman/shard.rb +718 -11
- data/lib/switchman/active_record/association.rb +12 -11
- data/lib/switchman/active_record/base.rb +10 -2
- data/lib/switchman/active_record/calculations.rb +1 -2
- data/lib/switchman/active_record/connection_handler.rb +17 -6
- data/lib/switchman/active_record/connection_pool.rb +17 -3
- data/lib/switchman/active_record/log_subscriber.rb +8 -12
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +5 -24
- data/lib/switchman/active_record/query_cache.rb +17 -107
- data/lib/switchman/active_record/statement_cache.rb +1 -9
- data/lib/switchman/active_support/cache.rb +16 -0
- data/lib/switchman/connection_pool_proxy.rb +27 -11
- data/lib/switchman/database_server.rb +8 -1
- data/lib/switchman/default_shard.rb +1 -0
- data/lib/switchman/engine.rb +7 -6
- data/lib/switchman/r_spec_helper.rb +2 -2
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +17 -4
- metadata +6 -7
- data/app/models/switchman/shard_internal.rb +0 -719
|
@@ -30,17 +30,14 @@ module Switchman
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
module CollectionAssociation
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
|
|
40
|
-
super
|
|
41
|
-
end
|
|
33
|
+
def find_target
|
|
34
|
+
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
|
|
35
|
+
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
36
|
+
# when called for the owner, will be returned relative to shard the query will execute on
|
|
37
|
+
Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
|
|
38
|
+
super
|
|
42
39
|
end
|
|
43
|
-
|
|
40
|
+
end
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
module BelongsToAssociation
|
|
@@ -88,7 +85,11 @@ module Switchman
|
|
|
88
85
|
# Copypasta from Activerecord but with added global_id_for goodness.
|
|
89
86
|
def records_for(ids)
|
|
90
87
|
scope.where(association_key_name => ids).load do |record|
|
|
91
|
-
global_key =
|
|
88
|
+
global_key = if record.class.shard_category == :unsharded
|
|
89
|
+
convert_key(record[association_key_name])
|
|
90
|
+
else
|
|
91
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
|
92
|
+
end
|
|
92
93
|
owner = owners_by_key[global_key.to_s].first
|
|
93
94
|
association = owner.association(reflection.name)
|
|
94
95
|
association.set_inverse_instance(record)
|
|
@@ -68,6 +68,14 @@ module Switchman
|
|
|
68
68
|
result
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
|
+
|
|
72
|
+
def clear_query_caches_for_current_thread
|
|
73
|
+
::ActiveRecord::Base.connection_handlers.each_value do |handler|
|
|
74
|
+
handler.connection_pool_list.each do |pool|
|
|
75
|
+
pool.connection(switch_shard: false).clear_query_cache if pool.active_connection?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
def self.included(klass)
|
|
@@ -99,12 +107,12 @@ module Switchman
|
|
|
99
107
|
|
|
100
108
|
def save(*args)
|
|
101
109
|
@shard_set_in_stone = true
|
|
102
|
-
self.class.shard(shard, :implicit).scoping { super }
|
|
110
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
103
111
|
end
|
|
104
112
|
|
|
105
113
|
def save!(*args)
|
|
106
114
|
@shard_set_in_stone = true
|
|
107
|
-
self.class.shard(shard, :implicit).scoping { super }
|
|
115
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
108
116
|
end
|
|
109
117
|
|
|
110
118
|
def destroy
|
|
@@ -121,9 +121,8 @@ module Switchman
|
|
|
121
121
|
group_fields = group_attrs
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
# .clone below corrects for what I consider a Rails bug. column_alias_for modifies the string in place.
|
|
125
124
|
# to_s is because Rails 5 returns a string but Rails 6 returns a symbol.
|
|
126
|
-
group_aliases = group_fields.map { |field| column_alias_for(field.
|
|
125
|
+
group_aliases = group_fields.map { |field| column_alias_for(field.downcase.to_s).to_s }
|
|
127
126
|
group_columns = group_aliases.zip(group_fields).map { |aliaz, field|
|
|
128
127
|
[aliaz, type_for(field), column_name_for(field)]
|
|
129
128
|
}
|
|
@@ -24,6 +24,10 @@ module Switchman
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def establish_connection(spec)
|
|
27
|
+
# Just skip establishing a sharded connection if sharding isn't loaded; we'll do it again later
|
|
28
|
+
# This only can happen when loading ActiveRecord::Base; after everything is loaded Shard will
|
|
29
|
+
# be defined and this will actually establish a connection
|
|
30
|
+
return unless defined?(Shard)
|
|
27
31
|
pool = super
|
|
28
32
|
|
|
29
33
|
# this is the first place that the adapter would have been required; but now we
|
|
@@ -39,16 +43,24 @@ module Switchman
|
|
|
39
43
|
# to sharding will recurse onto itself trying to access column information
|
|
40
44
|
Shard.default
|
|
41
45
|
|
|
46
|
+
config = pool.spec.config
|
|
42
47
|
# automatically change config to allow for sharing connections with simple config
|
|
43
|
-
config = ::Rails.version < '5.1' ? spec.config : pool.spec.config
|
|
44
48
|
ConnectionHandler.make_sharing_automagic(config)
|
|
45
49
|
ConnectionHandler.make_sharing_automagic(Shard.default.database_server.config)
|
|
46
50
|
|
|
47
|
-
if ::Rails.version < '
|
|
48
|
-
::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
|
|
49
|
-
else
|
|
51
|
+
if ::Rails.version < '6.0'
|
|
50
52
|
::ActiveRecord::Base.configurations[::Rails.env] = config.stringify_keys
|
|
53
|
+
else
|
|
54
|
+
# Adopted from the deprecated code that currently lives in rails proper
|
|
55
|
+
remaining_configs = ::ActiveRecord::Base.configurations.configurations.reject { |db_config| db_config.env_name == ::Rails.env }
|
|
56
|
+
new_config = ::ActiveRecord::DatabaseConfigurations.new(::Rails.env => config.stringify_keys).configurations
|
|
57
|
+
new_configs = remaining_configs + new_config
|
|
58
|
+
|
|
59
|
+
::ActiveRecord::Base.configurations = new_configs
|
|
51
60
|
end
|
|
61
|
+
else
|
|
62
|
+
# this is probably wrong now
|
|
63
|
+
Shard.default.remove_instance_variable(:@name) if Shard.default.instance_variable_defined?(:@name)
|
|
52
64
|
end
|
|
53
65
|
|
|
54
66
|
@shard_connection_pools ||= { [:master, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
|
|
@@ -122,8 +134,7 @@ module Switchman
|
|
|
122
134
|
else
|
|
123
135
|
ancestor_pool.spec
|
|
124
136
|
end
|
|
125
|
-
|
|
126
|
-
pool = establish_connection spec
|
|
137
|
+
pool = establish_connection(spec.to_hash)
|
|
127
138
|
pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
|
|
128
139
|
pool
|
|
129
140
|
elsif spec_name != "primary"
|
|
@@ -32,10 +32,10 @@ module Switchman
|
|
|
32
32
|
conn
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def connection
|
|
36
|
-
conn = super
|
|
35
|
+
def connection(switch_shard: true)
|
|
36
|
+
conn = super()
|
|
37
37
|
raise NonExistentShardError if shard.new_record?
|
|
38
|
-
switch_database(conn) if conn.shard != self.shard
|
|
38
|
+
switch_database(conn) if conn.shard != self.shard && switch_shard
|
|
39
39
|
conn
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -47,6 +47,20 @@ module Switchman
|
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def remove_shard!(shard)
|
|
51
|
+
synchronize do
|
|
52
|
+
# The shard might be currently active, so we need to update our own shard
|
|
53
|
+
if self.shard == shard
|
|
54
|
+
self.shard = Shard.default
|
|
55
|
+
end
|
|
56
|
+
# Update out any connections that may be using this shard
|
|
57
|
+
@connections.each do |conn|
|
|
58
|
+
# This will also update the connection's shard to the default shard
|
|
59
|
+
switch_database(conn) if conn.shard == shard
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
50
64
|
def clear_idle_connections!(since_when)
|
|
51
65
|
synchronize do
|
|
52
66
|
@connections.reject! do |conn|
|
|
@@ -18,18 +18,14 @@ module Switchman
|
|
|
18
18
|
shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
|
|
19
19
|
|
|
20
20
|
unless (payload[:binds] || []).empty?
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
|
30
|
-
render_bind(attr, value)
|
|
31
|
-
}.inspect
|
|
32
|
-
end
|
|
21
|
+
use_old_format = (::Rails.version < '5.1.5')
|
|
22
|
+
args = use_old_format ?
|
|
23
|
+
[payload[:binds], payload[:type_casted_binds]] :
|
|
24
|
+
[payload[:type_casted_binds]]
|
|
25
|
+
casted_params = type_casted_binds(*args)
|
|
26
|
+
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
|
27
|
+
render_bind(attr, value)
|
|
28
|
+
}.inspect
|
|
33
29
|
end
|
|
34
30
|
|
|
35
31
|
name = colorize_payload_name(name, payload[:name])
|
|
@@ -4,7 +4,7 @@ module Switchman
|
|
|
4
4
|
module ClassMethods
|
|
5
5
|
def quoted_table_name
|
|
6
6
|
@quoted_table_name ||= {}
|
|
7
|
-
@quoted_table_name[Shard.current.id] ||= connection.quote_table_name(table_name)
|
|
7
|
+
@quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -52,31 +52,12 @@ module Switchman
|
|
|
52
52
|
SQL
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
name.instance_variable_set(:@schema, shard.name)
|
|
60
|
-
end
|
|
61
|
-
[name.schema, name.identifier]
|
|
62
|
-
end
|
|
63
|
-
else
|
|
64
|
-
def data_source_exists?(name)
|
|
65
|
-
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
|
|
66
|
-
return false unless name.identifier
|
|
67
|
-
if !name.schema && use_qualified_names?
|
|
68
|
-
name.instance_variable_set(:@schema, shard.name)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
|
72
|
-
SELECT COUNT(*)
|
|
73
|
-
FROM pg_class c
|
|
74
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
75
|
-
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
|
|
76
|
-
AND c.relname = '#{name.identifier}'
|
|
77
|
-
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
|
|
78
|
-
SQL
|
|
55
|
+
def extract_schema_qualified_name(string)
|
|
56
|
+
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
|
|
57
|
+
if string && !name.schema && use_qualified_names?
|
|
58
|
+
name.instance_variable_set(:@schema, shard.name)
|
|
79
59
|
end
|
|
60
|
+
[name.schema, name.identifier]
|
|
80
61
|
end
|
|
81
62
|
|
|
82
63
|
def view_exists?(name)
|
|
@@ -1,122 +1,32 @@
|
|
|
1
1
|
module Switchman
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module QueryCache
|
|
4
|
-
if ::Rails.version < '5.0.1'
|
|
5
|
-
# thread local accessors to replace @query_cache_enabled
|
|
6
|
-
def query_cache
|
|
7
|
-
thread_cache = Thread.current[:query_cache] ||= {}
|
|
8
|
-
thread_cache[self.object_id] ||= Hash.new { |h,sql| h[sql] = {} }
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def query_cache_enabled
|
|
12
|
-
Thread.current[:query_cache_enabled]
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def query_cache_enabled=(value)
|
|
16
|
-
Thread.current[:query_cache_enabled] = value
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# basically wholesale repeat of the methods from the original (see
|
|
20
|
-
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),
|
|
21
|
-
# but with self.query_cache_enabled and self.query_cache_enabled= instead
|
|
22
|
-
# of @query_cache_enabled.
|
|
23
|
-
|
|
24
|
-
def enable_query_cache!
|
|
25
|
-
self.query_cache_enabled = true
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def disable_query_cache!
|
|
29
|
-
self.query_cache_enabled = false
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def cache
|
|
33
|
-
old, self.query_cache_enabled = query_cache_enabled, true
|
|
34
|
-
yield
|
|
35
|
-
ensure
|
|
36
|
-
self.query_cache_enabled = old
|
|
37
|
-
clear_query_cache unless self.query_cache_enabled
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def uncached
|
|
41
|
-
old, self.query_cache_enabled = query_cache_enabled, false
|
|
42
|
-
yield
|
|
43
|
-
ensure
|
|
44
|
-
self.query_cache_enabled = old
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def clear_query_cache
|
|
48
|
-
Thread.current[:query_cache]&.clear
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def select_all(arel, name = nil, binds = [], preparable: nil)
|
|
52
|
-
if self.query_cache_enabled && !locked?(arel)
|
|
53
|
-
arel, binds = binds_from_relation(arel, binds)
|
|
54
|
-
sql = to_sql(arel, binds)
|
|
55
|
-
cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
|
|
56
|
-
else
|
|
57
|
-
super
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# no reason to define these on the including class directly. the super
|
|
62
|
-
# works just as well from a method on the included module
|
|
63
|
-
[:insert, :update, :delete].each do |method_name|
|
|
64
|
-
class_eval <<-end_code, __FILE__, __LINE__ + 1
|
|
65
|
-
def #{method_name}(*args)
|
|
66
|
-
clear_query_cache if self.query_cache_enabled
|
|
67
|
-
super
|
|
68
|
-
end
|
|
69
|
-
end_code
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
4
|
|
|
73
5
|
private
|
|
74
6
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
7
|
+
def cache_sql(sql, name, binds)
|
|
8
|
+
# have to include the shard id in the cache key because of switching dbs on the same connection
|
|
9
|
+
sql = "#{self.shard.id}::#{sql}"
|
|
10
|
+
@lock.synchronize do
|
|
79
11
|
result =
|
|
80
12
|
if query_cache[sql].key?(binds)
|
|
81
|
-
args = {
|
|
82
|
-
|
|
83
|
-
|
|
13
|
+
args = {
|
|
14
|
+
sql: sql,
|
|
15
|
+
binds: binds,
|
|
16
|
+
name: name,
|
|
17
|
+
connection_id: object_id,
|
|
18
|
+
cached: true
|
|
19
|
+
}
|
|
20
|
+
args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
|
|
21
|
+
::ActiveSupport::Notifications.instrument(
|
|
22
|
+
"sql.active_record",
|
|
23
|
+
args
|
|
24
|
+
)
|
|
84
25
|
query_cache[sql][binds]
|
|
85
26
|
else
|
|
86
27
|
query_cache[sql][binds] = yield
|
|
87
28
|
end
|
|
88
|
-
|
|
89
|
-
if ::ActiveRecord::Result === result
|
|
90
|
-
result.dup
|
|
91
|
-
else
|
|
92
|
-
result.collect { |row| row.dup }
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
else
|
|
96
|
-
def cache_sql(sql, name, binds)
|
|
97
|
-
# have to include the shard id in the cache key because of switching dbs on the same connection
|
|
98
|
-
sql = "#{self.shard.id}::#{sql}"
|
|
99
|
-
@lock.synchronize do
|
|
100
|
-
result =
|
|
101
|
-
if query_cache[sql].key?(binds)
|
|
102
|
-
args = {
|
|
103
|
-
sql: sql,
|
|
104
|
-
binds: binds,
|
|
105
|
-
name: name,
|
|
106
|
-
connection_id: object_id,
|
|
107
|
-
cached: true
|
|
108
|
-
}
|
|
109
|
-
args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
|
|
110
|
-
::ActiveSupport::Notifications.instrument(
|
|
111
|
-
"sql.active_record",
|
|
112
|
-
args
|
|
113
|
-
)
|
|
114
|
-
query_cache[sql][binds]
|
|
115
|
-
else
|
|
116
|
-
query_cache[sql][binds] = yield
|
|
117
|
-
end
|
|
118
|
-
result.dup
|
|
119
|
-
end
|
|
29
|
+
result.dup
|
|
120
30
|
end
|
|
121
31
|
end
|
|
122
32
|
end
|
|
@@ -56,15 +56,7 @@ module Switchman
|
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
if ::Rails.version < '5.
|
|
60
|
-
def generic_query_builder(connection)
|
|
61
|
-
@query_builder ||= connection.cacheable_query(@arel)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def qualified_query_builder(shard, klass)
|
|
65
|
-
@qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(@arel)
|
|
66
|
-
end
|
|
67
|
-
elsif ::Rails.version < '5.2'
|
|
59
|
+
if ::Rails.version < '5.2'
|
|
68
60
|
def generic_query_builder(connection)
|
|
69
61
|
@query_builder ||= connection.cacheable_query(self.class, @arel)
|
|
70
62
|
end
|
|
@@ -4,10 +4,26 @@ module Switchman
|
|
|
4
4
|
module ClassMethods
|
|
5
5
|
def lookup_store(*store_options)
|
|
6
6
|
store = super
|
|
7
|
+
# can't use defined?, because it's a _ruby_ autoloaded constant,
|
|
8
|
+
# so just checking that will cause it to get required
|
|
9
|
+
if store.class.name == "ActiveSupport::Cache::RedisCacheStore" && !::ActiveSupport::Cache::RedisCacheStore.ancestors.include?(RedisCacheStore)
|
|
10
|
+
::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
|
|
11
|
+
end
|
|
7
12
|
store.options[:namespace] ||= lambda { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
|
|
8
13
|
store
|
|
9
14
|
end
|
|
10
15
|
end
|
|
16
|
+
|
|
17
|
+
module RedisCacheStore
|
|
18
|
+
def clear(options = {})
|
|
19
|
+
# RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
|
|
20
|
+
# unfortunately, it uses the keys command, which is extraordinarily inefficient in a large redis instance
|
|
21
|
+
# fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
|
|
22
|
+
# always unset it temporarily for clear calls
|
|
23
|
+
options[:namespace] = nil
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
11
27
|
end
|
|
12
28
|
end
|
|
13
29
|
end
|
|
@@ -23,7 +23,14 @@ module Switchman
|
|
|
23
23
|
@category = category
|
|
24
24
|
@default_pool = default_pool
|
|
25
25
|
@connection_pools = shard_connection_pools
|
|
26
|
-
@schema_cache =
|
|
26
|
+
@schema_cache = default_pool.get_schema_cache(nil) if ::Rails.version >= '6'
|
|
27
|
+
@schema_cache = SchemaCache.new(self) unless @schema_cache.is_a?(SchemaCache)
|
|
28
|
+
if ::Rails.version >= '6'
|
|
29
|
+
@default_pool.set_schema_cache(@schema_cache)
|
|
30
|
+
@connection_pools.each_value do |pool|
|
|
31
|
+
pool.set_schema_cache(@schema_cache)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
27
34
|
end
|
|
28
35
|
|
|
29
36
|
def active_shard
|
|
@@ -46,11 +53,11 @@ module Switchman
|
|
|
46
53
|
connection_pools.map(&:connections).inject([], &:+)
|
|
47
54
|
end
|
|
48
55
|
|
|
49
|
-
def connection
|
|
56
|
+
def connection(switch_shard: true)
|
|
50
57
|
pool = current_pool
|
|
51
58
|
begin
|
|
52
|
-
connection = pool.connection
|
|
53
|
-
connection.instance_variable_set(:@schema_cache, @schema_cache)
|
|
59
|
+
connection = pool.connection(switch_shard: switch_shard)
|
|
60
|
+
connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
|
|
54
61
|
connection
|
|
55
62
|
rescue ConnectionError
|
|
56
63
|
raise if active_shard.database_server == Shard.default.database_server && active_shackles_environment == :master
|
|
@@ -60,7 +67,7 @@ module Switchman
|
|
|
60
67
|
pool = create_pool(config.dup)
|
|
61
68
|
begin
|
|
62
69
|
connection = pool.connection
|
|
63
|
-
connection.instance_variable_set(:@schema_cache, @schema_cache)
|
|
70
|
+
connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
|
|
64
71
|
rescue ConnectionError
|
|
65
72
|
raise if idx == configs.length - 1
|
|
66
73
|
next
|
|
@@ -71,6 +78,10 @@ module Switchman
|
|
|
71
78
|
end
|
|
72
79
|
end
|
|
73
80
|
|
|
81
|
+
def get_schema_cache(_connection)
|
|
82
|
+
@schema_cache
|
|
83
|
+
end
|
|
84
|
+
|
|
74
85
|
%w{release_connection
|
|
75
86
|
disconnect!
|
|
76
87
|
flush!
|
|
@@ -98,6 +109,10 @@ module Switchman
|
|
|
98
109
|
connection_pools.each { |pool| pool.clear_idle_connections!(since_when) }
|
|
99
110
|
end
|
|
100
111
|
|
|
112
|
+
def remove_shard!(shard)
|
|
113
|
+
connection_pools.each { |pool| pool.remove_shard!(shard) }
|
|
114
|
+
end
|
|
115
|
+
|
|
101
116
|
protected
|
|
102
117
|
|
|
103
118
|
def connection_pools
|
|
@@ -132,18 +147,19 @@ module Switchman
|
|
|
132
147
|
end
|
|
133
148
|
end
|
|
134
149
|
end
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(
|
|
151
|
+
category,
|
|
152
|
+
config,
|
|
153
|
+
"#{config[:adapter]}_connection"
|
|
154
|
+
)
|
|
138
155
|
# unfortunately the AR code that does this require logic can't really be
|
|
139
156
|
# called in isolation
|
|
140
157
|
require "active_record/connection_adapters/#{config[:adapter]}_adapter"
|
|
141
158
|
|
|
142
159
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec).tap do |pool|
|
|
143
160
|
pool.shard = shard
|
|
144
|
-
if ::Rails.version >= '
|
|
145
|
-
|
|
146
|
-
end
|
|
161
|
+
pool.set_schema_cache(@schema_cache) if ::Rails.version >= '6'
|
|
162
|
+
pool.enable_query_cache! if !@connection_pools.empty? && @connection_pools.first.last.query_cache_enabled
|
|
147
163
|
end
|
|
148
164
|
end
|
|
149
165
|
end
|
|
@@ -185,6 +185,7 @@ module Switchman
|
|
|
185
185
|
shard = Shard.create!(:id => shard_id,
|
|
186
186
|
:name => name,
|
|
187
187
|
:database_server => self)
|
|
188
|
+
schema_already_existed = false
|
|
188
189
|
|
|
189
190
|
begin
|
|
190
191
|
self.class.creating_new_shard = true
|
|
@@ -192,6 +193,10 @@ module Switchman
|
|
|
192
193
|
::Shackles.activate(:deploy) do
|
|
193
194
|
begin
|
|
194
195
|
if create_statement
|
|
196
|
+
if (::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"))
|
|
197
|
+
schema_already_existed = true
|
|
198
|
+
raise "This schema already exists; cannot overwrite"
|
|
199
|
+
end
|
|
195
200
|
Array(create_statement.call).each do |stmt|
|
|
196
201
|
::ActiveRecord::Base.connection.execute(stmt)
|
|
197
202
|
end
|
|
@@ -229,7 +234,9 @@ module Switchman
|
|
|
229
234
|
shard
|
|
230
235
|
rescue
|
|
231
236
|
shard.destroy
|
|
232
|
-
|
|
237
|
+
unless schema_already_existed
|
|
238
|
+
shard.drop_database rescue nil
|
|
239
|
+
end
|
|
233
240
|
reset_column_information unless create_schema == false rescue nil
|
|
234
241
|
raise
|
|
235
242
|
ensure
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -88,7 +88,7 @@ module Switchman
|
|
|
88
88
|
require "switchman/call_super"
|
|
89
89
|
require "switchman/rails"
|
|
90
90
|
require "switchman/shackles/relation"
|
|
91
|
-
require_dependency "switchman/
|
|
91
|
+
require_dependency "switchman/shard"
|
|
92
92
|
require "switchman/standard_error"
|
|
93
93
|
|
|
94
94
|
::StandardError.include(StandardError)
|
|
@@ -118,11 +118,6 @@ module Switchman
|
|
|
118
118
|
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
119
119
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
120
120
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
121
|
-
# when we call super in Switchman::ActiveRecord::QueryCache#select_all,
|
|
122
|
-
# we want it to find the definition from
|
|
123
|
-
# ActiveRecord::ConnectionAdapters::DatabaseStatements, not
|
|
124
|
-
# ActiveRecord::ConnectionAdapters::QueryCache
|
|
125
|
-
::ActiveRecord::ConnectionAdapters::QueryCache.send(:remove_method, :select_all) if ::Rails.version < '5.0.1'
|
|
126
121
|
|
|
127
122
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
128
123
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
@@ -173,6 +168,12 @@ module Switchman
|
|
|
173
168
|
require "switchman/active_record/postgresql_adapter"
|
|
174
169
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
175
170
|
end
|
|
171
|
+
|
|
172
|
+
# If Switchman::Shard wasn't loaded as of when ActiveRecord::Base initialized
|
|
173
|
+
# establish a connection here instead
|
|
174
|
+
if !Shard.instance_variable_get(:@default)
|
|
175
|
+
::ActiveRecord::Base.establish_connection
|
|
176
|
+
end
|
|
176
177
|
end
|
|
177
178
|
end
|
|
178
179
|
|
|
@@ -121,7 +121,7 @@ module Switchman
|
|
|
121
121
|
klass.before do
|
|
122
122
|
raise "Sharding did not set up correctly" if @@sharding_failed
|
|
123
123
|
Shard.clear_cache
|
|
124
|
-
if
|
|
124
|
+
if use_transactional_tests
|
|
125
125
|
Shard.default(true)
|
|
126
126
|
@shard1 = Shard.find(@shard1.id)
|
|
127
127
|
@shard2 = Shard.find(@shard2.id)
|
|
@@ -137,7 +137,7 @@ module Switchman
|
|
|
137
137
|
|
|
138
138
|
klass.after do
|
|
139
139
|
next if @@sharding_failed
|
|
140
|
-
if
|
|
140
|
+
if use_transactional_tests
|
|
141
141
|
shards = [@shard2]
|
|
142
142
|
shards << @shard1 unless @shard1.database_server == Shard.default.database_server
|
|
143
143
|
shards.each do |shard|
|
data/lib/switchman/version.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
|
@@ -20,7 +20,12 @@ module Switchman
|
|
|
20
20
|
open = servers.delete('open')
|
|
21
21
|
|
|
22
22
|
servers = servers.map { |server| DatabaseServer.find(server) }.compact
|
|
23
|
-
|
|
23
|
+
if open
|
|
24
|
+
open_servers = DatabaseServer.all.select { |server| server.config[:open] }
|
|
25
|
+
servers.concat(open_servers)
|
|
26
|
+
servers << DatabaseServer.find(nil) if open_servers.empty?
|
|
27
|
+
servers.uniq!
|
|
28
|
+
end
|
|
24
29
|
servers = DatabaseServer.all - servers if negative
|
|
25
30
|
end
|
|
26
31
|
|
|
@@ -67,7 +72,17 @@ module Switchman
|
|
|
67
72
|
shard = Shard.current
|
|
68
73
|
puts "#{shard.id}: #{shard.description}"
|
|
69
74
|
::ActiveRecord::Base.connection_pool.spec.config[:shard_name] = Shard.current.name
|
|
70
|
-
::
|
|
75
|
+
if ::Rails.version < '6.0'
|
|
76
|
+
::ActiveRecord::Base.configurations[::Rails.env] = ::ActiveRecord::Base.connection_pool.spec.config.stringify_keys
|
|
77
|
+
else
|
|
78
|
+
# Adopted from the deprecated code that currently lives in rails proper
|
|
79
|
+
remaining_configs = ::ActiveRecord::Base.configurations.configurations.reject { |db_config| db_config.env_name == ::Rails.env }
|
|
80
|
+
new_config = ::ActiveRecord::DatabaseConfigurations.new(::Rails.env =>
|
|
81
|
+
::ActiveRecord::Base.connection_pool.spec.config.stringify_keys).configurations
|
|
82
|
+
new_configs = remaining_configs + new_config
|
|
83
|
+
|
|
84
|
+
::ActiveRecord::Base.configurations = new_configs
|
|
85
|
+
end
|
|
71
86
|
shard.database_server.unshackle do
|
|
72
87
|
old_actions.each { |action| action.call(*task_args) }
|
|
73
88
|
end
|
|
@@ -195,9 +210,7 @@ module Switchman
|
|
|
195
210
|
@filter_database_servers_chain ||= ->(servers) { servers }
|
|
196
211
|
end
|
|
197
212
|
end
|
|
198
|
-
end
|
|
199
213
|
|
|
200
|
-
module Switchman
|
|
201
214
|
module ActiveRecord
|
|
202
215
|
module PostgreSQLDatabaseTasks
|
|
203
216
|
def structure_dump(filename, extra_flags=nil)
|