strong_migrations 0.7.6 → 1.7.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,599 +1,211 @@
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
7
+
8
+ class << self
9
+ attr_accessor :safe
10
+ end
6
11
 
7
12
  def initialize(migration)
8
13
  @migration = migration
9
14
  @new_tables = []
10
- @safe = false
15
+ @new_columns = []
11
16
  @timeouts_set = false
12
- @lock_timeout_checked = false
17
+ @committed = false
13
18
  end
14
19
 
15
- def safety_assured
16
- previous_value = @safe
20
+ def self.safety_assured
21
+ previous_value = safe
17
22
  begin
18
- @safe = true
23
+ self.safe = true
19
24
  yield
20
25
  ensure
21
- @safe = previous_value
26
+ self.safe = previous_value
22
27
  end
23
28
  end
24
29
 
25
30
  def perform(method, *args)
31
+ check_version_supported
26
32
  set_timeouts
27
33
  check_lock_timeout
28
34
 
29
35
  if !safe? || safe_by_default_method?(method)
36
+ # TODO better pattern
37
+ # see checks.rb for methods
30
38
  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
39
+ when :add_check_constraint
40
+ check_add_check_constraint(*args)
84
41
  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
42
+ check_add_column(*args)
43
+ when :add_exclusion_constraint
44
+ check_add_exclusion_constraint(*args)
45
+ when :add_foreign_key
46
+ check_add_foreign_key(*args)
47
+ when :add_index
48
+ check_add_index(*args)
49
+ when :add_reference, :add_belongs_to
50
+ check_add_reference(method, *args)
51
+ when :add_unique_constraint
52
+ check_add_unique_constraint(*args)
111
53
  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
54
+ check_change_column(*args)
55
+ when :change_column_default
56
+ check_change_column_default(*args)
57
+ when :change_column_null
58
+ check_change_column_null(*args)
59
+ when :change_table
60
+ check_change_table
61
+ when :create_join_table
62
+ check_create_join_table(*args)
173
63
  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
64
+ check_create_table(*args)
214
65
  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
66
+ check_execute
67
+ when :remove_column, :remove_columns, :remove_timestamps, :remove_reference, :remove_belongs_to
68
+ check_remove_column(method, *args)
69
+ when :remove_index
70
+ check_remove_index(*args)
71
+ when :rename_column
72
+ check_rename_column
73
+ when :rename_table
74
+ check_rename_table
328
75
  when :validate_check_constraint
329
- if postgresql? && writes_blocked?
330
- raise_error :validate_check_constraint
331
- end
76
+ check_validate_check_constraint
77
+ when :validate_foreign_key
78
+ check_validate_foreign_key
79
+ when :commit_db_transaction
80
+ # if committed, likely no longer in DDL transaction
81
+ # and no longer eligible to be retried at migration level
82
+ # okay to have false positives
83
+ @committed = true
332
84
  end
333
85
 
334
- StrongMigrations.checks.each do |check|
335
- @migration.instance_exec(method, args, &check)
86
+ if !safe?
87
+ # custom checks
88
+ StrongMigrations.checks.each do |check|
89
+ @migration.instance_exec(method, args, &check)
90
+ end
336
91
  end
337
92
  end
338
93
 
339
- result = yield
94
+ result =
95
+ if retry_lock_timeouts?(method)
96
+ # TODO figure out how to handle methods that generate multiple statements
97
+ # like add_reference(table, ref, index: {algorithm: :concurrently})
98
+ # lock timeout after first statement will cause retry to fail
99
+ retry_lock_timeouts { yield }
100
+ else
101
+ yield
102
+ end
340
103
 
341
104
  # outdated statistics + a new index can hurt performance of existing queries
342
105
  if StrongMigrations.auto_analyze && direction == :up && method == :add_index
