strong_migrations 0.7.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,16 @@
1
1
  module StrongMigrations
2
2
  class Checker
3
+ include Checks
3
4
  include SafeMethods
4
5
 
5
- attr_accessor :direction, :transaction_disabled
6
+ attr_accessor :direction, :transaction_disabled, :timeouts_set
6
7
 
7
8
  def initialize(migration)
8
9
  @migration = migration
9
10
  @new_tables = []
10
11
  @safe = false
11
12
  @timeouts_set = false
12
- @lock_timeout_checked = false
13
+ @committed = false
13
14
  end
14
15
 
15
16
  def safety_assured
@@ -23,580 +24,174 @@ module StrongMigrations
23
24
  end
24
25
 
25
26
  def perform(method, *args)
27
+ check_version_supported
26
28
  set_timeouts
27
29
  check_lock_timeout
28
30
 
29
31
  if !safe? || safe_by_default_method?(method)
32
+ # TODO better pattern
33
+ # see checks.rb for methods
30
34
  case method
31
- when :remove_column, :remove_columns, :remove_timestamps, :remove_reference, :remove_belongs_to
32
- columns =
33
- case method
34
- when :remove_timestamps
35
- ["created_at", "updated_at"]
36
- when :remove_column
37
- [args[1].to_s]
38
- when :remove_columns
39
- args[1..-1].map(&:to_s)
40
- else
41
- options = args[2] || {}
42
- reference = args[1]
43
- cols = []
44
- cols << "#{reference}_type" if options[:polymorphic]
45
- cols << "#{reference}_id"
46
- cols
47
- end
48
-
49
- code = "self.ignored_columns = #{columns.inspect}"
50
-
51
- raise_error :remove_column,
52
- model: args[0].to_s.classify,
53
- code: code,
54
- command: command_str(method, args),
55
- column_suffix: columns.size > 1 ? "s" : ""
56
- when :change_table
57
- raise_error :change_table, header: "Possibly dangerous operation"
58
- when :rename_table
59
- raise_error :rename_table
60
- when :rename_column
61
- raise_error :rename_column
62
- when :add_index
63
- table, columns, options = args
64
- options ||= {}
65
-
66
- if columns.is_a?(Array) && columns.size > 3 && !options[:unique]
67
- raise_error :add_index_columns, header: "Best practice"
68
- end
69
- if postgresql? && options[:algorithm] != :concurrently && !new_table?(table)
70
- return safe_add_index(table, columns, options) if StrongMigrations.safe_by_default
71
- raise_error :add_index, command: command_str("add_index", [table, columns, options.merge(algorithm: :concurrently)])
72
- end
73
- when :remove_index
74
- table, options = args
75
- unless options.is_a?(Hash)
76
- options = {column: options}
77
- end
78
- options ||= {}
79
-
80
- if postgresql? && options[:algorithm] != :concurrently && !new_table?(table)
81
- return safe_remove_index(table, options) if StrongMigrations.safe_by_default
82
- raise_error :remove_index, command: command_str("remove_index", [table, options.merge(algorithm: :concurrently)])
83
- end
35
+ when :add_check_constraint
36
+ check_add_check_constraint(args)
84
37
  when :add_column
85
- table, column, type, options = args
86
- options ||= {}
87
- default = options[:default]
88
-
89
- if !default.nil? && !((postgresql? && postgresql_version >= Gem::Version.new("11")) || (mysql? && mysql_version >= Gem::Version.new("8.0.12")) || (mariadb? && mariadb_version >= Gem::Version.new("10.3.2")))
90
-
91
- if options[:null] == false
92
- options = options.except(:null)
93
- append = "
94
-
95
- Then add the NOT NULL constraint in separate migrations."
96
- end
97
-
98
- raise_error :add_column_default,
99
- add_command: command_str("add_column", [table, column, type, options.except(:default)]),
100
- change_command: command_str("change_column_default", [table, column, default]),
101
- remove_command: command_str("remove_column", [table, column]),
102
- code: backfill_code(table, column, default),
103
- append: append,
104
- rewrite_blocks: rewrite_blocks
105
- end
106
-
107
- if type.to_s == "json" && postgresql?
108
- raise_error :add_column_json,
109
- command: command_str("add_column", [table, column, :jsonb, options])
110
- end
38
+ check_add_column(args)
39
+ when :add_foreign_key
40
+ check_add_foreign_key(args)
41
+ when :add_index
42
+ check_add_index(args)
43
+ when :add_reference, :add_belongs_to
44
+ check_add_reference(method, args)
111
45
  when :change_column
