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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/app/models/switchman/shard.rb +746 -11
  3. data/db/migrate/20130328212039_create_switchman_shards.rb +3 -1
  4. data/db/migrate/20130328224244_create_default_shard.rb +4 -2
  5. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +13 -0
  6. data/db/migrate/20180828183945_add_default_shard_index.rb +15 -0
  7. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +17 -0
  8. data/db/migrate/20190114212900_add_unique_name_indexes.rb +9 -0
  9. data/lib/switchman/action_controller/caching.rb +2 -0
  10. data/lib/switchman/active_record/abstract_adapter.rb +14 -4
  11. data/lib/switchman/active_record/association.rb +64 -37
  12. data/lib/switchman/active_record/attribute_methods.rb +16 -5
  13. data/lib/switchman/active_record/base.rb +67 -22
  14. data/lib/switchman/active_record/batches.rb +3 -1
  15. data/lib/switchman/active_record/calculations.rb +16 -21
  16. data/lib/switchman/active_record/connection_handler.rb +71 -79
  17. data/lib/switchman/active_record/connection_pool.rb +28 -23
  18. data/lib/switchman/active_record/finder_methods.rb +20 -29
  19. data/lib/switchman/active_record/log_subscriber.rb +14 -19
  20. data/lib/switchman/active_record/migration.rb +80 -0
  21. data/lib/switchman/active_record/model_schema.rb +3 -1
  22. data/lib/switchman/active_record/persistence.rb +9 -1
  23. data/lib/switchman/active_record/postgresql_adapter.rb +166 -126
  24. data/lib/switchman/active_record/predicate_builder.rb +2 -0
  25. data/lib/switchman/active_record/query_cache.rb +22 -87
  26. data/lib/switchman/active_record/query_methods.rb +122 -126
  27. data/lib/switchman/active_record/reflection.rb +37 -20
  28. data/lib/switchman/active_record/relation.rb +50 -29
  29. data/lib/switchman/active_record/spawn_methods.rb +2 -0
  30. data/lib/switchman/active_record/statement_cache.rb +44 -52
  31. data/lib/switchman/active_record/table_definition.rb +4 -2
  32. data/lib/switchman/active_record/type_caster.rb +2 -0
  33. data/lib/switchman/active_record/where_clause_factory.rb +5 -2
  34. data/lib/switchman/active_support/cache.rb +18 -0
  35. data/lib/switchman/arel.rb +8 -25
  36. data/lib/switchman/call_super.rb +19 -0
  37. data/lib/switchman/connection_pool_proxy.rb +70 -24
  38. data/lib/switchman/database_server.rb +69 -59
  39. data/lib/switchman/default_shard.rb +3 -0
  40. data/lib/switchman/engine.rb +42 -40
  41. data/lib/switchman/environment.rb +2 -0
  42. data/lib/switchman/errors.rb +2 -0
  43. data/lib/switchman/{shackles → guard_rail}/relation.rb +7 -5
  44. data/lib/switchman/{shackles.rb → guard_rail.rb} +6 -4
  45. data/lib/switchman/open4.rb +2 -0
  46. data/lib/switchman/r_spec_helper.rb +14 -8
  47. data/lib/switchman/rails.rb +2 -0
  48. data/lib/switchman/schema_cache.rb +17 -0
  49. data/lib/switchman/sharded_instrumenter.rb +4 -2
  50. data/lib/switchman/standard_error.rb +4 -2
  51. data/lib/switchman/test_helper.rb +6 -3
  52. data/lib/switchman/version.rb +3 -1
  53. data/lib/switchman.rb +3 -1
  54. data/lib/tasks/switchman.rake +46 -72
  55. metadata +87 -41
  56. 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
- ::ActiveRecord::Base.configurations.each do |(id, config)|
40
- @database_servers[id] = DatabaseServer.new(id, config)
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 = :master)
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 Shackles doesn't get any brilliant ideas about choosing the first possible server
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 shackles_environment
80
- @shackles_environment || ::Shackles.environment
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 Shackles.environment)
86
- def shackle!(environment = :slave)
87
- @shackles_environment = environment
97
+ # value of GuardRail.environment)
98
+ def guard!(environment = :secondary)
99
+ @guard_rail_environment = environment
88
100
  end
