switchman 3.0.5 → 4.0.0

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 +4 -4
  2. data/Rakefile +16 -15
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +2 -2
  4. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +1 -1
  5. data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
  6. data/lib/switchman/action_controller/caching.rb +2 -2
  7. data/lib/switchman/active_record/abstract_adapter.rb +6 -15
  8. data/lib/switchman/active_record/associations.rb +331 -0
  9. data/lib/switchman/active_record/attribute_methods.rb +182 -77
  10. data/lib/switchman/active_record/base.rb +249 -46
  11. data/lib/switchman/active_record/calculations.rb +98 -44
  12. data/lib/switchman/active_record/connection_handler.rb +18 -0
  13. data/lib/switchman/active_record/connection_pool.rb +27 -28
  14. data/lib/switchman/active_record/database_configurations.rb +44 -6
  15. data/lib/switchman/active_record/finder_methods.rb +46 -16
  16. data/lib/switchman/active_record/log_subscriber.rb +11 -5
  17. data/lib/switchman/active_record/migration.rb +52 -5
  18. data/lib/switchman/active_record/model_schema.rb +1 -1
  19. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  20. data/lib/switchman/active_record/persistence.rb +37 -2
  21. data/lib/switchman/active_record/postgresql_adapter.rb +12 -11
  22. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  23. data/lib/switchman/active_record/query_cache.rb +49 -20
  24. data/lib/switchman/active_record/query_methods.rb +202 -136
  25. data/lib/switchman/active_record/reflection.rb +1 -1
  26. data/lib/switchman/active_record/relation.rb +40 -28
  27. data/lib/switchman/active_record/spawn_methods.rb +2 -2
  28. data/lib/switchman/active_record/statement_cache.rb +11 -7
  29. data/lib/switchman/active_record/table_definition.rb +1 -1
  30. data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
  31. data/lib/switchman/active_record/test_fixtures.rb +53 -0
  32. data/lib/switchman/active_support/cache.rb +25 -4
  33. data/lib/switchman/arel.rb +45 -7
  34. data/lib/switchman/call_super.rb +2 -2
  35. data/lib/switchman/database_server.rb +123 -79
  36. data/lib/switchman/default_shard.rb +14 -5
  37. data/lib/switchman/engine.rb +79 -131
  38. data/lib/switchman/environment.rb +2 -2
  39. data/lib/switchman/errors.rb +17 -2
  40. data/lib/switchman/guard_rail/relation.rb +7 -10
  41. data/lib/switchman/guard_rail.rb +5 -0
  42. data/lib/switchman/parallel.rb +68 -0
  43. data/lib/switchman/r_spec_helper.rb +17 -28
  44. data/lib/switchman/rails.rb +1 -4
  45. data/{app/models → lib}/switchman/shard.rb +226 -241
  46. data/lib/switchman/sharded_instrumenter.rb +3 -3
  47. data/lib/switchman/shared_schema_cache.rb +11 -0
  48. data/lib/switchman/standard_error.rb +15 -12
  49. data/lib/switchman/test_helper.rb +2 -2
  50. data/{app/models → lib}/switchman/unsharded_record.rb +1 -1
  51. data/lib/switchman/version.rb +1 -1
  52. data/lib/switchman.rb +44 -12
  53. data/lib/tasks/switchman.rake +101 -54
  54. metadata +50 -58
  55. data/lib/switchman/active_record/association.rb +0 -206
  56. data/lib/switchman/open4.rb +0 -80
@@ -4,8 +4,8 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module StatementCache
6
6
  module ClassMethods
7
- def create(connection, &block)
8
- relation = block.call ::ActiveRecord::StatementCache::Params.new
7
+ def create(connection)
8
+ relation = yield ::ActiveRecord::StatementCache::Params.new
9
9
 
10
10
  _query_builder, binds = connection.cacheable_query(self, relation.arel)
11
11
  bind_map = ::ActiveRecord::StatementCache::BindMap.new(binds)
@@ -25,7 +25,7 @@ module Switchman
25
25
  # we can make some assumptions about the shard source
26
26
  # (e.g. infer from the primary key or use the current shard)
27
27
 
28
- def execute(*args)
28
+ def execute(*args, &block)
29
29
  params, connection = args
30
30
  klass = @klass
31
31
  target_shard = nil
@@ -33,14 +33,14 @@ module Switchman
33
33
  primary_value = params[primary_index]
