departure 6.8.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.
@@ -0,0 +1,285 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ departure (6.8.0)
5
+ activerecord (>= 7.0.1)
6
+ mysql2 (>= 0.4.0, < 0.6.0)
7
+ railties (>= 7.0.1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actioncable (8.0.2.1)
13
+ actionpack (= 8.0.2.1)
14
+ activesupport (= 8.0.2.1)
15
+ nio4r (~> 2.0)
16
+ websocket-driver (>= 0.6.1)
17
+ zeitwerk (~> 2.6)
18
+ actionmailbox (8.0.2.1)
19
+ actionpack (= 8.0.2.1)
20
+ activejob (= 8.0.2.1)
21
+ activerecord (= 8.0.2.1)
22
+ activestorage (= 8.0.2.1)
23
+ activesupport (= 8.0.2.1)
24
+ mail (>= 2.8.0)
25
+ actionmailer (8.0.2.1)
26
+ actionpack (= 8.0.2.1)
27
+ actionview (= 8.0.2.1)
28
+ activejob (= 8.0.2.1)
29
+ activesupport (= 8.0.2.1)
30
+ mail (>= 2.8.0)
31
+ rails-dom-testing (~> 2.2)
32
+ actionpack (8.0.2.1)
33
+ actionview (= 8.0.2.1)
34
+ activesupport (= 8.0.2.1)
35
+ nokogiri (>= 1.8.5)
36
+ rack (>= 2.2.4)
37
+ rack-session (>= 1.0.1)
38
+ rack-test (>= 0.6.3)
39
+ rails-dom-testing (~> 2.2)
40
+ rails-html-sanitizer (~> 1.6)
41
+ useragent (~> 0.16)
42
+ actiontext (8.0.2.1)
43
+ actionpack (= 8.0.2.1)
44
+ activerecord (= 8.0.2.1)
45
+ activestorage (= 8.0.2.1)
46
+ activesupport (= 8.0.2.1)
47
+ globalid (>= 0.6.0)
48
+ nokogiri (>= 1.8.5)
49
+ actionview (8.0.2.1)
50
+ activesupport (= 8.0.2.1)
51
+ builder (~> 3.1)
52
+ erubi (~> 1.11)
53
+ rails-dom-testing (~> 2.2)
54
+ rails-html-sanitizer (~> 1.6)
55
+ activejob (8.0.2.1)
56
+ activesupport (= 8.0.2.1)
57
+ globalid (>= 0.3.6)
58
+ activemodel (8.0.2.1)
59
+ activesupport (= 8.0.2.1)
60
+ activerecord (8.0.2.1)
61
+ activemodel (= 8.0.2.1)
62
+ activesupport (= 8.0.2.1)
63
+ timeout (>= 0.4.0)
64
+ activestorage (8.0.2.1)
65
+ actionpack (= 8.0.2.1)
66
+ activejob (= 8.0.2.1)
67
+ activerecord (= 8.0.2.1)
68
+ activesupport (= 8.0.2.1)
69
+ marcel (~> 1.0)
70
+ activesupport (8.0.2.1)
71
+ base64
72
+ benchmark (>= 0.3)
73
+ bigdecimal
74
+ concurrent-ruby (~> 1.0, >= 1.3.1)
75
+ connection_pool (>= 2.2.5)
76
+ drb
77
+ i18n (>= 1.6, < 2)
78
+ logger (>= 1.4.2)
79
+ minitest (>= 5.1)
80
+ securerandom (>= 0.3)
81
+ tzinfo (~> 2.0, >= 2.0.5)
82
+ uri (>= 0.13.1)
83
+ appraisal (2.4.1)
84
+ bundler
85
+ rake
86
+ thor (>= 0.14.0)
87
+ ast (2.4.3)
88
+ base64 (0.2.0)
89
+ benchmark (0.4.0)
90
+ bigdecimal (3.1.9)
91
+ builder (3.3.0)
92
+ byebug (12.0.0)
93
+ climate_control (0.0.4)
94
+ activesupport (>= 3.0)
95
+ codeclimate-test-reporter (1.0.9)
96
+ simplecov (<= 0.13)
97
+ coderay (1.1.3)
98
+ concurrent-ruby (1.3.5)
99
+ connection_pool (2.5.0)
100
+ crass (1.0.6)
101
+ date (3.4.1)
102
+ diff-lcs (1.6.1)
103
+ docile (1.1.5)
104
+ drb (2.2.1)
105
+ erubi (1.13.1)
106
+ globalid (1.2.1)
107
+ activesupport (>= 6.1)
108
+ i18n (1.14.7)
109
+ concurrent-ruby (~> 1.0)
110
+ io-console (0.8.0)
111
+ irb (1.15.2)
112
+ pp (>= 0.6.0)
113
+ rdoc (>= 4.0.0)
114
+ reline (>= 0.4.2)
115
+ json (2.10.2)
116
+ language_server-protocol (3.17.0.4)
117
+ lhm (2.2.0)
118
+ lint_roller (1.1.0)
119
+ logger (1.7.0)
120
+ loofah (2.24.0)
121
+ crass (~> 1.0.2)
122
+ nokogiri (>= 1.12.0)
123
+ mail (2.8.1)
124
+ mini_mime (>= 0.1.1)
125
+ net-imap
126
+ net-pop
127
+ net-smtp
128
+ marcel (1.0.4)
129
+ method_source (1.1.0)
130
+ mini_mime (1.1.5)
131
+ minitest (5.25.5)
132
+ mutex_m (0.3.0)
133
+ mysql2 (0.5.6)
134
+ net-imap (0.5.6)
135
+ date
136
+ net-protocol
137
+ net-pop (0.1.2)
138
+ net-protocol
139
+ net-protocol (0.2.2)
140
+ timeout
141
+ net-smtp (0.5.1)
142
+ net-protocol
143
+ nio4r (2.7.4)
144
+ nokogiri (1.18.7-x86_64-linux-gnu)
145
+ racc (~> 1.4)
146
+ parallel (1.26.3)
147
+ parser (3.3.7.4)
148
+ ast (~> 2.4.1)
149
+ racc
150
+ pp (0.6.2)
151
+ prettyprint
152
+ prettyprint (0.2.0)
153
+ prism (1.4.0)
154
+ pry (0.15.2)
155
+ coderay (~> 1.1)
156
+ method_source (~> 1.0)
157
+ pry-byebug (3.11.0)
158
+ byebug (~> 12.0)
159
+ pry (>= 0.13, < 0.16)
160
+ psych (5.2.3)
161
+ date
162
+ stringio
163
+ racc (1.8.1)
164
+ rack (3.1.12)
165
+ rack-session (2.1.0)
166
+ base64 (>= 0.1.0)
167
+ rack (>= 3.0.0)
168
+ rack-test (2.2.0)
169
+ rack (>= 1.3)
170
+ rackup (2.2.1)
171
+ rack (>= 3)
172
+ rails (8.0.2.1)
173
+ actioncable (= 8.0.2.1)
174
+ actionmailbox (= 8.0.2.1)
175
+ actionmailer (= 8.0.2.1)
176
+ actionpack (= 8.0.2.1)
177
+ actiontext (= 8.0.2.1)
178
+ actionview (= 8.0.2.1)
179
+ activejob (= 8.0.2.1)
180
+ activemodel (= 8.0.2.1)
181
+ activerecord (= 8.0.2.1)
182
+ activestorage (= 8.0.2.1)
183
+ activesupport (= 8.0.2.1)
184
+ bundler (>= 1.15.0)
185
+ railties (= 8.0.2.1)
186
+ rails-dom-testing (2.2.0)
187
+ activesupport (>= 5.0.0)
188
+ minitest
189
+ nokogiri (>= 1.6)
190
+ rails-html-sanitizer (1.6.2)
191
+ loofah (~> 2.21)
192
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
193
+ railties (8.0.2.1)
194
+ actionpack (= 8.0.2.1)
195
+ activesupport (= 8.0.2.1)
196
+ irb (~> 1.13)
197
+ rackup (>= 1.0.0)
198
+ rake (>= 12.2)
199
+ thor (~> 1.0, >= 1.2.2)
200
+ zeitwerk (~> 2.6)
201
+ rainbow (3.1.1)
202
+ rake (13.2.1)
203
+ rdoc (6.13.1)
204
+ psych (>= 4.0.0)
205
+ regexp_parser (2.10.0)
206
+ reline (0.6.0)
207
+ io-console (~> 0.5)
208
+ rspec (3.13.0)
209
+ rspec-core (~> 3.13.0)
210
+ rspec-expectations (~> 3.13.0)
211
+ rspec-mocks (~> 3.13.0)
212
+ rspec-core (3.13.3)
213
+ rspec-support (~> 3.13.0)
214
+ rspec-expectations (3.13.3)
215
+ diff-lcs (>= 1.2.0, < 2.0)
216
+ rspec-support (~> 3.13.0)
217
+ rspec-its (1.3.1)
218
+ rspec-core (>= 3.0.0)
219
+ rspec-expectations (>= 3.0.0)
220
+ rspec-mocks (3.13.2)
221
+ diff-lcs (>= 1.2.0, < 2.0)
222
+ rspec-support (~> 3.13.0)
223
+ rspec-support (3.13.2)
224
+ rubocop (1.74.0)
225
+ json (~> 2.3)
226
+ language_server-protocol (~> 3.17.0.2)
227
+ lint_roller (~> 1.1.0)
228
+ parallel (~> 1.10)
229
+ parser (>= 3.3.0.2)
230
+ rainbow (>= 2.2.2, < 4.0)
231
+ regexp_parser (>= 2.9.3, < 3.0)
232
+ rubocop-ast (>= 1.38.0, < 2.0)
233
+ ruby-progressbar (~> 1.7)
234
+ unicode-display_width (>= 2.4.0, < 4.0)
235
+ rubocop-ast (1.44.0)
236
+ parser (>= 3.3.7.2)
237
+ prism (~> 1.4)
238
+ rubocop-performance (1.20.2)
239
+ rubocop (>= 1.48.1, < 2.0)
240
+ rubocop-ast (>= 1.30.0, < 2.0)
241
+ ruby-progressbar (1.13.0)
242
+ securerandom (0.4.1)
243
+ simplecov (0.13.0)
244
+ docile (~> 1.1.0)
245
+ json (>= 1.8, < 3)
246
+ simplecov-html (~> 0.10.0)
247
+ simplecov-html (0.10.2)
248
+ stringio (3.1.6)
249
+ thor (1.3.2)
250
+ timeout (0.4.3)
251
+ tzinfo (2.0.6)
252
+ concurrent-ruby (~> 1.0)
253
+ unicode-display_width (3.1.4)
254
+ unicode-emoji (~> 4.0, >= 4.0.4)
255
+ unicode-emoji (4.0.4)
256
+ uri (1.0.3)
257
+ useragent (0.16.11)
258
+ websocket-driver (0.7.7)
259
+ base64
260
+ websocket-extensions (>= 0.1.0)
261
+ websocket-extensions (0.1.5)
262
+ zeitwerk (2.7.2)
263
+
264
+ PLATFORMS
265
+ x86_64-linux
266
+
267
+ DEPENDENCIES
268
+ appraisal (~> 2.4.1)
269
+ base64
270
+ climate_control (~> 0.0.3)
271
+ codeclimate-test-reporter (~> 1.0.3)
272
+ departure!
273
+ lhm
274
+ logger
275
+ mutex_m
276
+ pry-byebug
277
+ rails (= 8.0.2.1)
278
+ rake (>= 10.0)
279
+ rspec (~> 3.4, >= 3.4.0)
280
+ rspec-its (~> 1.2)
281
+ rubocop (~> 1.74.0)
282
+ rubocop-performance (~> 1.20.2)
283
+
284
+ BUNDLED WITH
285
+ 2.6.6
@@ -52,27 +52,14 @@ module ForAlterStatements
52
52
  end
53
53
 
54
54
  def add_index_for_alter(table_name, column_name, options = {})
55
- if ActiveRecord::VERSION::STRING >= '6.1'
56
- index_definition, = add_index_options(table_name, column_name, **options)
55
+ index_definition, = add_index_options(table_name, column_name, **options)
57
56
 
58
- "ADD #{schema_creation.accept(index_definition)}"
59
- else
60
- index_name, index_type, index_columns, _,
61
- index_algorithm, index_using = add_index_options(table_name, column_name, **options)
62
- index_algorithm[0, 0] = ', ' if index_algorithm.present?
63
-
64
- "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
65
- end
57
+ "ADD #{schema_creation.accept(index_definition)}"
66
58
  end
67
59
 
68
60
  def remove_index_for_alter(table_name, column_name = nil, **options)
69
- index_name =
70
- if ActiveRecord::VERSION::STRING >= '6.1'
71
- index_name_for_remove(table_name, column_name, options)
72
- else
73
- options = [column_name, options] if column_name
74
- index_name_for_remove(table_name, options)
75
- end
61
+ index_name = index_name_for_remove(table_name, column_name, options)
62
+
76
63
  "DROP INDEX #{quote_column_name(index_name)}"
77
64
  end
78
65
 
@@ -99,20 +99,11 @@ module ActiveRecord
99
99
  # @param column_name [String, Symbol]
100
100
  # @param options [Hash] optional
101
101
  def add_index(table_name, column_name, options = {})
102
- if ActiveRecord::VERSION::STRING >= '6.1'
103
- index_definition, = add_index_options(table_name, column_name, **options)
104
- execute <<-SQL.squish
105
- ALTER TABLE #{quote_table_name(index_definition.table)}
106
- ADD #{schema_creation.accept(index_definition)}
107
- SQL
108
- else
109
- index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, **options)
110
- execute <<-SQL.squish
111
- ALTER TABLE #{quote_table_name(table_name)}
112
- ADD #{index_type} INDEX
113
- #{quote_column_name(index_name)} (#{index_columns})#{index_options}
114
- SQL
115
- 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
116
107
  end
117
108
 
118
109
  # Remove the given index from the table.
@@ -120,12 +111,8 @@ module ActiveRecord
120
111
  # @param table_name [String, Symbol]
121
112
  # @param options [Hash] optional
122
113
  def remove_index(table_name, column_name = nil, **options)
123
- if ActiveRecord::VERSION::STRING >= '6.1'
124
- return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
125
- index_name = index_name_for_remove(table_name, column_name, options)
126
- else
127
- index_name = index_name_for_remove(table_name, options)
128
- 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)
129
116
 
130
117
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP INDEX #{quote_column_name(index_name)}"
131
118
  end
@@ -166,7 +153,7 @@ module ActiveRecord
166
153
 
167
154
  attr_reader :mysql_adapter
168
155
 
169
- if ActiveRecord.version >= Gem::Version.create('7.1.0')
156
+ if Departure::RailsAdapter.version_matches?(ActiveRecord.version, '~> 7.1.0')
170
157
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
171
158
  log(sql, name, async: async) do
172
159
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
@@ -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
@@ -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