switchman 3.4.2 → 3.5.3

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 (40) 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/associations.rb +16 -5
  6. data/lib/switchman/active_record/attribute_methods.rb +67 -22
  7. data/lib/switchman/active_record/base.rb +32 -12
  8. data/lib/switchman/active_record/calculations.rb +54 -37
  9. data/lib/switchman/active_record/connection_pool.rb +4 -2
  10. data/lib/switchman/active_record/database_configurations.rb +20 -10
  11. data/lib/switchman/active_record/finder_methods.rb +1 -1
  12. data/lib/switchman/active_record/log_subscriber.rb +2 -2
  13. data/lib/switchman/active_record/migration.rb +4 -2
  14. data/lib/switchman/active_record/persistence.rb +18 -0
  15. data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
  16. data/lib/switchman/active_record/query_cache.rb +1 -1
  17. data/lib/switchman/active_record/query_methods.rb +72 -26
  18. data/lib/switchman/active_record/relation.rb +13 -7
  19. data/lib/switchman/active_record/spawn_methods.rb +2 -2
  20. data/lib/switchman/active_record/statement_cache.rb +2 -2
  21. data/lib/switchman/active_record/tasks/database_tasks.rb +1 -1
  22. data/lib/switchman/active_record/test_fixtures.rb +19 -16
  23. data/lib/switchman/active_support/cache.rb +4 -1
  24. data/lib/switchman/arel.rb +6 -6
  25. data/lib/switchman/call_super.rb +1 -1
  26. data/lib/switchman/database_server.rb +22 -16
  27. data/lib/switchman/default_shard.rb +3 -3
  28. data/lib/switchman/engine.rb +33 -18
  29. data/lib/switchman/environment.rb +2 -2
  30. data/lib/switchman/errors.rb +4 -1
  31. data/lib/switchman/guard_rail/relation.rb +1 -1
  32. data/lib/switchman/parallel.rb +1 -1
  33. data/lib/switchman/r_spec_helper.rb +10 -10
  34. data/lib/switchman/shard.rb +85 -54
  35. data/lib/switchman/sharded_instrumenter.rb +5 -1
  36. data/lib/switchman/test_helper.rb +1 -1
  37. data/lib/switchman/version.rb +1 -1
  38. data/lib/switchman.rb +12 -4
  39. data/lib/tasks/switchman.rake +40 -37
  40. metadata +9 -9
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
4
 
5
5
  module Switchman
6
6
  class DatabaseServer
@@ -16,13 +16,13 @@ module Switchman
16
16
 
17
17
  def find(id_or_all)
18
18
  return all if id_or_all == :all
19
- return id_or_all.map { |id| database_servers[id || ::Rails.env] }.compact.uniq if id_or_all.is_a?(Array)
19
+ return id_or_all.filter_map { |id| database_servers[id || ::Rails.env] }.uniq if id_or_all.is_a?(Array)
20
20
 
21
21
  database_servers[id_or_all || ::Rails.env]
22
22
  end
23
23
 
24
24
  def create(settings = {})
25
- raise 'database servers should be set up in database.yml' unless ::Rails.env.test?
25
+ raise "database servers should be set up in database.yml" unless ::Rails.env.test?
26
26
 
27
27
  id = settings[:id]
28
28
  unless id
@@ -64,8 +64,8 @@ module Switchman
64
64
  @database_servers = {}.with_indifferent_access
65
65
  roles = []
66
66
  ::ActiveRecord::Base.configurations.configurations.each do |config|
67
- if config.name.include?('/')
68
- name, role = config.name.split('/')
67
+ if config.name.include?("/")
68
+ name, role = config.name.split("/")
69
69
  else
70
70
  name, role = config.env_name, config.name
71
71
  end
@@ -81,6 +81,8 @@ module Switchman
81
81
  # Do this after so that all database servers for all roles are established and we won't prematurely
82
82
  # configure a connection for the wrong role
83
83
  @all_roles = roles.uniq
84
+ return @database_servers if @database_servers.empty?
85
+
84
86
  Shard.send(:configure_connects_to)
85
87
  end
86
88
  @database_servers
@@ -110,8 +112,9 @@ module Switchman
110
112
  self.class.send(:database_servers).delete(id) if id
111
113
  Shard.sharded_models.each do |klass|
112
114
  self.class.all_roles.each do |role|
113
- klass.connection_handler.remove_connection_pool(klass.connection_specification_name, role: role,
114
- shard: id.to_sym)
115
+ klass.connection_handler.remove_connection_pool(klass.connection_specification_name,
116
+ role: role,
117
+ shard: id.to_sym)
115
118
  end
