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,8 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
1
5
|
module Switchman
|
|
2
6
|
class DatabaseServer
|
|
3
7
|
attr_accessor :id
|
|
4
8
|
|
|
5
9
|
class << self
|
|
10
|
+
attr_accessor :creating_new_shard
|
|
11
|
+
|
|
6
12
|
def all
|
|
7
13
|
database_servers.values
|
|
8
14
|
end
|
|
@@ -36,8 +42,14 @@ module Switchman
|
|
|
36
42
|
def database_servers
|
|
37
43
|
unless @database_servers
|
|
38
44
|
@database_servers = {}.with_indifferent_access
|
|
39
|
-
::
|
|
40
|
-
|
|
45
|
+
if ::Rails.version >= '6.0'
|
|
46
|
+
::ActiveRecord::Base.configurations.configurations.each do |config|
|
|
47
|
+
@database_servers[config.env_name] = DatabaseServer.new(config.env_name, config.config)
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
::ActiveRecord::Base.configurations.each do |(id, config)|
|
|
51
|
+
@database_servers[id] = DatabaseServer.new(id, config)
|
|
52
|
+
end
|
|
41
53
|
end
|
|
42
54
|
end
|
|
43
55
|
@database_servers
|
|
@@ -59,12 +71,12 @@ module Switchman
|
|
|
59
71
|
@fake
|
|
60
72
|
end
|
|
61
73
|
|
|
62
|
-
def config(environment = :
|
|
74
|
+
def config(environment = :primary)
|
|
63
75
|
@configs[environment] ||= begin
|
|
64
76
|
if @config[environment].is_a?(Array)
|
|
65
77
|
@config[environment].map do |config|
|
|
66
78
|
config = @config.merge((config || {}).symbolize_keys)
|
|
67
|
-
# make sure
|
|
79
|
+
# make sure GuardRail doesn't get any brilliant ideas about choosing the first possible server
|
|
68
80
|
config.delete(environment)
|
|
69
81
|
config
|
|
70
82
|
end
|
|
@@ -76,33 +88,33 @@ module Switchman
|
|
|
76
88
|
end
|
|
77
89
|
end
|
|
78
90
|
|
|
79
|
-
def
|
|
80
|
-
@
|
|
91
|
+
def guard_rail_environment
|
|
92
|
+
@guard_rail_environment || ::GuardRail.environment
|
|
81
93
|
end
|
|
82
94
|
|
|
83
95
|
# locks this db to a specific environment, except for
|
|
84
96
|
# when doing writes (then it falls back to the current
|
|
85
|
-
# value of
|
|
86
|
-
def
|
|
87
|
-
@
|
|
97
|
+
# value of GuardRail.environment)
|
|
98
|
+
def guard!(environment = :secondary)
|
|
99
|
+
@guard_rail_environment = environment
|
|
88
100
|
end
|
|
89
101
|
|
|
90
|
-
def
|
|
91
|
-
@
|
|
102
|
+
def unguard!
|
|
103
|
+
@guard_rail_environment = nil
|
|
92
104
|
end
|
|
93
105
|
|
|
94
|
-
def
|
|
95
|
-
old_env = @
|
|
96
|
-
|
|
106
|
+
def unguard
|
|
107
|
+
old_env = @guard_rail_environment
|
|
108
|
+
unguard!
|
|
97
109
|
yield
|
|
98
110
|
ensure
|
|
99
|
-
|
|
111
|
+
guard!(old_env)
|
|
100
112
|
end
|
|
101
113
|
|
|
102
114
|
def shareable?
|
|
103
115
|
@shareable_environment_key ||= []
|
|
104
|
-
environment =
|
|
105
|
-
explicit_user = ::
|
|
116
|
+
environment = guard_rail_environment
|
|
117
|
+
explicit_user = ::GuardRail.global_config[:username]
|
|
106
118
|
return @shareable if @shareable_environment_key == [environment, explicit_user]
|
|
107
119
|
@shareable_environment_key = [environment, explicit_user]
|
|
108
120
|
if explicit_user
|
|
@@ -112,7 +124,7 @@ module Switchman
|
|
|
112
124
|
config = config.first if config.is_a?(Array)
|
|
113
125
|
username = config[:username]
|
|
114
126
|
end
|
|
115
|
-
@shareable =
|
|
127
|
+
@shareable = username !~ /%?\{[a-zA-Z0-9_]+\}/
|
|
116
128
|
end
|
|
117
129
|
|
|
118
130
|
def shards
|
|
@@ -134,31 +146,16 @@ module Switchman
|
|
|
134
146
|
create_schema = options[:schema]
|
|
135
147
|
# look for another shard associated with this db
|
|
136
148
|
other_shard = self.shards.where("name<>':memory:' OR name IS NULL").order(:id).first
|
|
137
|
-
temp_name = other_shard.try(:name) unless id == ::Rails.env
|
|
138
|
-
temp_name = Shard.default.name if id == ::Rails.env
|
|
139
149
|
|
|
140
150
|
case config[:adapter]
|
|
141
151
|
when 'postgresql'
|
|
142
|
-
temp_name ||= 'public'
|
|
143
152
|
create_statement = lambda { "CREATE SCHEMA #{name}" }
|
|
144
153
|
password = " PASSWORD #{::ActiveRecord::Base.connection.quote(config[:password])}" if config[:password]
|
|
145
|
-
when 'sqlite3'
|
|
146
|
-
if name
|
|
147
|
-
# Try to create a db on-disk even if the only shards for sqlite are in-memory
|
|
148
|
-
temp_name = nil if temp_name == ':memory:'
|
|
149
|
-
# Put it in the db directory if there are no other sqlite shards
|
|
150
|
-
temp_name ||= 'db/dummy'
|
|
151
|
-
temp_name = File.join(File.dirname(temp_name), "#{name}.sqlite3")
|
|
152
|
-
# If they really asked for :memory:, give them :memory:
|
|
153
|
-
temp_name = name if name == ':memory:'
|
|
154
|
-
name = temp_name
|
|
155
|
-
end
|
|
156
154
|
else
|
|
157
|
-
temp_name ||= self.config[:database] % self.config
|
|
158
155
|
create_statement = lambda { "CREATE DATABASE #{name}" }
|
|
159
156
|
end
|
|
160
157
|
sharding_config = Switchman.config
|
|
161
|
-
config_create_statement = sharding_config[config[:adapter]]
|
|
158
|
+
config_create_statement = sharding_config[config[:adapter]]&.[](:create_statement)
|
|
162
159
|
config_create_statement ||= sharding_config[:create_statement]
|
|
163
160
|
if config_create_statement
|
|
164
161
|
create_commands = Array(config_create_statement).dup
|
|
@@ -168,42 +165,49 @@ module Switchman
|
|
|
168
165
|
end
|
|
169
166
|
|
|
170
167
|
create_shard = lambda do
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
shard_id = options.fetch(:id) do
|
|
169
|
+
case config[:adapter]
|
|
170
|
+
when 'postgresql'
|
|
171
|
+
id_seq = Shard.connection.quote(Shard.connection.quote_table_name('switchman_shards_id_seq'))
|
|
172
|
+
next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
|
|
173
|
+
next_id.to_i
|
|
174
|
+
else
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
if name.nil?
|
|
180
|
+
base_name = self.config[:database].to_s % self.config
|
|
181
|
+
base_name = nil if base_name == ':memory:'
|
|
182
|
+
base_name << '_' if base_name
|
|
183
|
+
base_id = shard_id || SecureRandom.uuid
|
|
184
|
+
name = "#{base_name}shard_#{base_id}"
|
|
174
185
|
end
|
|
186
|
+
|
|
187
|
+
shard = Shard.create!(:id => shard_id,
|
|
188
|
+
:name => name,
|
|
189
|
+
:database_server => self)
|
|
190
|
+
schema_already_existed = false
|
|
191
|
+
|
|
175
192
|
begin
|
|
176
|
-
|
|
177
|
-
base_name = self.config[:database] % self.config
|
|
178
|
-
base_name = $1 if base_name =~ /(?:.*\/)(.+)_shard_\d+(?:\.sqlite3)?$/
|
|
179
|
-
base_name = nil if base_name == ':memory:'
|
|
180
|
-
base_name << '_' if base_name
|
|
181
|
-
name = "#{base_name}shard_#{shard.id}"
|
|
182
|
-
if config[:adapter] == 'sqlite3'
|
|
183
|
-
# Try to create a db on-disk even if the only shards for sqlite are in-memory
|
|
184
|
-
temp_name = nil if temp_name == ':memory:'
|
|
185
|
-
# Put it in the db directory if there are no other sqlite shards
|
|
186
|
-
temp_name ||= 'db/dummy'
|
|
187
|
-
name = File.join(File.dirname(temp_name), "#{name}.sqlite3")
|
|
188
|
-
shard.name = name
|
|
189
|
-
end
|
|
190
|
-
end
|
|
193
|
+
self.class.creating_new_shard = true
|
|
191
194
|
shard.activate(*Shard.categories) do
|
|
192
|
-
::
|
|
195
|
+
::GuardRail.activate(:deploy) do
|
|
193
196
|
begin
|
|
194
197
|
if create_statement
|
|
198
|
+
if (::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"))
|
|
199
|
+
schema_already_existed = true
|
|
200
|
+
raise "This schema already exists; cannot overwrite"
|
|
201
|
+
end
|
|
195
202
|
Array(create_statement.call).each do |stmt|
|
|
196
203
|
::ActiveRecord::Base.connection.execute(stmt)
|
|
197
204
|
end
|
|
198
205
|
# have to disconnect and reconnect to the correct db
|
|
199
|
-
shard.name = name
|
|
200
206
|
if self.shareable? && other_shard
|
|
201
207
|
other_shard.activate { ::ActiveRecord::Base.connection }
|
|
202
208
|
else
|
|
203
209
|
::ActiveRecord::Base.connection_pool.current_pool.disconnect!
|
|
204
210
|
end
|
|
205
|
-
else
|
|
206
|
-
shard.name = name
|
|
207
211
|
end
|
|
208
212
|
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {} if config[:adapter] == 'postgresql'
|
|
209
213
|
old_verbose = ::ActiveRecord::Migration.verbose
|
|
@@ -211,7 +215,10 @@ module Switchman
|
|
|
211
215
|
|
|
212
216
|
unless create_schema == false
|
|
213
217
|
reset_column_information
|
|
214
|
-
|
|
218
|
+
|
|
219
|
+
migrate = ::Rails.version >= '5.2' ?
|
|
220
|
+
-> { ::ActiveRecord::Base.connection.migration_context.migrate } :
|
|
221
|
+
-> { ::ActiveRecord::Migrator.migrate(::ActiveRecord::Migrator.migrations_paths) }
|
|
215
222
|
if ::ActiveRecord::Base.connection.supports_ddl_transactions?
|
|
216
223
|
::ActiveRecord::Base.connection.transaction(requires_new: true, &migrate)
|
|
217
224
|
else
|
|
@@ -226,13 +233,16 @@ module Switchman
|
|
|
226
233
|
end
|
|
227
234
|
end
|
|
228
235
|
end
|
|
229
|
-
shard.save!
|
|
230
236
|
shard
|
|
231
237
|
rescue
|
|
232
238
|
shard.destroy
|
|
233
|
-
|
|
239
|
+
unless schema_already_existed
|
|
240
|
+
shard.drop_database rescue nil
|
|
241
|
+
end
|
|
234
242
|
reset_column_information unless create_schema == false rescue nil
|
|
235
243
|
raise
|
|
244
|
+
ensure
|
|
245
|
+
self.class.creating_new_shard = false
|
|
236
246
|
end
|
|
237
247
|
end
|
|
238
248
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'switchman/database_server'
|
|
2
4
|
|
|
3
5
|
module Switchman
|
|
4
6
|
class DefaultShard
|
|
5
7
|
def id; 'default'; end
|
|
8
|
+
alias cache_key id
|
|
6
9
|
def activate(*categories); yield; end
|
|
7
10
|
def activate!(*categories); end
|
|
8
11
|
def default?; true; end
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
class Engine < ::Rails::Engine
|
|
3
5
|
isolate_namespace Switchman
|
|
4
6
|
|
|
5
|
-
config.autoload_once_paths << File.expand_path(
|
|
7
|
+
config.autoload_once_paths << File.expand_path("app/models", config.paths.path)
|
|
6
8
|
|
|
7
9
|
def self.lookup_stores(cache_store_config)
|
|
8
10
|
result = {}
|
|
@@ -72,6 +74,7 @@ module Switchman
|
|
|
72
74
|
require "switchman/active_record/connection_pool"
|
|
73
75
|
require "switchman/active_record/finder_methods"
|
|
74
76
|
require "switchman/active_record/log_subscriber"
|
|
77
|
+
require "switchman/active_record/migration"
|
|
75
78
|
require "switchman/active_record/model_schema"
|
|
76
79
|
require "switchman/active_record/persistence"
|
|
77
80
|
require "switchman/active_record/predicate_builder"
|
|
@@ -80,12 +83,14 @@ module Switchman
|
|
|
80
83
|
require "switchman/active_record/reflection"
|
|
81
84
|
require "switchman/active_record/relation"
|
|
82
85
|
require "switchman/active_record/spawn_methods"
|
|
83
|
-
require "switchman/active_record/
|
|
86
|
+
require "switchman/active_record/statement_cache"
|
|
84
87
|
require "switchman/active_record/type_caster"
|
|
88
|
+
require "switchman/active_record/where_clause_factory"
|
|
85
89
|
require "switchman/arel"
|
|
90
|
+
require "switchman/call_super"
|
|
86
91
|
require "switchman/rails"
|
|
87
|
-
require "switchman/
|
|
88
|
-
|
|
92
|
+
require "switchman/guard_rail/relation"
|
|
93
|
+
require_dependency "switchman/shard"
|
|
89
94
|
require "switchman/standard_error"
|
|
90
95
|
|
|
91
96
|
::StandardError.include(StandardError)
|
|
@@ -95,69 +100,60 @@ module Switchman
|
|
|
95
100
|
include ActiveRecord::Persistence
|
|
96
101
|
singleton_class.prepend ActiveRecord::ModelSchema::ClassMethods
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
::ActiveRecord::StatementCache::BindMap.prepend(ActiveRecord::StatementCache::BindMap)
|
|
103
|
-
::ActiveRecord::StatementCache::Substitute.send(:attr_accessor, :primary, :sharded)
|
|
103
|
+
::ActiveRecord::StatementCache.prepend(ActiveRecord::StatementCache)
|
|
104
|
+
::ActiveRecord::StatementCache.singleton_class.prepend(ActiveRecord::StatementCache::ClassMethods)
|
|
105
|
+
::ActiveRecord::StatementCache::BindMap.prepend(ActiveRecord::StatementCache::BindMap)
|
|
106
|
+
::ActiveRecord::StatementCache::Substitute.send(:attr_accessor, :primary, :sharded)
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
end
|
|
108
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
|
|
110
|
-
end
|
|
110
|
+
::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
prepend(ActiveRecord::AutosaveAssociation)
|
|
114
|
-
end
|
|
112
|
+
prepend(ActiveRecord::AutosaveAssociation)
|
|
115
113
|
|
|
116
114
|
::ActiveRecord::Associations::Association.prepend(ActiveRecord::Association)
|
|
117
115
|
::ActiveRecord::Associations::BelongsToAssociation.prepend(ActiveRecord::BelongsToAssociation)
|
|
118
116
|
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::CollectionProxy)
|
|
119
|
-
if ::Rails.version < '5'
|
|
120
|
-
::ActiveRecord::Associations::Builder::CollectionAssociation.include(ActiveRecord::Builder::CollectionAssociation)
|
|
121
|
-
end
|
|
122
117
|
|
|
123
118
|
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Preloader::Association)
|
|
124
119
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
|
|
125
120
|
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
126
121
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
127
122
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
128
|
-
# when we call super in Switchman::ActiveRecord::QueryCache#select_all,
|
|
129
|
-
# we want it to find the definition from
|
|
130
|
-
# ActiveRecord::ConnectionAdapters::DatabaseStatements, not
|
|
131
|
-
# ActiveRecord::ConnectionAdapters::QueryCache
|
|
132
|
-
::ActiveRecord::ConnectionAdapters::QueryCache.send(:remove_method, :select_all)
|
|
133
123
|
|
|
134
124
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
125
|
+
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
126
|
+
::ActiveRecord::Migration::Compatibility::V5_0.prepend(ActiveRecord::Migration::Compatibility::V5_0)
|
|
127
|
+
::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext) if ::Rails.version >= '5.2'
|
|
128
|
+
::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
|
|
129
|
+
|
|
130
|
+
::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
|
|
131
|
+
::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
132
|
+
::ActiveRecord::Reflection::ThroughReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
|
|
135
133
|
::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationReflection)
|
|
136
134
|
::ActiveRecord::Relation.prepend(ActiveRecord::Batches)
|
|
137
135
|
::ActiveRecord::Relation.prepend(ActiveRecord::Calculations)
|
|
138
136
|
::ActiveRecord::Relation.include(ActiveRecord::FinderMethods)
|
|
139
137
|
::ActiveRecord::Relation.include(ActiveRecord::QueryMethods)
|
|
138
|
+
::ActiveRecord::Relation.prepend(GuardRail::Relation)
|
|
140
139
|
::ActiveRecord::Relation.prepend(ActiveRecord::Relation)
|
|
141
140
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
|
142
|
-
::ActiveRecord::Relation.
|
|
141
|
+
::ActiveRecord::Relation.include(CallSuper)
|
|
143
142
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
|
149
|
-
end
|
|
143
|
+
::ActiveRecord::Relation::WhereClauseFactory.prepend(ActiveRecord::WhereClauseFactory)
|
|
144
|
+
::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
|
|
145
|
+
::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
|
|
146
|
+
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
|
150
147
|
|
|
151
148
|
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
152
149
|
|
|
153
150
|
::Arel::Table.prepend(Arel::Table)
|
|
154
151
|
::Arel::Visitors::ToSql.prepend(Arel::Visitors::ToSql)
|
|
155
|
-
::Arel::Visitors::PostgreSQL.include(Arel::Visitors::PostgreSQL) if ::Rails.version < '4.2'
|
|
156
152
|
end
|
|
157
153
|
end
|
|
158
154
|
|
|
159
|
-
def self.foreign_key_check(name, type,
|
|
160
|
-
if name.to_s =~ /_id\z/ && type.to_s == 'integer' &&
|
|
155
|
+
def self.foreign_key_check(name, type, limit: nil)
|
|
156
|
+
if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
|
|
161
157
|
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`"
|
|
162
158
|
end
|
|
163
159
|
end
|
|
@@ -175,6 +171,12 @@ module Switchman
|
|
|
175
171
|
require "switchman/active_record/postgresql_adapter"
|
|
176
172
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
177
173
|
end
|
|
174
|
+
|
|
175
|
+
# If Switchman::Shard wasn't loaded as of when ActiveRecord::Base initialized
|
|
176
|
+
# establish a connection here instead
|
|
177
|
+
if !Shard.instance_variable_get(:@default)
|
|
178
|
+
::ActiveRecord::Base.establish_connection
|
|
179
|
+
end
|
|
178
180
|
end
|
|
179
181
|
end
|
|
180
182
|
|
|
@@ -185,15 +187,15 @@ module Switchman
|
|
|
185
187
|
end
|
|
186
188
|
end
|
|
187
189
|
|
|
188
|
-
initializer 'switchman.
|
|
190
|
+
initializer 'switchman.extend_guard_rail', :before => "switchman.extend_ar" do
|
|
189
191
|
::ActiveSupport.on_load(:active_record) do
|
|
190
|
-
require "switchman/
|
|
192
|
+
require "switchman/guard_rail"
|
|
191
193
|
|
|
192
|
-
::
|
|
194
|
+
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
|
193
195
|
end
|
|
194
196
|
end
|
|
195
197
|
|
|
196
|
-
initializer 'switchman.extend_controller', :after => "
|
|
198
|
+
initializer 'switchman.extend_controller', :after => "guard_rail.extend_ar" do
|
|
197
199
|
::ActiveSupport.on_load(:action_controller) do
|
|
198
200
|
require "switchman/action_controller/caching"
|
|
199
201
|
|
data/lib/switchman/errors.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
|
-
module
|
|
4
|
+
module GuardRail
|
|
3
5
|
module Relation
|
|
4
6
|
def exec_queries(*args)
|
|
5
7
|
if self.lock_value
|
|
6
8
|
db = Shard.current(shard_category).database_server
|
|
7
|
-
if ::
|
|
8
|
-
return db.
|
|
9
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
10
|
+
return db.unguard { super }
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
super
|
|
@@ -15,8 +17,8 @@ module Switchman
|
|
|
15
17
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
16
18
|
def #{method}(*args)
|
|
17
19
|
db = Shard.current(shard_category).database_server
|
|
18
|
-
if ::
|
|
19
|
-
db.
|
|
20
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
|
21
|
+
db.unguard { super }
|
|
20
22
|
else
|
|
21
23
|
super
|
|
22
24
|
end
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
|
-
module
|
|
4
|
+
module GuardRail
|
|
3
5
|
module ClassMethods
|
|
4
6
|
def self.prepended(klass)
|
|
5
7
|
klass.send(:remove_method, :ensure_handler)
|
|
6
8
|
end
|
|
7
9
|
|
|
8
10
|
# drops the save_handler and ensure_handler calls from the vanilla
|
|
9
|
-
#
|
|
11
|
+
# GuardRail' implementation.
|
|
10
12
|
def activate!(environment)
|
|
11
|
-
environment ||= :
|
|
13
|
+
environment ||= :primary
|
|
12
14
|
activated_environments << environment
|
|
13
15
|
old_environment = self.environment
|
|
14
|
-
|
|
16
|
+
Thread.current[:guard_rail_environment] = environment
|
|
15
17
|
old_environment
|
|
16
18
|
end
|
|
17
19
|
|
data/lib/switchman/open4.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "switchman/test_helper"
|
|
2
4
|
|
|
3
5
|
module Switchman
|
|
@@ -36,6 +38,7 @@ module Switchman
|
|
|
36
38
|
|
|
37
39
|
puts "Setting up sharding for all specs..."
|
|
38
40
|
Shard.delete_all
|
|
41
|
+
Switchman.cache.delete("default_shard")
|
|
39
42
|
|
|
40
43
|
@@shard1, @@shard2 = TestHelper.recreate_persistent_test_shards
|
|
41
44
|
@@default_shard = Shard.default
|
|
@@ -64,14 +67,15 @@ module Switchman
|
|
|
64
67
|
(@@shard3.drop_database if @@shard3) rescue nil
|
|
65
68
|
@@shard1 = @@shard2 = @@shard3 = nil
|
|
66
69
|
Shard.delete_all
|
|
67
|
-
Shard.default(true)
|
|
70
|
+
Shard.default(reload: true)
|
|
68
71
|
next
|
|
69
72
|
end
|
|
70
73
|
end
|
|
71
74
|
# we'll re-persist in the group's `before :all`; we don't want them to exist
|
|
72
75
|
# in the db before then
|
|
73
76
|
Shard.delete_all
|
|
74
|
-
|
|
77
|
+
Switchman.cache.delete("default_shard")
|
|
78
|
+
Shard.default(reload: true)
|
|
75
79
|
puts "Done!"
|
|
76
80
|
|
|
77
81
|
at_exit do
|
|
@@ -99,7 +103,8 @@ module Switchman
|
|
|
99
103
|
dup = @@default_shard.dup
|
|
100
104
|
dup.id = @@default_shard.id
|
|
101
105
|
dup.save!
|
|
102
|
-
|
|
106
|
+
Switchman.cache.delete("default_shard")
|
|
107
|
+
Shard.default(reload: true)
|
|
103
108
|
dup = @@shard1.dup
|
|
104
109
|
dup.id = @@shard1.id
|
|
105
110
|
dup.save!
|
|
@@ -118,8 +123,8 @@ module Switchman
|
|
|
118
123
|
klass.before do
|
|
119
124
|
raise "Sharding did not set up correctly" if @@sharding_failed
|
|
120
125
|
Shard.clear_cache
|
|
121
|
-
if
|
|
122
|
-
Shard.default(true)
|
|
126
|
+
if use_transactional_tests
|
|
127
|
+
Shard.default(reload: true)
|
|
123
128
|
@shard1 = Shard.find(@shard1.id)
|
|
124
129
|
@shard2 = Shard.find(@shard2.id)
|
|
125
130
|
shards = [@shard2]
|
|
@@ -134,7 +139,7 @@ module Switchman
|
|
|
134
139
|
|
|
135
140
|
klass.after do
|
|
136
141
|
next if @@sharding_failed
|
|
137
|
-
if
|
|
142
|
+
if use_transactional_tests
|
|
138
143
|
shards = [@shard2]
|
|
139
144
|
shards << @shard1 unless @shard1.database_server == Shard.default.database_server
|
|
140
145
|
shards.each do |shard|
|
|
@@ -146,8 +151,9 @@ module Switchman
|
|
|
146
151
|
end
|
|
147
152
|
|
|
148
153
|
klass.after(:all) do
|
|
149
|
-
Shard.
|
|
150
|
-
|
|
154
|
+
Shard.connection.update("TRUNCATE #{Shard.quoted_table_name} CASCADE")
|
|
155
|
+
Switchman.cache.delete("default_shard")
|
|
156
|
+
Shard.default(reload: true)
|
|
151
157
|
end
|
|
152
158
|
end
|
|
153
159
|
end
|
data/lib/switchman/rails.rb
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
class SchemaCache < ::ActiveRecord::ConnectionAdapters::SchemaCache
|
|
3
5
|
delegate :connection, to: :pool
|
|
4
6
|
attr_reader :pool
|
|
5
7
|
|
|
8
|
+
SHARED_IVS = %i{@columns @columns_hash @primary_keys @data_sources @indexes}.freeze
|
|
9
|
+
|
|
6
10
|
def initialize(pool)
|
|
7
11
|
@pool = pool
|
|
8
12
|
super(nil)
|
|
9
13
|
end
|
|
14
|
+
|
|
15
|
+
def copy_values(other_cache)
|
|
16
|
+
SHARED_IVS.each do |iv|
|
|
17
|
+
instance_variable_get(iv).replace(other_cache.instance_variable_get(iv))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def copy_references(other_cache)
|
|
22
|
+
# use the same cached values but still fall back to the correct pool
|
|
23
|
+
SHARED_IVS.each do |iv|
|
|
24
|
+
instance_variable_set(iv, other_cache.instance_variable_get(iv))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
10
27
|
end
|
|
11
28
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
class ShardedInstrumenter < ::SimpleDelegator
|
|
3
5
|
def initialize(instrumenter, shard_host)
|
|
@@ -6,7 +8,7 @@ module Switchman
|
|
|
6
8
|
end
|
|
7
9
|
|
|
8
10
|
def instrument(name, payload={})
|
|
9
|
-
shard = @shard_host
|
|
11
|
+
shard = @shard_host&.shard
|
|
10
12
|
# attribute_methods_generated? will be false during a reload -
|
|
11
13
|
# when we might be doing a query while defining attribute methods,
|
|
12
14
|
# so just avoid logging then
|
|
@@ -14,7 +16,7 @@ module Switchman
|
|
|
14
16
|
payload[:shard] = {
|
|
15
17
|
database_server_id: shard.database_server.id,
|
|
16
18
|
id: shard.id,
|
|
17
|
-
env: shard.database_server.
|
|
19
|
+
env: shard.database_server.guard_rail_environment
|
|
18
20
|
}
|
|
19
21
|
end
|
|
20
22
|
super name, payload
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module StandardError
|
|
3
5
|
def initialize(*args)
|
|
@@ -5,8 +7,8 @@ module Switchman
|
|
|
5
7
|
super
|
|
6
8
|
end
|
|
7
9
|
|
|
8
|
-
def current_shard(category = :
|
|
9
|
-
@active_shards[category
|
|
10
|
+
def current_shard(category = :primary)
|
|
11
|
+
@active_shards&.[](category) || Shard.default
|
|
10
12
|
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Switchman
|
|
2
4
|
module TestHelper
|
|
3
5
|
class << self
|
|
4
6
|
def recreate_persistent_test_shards(dont_create: false)
|
|
5
7
|
# recreate the default shard (it got buhleted)
|
|
6
|
-
|
|
8
|
+
::GuardRail.activate(:deploy) { Switchman.cache.clear }
|
|
9
|
+
if Shard.default(reload: true).is_a?(DefaultShard)
|
|
7
10
|
begin
|
|
8
11
|
Shard.create!(default: true)
|
|
9
12
|
rescue
|
|
@@ -11,7 +14,7 @@ module Switchman
|
|
|
11
14
|
# database doesn't exist yet, presumably cause we're creating it right now
|
|
12
15
|
return [nil, nil]
|
|
13
16
|
end
|
|
14
|
-
Shard.default(true)
|
|
17
|
+
Shard.default(reload: true)
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
# can't auto-create a new shard on the default shard's db server if the
|
|
@@ -21,7 +24,7 @@ module Switchman
|
|
|
21
24
|
else
|
|
22
25
|
server1 = Shard.default.database_server
|
|
23
26
|
end
|
|
24
|
-
server2 = DatabaseServer.create(Shard.default.database_server.config)
|
|
27
|
+
server2 = DatabaseServer.create(Shard.default.database_server.config.merge(server2: true))
|
|
25
28
|
|
|
26
29
|
if server1 == Shard.default.database_server && server1.config[:shard1] && server1.config[:shard2]
|
|
27
30
|
# look for the shards in the db already
|