34
34
  target_shard = Shard.local_id_for(primary_value)[1]
35
35
  end
36
- current_shard = Shard.current(klass.connection_classes)
36
+ current_shard = Shard.current(klass.connection_class_for_self)
37
37
  target_shard ||= current_shard
38
38
 
39
39
  bind_values = bind_map.bind(params, current_shard, target_shard)
40
40
 
41
- target_shard.activate(klass.connection_classes) do
41
+ target_shard.activate(klass.connection_class_for_self) do
42
42
  sql = qualified_query_builder(target_shard, klass).sql_for(bind_values, connection)
43
- klass.find_by_sql(sql, bind_values)
43
+ klass.find_by_sql(sql, bind_values, &block)
44
44
  end
45
45
  end
46
46
 
@@ -66,7 +66,11 @@ module Switchman
66
66
 
67
67
  def primary_value_index
68
68
  primary_ba_index = @bound_attributes.index do |ba|
69
- ba.is_a?(::ActiveRecord::Relation::QueryAttribute) && ba.value.primary
69
+ if ba.value.is_a?(::ActiveRecord::StatementCache::Substitute)
70
+ ba.is_a?(::ActiveRecord::Relation::QueryAttribute) && ba.value.primary
71
+ else
72
+ false
73
+ end
70
74
  end
71
75
  @indexes.index(primary_ba_index) if primary_ba_index
72
76
  end
@@ -4,7 +4,7 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module TableDefinition
6
6
  def column(name, type, limit: nil, **)
7
- Engine.foreign_key_check(name, type, limit: limit)
7
+ Switchman.foreign_key_check(name, type, limit: limit)
8
8
  super
9
9
  end
10
10
  end
@@ -7,9 +7,14 @@ module Switchman
7
7
  def drop(*)
8
8
  super
9
9
  # no really, it's gone
10
- Switchman.cache.delete('default_shard')
10
+ Switchman.cache.delete("default_shard")
11
11
  Shard.default(reload: true)
12
12
  end
13
+
14
+ def raise_for_multi_db(*)
15
+ # ignore; Switchman doesn't use namespaced tasks for multiple shards; it uses
16
+ # environment variables to filter which shards you want to target
17
+ end
13
18
  end
14
19
  end
15
20
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchman
4
+ module ActiveRecord
5
+ module TestFixtures
6
+ FORBIDDEN_DB_ENVS = %i[development production].freeze
7
+ def setup_fixtures(config = ::ActiveRecord::Base)
8
+ super
9
+
10
+ return unless run_in_transaction?
11
+
12
+ # Replace the one that activerecord natively uses with a switchman-optimized one
13
+ ::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
14
+ # Code adapted from the code in rails proper
15
+ @connection_subscriber =
16
+ ::ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
17
+ spec_name = if ::Rails.version < "7.1"
18
+ payload[:spec_name] if payload.key?(:spec_name)
19
+ elsif payload.key?(:connection_name)
20
+ payload[:connection_name]
21
+ end
22
+ shard = payload[:shard] if payload.key?(:shard)
23
+
24
+ if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
25
+ begin
26
+ connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
27
+ connection.connect! if ::Rails.version >= "7.1" # eagerly validate the connection
28
+ rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
29
+ connection = nil
30
+ end
31
+
32
+ if connection
33
+ setup_shared_connection_pool
34
+ unless @fixture_connections.include?(connection)
35
+ connection.begin_transaction joinable: false, _lazy: false
36
+ connection.pool.lock_thread = true if lock_threads
37
+ @fixture_connections << connection
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def enlist_fixture_connections
45
+ setup_shared_connection_pool
46
+
47
+ ::ActiveRecord::Base.connection_handler.connection_pool_list(:primary).reject do |cp|
48
+ FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym)
49
+ end.map(&:connection)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -4,11 +4,32 @@ module Switchman
4
4
  module ActiveSupport
5
5
  module Cache
6
6
  module ClassMethods
7
+ def lookup_stores(cache_store_config)
8
+ result = {}
9
+ cache_store_config.each do |key, value|
10
+ next if value.is_a?(String)
11
+
12
+ result[key] = ::ActiveSupport::Cache.lookup_store(value)
13
+ end
14
+
15
+ cache_store_config.each do |key, value| # rubocop:disable Style/CombinableLoops
16
+ next unless value.is_a?(String)
17
+
18
+ result[key] = result[value]
19
+ end
20
+ result
21
+ end
22
+
7
23
  def lookup_store(*store_options)
