switchman 3.4.2 → 3.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +15 -14
  3. data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
  4. data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
  5. data/lib/switchman/active_record/abstract_adapter.rb +4 -2
  6. data/lib/switchman/active_record/associations.rb +89 -16
  7. data/lib/switchman/active_record/attribute_methods.rb +67 -22
  8. data/lib/switchman/active_record/base.rb +112 -22
  9. data/lib/switchman/active_record/calculations.rb +93 -37
  10. data/lib/switchman/active_record/connection_handler.rb +18 -0
  11. data/lib/switchman/active_record/connection_pool.rb +18 -14
  12. data/lib/switchman/active_record/database_configurations.rb +37 -15
  13. data/lib/switchman/active_record/finder_methods.rb +44 -14
  14. data/lib/switchman/active_record/log_subscriber.rb +11 -5
  15. data/lib/switchman/active_record/migration.rb +28 -9
  16. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  17. data/lib/switchman/active_record/persistence.rb +22 -0
  18. data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
  19. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  20. data/lib/switchman/active_record/query_cache.rb +49 -20
  21. data/lib/switchman/active_record/query_methods.rb +93 -30
  22. data/lib/switchman/active_record/relation.rb +22 -11
  23. data/lib/switchman/active_record/spawn_methods.rb +2 -2
  24. data/lib/switchman/active_record/statement_cache.rb +2 -2
  25. data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
  26. data/lib/switchman/active_record/test_fixtures.rb +26 -16
  27. data/lib/switchman/active_support/cache.rb +9 -4
  28. data/lib/switchman/arel.rb +34 -18
  29. data/lib/switchman/call_super.rb +2 -8
  30. data/lib/switchman/database_server.rb +68 -21
  31. data/lib/switchman/default_shard.rb +14 -3
  32. data/lib/switchman/engine.rb +39 -19
  33. data/lib/switchman/environment.rb +2 -2
  34. data/lib/switchman/errors.rb +4 -1
  35. data/lib/switchman/guard_rail/relation.rb +1 -2
  36. data/lib/switchman/parallel.rb +5 -5
  37. data/lib/switchman/r_spec_helper.rb +11 -11
  38. data/lib/switchman/shard.rb +166 -64
  39. data/lib/switchman/sharded_instrumenter.rb +7 -3
  40. data/lib/switchman/standard_error.rb +4 -0
  41. data/lib/switchman/test_helper.rb +2 -2
  42. data/lib/switchman/version.rb +1 -1
  43. data/lib/switchman.rb +27 -15
  44. data/lib/tasks/switchman.rake +117 -51
  45. metadata +19 -44
@@ -44,12 +44,18 @@ module Switchman
44
44
  primary_shard.activate(klass.connection_class_for_self) { super }
45
45
  end
46
46
 
47
- def explain
48
- activate { |relation| relation.call_super(:explain, Relation) }
47
+ if ::Rails.version > "7.1.2"
48
+ def transaction(...)
49
+ primary_shard.activate(klass.connection_class_for_self) { super }
50
+ end
51
+ end
52
+
53
+ def explain(*args)
54
+ activate { |relation| relation.call_super(:explain, Relation, *args) }
49
55
  end
50
56
 
51
57
  def load(&block)
52
- if !loaded? || (::Rails.version >= '7.0' && scheduled?)
58
+ if !loaded? || (::Rails.version >= "7.0" && scheduled?)
53
59
  @records = activate { |relation| relation.send(:exec_queries, &block) }
54
60
  @loaded = true
55
61
  end
@@ -58,10 +64,9 @@ module Switchman
58
64
  end
59
65
 
60
66
  %I[update_all delete_all].each do |method|
