departure 6.7.0 → 7.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/.github/workflows/test.yml +5 -14
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +1 -1
  5. data/Appraisals +4 -24
  6. data/CHANGELOG.md +15 -1
  7. data/Gemfile +6 -1
  8. data/Gemfile.lock +108 -86
  9. data/README.md +8 -0
  10. data/Rakefile +9 -0
  11. data/bin/rails +24 -0
  12. data/departure.gemspec +3 -3
  13. data/gemfiles/rails_7_0.gemfile +8 -2
  14. data/gemfiles/rails_7_0.gemfile.lock +182 -131
  15. data/gemfiles/rails_7_1.gemfile +7 -2
  16. data/gemfiles/rails_7_1.gemfile.lock +156 -140
  17. data/gemfiles/{ruby_2.7_rails_6_0.gemfile → rails_7_2.gemfile} +7 -3
  18. data/gemfiles/rails_7_2.gemfile.lock +288 -0
  19. data/gemfiles/{rails_6_0.gemfile → rails_8_0.gemfile} +6 -2
  20. data/gemfiles/rails_8_0.gemfile.lock +285 -0
  21. data/lib/active_record/connection_adapters/for_alter.rb +4 -17
  22. data/lib/active_record/connection_adapters/patch_connection_handling.rb +18 -0
  23. data/lib/active_record/connection_adapters/percona_adapter.rb +9 -56
  24. data/lib/active_record/connection_adapters/rails_7_2_departure_adapter.rb +215 -0
  25. data/lib/active_record/connection_adapters/rails_8_0_departure_adapter.rb +293 -0
  26. data/lib/departure/command.rb +6 -2
  27. data/lib/departure/configuration.rb +2 -1
  28. data/lib/departure/migration.rb +1 -5
  29. data/lib/departure/rails_adapter.rb +146 -0
  30. data/lib/departure/rails_patches/active_record_migrator_with_advisory_lock_patch.rb +25 -0
  31. data/lib/departure/runner.rb +24 -4
  32. data/lib/departure/version.rb +1 -1
  33. data/lib/departure.rb +2 -5
  34. data/lib/lhm/column_with_sql.rb +1 -8
  35. data/test_database.rb +2 -6
  36. metadata +18 -47
  37. data/gemfiles/rails_6_0.gemfile.lock +0 -238
  38. data/gemfiles/rails_6_1.gemfile +0 -10
  39. data/gemfiles/rails_6_1.gemfile.lock +0 -241
  40. data/gemfiles/ruby_2.7_rails_6_0.gemfile.lock +0 -239
  41. data/gemfiles/ruby_2.7_rails_6_1.gemfile +0 -11
  42. data/gemfiles/ruby_2.7_rails_6_1.gemfile.lock +0 -242
  43. data/gemfiles/ruby_2.7_rails_7_0.gemfile +0 -11
  44. data/gemfiles/ruby_2.7_rails_7_0.gemfile.lock +0 -241
  45. data/gemfiles/ruby_2.7_rails_7_1.gemfile +0 -11
  46. data/gemfiles/ruby_2.7_rails_7_1.gemfile.lock +0 -275
@@ -1,46 +1,12 @@
1
1
  require 'active_record/connection_adapters/abstract_mysql_adapter'
2
2
  require 'active_record/connection_adapters/statement_pool'
3
3
  require 'active_record/connection_adapters/mysql2_adapter'
4
+ require 'active_record/connection_adapters/patch_connection_handling'
4
5
  require 'active_support/core_ext/string/filters'
5
6
  require 'departure'
6
7
  require 'forwardable'
7
8
 
8
9
  module ActiveRecord
9
- module ConnectionHandling
10
- # Establishes a connection to the database that's used by all Active
11
- # Record objects.
12
- def percona_connection(config)
13
- if config[:username].nil?
14
- config = config.dup if config.frozen?
15
- config[:username] = 'root'
16
- end
17
- mysql2_connection = mysql2_connection(config)
18
-
19
- connection_details = Departure::ConnectionDetails.new(config)
20
- verbose = ActiveRecord::Migration.verbose
21
- sanitizers = [
22
- Departure::LogSanitizers::PasswordSanitizer.new(connection_details)
23
- ]
24
- percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
25
- cli_generator = Departure::CliGenerator.new(connection_details)
26
-
27
- runner = Departure::Runner.new(
28
- percona_logger,
29
- cli_generator,
30
- mysql2_connection
31
- )
32
-
33
- connection_options = { mysql_adapter: mysql2_connection }
34
-
35
- ConnectionAdapters::DepartureAdapter.new(
36
- runner,
37
- logger,
38
- connection_options,
39
- config
40
- )
41
- end
42
- end
43
-
44
10
  module ConnectionAdapters