8
24
  store = super
9
- # can't use defined?, because it's a _ruby_ autoloaded constant,
10
- # so just checking that will cause it to get required
11
- ::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore) if store.instance_of?(ActiveSupport::Cache::RedisCacheStore) && !::ActiveSupport::Cache::RedisCacheStore.ancestors.include?(RedisCacheStore)
25
+ # must use the string name, otherwise it will try to auto-load the constant
26
+ # and we don't want to require redis in this file (since it's not a hard dependency)
27
+ # rubocop:disable Style/ClassEqualityComparison
28
+ if store.class.name == "ActiveSupport::Cache::RedisCacheStore" &&
29
+ !(::ActiveSupport::Cache::RedisCacheStore <= RedisCacheStore)
30
+ ::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
31
+ end
32
+ # rubocop:enable Style/ClassEqualityComparison
12
33
  store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
13
34
  store
14
35
  end
@@ -17,7 +38,7 @@ module Switchman
17
38
  module RedisCacheStore
18
39
  def clear(namespace: nil, **)
19
40
  # RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
20
- # unfortunately, it uses the keys command, which is extraordinarily inefficient in a large redis instance
41
+ # unfortunately, it doesn't work using redis clustering because of the way redis keys are distributed
21
42
  # fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
22
43
  # always unset it temporarily for clear calls
23
44
  namespace = nil # rubocop:disable Lint/ShadowedArgument
@@ -11,22 +11,60 @@ module Switchman
11
11
  module Visitors
12
12
  module ToSql
13
13
  # rubocop:disable Naming/MethodName
14
+ # rubocop:disable Naming/MethodParameterName
14
15
 
15
- def visit_Arel_Nodes_TableAlias(*args)
16
- o, collector = args
16
+ def visit_Arel_Nodes_Cte(o, collector)
17
+ collector << quote_local_table_name(o.name)
18
+ collector << " AS "
19
+
20
+ case o.materialized
21
+ when true
22
+ collector << "MATERIALIZED "
23
+ when false
24
+ collector << "NOT MATERIALIZED "
25
+ end
26
+
27
+ visit o.relation, collector
28
+ end
29
+
30
+ def visit_Arel_Nodes_TableAlias(o, collector)
17
31
  collector = visit o.relation, collector
18
- collector << ' '
32
+ collector << " "
19
33
  collector << quote_local_table_name(o.name)
20
34
  end
21
35
 
22
- def visit_Arel_Attributes_Attribute(*args)
23
- o = args.first
36
+ def visit_Arel_Attributes_Attribute(o, collector)
24
37
  join_name = o.relation.table_alias || o.relation.name
25
- result = "#{quote_local_table_name join_name}.#{quote_column_name o.name}"
26
- args.last << result
38
+ collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
39
+ end
40
+
41
+ if ::Rails.version < "7.1"
42
+ def visit_Arel_Nodes_HomogeneousIn(o, collector)
43
+ collector.preparable = false
44
+
45
+ collector << quote_local_table_name(o.table_name) << "." << quote_column_name(o.column_name)
46
+
47
+ collector << if o.type == :in
48
+ " IN ("
49
+ else
50
+ " NOT IN ("
51
+ end
52
+
53
+ values = o.casted_values
54
+
55
+ if values.empty?
56
+ collector << @connection.quote(nil)
57
+ else
58
+ collector.add_binds(values, o.proc_for_binds, &bind_block)
59
+ end
60
+
61
+ collector << ")"
62
+ collector
63
+ end
27
64
  end
28
65
 
29
66
  # rubocop:enable Naming/MethodName
67
+ # rubocop:enable Naming/MethodParameterName
30
68
 
31
69
  def quote_local_table_name(name)
32
70
  return name if ::Arel::Nodes::SqlLiteral === name
@@ -12,8 +12,8 @@ module Switchman
12
12
  method.super_method
13
13
  end
14
14
 
15
- def call_super(method, above_module, *args, &block)
16
- super_method_above(method, above_module).call(*args, &block)
15
+ def call_super(method, above_module, ...)
16
+ super_method_above(method, above_module).call(...)
17
17
  end
18
18
  end
19
19
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
4
 
5
5
  module Switchman
6
6
  class DatabaseServer