61
- arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
62
67
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
63
- def #{method}(#{arg_params})
64
- result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, #{arg_params}) }
68
+ def #{method}(*args, **kwargs)
69
+ result = self.activate(unordered: true) { |relation| relation.call_super(#{method.inspect}, Relation, *args, **kwargs) }
65
70
  result = result.sum if result.is_a?(Array)
66
71
  result
67
72
  end
@@ -73,12 +78,16 @@ module Switchman
73
78
  loose_mode = options[:loose] && is_integer
74
79
  # loose_mode: if we don't care about getting exactly batch_size ids in between
75
80
  # don't get the max - just get the min and add batch_size so we get that many _at most_
76
- values = loose_mode ? 'MIN(id)' : 'MIN(id), MAX(id)'
81
+ values = loose_mode ? "MIN(id)" : "MIN(id), MAX(id)"
77
82
 
78
83
  batch_size = options[:batch_size].try(:to_i) || 1000
79
- quoted_primary_key = "#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
80
- as_id = ' AS id' unless primary_key == 'id'
81
- subquery_scope = except(:select).select("#{quoted_primary_key}#{as_id}").reorder(primary_key.to_sym).limit(loose_mode ? 1 : batch_size)
84
+ quoted_primary_key =
85
+ "#{klass.connection.quote_local_table_name(table_name)}.#{klass.connection.quote_column_name(primary_key)}"
86
+ as_id = " AS id" unless primary_key == "id"
87
+ subquery_scope = except(:select)
88
+ .select("#{quoted_primary_key}#{as_id}")
89
+ .reorder(primary_key.to_sym)
90
+ .limit(loose_mode ? 1 : batch_size)
82
91
  subquery_scope = subquery_scope.where("#{quoted_primary_key} <= ?", options[:end_at]) if options[:end_at]
83
92
 
84
93
  first_subquery_scope = if options[:start_at]
@@ -121,7 +130,9 @@ module Switchman
121
130
  relation = shard(Shard.current(klass.connection_class_for_self))
122
131
  relation.remove_nonlocal_primary_keys!
123
132
  # do a minimal query if possible
124
- relation = relation.limit(limit_value - result_count) if limit_value && !result_count.zero? && order_values.empty?
133
+ if limit_value && !result_count.zero? && order_values.empty?
134
+ relation = relation.limit(limit_value - result_count)
135
+ end
125
136
 
126
137
  shard_results = relation.activate(&block)
127
138
 
@@ -17,7 +17,7 @@ module Switchman
17
17
  final_shard_source_value = %i[explicit association].detect do |source_value|
18
18
  shard_source_value == source_value || rhs.shard_source_value == source_value
19
19
  end
20
- raise 'unknown shard_source_value' unless final_shard_source_value
20
+ raise "unknown shard_source_value" unless final_shard_source_value
21
21
 
22
22
  # have to merge shard_value
23
23
  lhs_shard_value = all_shards
@@ -36,7 +36,7 @@ module Switchman
36
36
  final_shard_source_value = %i[explicit association implicit].detect do |source_value|
37
37
  shard_source_value == source_value || rhs.shard_source_value == source_value
38
38
  end
39
- raise 'unknown shard_source_value' unless final_shard_source_value
39
+ raise "unknown shard_source_value" unless final_shard_source_value
40
40
  end
41
41
 
42
42
  [final_shard_value, final_primary_shard, final_shard_source_value]
@@ -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)
@@ -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
@@ -12,31 +12,41 @@ module Switchman
12
12
  # Replace the one that activerecord natively uses with a switchman-optimized one
13
13
  ::ActiveSupport::Notifications.unsubscribe(@connection_subscriber)
14
14
  # Code adapted from the code in rails proper
15
- @connection_subscriber = ::ActiveSupport::Notifications.subscribe('!connection.active_record') do |_, _, _, _, payload|
16
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
17
- shard = payload[:shard] if payload.key?(:shard)
18
- setup_shared_connection_pool
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)
19
23
 
20
- if spec_name && !FORBIDDEN_DB_ENVS.include?(shard)
21
- begin
22
- connection = ::ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
23
- rescue ::ActiveRecord::ConnectionNotEstablished, ::ActiveRecord::NoDatabaseError
24
- connection = nil
25
- end
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
26
31
 
27
- if connection && !@fixture_connections.include?(connection)
28
- connection.begin_transaction joinable: false, _lazy: false
29
- connection.pool.lock_thread = true if lock_threads
30
- @fixture_connections << connection
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
31
40
  end
32
41
  end
33
- end
34
42
  end
35
43
 
36
44
  def enlist_fixture_connections
