switchman 3.4.2 → 3.6.7

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 (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
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'etc'
3
+ require "etc"
4
4
 
5
5
  module Switchman
6
6
  class Environment
7
- def self.cpu_count(nproc_bin = 'nproc')
7
+ def self.cpu_count(nproc_bin = "nproc")
8
8
  return Etc.nprocessors if Etc.respond_to?(:nprocessors)
9
9
 
10
10
  `#{nproc_bin}`.to_i
@@ -3,7 +3,10 @@
3
3
  module Switchman
4
4
  module Errors
5
5
  class ManuallyCreatedShadowRecordError < RuntimeError
6
- def initialize(msg = "It looks like you're trying to manually create a shadow record. Please use Switchman::ActiveRecord::Base#save_shadow_record instead.")
6
+ DEFAULT_MSG = "It looks like you're trying to manually create a shadow record. " \
7
+ "Please use Switchman::ActiveRecord::Base#save_shadow_record instead."
8
+
9
+ def initialize(msg = DEFAULT_MSG)
7
10
  super
8
11
  end
9
12
  end
@@ -13,9 +13,8 @@ module Switchman
13
13
  end
14
14
 
15
15
  %w[update_all delete_all].each do |method|
16
- arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
17
16
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
18
- def #{method}(#{arg_params})
17
+ def #{method}(*args, **kwargs)
19
18
  db = Shard.current(connection_class_for_self).database_server
20
19
  db.unguard { super }
21
20
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'parallel'
3
+ require "parallel"
4
4
 
5
5
  module Switchman
6
6
  module Parallel
@@ -50,16 +50,16 @@ module Switchman
50
50
  end
51
51
  end
52
52
 
53
- class PrefixingIO
53
+ class TransformingIO
54
54
  delegate_missing_to :@original_io
55
55
 
56
- def initialize(prefix, original_io)
57
- @prefix = prefix
56
+ def initialize(transformer, original_io)
57
+ @transformer = transformer
58
58
  @original_io = original_io
59
59
  end
60
60
 
61
61
  def puts(*args)
62
- args.flatten.each { |arg| @original_io.puts "#{@prefix}: #{arg}" }
62
+ args.flatten.each { |arg| @original_io.puts @transformer.call(arg) }
63
63
  end
64
64
  end
65
65
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'switchman/test_helper'
3
+ require "switchman/test_helper"
4
4
 
5
5
  module Switchman
6
6
  # including this module in your specs will give you several shards to
@@ -34,9 +34,9 @@ module Switchman
34
34
  groups = group.class.descendant_filtered_examples.map(&:example_group).uniq
35
35
  next unless groups.any? { |descendant_group| RSpecHelper.included_in?(descendant_group) }
36
36
 
37
- puts 'Setting up sharding for all specs...'
37
+ puts "Setting up sharding for all specs..."
38
38
  Shard.delete_all
39
- Switchman.cache.delete('default_shard')
39
+ Switchman.cache.delete("default_shard")
40
40
 
41
41
  @@shard1, @@shard2 = TestHelper.recreate_persistent_test_shards
42
42
  @@default_shard = Shard.default
@@ -48,7 +48,7 @@ module Switchman
48
48
  @@shard1 = @@shard1.create_new_shard
49
49
  @@shard2 = @@shard2.create_new_shard
50
50
  rescue => e
51
- warn 'Sharding setup FAILED!:'
51
+ warn "Sharding setup FAILED!:"
52
52
  while e
53
53
  warn "\n#{e}\n"
54
54
  warn e.backtrace
@@ -66,9 +66,9 @@ module Switchman
66
66
  # we'll re-persist in the group's `before :all`; we don't want them to exist
67
67
  # in the db before then
68
68
  Shard.delete_all
69
- Switchman.cache.delete('default_shard')
69
+ Switchman.cache.delete("default_shard")
70
70
  Shard.default(reload: true)
71
- puts 'Done!'
71
+ puts "Done!"
72
72
 
73
73
  main_pid = Process.pid
74
74
  at_exit do
@@ -76,7 +76,7 @@ module Switchman
76
76
 
77
77
  # preserve rspec's exit status
78
78
  status = $!.is_a?(::SystemExit) ? $!.status : nil
79
- puts 'Tearing down sharding for all specs'
79
+ puts "Tearing down sharding for all specs"
80
80
  @@shard1.database_server.destroy unless @@shard1.database_server == Shard.default.database_server
81
81
  unless @@keep_the_shards
82
82
  @@shard1.drop_database
@@ -95,7 +95,7 @@ module Switchman
95
95
  dup = @@default_shard.dup
96
96
  dup.id = @@default_shard.id
97
97
  dup.save!
98
- Switchman.cache.delete('default_shard')
98
+ Switchman.cache.delete("default_shard")
99
99
  Shard.default(reload: true)
100
100
  dup = @@shard1.dup
101
101
  dup.id = @@shard1.id
@@ -107,7 +107,7 @@ module Switchman
107
107
  end
108
108
 
109
109
  klass.before do
110
- raise 'Sharding did not set up correctly' if @@sharding_failed
110
+ raise "Sharding did not set up correctly" if @@sharding_failed
111
111
 
112
112
  Shard.clear_cache
113
113
  if use_transactional_tests
@@ -121,7 +121,7 @@ module Switchman
121
121
  next if @@sharding_failed
122
122
 
123
123
  # clean up after specs
124
- DatabaseServer.all.each do |ds|
124
+ DatabaseServer.each do |ds|
125
125
  if ds.fake? && ds != @shard2.database_server
126
126
  ds.shards.delete_all unless use_transactional_tests
127
127
  ds.destroy
@@ -132,7 +132,7 @@ module Switchman
132
132
  klass.after(:all) do
133
133
  # Don't truncate because that can create some fun cross-connection lock contention
134
134
  Shard.delete_all
135
- Switchman.cache.delete('default_shard')
135
+ Switchman.cache.delete("default_shard")
136
136
  Shard.default(reload: true)
137
137
  end
138
138
  end
@@ -13,6 +13,47 @@ module Switchman
13
13
 
14
14
  scope :primary, -> { where(name: nil).order(:database_server_id, :id).distinct_on(:database_server_id) }
15
15
 
16
+ scope :in_region, (lambda do |region, include_regionless: true|
17
+ next in_current_region if region.nil?
18
+
19
+ dbs_by_region = DatabaseServer.group_by(&:region)
20
+ db_count_in_this_region = dbs_by_region[region]&.length.to_i
21
+ db_count_in_this_region += dbs_by_region[nil]&.length.to_i if include_regionless
22
+ non_existent_database_servers = Shard.send(:non_existent_database_servers)
23
+ db_count_in_other_regions = DatabaseServer.all.length -
24
+ db_count_in_this_region +
25
+ non_existent_database_servers.length
26
+
27
+ dbs_in_this_region = dbs_by_region[region]&.map(&:id) || []
28
+ dbs_in_this_region += dbs_by_region[nil]&.map(&:id) || [] if include_regionless
29
+
30
+ if db_count_in_this_region <= db_count_in_other_regions
31
+ if dbs_in_this_region.include?(Shard.default.database_server.id)
32
+ where("database_server_id IN (?) OR database_server_id IS NULL", dbs_in_this_region)
33
+ else
34
+ where(database_server_id: dbs_in_this_region)
35
+ end
36
+ elsif db_count_in_other_regions.zero?
37
+ all
38
+ else
39
+ dbs_not_in_this_region = DatabaseServer.map(&:id) - dbs_in_this_region + non_existent_database_servers
40
+ if dbs_in_this_region.include?(Shard.default.database_server.id)
41
+ where("database_server_id NOT IN (?) OR database_server_id IS NULL", dbs_not_in_this_region)
42
+ else
43
+ where.not(database_server_id: dbs_not_in_this_region)
44
+ end
45
+ end
46
+ end)
47
+
48
+ scope :in_current_region, (lambda do |include_regionless: true|
49
+ # sharding isn't set up? maybe we're in tests, or a somehow degraded environment
50
+ # either way there's only one shard, and we always want to see it
51
+ return [default] unless default.is_a?(Switchman::Shard)
52
+ return all if !Switchman.region || DatabaseServer.none?(&:region)
53
+
54
+ in_region(Switchman.region, include_regionless: include_regionless)
55
+ end)
56
+
16
57
  class << self
17
58
  def sharded_models
18
59
  @sharded_models ||= [::ActiveRecord::Base, UnshardedRecord].freeze
@@ -36,13 +77,15 @@ module Switchman
36
77
 
37
78
  # Now find the actual record, if it exists
38
79
  @default = begin
39
- find_cached('default_shard') { Shard.where(default: true).take } || default
80
+ find_cached("default_shard") { Shard.where(default: true).take } || default
40
81
  rescue
41
82
  default
42
83
  end
43
84
 
44
85
  # make sure this is not erroneously cached
45
- @default.database_server.remove_instance_variable(:@primary_shard) if @default.database_server.instance_variable_defined?(:@primary_shard)
86
+ if @default.database_server.instance_variable_defined?(:@primary_shard)
87
+ @default.database_server.remove_instance_variable(:@primary_shard)
88
+ end
46
89
 
47
90
  # and finally, check for cached references to the default shard on the existing connection
48
91
  sharded_models.each do |klass|
@@ -75,28 +118,30 @@ module Switchman
75
118
  klass.current_switchman_shard != shard
76
119
 
77
120
  (activated_classes ||= []) << klass
78
- klass.connected_to_stack << { shard: shard.database_server.id.to_sym, klasses: [klass], switchman_shard: shard }
121
+ klass.connected_to_stack << { shard: shard.database_server.id.to_sym,
122
+ klasses: [klass],
123
+ switchman_shard: shard }
79
124
  end
80
125
  activated_classes
81
126
  end
82
127
 
83
128
  def active_shards
84
- sharded_models.map do |klass|
129
+ sharded_models.filter_map do |klass|
85
130
  [klass, current(klass)]
86
- end.compact.to_h
131
+ end.to_h
87
132
  end
88
133
 
89
134
  def lookup(id)
90
135
  id_i = id.to_i
91
- return current if id_i == current.id || id == 'self'
92
- return default if id_i == default.id || id.nil? || id == 'default'
136
+ return current if id_i == current.id || id == "self"
137
+ return default if id_i == default.id || id.nil? || id == "default"
93
138
 
94
139
  id = id_i
95
140
  raise ArgumentError if id.zero?
96
141
 
97
142
  unless cached_shards.key?(id)
98
143
  cached_shards[id] = Shard.default.activate do
99
- find_cached(['shard', id]) { find_by(id: id) }
144
+ find_cached(["shard", id]) { find_by(id: id) }
100
145
  end
101
146
  end
102
147
  cached_shards[id]
@@ -120,7 +165,7 @@ module Switchman
120
165
  # forking.
121
166
  # exception: - :ignore, :raise, :defer (wait until the end and raise the first
122
167
  # error), or a proc
123
- # output: - :simple, :decorated (with database_server_id:shard_name)
168
+ # output: - :simple, :decorated (with database_server_id:shard_name), custom lambda transformer
124
169
  def with_each_shard(*args, parallel: false, exception: :raise, output: :simple)
125
170
  raise ArgumentError, "wrong number of arguments (#{args.length} for 0...2)" if args.length > 2
126
171
 
@@ -136,17 +181,26 @@ module Switchman
136
181
  scope, classes = args
137
182
  end
138
183
 
184
+ output = if output == :decorated
185
+ ->(arg) { "#{Shard.current.description}: #{arg}" }
186
+ elsif output == :simple
187
+ nil
188
+ else
189
+ output
190
+ end
139
191
  parallel = [Environment.cpu_count || 2, 2].min if parallel == true
140
192
  parallel = 0 if parallel == false || parallel.nil?
141
193
 
142
194
  scope ||= Shard.all
143
- scope = scope.order(::Arel.sql('database_server_id IS NOT NULL, database_server_id, id')) if ::ActiveRecord::Relation === scope && scope.order_values.empty?
195
+ if ::ActiveRecord::Relation === scope && scope.order_values.empty?
196
+ scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
197
+ end
144
198
 
145
199
  if parallel > 1
146
200
  if ::ActiveRecord::Relation === scope
147
201
  # still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
148
- database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
149
- map(&:database_server).compact.uniq
202
+ database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
203
+ .filter_map(&:database_server).uniq
150
204
  # nothing to do
151
205
  return if database_servers.count.zero?
152
206
 
@@ -160,20 +214,27 @@ module Switchman
160
214
  # clear connections prior to forking (no more queries will be executed in the parent,
161
215
  # and we want them gone so that we don't accidentally use them post-fork doing something
162
216
  # silly like dealloc'ing prepared statements)
163
- ::ActiveRecord::Base.clear_all_connections!
217
+ if ::Rails.version < "7.1"
218
+ ::ActiveRecord::Base.clear_all_connections!(nil)
219
+ else
220
+ ::ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
221
+ end
164
222
 
165
- parent_process_name = `ps -ocommand= -p#{Process.pid}`.slice(/#{$0}.*/)
166
- ret = ::Parallel.map(scopes, in_processes: scopes.length > 1 ? parallel : 0) do |server, subscope|
223
+ parent_process_name = sanitized_process_title
224
+ ret = ::Parallel.map(scopes, in_processes: (scopes.length > 1) ? parallel : 0) do |server, subscope|
167
225
  name = server.id
168
226
  last_description = name
169
227
 
170
228
  begin
171
229
  max_length = 128 - name.length - 3
172
230
  short_parent_name = parent_process_name[0..max_length] if max_length >= 0
173
- new_title = [short_parent_name, name].join(' ')
231
+ new_title = [short_parent_name, name].join(" ")
174
232
  Process.setproctitle(new_title)
175
233
  Switchman.config[:on_fork_proc]&.call
176
- with_each_shard(subscope, classes, exception: exception, output: :decorated) do
234
+ with_each_shard(subscope,
235
+ classes,
236
+ exception: exception,
237
+ output: output || :decorated) do
177
238
  last_description = Shard.current.description
178
239
  Parallel::ResultWrapper.new(yield)
179
240
  end
@@ -187,8 +248,9 @@ module Switchman
187
248
  unless errors.empty?
188
249
  raise errors.first.exception if errors.length == 1
189
250
 
251
+ errors_desc = errors.map(&:name).sort.join(", ")
190
252
  raise Errors::ParallelShardExecError,
191
- "The following database server(s) did not finish processing cleanly: #{errors.map(&:name).sort.join(', ')}",
253
+ "The following database server(s) did not finish processing cleanly: #{errors_desc}",
192
254
  cause: errors.first.exception
193
255
  end
194
256
 
@@ -206,9 +268,9 @@ module Switchman
206
268
  next unless shard.database_server
207
269
 
208
270
  shard.activate(*classes) do
209
- if output == :decorated
210
- $stdout = Parallel::PrefixingIO.new(shard.description, $stdout)
211
- $stderr = Parallel::PrefixingIO.new(shard.description, $stderr)
271
+ if output
272
+ $stdout = Parallel::TransformingIO.new(output, $stdout)
273
+ $stderr = Parallel::TransformingIO.new(output, $stderr)
212
274
  end
213
275
 
214
276
  result.concat Array.wrap(yield)
@@ -251,7 +313,7 @@ module Switchman
251
313
  else
252
314
  shard = partition_object.shard
253
315
  end
254
- when Integer, /^\d+$/, /^(\d+)~(\d+)$/
316
+ when Integer, /\A\d+\Z/, /\A(\d+)~(\d+)\Z/
255
317
  local_id, shard = Shard.local_id_for(partition_object)
256
318
  local_id ||= partition_object
257
319
  object = local_id unless partition_proc
@@ -293,14 +355,14 @@ module Switchman
293
355
  case any_id
294
356
  when ::ActiveRecord::Base
295
357
  any_id.id
296
- when /^(\d+)~(-?\d+)$/
358
+ when /\A(\d+)~(-?\d+)\Z/
297
359
  local_id = $2.to_i
298
360
  signed_id_operation(local_id) do |id|
299
361
  return nil if id > IDS_PER_SHARD
300
362
 
301
363
  ($1.to_i * IDS_PER_SHARD) + id
302
364
  end
303
- when Integer, /^-?\d+$/
365
+ when Integer, /\A-?\d+\Z/
304
366
  any_id.to_i
305
367
  end
306
368
  end
@@ -384,7 +446,7 @@ module Switchman
384
446
  end
385
447
 
386
448
  def configure_connects_to
387
- full_connects_to_hash = DatabaseServer.all.to_h { |db| [db.id.to_sym, db.connects_to_hash] }
449
+ full_connects_to_hash = DatabaseServer.to_h { |db| [db.id.to_sym, db.connects_to_hash] }
388
450
  sharded_models.each do |klass|
389
451
  connects_to_hash = full_connects_to_hash.deep_dup
390
452
  if klass == UnshardedRecord
@@ -403,7 +465,10 @@ module Switchman
403
465
  end
404
466
  end
405
467
 
468
+ # this resets the default shard on rails 7.1+, but we want to preserve it
469
+ shard_was = klass.default_shard
406
470
  klass.connects_to shards: connects_to_hash
471
+ klass.default_shard = shard_was
407
472
  end
408
473
  end
409
474
 
@@ -437,8 +502,50 @@ module Switchman
437
502
  shard = nil unless shard.database_server
438
503
  shard
439
504
  end
505
+
506
+ # Determines the name of the current process, including arguments, but stripping
507
+ # any shebang from the invoked script, and any additional path info from the
508
+ # executable.
509
+ #
510
+ # @return [String]
511
+ def sanitized_process_title
512
+ # get the effective process name from `ps`; this will include any changes
513
+ # from Process.setproctitle _or_ assigning to $0.
514
+ parent_process_name = `ps -ocommand= -p#{Process.pid}`.strip
515
+ # Effective process titles may be shorter than the actual
516
+ # command; truncate our ARGV[0] so that they are comparable
517
+ # for the next step
518
+ argv0 = if parent_process_name.length < Process.argv0.length
519
+ Process.argv0[0..parent_process_name.length]
520
+ else
521
+ Process.argv0
522
+ end
523
+
524
+ # when running via a shebang, the `ps` output will include the shebang
525
+ # (i.e. it will be "ruby bin/rails c"); attempt to strip it off.
526
+ # Note that argv0 in this case will _only_ be `bin/rails` (no shebang,
527
+ # no arguments). We want to preserve the arguments we got from `ps`
528
+ if (index = parent_process_name.index(argv0))
529
+ parent_process_name.slice!(0...index)
530
+ end
531
+
532
+ # remove directories from the main executable to make more room
533
+ # for additional info
534
+ argv = parent_process_name.shellsplit
535
+ argv[0] = File.basename(argv[0])
536
+ argv.shelljoin
537
+ end
538
+
539
+ # @return [Array<String>] the list of database servers that are in the
540
+ # config, but don't have any shards on them
541
+ def non_existent_database_servers
542
+ @non_existent_database_servers ||=
543
+ Shard.distinct.pluck(:database_server_id).compact - DatabaseServer.all.map(&:id)
544
+ end
440
545
  end
441
546
 
547
+ delegate :region, :in_region?, :in_current_region?, to: :database_server
548
+
442
549
  def name
443
550
  unless instance_variable_defined?(:@name)
444
551
  # protect against re-entrancy
@@ -467,7 +574,7 @@ module Switchman
467
574
  end
468
575
 
469
576
  def description
470
- [database_server.id, name].compact.join(':')
577
+ [database_server.id, name].compact.join(":")
471
578
  end
472
579
 
473
580
  # Shards are always on the default shard
@@ -479,6 +586,10 @@ module Switchman
479
586
  id
480
587
  end
481
588
 
589
+ def original_id_value
590
+ id
591
+ end
592
+
482
593
  def activate(*classes, &block)
483
594
  shards = hashify_classes(classes)
484
595
  Shard.activate(shards, &block)
@@ -501,48 +612,39 @@ module Switchman
501
612
  end
502
613
 
503
614
  def drop_database
504
- raise('Cannot drop the database of the default shard') if default?
615
+ raise "Cannot drop the database of the default shard" if default?
505
616
  return unless read_attribute(:name)
506
617
 
507
618
  begin
508
- adapter = database_server.config[:adapter]
509
- sharding_config = Switchman.config || {}
510
- drop_statement = sharding_config[adapter]&.[](:drop_statement)
511
- drop_statement ||= sharding_config[:drop_statement]
512
- if drop_statement
513
- drop_statement = Array(drop_statement).dup.
514
- map { |statement| statement.gsub('%{name}', name) }
619
+ activate do
620
+ self.class.drop_database(name)
515
621
  end
622
+ rescue ::ActiveRecord::StatementInvalid => e
623
+ logger.error "Drop failed: #{e}"
624
+ end
625
+ end
516
626
 
517
- case adapter
518
- when 'mysql', 'mysql2'
519
- activate do
520
- ::GuardRail.activate(:deploy) do
521
- drop_statement ||= "DROP DATABASE #{name}"
522
- Array(drop_statement).each do |stmt|
523
- ::ActiveRecord::Base.connection.execute(stmt)
524
- end
525
- end
526
- end
527
- when 'postgresql'
528
- activate do
529
- ::GuardRail.activate(:deploy) do
530
- # Shut up, Postgres!
531
- conn = ::ActiveRecord::Base.connection
532
- old_proc = conn.raw_connection.set_notice_processor {}
533
- begin
534
- drop_statement ||= "DROP SCHEMA #{name} CASCADE"
535
- Array(drop_statement).each do |stmt|
536
- ::ActiveRecord::Base.connection.execute(stmt)
537
- end
538
- ensure
539
- conn.raw_connection.set_notice_processor(&old_proc) if old_proc
540
- end
541
- end
627
+ #
628
+ # Drops a specific database/schema from the currently active connection
629
+ #
630
+ def self.drop_database(name)
631
+ sharding_config = Switchman.config || {}
632
+ drop_statement = sharding_config["postgresql"]&.[](:drop_statement)
633
+ drop_statement ||= sharding_config[:drop_statement]
634
+ drop_statement = Array(drop_statement).map { |statement| statement.gsub("%{name}", name) } if drop_statement
635
+
636
+ ::GuardRail.activate(:deploy) do
637
+ # Shut up, Postgres!
638
+ conn = ::ActiveRecord::Base.connection
639
+ old_proc = conn.raw_connection.set_notice_processor {}
640
+ begin
641
+ drop_statement ||= "DROP SCHEMA #{name} CASCADE"
642
+ Array(drop_statement).each do |stmt|
643
+ ::ActiveRecord::Base.connection.execute(stmt)
542
644
  end
645
+ ensure
646
+ conn.raw_connection.set_notice_processor(&old_proc) if old_proc
543
647
  end
544
- rescue
545
- logger.info "Drop failed: #{$!}"
546
648
  end
547
649
  end
548
650
 
@@ -561,7 +663,7 @@ module Switchman
561
663
  end
562
664
 
563
665
  def destroy
564
- raise('Cannot destroy the default shard') if default?
666
+ raise("Cannot destroy the default shard") if default?
565
667
 
566
668
  super
567
669
  end
@@ -570,8 +672,8 @@ module Switchman
570
672
 
571
673
  def clear_cache
572
674
  Shard.default.activate do
573
- Switchman.cache.delete(['shard', id].join('/'))
574
- Switchman.cache.delete('default_shard') if default?
675
+ Switchman.cache.delete(["shard", id].join("/"))
676
+ Switchman.cache.delete("default_shard") if default?
575
677
  end
576
678
  self.class.clear_cache
577
679
  end
@@ -3,7 +3,7 @@
3
3
  module Switchman
4
4
  class ShardedInstrumenter < ::SimpleDelegator
5
5
  def initialize(instrumenter, shard_host)
6
- super instrumenter
6
+ super(instrumenter)
7
7
  @shard_host = shard_host
8
8
  end
9
9
 
@@ -16,10 +16,14 @@ module Switchman
16
16
  payload[:shard] = {
17
17
  database_server_id: shard.database_server.id,
18
18
  id: shard.id,
19
- env: ::Rails.version < '7.0' ? @shard_host.pool.connection_klass&.current_role : @shard_host.pool.connection_class&.current_role
19
+ env: if ::Rails.version < "7.0"
20
+ @shard_host.pool.connection_klass&.current_role
21
+ else
22
+ @shard_host.pool.connection_class&.current_role
23
+ end
20
24
  }
21
25
  end
22
- super name, payload
26
+ super(name, payload)
23
27
  end
24
28
  end
25
29
  end
@@ -13,6 +13,10 @@ module Switchman
13
13
  Thread.current[:switchman_error_handler] = true
14
14
 
15
15
  @active_shards ||= Shard.active_shards
16
+ rescue
17
+ # intentionally empty - don't allow calculating the active_shards to prevent
18
+ # creating the StandardError for any reason. this prevents various random issues
19
+ # when a StandardError is created within a finalizer
16
20
  ensure
17
21
  Thread.current[:switchman_error_handler] = nil
18
22
  end
@@ -19,7 +19,7 @@ module Switchman
19
19
  end
20
20
 
21
21
  server1 = Shard.default.database_server
22
- server2 = DatabaseServer.create(Shard.default.database_server.config.merge(server2: true))
22
+ server2 = DatabaseServer.create(Shard.default.database_server.config.merge(server2: true, schema_dump: false))
23
23
 
24
24
  if server1 == Shard.default.database_server && server1.config[:shard1] && server1.config[:shard2]
25
25
  # look for the shards in the db already
@@ -65,7 +65,7 @@ module Switchman
65
65
  if server == Shard.default.database_server
66
66
  server.shards.where(name: name).first
67
67
  else
68
- shard = Shard.where('database_server_id IS NOT NULL AND name=?', name).first
68
+ shard = Shard.where("database_server_id IS NOT NULL AND name=?", name).first
69
69
  # if somehow databases got created in a different order, change the shard to match
70
70
  shard.database_server = server if shard
71
71
  shard
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = '3.4.2'
4
+ VERSION = "3.6.7"
5
5
  end