45
11
  class DepartureAdapter < AbstractMysqlAdapter
46
12
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
@@ -133,20 +99,11 @@ module ActiveRecord
133
99
  # @param column_name [String, Symbol]
134
100
  # @param options [Hash] optional
135
101
  def add_index(table_name, column_name, options = {})
136
- if ActiveRecord::VERSION::STRING >= '6.1'
137
- index_definition, = add_index_options(table_name, column_name, **options)
138
- execute <<-SQL.squish
139
- ALTER TABLE #{quote_table_name(index_definition.table)}
140
- ADD #{schema_creation.accept(index_definition)}
141
- SQL
142
- else
143
- index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, **options)
144
- execute <<-SQL.squish
145
- ALTER TABLE #{quote_table_name(table_name)}
146
- ADD #{index_type} INDEX
147
- #{quote_column_name(index_name)} (#{index_columns})#{index_options}
148
- SQL
149
- end
102
+ index_definition, = add_index_options(table_name, column_name, **options)
103
+ execute <<-SQL.squish
104
+ ALTER TABLE #{quote_table_name(index_definition.table)}
105
+ ADD #{schema_creation.accept(index_definition)}
106
+ SQL
150
107
  end
151
108
 
152
109
  # Remove the given index from the table.
@@ -154,12 +111,8 @@ module ActiveRecord
154
111
  # @param table_name [String, Symbol]
155
112
  # @param options [Hash] optional
156
113
  def remove_index(table_name, column_name = nil, **options)
157
- if ActiveRecord::VERSION::STRING >= '6.1'
158
- return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
159
- index_name = index_name_for_remove(table_name, column_name, options)
160
- else
161
- index_name = index_name_for_remove(table_name, options)
162
- end
114
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
115
+ index_name = index_name_for_remove(table_name, column_name, options)
163
116
 
164
117
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
165
118
  end
@@ -200,7 +153,7 @@ module ActiveRecord
200
153
 
201
154
  attr_reader :mysql_adapter
202
155
 
203
- if ActiveRecord.version >= Gem::Version.create('7.1.0')
156
+ if Departure::RailsAdapter.version_matches?(ActiveRecord.version, '~> 7.1.0')
204
157
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
205
158
  log(sql, name, async: async) do
