switchman 1.5.21 → 2.1.5
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 +757 -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 +54 -22
- data/lib/switchman/active_record/base.rb +76 -31
- data/lib/switchman/active_record/batches.rb +3 -1
- data/lib/switchman/active_record/calculations.rb +17 -22
- data/lib/switchman/active_record/connection_handler.rb +88 -78
- 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 +170 -126
- data/lib/switchman/active_record/predicate_builder.rb +3 -1
- data/lib/switchman/active_record/query_cache.rb +22 -87
- data/lib/switchman/active_record/query_methods.rb +139 -125
- data/lib/switchman/active_record/reflection.rb +42 -14
- data/lib/switchman/active_record/relation.rb +108 -33
- 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 +44 -41
- 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 +7 -10
- data/lib/switchman/version.rb +3 -1
- data/lib/switchman.rb +5 -1
- data/lib/tasks/switchman.rake +53 -72
- metadata +84 -38
- 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,84 +83,78 @@ 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)
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
prepend ActiveRecord::Base
|
|
94
99
|
include ActiveRecord::AttributeMethods
|
|
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
|
-
|
|
149
|
-
end
|
|
143
|
+
::ActiveRecord::Relation::WhereClauseFactory.prepend(ActiveRecord::WhereClauseFactory)
|
|
144
|
+
::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
|
|
145
|
+
::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
|
|
146
|
+
::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
|
|
147
|
+
::ActiveRecord::TypeCaster::Connection.include(ActiveRecord::TypeCaster::Connection)
|
|
150
148
|
|
|
151
149
|
::Rails.singleton_class.prepend(Rails::ClassMethods)
|
|
152
150
|
|
|
153
151
|
::Arel::Table.prepend(Arel::Table)
|
|
154
152
|
::Arel::Visitors::ToSql.prepend(Arel::Visitors::ToSql)
|
|
155
|
-
::Arel::Visitors::PostgreSQL.include(Arel::Visitors::PostgreSQL) if ::Rails.version < '4.2'
|
|
156
153
|
end
|
|
157
154
|
end
|
|
158
155
|
|
|
159
|
-
def self.foreign_key_check(name, type,
|
|
160
|
-
if name.to_s =~ /_id\z/ && type.to_s == 'integer' &&
|
|
156
|
+
def self.foreign_key_check(name, type, limit: nil)
|
|
157
|
+
if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
|
|
161
158
|
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
159
|
end
|
|
163
160
|
end
|
|
@@ -175,6 +172,12 @@ module Switchman
|
|
|
175
172
|
require "switchman/active_record/postgresql_adapter"
|
|
176
173
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
177
174
|
end
|
|
175
|
+
|
|
176
|
+
# If Switchman::Shard wasn't loaded as of when ActiveRecord::Base initialized
|
|
177
|
+
# establish a connection here instead
|
|
178
|
+
if !Shard.instance_variable_get(:@default)
|
|
179
|
+
::ActiveRecord::Base.establish_connection
|
|
180
|
+
end
|
|
178
181
|
end
|
|
179
182
|
end
|
|
180
183
|
|
|
@@ -185,15 +188,15 @@ module Switchman
|
|
|
185
188
|
end
|
|
186
189
|
end
|
|
187
190
|
|
|
188
|
-
initializer 'switchman.
|
|
191
|
+
initializer 'switchman.extend_guard_rail', :before => "switchman.extend_ar" do
|
|
189
192
|
::ActiveSupport.on_load(:active_record) do
|
|
190
|
-
require "switchman/
|
|
193
|
+
require "switchman/guard_rail"
|
|
191
194
|
|
|
192
|
-
::
|
|
195
|
+
::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
|
|
193
196
|
end
|
|
194
197
|
end
|
|
195
198
|
|
|
196
|
-
initializer 'switchman.extend_controller', :after => "
|
|
199
|
+
initializer 'switchman.extend_controller', :after => "guard_rail.extend_ar" do
|
|
197
200
|
::ActiveSupport.on_load(:action_controller) do
|
|
198
201
|
require "switchman/action_controller/caching"
|
|
199
202
|
|
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
|