112
- table, column, type, options = args
113
- options ||= {}
114
-
115
- safe = false
116
- existing_column = connection.columns(table).find { |c| c.name.to_s == column.to_s }
117
- if existing_column
118
- existing_type = existing_column.sql_type.split("(").first
119
- if postgresql?
120
- case type.to_s
121
- when "string"
122
- # safe to increase limit or remove it
123
- # not safe to decrease limit or add a limit
124
- case existing_type
125
- when "character varying"
126
- safe = !options[:limit] || (existing_column.limit && options[:limit] >= existing_column.limit)
127
- when "text"
128
- safe = !options[:limit]
129
- end
130
- when "text"
131
- # safe to change varchar to text (and text to text)
132
- safe = ["character varying", "text"].include?(existing_type)
133
- when "numeric", "decimal"
134
- # numeric and decimal are equivalent and can be used interchangably
135
- safe = ["numeric", "decimal"].include?(existing_type) &&
136
- (
137
- (
138
- # unconstrained
139
- !options[:precision] && !options[:scale]
140
- ) || (
141
- # increased precision, same scale
142
- options[:precision] && existing_column.precision &&
143
- options[:precision] >= existing_column.precision &&
144
- options[:scale] == existing_column.scale
145
- )
146
- )
147
- when "datetime", "timestamp", "timestamptz"
148
- safe = ["timestamp without time zone", "timestamp with time zone"].include?(existing_type) &&
149
- postgresql_version >= Gem::Version.new("12") &&
150
- connection.select_all("SHOW timezone").first["TimeZone"] == "UTC"
151
- end
152
- elsif mysql? || mariadb?
153
- case type.to_s
154
- when "string"
155
- # https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html
156
- # https://mariadb.com/kb/en/innodb-online-ddl-operations-with-the-instant-alter-algorithm/#changing-the-data-type-of-a-column
157
- # increased limit, but doesn't change number of length bytes
158
- # 1-255 = 1 byte, 256-65532 = 2 bytes, 65533+ = too big for varchar
159
- limit = options[:limit] || 255
160
- safe = ["varchar"].include?(existing_type) &&
161
- limit >= existing_column.limit &&
162
- (limit <= 255 || existing_column.limit > 255)
163
- end
164
- end
165
- end
166
-
167
- # unsafe to set NOT NULL for safe types
168
- if safe && existing_column.null && options[:null] == false
169
- raise_error :change_column_with_not_null
170
- end
171
-
172
- raise_error :change_column, rewrite_blocks: rewrite_blocks unless safe
46
+ check_change_column(args)
47
+ when :change_column_null
48
+ check_change_column_null(args)
49
+ when :change_table
50
+ check_change_table
173
51
  when :create_table
174
- table, options = args
175
- options ||= {}
176
-
177
- raise_error :create_table if options[:force]
178
-
179
- # keep track of new tables of add_index check
180
- @new_tables << table.to_s
181
- when :add_reference, :add_belongs_to
182
- table, reference, options = args
183
- options ||= {}
184
-
185
- if postgresql?
186
- index_value = options.fetch(:index, true)
187
- concurrently_set = index_value.is_a?(Hash) && index_value[:algorithm] == :concurrently
188
- bad_index = index_value && !concurrently_set
189
-
190
- if bad_index || options[:foreign_key]
191
- if index_value.is_a?(Hash)
192
- options[:index] = options[:index].merge(algorithm: :concurrently)
193
- else
194
- options = options.merge(index: {algorithm: :concurrently})
195
- end
196
-
197
- return safe_add_reference(table, reference, options) if StrongMigrations.safe_by_default
198
-
199
- if options.delete(:foreign_key)
200
- headline = "Adding a foreign key blocks writes on both tables."
201
- append = "
202
-
203
- Then add the foreign key in separate migrations."
204
- else
205
- headline = "Adding an index non-concurrently locks the table."
206
- end
207
-
208
- raise_error :add_reference,
209
- headline: headline,
210
- command: command_str(method, [table, reference, options]),
211
- append: append
212
- end
213
- end
52
+ check_create_table(args)
214
53
  when :execute