206
159
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
@@ -0,0 +1,215 @@
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_record/connection_adapters/mysql2_adapter'
4
+ require 'active_record/connection_adapters/patch_connection_handling'
5
+ require 'active_support/core_ext/string/filters'
6
+ require 'departure'
7
+ require 'forwardable'
8
+
9
+ module ActiveRecord
10
+ module ConnectionAdapters
11
+ class Rails72DepartureAdapter < AbstractMysqlAdapter
12
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
13
+
14
+ class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
15
+ def adapter
16
+ Rails72DepartureAdapter
17
+ end
18
+ end
19
+
20
+ class SchemaCreation < ActiveRecord::ConnectionAdapters::MySQL::SchemaCreation
21
+ def visit_DropForeignKey(name) # rubocop:disable Style/MethodName
22
+ fk_name =
23
+ if name =~ /^__(.+)/
24
+ Regexp.last_match(1)
25
+ else
26
+ "_#{name}"
27
+ end
28
+
29
+ "DROP FOREIGN KEY #{fk_name}"
30
+ end
31
+ end
32
+
33
+ extend Forwardable
34
+
35
+ include ForAlterStatements unless method_defined?(:change_column_for_alter)
36
+
37
+ ADAPTER_NAME = 'Percona'.freeze
38
+
39
+ def self.new_client(config)
40
+ connection_details = Departure::ConnectionDetails.new(config)
41
+ verbose = ActiveRecord::Migration.verbose
42
+ sanitizers = [
43
+ Departure::LogSanitizers::PasswordSanitizer.new(connection_details)
44
+ ]
45
+ percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
46
+ cli_generator = Departure::CliGenerator.new(connection_details)
47
+
48
+ mysql_adapter = ActiveRecord::ConnectionAdapters::Mysql2Adapter.new(config.merge(adapter: 'mysql2'))
49
+
50
+ Departure::Runner.new(
51
+ percona_logger,
52
+ cli_generator,
53
+ mysql_adapter
54
+ )
55
+ end
56
+
57
+ def initialize(config)
58
+ super
59
+
60
+ @config[:flags] ||= 0
61
+
62
+ if @config[:flags].is_a? Array
63
+ @config[:flags].push 'FOUND_ROWS'
64
+ else
65
+ @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
66
+ end
67
+
68
+ @prepared_statements = false
69
+ end
70
+
71
+ def write_query?(sql) # :nodoc:
72
+ !ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
73
+ :desc, :describe, :set, :show, :use
74
+ ).match?(sql)
75
+ end
76
+
77
+ def exec_delete(sql, name, binds)
78
+ execute(to_sql(sql, binds), name)
79
+
80
+ @raw_connection.affected_rows
81
+ end
82
+ alias exec_update exec_delete
83
+
84
+ def exec_insert(sql, name, binds, pky = nil, sequence_name = nil, returning: nil) # rubocop:disable Lint/UnusedMethodArgument, Metrics/Metrics/ParameterLists
85
+ execute(to_sql(sql, binds), name)
86
+ end
87
+
88
+ def internal_exec_query(sql, name = 'SQL', _binds = [], **_kwargs) # :nodoc:
89
+ result = execute(sql, name)
90
+ fields = result.fields if defined?(result.fields)
91
+ ActiveRecord::Result.new(fields || [], result.to_a)
92
+ end
93
+ alias exec_query internal_exec_query
94
+
95
+ # Executes a SELECT query and returns an array of rows. Each row is an
96
+ # array of field values.
97
+
98
+ def select_rows(arel, name = nil, binds = [])
99
+ select_all(arel, name, binds).rows
100
+ end
101
+
102
+ # Executes a SELECT query and returns an array of record hashes with the
103
+ # column names as keys and column values as values.
104
+ def select(sql, name = nil, binds = [], **kwargs)
105
+ exec_query(sql, name, binds, **kwargs)
106
+ end
107
+
108
+ # Returns true, as this adapter supports migrations
109
+ def supports_migrations?
110
+ true
111
+ end
112
+
113
+ def new_column(...)
114
+ Column.new(...)
115
+ end
116
+
117
+ # Adds a new index to the table
118
+ #
119
+ # @param table_name [String, Symbol]
120
+ # @param column_name [String, Symbol]
121
+ # @param options [Hash] optional
122
+ def add_index(table_name, column_name, options = {})
123
+ index_definition, = add_index_options(table_name, column_name, **options)
124
+ execute <<-SQL.squish
125
+ ALTER TABLE #{quote_table_name(index_definition.table)}
126
+ ADD #{schema_creation.accept(index_definition)}
127
+ SQL
128
+ end
129
+
130
+ # Remove the given index from the table.
131
+ #
132
+ # @param table_name [String, Symbol]
133
+ # @param options [Hash] optional
134
+ def remove_index(table_name, column_name = nil, **options)
135
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
136
+
137
+ index_name = index_name_for_remove(table_name, column_name, options)
138
+
139
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
140
+ end
141
+
142
+ def schema_creation
143
+ SchemaCreation.new(self)
144
+ end
145
+
146
+ def change_table(table_name, _options = {})
147
+ recorder = ActiveRecord::Migration::CommandRecorder.new(self)
148
+ yield update_table_definition(table_name, recorder)
149
+ bulk_change_table(table_name, recorder.commands)
150
+ end
151
+
152
+ def full_version
153
+ get_full_version
154
+ end
155
+
156
+ def get_full_version # rubocop:disable Style/AccessorMethodName
157
+ return @get_full_version if defined? @get_full_version
158
+
159
+ with_raw_connection do |conn|
160
+ @get_full_version = conn.database_adapter.get_database_version.full_version_string
161
+ end
162
+ end
163
+
164
+ def last_inserted_id(result)
165
+ @raw_connection.database_adapter.send(:last_inserted_id, result)
166
+ end
167
+
168
+ private
169
+
170
+ attr_reader :mysql_adapter
171
+
172
+ def each_hash(result, &block) # :nodoc:
173
+ @raw_connection.database_adapter.each_hash(result, &block)
174
+ end
175
+
176
+ # Must return the MySQL error number from the exception, if the exception has an
177
+ # error number.
178
+ def error_number(exception)
179
+ @raw_connection.database_adapter.error_number(exception)
180
+ end
181
+
182
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
183
+ log(sql, name, async: async) do |notification_payload|
184
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
185
+ sync_timezone_changes(conn)
186
+ result = conn.query(sql)
187
+ verified! if allow_retry
188
+ handle_warnings(sql)
189
+ if result.is_a? Process::Status
190
+ notification_payload[:exit_code] = result.exitstatus
191
+ notification_payload[:exit_pid] = result.pid
192
+ elsif result.respond_to?(:size)
193
+ notification_payload[:row_count] = result.size
194
+ end
195
+ result
196
+ end
197
+ end
198
+ end
199
+
200
+ def connect
201
+ @raw_connection = self.class.new_client(@config)
202
+ rescue ConnectionNotEstablished => e
203
+ raise e.set_pool(@pool)
204
+ end
205
+
206
+ def reconnect
207
+ @lock.synchronize do
208
+ @raw_connection&.close
209
+ @raw_connection = nil
210
+ connect
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,293 @@
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_record/connection_adapters/mysql2_adapter'
4
+ require 'active_record/connection_adapters/mysql2/database_statements'
5
+ require 'active_record/connection_adapters/patch_connection_handling'
6
+ require 'active_support/core_ext/string/filters'
7
+ require 'departure'
8
+ require 'forwardable'
9
+
10
+ module ActiveRecord
11
+ module ConnectionAdapters
12
+ class Rails80DepartureAdapter < AbstractMysqlAdapter
13
+ include ActiveRecord::ConnectionAdapters::Mysql2::DatabaseStatements
14
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) } if defined?(initialize_type_map)
15
+
16
+ class Column < ActiveRecord::ConnectionAdapters::MySQL::Column
17
+ def adapter
18
+ Rails80DepartureAdapter
19
+ end
20
+ end
21
+
22
+ class SchemaCreation < ActiveRecord::ConnectionAdapters::MySQL::SchemaCreation
23
+ def visit_DropForeignKey(name) # rubocop:disable Naming/MethodName
24
+ fk_name =
25
+ if name =~ /^__(.+)/
26
+ Regexp.last_match(1)
27
+ else
28
+ "_#{name}"
29
+ end
30
+
31
+ "DROP FOREIGN KEY #{fk_name}"
32
+ end
33
+ end
34
+
35
+ extend Forwardable
36
+
37
+ include ForAlterStatements unless method_defined?(:change_column_for_alter)
38
+
39
+ ADAPTER_NAME = 'Percona'.freeze
40
+
41
+ def self.new_client(config)
42
+ connection_details = Departure::ConnectionDetails.new(config)
43
+ verbose = ActiveRecord::Migration.verbose
44
+ sanitizers = [
45
+ Departure::LogSanitizers::PasswordSanitizer.new(connection_details)
46
+ ]
47
+ percona_logger = Departure::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
48
+ cli_generator = Departure::CliGenerator.new(connection_details)
49
+
50
+ mysql_adapter = ActiveRecord::ConnectionAdapters::Mysql2Adapter.new(config.merge(adapter: 'mysql2'))
51
+
52
+ Departure::Runner.new(
53
+ percona_logger,
54
+ cli_generator,
55
+ mysql_adapter
56
+ )
57
+ end
58
+
59
+ def initialize(config)
60
+ super
61
+
62
+ @config[:flags] ||= 0
63
+
64
+ if @config[:flags].is_a? Array
65
+ @config[:flags].push 'FOUND_ROWS'
66
+ else
67
+ @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
68
+ end
69
+
70
+ @prepared_statements = false
71
+ end
72
+
73
+ def write_query?(sql) # :nodoc:
74
+ !ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
75
+ :desc, :describe, :set, :show, :use
76
+ ).match?(sql)
77
+ end
78
+
79
+ def exec_delete(sql, name, binds)
80
+ execute(to_sql(sql, binds), name)
81
+
82
+ @raw_connection.affected_rows
83
+ end
84
+ alias exec_update exec_delete
85
+
86
+ def exec_insert(sql, name, binds, pky = nil, sequence_name = nil, returning: nil) # rubocop:disable Lint/UnusedMethodArgument, Metrics/ParameterLists
87
+ execute(to_sql(sql, binds), name)
88
+ end
89
+
90
+ def internal_exec_query(sql, name = 'SQL', _binds = [], **_kwargs) # :nodoc:
91
+ result = execute(sql, name)
92
+ fields = result.fields if defined?(result.fields)
93
+ ActiveRecord::Result.new(fields || [], result.to_a)
94
+ end
95
+ alias exec_query internal_exec_query
96
+
97
+ # Executes a SELECT query and returns an array of rows. Each row is an
98
+ # array of field values.
99
+
100
+ def select_rows(arel, name = nil, binds = [])
101
+ select_all(arel, name, binds).rows
102
+ end
103
+
104
+ # Executes a SELECT query and returns an array of record hashes with the
105
+ # column names as keys and column values as values.
106
+ def select(sql, name = nil, binds = [], **kwargs)
107
+ exec_query(sql, name, binds, **kwargs)
108
+ end
109
+
110
+ # Returns true, as this adapter supports migrations
111
+ def supports_migrations?
112
+ true
113
+ end
114
+
115
+ def new_column(...)
116
+ Column.new(...)
117
+ end
118
+
119
+ # Adds a new index to the table
120
+ #
121
+ # @param table_name [String, Symbol]
122
+ # @param column_name [String, Symbol]
123
+ # @param options [Hash] optional
124
+ def add_index(table_name, column_name, options = {})
125
+ index_definition, = add_index_options(table_name, column_name, **options)
126
+ execute <<-SQL.squish
127
+ ALTER TABLE #{quote_table_name(index_definition.table)}
128
+ ADD #{schema_creation.accept(index_definition)}
129
+ SQL
130
+ end
131
+
132
+ # Remove the given index from the table.
133
+ #
134
+ # @param table_name [String, Symbol]
135
+ # @param options [Hash] optional
136
+ def remove_index(table_name, column_name = nil, **options)
137
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
138
+
139
+ index_name = index_name_for_remove(table_name, column_name, options)
140
+
141
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
142
+ end
143
+
144
+ def schema_creation
145
+ SchemaCreation.new(self)
146
+ end
147
+
148
+ def change_table(table_name, _options = {})
149
+ recorder = ActiveRecord::Migration::CommandRecorder.new(self)
150
+ yield update_table_definition(table_name, recorder)
151
+ bulk_change_table(table_name, recorder.commands)
152
+ end
153
+
154
+ def full_version
155
+ get_full_version
156
+ end
157
+
158
+ def get_full_version # rubocop:disable Naming/AccessorMethodName
159
+ return @get_full_version if defined? @get_full_version
160
+
161
+ with_raw_connection do |conn|
162
+ @get_full_version = conn.database_adapter.get_database_version.full_version_string
163
+ end
164
+ end
165
+
166
+ def last_inserted_id(result)
167
+ @raw_connection.database_adapter.send(:last_inserted_id, result)
168
+ end
169
+
170
+ private
171
+
172
+ attr_reader :mysql_adapter
173
+
174
+ def each_hash(result, &block) # :nodoc:
175
+ @raw_connection.database_adapter.each_hash(result, &block)
176
+ end
177
+
178
+ # Must return the MySQL error number from the exception, if the exception has an
179
+ # error number.
180
+ def error_number(exception)
181
+ @raw_connection.database_adapter.error_number(exception)
182
+ end
183
+
184
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/ParameterLists
185
+ def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false,
186
+ materialize_transactions: true, batch: false)
187
+ type_casted_binds = type_casted_binds(binds)
188
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
189
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
190
+ perform_query(conn, sql, binds, type_casted_binds, prepare: prepare,
191
+ notification_payload: notification_payload, batch: batch)
192
+ end
193
+ end
194
+ end
195
+
196
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
197
+ reset_multi_statement = if batch && !multi_statements_enabled?
198
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
199
+ true
200
+ end
201
+
202
+ # Make sure we carry over any changes to ActiveRecord.default_timezone that have been
203
+ # made since we established the connection
204
+ raw_connection.query_options[:database_timezone] = default_timezone
205
+
206
+ result = nil
207
+ if binds.nil? || binds.empty?
208
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
209
+ result = raw_connection.query(sql)
210
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
211
+ # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
212
+ # from that same connection was GCed while `#query` released the GVL.
213
+ # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
214
+ # of hitting the bug.
215
+
216
+ # THIS IS THE CORE CHANGES 1 related to size
217
+ # We will sometimes have a process exit code instead of a result from executing
218
+ @affected_rows_before_warnings = result.try(:size) || raw_connection.affected_rows
219
+ end
220
+ elsif prepare
221
+
222
+ stmt = @statements[sql] ||= raw_connection.prepare(sql)
223
+ begin
224
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
225
+ result = stmt.execute(*type_casted_binds)
226
+ @affected_rows_before_warnings = stmt.affected_rows
227
+ end
228
+ rescue ::Mysql2::Error
229
+ @statements.delete(sql)
230
+ raise
231
+ end
232
+ else
233
+ stmt = raw_connection.prepare(sql)
234
+
235
+ begin
236
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
237
+ result = stmt.execute(*type_casted_binds)
238
+ @affected_rows_before_warnings = stmt.affected_rows
239
+ end
240
+
241
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
242
+ # by eagerly closing uncached prepared statements, we also reduce the chances of
243
+ # that bug happening. It can still happen if `#execute` is used as we have no callback
244
+ # to eagerly close the statement.
245
+ if result
246
+ result.instance_variable_set(:@_ar_stmt_to_close, stmt)
247
+ else
248
+ stmt.close
249
+ end
250
+ rescue ::Mysql2::Error
251
+ stmt.close
252
+ raise
253
+ end
254
+ end
255
+
256
+ notification_payload[:affected_rows] = @affected_rows_before_warnings
257
+
258
+ # THIS IS THE CORE CHANGES 2 related to size
259
+ notification_payload[:row_count] = result.try(:size) || 0
260
+
261
+ if result.is_a? Process::Status
262
+ notification_payload[:exit_code] = result.exitstatus
263
+ notification_payload[:exit_pid] = result.pid
264
+ end
265
+
266
+ raw_connection.abandon_results!
267
+
268
+ verified!
269
+ handle_warnings(sql)
270
+ result
271
+ ensure
272
+ if reset_multi_statement && active?
273
+ raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
274
+ end
275
+ end
276
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/ParameterLists
277
+
278
+ def connect
279
+ @raw_connection = self.class.new_client(@config)
280
+ rescue ConnectionNotEstablished => e
281
+ raise e.set_pool(@pool)
282
+ end
283
+
284
+ def reconnect
285
+ @lock.synchronize do
286
+ @raw_connection&.close
287
+ @raw_connection = nil
288
+ connect
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
@@ -42,7 +42,7 @@ module Departure
42
42
  # execution status