37
45
  setup_shared_connection_pool
38
46
 
39
- ::ActiveRecord::Base.connection_handler.connection_pool_list.reject { |cp| FORBIDDEN_DB_ENVS.include?(cp.db_config.env_name.to_sym) }.map(&:connection)
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)
40
50
  end
41
51
  end
42
52
  end
@@ -22,9 +22,14 @@ module Switchman
22
22
 
23
23
  def lookup_store(*store_options)
24
24
  store = super
25
- # can't use defined?, because it's a _ruby_ autoloaded constant,
26
- # so just checking that will cause it to get required
27
- ::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
28
33
  store.options[:namespace] ||= -> { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
29
34
  store
30
35
  end
@@ -33,7 +38,7 @@ module Switchman
33
38
  module RedisCacheStore
34
39
  def clear(namespace: nil, **)
35
40
  # RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
36
- # 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
37
42
  # fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
38
43
  # always unset it temporarily for clear calls
39
44
  namespace = nil # rubocop:disable Lint/ShadowedArgument
@@ -13,38 +13,54 @@ module Switchman
13
13
  # rubocop:disable Naming/MethodName
14
14
  # rubocop:disable Naming/MethodParameterName
15
15
 
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
+
16
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
36
  def visit_Arel_Attributes_Attribute(o, collector)
23
37
  join_name = o.relation.table_alias || o.relation.name
24
- collector << quote_local_table_name(join_name) << '.' << quote_column_name(o.name)
38
+ collector << quote_local_table_name(join_name) << "." << quote_column_name(o.name)
25
39
  end
26
40
 
27
- def visit_Arel_Nodes_HomogeneousIn(o, collector)
28
- collector.preparable = false
41
+ if ::Rails.version < "7.1"
42
+ def visit_Arel_Nodes_HomogeneousIn(o, collector)
43
+ collector.preparable = false
29
44
 
30
- collector << quote_local_table_name(o.table_name) << '.' << quote_column_name(o.column_name)
45
+ collector << quote_local_table_name(o.table_name) << "." << quote_column_name(o.column_name)
31
46
 
32
- collector << if o.type == :in
33
- ' IN ('
34
- else
35
- ' NOT IN ('
36
- end
47
+ collector << if o.type == :in
48
+ " IN ("
49
+ else
50
+ " NOT IN ("
51
+ end
37
52
 
38
- values = o.casted_values
53
+ values = o.casted_values
39
54
 
40
- if values.empty?
41
- collector << @connection.quote(nil)
42
- else
43
- collector.add_binds(values, o.proc_for_binds, &bind_block)
44
- end
55
+ if values.empty?
56
+ collector << @connection.quote(nil)
57
+ else
58
+ collector.add_binds(values, o.proc_for_binds, &bind_block)
59
+ end
45
60
 
46
- collector << ')'
47
- collector
61
+ collector << ")"
62
+ collector
63
+ end
48
64
  end
49
65
 
50
66
  # rubocop:enable Naming/MethodName
@@ -12,14 +12,8 @@ module Switchman
12
12
  method.super_method
13
13
  end
14
14
 
15
- if RUBY_VERSION <= '2.8'
16
- def call_super(method, above_module, *args, &block)
17
- super_method_above(method, above_module).call(*args, &block)
18
- end
19
- else
20
- def call_super(method, above_module, *args, **kwargs, &block)
21
- super_method_above(method, above_module).call(*args, **kwargs, &block)
22
- end
15
+ def call_super(method, above_module, ...)
16
+ super_method_above(method, above_module).call(...)
23
17
  end
24
18
  end
25
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
@@ -10,19 +10,23 @@ module Switchman
10
10
  attr_accessor :creating_new_shard
11
11
  attr_reader :all_roles
12
12
 
13
+ include Enumerable
14
+
15
+ delegate :each, to: :all
16
+
13
17
  def all
14
18
  database_servers.values
15
19
  end
16
20
 
17
21
  def find(id_or_all)
18
22
  return all if id_or_all == :all
19
- 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)
20
24
 
21
25
  database_servers[id_or_all || ::Rails.env]
22
26
  end
23
27
 
24
28
  def create(settings = {})