215
- raise_error :execute, header: "Possibly dangerous operation"
216
- when :change_column_null
217
- table, column, null, default = args
218
- if !null
219
- if postgresql?
220
- safe = false
221
- if postgresql_version >= Gem::Version.new("12")
222
- safe = constraints(table).any? { |c| c["def"] == "CHECK ((#{column} IS NOT NULL))" || c["def"] == "CHECK ((#{connection.quote_column_name(column)} IS NOT NULL))" }
223
- end
224
-
225
- unless safe
226
- # match https://github.com/nullobject/rein
227
- constraint_name = "#{table}_#{column}_null"
228
-
229
- add_code = constraint_str("ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s IS NOT NULL) NOT VALID", [table, constraint_name, column])
230
- validate_code = constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
231
- remove_code = constraint_str("ALTER TABLE %s DROP CONSTRAINT %s", [table, constraint_name])
232
-
233
- validate_constraint_code =
234
- if ar_version >= 6.1
235
- String.new(command_str(:validate_check_constraint, [table, {name: constraint_name}]))
236
- else
237
- String.new(safety_assured_str(validate_code))
238
- end
239
-
240
- if postgresql_version >= Gem::Version.new("12")
241
- change_args = [table, column, null]
242
-
243
- validate_constraint_code << "\n #{command_str(:change_column_null, change_args)}"
244
-
245
- if ar_version >= 6.1
246
- validate_constraint_code << "\n #{command_str(:remove_check_constraint, [table, {name: constraint_name}])}"
247
- else
248
- validate_constraint_code << "\n #{safety_assured_str(remove_code)}"
249
- end
250
- end
251
-
252
- return safe_change_column_null(add_code, validate_code, change_args, remove_code) if StrongMigrations.safe_by_default
253
-
254
- add_constraint_code =
255
- if ar_version >= 6.1
256
- # only quote when needed
257
- expr_column = column.to_s =~ /\A[a-z0-9_]+\z/ ? column : connection.quote_column_name(column)
258
- command_str(:add_check_constraint, [table, "#{expr_column} IS NOT NULL", {name: constraint_name, validate: false}])
259
- else
260
- safety_assured_str(add_code)
261
- end
262
-
263
- raise_error :change_column_null_postgresql,
264
- add_constraint_code: add_constraint_code,
265
- validate_constraint_code: validate_constraint_code
266
- end
267
- elsif mysql? || mariadb?
268
- raise_error :change_column_null_mysql
269
- elsif !default.nil?
270
- raise_error :change_column_null,
271
- code: backfill_code(table, column, default)
272
- end
273
- end
274
- when :add_foreign_key
275
- from_table, to_table, options = args
276
- options ||= {}
277
-
278
- # always validated before 5.2
279
- validate = options.fetch(:validate, true) || ar_version < 5.2
280
-
281
- if postgresql? && validate
282
- if ar_version < 5.2
283
- # fk name logic from rails
284
- primary_key = options[:primary_key] || "id"
285
- column = options[:column] || "#{to_table.to_s.singularize}_id"
286
- hashed_identifier = Digest::SHA256.hexdigest("#{from_table}_#{column}_fk").first(10)
287
- fk_name = options[:name] || "fk_rails_#{hashed_identifier}"
288
-
289
- add_code = constraint_str("ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s) NOT VALID", [from_table, fk_name, column, to_table, primary_key])
290
- validate_code = constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [from_table, fk_name])
291
-
292
- return safe_add_foreign_key_code(from_table, to_table, add_code, validate_code) if StrongMigrations.safe_by_default
293
-
294
- raise_error :add_foreign_key,
295
- add_foreign_key_code: safety_assured_str(add_code),
296
- validate_foreign_key_code: safety_assured_str(validate_code)
297
- else
298
- return safe_add_foreign_key(from_table, to_table, options) if StrongMigrations.safe_by_default
299
-
300
- raise_error :add_foreign_key,
301
- add_foreign_key_code: command_str("add_foreign_key", [from_table, to_table, options.merge(validate: false)]),
302
- validate_foreign_key_code: command_str("validate_foreign_key", [from_table, to_table])
303
- end
304
- end
305
- when :validate_foreign_key
306
- if postgresql? && writes_blocked?
307
- raise_error :validate_foreign_key
308
- end
309
- when :add_check_constraint
310
- table, expression, options = args
311
- options ||= {}
312
-
313
- if !new_table?(table)
314
- if postgresql? && options[:validate] != false
315
- add_options = options.merge(validate: false)
316
- name = options[:name] || @migration.check_constraint_options(table, expression, options)[:name]
317
- validate_options = {name: name}
318
-
319
- return safe_add_check_constraint(table, expression, add_options, validate_options) if StrongMigrations.safe_by_default
320
-
321
- raise_error :add_check_constraint,
322
- add_check_constraint_code: command_str("add_check_constraint", [table, expression, add_options]),
323
- validate_check_constraint_code: command_str("validate_check_constraint", [table, validate_options])
324
- elsif mysql? || mariadb?
325
- raise_error :add_check_constraint_mysql
326
- end
327
- end
54
+ check_execute
55
+ when :remove_column, :remove_columns, :remove_timestamps, :remove_reference, :remove_belongs_to
56
+ check_remove_column(method, args)
57
+ when :remove_index
58
+ check_remove_index(args)
59
+ when :rename_column
60
+ check_rename_column
61
+ when :rename_table
62
+ check_rename_table
328
63
  when :validate_check_constraint
