switchman 1.5.13 → 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 +37 -28
- 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 +123 -124
- 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 +72 -60
- data/lib/switchman/default_shard.rb +3 -0
- data/lib/switchman/engine.rb +45 -40
- data/lib/switchman/environment.rb +2 -0
- data/lib/switchman/errors.rb +3 -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 +80 -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 +14 -0
- data/lib/switchman/test_helper.rb +6 -3
- data/lib/switchman/version.rb +3 -1
- data/lib/switchman.rb +4 -2
- data/lib/tasks/switchman.rake +54 -68
- metadata +87 -40
- data/app/models/switchman/shard_internal.rb +0 -659
- data/lib/switchman/engine.rb~ +0 -203
|
@@ -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
|
|
|
@@ -272,7 +282,9 @@ module Switchman
|
|
|
272
282
|
|
|
273
283
|
def primary_shard
|
|
274
284
|
unless instance_variable_defined?(:@primary_shard)
|
|
275
|
-
|
|
285
|
+
# if sharding isn't fully set up yet, we may not be able to query the shards table
|
|
286
|
+
@primary_shard = Shard.default if Shard.default.database_server == self
|
|
287
|
+
@primary_shard ||= shards.where(name: nil).first
|
|
276
288
|
end
|
|
277
289
|
@primary_shard
|
|
278
290
|
end
|
|
@@ -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,81 +83,77 @@ 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"
|
|
94
|
+
require "switchman/standard_error"
|
|
95
|
+
|
|
96
|
+
::StandardError.include(StandardError)
|
|
89
97
|
|
|
90
98
|
include ActiveRecord::Base
|
|
91
99
|
include ActiveRecord::AttributeMethods
|
|
92
100
|
include ActiveRecord::Persistence
|
|
93
101
|
singleton_class.prepend ActiveRecord::ModelSchema::ClassMethods
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
::ActiveRecord::StatementCache::BindMap.prepend(ActiveRecord::StatementCache::BindMap)
|
|
100
|
-
::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)
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
end
|
|
108
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
|
|
107
|
-
end
|
|
110
|
+
::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
|
|
108
111
|
|
|
109
|
-
|
|
110
|
-
prepend(ActiveRecord::AutosaveAssociation)
|
|
111
|
-
end
|
|
112
|
+
prepend(ActiveRecord::AutosaveAssociation)
|
|
112
113
|
|
|
113
114
|
::ActiveRecord::Associations::Association.prepend(ActiveRecord::Association)
|
|
114
115
|
::ActiveRecord::Associations::BelongsToAssociation.prepend(ActiveRecord::BelongsToAssociation)
|
|
115
116
|
::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::CollectionProxy)
|
|
116
|
-
if ::Rails.version < '5'
|
|
117
|
-
::ActiveRecord::Associations::Builder::CollectionAssociation.include(ActiveRecord::Builder::CollectionAssociation)
|
|
118
|
-
end
|
|
119
117
|
|
|
120
118
|
::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Preloader::Association)
|
|
121
119
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
|
|
122
120
|
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
123
121
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
124
122
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
125
|
-
# when we call super in Switchman::ActiveRecord::QueryCache#select_all,
|
|
126
|
-
# we want it to find the definition from
|
|
127
|
-
# ActiveRecord::ConnectionAdapters::DatabaseStatements, not
|
|
128
|
-
# ActiveRecord::ConnectionAdapters::QueryCache
|
|
129
|
-
::ActiveRecord::ConnectionAdapters::QueryCache.send(:remove_method, :select_all)
|
|
130
123
|
|
|
131
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)
|
|
132
133
|
::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationReflection)
|
|
133
134
|
::ActiveRecord::Relation.prepend(ActiveRecord::Batches)
|
|
134
135
|
::ActiveRecord::Relation.prepend(ActiveRecord::Calculations)
|
|
135
136
|
::ActiveRecord::Relation.include(ActiveRecord::FinderMethods)
|
|
136
137
|
::ActiveRecord::Relation.include(ActiveRecord::QueryMethods)
|
|
138
|
+
::ActiveRecord::Relation.prepend(GuardRail::Relation)
|
|
137
139
|
::ActiveRecord::Relation.prepend(ActiveRecord::Relation)
|
|
138
140
|
::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
|
|
139
|
-
::ActiveRecord::Relation.
|
|
141
|
+
::ActiveRecord::Relation.include(CallSuper)
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
|
146
|
-
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)
|
|
147
147
|
|
|
148
148
|
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
149
149
|
|
|
150
150
|
::Arel::Table.prepend(Arel::Table)
|
|
151
151
|
::Arel::Visitors::ToSql.prepend(Arel::Visitors::ToSql)
|
|
152
|
-
::Arel::Visitors::PostgreSQL.include(Arel::Visitors::PostgreSQL) if ::Rails.version < '4.2'
|
|
153
152
|
end
|
|
154
153
|
end
|
|
155
154
|
|
|
156
|
-
def self.foreign_key_check(name, type,
|
|
157
|
-
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
|
|
158
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`"
|
|
159
158
|
end
|
|
160
159
|
end
|
|
@@ -172,6 +171,12 @@ module Switchman
|
|
|
172
171
|
require "switchman/active_record/postgresql_adapter"
|
|
173
172
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
174
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
|
|
175
180
|
end
|
|
176
181
|
end
|
|
177
182
|
|
|
@@ -182,15 +187,15 @@ module Switchman
|
|
|
182
187
|
end
|
|
183
188
|
end
|
|
184
189
|
|
|
185
|
-
initializer 'switchman.
|
|
190
|
+
initializer 'switchman.extend_guard_rail', :before => "switchman.extend_ar" do
|
|
186
191
|
::ActiveSupport.on_load(:active_record) do
|
|
187
|
-
require "switchman/
|
|
192
|
+
require "switchman/guard_rail"
|
|
188
193
|
|
|
189
|
-
::
|
|
194
|
+
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
|
190
195
|
end
|
|
191
196
|
end
|
|
192
197
|
|
|
193
|
-
initializer 'switchman.extend_controller', :after => "
|
|
198
|
+
initializer 'switchman.extend_controller', :after => "guard_rail.extend_ar" do
|
|
194
199
|
::ActiveSupport.on_load(:action_controller) do
|
|
195
200
|
require "switchman/action_controller/caching"
|
|
196
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
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open4'
|
|
4
|
+
|
|
5
|
+
# This fixes a bug with exception handling,
|
|
6
|
+
# see https://github.com/ahoward/open4/pull/30
|
|
7
|
+
module Open4
|
|
8
|
+
def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)
|
|
9
|
+
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
|
|
10
|
+
|
|
11
|
+
verbose = $VERBOSE
|
|
12
|
+
begin
|
|
13
|
+
$VERBOSE = nil
|
|
14
|
+
|
|
15
|
+
cid = fork {
|
|
16
|
+
if closefds
|
|
17
|
+
exlist = [0, 1, 2] | [pw,pr,pe,ps].map{|p| [p.first.fileno, p.last.fileno] }.flatten
|
|
18
|
+
ObjectSpace.each_object(IO){|io|
|
|
19
|
+
io.close if (not io.closed?) and (not exlist.include? io.fileno) rescue nil
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
pw.last.close
|
|
24
|
+
STDIN.reopen pw.first
|
|
25
|
+
pw.first.close
|
|
26
|
+
|
|
27
|
+
pr.first.close
|
|
28
|
+
STDOUT.reopen pr.last
|
|
29
|
+
pr.last.close
|
|
30
|
+
|
|
31
|
+
pe.first.close
|
|
32
|
+
STDERR.reopen pe.last
|
|
33
|
+
pe.last.close
|
|
34
|
+
|
|
35
|
+
STDOUT.sync = STDERR.sync = true
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
cmd.call(ps)
|
|
39
|
+
rescue Exception => e
|
|
40
|
+
begin
|
|
41
|
+
Marshal.dump(e, ps.last)
|
|
42
|
+
ps.last.flush
|
|
43
|
+
rescue Errno::EPIPE
|
|
44
|
+
raise e
|
|
45
|
+
end
|
|
46
|
+
ensure
|
|
47
|
+
ps.last.close unless ps.last.closed?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
exit!
|
|
51
|
+
}
|
|
52
|
+
ensure
|
|
53
|
+
$VERBOSE = verbose
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
[ pw.first, pr.last, pe.last, ps.last ].each { |fd| fd.close }
|
|
57
|
+
|
|
58
|
+
Open4.propagate_exception cid, ps.first if exception_propagation_at == :init
|
|
59
|
+
|
|
60
|
+
pw.last.sync = true
|
|
61
|
+
|
|
62
|
+
pi = [ pw.last, pr.first, pe.first ]
|
|
63
|
+
|
|
64
|
+
begin
|
|
65
|
+
return [cid, *pi] unless b
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
b.call(cid, *pi)
|
|
69
|
+
ensure
|
|
70
|
+
pi.each { |fd| fd.close unless fd.closed? }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Open4.propagate_exception cid, ps.first if exception_propagation_at == :block
|
|
74
|
+
|
|
75
|
+
Process.waitpid2(cid).last
|
|
76
|
+
ensure
|
|
77
|
+
ps.first.close unless ps.first.closed?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -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
|