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.
Files changed (57) 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 +37 -28
  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 +123 -124
  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 +72 -60
  39. data/lib/switchman/default_shard.rb +3 -0
  40. data/lib/switchman/engine.rb +45 -40
  41. data/lib/switchman/environment.rb +2 -0
  42. data/lib/switchman/errors.rb +3 -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 +80 -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 +14 -0
  51. data/lib/switchman/test_helper.rb +6 -3
  52. data/lib/switchman/version.rb +3 -1
  53. data/lib/switchman.rb +4 -2
  54. data/lib/tasks/switchman.rake +54 -68
  55. metadata +87 -40
  56. data/app/models/switchman/shard_internal.rb +0 -659
  57. 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
- ::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
 
@@ -272,7 +282,9 @@ module Switchman
272
282
 
273
283
  def primary_shard
274
284
  unless instance_variable_defined?(:@primary_shard)
275
- @primary_shard = shards.where(name: nil).first
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
@@ -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,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/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"
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
- if ::Rails.version > '4.2'
96
- require "switchman/active_record/statement_cache"
97
- ::ActiveRecord::StatementCache.prepend(ActiveRecord::StatementCache)
98
- ::ActiveRecord::StatementCache.singleton_class.prepend(ActiveRecord::StatementCache::ClassMethods)
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
- ::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
103
- end
108
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(ActiveRecord::CollectionAssociation)
104
109
 
105
- if ::Rails.version >= '4.1.15'
106
- ::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
107
- end
110
+ ::ActiveRecord::PredicateBuilder.singleton_class.prepend(ActiveRecord::PredicateBuilder)
108
111
 
109
- if ::Rails.version > '4.1'
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.prepend(Shackles::Relation)
141
+ ::ActiveRecord::Relation.include(CallSuper)
140
142
 
141
- if ::Rails.version >= '5'
142
- ::ActiveRecord::Relation::WhereClauseFactory.prepend(ActiveRecord::WhereClauseFactory)
143
- ::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
144
- ::ActiveRecord::TypeCaster::Map.include(ActiveRecord::TypeCaster::Map)
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, options)
157
- 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
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.extend_shackles', :before => "switchman.extend_ar" do
190
+ initializer 'switchman.extend_guard_rail', :before => "switchman.extend_ar" do
186
191
  ::ActiveSupport.on_load(:active_record) do
187
- require "switchman/shackles"
192
+ require "switchman/guard_rail"
188
193
 
189
- ::Shackles.singleton_class.prepend(Shackles::ClassMethods)
194
+ ::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
190
195
  end
191
196
  end
192
197
 
193
- initializer 'switchman.extend_controller', :after => "shackles.extend_ar" do
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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'etc'
2
4
 
3
5
  module Switchman
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  class NonExistentShardError < RuntimeError; end
5
+ class ParallelShardExecError < RuntimeError; end
3
6
  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
 
@@ -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
- 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)