329
- if postgresql? && writes_blocked?
330
- raise_error :validate_check_constraint
331
- end
64
+ check_validate_check_constraint
65
+ when :validate_foreign_key
66
+ check_validate_foreign_key
67
+ when :commit_db_transaction
68
+ # if committed, likely no longer in DDL transaction
69
+ # and no longer eligible to be retried at migration level
70
+ # okay to have false positives
71
+ @committed = true
332
72
  end
333
73
 
74
+ # custom checks
334
75
  StrongMigrations.checks.each do |check|
335
76
  @migration.instance_exec(method, args, &check)
336
77
  end
337
78
  end
338
79
 
339
- result = yield
80
+ result =
81
+ if retry_lock_timeouts?(method)
82
+ # TODO figure out how to handle methods that generate multiple statements
83
+ # like add_reference(table, ref, index: {algorithm: :concurrently})
84
+ # lock timeout after first statement will cause retry to fail
85
+ retry_lock_timeouts { yield }
86
+ else
87
+ yield
88
+ end
340
89
 
341
90
  # outdated statistics + a new index can hurt performance of existing queries
342
91
  if StrongMigrations.auto_analyze && direction == :up && method == :add_index
343
- analyze_table(args[0])
92
+ adapter.analyze_table(args[0])
344
93
  end
345
94
 
346
95
  result
347
96
  end
348
97
 
349
- private
350
-
351
- def set_timeouts
352
- if !@timeouts_set
353
- if StrongMigrations.statement_timeout
354
- statement =
355
- if postgresql?
356
- "SET statement_timeout TO #{connection.quote(postgresql_timeout(StrongMigrations.statement_timeout))}"
357
- elsif mysql?
358
- # use ceil to prevent no timeout for values under 1 ms
359
- "SET max_execution_time = #{connection.quote((StrongMigrations.statement_timeout.to_f * 1000).ceil)}"
360
- elsif mariadb?
361
- "SET max_statement_time = #{connection.quote(StrongMigrations.statement_timeout)}"
362
- else
363
- raise StrongMigrations::Error, "Statement timeout not supported for this database"
364
- end
365
-
366
- connection.select_all(statement)
367
- end
368
-
369
- if StrongMigrations.lock_timeout
370
- statement =
371
- if postgresql?
372
- "SET lock_timeout TO #{connection.quote(postgresql_timeout(StrongMigrations.lock_timeout))}"
373
- elsif mysql? || mariadb?
374
- "SET lock_wait_timeout = #{connection.quote(StrongMigrations.lock_timeout)}"
375
- else
376
- raise StrongMigrations::Error, "Lock timeout not supported for this database"
377
- end
378
-
379
- connection.select_all(statement)
98
+ def retry_lock_timeouts(check_committed: false)
99
+ retries = 0
100
+ begin
101
+ yield
102
+ rescue ActiveRecord::LockWaitTimeout => e
103
+ if retries < StrongMigrations.lock_timeout_retries && !(check_committed && @committed)
104
+ retries += 1
105
+ delay = StrongMigrations.lock_timeout_retry_delay
106
+ @migration.say("Lock timeout. Retrying in #{delay} seconds...")
107
+ sleep(delay)
108
+ retry
380
109
  end
381
-
382
- @timeouts_set = true
110
+ raise e
383
111
  end
384
112
  end
385
113
 
386
- def connection
387
- @migration.connection
388
- end
389
-
390
- def version
391
- @migration.version
392
- end
393
-
394
- def safe?
395
- @safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe?
396
- end
397
-
398
- def version_safe?
399
- version && version <= StrongMigrations.start_after
400
- end
114
+ private
401
115
 
402
- def postgresql?
403
- connection.adapter_name =~ /postg/i # PostgreSQL, PostGIS
404
- end
116
+ def check_version_supported
117
+ return if defined?(@version_checked)
405
118
 