343
- if postgresql?
344
- connection.execute "ANALYZE #{connection.quote_table_name(args[0].to_s)}"
345
- elsif mariadb? || mysql?
346
- connection.execute "ANALYZE TABLE #{connection.quote_table_name(args[0].to_s)}"
347
- end
106
+ adapter.analyze_table(args[0])
348
107
  end
349
108
 
350
109
  result
351
110
  end
352
111
 
353
- private
354
-
355
- def set_timeouts
356
- if !@timeouts_set
357
- if StrongMigrations.statement_timeout
358
- statement =
359
- if postgresql?
360
- "SET statement_timeout TO #{connection.quote(postgresql_timeout(StrongMigrations.statement_timeout))}"
361
- elsif mysql?
362
- # use ceil to prevent no timeout for values under 1 ms
363
- "SET max_execution_time = #{connection.quote((StrongMigrations.statement_timeout.to_f * 1000).ceil)}"
364
- elsif mariadb?
365
- "SET max_statement_time = #{connection.quote(StrongMigrations.statement_timeout)}"
366
- else
367
- raise StrongMigrations::Error, "Statement timeout not supported for this database"
368
- end
369
-
370
- connection.select_all(statement)
371
- end
372
-
373
- if StrongMigrations.lock_timeout
374
- statement =
375
- if postgresql?
376
- "SET lock_timeout TO #{connection.quote(postgresql_timeout(StrongMigrations.lock_timeout))}"
377
- elsif mysql? || mariadb?
378
- "SET lock_wait_timeout = #{connection.quote(StrongMigrations.lock_timeout)}"
379
- else
380
- raise StrongMigrations::Error, "Lock timeout not supported for this database"
381
- end
382
-
383
- connection.select_all(statement)
112
+ def retry_lock_timeouts(check_committed: false)
113
+ retries = 0
114
+ begin
115
+ yield
116
+ rescue ActiveRecord::LockWaitTimeout => e
117
+ if retries < StrongMigrations.lock_timeout_retries && !(check_committed && @committed)
118
+ retries += 1
119
+ delay = StrongMigrations.lock_timeout_retry_delay
120
+ @migration.say("Lock timeout. Retrying in #{delay} seconds...")
121
+ sleep(delay)
122
+ retry
384
123
  end
385
-
386
- @timeouts_set = true
124
+ raise e
387
125
  end
388
126
  end
389
127
 
390
- def connection
391
- @migration.connection
392
- end
393
-
394
- def version
395
- @migration.version
396
- end
397
-
398
- def safe?
399
- @safe || ENV["SAFETY_ASSURED"] || @migration.is_a?(ActiveRecord::Schema) ||
400
- (direction == :down && !StrongMigrations.check_down) || version_safe?
401
- end
402
-
403
128
  def version_safe?
404
129
  version && version <= StrongMigrations.start_after
405
130
  end
406
131
 
407
- def postgresql?
408
- connection.adapter_name =~ /postg/i # PostgreSQL, PostGIS
409
- end
410
-
411
- def postgresql_version
412
- @postgresql_version ||= begin
413
- target_version(StrongMigrations.target_postgresql_version) do
414
- # only works with major versions
415
- connection.select_all("SHOW server_version_num").first["server_version_num"].to_i / 10000
416
- end
417
- end
418
- end
419
-
420
- def mysql?
421
- connection.adapter_name =~ /mysql/i && !connection.try(:mariadb?)
422
- end
423
-
424
- def mysql_version
425
- @mysql_version ||= begin
426
- target_version(StrongMigrations.target_mysql_version) do
427
- connection.select_all("SELECT VERSION()").first["VERSION()"].split("-").first
428
- end
429
- end
430
- end
132
+ private
431
133
 
432
- def mariadb?
433
- connection.adapter_name =~ /mysql/i && connection.try(:mariadb?)
434
- end
134
+ def check_version_supported
135
+ return if defined?(@version_checked)
435
136
 