25
- 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?
26
30
 
27
31
  id = settings[:id]
28
32
  unless id
@@ -50,6 +54,10 @@ module Switchman
50
54
  all.each { |db| db.guard! if db.config[:prefer_secondary] }
51
55
  end
52
56
 
57
+ def regions
58
+ @regions ||= all.filter_map(&:region).uniq.sort
59
+ end
60
+
53
61
  private
54
62
 
55
63
  def reference_role(role)
@@ -64,8 +72,8 @@ module Switchman
64
72
  @database_servers = {}.with_indifferent_access
65
73
  roles = []
66
74
  ::ActiveRecord::Base.configurations.configurations.each do |config|
67
- if config.name.include?('/')
68
- name, role = config.name.split('/')
75
+ if config.name.include?("/")
76
+ name, role = config.name.split("/")
69
77
  else
70
78
  name, role = config.env_name, config.name
71
79
  end
@@ -81,6 +89,8 @@ module Switchman
81
89
  # Do this after so that all database servers for all roles are established and we won't prematurely
82
90
  # configure a connection for the wrong role
83
91
  @all_roles = roles.uniq
92
+ return @database_servers if @database_servers.empty?
93
+
84
94
  Shard.send(:configure_connects_to)
85
95
  end
86
96
  @database_servers
@@ -110,8 +120,9 @@ module Switchman
110
120
  self.class.send(:database_servers).delete(id) if id
111
121
  Shard.sharded_models.each do |klass|
112
122
  self.class.all_roles.each do |role|
113
- klass.connection_handler.remove_connection_pool(klass.connection_specification_name, role: role,
114
- shard: id.to_sym)
123
+ klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
124
+ role: role,
125
+ shard: id.to_sym)
115
126
  end
116
127
  end
117
128
  end
@@ -137,16 +148,41 @@ module Switchman
137
148
  end
138
149
  end
139
150
 
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
172
+ end
173
+
140
174
  # locks this db to a specific environment, except for
141
175
  # when doing writes (then it falls back to the current
142
176
  # value of GuardRail.environment)
143
177
  def guard!(environment = :secondary)
144
178
  DatabaseServer.send(:reference_role, environment)
145
- ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment }, klasses: [::ActiveRecord::Base] }
179
+ ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
180
+ klasses: [::ActiveRecord::Base] }
146
181
  end
147
182
 
148
183
  def unguard!
149
- ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit }, klasses: [::ActiveRecord::Base] }
184
+ ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
185
+ klasses: [::ActiveRecord::Base] }
150
186
  end
151
187
 
152
188
  def unguard
@@ -162,7 +198,7 @@ module Switchman
162
198
 
163
199
  def shards
164
200
  if id == ::Rails.env
165
- 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)
166
202
  else
167
203
  Shard.where(database_server_id: id)
168
204
  end
@@ -187,7 +223,7 @@ module Switchman
187
223
  end
188
224
 
189
225
  id ||= begin
190
- 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"))
191
227
  next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
192
228
  next_id.to_i
193
229
  end
@@ -204,17 +240,18 @@ module Switchman
204
240
  name: name,
205
241
  database_server_id: self.id)
206
242
  if create_statement
207
- if ::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}")
243
+ if ::ActiveRecord::Base.connection.select_value(
244
+ "SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"
245
+ )
208
246
  schema_already_existed = true
209
- raise 'This schema already exists; cannot overwrite'
247
+ raise "This schema already exists; cannot overwrite"
210
248
  end
211
249
  Array(create_statement.call).each do |stmt|
212
250
  ::ActiveRecord::Base.connection.execute(stmt)
213
251
  end
214
252
  end
215
- if config[:adapter] == 'postgresql'
216
- old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor do
217
- end
253
+ if config[:adapter] == "postgresql"
254
+ old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {}
218
255
  end
219
256
  old_verbose = ::ActiveRecord::Migration.verbose
220
257
  ::ActiveRecord::Migration.verbose = false
@@ -222,7 +259,11 @@ module Switchman
222
259
  unless schema == false
223
260
  shard.activate do
224
261
  ::ActiveRecord::Base.connection.transaction(requires_new: true) do