406
- def postgresql_version
407
- @postgresql_version ||= begin
408
- target_version(StrongMigrations.target_postgresql_version) do
409
- # only works with major versions
410
- connection.select_all("SHOW server_version_num").first["server_version_num"].to_i / 10000
119
+ min_version = adapter.min_version
120
+ if min_version
121
+ version = adapter.server_version
122
+ if version < Gem::Version.new(min_version)
123
+ raise UnsupportedVersion, "#{adapter.name} version (#{version}) not supported in this version of Strong Migrations (#{StrongMigrations::VERSION})"
411
124
  end
412
125
  end
413
- end
414
-
415
- def mysql?
416
- connection.adapter_name =~ /mysql/i && !connection.try(:mariadb?)
417
- end
418
126
 
419
- def mysql_version
420
- @mysql_version ||= begin
421
- target_version(StrongMigrations.target_mysql_version) do
422
- connection.select_all("SELECT VERSION()").first["VERSION()"].split("-").first
423
- end
424
- end
127
+ @version_checked = true
425
128
  end
426
129
 
427
- def mariadb?
428
- connection.adapter_name =~ /mysql/i && connection.try(:mariadb?)
429
- end
130
+ def set_timeouts
131
+ return if @timeouts_set
430
132
 
431
- def mariadb_version
432
- @mariadb_version ||= begin
433
- target_version(StrongMigrations.target_mariadb_version) do
434
- connection.select_all("SELECT VERSION()").first["VERSION()"].split("-").first
435
- end
133
+ if StrongMigrations.statement_timeout
134
+ adapter.set_statement_timeout(StrongMigrations.statement_timeout)
436
135
  end
437
- end
438
-
439
- def target_version(target_version)
440
- target_version ||= StrongMigrations.target_version
441
- version =
442
- if target_version && StrongMigrations.developer_env?
443
- target_version.to_s
444
- else
445
- yield
446
- end
447
- Gem::Version.new(version)
448
- end
449
-
450
- def ar_version
451
- ActiveRecord::VERSION::STRING.to_f
452
- end
453
-
454
- def check_lock_timeout
455
- limit = StrongMigrations.lock_timeout_limit
456
-
457
- if limit && !@lock_timeout_checked
458
- if postgresql?
459
- lock_timeout = connection.select_all("SHOW lock_timeout").first["lock_timeout"]
460
- lock_timeout_sec = timeout_to_sec(lock_timeout)
461
- if lock_timeout_sec == 0
462
- warn "[strong_migrations] DANGER: No lock timeout set"
463
- elsif lock_timeout_sec > limit
464
- warn "[strong_migrations] DANGER: Lock timeout is longer than #{limit} seconds: #{lock_timeout}"
465
- end
466
- elsif mysql? || mariadb?
467
- lock_timeout = connection.select_all("SHOW VARIABLES LIKE 'lock_wait_timeout'").first["Value"]
468
- # lock timeout is an integer
469
- if lock_timeout.to_i > limit
470
- warn "[strong_migrations] DANGER: Lock timeout is longer than #{limit} seconds: #{lock_timeout}"
471
- end
472
- end
473
- @lock_timeout_checked = true
136
+ if StrongMigrations.lock_timeout
137
+ adapter.set_lock_timeout(StrongMigrations.lock_timeout)
474
138
  end
475
- end
476
139
 
477
- # units: https://www.postgresql.org/docs/current/config-setting.html
478
- def timeout_to_sec(timeout)
479
- units = {
480
- "us" => 0.001,
481
- "ms" => 1,
482
- "s" => 1000,
483
- "min" => 1000 * 60,
484
- "h" => 1000 * 60 * 60,
485
- "d" => 1000 * 60 * 60 * 24
486
- }
487
- timeout_ms = timeout.to_i
488
- units.each do |k, v|
489
- if timeout.end_with?(k)
490
- timeout_ms *= v
491
- break
492
- end
493
- end
494
- timeout_ms / 1000.0
140
+ @timeouts_set = true
495
141
  end
496
142
 
497
- def postgresql_timeout(timeout)
498
- if timeout.is_a?(String)
499
- timeout
500
- else
501
- # use ceil to prevent no timeout for values under 1 ms
502
- (timeout.to_f * 1000).ceil
503
- end
504
- end
143
+ def check_lock_timeout
144
+ return if defined?(@lock_timeout_checked)
505
145
 