436
- def mariadb_version
437
- @mariadb_version ||= begin
438
- target_version(StrongMigrations.target_mariadb_version) do
439
- connection.select_all("SELECT VERSION()").first["VERSION()"].split("-").first
137
+ min_version = adapter.min_version
138
+ if min_version
139
+ version = adapter.server_version
140
+ if version < Gem::Version.new(min_version)
141
+ raise UnsupportedVersion, "#{adapter.name} version (#{version}) not supported in this version of Strong Migrations (#{StrongMigrations::VERSION})"
440
142
  end
441
143
  end
442
- end
443
-
444
- def target_version(target_version)
445
- target_version ||= StrongMigrations.target_version
446
- version =
447
- if target_version && StrongMigrations.developer_env?
448
- target_version.to_s
449
- else
450
- yield
451
- end
452
- Gem::Version.new(version)
453
- end
454
144
 
455
- def ar_version
456
- ActiveRecord::VERSION::STRING.to_f
145
+ @version_checked = true
457
146
  end
458
147
 
459
- def check_lock_timeout
460
- limit = StrongMigrations.lock_timeout_limit
461
-
462
- if limit && !@lock_timeout_checked
463
- if postgresql?
464
- lock_timeout = connection.select_all("SHOW lock_timeout").first["lock_timeout"]
465
- lock_timeout_sec = timeout_to_sec(lock_timeout)
466
- if lock_timeout_sec == 0
467
- warn "[strong_migrations] DANGER: No lock timeout set"
468
- elsif lock_timeout_sec > limit
469
- warn "[strong_migrations] DANGER: Lock timeout is longer than #{limit} seconds: #{lock_timeout}"
470
- end
471
- elsif mysql? || mariadb?
472
- lock_timeout = connection.select_all("SHOW VARIABLES LIKE 'lock_wait_timeout'").first["Value"]
473
- # lock timeout is an integer
474
- if lock_timeout.to_i > limit
475
- warn "[strong_migrations] DANGER: Lock timeout is longer than #{limit} seconds: #{lock_timeout}"
476
- end
477
- end
478
- @lock_timeout_checked = true
479
- end
480
- end
148
+ def set_timeouts
149
+ return if @timeouts_set
481
150
 
482
- # units: https://www.postgresql.org/docs/current/config-setting.html
483
- def timeout_to_sec(timeout)
484
- units = {
485
- "us" => 0.001,
486
- "ms" => 1,
487
- "s" => 1000,
488
- "min" => 1000 * 60,
489
- "h" => 1000 * 60 * 60,
490
- "d" => 1000 * 60 * 60 * 24
491
- }
492
- timeout_ms = timeout.to_i
493
- units.each do |k, v|
494
- if timeout.end_with?(k)
495
- timeout_ms *= v
496
- break
497
- end
151
+ if StrongMigrations.statement_timeout
152
+ adapter.set_statement_timeout(StrongMigrations.statement_timeout)
498
153
  end
499
- timeout_ms / 1000.0
500
- end
501
-
502
- def postgresql_timeout(timeout)
503
- if timeout.is_a?(String)
504
- timeout
505
- else
506
- # use ceil to prevent no timeout for values under 1 ms
507
- (timeout.to_f * 1000).ceil
154
+ if StrongMigrations.lock_timeout
155
+ adapter.set_lock_timeout(StrongMigrations.lock_timeout)
508
156
  end
509
- end
510
157
 
511
- def constraints(table_name)
512
- query = <<~SQL
513
- SELECT
514
- conname AS name,
515
- pg_get_constraintdef(oid) AS def
516
- FROM
517
- pg_constraint
518
- WHERE
519
- contype = 'c' AND
520
- convalidated AND
521
- conrelid = #{connection.quote(connection.quote_table_name(table_name))}::regclass
522
- SQL
523
- connection.select_all(query.squish).to_a
158
+ @timeouts_set = true
524
159
  end
525
160
 
