switchman 1.13.3 → 2.0.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 +743 -11
- data/db/migrate/20130328224244_create_default_shard.rb +1 -1
- data/lib/switchman/active_record/abstract_adapter.rb +2 -6
- data/lib/switchman/active_record/association.rb +35 -12
- data/lib/switchman/active_record/attribute_methods.rb +3 -0
- data/lib/switchman/active_record/base.rb +47 -9
- data/lib/switchman/active_record/calculations.rb +2 -1
- data/lib/switchman/active_record/connection_handler.rb +23 -20
- data/lib/switchman/active_record/connection_pool.rb +17 -15
- data/lib/switchman/active_record/log_subscriber.rb +8 -12
- data/lib/switchman/active_record/migration.rb +9 -0
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +22 -47
- data/lib/switchman/active_record/query_cache.rb +17 -107
- data/lib/switchman/active_record/query_methods.rb +4 -0
- data/lib/switchman/active_record/relation.rb +5 -5
- data/lib/switchman/active_record/statement_cache.rb +4 -25
- data/lib/switchman/active_record/table_definition.rb +2 -2
- data/lib/switchman/active_support/cache.rb +16 -0
- data/lib/switchman/connection_pool_proxy.rb +38 -22
- data/lib/switchman/database_server.rb +32 -19
- data/lib/switchman/default_shard.rb +1 -0
- data/lib/switchman/engine.rb +16 -14
- data/lib/switchman/{shackles → guard_rail}/relation.rb +5 -5
- data/lib/switchman/{shackles.rb → guard_rail.rb} +4 -4
- data/lib/switchman/r_spec_helper.rb +7 -7
- data/lib/switchman/sharded_instrumenter.rb +1 -1
- data/lib/switchman/test_helper.rb +3 -3
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +1 -1
- data/lib/tasks/switchman.rake +23 -17
- metadata +35 -22
- data/app/models/switchman/shard_internal.rb +0 -714
|
@@ -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
|
|
@@ -5,7 +5,7 @@ module Switchman
|
|
|
5
5
|
klass::SINGLE_VALUE_METHODS.concat [ :shard, :shard_source ]
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def initialize(
|
|
8
|
+
def initialize(*, **)
|
|
9
9
|
super
|
|
10
10
|
self.shard_value = Shard.current(klass ? klass.shard_category : :primary) unless shard_value
|
|
11
11
|
self.shard_source_value = :implicit unless shard_source_value
|
|
@@ -17,7 +17,7 @@ module Switchman
|
|
|
17
17
|
result
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def merge(*
|
|
20
|
+
def merge(*)
|
|
21
21
|
relation = super
|
|
22
22
|
if relation.shard_value != self.shard_value && relation.shard_source_value == :implicit
|
|
23
23
|
relation.shard_value = self.shard_value
|
|
@@ -26,15 +26,15 @@ module Switchman
|
|
|
26
26
|
relation
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def new(
|
|
29
|
+
def new(*, &block)
|
|
30
30
|
primary_shard.activate(klass.shard_category) { super }
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def create(
|
|
33
|
+
def create(*, &block)
|
|
34
34
|
primary_shard.activate(klass.shard_category) { super }
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def create!(
|
|
37
|
+
def create!(*, &block)
|
|
38
38
|
primary_shard.activate(klass.shard_category) { super }
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -2,7 +2,7 @@ module Switchman
|
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module StatementCache
|
|
4
4
|
module ClassMethods
|
|
5
|
-
def create(connection, block
|
|
5
|
+
def create(connection, &block)
|
|
6
6
|
relation = block.call ::ActiveRecord::StatementCache::Params.new
|
|
7
7
|
|
|
8
8
|
if ::Rails.version >= "5.2"
|
|
@@ -46,37 +46,16 @@ module Switchman
|
|
|
46
46
|
bind_values = bind_map.bind(params, current_shard, target_shard)
|
|
47
47
|
|
|
48
48
|
target_shard.activate(klass.shard_category) do
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
klass.find_by_sql(sql, bind_values)
|
|
52
|
-
else
|
|
53
|
-
sql = generic_query_builder(connection).sql_for(bind_values, connection)
|
|
54
|
-
klass.find_by_sql(sql, bind_values)
|
|
55
|
-
end
|
|
49
|
+
sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
|
|
50
|
+
klass.find_by_sql(sql, bind_values)
|
|
56
51
|
end
|
|
57
52
|
end
|
|
58
53
|
|
|
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'
|
|
68
|
-
def generic_query_builder(connection)
|
|
69
|
-
@query_builder ||= connection.cacheable_query(self.class, @arel)
|
|
70
|
-
end
|
|
71
|
-
|
|
54
|
+
if ::Rails.version < '5.2'
|
|
72
55
|
def qualified_query_builder(shard, klass)
|
|
73
56
|
@qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(self.class, @arel)
|
|
74
57
|
end
|
|
75
58
|
else
|
|
76
|
-
def generic_query_builder(connection)
|
|
77
|
-
@query_builder ||= connection.cacheable_query(self.class, @arel).first
|
|
78
|
-
end
|
|
79
|
-
|
|
80
59
|
def qualified_query_builder(shard, klass)
|
|
81
60
|
@qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(self.class, @arel).first
|
|
82
61
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Switchman
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module TableDefinition
|
|
4
|
-
def column(name, type,
|
|
5
|
-
Engine.foreign_key_check(name, type,
|
|
4
|
+
def column(name, type, limit: nil, **)
|
|
5
|
+
Engine.foreign_key_check(name, type, limit: limit)
|
|
6
6
|
super
|
|
7
7
|
end
|
|
8
8
|
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,20 +23,27 @@ 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
|
|
30
37
|
Shard.current(@category)
|
|
31
38
|
end
|
|
32
39
|
|
|
33
|
-
def
|
|
34
|
-
::Rails.env.test? ? :
|
|
40
|
+
def active_guard_rail_environment
|
|
41
|
+
::Rails.env.test? ? :primary : active_shard.database_server.guard_rail_environment
|
|
35
42
|
end
|
|
36
43
|
|
|
37
44
|
def current_pool
|
|
38
45
|
current_active_shard = active_shard
|
|
39
|
-
pool = self.default_pool if current_active_shard.database_server == Shard.default.database_server &&
|
|
46
|
+
pool = self.default_pool if current_active_shard.database_server == Shard.default.database_server && active_guard_rail_environment == :primary && (current_active_shard.default? || current_active_shard.database_server.shareable?)
|
|
40
47
|
pool = @connection_pools[pool_key] ||= create_pool unless pool
|
|
41
48
|
pool.shard = current_active_shard
|
|
42
49
|
pool
|
|
@@ -46,21 +53,21 @@ 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
|
-
raise if active_shard.database_server == Shard.default.database_server &&
|
|
57
|
-
configs = active_shard.database_server.config(
|
|
63
|
+
raise if active_shard.database_server == Shard.default.database_server && active_guard_rail_environment == :primary
|
|
64
|
+
configs = active_shard.database_server.config(active_guard_rail_environment)
|
|
58
65
|
raise unless configs.is_a?(Array)
|
|
59
66
|
configs.each_with_index do |config, idx|
|
|
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
|
|
@@ -105,7 +120,7 @@ module Switchman
|
|
|
105
120
|
end
|
|
106
121
|
|
|
107
122
|
def pool_key
|
|
108
|
-
[
|
|
123
|
+
[active_guard_rail_environment,
|
|
109
124
|
active_shard.database_server.shareable? ? active_shard.database_server.pool_key : active_shard]
|
|
110
125
|
end
|
|
111
126
|
|
|
@@ -113,7 +128,7 @@ module Switchman
|
|
|
113
128
|
shard = active_shard
|
|
114
129
|
unless config
|
|
115
130
|
if shard != Shard.default
|
|
116
|
-
config = shard.database_server.config(
|
|
131
|
+
config = shard.database_server.config(active_guard_rail_environment)
|
|
117
132
|
config = config.first if config.is_a?(Array)
|
|
118
133
|
config = config.dup
|
|
119
134
|
else
|
|
@@ -123,27 +138,28 @@ module Switchman
|
|
|
123
138
|
# different models could be using different configs on the default
|
|
124
139
|
# shard, and database server wouldn't know about that
|
|
125
140
|
config = default_pool.spec.instance_variable_get(:@config)
|
|
126
|
-
if config[
|
|
127
|
-
config = config.merge(config[
|
|
128
|
-
elsif config[
|
|
129
|
-
config = config.merge(config[
|
|
141
|
+
if config[active_guard_rail_environment].is_a?(Hash)
|
|
142
|
+
config = config.merge(config[active_guard_rail_environment])
|
|
143
|
+
elsif config[active_guard_rail_environment].is_a?(Array)
|
|
144
|
+
config = config.merge(config[active_guard_rail_environment].first)
|
|
130
145
|
else
|
|
131
146
|
config = config.dup
|
|
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
|
|
@@ -40,8 +40,14 @@ module Switchman
|
|
|
40
40
|
def database_servers
|
|
41
41
|
unless @database_servers
|
|
42
42
|
@database_servers = {}.with_indifferent_access
|
|
43
|
-
::
|
|
44
|
-
|
|
43
|
+
if ::Rails.version >= '6.0'
|
|
44
|
+
::ActiveRecord::Base.configurations.configurations.each do |config|
|
|
45
|
+
@database_servers[config.env_name] = DatabaseServer.new(config.env_name, config.config)
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
::ActiveRecord::Base.configurations.each do |(id, config)|
|
|
49
|
+
@database_servers[id] = DatabaseServer.new(id, config)
|
|
50
|
+
end
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
@database_servers
|
|
@@ -63,12 +69,12 @@ module Switchman
|
|
|
63
69
|
@fake
|
|
64
70
|
end
|
|
65
71
|
|
|
66
|
-
def config(environment = :
|
|
72
|
+
def config(environment = :primary)
|
|
67
73
|
@configs[environment] ||= begin
|
|
68
74
|
if @config[environment].is_a?(Array)
|
|
69
75
|
@config[environment].map do |config|
|
|
70
76
|
config = @config.merge((config || {}).symbolize_keys)
|
|
71
|
-
# make sure
|
|
77
|
+
# make sure GuardRail doesn't get any brilliant ideas about choosing the first possible server
|
|
72
78
|
config.delete(environment)
|
|
73
79
|
config
|
|
74
80
|
end
|
|
@@ -80,33 +86,33 @@ module Switchman
|
|
|
80
86
|
end
|
|
81
87
|
end
|
|
82
88
|
|
|
83
|
-
def
|
|
84
|
-
@
|
|
89
|
+
def guard_rail_environment
|
|
90
|
+
@guard_rail_environment || ::GuardRail.environment
|
|
85
91
|
end
|
|
86
92
|
|
|
87
93
|
# locks this db to a specific environment, except for
|
|
88
94
|
# when doing writes (then it falls back to the current
|
|
89
|
-
# value of
|
|
90
|
-
def
|
|
91
|
-
@
|
|
95
|
+
# value of GuardRail.environment)
|
|
96
|
+
def guard!(environment = :secondary)
|
|
97
|
+
@guard_rail_environment = environment
|
|
92
98
|
end
|
|
93
99
|
|
|
94
|
-
def
|
|
95
|
-
@
|
|
100
|
+
def unguard!
|
|
101
|
+
@guard_rail_environment = nil
|
|
96
102
|
end
|
|
97
103
|
|
|
98
|
-
def
|
|
99
|
-
old_env = @
|
|
100
|
-
|
|
104
|
+
def unguard
|
|
105
|
+
old_env = @guard_rail_environment
|
|
106
|
+
unguard!
|
|
101
107
|
yield
|
|
102
108
|
ensure
|
|
103
|
-
|
|
109
|
+
guard!(old_env)
|
|
104
110
|
end
|
|
105
111
|
|
|
106
112
|
def shareable?
|
|
107
113
|
@shareable_environment_key ||= []
|
|
108
|
-
environment =
|
|
109
|
-
explicit_user = ::
|
|
114
|
+
environment = guard_rail_environment
|
|
115
|
+
explicit_user = ::GuardRail.global_config[:username]
|
|
110
116
|
return @shareable if @shareable_environment_key == [environment, explicit_user]
|
|
111
117
|
@shareable_environment_key = [environment, explicit_user]
|
|
112
118
|
if explicit_user
|
|
@@ -179,13 +185,18 @@ module Switchman
|
|
|
179
185
|
shard = Shard.create!(:id => shard_id,
|
|
180
186
|
:name => name,
|
|
181
187
|
:database_server => self)
|
|
188
|
+
schema_already_existed = false
|
|
182
189
|
|
|
183
190
|
begin
|
|
184
191
|
self.class.creating_new_shard = true
|
|
185
192
|
shard.activate(*Shard.categories) do
|
|
186
|
-
::
|
|
193
|
+
::GuardRail.activate(:deploy) do
|
|
187
194
|
begin
|
|
188
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
|
|
189
200
|
Array(create_statement.call).each do |stmt|
|
|
190
201
|
::ActiveRecord::Base.connection.execute(stmt)
|
|
191
202
|
end
|
|
@@ -223,7 +234,9 @@ module Switchman
|
|
|
223
234
|
shard
|
|
224
235
|
rescue
|
|
225
236
|
shard.destroy
|
|
226
|
-
|
|
237
|
+
unless schema_already_existed
|
|
238
|
+
shard.drop_database rescue nil
|
|
239
|
+
end
|
|
227
240
|
reset_column_information unless create_schema == false rescue nil
|
|
228
241
|
raise
|
|
229
242
|
ensure
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -87,8 +87,8 @@ module Switchman
|
|
|
87
87
|
require "switchman/arel"
|
|
88
88
|
require "switchman/call_super"
|
|
89
89
|
require "switchman/rails"
|
|
90
|
-
require "switchman/
|
|
91
|
-
require_dependency "switchman/
|
|
90
|
+
require "switchman/guard_rail/relation"
|
|
91
|
+
require_dependency "switchman/shard"
|
|
92
92
|
require "switchman/standard_error"
|
|
93
93
|
|
|
94
94
|
::StandardError.include(StandardError)
|
|
@@ -118,15 +118,11 @@ 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)
|
|
129
124
|
::ActiveRecord::Migration::Compatibility::V5_0.prepend(ActiveRecord::Migration::Compatibility::V5_0)
|
|
125
|
+
::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext) if ::Rails.version >= '5.2'
|
|
130
126
|
::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
|
|
131
127
|
|
|
132
128
|
::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
|
|
@@ -137,7 +133,7 @@ module Switchman
|
|
|
137
133
|
::ActiveRecord::Relation.prepend(ActiveRecord::Calculations)
|
|
138
134
|
::ActiveRecord::Relation.include(ActiveRecord::FinderMethods)
|
|
139
135
|
::ActiveRecord::Relation.include(ActiveRecord::QueryMethods)
|
|
140
|
-
::ActiveRecord::Relation.prepend(
|
|
136
|
+
::ActiveRecord::Relation.prepend(GuardRail::Relation)
|
|
141
137
|
::ActiveRecord::Relation.prepend(ActiveRecord::Relation)
|
|
142
138
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
|
143
139
|
::ActiveRecord::Relation.include(CallSuper)
|
|
@@ -154,8 +150,8 @@ module Switchman
|
|
|
154
150
|
end
|
|
155
151
|
end
|
|
156
152
|
|
|
157
|
-
def self.foreign_key_check(name, type,
|
|
158
|
-
if name.to_s =~ /_id\z/ && type.to_s == 'integer' &&
|
|
153
|
+
def self.foreign_key_check(name, type, limit: nil)
|
|
154
|
+
if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
|
|
159
155
|
puts "WARNING: All foreign keys need to be 8-byte integers. #{name} looks like a foreign key. If so, please add the option: `:limit => 8`"
|
|
160
156
|
end
|
|
161
157
|
end
|
|
@@ -173,6 +169,12 @@ module Switchman
|
|
|
173
169
|
require "switchman/active_record/postgresql_adapter"
|
|
174
170
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
175
171
|
end
|
|
172
|
+
|
|
173
|
+
# If Switchman::Shard wasn't loaded as of when ActiveRecord::Base initialized
|
|
174
|
+
# establish a connection here instead
|
|
175
|
+
if !Shard.instance_variable_get(:@default)
|
|
176
|
+
::ActiveRecord::Base.establish_connection
|
|
177
|
+
end
|
|
176
178
|
end
|
|
177
179
|
end
|
|
178
180
|
|
|
@@ -183,15 +185,15 @@ module Switchman
|
|
|
183
185
|
end
|
|
184
186
|
end
|
|
185
187
|
|
|
186
|
-
initializer 'switchman.
|
|
188
|
+
initializer 'switchman.extend_guard_rail', :before => "switchman.extend_ar" do
|
|
187
189
|
::ActiveSupport.on_load(:active_record) do
|
|
188
|
-
require "switchman/
|
|
190
|
+
require "switchman/guard_rail"
|
|
189
191
|
|
|
190
|
-
::
|
|
192
|
+
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
|
191
193
|
end
|
|
192
194
|
end
|
|
193
195
|
|
|
194
|
-
initializer 'switchman.extend_controller', :after => "
|
|
196
|
+
initializer 'switchman.extend_controller', :after => "guard_rail.extend_ar" do
|
|
195
197
|
::ActiveSupport.on_load(:action_controller) do
|
|
196
198
|
require "switchman/action_controller/caching"
|
|
197
199
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
module Switchman
|
|
2
|
-
module
|
|
2
|
+
module GuardRail
|
|
3
3
|
module Relation
|
|
4
4
|
def exec_queries(*args)
|
|
5
5
|
if self.lock_value
|
|
6
6
|
db = Shard.current(shard_category).database_server
|
|
7
|
-
if ::
|
|
8
|
-
return db.
|
|
7
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
8
|
+
return db.unguard { super }
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
super
|
|
@@ -15,8 +15,8 @@ module Switchman
|
|
|
15
15
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
16
16
|
def #{method}(*args)
|
|
17
17
|
db = Shard.current(shard_category).database_server
|
|
18
|
-
if ::
|
|
19
|
-
db.
|
|
18
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
19
|
+
db.unguard { super }
|
|
20
20
|
else
|
|
21
21
|
super
|
|
22
22
|
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
module Switchman
|
|
2
|
-
module
|
|
2
|
+
module GuardRail
|
|
3
3
|
module ClassMethods
|
|
4
4
|
def self.prepended(klass)
|
|
5
5
|
klass.send(:remove_method, :ensure_handler)
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
# drops the save_handler and ensure_handler calls from the vanilla
|
|
9
|
-
#
|
|
9
|
+
# GuardRail' implementation.
|
|
10
10
|
def activate!(environment)
|
|
11
|
-
environment ||= :
|
|
11
|
+
environment ||= :primary
|
|
12
12
|
activated_environments << environment
|
|
13
13
|
old_environment = self.environment
|
|
14
|
-
Thread.current[:
|
|
14
|
+
Thread.current[:guard_rail_environment] = environment
|
|
15
15
|
old_environment
|
|
16
16
|
end
|
|
17
17
|
|