506
- def analyze_table(table)
507
- if postgresql?
508
- connection.execute "ANALYZE #{connection.quote_table_name(table.to_s)}"
509
- elsif mariadb? || mysql?
510
- connection.execute "ANALYZE TABLE #{connection.quote_table_name(table.to_s)}"
146
+ if StrongMigrations.lock_timeout_limit
147
+ adapter.check_lock_timeout(StrongMigrations.lock_timeout_limit)
511
148
  end
512
- end
513
149
 
514
- def constraints(table_name)
515
- query = <<~SQL
516
- SELECT
517
- conname AS name,
518
- pg_get_constraintdef(oid) AS def
519
- FROM
520
- pg_constraint
521
- WHERE
522
- contype = 'c' AND
523
- convalidated AND
524
- conrelid = #{connection.quote(connection.quote_table_name(table_name))}::regclass
525
- SQL
526
- connection.select_all(query.squish).to_a
150
+ @lock_timeout_checked = true
527
151
  end
528
152
 
529
- def raise_error(message_key, header: nil, append: nil, **vars)
530
- return unless StrongMigrations.check_enabled?(message_key, version: version)
531
-
532
- message = StrongMigrations.error_messages[message_key] || "Missing message"
533
- message = message + append if append
534
-
535
- vars[:migration_name] = @migration.class.name
536
- vars[:migration_suffix] = "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
537
- vars[:base_model] = "ApplicationRecord"
538
-
539
- # escape % not followed by {
540
- message = message.gsub(/%(?!{)/, "%%") % vars if message.include?("%")
541
- @migration.stop!(message, header: header || "Dangerous operation detected")
153
+ def safe?
154
+ @safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe?
542
155
  end
543
156
 
544
- def constraint_str(statement, identifiers)
545
- # not all identifiers are tables, but this method of quoting should be fine
546
- statement % identifiers.map { |v| connection.quote_table_name(v) }
157
+ def version_safe?
158
+ version && version <= StrongMigrations.start_after
547
159
  end
548
160
 
549
- def safety_assured_str(code)
550
- "safety_assured do\n execute '#{code}' \n end"
161
+ def version
162
+ @migration.version
551
163
  end
552
164
 
553
- def command_str(command, args)
554
- str_args = args[0..-2].map { |a| a.inspect }
555
-
556
- # prettier last arg
557
- last_arg = args[-1]
558
- if last_arg.is_a?(Hash)
559
- if last_arg.any?
560
- str_args << last_arg.map do |k, v|
561
- if v.is_a?(Hash)
562
- # pretty index: {algorithm: :concurrently}
563
- "#{k}: {#{v.map { |k2, v2| "#{k2}: #{v2.inspect}" }.join(", ")}}"
165
+ def adapter
166
+ @adapter ||= begin
167
+ cls =
168
+ case connection.adapter_name
169
+ when /postg/i # PostgreSQL, PostGIS
170
+ Adapters::PostgreSQLAdapter
171
+ when /mysql/i
172
+ if connection.try(:mariadb?)
173
+ Adapters::MariaDBAdapter
564
174
  else
565
- "#{k}: #{v.inspect}"
175
+ Adapters::MySQLAdapter
566
176
  end
567
- end.join(", ")
568
- end
569
- else
570
- str_args << last_arg.inspect
571
- end
572
-
573
- "#{command} #{str_args.join(", ")}"
574
- end
575
-
576
- def writes_blocked?
577
- query = <<~SQL
578
- SELECT
579
- relation::regclass::text
580
- FROM
581
- pg_locks
582
- WHERE
583
- mode IN ('ShareRowExclusiveLock', 'AccessExclusiveLock') AND
584
- pid = pg_backend_pid()
585
- SQL
586
- connection.select_all(query.squish).any?
587
- end
177
+ else
178
+ Adapters::AbstractAdapter
179
+ end
588
180
 
589
- def rewrite_blocks
590
- mysql? || mariadb? ? "writes" : "reads and writes"
181
+ cls.new(self)
182
+ end
591
183
  end
592
184
 
593
- def backfill_code(table, column, default)
594
- model = table.to_s.classify
595
- "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
185
+ def connection
186
+ @migration.connection
596
187
  end
597
188
 
598
- def new_table?(table)
599
- @new_tables.include?(table.to_s)
189
+ def retry_lock_timeouts?(method)
190
+ (
191
+ StrongMigrations.lock_timeout_retries > 0 &&
192
+ !in_transaction? &&
193
+ method != :transaction
194
+ )
600
195
  end
601
196
  end
602
197
  end