225
- ::ActiveRecord::Base.connection.migration_context.migrate
262
+ if ::Rails.version < "7.1"
263
+ ::ActiveRecord::Base.connection.migration_context.migrate
264
+ else
265
+ ::ActiveRecord::MigrationContext.new(::ActiveRecord::Migrator.migrations_paths).migrate
266
+ end
226
267
  end
227
268
 
228
269
  ::ActiveRecord::Base.descendants.reject do |m|
@@ -262,12 +303,18 @@ module Switchman
262
303
  end
263
304
 
264
305
  def primary_shard
265
- unless instance_variable_defined?(:@primary_shard)
306
+ return nil unless primary_shard_id
307
+
308
+ Shard.lookup(primary_shard_id)
309
+ end
310
+
311
+ def primary_shard_id
312
+ unless instance_variable_defined?(:@primary_shard_id)
266
313
  # if sharding isn't fully set up yet, we may not be able to query the shards table
267
- @primary_shard = Shard.default if Shard.default.database_server == self
268
- @primary_shard ||= shards.where(name: nil).first
314
+ @primary_shard_id = Shard.default.id if Shard.default.database_server == self
315
+ @primary_shard_id ||= shards.where(name: nil).first&.id
269
316
  end
270
- @primary_shard
317
+ @primary_shard_id
271
318
  end
272
319
  end
273
320
  end
@@ -3,9 +3,10 @@
3
3
  module Switchman
4
4
  class DefaultShard
5
5
  def id
6
- 'default'
6
+ "default"
7
7
  end
8
- alias cache_key id
8
+ alias_method :cache_key, :id
9
+
9
10
  def activate(*_classes)
10
11
  yield
11
12
  end
@@ -57,8 +58,18 @@ module Switchman
57
58
  self
58
59
  end
59
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
+
60
71
  def _dump(_depth)
61
- ''
72
+ ""
62
73
  end
63
74
 
64
75
  def self._load(_str)
@@ -5,24 +5,28 @@ module Switchman
5
5
  isolate_namespace Switchman
6
6
 
7
7
  # enable Rails 6.1 style connection handling
8
- config.active_record.legacy_connection_handling = false
8
+ config.active_record.legacy_connection_handling = false if ::Rails.version < "7.1"
9
9
  config.active_record.writing_role = :primary
10
10
 
11
11
  ::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
12
12
 
13
- # after :initialize_dependency_mechanism to ensure autoloading is configured for any downstream initializers that care
14
- # In rails 7.0 we should be able to just use an explicit after on configuring the once autoloaders and not need to go monkey around with initializer order
15
- if ::Rails.version < '7.0'
16
- initialize_dependency_mechanism = ::Rails::Application::Bootstrap.initializers.find { |i| i.name == :initialize_dependency_mechanism }
13
+ # after :initialize_dependency_mechanism to ensure autoloading is
14
+ # configured for any downstream initializers that care. In rails 7.0 we
15
+ # should be able to just use an explicit after on configuring the once
16
+ # autoloaders and not need to go monkey around with initializer order
17
+ if ::Rails.version < "7.0"
18
+ initialize_dependency_mechanism = ::Rails::Application::Bootstrap.initializers.find do |i|
19
+ i.name == :initialize_dependency_mechanism
20
+ end
17
21
  initialize_dependency_mechanism.instance_variable_get(:@options)[:after] = :set_autoload_paths
18
22
  end
19
23
 
20
- initializer 'switchman.active_record_patch',
21
- before: 'active_record.initialize_database',
22
- after: (::Rails.version < '7.0' ? :initialize_dependency_mechanism : :setup_once_autoloader) do
24
+ initializer "switchman.active_record_patch",
25
+ before: "active_record.initialize_database",
26
+ after: ((::Rails.version < "7.0") ? :initialize_dependency_mechanism : :setup_once_autoloader) do
23
27
  ::ActiveSupport.on_load(:active_record) do
24
28
  # Switchman requires postgres, so just always load the pg adapter
25
- require 'active_record/connection_adapters/postgresql_adapter'
29
+ require "active_record/connection_adapters/postgresql_adapter"
26
30
 
