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
@@ -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