strong_migrations 0.7.9 → 1.4.4

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,178 @@ 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_exclusion_constraint
40
+ check_add_exclusion_constraint(*args)
41
+ when :add_foreign_key
42
+ check_add_foreign_key(*args)
43
+ when :add_index
44
+ check_add_index(*args)
45
+ when :add_reference, :add_belongs_to
46
+ check_add_reference(method, *args)
111
47
  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
48
+ check_change_column(*args)
49
+ when :change_column_null
50
+ check_change_column_null(*args)
51
+ when :change_table
52
+ check_change_table
53
+ when :create_join_table
54
+ check_create_join_table(*args)
173
55
  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
56
+ check_create_table(*args)
214
57
  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
58
+ check_execute
59
+ when :remove_column, :remove_columns, :remove_timestamps, :remove_reference, :remove_belongs_to
60
+ check_remove_column(method, *args)
61
+ when :remove_index
62
+ check_remove_index(*args)
63
+ when :rename_column
64
+ check_rename_column
65
+ when :rename_table
66
+ check_rename_table
328
67
  when :validate_check_constraint
329
- if postgresql? && writes_blocked?
330
- raise_error :validate_check_constraint
331
- end
68
+ check_validate_check_constraint
69
+ when :validate_foreign_key
70
+ check_validate_foreign_key
71
+ when :commit_db_transaction
72
+ # if committed, likely no longer in DDL transaction
73
+ # and no longer eligible to be retried at migration level
74
+ # okay to have false positives
75
+ @committed = true
332
76
  end
333
77
 
78
+ # custom checks
334
79
  StrongMigrations.checks.each do |check|
335
80
  @migration.instance_exec(method, args, &check)
336
81
  end
337
82
  end
338
83
 
339
- result = yield
84
+ result =
85
+ if retry_lock_timeouts?(method)
86
+ # TODO figure out how to handle methods that generate multiple statements
87
+ # like add_reference(table, ref, index: {algorithm: :concurrently})
88
+ # lock timeout after first statement will cause retry to fail
89
+ retry_lock_timeouts { yield }
90
+ else
91
+ yield
92
+ end
340
93
 
341
94
  # outdated statistics + a new index can hurt performance of existing queries
342
95
  if StrongMigrations.auto_analyze && direction == :up && method == :add_index
343
- analyze_table(args[0])
96
+ adapter.analyze_table(args[0])
344
97
  end
345
98
 
346
99
  result
347
100
  end
348
101
 
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)
102
+ def retry_lock_timeouts(check_committed: false)
103
+ retries = 0
104
+ begin
105
+ yield
106
+ rescue ActiveRecord::LockWaitTimeout => e
107
+ if retries < StrongMigrations.lock_timeout_retries && !(check_committed && @committed)
108
+ retries += 1
109
+ delay = StrongMigrations.lock_timeout_retry_delay
110
+ @migration.say("Lock timeout. Retrying in #{delay} seconds...")
111
+ sleep(delay)
112
+ retry
380
113
  end
381
-
382
- @timeouts_set = true
114
+ raise e
383
115
  end
384
116
  end
385
117
 
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
118
+ private
401
119
 
402
- def postgresql?
403
- connection.adapter_name =~ /postg/i # PostgreSQL, PostGIS
404
- end
120
+ def check_version_supported
121
+ return if defined?(@version_checked)
405
122
 
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
123
+ min_version = adapter.min_version
124
+ if min_version
125
+ version = adapter.server_version
126
+ if version < Gem::Version.new(min_version)
127
+ raise UnsupportedVersion, "#{adapter.name} version (#{version}) not supported in this version of Strong Migrations (#{StrongMigrations::VERSION})"
411
128
  end
412
129
  end
413
- end
414
-
415
- def mysql?
416
- connection.adapter_name =~ /mysql/i && !connection.try(:mariadb?)
417
- end
418
130
 
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
131
+ @version_checked = true
425
132
  end
426
133
 
427
- def mariadb?
428
- connection.adapter_name =~ /mysql/i && connection.try(:mariadb?)
429
- end
134
+ def set_timeouts
135
+ return if @timeouts_set
430
136
 
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
137
+ if StrongMigrations.statement_timeout
138
+ adapter.set_statement_timeout(StrongMigrations.statement_timeout)
436
139
  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
140
+ if StrongMigrations.lock_timeout
141
+ adapter.set_lock_timeout(StrongMigrations.lock_timeout)
474
142
  end
475
- end
476
143
 
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
144
+ @timeouts_set = true
495
145
  end
496
146
 
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
147
+ def check_lock_timeout
148
+ return if defined?(@lock_timeout_checked)
505
149
 
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)}"
150
+ if StrongMigrations.lock_timeout_limit
151
+ adapter.check_lock_timeout(StrongMigrations.lock_timeout_limit)
511
152
  end
512
- end
513
153
 
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
154
+ @lock_timeout_checked = true
527
155
  end
528
156
 
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")
157
+ def safe?
158
+ @safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe?
542
159
  end
543
160
 
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) }
161
+ def version_safe?
162
+ version && version <= StrongMigrations.start_after
547
163
  end
548
164
 
549
- def safety_assured_str(code)
550
- "safety_assured do\n execute '#{code}' \n end"
165
+ def version
166
+ @migration.version
551
167
  end
552
168
 
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(", ")}}"
169
+ def adapter
170
+ @adapter ||= begin
171
+ cls =
172
+ case connection.adapter_name
173
+ when /postg/i # PostgreSQL, PostGIS
174
+ Adapters::PostgreSQLAdapter
175
+ when /mysql/i
176
+ if connection.try(:mariadb?)
177
+ Adapters::MariaDBAdapter
564
178
  else
565
- "#{k}: #{v.inspect}"
179
+ Adapters::MySQLAdapter
566
180
  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
181
+ else
182
+ Adapters::AbstractAdapter
183
+ end
588
184
 
589
- def rewrite_blocks
590
- mysql? || mariadb? ? "writes" : "reads and writes"
185
+ cls.new(self)
186
+ end
591
187
  end
592
188
 
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"
189
+ def connection
190
+ @migration.connection
596
191
  end
597
192
 
598
- def new_table?(table)
599
- @new_tables.include?(table.to_s)
193
+ def retry_lock_timeouts?(method)
194
+ (
195
+ StrongMigrations.lock_timeout_retries > 0 &&
196
+ !in_transaction? &&
197
+ method != :transaction
198
+ )
600
199
  end
601
200
  end
602
201
  end