116
119
  end
117
120
  end
@@ -142,11 +145,13 @@ module Switchman
142
145
  # value of GuardRail.environment)
143
146
  def guard!(environment = :secondary)
144
147
  DatabaseServer.send(:reference_role, environment)
145
- ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment }, klasses: [::ActiveRecord::Base] }
148
+ ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => environment },
149
+ klasses: [::ActiveRecord::Base] }
146
150
  end
147
151
 
148
152
  def unguard!
149
- ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit }, klasses: [::ActiveRecord::Base] }
153
+ ::ActiveRecord::Base.connected_to_stack << { shard_roles: { id.to_sym => :_switchman_inherit },
154
+ klasses: [::ActiveRecord::Base] }
150
155
  end
151
156
 
152
157
  def unguard
@@ -162,7 +167,7 @@ module Switchman
162
167
 
163
168
  def shards
164
169
  if id == ::Rails.env
165
- Shard.where('database_server_id IS NULL OR database_server_id=?', id)
170
+ Shard.where("database_server_id IS NULL OR database_server_id=?", id)
166
171
  else
167
172
  Shard.where(database_server_id: id)
168
173
  end
@@ -187,7 +192,7 @@ module Switchman
187
192
  end
188
193
 
189
194
  id ||= begin
190
- id_seq = Shard.connection.quote(Shard.connection.quote_table_name('switchman_shards_id_seq'))
195
+ id_seq = Shard.connection.quote(Shard.connection.quote_table_name("switchman_shards_id_seq"))
191
196
  next_id = Shard.connection.select_value("SELECT nextval(#{id_seq})")
192
197
  next_id.to_i
193
198
  end
@@ -204,17 +209,18 @@ module Switchman
204
209
  name: name,
205
210
  database_server_id: self.id)
206
211
  if create_statement
207
- if ::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}")
212
+ if ::ActiveRecord::Base.connection.select_value(
213
+ "SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"
214
+ )
208
215
  schema_already_existed = true
209
- raise 'This schema already exists; cannot overwrite'
216
+ raise "This schema already exists; cannot overwrite"
210
217
  end
211
218
  Array(create_statement.call).each do |stmt|
212
219
  ::ActiveRecord::Base.connection.execute(stmt)
213
220
  end
214
221
  end
215
- if config[:adapter] == 'postgresql'
216
- old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor do
217
- end
222
+ if config[:adapter] == "postgresql"
223
+ old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {}
218
224
  end
219
225
  old_verbose = ::ActiveRecord::Migration.verbose
220
226
  ::ActiveRecord::Migration.verbose = false
@@ -3,9 +3,9 @@
3
3
  module Switchman
4
4
  class DefaultShard
5
5
  def id
6
- 'default'
6
+ "default"
7
7
  end
8
- alias cache_key id
8
+ alias_method :cache_key, :id
9
9
  def activate(*_classes)
10
10
  yield
11
11
  end
@@ -58,7 +58,7 @@ module Switchman
58
58
  end
59
59
 
60
60
  def _dump(_depth)
61
- ''
61
+ ""
62
62
  end
63
63
 
64
64
  def self._load(_str)
@@ -10,19 +10,23 @@ module Switchman
10
10
 
11
11
  ::GuardRail.singleton_class.prepend(GuardRail::ClassMethods)
12
12
 
13
- # after :initialize_dependency_mechanism to ensure autoloading is configured for any downstream initializers that care
14
- # In rails 7.0 we should be able to just use an explicit after on configuring the once autoloaders and not need to go monkey around with initializer order
15
- if ::Rails.version < '7.0'
16
- initialize_dependency_mechanism = ::Rails::Application::Bootstrap.initializers.find { |i| i.name == :initialize_dependency_mechanism }
13
+ # after :initialize_dependency_mechanism to ensure autoloading is
14
+ # configured for any downstream initializers that care. In rails 7.0 we
15
+ # should be able to just use an explicit after on configuring the once
16
+ # autoloaders and not need to go monkey around with initializer order
17
+ if ::Rails.version < "7.0"
18
+ initialize_dependency_mechanism = ::Rails::Application::Bootstrap.initializers.find do |i|
19
+ i.name == :initialize_dependency_mechanism
20
+ end
17
21
  initialize_dependency_mechanism.instance_variable_get(:@options)[:after] = :set_autoload_paths
18
22
  end
19
23
 
