switchman 3.2.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) 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 -49
  7. data/lib/switchman/active_record/attribute_methods.rb +72 -34
  8. data/lib/switchman/active_record/base.rb +145 -27
  9. data/lib/switchman/active_record/calculations.rb +96 -49
  10. data/lib/switchman/active_record/connection_handler.rb +18 -0
  11. data/lib/switchman/active_record/connection_pool.rb +24 -3
  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 +45 -3
  16. data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
  17. data/lib/switchman/active_record/persistence.rb +30 -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 +192 -135
  22. data/lib/switchman/active_record/relation.rb +28 -13
  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 -2
  30. data/lib/switchman/database_server.rb +69 -31
  31. data/lib/switchman/default_shard.rb +14 -3
  32. data/lib/switchman/engine.rb +29 -22
  33. data/lib/switchman/environment.rb +2 -2
  34. data/lib/switchman/errors.rb +13 -0
  35. data/lib/switchman/guard_rail/relation.rb +1 -1
  36. data/lib/switchman/parallel.rb +6 -6
  37. data/lib/switchman/r_spec_helper.rb +12 -11
  38. data/lib/switchman/shard.rb +180 -68
  39. data/lib/switchman/sharded_instrumenter.rb +3 -3
  40. data/lib/switchman/shared_schema_cache.rb +11 -0
  41. data/lib/switchman/standard_error.rb +4 -0
  42. data/lib/switchman/test_helper.rb +2 -2
  43. data/lib/switchman/version.rb +1 -1
  44. data/lib/switchman.rb +27 -15
  45. data/lib/tasks/switchman.rake +96 -60
  46. metadata +35 -45
@@ -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,8 @@ 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
- def with_each_shard(*args, parallel: false, exception: :raise, &block)
168
+ # output: - :simple, :decorated (with database_server_id:shard_name), custom lambda transformer
169
+ def with_each_shard(*args, parallel: false, exception: :raise, output: :simple)
124
170
  raise ArgumentError, "wrong number of arguments (#{args.length} for 0...2)" if args.length > 2
125
171
 
126
172
  return Array.wrap(yield) unless default.is_a?(Shard)
@@ -135,17 +181,26 @@ module Switchman
135
181
  scope, classes = args
136
182
  end
137
183
 
184
+ output = if output == :decorated
185
+ ->(arg) { "#{Shard.current.description}: #{arg}" }
186
+ elsif output == :simple
187
+ nil
188
+ else
189
+ output
190
+ end
138
191
  parallel = [Environment.cpu_count || 2, 2].min if parallel == true
139
192
  parallel = 0 if parallel == false || parallel.nil?
140
193
 
141
194
  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?
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
143
198
 
144
199
  if parallel > 1
145
200
  if ::ActiveRecord::Relation === scope
146
201
  # 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
202
+ database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
203
+ .filter_map(&:database_server).uniq
149
204
  # nothing to do
150
205
  return if database_servers.count.zero?
151
206
 
@@ -159,25 +214,33 @@ module Switchman
159
214
  # clear connections prior to forking (no more queries will be executed in the parent,
160
215
  # and we want them gone so that we don't accidentally use them post-fork doing something
161
216
  # silly like dealloc'ing prepared statements)
162
- ::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
163
222
 
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|
223
+ parent_process_name = sanitized_process_title
224
+ ret = ::Parallel.map(scopes, in_processes: (scopes.length > 1) ? parallel : 0) do |server, subscope|
166
225
  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
226
+ last_description = name
227
+
171
228
  begin
172
229
  max_length = 128 - name.length - 3
173
230
  short_parent_name = parent_process_name[0..max_length] if max_length >= 0
174
- new_title = [short_parent_name, name].join(' ')
231
+ new_title = [short_parent_name, name].join(" ")
175
232
  Process.setproctitle(new_title)
176
233
  Switchman.config[:on_fork_proc]&.call
177
- with_each_shard(subscope, classes, exception: exception, &block).map { |result| Parallel::ResultWrapper.new(result) }
234
+ with_each_shard(subscope,
235
+ classes,
236
+ exception: exception,
237
+ output: output || :decorated) do
238
+ last_description = Shard.current.description
239
+ Parallel::ResultWrapper.new(yield)
240
+ end
178
241
  rescue => e
179
242
  logger.error e.full_message
180
- Parallel::QuietExceptionWrapper.new(name, ::Parallel::ExceptionWrapper.new(e))
243
+ Parallel::QuietExceptionWrapper.new(last_description, ::Parallel::ExceptionWrapper.new(e))
181
244
  end
182
245
  end.flatten
183
246
 
@@ -185,8 +248,9 @@ module Switchman
185
248
  unless errors.empty?
186
249
  raise errors.first.exception if errors.length == 1
187
250
 
251
+ errors_desc = errors.map(&:name).sort.join(", ")
188
252
  raise Errors::ParallelShardExecError,
189
- "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}",
190
254
  cause: errors.first.exception
191
255
  end
192
256
 
@@ -195,14 +259,20 @@ module Switchman
195
259
 
196
260
  classes ||= []
197
261
 
198
- previous_shard = nil
199
262
  result = []
200
263
  ex = nil
264
+ old_stdout = $stdout
265
+ old_stderr = $stderr
201
266
  scope.each do |shard|
202
267
  # shard references a database server that isn't configured in this environment