@@ -8,24 +8,25 @@ module Switchman
8
8
 
9
9
  class << self
10
10
  attr_accessor :creating_new_shard
11
+ attr_reader :all_roles
12
+
13
+ include Enumerable
14
+
15
+ delegate :each, to: :all
11
16
 
12
17
  def all
13
18
  database_servers.values
14
19
  end
15
20
 
16
- def all_roles
17
- @all_roles ||= all.map(&:roles).flatten.uniq
18
- end
19
-
20
21
  def find(id_or_all)
21
22
  return all if id_or_all == :all
22
- return id_or_all.map { |id| database_servers[id || ::Rails.env] }.compact.uniq if id_or_all.is_a?(Array)
23
+ return id_or_all.filter_map { |id| database_servers[id || ::Rails.env] }.uniq if id_or_all.is_a?(Array)
23
24
 
24
25
  database_servers[id_or_all || ::Rails.env]
25
26
  end
26
27
 
27
28
  def create(settings = {})
28
- raise 'database servers should be set up in database.yml' unless ::Rails.env.test?
29
+ raise "database servers should be set up in database.yml" unless ::Rails.env.test?
29
30
 
30
31
  id = settings[:id]
31
32
  unless id
@@ -38,7 +39,7 @@ module Switchman
38
39
  database_servers[server.id] = server
39
40
  ::ActiveRecord::Base.configurations.configurations <<
40
41
  ::ActiveRecord::DatabaseConfigurations::HashConfig.new(::Rails.env, "#{server.id}/primary", settings)
41
- Shard.send(:initialize_sharding)
42
+ Shard.send(:configure_connects_to)
42
43
  server
43
44
  end
44
45
 
@@ -49,31 +50,48 @@ module Switchman
49
50
  servers[rand(servers.length)]
50
51
  end
51
52
 
53
+ def guard_servers
54
+ all.each { |db| db.guard! if db.config[:prefer_secondary] }
55
+ end
56
+
57
+ def regions
58
+ @regions ||= all.filter_map(&:region).uniq.sort
59
+ end
60
+
52
61
  private
53
62
 
54
63
  def reference_role(role)
55
64
  return if all_roles.include?(role)
56
65
 
57
66
  @all_roles << role
58
- Shard.send(:initialize_sharding)
67
+ Shard.send(:configure_connects_to)
59
68
  end
60
69
 
61
70
  def database_servers
62
- unless @database_servers
71
+ if !@database_servers || @database_servers.empty?
63
72
  @database_servers = {}.with_indifferent_access
73
+ roles = []
64
74
  ::ActiveRecord::Base.configurations.configurations.each do |config|
65
- if config.name.include?('/')
66
- name, role = config.name.split('/')
75
+ if config.name.include?("/")
76
+ name, role = config.name.split("/")
67
77
  else
68
78
  name, role = config.env_name, config.name
69
79
  end
80
+ role = role.to_sym
70
81
 
71
- if role == 'primary'
82
+ roles << role
83
+ if role == :primary
72
84
  @database_servers[name] = DatabaseServer.new(config.env_name, config.configuration_hash)
73
85
  else
74
86
  @database_servers[name].roles << role
75
87
  end
76
88
  end
89
+ # Do this after so that all database servers for all roles are established and we won't prematurely
90
+ # configure a connection for the wrong role
91
+ @all_roles = roles.uniq
92
+ return @database_servers if @database_servers.empty?
93
+
94
+ Shard.send(:configure_connects_to)
77
95
  end
78
96
  @database_servers
79
97
  end
@@ -89,21 +107,22 @@ module Switchman
89
107
  end
90
108
 
91
109
  def connects_to_hash
92
- self.class.all_roles.map do |role|
110
+ self.class.all_roles.to_h do |role|
93
111
  config_role = role
94
112
  config_role = :primary unless roles.include?(role)
95
113
  config_name = :"#{id}/#{config_role}"
96
114
  config_name = :primary if id == ::Rails.env && config_role == :primary
97
115
  [role.to_sym, config_name]
98
- end.to_h
116
+ end
99
117
  end
100
118
 
101
119
  def destroy
102
120
  self.class.send(:database_servers).delete(id) if id
103
121
  Shard.sharded_models.each do |klass|
104
122
  self.class.all_roles.each do |role|
105
- klass.connection_handler.remove_connection_pool(klass.connection_specification_name, role: role,
106
- shard: id.to_sym)
123
+ klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
124
+ role: role,
125
+ shard: id.to_sym)
107
126
  end