27
31
  self.default_shard = ::Rails.env.to_sym
28
32
  self.default_role = :primary
@@ -50,14 +54,23 @@ module Switchman
50
54
  ::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::Associations::CollectionProxy)
51
55
 
52
56
  ::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Associations::Preloader::Association)
53
- ::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(ActiveRecord::Associations::Preloader::Association::LoaderRecords) unless ::Rails.version < '7.0'
57
+ unless ::Rails.version < "7.0"
58
+ ::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
59
+ ActiveRecord::Associations::Preloader::Association::LoaderRecords
60
+ )
61
+ end
54
62
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
63
+ unless ::Rails.version < "7.1"
64
+ ::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
65
+ end
55
66
  ::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
56
67
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
57
68
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
58
69
 
59
70
  ::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
60
- ::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(ActiveRecord::DatabaseConfigurations::DatabaseConfig)
71
+ ::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
72
+ ActiveRecord::DatabaseConfigurations::DatabaseConfig
73
+ )
61
74
 
62
75
  ::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
63
76
  ::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
@@ -65,6 +78,11 @@ module Switchman
65
78
  ::ActiveRecord::MigrationContext.prepend(ActiveRecord::MigrationContext)
66
79
  ::ActiveRecord::Migrator.prepend(ActiveRecord::Migrator)
67
80
 
81
+ if ::Rails.version > "7.1.3"
82
+ ::ActiveRecord::PendingMigrationConnection.singleton_class
83
+ .include(ActiveRecord::PendingMigrationConnection::ClassMethods)
84
+ end
85
+
68
86
  ::ActiveRecord::Reflection::AbstractReflection.include(ActiveRecord::Reflection::AbstractReflection)
69
87
  ::ActiveRecord::Reflection::AssociationReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
70
88
  ::ActiveRecord::Reflection::ThroughReflection.prepend(ActiveRecord::Reflection::AssociationScopeCache)
@@ -77,8 +95,9 @@ module Switchman
77
95
  ::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
78
96
  ::ActiveRecord::Relation.include(CallSuper)
79
97
 
80
- ::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
81
- ::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
98
+ ::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(
99
+ ActiveRecord::PredicateBuilder::PolymorphicArrayValue
100
+ )
82
101
 
83
102
  ::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
84
103
 
@@ -96,17 +115,18 @@ module Switchman
96
115
 
97
116
  ::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
98
117
  end
99
- # Ensure that ActiveRecord::Base is always loaded before any app-level initializers can go try to load Switchman::Shard or we get a loop
118
+ # Ensure that ActiveRecord::Base is always loaded before any app-level
119
+ # initializers can go try to load Switchman::Shard or we get a loop
100
120
  ::ActiveRecord::Base
101
121
  end
102
122
 
103
- initializer 'switchman.error_patch', after: 'active_record.initialize_database' do
123
+ initializer "switchman.error_patch", after: "active_record.initialize_database" do
104
124
  ::ActiveSupport.on_load(:active_record) do
105
125
  ::StandardError.include(StandardError)
106
126
  end
107
127
  end
108
128
 
109
- initializer 'switchman.initialize_cache', before: :initialize_cache, after: 'active_record.initialize_database' do
129
+ initializer "switchman.initialize_cache", before: :initialize_cache, after: "active_record.initialize_database" do
110
130
  ::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
111
131
 
112
132
  # if we haven't already setup our cache map out-of-band, set it up from
@@ -130,11 +150,11 @@ module Switchman
130
150
  Switchman.config[:cache_map][::Rails.env] = value
131
151
  end
132
152
 
133
- middlewares = Switchman.config[:cache_map].values.map do |store|
153
+ middlewares = Switchman.config[:cache_map].values.filter_map do |store|
134
154
  store.middleware if store.respond_to?(:middleware)
135
- end.compact.uniq
155
+ end.uniq
136
156
  middlewares.each do |middleware|
137
- config.middleware.insert_before('Rack::Runtime', middleware)
157
+ config.middleware.insert_before("Rack::Runtime", middleware)
138
158
  end
139
159
 
140
160
  # prevent :initialize_cache from trying to (or needing to) set