strong_migrations 0.7.4 → 0.7.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/LICENSE.txt +1 -1
- data/README.md +101 -29
- data/lib/strong_migrations.rb +24 -0
- data/lib/strong_migrations/checker.rb +52 -5
- data/lib/strong_migrations/safe_methods.rb +14 -1
- data/lib/strong_migrations/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2361d565fefd198370b908622526d01c4b5052f5361947c6410d1176b21b20a9
|
4
|
+
data.tar.gz: c9ee9a7b444980b957f60dbb09290a5296a94e9f121af6b1d83756b2645ab54e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca57e53122e37d7674143a48a5699bacf07f07f3c638abbf71331a3c4686a1e08f4f1fa02160e99ea0e21ed3a811aa43dc11581143b0229f5201ddc335fd792b
|
7
|
+
data.tar.gz: 4b760e24cda9a14efed64300992645ec9e4b207bbafb5210bdfd2195764980cc0ab08ebbd6693aac6518d5302da918a74e20d5d282d654700efe89e4a8418a63
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -43,7 +43,7 @@ end
|
|
43
43
|
|
44
44
|
Deploy the code, then wrap this step in a safety_assured { ... } block.
|
45
45
|
|
46
|
-
class RemoveColumn < ActiveRecord::Migration[6.
|
46
|
+
class RemoveColumn < ActiveRecord::Migration[6.1]
|
47
47
|
def change
|
48
48
|
safety_assured { remove_column :users, :name }
|
49
49
|
end
|
@@ -66,6 +66,7 @@ Potentially dangerous operations:
|
|
66
66
|
- [renaming a column](#renaming-a-column)
|
67
67
|
- [renaming a table](#renaming-a-table)
|
68
68
|
- [creating a table with the force option](#creating-a-table-with-the-force-option)
|
69
|
+
- [adding a check constraint](#adding-a-check-constraint)
|
69
70
|
- [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
|
70
71
|
- [executing SQL directly](#executing-SQL-directly)
|
71
72
|
|
@@ -89,7 +90,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
|
|
89
90
|
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
90
91
|
|
91
92
|
```ruby
|
92
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.
|
93
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
|
93
94
|
def change
|
94
95
|
remove_column :users, :some_column
|
95
96
|
end
|
@@ -110,7 +111,7 @@ end
|
|
110
111
|
3. Write a migration to remove the column (wrap in `safety_assured` block)
|
111
112
|
|
112
113
|
```ruby
|
113
|
-
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.
|
114
|
+
class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
|
114
115
|
def change
|
115
116
|
safety_assured { remove_column :users, :some_column }
|
116
117
|
end
|
@@ -126,7 +127,7 @@ end
|
|
126
127
|
In earlier versions of Postgres, MySQL, and MariaDB, adding a column with a default value to an existing table causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
127
128
|
|
128
129
|
```ruby
|
129
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[6.
|
130
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
|
130
131
|
def change
|
131
132
|
add_column :users, :some_column, :text, default: "default_value"
|
132
133
|
end
|
@@ -140,7 +141,7 @@ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a t
|
|
140
141
|
Instead, add the column without a default value, then change the default.
|
141
142
|
|
142
143
|
```ruby
|
143
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[6.
|
144
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
|
144
145
|
def up
|
145
146
|
add_column :users, :some_column, :text
|
146
147
|
change_column_default :users, :some_column, "default_value"
|
@@ -161,7 +162,7 @@ See the next section for how to backfill.
|
|
161
162
|
Active Record creates a transaction around each migration, and backfilling in the same transaction that alters a table keeps the table locked for the [duration of the backfill](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
|
162
163
|
|
163
164
|
```ruby
|
164
|
-
class AddSomeColumnToUsers < ActiveRecord::Migration[6.
|
165
|
+
class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
|
165
166
|
def change
|
166
167
|
add_column :users, :some_column, :text
|
167
168
|
User.update_all some_column: "default_value"
|
@@ -176,7 +177,7 @@ Also, running a single query to update data can cause issues for large tables.
|
|
176
177
|
There are three keys to backfilling safely: batching, throttling, and running it outside a transaction. Use the Rails console or a separate migration with `disable_ddl_transaction!`.
|
177
178
|
|
178
179
|
```ruby
|
179
|
-
class BackfillSomeColumn < ActiveRecord::Migration[6.
|
180
|
+
class BackfillSomeColumn < ActiveRecord::Migration[6.1]
|
180
181
|
disable_ddl_transaction!
|
181
182
|
|
182
183
|
def up
|
@@ -195,7 +196,7 @@ end
|
|
195
196
|
Changing the type of a column causes the entire table to be rewritten. During this time, reads and writes are blocked in Postgres, and writes are blocked in MySQL and MariaDB.
|
196
197
|
|
197
198
|
```ruby
|
198
|
-
class ChangeSomeColumnType < ActiveRecord::Migration[6.
|
199
|
+
class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
|
199
200
|
def change
|
200
201
|
change_column :users, :some_column, :new_type
|
201
202
|
end
|
@@ -234,7 +235,7 @@ A safer approach is to:
|
|
234
235
|
Renaming a column that’s in use will cause errors in your application.
|
235
236
|
|
236
237
|
```ruby
|
237
|
-
class RenameSomeColumn < ActiveRecord::Migration[6.
|
238
|
+
class RenameSomeColumn < ActiveRecord::Migration[6.1]
|
238
239
|
def change
|
239
240
|
rename_column :users, :some_column, :new_name
|
240
241
|
end
|
@@ -259,7 +260,7 @@ A safer approach is to:
|
|
259
260
|
Renaming a table that’s in use will cause errors in your application.
|
260
261
|
|
261
262
|
```ruby
|
262
|
-
class RenameUsersToCustomers < ActiveRecord::Migration[6.
|
263
|
+
class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
|
263
264
|
def change
|
264
265
|
rename_table :users, :customers
|
265
266
|
end
|
@@ -284,7 +285,7 @@ A safer approach is to:
|
|
284
285
|
The `force` option can drop an existing table.
|
285
286
|
|
286
287
|
```ruby
|
287
|
-
class CreateUsers < ActiveRecord::Migration[6.
|
288
|
+
class CreateUsers < ActiveRecord::Migration[6.1]
|
288
289
|
def change
|
289
290
|
create_table :users, force: true do |t|
|
290
291
|
# ...
|
@@ -298,7 +299,7 @@ end
|
|
298
299
|
Create tables without the `force` option.
|
299
300
|
|
300
301
|
```ruby
|
301
|
-
class CreateUsers < ActiveRecord::Migration[6.
|
302
|
+
class CreateUsers < ActiveRecord::Migration[6.1]
|
302
303
|
def change
|
303
304
|
create_table :users do |t|
|
304
305
|
# ...
|
@@ -309,6 +310,48 @@ end
|
|
309
310
|
|
310
311
|
If you intend to drop an existing table, run `drop_table` first.
|
311
312
|
|
313
|
+
### Adding a check constraint
|
314
|
+
|
315
|
+
:turtle: Safe by default available
|
316
|
+
|
317
|
+
#### Bad
|
318
|
+
|
319
|
+
Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
class AddCheckConstraint < ActiveRecord::Migration[6.1]
|
323
|
+
def change
|
324
|
+
add_check_constraint :users, "price > 0", name: "price_check"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
#### Good - Postgres
|
330
|
+
|
331
|
+
Add the check constraint without validating existing rows:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
class AddCheckConstraint < ActiveRecord::Migration[6.1]
|
335
|
+
def change
|
336
|
+
add_check_constraint :users, "price > 0", name: "price_check", validate: false
|
337
|
+
end
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
341
|
+
Then validate them in a separate migration.
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
class ValidateCheckConstraint < ActiveRecord::Migration[6.1]
|
345
|
+
def change
|
346
|
+
validate_check_constraint :users, name: "price_check"
|
347
|
+
end
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
351
|
+
#### Good - MySQL and MariaDB
|
352
|
+
|
353
|
+
[Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this (check constraints can be added with `NOT ENFORCED`, but enforcing blocks writes).
|
354
|
+
|
312
355
|
### Setting NOT NULL on an existing column
|
313
356
|
|
314
357
|
:turtle: Safe by default available
|
@@ -318,7 +361,7 @@ If you intend to drop an existing table, run `drop_table` first.
|
|
318
361
|
Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
|
319
362
|
|
320
363
|
```ruby
|
321
|
-
class SetSomeColumnNotNull < ActiveRecord::Migration[6.
|
364
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
|
322
365
|
def change
|
323
366
|
change_column_null :users, :some_column, false
|
324
367
|
end
|
@@ -327,7 +370,19 @@ end
|
|
327
370
|
|
328
371
|
#### Good - Postgres
|
329
372
|
|
330
|
-
Instead, add a check constraint
|
373
|
+
Instead, add a check constraint.
|
374
|
+
|
375
|
+
For Rails 6.1, use:
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
|
379
|
+
def change
|
380
|
+
add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
|
381
|
+
end
|
382
|
+
end
|
383
|
+
```
|
384
|
+
|
385
|
+
For Rails < 6.1, use:
|
331
386
|
|
332
387
|
```ruby
|
333
388
|
class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
@@ -339,7 +394,23 @@ class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
|
339
394
|
end
|
340
395
|
```
|
341
396
|
|
342
|
-
Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column
|
397
|
+
Then validate it in a separate migration. A `NOT NULL` check constraint is [functionally equivalent](https://medium.com/doctolib/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c) to setting `NOT NULL` on the column (but it won’t show up in `schema.rb` in Rails < 6.1). In Postgres 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
|
398
|
+
|
399
|
+
For Rails 6.1, use:
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.1]
|
403
|
+
def change
|
404
|
+
validate_check_constraint :users, name: "users_some_column_null"
|
405
|
+
|
406
|
+
# in Postgres 12+, you can then safely set NOT NULL on the column
|
407
|
+
change_column_null :users, :some_column, false
|
408
|
+
remove_check_constraint :users, name: "users_some_column_null"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
```
|
412
|
+
|
413
|
+
For Rails < 6.1, use:
|
343
414
|
|
344
415
|
```ruby
|
345
416
|
class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
|
@@ -366,7 +437,7 @@ end
|
|
366
437
|
Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
|
367
438
|
|
368
439
|
```ruby
|
369
|
-
class ExecuteSQL < ActiveRecord::Migration[6.
|
440
|
+
class ExecuteSQL < ActiveRecord::Migration[6.1]
|
370
441
|
def change
|
371
442
|
safety_assured { execute "..." }
|
372
443
|
end
|
@@ -382,7 +453,7 @@ end
|
|
382
453
|
In Postgres, adding an index non-concurrently blocks writes.
|
383
454
|
|
384
455
|
```ruby
|
385
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[6.
|
456
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
386
457
|
def change
|
387
458
|
add_index :users, :some_column
|
388
459
|
end
|
@@ -394,7 +465,7 @@ end
|
|
394
465
|
Add indexes concurrently.
|
395
466
|
|
396
467
|
```ruby
|
397
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[6.
|
468
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
398
469
|
disable_ddl_transaction!
|
399
470
|
|
400
471
|
def change
|
@@ -420,7 +491,7 @@ rails g index table column
|
|
420
491
|
Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
|
421
492
|
|
422
493
|
```ruby
|
423
|
-
class AddReferenceToUsers < ActiveRecord::Migration[6.
|
494
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.1]
|
424
495
|
def change
|
425
496
|
add_reference :users, :city
|
426
497
|
end
|
@@ -432,7 +503,7 @@ end
|
|
432
503
|
Make sure the index is added concurrently.
|
433
504
|
|
434
505
|
```ruby
|
435
|
-
class AddReferenceToUsers < ActiveRecord::Migration[6.
|
506
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.1]
|
436
507
|
disable_ddl_transaction!
|
437
508
|
|
438
509
|
def change
|
@@ -450,7 +521,7 @@ end
|
|
450
521
|
In Postgres, adding a foreign key blocks writes on both tables.
|
451
522
|
|
452
523
|
```ruby
|
453
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.
|
524
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
|
454
525
|
def change
|
455
526
|
add_foreign_key :users, :orders
|
456
527
|
end
|
@@ -460,7 +531,7 @@ end
|
|
460
531
|
or
|
461
532
|
|
462
533
|
```ruby
|
463
|
-
class AddReferenceToUsers < ActiveRecord::Migration[6.
|
534
|
+
class AddReferenceToUsers < ActiveRecord::Migration[6.1]
|
464
535
|
def change
|
465
536
|
add_reference :users, :order, foreign_key: true
|
466
537
|
end
|
@@ -474,7 +545,7 @@ Add the foreign key without validating existing rows, then validate them in a se
|
|
474
545
|
For Rails 5.2+, use:
|
475
546
|
|
476
547
|
```ruby
|
477
|
-
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.
|
548
|
+
class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
|
478
549
|
def change
|
479
550
|
add_foreign_key :users, :orders, validate: false
|
480
551
|
end
|
@@ -484,7 +555,7 @@ end
|
|
484
555
|
Then:
|
485
556
|
|
486
557
|
```ruby
|
487
|
-
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.
|
558
|
+
class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
|
488
559
|
def change
|
489
560
|
validate_foreign_key :users, :orders
|
490
561
|
end
|
@@ -522,7 +593,7 @@ end
|
|
522
593
|
In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
|
523
594
|
|
524
595
|
```ruby
|
525
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[6.
|
596
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
|
526
597
|
def change
|
527
598
|
add_column :users, :properties, :json
|
528
599
|
end
|
@@ -534,7 +605,7 @@ end
|
|
534
605
|
Use `jsonb` instead.
|
535
606
|
|
536
607
|
```ruby
|
537
|
-
class AddPropertiesToUsers < ActiveRecord::Migration[6.
|
608
|
+
class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
|
538
609
|
def change
|
539
610
|
add_column :users, :properties, :jsonb
|
540
611
|
end
|
@@ -548,7 +619,7 @@ end
|
|
548
619
|
Adding a non-unique index with more than three columns rarely improves performance.
|
549
620
|
|
550
621
|
```ruby
|
551
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[6.
|
622
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
552
623
|
def change
|
553
624
|
add_index :users, [:a, :b, :c, :d]
|
554
625
|
end
|
@@ -560,7 +631,7 @@ end
|
|
560
631
|
Instead, start an index with columns that narrow down the results the most.
|
561
632
|
|
562
633
|
```ruby
|
563
|
-
class AddSomeIndexToUsers < ActiveRecord::Migration[6.
|
634
|
+
class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
|
564
635
|
def change
|
565
636
|
add_index :users, [:b, :d]
|
566
637
|
end
|
@@ -574,7 +645,7 @@ For Postgres, be sure to add them concurrently.
|
|
574
645
|
To mark a step in the migration as safe, despite using a method that might otherwise be dangerous, wrap it in a `safety_assured` block.
|
575
646
|
|
576
647
|
```ruby
|
577
|
-
class MySafeMigration < ActiveRecord::Migration[6.
|
648
|
+
class MySafeMigration < ActiveRecord::Migration[6.1]
|
578
649
|
def change
|
579
650
|
safety_assured { remove_column :users, :some_column }
|
580
651
|
end
|
@@ -591,6 +662,7 @@ Make operations safe by default.
|
|
591
662
|
|
592
663
|
- adding and removing an index
|
593
664
|
- adding a foreign key
|
665
|
+
- adding a check constraint
|
594
666
|
- setting NOT NULL on an existing column
|
595
667
|
|
596
668
|
Add to `config/initializers/strong_migrations.rb`:
|
data/lib/strong_migrations.rb
CHANGED
@@ -216,6 +216,30 @@ end",
|
|
216
216
|
|
217
217
|
validate_foreign_key:
|
218
218
|
"Validating a foreign key while writes are blocked is dangerous.
|
219
|
+
Use disable_ddl_transaction! or a separate migration.",
|
220
|
+
|
221
|
+
add_check_constraint:
|
222
|
+
"Adding a check constraint key blocks reads and writes while every row is checked.
|
223
|
+
Instead, add the check constraint without validating existing rows,
|
224
|
+
then validate them in a separate migration.
|
225
|
+
|
226
|
+
class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
|
227
|
+
def change
|
228
|
+
%{add_check_constraint_code}
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
|
233
|
+
def change
|
234
|
+
%{validate_check_constraint_code}
|
235
|
+
end
|
236
|
+
end",
|
237
|
+
|
238
|
+
add_check_constraint_mysql:
|
239
|
+
"Adding a check constraint to an existing table is not safe with your database engine.",
|
240
|
+
|
241
|
+
validate_check_constraint:
|
242
|
+
"Validating a check constraint while writes are blocked is dangerous.
|
219
243
|
Use disable_ddl_transaction! or a separate migration."
|
220
244
|
}
|
221
245
|
self.enabled_checks = (error_messages.keys - [:remove_index]).map { |k| [k, {}] }.to_h
|
@@ -231,18 +231,38 @@ Then add the foreign key in separate migrations."
|
|
231
231
|
validate_code = constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
|
232
232
|
remove_code = constraint_str("ALTER TABLE %s DROP CONSTRAINT %s", [table, constraint_name])
|
233
233
|
|
234
|
-
validate_constraint_code =
|
234
|
+
validate_constraint_code =
|
235
|
+
if ar_version >= 6.1
|
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
|
+
|
235
241
|
if postgresql_version >= Gem::Version.new("12")
|
236
242
|
change_args = [table, column, null]
|
237
243
|
|
238
244
|
validate_constraint_code << "\n #{command_str(:change_column_null, change_args)}"
|
239
|
-
|
245
|
+
|
246
|
+
if ar_version >= 6.1
|
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
|
240
251
|
end
|
241
252
|
|
242
253
|
return safe_change_column_null(add_code, validate_code, change_args, remove_code) if StrongMigrations.safe_by_default
|
243
254
|
|
255
|
+
add_constraint_code =
|
256
|
+
if ar_version >= 6.1
|
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
|
+
|
244
264
|
raise_error :change_column_null_postgresql,
|
245
|
-
add_constraint_code:
|
265
|
+
add_constraint_code: add_constraint_code,
|
246
266
|
validate_constraint_code: validate_constraint_code
|
247
267
|
end
|
248
268
|
elsif mysql? || mariadb?
|
@@ -257,10 +277,10 @@ Then add the foreign key in separate migrations."
|
|
257
277
|
options ||= {}
|
258
278
|
|
259
279
|
# always validated before 5.2
|
260
|
-
validate = options.fetch(:validate, true) ||
|
280
|
+
validate = options.fetch(:validate, true) || ar_version < 5.2
|
261
281
|
|
262
282
|
if postgresql? && validate
|
263
|
-
if
|
283
|
+
if ar_version < 5.2
|
264
284
|
# fk name logic from rails
|
265
285
|
primary_key = options[:primary_key] || "id"
|
266
286
|
column = options[:column] || "#{to_table.to_s.singularize}_id"
|
@@ -287,6 +307,29 @@ Then add the foreign key in separate migrations."
|
|
287
307
|
if postgresql? && writes_blocked?
|
288
308
|
raise_error :validate_foreign_key
|
289
309
|
end
|
310
|
+
when :add_check_constraint
|
311
|
+
table, expression, options = args
|
312
|
+
options ||= {}
|
313
|
+
|
314
|
+
if !new_table?(table)
|
315
|
+
if postgresql? && options[:validate] != false
|
316
|
+
add_options = options.merge(validate: false)
|
317
|
+
name = options[:name] || @migration.check_constraint_options(table, expression, options)[:name]
|
318
|
+
validate_options = {name: name}
|
319
|
+
|
320
|
+
return safe_add_check_constraint(table, expression, add_options, validate_options) if StrongMigrations.safe_by_default
|
321
|
+
|
322
|
+
raise_error :add_check_constraint,
|
323
|
+
add_check_constraint_code: command_str("add_check_constraint", [table, expression, add_options]),
|
324
|
+
validate_check_constraint_code: command_str("validate_check_constraint", [table, validate_options])
|
325
|
+
elsif mysql? || mariadb?
|
326
|
+
raise_error :add_check_constraint_mysql
|
327
|
+
end
|
328
|
+
end
|
329
|
+
when :validate_check_constraint
|
330
|
+
if postgresql? && writes_blocked?
|
331
|
+
raise_error :validate_check_constraint
|
332
|
+
end
|
290
333
|
end
|
291
334
|
|
292
335
|
StrongMigrations.checks.each do |check|
|
@@ -410,6 +453,10 @@ Then add the foreign key in separate migrations."
|
|
410
453
|
Gem::Version.new(version)
|
411
454
|
end
|
412
455
|
|
456
|
+
def ar_version
|
457
|
+
ActiveRecord::VERSION::STRING.to_f
|
458
|
+
end
|
459
|
+
|
413
460
|
def check_lock_timeout
|
414
461
|
limit = StrongMigrations.lock_timeout_limit
|
415
462
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module StrongMigrations
|
2
2
|
module SafeMethods
|
3
3
|
def safe_by_default_method?(method)
|
4
|
-
StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :change_column_null].include?(method)
|
4
|
+
StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
|
5
5
|
end
|
6
6
|
|
7
7
|
# TODO check if invalid index with expected name exists and remove if needed
|
@@ -67,6 +67,19 @@ module StrongMigrations
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
+
def safe_add_check_constraint(table, expression, add_options, validate_options)
|
71
|
+
@migration.reversible do |dir|
|
72
|
+
dir.up do
|
73
|
+
@migration.add_check_constraint(table, expression, **add_options)
|
74
|
+
disable_transaction
|
75
|
+
@migration.validate_check_constraint(table, **validate_options)
|
76
|
+
end
|
77
|
+
dir.down do
|
78
|
+
@migration.remove_check_constraint(table, expression, **add_options)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
70
83
|
def safe_change_column_null(add_code, validate_code, change_args, remove_code)
|
71
84
|
@migration.reversible do |dir|
|
72
85
|
dir.up do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-01-13 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -138,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: '0'
|
140
140
|
requirements: []
|
141
|
-
rubygems_version: 3.2.
|
141
|
+
rubygems_version: 3.2.3
|
142
142
|
signing_key:
|
143
143
|
specification_version: 4
|
144
144
|
summary: Catch unsafe migrations in development
|