108
127
  end
109
128
  end
@@ -129,32 +148,57 @@ module Switchman
129
148
  end
130
149
  end
131
150
 
132
- def guard_rail_environment
133
- @guard_rail_environment || ::GuardRail.environment
151
+ def region
152
+ config[:region]
153
+ end
154
+
155
+ # @param region [String, Array<String>] the region(s) to check against
156
+ # @return true if the database server doesn't have a region, or it
157
+ # matches the specified region
158
+ def in_region?(region)
159
+ !self.region || (region.is_a?(Array) ? region.include?(self.region) : self.region == region)
160
+ end
161
+
162
+ # @return true if the database server doesn't have a region, Switchman is
163
+ # not configured with a region, or the database server's region matches
164
+ # Switchman's current region
165
+ def in_current_region?
166
+ unless instance_variable_defined?(:@in_current_region)
167
+ @in_current_region = !region ||
168
+ !Switchman.region ||
169
+ region == Switchman.region
170
+ end
171
+ @in_current_region
134
172
  end
135
173
 
136
174
  # locks this db to a specific environment, except for
137
175
  # when doing writes (then it falls back to the current
138
176
  # value of GuardRail.environment)
139
177
  def guard!(environment = :secondary)
140
- @guard_rail_environment = environment
178
+ DatabaseServer.send(:reference_role, environment)
179
+ ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
180
+ klasses: [::ActiveRecord::Base] }
141
181
  end
142
182
 
143
183
  def unguard!
144
- @guard_rail_environment = nil
184
+ ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
185
+ klasses: [::ActiveRecord::Base] }
145
186
  end
146
187
 
147
188
  def unguard
148
- old_env = @guard_rail_environment
149
- unguard!
150
- yield
151
- ensure
152
- guard!(old_env)
189
+ return yield unless ::ActiveRecord::Base.role_overriden?(id.to_sym)
190
+
191
+ begin
192
+ unguard!
193
+ yield
194
+ ensure
195
+ ::ActiveRecord::Base.connected_to_stack.pop
196
+ end
153
197
  end
154
198
 
155
199
  def shards
156
200
  if id == ::Rails.env
157
- Shard.where('database_server_id IS NULL OR database_server_id=?', id)
201
+ Shard.where("database_server_id IS NULL OR database_server_id=?", id)
158
202
  else
159
203
  Shard.where(database_server_id: id)
160
204
  end
@@ -179,65 +223,65 @@ module Switchman
179
223
  end
180
224
 
181
225
  id ||= begin
182
- id_seq = Shard.connection.quote(Shard.connection.quote_table_name('switchman_shards_id_seq'))
226
+ id_seq = Shard.connection.quote(Shard.connection.quote_table_name("switchman_shards_id_seq"))
183
227
  next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
184
228
  next_id.to_i
185
229
  end
186
230
 
187
231
  name ||= "#{config[:database]}_shard_#{id}"
188
232
 
233
+ schema_already_existed = false
234
+ shard = nil
189
235
  Shard.connection.transaction do
190
- shard = Shard.create!(id: id,
191
- name: name,
192
- database_server_id: self.id)
193
- schema_already_existed = false
194
-
195
- begin
196
- self.class.creating_new_shard = true
197
- DatabaseServer.send(:reference_role, :deploy)
198
- ::ActiveRecord::Base.connected_to(shard: self.id.to_sym, role: :deploy) do
199
- if create_statement
200
- if ::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}")
201
- schema_already_existed = true
202
- raise 'This schema already exists; cannot overwrite'
203
- end
204
- Array(create_statement.call).each do |stmt|
205
- ::ActiveRecord::Base.connection.execute(stmt)
206
- end
236
+ self.class.creating_new_shard = true
237
+ DatabaseServer.send(:reference_role, :deploy)
238
+ ::ActiveRecord::Base.connected_to(shard: self.id.to_sym, role: :deploy) do
239
+ shard = Shard.create!(id: id,
240
+ name: name,
241
+ database_server_id: self.id)
242
+ if create_statement
243
+ if ::ActiveRecord::Base.connection.select_value(
244
+ "SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"
245
+ )
246
+ schema_already_existed = true
247
+ raise "This schema already exists; cannot overwrite"
207
248
  end