526
- def raise_error(message_key, header: nil, append: nil, **vars)
527
- return unless StrongMigrations.check_enabled?(message_key, version: version)
528
-
529
- message = StrongMigrations.error_messages[message_key] || "Missing message"
530
- message = message + append if append
161
+ def check_lock_timeout
162
+ return if defined?(@lock_timeout_checked)
531
163
 
532
- vars[:migration_name] = @migration.class.name
533
- vars[:migration_suffix] = "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
534
- vars[:base_model] = "ApplicationRecord"
164
+ if StrongMigrations.lock_timeout_limit
165
+ adapter.check_lock_timeout(StrongMigrations.lock_timeout_limit)
166
+ end
535
167
 
536
- # escape % not followed by {
537
- message = message.gsub(/%(?!{)/, "%%") % vars if message.include?("%")
538
- @migration.stop!(message, header: header || "Dangerous operation detected")
168
+ @lock_timeout_checked = true
539
169
  end
540
170
 
541
- def constraint_str(statement, identifiers)
542
- # not all identifiers are tables, but this method of quoting should be fine
543
- statement % identifiers.map { |v| connection.quote_table_name(v) }
171
+ def safe?
172
+ self.class.safe || ENV["SAFETY_ASSURED"] || (direction == :down && !StrongMigrations.check_down) || version_safe? || @migration.reverting?
544
173
  end
545
174
 
546
- def safety_assured_str(code)
547
- "safety_assured do\n execute '#{code}' \n end"
175
+ def version
176
+ @migration.version
548
177
  end
549
178
 
550
- def command_str(command, args)
551
- str_args = args[0..-2].map { |a| a.inspect }
552
-
553
- # prettier last arg
554
- last_arg = args[-1]
555
- if last_arg.is_a?(Hash)
556
- if last_arg.any?
557
- str_args << last_arg.map do |k, v|
558
- if v.is_a?(Hash)
559
- # pretty index: {algorithm: :concurrently}
560
- "#{k}: {#{v.map { |k2, v2| "#{k2}: #{v2.inspect}" }.join(", ")}}"
179
+ def adapter
180
+ @adapter ||= begin
181
+ cls =
182
+ case connection.adapter_name
183
+ when /postg/i # PostgreSQL, PostGIS
184
+ Adapters::PostgreSQLAdapter
185
+ when /mysql|trilogy/i
186
+ if connection.try(:mariadb?)
187
+ Adapters::MariaDBAdapter
561
188
  else
562
- "#{k}: #{v.inspect}"
189
+ Adapters::MySQLAdapter
563
190
  end
564
- end.join(", ")
565
- end
566
- else
567
- str_args << last_arg.inspect
568
- end
569
-
570
- "#{command} #{str_args.join(", ")}"
571
- end
572
-
573
- def writes_blocked?
574
- query = <<~SQL
575
- SELECT
576
- relation::regclass::text
577
- FROM
578
- pg_locks
579
- WHERE
580
- mode IN ('ShareRowExclusiveLock', 'AccessExclusiveLock') AND
581
- pid = pg_backend_pid()
582
- SQL
583
- connection.select_all(query.squish).any?
584
- end
191
+ else
192
+ Adapters::AbstractAdapter
193
+ end
585
194
 
586
- def rewrite_blocks
587
- mysql? || mariadb? ? "writes" : "reads and writes"
195
+ cls.new(self)
196
+ end
588
197
  end
589
198
 
590
- def backfill_code(table, column, default)
591
- model = table.to_s.classify
592
- "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
199
+ def connection
200
+ @migration.connection
593
201
  end
594
202
 
595
- def new_table?(table)
596
- @new_tables.include?(table.to_s)
203
+ def retry_lock_timeouts?(method)
204
+ (
205
+ StrongMigrations.lock_timeout_retries > 0 &&
206
+ !in_transaction? &&
207
+ method != :transaction
208
+ )
597
209
  end
598
210
  end
599
211
  end