strong_migrations 0.7.8 → 1.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.
@@ -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