89
101
 
90
- def unshackle!
91
- @shackles_environment = nil
102
+ def unguard!
103
+ @guard_rail_environment = nil
92
104
  end
93
105
 
94
- def unshackle
95
- old_env = @shackles_environment
96
- unshackle!
106
+ def unguard
107
+ old_env = @guard_rail_environment
108
+ unguard!
97
109
  yield
98
110
  ensure
99
- shackle!(old_env)
111
+ guard!(old_env)
100
112
  end
101
113
 
102
114
  def shareable?
103
115
  @shareable_environment_key ||= []
104
- environment = shackles_environment
105
- explicit_user = ::Shackles.global_config[:username]
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 = self.config[:adapter] != 'sqlite3' && username !~ /%?\{[a-zA-Z0-9_]+\}/
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]].try(:[], :create_statement)
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
- shard = Shard.create!(:name => temp_name,
172
- :database_server => self) do |shard|
173
- shard.id = options[:id] if options[:id]
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
- if name.nil?
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
- ::Shackles.activate(:deploy) do
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
- migrate = -> { ::ActiveRecord::Migrator.migrate(::ActiveRecord::Migrator.migrations_paths) }
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
- shard.drop_database if shard.name == name rescue nil
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
@@ -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(File.join(__FILE__, "../../../app/models"))
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/where_clause_factory"
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/shackles/relation"
88
- require "switchman/shard_internal"
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
- if ::Rails.version > '4.2'
99
- require "switchman/active_record/statement_cache"
100
- ::ActiveRecord::StatementCache.prepend(ActiveRecord::StatementCache)
101
- ::ActiveRecord::StatementCache.singleton_class.prepend(ActiveRecord::StatementCache::ClassMethods)
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
- ::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
106
- end
108
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
107
109
 
108
- if ::Rails.version >= '4.1.15'
109
- ::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
110
- end
110
+ ::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
111
111
 
112
- if ::Rails.version > '4.1'
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.prepend(Shackles::Relation)
141
+ ::ActiveRecord::Relation.include(CallSuper)
143
142
 
144
- if ::Rails.version >= '5'
145
- ::ActiveRecord::Relation::WhereClauseFactory.prepend(ActiveRecord::WhereClauseFactory)
146
- ::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
147
- ::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
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, options)
160
- if name.to_s =~ /_id\z/ && type.to_s == 'integer' && options[:limit].to_i < 8
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.extend_shackles', :before => "switchman.extend_ar" do
190
+ initializer 'switchman.extend_guard_rail', :before => "switchman.extend_ar" do
189
191
  ::ActiveSupport.on_load(:active_record) do
190
- require "switchman/shackles"
192
+ require "switchman/guard_rail"
191
193
 
192
- ::Shackles.singleton_class.prepend(Shackles::ClassMethods)
194
+ ::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
193
195
  end
194
196
  end
195
197
 
196
- initializer 'switchman.extend_controller', :after => "shackles.extend_ar" do
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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'etc'
2
4
 
3
5
  module Switchman
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  class NonExistentShardError < RuntimeError; end
3
5
  class ParallelShardExecError < RuntimeError; end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
- module Shackles
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 ::Shackles.environment != db.shackles_environment
8
- return db.unshackle { super }
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 ::Shackles.environment != db.shackles_environment
19
- db.unshackle { super }
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 Shackles
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
- # Shackles' implementation.
11
+ # GuardRail' implementation.
10
12
  def activate!(environment)
11
- environment ||= :master
13
+ environment ||= :primary
12
14
  activated_environments << environment
13
15
  old_environment = self.environment
14
- @environment = environment
16
+ Thread.current[:guard_rail_environment] = environment
15
17
  old_environment
16
18
  end
17
19
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'open4'
2
4
 
3
5
  # This fixes a bug with exception handling,
@@ -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
- Shard.default(true)
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
- Shard.default(true)
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 use_transactional_fixtures
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 use_transactional_fixtures
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.delete_all
150
- Shard.default(true)
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman::Rails
2
4
  module ClassMethods
3
5
  def self.prepended(klass)
@@ -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.try(:shard)
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.shackles_environment
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 = :default)
9
- @active_shards[category] || Shard.default
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
- if Shard.default(true).is_a?(DefaultShard)
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