43
43
  def run_in_process
44
44
  Open3.popen3(full_command) do |_stdin, stdout, _stderr, waith_thr|
45
- begin
45
+ begin # rubocop:disable Style/RedundantBegin
46
46
  loop do
47
47
  IO.select([stdout])
48
48
  data = stdout.read_nonblock(8192)
@@ -83,7 +83,11 @@ module Departure
83
83
  #
84
84
  # @return [String]
85
85
  def error_message
86
- File.read(error_log_path)
86
+ if redirect_stderr
87
+ File.read(error_log_path)
88
+ else
89
+ ''
90
+ end
87
91
  end
88
92
 
89
93
  # Logs when the execution started
@@ -1,6 +1,7 @@
1
1
  module Departure
2
2
  class Configuration
3
- attr_accessor :tmp_path, :global_percona_args, :enabled_by_default, :redirect_stderr
3
+ attr_accessor :tmp_path, :global_percona_args, :enabled_by_default, :redirect_stderr,
4
+ :disable_rails_advisory_lock_patch
4
5
 
5
6
  def initialize
6
7
  @tmp_path = '.'.freeze
@@ -94,11 +94,7 @@ module Departure
94
94
  end
95
95
 
96
96
  private def configuration_hash
97
- if ActiveRecord::VERSION::STRING >= '6.1'
98
- ActiveRecord::Base.connection_db_config.configuration_hash
99
- else
100
- ActiveRecord::Base.connection_config
101
- end
97
+ ActiveRecord::Base.connection_db_config.configuration_hash
102
98
  end
103
99
  end
104
100
  end