208
- if config[:adapter] == 'postgresql'
209
- old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor do
210
- end
249
+ Array(create_statement.call).each do |stmt|
250
+ ::ActiveRecord::Base.connection.execute(stmt)
211
251
  end
212
- old_verbose = ::ActiveRecord::Migration.verbose
213
- ::ActiveRecord::Migration.verbose = false
214
-
215
- unless schema == false
216
- shard.activate(*Shard.sharded_models) do
217
- reset_column_information
252
+ end
253
+ if config[:adapter] == "postgresql"
254
+ old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {}
255
+ end
256
+ old_verbose = ::ActiveRecord::Migration.verbose
257
+ ::ActiveRecord::Migration.verbose = false
218
258
 
219
- ::ActiveRecord::Base.connection.transaction(requires_new: true) do
259
+ unless schema == false
260
+ shard.activate do
261
+ ::ActiveRecord::Base.connection.transaction(requires_new: true) do
262
+ if ::Rails.version < "7.1"
220
263
  ::ActiveRecord::Base.connection.migration_context.migrate
264
+ else
265
+ ::ActiveRecord::MigrationContext.new(::ActiveRecord::Migrator.migrations_paths).migrate
221
266
  end
222
- reset_column_information
223
- ::ActiveRecord::Base.descendants.reject do |m|
224
- m <= UnshardedRecord || !m.table_exists?
225
- end.each(&:define_attribute_methods)
226
267
  end
268
+
269
+ ::ActiveRecord::Base.descendants.reject do |m|
270
+ m <= UnshardedRecord || !m.table_exists?
271
+ end.each(&:define_attribute_methods)
227
272
  end
228
- ensure
229
- ::ActiveRecord::Migration.verbose = old_verbose
230
- ::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
231
273
  end
232
- shard
233
- rescue
234
- shard.destroy
235
- shard.drop_database rescue nil unless schema_already_existed
236
- reset_column_information unless schema == false rescue nil
237
- raise
238
274
  ensure
239
- self.class.creating_new_shard = false
275
+ ::ActiveRecord::Migration.verbose = old_verbose
276
+ ::ActiveRecord::Base.connection.raw_connection.set_notice_processor(&old_proc) if old_proc
240
277
  end
278
+ shard
279
+ rescue
280
+ shard&.destroy
281
+ shard&.drop_database rescue nil unless schema_already_existed
282
+ raise
283
+ ensure
284
+ self.class.creating_new_shard = false
241
285
  end
242
286
  end
243
287
 
@@ -259,18 +303,18 @@ module Switchman
259
303
  end
260
304
 
261
305
  def primary_shard
262
- unless instance_variable_defined?(:@primary_shard)
263
- # if sharding isn't fully set up yet, we may not be able to query the shards table
264
- @primary_shard = Shard.default if Shard.default.database_server == self
265
- @primary_shard ||= shards.where(name: nil).first
266
- end
267
- @primary_shard
268
- end
306
+ return nil unless primary_shard_id
269
307
 
270
- private
308
+ Shard.lookup(primary_shard_id)
309
+ end
271
310
 
272
- def reset_column_information
273
- ::ActiveRecord::Base.descendants.reject { |m| m <= UnshardedRecord }.each(&:reset_column_information)
311
+ def primary_shard_id
312
+ unless instance_variable_defined?(:@primary_shard_id)
313
+ # if sharding isn't fully set up yet, we may not be able to query the shards table
314
+ @primary_shard_id = Shard.default.id if Shard.default.database_server == self
315
+ @primary_shard_id ||= shards.where(name: nil).first&.id
316
+ end
317
+ @primary_shard_id
274
318
  end
275
319
  end
276
320
  end
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'switchman/database_server'
4
-
5
3
  module Switchman
6
4
  class DefaultShard
7
5
  def id
8
- 'default'
6
+ "default"
9
7
  end
10
- alias cache_key id
8
+ alias_method :cache_key, :id
9
+
11
10
  def activate(*_classes)
12
11
  yield
13
12
  end
@@ -59,8 +58,18 @@ module Switchman
59
58
  self
60
59
  end
61
60
 
61
+ def region; end
62
+
63
+ def in_region?(_region)
64
+ true
65
+ end
66
+
67
+ def in_current_region?
68
+ true
69
+ end
70
+
62
71
  def _dump(_depth)
63
- ''
72
+ ""
64
73
  end
65
74
 
66
75
  def self._load(_str)