strong_migrations 1.6.0 → 2.8.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 +100 -1
- data/LICENSE.txt +1 -1
- data/README.md +421 -220
- data/lib/generators/strong_migrations/install_generator.rb +4 -8
- data/lib/generators/strong_migrations/templates/initializer.rb.tt +3 -0
- data/lib/strong_migrations/adapters/abstract_adapter.rb +13 -10
- data/lib/strong_migrations/adapters/mariadb_adapter.rb +2 -2
- data/lib/strong_migrations/adapters/mysql_adapter.rb +10 -5
- data/lib/strong_migrations/adapters/postgresql_adapter.rb +25 -28
- data/lib/strong_migrations/checker.rb +115 -19
- data/lib/strong_migrations/checks.rb +225 -97
- data/lib/strong_migrations/error_messages.rb +120 -14
- data/lib/strong_migrations/migration.rb +12 -6
- data/lib/strong_migrations/{database_tasks.rb → migration_context.rb} +20 -3
- data/lib/strong_migrations/migrator.rb +12 -6
- data/lib/strong_migrations/safe_methods.rb +60 -40
- data/lib/strong_migrations/schema_dumper.rb +15 -4
- data/lib/strong_migrations/version.rb +1 -1
- data/lib/strong_migrations.rb +9 -6
- metadata +7 -11
|
@@ -10,7 +10,7 @@ module StrongMigrations
|
|
|
10
10
|
if !new_table?(table)
|
|
11
11
|
if postgresql? && options[:validate] != false
|
|
12
12
|
add_options = options.merge(validate: false)
|
|
13
|
-
name = options[:name] ||
|
|
13
|
+
name = options[:name] || connection.check_constraint_options(table, expression, options)[:name]
|
|
14
14
|
validate_options = {name: name}
|
|
15
15
|
|
|
16
16
|
if StrongMigrations.safe_by_default
|
|
@@ -35,39 +35,40 @@ module StrongMigrations
|
|
|
35
35
|
# keep track of new columns of change_column_default check
|
|
36
36
|
@new_columns << [table.to_s, column.to_s]
|
|
37
37
|
|
|
38
|
-
#
|
|
38
|
+
# adding a column with a volatile default is not safe with Postgres
|
|
39
|
+
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
|
|
40
|
+
# functions like random() and clock_timestamp() are volatile
|
|
41
|
+
# functions like concat('A', 'B') are safe
|
|
42
|
+
# default expressions in Postgres cannot reference other columns
|
|
39
43
|
#
|
|
40
|
-
#
|
|
44
|
+
# adding a column with an expression default is not safe with MySQL
|
|
45
|
+
# even constant expressions like (3) are not safe
|
|
46
|
+
# literals like 3 are safe
|
|
47
|
+
#
|
|
48
|
+
# Active Record quotes default values except for procs
|
|
49
|
+
# there is also a special case for uuid columns
|
|
41
50
|
# https://github.com/rails/rails/blob/v7.0.3.1/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L92-L93
|
|
42
|
-
if
|
|
51
|
+
if !default.nil? && (!adapter.add_column_default_safe? || (volatile = (postgresql? && type.to_s == "uuid" && default.to_s.include?("()") && adapter.default_volatile?(default))))
|
|
43
52
|
if options[:null] == false
|
|
44
53
|
options = options.except(:null)
|
|
45
|
-
append = "
|
|
46
|
-
|
|
47
|
-
Then add the NOT NULL constraint in separate migrations."
|
|
54
|
+
append = "\n\nThen add the NOT NULL constraint in separate migrations."
|
|
48
55
|
end
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
remove_command: command_str("remove_column", [table, column]),
|
|
60
|
-
code: backfill_code(table, column, default, volatile),
|
|
61
|
-
append: append,
|
|
62
|
-
rewrite_blocks: adapter.rewrite_blocks,
|
|
63
|
-
default_type: (volatile ? "volatile" : "non-null")
|
|
64
|
-
end
|
|
65
|
-
elsif default.is_a?(Proc) && postgresql?
|
|
66
|
-
# adding a column with a VOLATILE default is not safe
|
|
67
|
-
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
|
|
68
|
-
# functions like random() and clock_timestamp() are VOLATILE
|
|
57
|
+
raise_error :add_column_default,
|
|
58
|
+
add_command: command_str("add_column", [table, column, type, options.except(:default)]),
|
|
59
|
+
change_command: command_str("change_column_default", [table, column, default]),
|
|
60
|
+
remove_command: command_str("remove_column", [table, column]),
|
|
61
|
+
code: backfill_code(table, column, default, volatile),
|
|
62
|
+
append: append,
|
|
63
|
+
rewrite_blocks: adapter.rewrite_blocks,
|
|
64
|
+
default_type: volatile ? "volatile" : "non-null"
|
|
65
|
+
elsif default.is_a?(Proc)
|
|
69
66
|
# check for Proc to match Active Record
|
|
70
|
-
raise_error :add_column_default_callable
|
|
67
|
+
raise_error :add_column_default_callable,
|
|
68
|
+
add_command: command_str("add_column", [table, column, type, options.except(:default)]),
|
|
69
|
+
change_command: command_str("change_column_default", [table, column]),
|
|
70
|
+
remove_command: command_str("remove_column", [table, column]),
|
|
71
|
+
default_type: postgresql? ? "volatile" : "an expression"
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
if type.to_s == "json" && postgresql?
|
|
@@ -78,6 +79,18 @@ Then add the NOT NULL constraint in separate migrations."
|
|
|
78
79
|
if type.to_s == "virtual" && options[:stored]
|
|
79
80
|
raise_error :add_column_generated_stored, rewrite_blocks: adapter.rewrite_blocks
|
|
80
81
|
end
|
|
82
|
+
|
|
83
|
+
if adapter.auto_incrementing_types.include?(type.to_s)
|
|
84
|
+
append = (mysql? || mariadb?) ? "\n\nIf using statement-based replication, this can also generate different values on replicas." : ""
|
|
85
|
+
raise_error :add_column_auto_incrementing,
|
|
86
|
+
rewrite_blocks: adapter.rewrite_blocks,
|
|
87
|
+
append: append
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
check_algorithm_option("add_column", *args, **options)
|
|
91
|
+
|
|
92
|
+
# not necessarily dangerous, but not necessary
|
|
93
|
+
check_lock_option("add_column", *args, **options)
|
|
81
94
|
end
|
|
82
95
|
|
|
83
96
|
def check_add_exclusion_constraint(*args)
|
|
@@ -114,6 +127,11 @@ Then add the NOT NULL constraint in separate migrations."
|
|
|
114
127
|
raise_error :add_foreign_key,
|
|
115
128
|
add_foreign_key_code: command_str("add_foreign_key", [from_table, to_table, options.merge(validate: false)]),
|
|
116
129
|
validate_foreign_key_code: command_str("validate_foreign_key", [from_table, to_table])
|
|
130
|
+
elsif mysql? || mariadb?
|
|
131
|
+
raise_error :add_foreign_key_mysql,
|
|
132
|
+
add_foreign_key_code: command_str("add_foreign_key", [from_table, to_table, options]),
|
|
133
|
+
# TODO exclude some options?
|
|
134
|
+
remove_foreign_key_code: command_str("remove_foreign_key", [from_table, to_table, options])
|
|
117
135
|
end
|
|
118
136
|
end
|
|
119
137
|
|
|
@@ -140,6 +158,10 @@ Then add the NOT NULL constraint in separate migrations."
|
|
|
140
158
|
|
|
141
159
|
raise_error :add_index, command: command_str("add_index", [table, columns, options.merge(algorithm: :concurrently)])
|
|
142
160
|
end
|
|
161
|
+
|
|
162
|
+
check_algorithm_option("add_index", *args, **options)
|
|
163
|
+
|
|
164
|
+
check_lock_option("add_index", *args, **options)
|
|
143
165
|
end
|
|
144
166
|
|
|
145
167
|
def check_add_reference(method, *args)
|
|
@@ -149,11 +171,15 @@ Then add the NOT NULL constraint in separate migrations."
|
|
|
149
171
|
if postgresql?
|
|
150
172
|
index_value = options.fetch(:index, true)
|
|
151
173
|
concurrently_set = index_value.is_a?(Hash) && index_value[:algorithm] == :concurrently
|
|
152
|
-
|
|
174
|
+
index_unsafe = index_value && !concurrently_set
|
|
175
|
+
|
|
176
|
+
foreign_key_value = options[:foreign_key]
|
|
177
|
+
validate_false = foreign_key_value.is_a?(Hash) && foreign_key_value[:validate] == false
|
|
178
|
+
foreign_key_unsafe = foreign_key_value && !validate_false
|
|
153
179
|
|
|
154
|
-
if
|
|
180
|
+
if index_unsafe || foreign_key_unsafe
|
|
155
181
|
if index_value.is_a?(Hash)
|
|
156
|
-
options
|
|
182
|
+
options = options.merge(index: index_value.merge(algorithm: :concurrently))
|
|
157
183
|
elsif index_value
|
|
158
184
|
options = options.merge(index: {algorithm: :concurrently})
|
|
159
185
|
end
|
|
@@ -163,11 +189,10 @@ Then add the NOT NULL constraint in separate migrations."
|
|
|
163
189
|
throw :safe
|
|
164
190
|
end
|
|
165
191
|
|
|
166
|
-
if
|
|
192
|
+
if foreign_key_unsafe
|
|
193
|
+
options.delete(:foreign_key)
|
|
167
194
|
headline = "Adding a foreign key blocks writes on both tables."
|
|
168
|
-
append = "
|
|
169
|
-
|
|
170
|
-
Then add the foreign key in separate migrations."
|
|
195
|
+
append = "\n\nThen add the foreign key in separate migrations."
|
|
171
196
|
else
|
|
172
197
|
headline = "Adding an index non-concurrently locks the table."
|
|
173
198
|
end
|
|
@@ -177,6 +202,63 @@ Then add the foreign key in separate migrations."
|
|
|
177
202
|
command: command_str(method, [table, reference, options]),
|
|
178
203
|
append: append
|
|
179
204
|
end
|
|
205
|
+
elsif mysql? || mariadb?
|
|
206
|
+
if options[:foreign_key]
|
|
207
|
+
raise_error :add_reference,
|
|
208
|
+
headline: "Adding a foreign key blocks writes on both tables.",
|
|
209
|
+
command: command_str(method, [table, reference, options.except(:foreign_key)]),
|
|
210
|
+
append: "\n\nThen add the foreign key in a separate migration."
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
check_algorithm_option("add_reference", *args, **options)
|
|
215
|
+
|
|
216
|
+
# not necessarily dangerous, but not necessary
|
|
217
|
+
check_lock_option("add_reference", *args, **options)
|
|
218
|
+
|
|
219
|
+
if (mysql? || mariadb?) && !new_table?(table)
|
|
220
|
+
index_value = options[:index]
|
|
221
|
+
copy_set = index_value.is_a?(Hash) && index_value[:algorithm] == :copy
|
|
222
|
+
if copy_set
|
|
223
|
+
index_value = index_value.except(:algorithm)
|
|
224
|
+
if index_value.empty?
|
|
225
|
+
options = options.except(:index)
|
|
226
|
+
else
|
|
227
|
+
options = options.merge(index: index_value)
|
|
228
|
+
end
|
|
229
|
+
raise_error :copy_algorithm, command: command_str("add_reference", args + [options])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
if ar_version >= 8.2
|
|
233
|
+
lock = index_value.is_a?(Hash) && index_value[:lock]
|
|
234
|
+
if [:shared, :exclusive].include?(lock)
|
|
235
|
+
index_value = index_value.except(:lock)
|
|
236
|
+
if index_value.empty?
|
|
237
|
+
options = options.except(:index)
|
|
238
|
+
else
|
|
239
|
+
options = options.merge(index: index_value)
|
|
240
|
+
end
|
|
241
|
+
raise_error :lock_option,
|
|
242
|
+
command: command_str(method, args + [options]),
|
|
243
|
+
lock_type: lock.to_s,
|
|
244
|
+
lock_blocks: lock == :shared ? "reads" : "reads and writes"
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def check_add_unique_constraint(*args)
|
|
251
|
+
args.extract_options!
|
|
252
|
+
table, column = args
|
|
253
|
+
|
|
254
|
+
# column and using_index cannot be used together
|
|
255
|
+
# check for column to ensure error message can be generated
|
|
256
|
+
if column && !new_table?(table)
|
|
257
|
+
index_name = connection.index_name(table, {column: column})
|
|
258
|
+
raise_error :add_unique_constraint,
|
|
259
|
+
index_command: command_str("add_index", [table, column, {unique: true, algorithm: :concurrently}]),
|
|
260
|
+
constraint_command: command_str("add_unique_constraint", [table, {using_index: index_name}]),
|
|
261
|
+
remove_command: command_str("remove_unique_constraint", [table, column])
|
|
180
262
|
end
|
|
181
263
|
end
|
|
182
264
|
|
|
@@ -199,17 +281,48 @@ Then add the foreign key in separate migrations."
|
|
|
199
281
|
end
|
|
200
282
|
|
|
201
283
|
raise_error :change_column, rewrite_blocks: adapter.rewrite_blocks unless safe
|
|
284
|
+
|
|
285
|
+
# constraints must be rechecked
|
|
286
|
+
# Postgres recommends dropping constraints before and adding them back
|
|
287
|
+
# https://www.postgresql.org/docs/current/ddl-alter.html#DDL-ALTER-COLUMN-TYPE
|
|
288
|
+
if postgresql?
|
|
289
|
+
constraints = adapter.constraints(table, column)
|
|
290
|
+
if constraints.any?
|
|
291
|
+
change_commands = []
|
|
292
|
+
constraints.each do |c|
|
|
293
|
+
change_commands << command_str("remove_check_constraint", [table, c.expression, {name: c.name}])
|
|
294
|
+
end
|
|
295
|
+
change_commands << command_str("change_column", args + [options])
|
|
296
|
+
constraints.each do |c|
|
|
297
|
+
change_commands << command_str("add_check_constraint", [table, c.expression, {name: c.name, validate: false}])
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
validate_commands = []
|
|
301
|
+
constraints.each do |c|
|
|
302
|
+
validate_commands << command_str("validate_check_constraint", [table, {name: c.name}])
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
raise_error :change_column_constraint,
|
|
306
|
+
change_column_code: change_commands.join("\n "),
|
|
307
|
+
validate_constraint_code: validate_commands.join("\n ")
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
check_algorithm_option("change_column", *args, **options)
|
|
312
|
+
|
|
313
|
+
# not necessarily dangerous, but not necessary
|
|
314
|
+
check_lock_option("change_column", *args, **options)
|
|
202
315
|
end
|
|
203
316
|
|
|
204
317
|
def check_change_column_default(*args)
|
|
205
318
|
table, column, _default_or_changes = args
|
|
206
319
|
|
|
207
320
|
# just check ActiveRecord::Base, even though can override on model
|
|
208
|
-
partial_inserts =
|
|
321
|
+
partial_inserts = ActiveRecord::Base.partial_inserts
|
|
209
322
|
|
|
210
323
|
if partial_inserts && !new_column?(table, column)
|
|
211
324
|
raise_error :change_column_default,
|
|
212
|
-
config:
|
|
325
|
+
config: "partial_inserts"
|
|
213
326
|
end
|
|
214
327
|
end
|
|
215
328
|
|
|
@@ -217,60 +330,40 @@ Then add the foreign key in separate migrations."
|
|
|
217
330
|
table, column, null, default = args
|
|
218
331
|
if !null
|
|
219
332
|
if postgresql?
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if safe_with_check_constraint
|
|
223
|
-
safe = adapter.constraints(table).any? { |c| c["def"] == "CHECK ((#{column} IS NOT NULL))" || c["def"] == "CHECK ((#{connection.quote_column_name(column)} IS NOT NULL))" }
|
|
224
|
-
end
|
|
333
|
+
constraints = connection.check_constraints(table)
|
|
334
|
+
safe = constraints.any? { |c| c.options[:validate] && (c.expression == "#{column} IS NOT NULL" || c.expression == "#{connection.quote_column_name(column)} IS NOT NULL") }
|
|
225
335
|
|
|
226
336
|
unless safe
|
|
337
|
+
expression = "#{quote_column_if_needed(column)} IS NOT NULL"
|
|
338
|
+
|
|
227
339
|
# match https://github.com/nullobject/rein
|
|
228
340
|
constraint_name = "#{table}_#{column}_null"
|
|
341
|
+
if adapter.max_constraint_name_length && constraint_name.bytesize > adapter.max_constraint_name_length
|
|
342
|
+
constraint_name = connection.check_constraint_options(table, expression, {})[:name]
|
|
229
343
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
constraint_methods = ar_version >= 6.1
|
|
235
|
-
|
|
236
|
-
validate_constraint_code =
|
|
237
|
-
if constraint_methods
|
|
238
|
-
String.new(command_str(:validate_check_constraint, [table, {name: constraint_name}]))
|
|
239
|
-
else
|
|
240
|
-
String.new(safety_assured_str(validate_code))
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
if safe_with_check_constraint
|
|
244
|
-
change_args = [table, column, null]
|
|
245
|
-
|
|
246
|
-
validate_constraint_code << "\n #{command_str(:change_column_null, change_args)}"
|
|
247
|
-
|
|
248
|
-
if constraint_methods
|
|
249
|
-
validate_constraint_code << "\n #{command_str(:remove_check_constraint, [table, {name: constraint_name}])}"
|
|
250
|
-
else
|
|
251
|
-
validate_constraint_code << "\n #{safety_assured_str(remove_code)}"
|
|
344
|
+
# avoid collision with Active Record naming for safe_by_default
|
|
345
|
+
if StrongMigrations.safe_by_default
|
|
346
|
+
constraint_name = constraint_name.sub("rails", "strong_migrations")
|
|
252
347
|
end
|
|
253
348
|
end
|
|
254
349
|
|
|
350
|
+
add_args = [table, expression, {name: constraint_name, validate: false}]
|
|
351
|
+
validate_args = [table, {name: constraint_name}]
|
|
352
|
+
change_args = [table, column, null]
|
|
353
|
+
remove_args = [table, {name: constraint_name}]
|
|
354
|
+
|
|
255
355
|
if StrongMigrations.safe_by_default
|
|
256
|
-
safe_change_column_null(
|
|
356
|
+
safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
|
|
257
357
|
throw :safe
|
|
258
358
|
end
|
|
259
359
|
|
|
260
|
-
add_constraint_code =
|
|
261
|
-
if constraint_methods
|
|
262
|
-
command_str(:add_check_constraint, [table, "#{quote_column_if_needed(column)} IS NOT NULL", {name: constraint_name, validate: false}])
|
|
263
|
-
else
|
|
264
|
-
safety_assured_str(add_code)
|
|
265
|
-
end
|
|
360
|
+
add_constraint_code = command_str("add_check_constraint", add_args)
|
|
266
361
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
"def change\n #{validate_constraint_code}\n end"
|
|
273
|
-
end
|
|
362
|
+
up_code = String.new(command_str("validate_check_constraint", validate_args))
|
|
363
|
+
up_code << "\n #{command_str("change_column_null", change_args)}"
|
|
364
|
+
up_code << "\n #{command_str("remove_check_constraint", remove_args)}"
|
|
365
|
+
down_code = "#{add_constraint_code}\n #{command_str("change_column_null", [table, column, true])}"
|
|
366
|
+
validate_constraint_code = "def up\n #{up_code}\n end\n\n def down\n #{down_code}\n end"
|
|
274
367
|
|
|
275
368
|
raise_error :change_column_null_postgresql,
|
|
276
369
|
add_constraint_code: add_constraint_code,
|
|
@@ -315,33 +408,33 @@ Then add the foreign key in separate migrations."
|
|
|
315
408
|
raise_error :execute, header: "Possibly dangerous operation"
|
|
316
409
|
end
|
|
317
410
|
|
|
411
|
+
# supports algorithm and lock options, but always raises
|
|
318
412
|
def check_remove_column(method, *args)
|
|
319
413
|
columns =
|
|
320
414
|
case method
|
|
321
415
|
when :remove_timestamps
|
|
322
|
-
[
|
|
416
|
+
[:created_at, :updated_at]
|
|
323
417
|
when :remove_column
|
|
324
|
-
[args[1]
|
|
418
|
+
[args[1]]
|
|
325
419
|
when :remove_columns
|
|
326
|
-
# Active Record 6.1+ supports options
|
|
327
420
|
if args.last.is_a?(Hash)
|
|
328
|
-
args[1..-2]
|
|
421
|
+
args[1..-2]
|
|
329
422
|
else
|
|
330
|
-
args[1..-1]
|
|
423
|
+
args[1..-1]
|
|
331
424
|
end
|
|
332
425
|
else
|
|
333
426
|
options = args[2] || {}
|
|
334
427
|
reference = args[1]
|
|
335
428
|
cols = []
|
|
336
|
-
cols << "#{reference}_type" if options[:polymorphic]
|
|
337
|
-
cols << "#{reference}_id"
|
|
429
|
+
cols << "#{reference}_type".to_sym if options[:polymorphic]
|
|
430
|
+
cols << "#{reference}_id".to_sym
|
|
338
431
|
cols
|
|
339
432
|
end
|
|
340
433
|
|
|
341
|
-
code = "self.ignored_columns
|
|
434
|
+
code = "self.ignored_columns += #{columns.map(&:to_s).inspect}"
|
|
342
435
|
|
|
343
436
|
raise_error :remove_column,
|
|
344
|
-
model: args[0]
|
|
437
|
+
model: model_name(args[0]),
|
|
345
438
|
code: code,
|
|
346
439
|
command: command_str(method, args),
|
|
347
440
|
column_suffix: columns.size > 1 ? "s" : ""
|
|
@@ -355,12 +448,6 @@ Then add the foreign key in separate migrations."
|
|
|
355
448
|
# avoid suggesting extra (invalid) args
|
|
356
449
|
args = args[0..1] unless StrongMigrations.safe_by_default
|
|
357
450
|
|
|
358
|
-
# Active Record < 6.1 only supports two arguments (including options)
|
|
359
|
-
if args.size == 2 && ar_version < 6.1
|
|
360
|
-
# arg takes precedence over option
|
|
361
|
-
options[:column] = args.pop
|
|
362
|
-
end
|
|
363
|
-
|
|
364
451
|
if StrongMigrations.safe_by_default
|
|
365
452
|
safe_remove_index(*args, **options)
|
|
366
453
|
throw :safe
|
|
@@ -368,12 +455,30 @@ Then add the foreign key in separate migrations."
|
|
|
368
455
|
|
|
369
456
|
raise_error :remove_index, command: command_str("remove_index", args + [options.merge(algorithm: :concurrently)])
|
|
370
457
|
end
|
|
458
|
+
|
|
459
|
+
check_algorithm_option("remove_index", *args, **options)
|
|
460
|
+
|
|
461
|
+
# not necessarily dangerous, but not necessary
|
|
462
|
+
check_lock_option("remove_index", *args, **options)
|
|
371
463
|
end
|
|
372
464
|
|
|
465
|
+
# supports algorithm and lock options, but always raises
|
|
373
466
|
def check_rename_column
|
|
374
467
|
raise_error :rename_column
|
|
375
468
|
end
|
|
376
469
|
|
|
470
|
+
def check_rename_enum_value(*args)
|
|
471
|
+
options = args.extract_options!
|
|
472
|
+
type_name, _ = args
|
|
473
|
+
|
|
474
|
+
raise_error :rename_enum_value,
|
|
475
|
+
command: command_str("add_enum_value", [type_name, options[:to], {after: options[:from]}])
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def check_rename_schema
|
|
479
|
+
raise_error :rename_schema
|
|
480
|
+
end
|
|
481
|
+
|
|
377
482
|
def check_rename_table
|
|
378
483
|
raise_error :rename_table
|
|
379
484
|
end
|
|
@@ -415,7 +520,7 @@ Then add the foreign key in separate migrations."
|
|
|
415
520
|
message = message + append if append
|
|
416
521
|
|
|
417
522
|
vars[:migration_name] = @migration.class.name
|
|
418
|
-
vars[:migration_suffix] =
|
|
523
|
+
vars[:migration_suffix] = migration_suffix
|
|
419
524
|
vars[:base_model] = "ApplicationRecord"
|
|
420
525
|
|
|
421
526
|
# escape % not followed by {
|
|
@@ -423,6 +528,21 @@ Then add the foreign key in separate migrations."
|
|
|
423
528
|
@migration.stop!(message, header: header || "Dangerous operation detected")
|
|
424
529
|
end
|
|
425
530
|
|
|
531
|
+
def check_algorithm_option(method, *args, **options)
|
|
532
|
+
if (mysql? || mariadb?) && options[:algorithm] == :copy && !new_table?(args[0]) && (ar_version >= 8.2 || method == "add_index")
|
|
533
|
+
raise_error :copy_algorithm, command: command_str(method, args + [options.except(:algorithm)])
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def check_lock_option(method, *args, **options)
|
|
538
|
+
if (mysql? || mariadb?) && [:shared, :exclusive].include?(options[:lock]) && !new_table?(args[0]) && ar_version >= 8.2
|
|
539
|
+
raise_error :lock_option,
|
|
540
|
+
command: command_str(method, args + [options.except(:lock)]),
|
|
541
|
+
lock_type: options[:lock].to_s,
|
|
542
|
+
lock_blocks: options[:lock] == :shared ? "reads" : "reads and writes"
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
426
546
|
def constraint_str(statement, identifiers)
|
|
427
547
|
# not all identifiers are tables, but this method of quoting should be fine
|
|
428
548
|
statement % identifiers.map { |v| connection.quote_table_name(v) }
|
|
@@ -456,20 +576,20 @@ Then add the foreign key in separate migrations."
|
|
|
456
576
|
end
|
|
457
577
|
|
|
458
578
|
def backfill_code(table, column, default, function = false)
|
|
459
|
-
model = table
|
|
579
|
+
model = model_name(table)
|
|
460
580
|
if function
|
|
461
581
|
# update_all(column: Arel.sql(default)) also works in newer versions of Active Record
|
|
462
582
|
update_expr = "#{quote_column_if_needed(column)} = #{default}"
|
|
463
|
-
"#{model}.unscoped.in_batches do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
|
|
583
|
+
"#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
|
|
464
584
|
else
|
|
465
|
-
"#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
|
|
585
|
+
"#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
|
|
466
586
|
end
|
|
467
587
|
end
|
|
468
588
|
|
|
469
589
|
# only quote when needed
|
|
470
590
|
# important! only use for display purposes
|
|
471
591
|
def quote_column_if_needed(column)
|
|
472
|
-
|
|
592
|
+
/\A[a-z0-9_]+\z/.match?(column.to_s) ? column : connection.quote_column_name(column)
|
|
473
593
|
end
|
|
474
594
|
|
|
475
595
|
def new_table?(table)
|
|
@@ -479,5 +599,13 @@ Then add the foreign key in separate migrations."
|
|
|
479
599
|
def new_column?(table, column)
|
|
480
600
|
new_table?(table) || @new_columns.include?([table.to_s, column.to_s])
|
|
481
601
|
end
|
|
602
|
+
|
|
603
|
+
def migration_suffix
|
|
604
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def model_name(table)
|
|
608
|
+
table.to_s.classify
|
|
609
|
+
end
|
|
482
610
|
end
|
|
483
611
|
end
|