switchman 3.0.24 → 4.2.4

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +16 -15
  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 +11 -7
  6. data/lib/switchman/active_record/associations.rb +157 -50
  7. data/lib/switchman/active_record/attribute_methods.rb +192 -101
  8. data/lib/switchman/active_record/base.rb +136 -33
  9. data/lib/switchman/active_record/calculations.rb +91 -48
  10. data/lib/switchman/active_record/connection_handler.rb +18 -0
  11. data/lib/switchman/active_record/connection_pool.rb +41 -6
  12. data/lib/switchman/active_record/database_configurations.rb +23 -13
  13. data/lib/switchman/active_record/finder_methods.rb +22 -16
  14. data/lib/switchman/active_record/log_subscriber.rb +3 -6
  15. data/lib/switchman/active_record/migration.rb +42 -17
  16. data/lib/switchman/active_record/model_schema.rb +1 -1
  17. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  18. data/lib/switchman/active_record/persistence.rb +32 -2
  19. data/lib/switchman/active_record/postgresql_adapter.rb +37 -22
  20. data/lib/switchman/active_record/predicate_builder.rb +2 -2
  21. data/lib/switchman/active_record/query_cache.rb +26 -17
  22. data/lib/switchman/active_record/query_methods.rb +249 -142
  23. data/lib/switchman/active_record/reflection.rb +10 -3
  24. data/lib/switchman/active_record/relation.rb +103 -32
  25. data/lib/switchman/active_record/spawn_methods.rb +3 -7
  26. data/lib/switchman/active_record/statement_cache.rb +13 -9
  27. data/lib/switchman/active_record/table_definition.rb +1 -1
  28. data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
  29. data/lib/switchman/active_record/test_fixtures.rb +71 -25
  30. data/lib/switchman/active_support/cache.rb +9 -4
  31. data/lib/switchman/arel.rb +16 -25
  32. data/lib/switchman/call_super.rb +2 -2
  33. data/lib/switchman/database_server.rb +68 -34
  34. data/lib/switchman/default_shard.rb +14 -3
  35. data/lib/switchman/engine.rb +36 -19
  36. data/lib/switchman/environment.rb +2 -2
  37. data/lib/switchman/errors.rb +13 -0
  38. data/lib/switchman/guard_rail/relation.rb +3 -3
  39. data/lib/switchman/parallel.rb +6 -6
  40. data/lib/switchman/r_spec_helper.rb +12 -11
  41. data/lib/switchman/shard.rb +182 -72
  42. data/lib/switchman/sharded_instrumenter.rb +9 -3
  43. data/lib/switchman/shared_schema_cache.rb +11 -0
  44. data/lib/switchman/standard_error.rb +4 -0
  45. data/lib/switchman/test_helper.rb +3 -3
  46. data/lib/switchman/version.rb +1 -1
  47. data/lib/switchman.rb +27 -15
  48. data/lib/tasks/switchman.rake +96 -60
  49. metadata +18 -168
@@ -5,14 +5,57 @@ module Switchman
5
5
  # ten trillion possible ids per shard. yup.
6
6
  IDS_PER_SHARD = 10_000_000_000_000
7
7
 
8
+ # rubocop:disable Style/SymbolProc -- transforming to a lambda produces "no receiver given"
8
9
  # only allow one default
9
10
  validates_uniqueness_of :default, if: ->(s) { s.default? }
11
+ # rubocop:enable Style/SymbolProc
10
12
 
11
13
  after_save :clear_cache
12
14
  after_destroy :clear_cache
13
15
 
14
16
  scope :primary, -> { where(name: nil).order(:database_server_id, :id).distinct_on(:database_server_id) }
15
17
 