203
268
  next unless shard.database_server
204
269
 
205
270
  shard.activate(*classes) do
271
+ if output
272
+ $stdout = Parallel::TransformingIO.new(output, $stdout)
273
+ $stderr = Parallel::TransformingIO.new(output, $stderr)
274
+ end
275
+
206
276
  result.concat Array.wrap(yield)
207
277
  rescue
208
278
  case exception
@@ -216,8 +286,10 @@ module Switchman
216
286
  else
217
287
  raise
218
288
  end
289
+ ensure
290
+ $stdout = old_stdout
291
+ $stderr = old_stderr
219
292
  end
220
- previous_shard = shard
221
293
  end
222
294
  raise ex if ex
223
295
 
@@ -241,7 +313,7 @@ module Switchman
241
313
  else
242
314
  shard = partition_object.shard
243
315
  end
244
- when Integer, /^\d+$/, /^(\d+)~(\d+)$/
316
+ when Integer, /\A\d+\Z/, /\A(\d+)~(\d+)\Z/
245
317
  local_id, shard = Shard.local_id_for(partition_object)
246
318
  local_id ||= partition_object
247
319
  object = local_id unless partition_proc
@@ -283,14 +355,14 @@ module Switchman
283
355
  case any_id
284
356
  when ::ActiveRecord::Base
285
357
  any_id.id
286
- when /^(\d+)~(-?\d+)$/
358
+ when /\A(\d+)~(-?\d+)\Z/
287
359
  local_id = $2.to_i
288
360
  signed_id_operation(local_id) do |id|
289
361
  return nil if id > IDS_PER_SHARD
290
362
 
291
363
  ($1.to_i * IDS_PER_SHARD) + id
292
364
  end
293
- when Integer, /^-?\d+$/
365
+ when Integer, /\A-?\d+\Z/
294
366
  any_id.to_i
295
367
  end
296
368
  end
@@ -374,7 +446,7 @@ module Switchman
374
446
  end
375
447
 
376
448
  def configure_connects_to
377
- 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] }
378
450
  sharded_models.each do |klass|
379
451
  connects_to_hash = full_connects_to_hash.deep_dup
380
452
  if klass == UnshardedRecord
@@ -393,7 +465,10 @@ module Switchman
393
465
  end
394
466
  end
395
467
 
468
+ # this resets the default shard on rails 7.1+, but we want to preserve it
469
+ shard_was = klass.default_shard
396
470
  klass.connects_to shards: connects_to_hash
471
+ klass.default_shard = shard_was
397
472
  end
398
473
  end
399
474
 
@@ -427,8 +502,50 @@ module Switchman
427
502
  shard = nil unless shard.database_server
428
503
  shard
429
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
430
545
  end
431
546
 
547
+ delegate :region, :in_region?, :in_current_region?, to: :database_server
548
+
432
549
  def name
433
550
  unless instance_variable_defined?(:@name)
434
551
  # protect against re-entrancy
@@ -457,7 +574,7 @@ module Switchman
457
574
  end
458
575
 
459
576
  def description
460
- [database_server.id, name].compact.join(':')
577
+ [database_server.id, name].compact.join(":")
461
578
  end
462
579
 
463
580
  # Shards are always on the default shard
@@ -469,6 +586,10 @@ module Switchman
469
586
  id
470
587
  end
471
588
 
589
+ def original_id_value
590
+ id
591
+ end
592
+
472
593
  def activate(*classes, &block)
473
594
  shards = hashify_classes(classes)
474
595
  Shard.activate(shards, &block)
@@ -491,48 +612,39 @@ module Switchman
491
612
  end
492
613
 
493
614
  def drop_database
494
- raise('Cannot drop the database of the default shard') if default?
615
+ raise "Cannot drop the database of the default shard" if default?
495
616
  return unless read_attribute(:name)
496
617
 
497
618
  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) }
619
+ activate do
620
+ self.class.drop_database(name)
505
621
  end
622
+ rescue ::ActiveRecord::StatementInvalid => e
623
+ logger.error "Drop failed: #{e}"
624
+ end
625
+ end
506
626
 
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
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)
532
644
  end
645
+ ensure
646
+ conn.raw_connection.set_notice_processor(&old_proc) if old_proc
533
647
  end
534
- rescue
535
- logger.info "Drop failed: #{$!}"
536
648
  end
537
649
  end
538
650
 
@@ -551,7 +663,7 @@ module Switchman
551
663
  end
552
664
 
553
665
  def destroy
554
- raise('Cannot destroy the default shard') if default?
666
+ raise("Cannot destroy the default shard") if default?
555
667
 
556
668
  super
557
669
  end
@@ -560,8 +672,8 @@ module Switchman
560
672
 
561
673
  def clear_cache
562
674
  Shard.default.activate do
563
- Switchman.cache.delete(['shard', id].join('/'))
564
- Switchman.cache.delete('default_shard') if default?
675
+ Switchman.cache.delete(["shard", id].join("/"))
676
+ Switchman.cache.delete("default_shard") if default?
565
677
  end
566
678
  self.class.clear_cache
567
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,10 @@ 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: @shard_host.pool.connection_class&.current_role
20
20
  }
21
21
  end
22
- super name, payload
22
+ super(name, payload)
23
23
  end
24
24
  end
25
25
  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
@@ -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.2.1'
4
+ VERSION = "4.0.0"
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