20
- initializer 'switchman.active_record_patch',
21
- before: 'active_record.initialize_database',
22
- after: (::Rails.version < '7.0' ? :initialize_dependency_mechanism : :setup_once_autoloader) do
24
+ initializer "switchman.active_record_patch",
25
+ before: "active_record.initialize_database",
26
+ after: ((::Rails.version < "7.0") ? :initialize_dependency_mechanism : :setup_once_autoloader) do
23
27
  ::ActiveSupport.on_load(:active_record) do
24
28
  # Switchman requires postgres, so just always load the pg adapter
25
- require 'active_record/connection_adapters/postgresql_adapter'
29
+ require "active_record/connection_adapters/postgresql_adapter"
26
30
 
27
31
  self.default_shard = ::Rails.env.to_sym
28
32
  self.default_role = :primary
@@ -50,14 +54,20 @@ module Switchman
50
54
  ::ActiveRecord::Associations::CollectionProxy.include(ActiveRecord::Associations::CollectionProxy)
51
55
 
52
56
  ::ActiveRecord::Associations::Preloader::Association.prepend(ActiveRecord::Associations::Preloader::Association)
53
- ::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(ActiveRecord::Associations::Preloader::Association::LoaderRecords) unless ::Rails.version < '7.0'
57
+ unless ::Rails.version < "7.0"
58
+ ::ActiveRecord::Associations::Preloader::Association::LoaderRecords.prepend(
59
+ ActiveRecord::Associations::Preloader::Association::LoaderRecords
60
+ )
61
+ end
54
62
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::AbstractAdapter)
55
63
  ::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
56
64
  ::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
57
65
  ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
58
66
 
59
67
  ::ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::DatabaseConfigurations)
60
- ::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(ActiveRecord::DatabaseConfigurations::DatabaseConfig)
68
+ ::ActiveRecord::DatabaseConfigurations::DatabaseConfig.prepend(
69
+ ActiveRecord::DatabaseConfigurations::DatabaseConfig
70
+ )
61
71
 
62
72
  ::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
63
73
  ::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
@@ -77,8 +87,12 @@ module Switchman
77
87
  ::ActiveRecord::Relation.include(ActiveRecord::SpawnMethods)
78
88
  ::ActiveRecord::Relation.include(CallSuper)
79
89
 
80
- ::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
81
- ::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(ActiveRecord::PredicateBuilder::AssociationQueryValue)
90
+ ::ActiveRecord::PredicateBuilder::AssociationQueryValue.prepend(
91
+ ActiveRecord::PredicateBuilder::AssociationQueryValue
92
+ )
93
+ ::ActiveRecord::PredicateBuilder::PolymorphicArrayValue.prepend(
94
+ ActiveRecord::PredicateBuilder::AssociationQueryValue
95
+ )
82
96
 
83
97
  ::ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(ActiveRecord::Tasks::DatabaseTasks)
84
98
 
@@ -96,17 +110,18 @@ module Switchman
96
110
 
97
111
  ::ActiveRecord::ConnectionAdapters::TableDefinition.prepend(ActiveRecord::TableDefinition)
98
112
  end
99
- # Ensure that ActiveRecord::Base is always loaded before any app-level initializers can go try to load Switchman::Shard or we get a loop
113
+ # Ensure that ActiveRecord::Base is always loaded before any app-level
114
+ # initializers can go try to load Switchman::Shard or we get a loop
100
115
  ::ActiveRecord::Base
101
116
  end
102
117
 
103
- initializer 'switchman.error_patch', after: 'active_record.initialize_database' do
118
+ initializer "switchman.error_patch", after: "active_record.initialize_database" do
104
119
  ::ActiveSupport.on_load(:active_record) do
105
120
  ::StandardError.include(StandardError)
106
121
  end
107
122
  end
108
123
 
109
- initializer 'switchman.initialize_cache', before: :initialize_cache, after: 'active_record.initialize_database' do
124
+ initializer "switchman.initialize_cache", before: :initialize_cache, after: "active_record.initialize_database" do
110
125
  ::ActiveSupport::Cache.singleton_class.prepend(ActiveSupport::Cache::ClassMethods)
111
126
 
112
127
  # if we haven't already setup our cache map out-of-band, set it up from
@@ -130,11 +145,11 @@ module Switchman
130
145
  Switchman.config[:cache_map][::Rails.env] = value
131
146
  end
132
147
 
133
- middlewares = Switchman.config[:cache_map].values.map do |store|
148
+ middlewares = Switchman.config[:cache_map].values.filter_map do |store|
134
149
  store.middleware if store.respond_to?(:middleware)
135
- end.compact.uniq
150
+ end.uniq
136
151
  middlewares.each do |middleware|