18
+ scope :in_region, (lambda do |region, include_regionless: true|
19
+ next in_current_region if region.nil?
20
+
21
+ dbs_by_region = DatabaseServer.group_by(&:region)
22
+ db_count_in_this_region = dbs_by_region[region]&.length.to_i
23
+ db_count_in_this_region += dbs_by_region[nil]&.length.to_i if include_regionless
24
+ non_existent_database_servers = Shard.send(:non_existent_database_servers)
25
+ db_count_in_other_regions = DatabaseServer.all.length -
26
+ db_count_in_this_region +
27
+ non_existent_database_servers.length
28
+
29
+ dbs_in_this_region = dbs_by_region[region]&.map(&:id) || []
30
+ dbs_in_this_region += dbs_by_region[nil]&.map(&:id) || [] if include_regionless
31
+
32
+ if db_count_in_this_region <= db_count_in_other_regions
33
+ if dbs_in_this_region.include?(Shard.default.database_server.id)
34
+ where("database_server_id IN (?) OR database_server_id IS NULL", dbs_in_this_region)
35
+ else
36
+ where(database_server_id: dbs_in_this_region)
37
+ end
38
+ elsif db_count_in_other_regions.zero?
39
+ all
40
+ else
41
+ dbs_not_in_this_region = DatabaseServer.map(&:id) - dbs_in_this_region + non_existent_database_servers
42
+ if dbs_in_this_region.include?(Shard.default.database_server.id)
43
+ where("database_server_id NOT IN (?) OR database_server_id IS NULL", dbs_not_in_this_region)
44
+ else
45
+ where.not(database_server_id: dbs_not_in_this_region)
46
+ end
47
+ end
48
+ end)
49
+
50
+ scope :in_current_region, (lambda do |include_regionless: true|
51
+ # sharding isn't set up? maybe we're in tests, or a somehow degraded environment
52
+ # either way there's only one shard, and we always want to see it
53
+ return [default] unless default.is_a?(Switchman::Shard)
54
+ return all if !Switchman.region || DatabaseServer.none?(&:region)
55
+
56
+ in_region(Switchman.region, include_regionless:)
57
+ end)
58
+
16
59
  class << self
17
60
  def sharded_models
18
61
  @sharded_models ||= [::ActiveRecord::Base, UnshardedRecord].freeze
@@ -36,13 +79,15 @@ module Switchman
36
79
 
37
80
  # Now find the actual record, if it exists
38
81
  @default = begin
39
- find_cached('default_shard') { Shard.where(default: true).take } || default
82
+ find_cached("default_shard") { Shard.where(default: true).take } || default
40
83
  rescue
41
84
  default
42
85
  end
43
86
 
44
87
  # 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)
88
+ if @default.database_server.instance_variable_defined?(:@primary_shard)
89
+ @default.database_server.remove_instance_variable(:@primary_shard)
90
+ end
46
91
 
47
92
  # and finally, check for cached references to the default shard on the existing connection
48
93
  sharded_models.each do |klass|
@@ -75,28 +120,30 @@ module Switchman
75
120
  klass.current_switchman_shard != shard
76
121
 
77
122
  (activated_classes ||= []) << klass
78
- klass.connected_to_stack << { shard: shard.database_server.id.to_sym, klasses: [klass], switchman_shard: shard }
123
+ klass.connected_to_stack << { shard: shard.database_server.id.to_sym,
124
+ klasses: [klass],
125
+ switchman_shard: shard }
79
126
  end
80
127
  activated_classes
81
128
  end
82
129
 
83
130
  def active_shards
84
- sharded_models.map do |klass|
131
+ sharded_models.filter_map do |klass|
85
132
  [klass, current(klass)]
86
- end.compact.to_h
133
+ end.to_h
87
134
  end
88
135
 
89
136
  def lookup(id)
90
137
  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'
138
+ return current if id_i == current.id || id == "self"
139
+ return default if id_i == default.id || id.nil? || id == "default"
93
140
 
94
141
  id = id_i
95
142
  raise ArgumentError if id.zero?
96
143
 
97
144
  unless cached_shards.key?(id)
98
145
  cached_shards[id] = Shard.default.activate do
99
- find_cached(['shard', id]) { find_by(id: id) }
146
+ find_cached(["shard", id]) { find_by(id:) }
100
147
  end
101
148
  end
102
149
  cached_shards[id]
@@ -120,7 +167,8 @@ module Switchman
120
167
  # forking.
121
168
  # exception: - :ignore, :raise, :defer (wait until the end and raise the first
122
169
  # error), or a proc
123
- def with_each_shard(*args, parallel: false, exception: :raise, &block)
170
+ # output: - :simple, :decorated (with database_server_id:shard_name), custom lambda transformer
171
+ def with_each_shard(*args, parallel: false, exception: :raise, output: :simple)
124
172
  raise ArgumentError, "wrong number of arguments (#{args.length} for 0...2)" if args.length > 2
125
173
 
126
174
  return Array.wrap(yield) unless default.is_a?(Shard)
@@ -135,19 +183,28 @@ module Switchman
135
183
  scope, classes = args
136
184
  end
137
185
 
