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