137
- config.middleware.insert_before('Rack::Runtime', middleware)
152
+ config.middleware.insert_before("Rack::Runtime", middleware)
138
153
  end
139
154
 
140
155
  # prevent :initialize_cache from trying to (or needing to) set
@@ -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,7 +13,7 @@ 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'
16
+ arg_params = (RUBY_VERSION <= "2.8") ? "*args" : "*args, **kwargs"
17
17
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
18
18
  def #{method}(#{arg_params})
19
19
  db = Shard.current(connection_class_for_self).database_server
@@ -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
@@ -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
@@ -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
@@ -36,13 +36,15 @@ module Switchman
36
36
 
37
37
  # Now find the actual record, if it exists
38
38
  @default = begin
39
- find_cached('default_shard') { Shard.where(default: true).take } || default
39
+ find_cached("default_shard") { Shard.where(default: true).take } || default
40
40
  rescue
41
41
  default
42
42
  end
43
43
 
44
44
  # 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)
45
+ if @default.database_server.instance_variable_defined?(:@primary_shard)
46
+ @default.database_server.remove_instance_variable(:@primary_shard)
47
+ end
46
48
 
47
49
  # and finally, check for cached references to the default shard on the existing connection
48
50
  sharded_models.each do |klass|
@@ -75,28 +77,30 @@ module Switchman
75
77
  klass.current_switchman_shard != shard
76
78
 
77
79
  (activated_classes ||= []) << klass
78
- klass.connected_to_stack << { shard: shard.database_server.id.to_sym, klasses: [klass], switchman_shard: shard }
80
+ klass.connected_to_stack << { shard: shard.database_server.id.to_sym,
81
+ klasses: [klass],
82
+ switchman_shard: shard }
79
83
  end
80
84
  activated_classes
81
85
  end
82
86
 
83
87
  def active_shards
84
- sharded_models.map do |klass|
88
+ sharded_models.filter_map do |klass|
85
89
  [klass, current(klass)]
86
- end.compact.to_h
90
+ end.to_h
87
91
  end
88
92
 
89
93
  def lookup(id)
90
94
  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'
95
+ return current if id_i == current.id || id == "self"
96
+ return default if id_i == default.id || id.nil? || id == "default"
93
97
 
94
98
  id = id_i
95
99
  raise ArgumentError if id.zero?
96
100
 
97
101
  unless cached_shards.key?(id)
98
102
  cached_shards[id] = Shard.default.activate do
99
- find_cached(['shard', id]) { find_by(id: id) }
103
+ find_cached(["shard", id]) { find_by(id: id) }
100
104
  end
101
105
  end
102
106
  cached_shards[id]
@@ -140,13 +144,15 @@ module Switchman
140
144
  parallel = 0 if parallel == false || parallel.nil?
141
145
 
142
146
  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?
147
+ if ::ActiveRecord::Relation === scope && scope.order_values.empty?
148
+ scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
149
+ end
144
150
 
145
151
  if parallel > 1
146
152
  if ::ActiveRecord::Relation === scope
147
153
  # 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
154
+ database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
155
+ .filter_map(&:database_server).uniq
150
156
  # nothing to do
151
157
  return if database_servers.count.zero?
152
158
 
@@ -162,15 +168,15 @@ module Switchman
162
168
  # silly like dealloc'ing prepared statements)
163
169
  ::ActiveRecord::Base.clear_all_connections!
164
170
 
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|
171
+ parent_process_name = sanitized_process_title
172
+ ret = ::Parallel.map(scopes, in_processes: (scopes.length > 1) ? parallel : 0) do |server, subscope|
167
173
  name = server.id
168
174
  last_description = name
169
175
 
170
176
  begin
171
177
  max_length = 128 - name.length - 3
172
178
  short_parent_name = parent_process_name[0..max_length] if max_length >= 0
173
- new_title = [short_parent_name, name].join(' ')
179
+ new_title = [short_parent_name, name].join(" ")
174
180
  Process.setproctitle(new_title)
175
181
  Switchman.config[:on_fork_proc]&.call
176
182
  with_each_shard(subscope, classes, exception: exception, output: :decorated) do
@@ -187,8 +193,9 @@ module Switchman
187
193
  unless errors.empty?
188
194
  raise errors.first.exception if errors.length == 1
189
195
 
196
+ errors_desc = errors.map(&:name).sort.join(", ")
190
197
  raise Errors::ParallelShardExecError,
191
- "The following database server(s) did not finish processing cleanly: #{errors.map(&:name).sort.join(', ')}",
198
+ "The following database server(s) did not finish processing cleanly: #{errors_desc}",
192
199
  cause: errors.first.exception