186
+ output = if output == :decorated
187
+ ->(arg) { "#{Shard.current.description}: #{arg}" }
188
+ elsif output == :simple
189
+ nil
190
+ else
191
+ output
192
+ end
138
193
  parallel = [Environment.cpu_count || 2, 2].min if parallel == true
139
194
  parallel = 0 if parallel == false || parallel.nil?
140
195
 
141
196
  scope ||= Shard.all
142
- scope = scope.order(::Arel.sql('database_server_id IS NOT NULL, database_server_id, id')) if ::ActiveRecord::Relation === scope && scope.order_values.empty?
197
+ if ::ActiveRecord::Relation === scope && scope.order_values.empty?
198
+ scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
199
+ end
143
200
 
144
201
  if parallel > 1
145
202
  if ::ActiveRecord::Relation === scope
146
203
  # still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
147
- database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
148
- map(&:database_server).compact.uniq
204
+ database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
205
+ .filter_map(&:database_server).uniq
149
206
  # nothing to do
150
- return if database_servers.count.zero?
207
+ return if database_servers.none?
151
208
 
152
209
  scopes = database_servers.to_h do |server|
153
210
  [server, scope.merge(server.shards)]
@@ -159,25 +216,29 @@ module Switchman
159
216
  # clear connections prior to forking (no more queries will be executed in the parent,
160
217
  # and we want them gone so that we don't accidentally use them post-fork doing something
161
218
  # silly like dealloc'ing prepared statements)
162
- ::ActiveRecord::Base.clear_all_connections!
219
+ ::ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
163
220
 
164
- parent_process_name = `ps -ocommand= -p#{Process.pid}`.slice(/#{$0}.*/)
165
- ret = ::Parallel.map(scopes, in_processes: scopes.length > 1 ? parallel : 0) do |server, subscope|
221
+ parent_process_name = sanitized_process_title
222
+ ret = ::Parallel.map(scopes, in_processes: (scopes.length > 1) ? parallel : 0) do |server, subscope|
166
223
  name = server.id
167
- # rubocop:disable Style/GlobalStdStream
168
- $stdout = Parallel::PrefixingIO.new(name, STDOUT)
169
- $stderr = Parallel::PrefixingIO.new(name, STDERR)
170
- # rubocop:enable Style/GlobalStdStream
224
+ last_description = name
225
+
171
226
  begin
172
227
  max_length = 128 - name.length - 3
173
228
  short_parent_name = parent_process_name[0..max_length] if max_length >= 0
174
- new_title = [short_parent_name, name].join(' ')
229
+ new_title = [short_parent_name, name].join(" ")
175
230
  Process.setproctitle(new_title)
176
231
  Switchman.config[:on_fork_proc]&.call
177
- with_each_shard(subscope, classes, exception: exception, &block).map { |result| Parallel::ResultWrapper.new(result) }
232
+ with_each_shard(subscope,
233
+ classes,
234
+ exception:,
235
+ output: output || :decorated) do
236
+ last_description = Shard.current.description
237
+ Parallel::ResultWrapper.new(yield)
238
+ end
178
239
  rescue => e
179
240
  logger.error e.full_message
180
- Parallel::QuietExceptionWrapper.new(name, ::Parallel::ExceptionWrapper.new(e))
241
+ Parallel::QuietExceptionWrapper.new(last_description, ::Parallel::ExceptionWrapper.new(e))
181
242
  end
182
243
  end.flatten
183
244
 
@@ -185,8 +246,9 @@ module Switchman
185
246
  unless errors.empty?
186
247
  raise errors.first.exception if errors.length == 1
187
248
 
249
+ errors_desc = errors.map(&:name).sort.join(", ")
188
250
  raise Errors::ParallelShardExecError,
189
- "The following database server(s) did not finish processing cleanly: #{errors.map(&:name).sort.join(', ')}",
251
+ "The following database server(s) did not finish processing cleanly: #{errors_desc}",
190
252
  cause: errors.first.exception
191
253
  end
192
254
 
@@ -195,14 +257,20 @@ module Switchman
195
257
 
196
258
  classes ||= []
197
259
 
198
- previous_shard = nil
199
260
  result = []
200
261
  ex = nil
262
+ old_stdout = $stdout
263
+ old_stderr = $stderr
201
264
  scope.each do |shard|
202
265
  # shard references a database server that isn't configured in this environment
203
266
  next unless shard.database_server
204
267
 
205
268
  shard.activate(*classes) do
269
+ if output
270
+ $stdout = Parallel::TransformingIO.new(output, $stdout)
271
+ $stderr = Parallel::TransformingIO.new(output, $stderr)
272
+ end
273
+
206
274
  result.concat Array.wrap(yield)
207
275
  rescue
208
276
  case exception
@@ -216,8 +284,10 @@ module Switchman
216
284
  else
217
285
  raise
218
286
  end
287
+ ensure
288
+ $stdout = old_stdout
289
+ $stderr = old_stderr
219
290
  end
220
- previous_shard = shard
221
291
  end
222
292
  raise ex if ex
223
293
 
@@ -241,7 +311,7 @@ module Switchman
241
311
  else
242
312
  shard = partition_object.shard
243
313
  end
244
- when Integer, /^\d+$/, /^(\d+)~(\d+)$/
314
+ when Integer, /\A\d+\Z/, /\A(\d+)~(\d+)\Z/
245
315
  local_id, shard = Shard.local_id_for(partition_object)
246
316
  local_id ||= partition_object
247
317
  object = local_id unless partition_proc
@@ -283,14 +353,14 @@ module Switchman
283
353
  case any_id
284
354
  when ::ActiveRecord::Base
285
355
  any_id.id
286
- when /^(\d+)~(-?\d+)$/
356
+ when /\A(\d+)~(-?\d+)\Z/
287
357
  local_id = $2.to_i
288
358
  signed_id_operation(local_id) do |id|
289
359
  return nil if id > IDS_PER_SHARD
290
360
 
291
361
  ($1.to_i * IDS_PER_SHARD) + id
292
362
  end
293
- when Integer, /^-?\d+$/
363
+ when Integer, /\A-?\d+\Z/
294
364
  any_id.to_i
295
365
  end
296
366
  end
@@ -374,7 +444,7 @@ module Switchman
374
444
  end
375
445
 
376
446
  def configure_connects_to
377
- full_connects_to_hash = DatabaseServer.all.to_h { |db| [db.id.to_sym, db.connects_to_hash] }
447
+ full_connects_to_hash = DatabaseServer.to_h { |db| [db.id.to_sym, db.connects_to_hash] }
378
448
  sharded_models.each do |klass|
379
449
  connects_to_hash = full_connects_to_hash.deep_dup
380
450
  if klass == UnshardedRecord
@@ -387,13 +457,16 @@ module Switchman
387
457
  connects_to_hash.each do |(db_name, role_hash)|
388
458
  role_hash.each_key do |role|
389
459
  role_hash.delete(role) if klass.connection_handler.retrieve_connection_pool(
390
- klass.connection_specification_name, role: role, shard: db_name
460
+ klass.connection_specification_name, role:, shard: db_name
391
461
  )
392
462
  end
393
463
  end
394
464
  end
395
465
 
466
+ # this resets the default shard on rails 7.1+, but we want to preserve it
467
+ shard_was = klass.default_shard
396
468
  klass.connects_to shards: connects_to_hash
469
+ klass.default_shard = shard_was
397
470
  end
398
471
  end
399
472
 
@@ -427,8 +500,50 @@ module Switchman
427
500
  shard = nil unless shard.database_server
428
501
  shard
429
502
  end
503
+
504
+ # Determines the name of the current process, including arguments, but stripping
505
+ # any shebang from the invoked script, and any additional path info from the
506
+ # executable.
507
+ #
508
+ # @return [String]
509
+ def sanitized_process_title
510
+ # get the effective process name from `ps`; this will include any changes
511
+ # from Process.setproctitle _or_ assigning to $0.
512
+ parent_process_name = `ps -ocommand= -p#{Process.pid}`.strip
513
+ # Effective process titles may be shorter than the actual
514
+ # command; truncate our ARGV[0] so that they are comparable
515
+ # for the next step
516
+ argv0 = if parent_process_name.length < Process.argv0.length
517
+ Process.argv0[0..parent_process_name.length]
518
+ else
519
+ Process.argv0
520
+ end
521
+
522
+ # when running via a shebang, the `ps` output will include the shebang
523
+ # (i.e. it will be "ruby bin/rails c"); attempt to strip it off.
524
+ # Note that argv0 in this case will _only_ be `bin/rails` (no shebang,
525
+ # no arguments). We want to preserve the arguments we got from `ps`
526
+ if (index = parent_process_name.index(argv0))
527
+ parent_process_name.slice!(0...index)
528
+ end
529
+
530
+ # remove directories from the main executable to make more room
531
+ # for additional info
532
+ argv = parent_process_name.shellsplit
533
+ argv[0] = File.basename(argv[0])
534
+ argv.shelljoin
535
+ end
536
+
537
+ # @return [Array<String>] the list of database servers that are in the
538
+ # config, but don't have any shards on them
539
+ def non_existent_database_servers
540
+ @non_existent_database_servers ||=
541
+ Shard.distinct.pluck(:database_server_id).compact - DatabaseServer.all.map(&:id)
542
+ end
430
543
  end
431
544
 
545
+ delegate :region, :in_region?, :in_current_region?, to: :database_server
546
+
432
547
  def name
433
548
  unless instance_variable_defined?(:@name)
434
549
  # protect against re-entrancy
@@ -457,7 +572,7 @@ module Switchman
457
572
  end
458
573
 
459
574
  def description
460
- [database_server.id, name].compact.join(':')
575
+ [database_server.id, name].compact.join(":")
461
576
  end
462
577
 
463
578
  # Shards are always on the default shard
@@ -469,9 +584,13 @@ module Switchman
469
584
  id
470
585
  end
471
586
 
472
- def activate(*classes, &block)
587
+ def original_id_value
588
+ id
589
+ end
590
+
591
+ def activate(*classes, &)
473
592
  shards = hashify_classes(classes)
474
- Shard.activate(shards, &block)
593
+ Shard.activate(shards, &)
475
594
  end
476
595
 
477
596
  # for use from console ONLY
@@ -491,48 +610,39 @@ module Switchman
491
610
  end
492
611
 
493
612
  def drop_database
494
- raise('Cannot drop the database of the default shard') if default?
613
+ raise "Cannot drop the database of the default shard" if default?
495
614
  return unless read_attribute(:name)
496
615
 
497
616
  begin
498
- adapter = database_server.config[:adapter]
499
- sharding_config = Switchman.config || {}
500
- drop_statement = sharding_config[adapter]&.[](:drop_statement)
501
- drop_statement ||= sharding_config[:drop_statement]
502
- if drop_statement
503
- drop_statement = Array(drop_statement).dup.
504
- map { |statement| statement.gsub('%{name}', name) }
617
+ activate do
618
+ self.class.drop_database(name)
505
619
  end
620
+ rescue ::ActiveRecord::StatementInvalid => e
621
+ logger.error "Drop failed: #{e}"
622
+ end
623
+ end
506
624
 
507
- case adapter
508
- when 'mysql', 'mysql2'
509
- activate do
510
- ::GuardRail.activate(:deploy) do
511
- drop_statement ||= "DROP DATABASE #{name}"
512
- Array(drop_statement).each do |stmt|
513
- ::ActiveRecord::Base.connection.execute(stmt)
514
- end
515
- end
516
- end
517
- when 'postgresql'
518
- activate do
519
- ::GuardRail.activate(:deploy) do
520
- # Shut up, Postgres!
521
- conn = ::ActiveRecord::Base.connection
522
- old_proc = conn.raw_connection.set_notice_processor {}
523
- begin
524
- drop_statement ||= "DROP SCHEMA #{name} CASCADE"
525
- Array(drop_statement).each do |stmt|
526
- ::ActiveRecord::Base.connection.execute(stmt)
527
- end
528
- ensure
529
- conn.raw_connection.set_notice_processor(&old_proc) if old_proc
530
- end
531
- end
625
+ #
626
+ # Drops a specific database/schema from the currently active connection
627
+ #
628
+ def self.drop_database(name)
629
+ sharding_config = Switchman.config || {}
630
+ drop_statement = sharding_config["postgresql"]&.[](:drop_statement)
631
+ drop_statement ||= sharding_config[:drop_statement]
632
+ drop_statement = Array(drop_statement).map { |statement| statement.gsub("%{name}", name) } if drop_statement
633
+
634
+ ::GuardRail.activate(:deploy) do
635
+ # Shut up, Postgres!
636
+ conn = ::ActiveRecord::Base.connection
637
+ old_proc = conn.raw_connection.set_notice_processor {}
638
+ begin
639
+ drop_statement ||= "DROP SCHEMA #{name} CASCADE"
640
+ Array(drop_statement).each do |stmt|
641
+ ::ActiveRecord::Base.connection.execute(stmt)
532
642
  end
643
+ ensure
644
+ conn.raw_connection.set_notice_processor(&old_proc) if old_proc
533
645
  end
534
- rescue
535
- logger.info "Drop failed: #{$!}"
536
646
  end
537
647
  end
538
648
 
@@ -551,7 +661,7 @@ module Switchman
551
661
  end
552
662
 
553
663
  def destroy
554
- raise('Cannot destroy the default shard') if default?
664
+ raise("Cannot destroy the default shard") if default?
555
665
 
556
666
  super
557
667
  end
@@ -560,8 +670,8 @@ module Switchman
560
670
 
561
671
  def clear_cache
562
672
  Shard.default.activate do
563
- Switchman.cache.delete(['shard', id].join('/'))
564
- Switchman.cache.delete('default_shard') if default?
673
+ Switchman.cache.delete(["shard", id].join("/"))
674
+ Switchman.cache.delete("default_shard") if default?
565
675
  end
566
676
  self.class.clear_cache
567
677
  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
 
@@ -13,13 +13,19 @@ module Switchman
13
13
  # when we might be doing a query while defining attribute methods,
14
14
  # so just avoid logging then
15
15
  if shard.is_a?(Shard) && Shard.instance_variable_get(:@attribute_methods_generated)
16
+ env = if ::Rails.version < "8.0"
17
+ @shard_host.pool.connection_class&.current_role
18
+ else
19
+ @shard_host.pool.connection_descriptor&.name&.constantize&.current_role
20
+ end
21
+
16
22
  payload[:shard] = {
17
23
  database_server_id: shard.database_server.id,
18
24
  id: shard.id,
19
- env: @shard_host.pool.connection_klass&.current_role
25
+ env:
20
26
  }
21
27
  end
22
- super name, payload
28
+ super
23
29
  end
24
30
  end
25
31
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchman
4
+ class SharedSchemaCache
5
+ def self.get_schema_cache(connection)
6
+ @schema_cache ||= ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
7
+ @schema_cache.connection = connection
8
+ @schema_cache
9
+ end
10
+ end
11
+ 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
@@ -63,9 +63,9 @@ module Switchman
63
63
 
64
64
  def find_existing_test_shard(server, name)
65
65
  if server == Shard.default.database_server
66
- server.shards.where(name: name).first
66
+ server.shards.where(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.0.24'
4
+ VERSION = "4.2.4"
5
5
  end
data/lib/switchman.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'guard_rail'
4
- require 'zeitwerk'
3
+ require "guard_rail"
4
+ require "zeitwerk"
5
5
 
6
6
  class SwitchmanInflector < Zeitwerk::GemInflector
7
7
  def camelize(basename, abspath)
8
8
  if basename =~ /\Apostgresql_(.*)/
9
- 'PostgreSQL' + super($1, abspath)
9
+ "PostgreSQL" + super($1, abspath)
10
10
  else
11
11
  super
12
12
  end
@@ -18,21 +18,33 @@ loader.inflector = SwitchmanInflector.new(__FILE__)
18
18
  loader.setup
19
19
 
20
20
  module Switchman
21
- def self.config
22
- # TODO: load from yaml
23
- @config ||= {}
24
- end
21
+ Deprecation = ::ActiveSupport::Deprecation.new("4.0", "Switchman")
25
22
 
26
- def self.cache
27
- (@cache.respond_to?(:call) ? @cache.call : @cache) || ::Rails.cache
28
- end
23
+ class << self
24
+ attr_writer :cache
29
25
 
30
- def self.cache=(cache)
31
- @cache = cache
32
- end
26
+ def config
27
+ # TODO: load from yaml
28
+ @config ||= {}
29
+ end
30
+
31
+ def cache
32
+ (@cache.respond_to?(:call) ? @cache.call : @cache) || ::Rails.cache
33
+ end
34
+
35
+ def region
36
+ config[:region]
37
+ end
33
38
 
34
- def self.foreign_key_check(name, type, limit: nil)
35
- puts "WARNING: All foreign keys need to be 8-byte integers. #{name} looks like a foreign key. If so, please add the option: `:limit => 8`" if name.to_s =~ /_id\z/ && type.to_s == 'integer' && limit.to_i < 8
39
+ def foreign_key_check(name, type, limit: nil)
40
+ return unless name.to_s.end_with?("_id") && type.to_s == "integer" && limit.to_i < 8
41
+
42
+ puts <<~TEXT.squish
43
+ WARNING: All foreign keys need to be 8-byte integers.
44
+ #{name} looks like a foreign key.
45
+ If so, please add the option: `:limit => 8`
46
+ TEXT
47
+ end
36
48
  end
37
49
 
38
50
  class OrderOnMultiShardQuery < RuntimeError; end