193
200
  end
194
201
 
@@ -437,6 +444,39 @@ module Switchman
437
444
  shard = nil unless shard.database_server
438
445
  shard
439
446
  end
447
+
448
+ # Determines the name of the current process, including arguments, but stripping
449
+ # any shebang from the invoked script, and any additional path info from the
450
+ # executable.
451
+ #
452
+ # @return [String]
453
+ def sanitized_process_title
454
+ # get the effective process name from `ps`; this will include any changes
455
+ # from Process.setproctitle _or_ assigning to $0.
456
+ parent_process_name = `ps -ocommand= -p#{Process.pid}`.strip
457
+ # Effective process titles may be shorter than the actual
458
+ # command; truncate our ARGV[0] so that they are comparable
459
+ # for the next step
460
+ argv0 = if parent_process_name.length < Process.argv0.length
461
+ Process.argv0[0..parent_process_name.length]
462
+ else
463
+ Process.argv0
464
+ end
465
+
466
+ # when running via a shebang, the `ps` output will include the shebang
467
+ # (i.e. it will be "ruby bin/rails c"); attempt to strip it off.
468
+ # Note that argv0 in this case will _only_ be `bin/rails` (no shebang,
469
+ # no arguments). We want to preserve the arguments we got from `ps`
470
+ if (index = parent_process_name.index(argv0))
471
+ parent_process_name.slice!(0...index)
472
+ end
473
+
474
+ # remove directories from the main executable to make more room
475
+ # for additional info
476
+ argv = parent_process_name.shellsplit
477
+ argv[0] = File.basename(argv[0])
478
+ argv.shelljoin
479
+ end
440
480
  end
441
481
 
442
482
  def name
@@ -467,7 +507,7 @@ module Switchman
467
507
  end
468
508
 
469
509
  def description
470
- [database_server.id, name].compact.join(':')
510
+ [database_server.id, name].compact.join(":")
471
511
  end
472
512
 
473
513
  # Shards are always on the default shard
@@ -501,48 +541,39 @@ module Switchman
501
541
  end
502
542
 
503
543
  def drop_database
504
- raise('Cannot drop the database of the default shard') if default?
544
+ raise "Cannot drop the database of the default shard" if default?
505
545
  return unless read_attribute(:name)
506
546
 
507
547
  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) }
548
+ activate do
549
+ self.class.drop_database(name)
515
550
  end
551
+ rescue ::ActiveRecord::StatementInvalid => e
552
+ logger.error "Drop failed: #{e}"
553
+ end
554
+ end
516
555
 
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
556
+ #
557
+ # Drops a specific database/schema from the currently active connection
558
+ #
559
+ def self.drop_database(name)
560
+ sharding_config = Switchman.config || {}
561
+ drop_statement = sharding_config["postgresql"]&.[](:drop_statement)
562
+ drop_statement ||= sharding_config[:drop_statement]
563
+ drop_statement = Array(drop_statement).map { |statement| statement.gsub("%{name}", name) } if drop_statement
564
+
565
+ ::GuardRail.activate(:deploy) do
566
+ # Shut up, Postgres!
567
+ conn = ::ActiveRecord::Base.connection
568
+ old_proc = conn.raw_connection.set_notice_processor {}
569
+ begin
570
+ drop_statement ||= "DROP SCHEMA #{name} CASCADE"
571
+ Array(drop_statement).each do |stmt|
572
+ ::ActiveRecord::Base.connection.execute(stmt)
542
573
  end
574
+ ensure
575
+ conn.raw_connection.set_notice_processor(&old_proc) if old_proc
543
576
  end
544
- rescue
545
- logger.info "Drop failed: #{$!}"
546
577
  end
547
578
  end
548
579
 
@@ -561,7 +592,7 @@ module Switchman
561
592
  end
562
593
 
563
594
  def destroy
564
- raise('Cannot destroy the default shard') if default?
595
+ raise("Cannot destroy the default shard") if default?
565
596
 
566
597
  super
567
598
  end
@@ -570,8 +601,8 @@ module Switchman
570
601
 
571
602
  def clear_cache
572
603
  Shard.default.activate do
573
- Switchman.cache.delete(['shard', id].join('/'))
574
- Switchman.cache.delete('default_shard') if default?
604
+ Switchman.cache.delete(["shard", id].join("/"))
605
+ Switchman.cache.delete("default_shard") if default?
575
606
  end
576
607
  self.class.clear_cache
577
608
  end
@@ -16,7 +16,11 @@ 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
26
  super name, payload
@@ -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.5.3"
5
5
  end