strong_migrations 0.7.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38cd87929d4889113bc2cc13aa786965c8b86fadf05d1d0439e2c927becfef61
4
- data.tar.gz: 0f17d511058c0bd1852adcddf9a2e978b8e407002ddf201be7aedb6c797dd294
3
+ metadata.gz: a22e260ec0e3e09954c65535725a3ddc4439b9cf94182a21cf5963e12d8485fe
4
+ data.tar.gz: 865633da561d77e615df16a9a00f8524a95bf73edc5590e3932855aaf544d4f7
5
5
  SHA512:
6
- metadata.gz: d28ef8e2d9cd837fac0a5e72743e1b88e7ef268ce9660e5119c27b50cc0e5215ae20e36a8be570a987e967f4a8bdf47ccd68eb6ee66f7188b787d3d64596c514
7
- data.tar.gz: f1e012eed6af3ebcc0ffbf5a1f4b90438ee054597f41a9420a3645af8535fff4cb3ef64b69abdc547fbf20a14658b03f0492395ad46c6bfc79b26726424c81c4
6
+ metadata.gz: f9b4df7a67aae2c8e1f7c58327b0e7e298261a33d9810dff4b5634bee3e000f1b26e64ad7b3997c8fc6c1cfef3a5cbeabe35ecbe508ed902679e3c9a720ecfb8
7
+ data.tar.gz: f4a7ecb6e64c40d1be1025a978387b94b9c2e47897cc25568c32dc32043292bc8fe14c9e6471108a4d84a1b1704459707f4fcf8a25227b7a86d8bcf2237dc19d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ ## 1.0.0 (2022-03-21)
2
+
3
+ New safe operations with MySQL and MariaDB
4
+
5
+ - Setting `NOT NULL` on an existing column with strict mode enabled
6
+
7
+ New safe operations with Postgres
8
+
9
+ - Changing between `text` and `citext` when not indexed
10
+ - Changing a `string` column to a `citext` column when not indexed
11
+ - Changing a `citext` column to a `string` column with no `:limit` when not indexed
12
+ - Changing a `cidr` column to an `inet` column
13
+ - Increasing `:precision` of an `interval` or `time` column
14
+
15
+ New unsafe operations with Postgres
16
+
17
+ - Adding a column with a callable default value
18
+ - Decreasing `:precision` of a `datetime` column
19
+ - Decreasing `:limit` of a `timestamptz` column
20
+ - Passing a default value to `change_column_null`
21
+
22
+ Other
23
+
24
+ - Added experimental support for lock timeout retries
25
+ - Added `target_sql_mode` option
26
+ - Added error for `change_column_null` with default value with `safe_by_default` option
27
+ - Fixed instructions for `remove_columns` with options
28
+ - Dropped support for Postgres < 10, MySQL < 5.7, and MariaDB < 10.2
29
+
30
+ ## 0.8.0 (2022-02-09)
31
+
32
+ - Fixed error with versioned schema with Active Record 7.0.2+
33
+ - Dropped support for Ruby < 2.6 and Active Record < 5.2
34
+
35
+ ## 0.7.9 (2021-12-15)
36
+
37
+ - Fixed error with multiple databases with Active Record 7
38
+
1
39
  ## 0.7.8 (2021-08-03)
2
40
 
3
41
  - Fixed issue with `add_reference ..., foreign_key: {to_table: ...}` with `safe_by_default`
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Bob Remeika and David Waller, 2015-2021 Andrew Kane
1
+ Copyright (c) 2013 Bob Remeika and David Waller, 2015-2022 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -4,7 +4,7 @@ Catch unsafe migrations in development
4
4
 
5
5
  &nbsp;&nbsp;✓&nbsp;&nbsp;Detects potentially dangerous operations<br />&nbsp;&nbsp;✓&nbsp;&nbsp;Prevents them from running by default<br />&nbsp;&nbsp;✓&nbsp;&nbsp;Provides instructions on safer ways to do what you want
6
6
 
7
- Supports for PostgreSQL, MySQL, and MariaDB
7
+ Supports PostgreSQL, MySQL, and MariaDB
8
8
 
9
9
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
10
10
 
@@ -15,7 +15,7 @@ Supports for PostgreSQL, MySQL, and MariaDB
15
15
  Add this line to your application’s Gemfile:
16
16
 
17
17
  ```ruby
18
- gem 'strong_migrations'
18
+ gem "strong_migrations"
19
19
  ```
20
20
 
21
21
  And run:
@@ -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.1]
46
+ class RemoveColumn < ActiveRecord::Migration[7.0]
47
47
  def change
48
48
  safety_assured { remove_column :users, :name }
49
49
  end
@@ -67,7 +67,6 @@ Potentially dangerous operations:
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
69
  - [adding a check constraint](#adding-a-check-constraint)
70
- - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
71
70
  - [executing SQL directly](#executing-SQL-directly)
72
71
 
73
72
  Postgres-specific checks:
@@ -76,6 +75,7 @@ Postgres-specific checks:
76
75
  - [adding a reference](#adding-a-reference)
77
76
  - [adding a foreign key](#adding-a-foreign-key)
78
77
  - [adding a json column](#adding-a-json-column)
78
+ - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
79
79
 
80
80
  Best practices:
81
81
 
@@ -90,7 +90,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
90
90
  Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
91
91
 
92
92
  ```ruby
93
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
93
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0]
94
94
  def change
95
95
  remove_column :users, :some_column
96
96
  end
@@ -111,7 +111,7 @@ end
111
111
  3. Write a migration to remove the column (wrap in `safety_assured` block)
112
112
 
113
113
  ```ruby
114
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
114
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[7.0]
115
115
  def change
116
116
  safety_assured { remove_column :users, :some_column }
117
117
  end
@@ -119,6 +119,7 @@ end
119
119
  ```
120
120
 
121
121
  4. Deploy and run migration
122
+ 5. Remove the line added in step 1
122
123
 
123
124
  ### Adding a column with a default value
124
125
 
@@ -127,7 +128,7 @@ end
127
128
  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.
128
129
 
129
130
  ```ruby
130
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
131
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
131
132
  def change
132
133
  add_column :users, :some_column, :text, default: "default_value"
133
134
  end
@@ -141,7 +142,7 @@ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a t
141
142
  Instead, add the column without a default value, then change the default.
142
143
 
143
144
  ```ruby
144
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
145
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
145
146
  def up
146
147
  add_column :users, :some_column, :text
147
148
  change_column_default :users, :some_column, "default_value"
@@ -162,7 +163,7 @@ See the next section for how to backfill.
162
163
  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/).
163
164
 
164
165
  ```ruby
165
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
166
+ class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
166
167
  def change
167
168
  add_column :users, :some_column, :text
168
169
  User.update_all some_column: "default_value"
@@ -177,7 +178,7 @@ Also, running a single query to update data can cause issues for large tables.
177
178
  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!`.
178
179
 
179
180
  ```ruby
180
- class BackfillSomeColumn < ActiveRecord::Migration[6.1]
181
+ class BackfillSomeColumn < ActiveRecord::Migration[7.0]
181
182
  disable_ddl_transaction!
182
183
 
183
184
  def up
@@ -196,26 +197,33 @@ end
196
197
  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.
197
198
 
198
199
  ```ruby
199
- class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
200
+ class ChangeSomeColumnType < ActiveRecord::Migration[7.0]
200
201
  def change
201
202
  change_column :users, :some_column, :new_type
202
203
  end
203
204
  end
204
205
  ```
205
206
 
206
- A few changes don’t require a table rewrite (and are safe) in Postgres:
207
+ Some changes don’t require a table rewrite and are safe in Postgres:
207
208
 
208
- - Increasing the length limit of a `varchar` column (or removing the limit)
209
- - Changing a `varchar` column to a `text` column
210
- - Changing a `text` column to a `varchar` column with no length limit
211
- - Increasing the precision of a `decimal` or `numeric` column
212
- - Making a `decimal` or `numeric` column unconstrained
213
- - Changing between `timestamp` and `timestamptz` columns when session time zone is UTC in Postgres 12+
209
+ Type | Safe Changes
210
+ --- | ---
211
+ `cidr` | Changing to `inet`
212
+ `citext` | Changing to `text` if not indexed, changing to `string` with no `:limit` if not indexed
213
+ `datetime` | Increasing or removing `:precision`, changing to `timestamptz` when session time zone is UTC in Postgres 12+
214
+ `decimal` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
215
+ `interval` | Increasing or removing `:precision`
216
+ `numeric` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
217
+ `string` | Increasing or removing `:limit`, changing to `text`, changing `citext` if not indexed
218
+ `text` | Changing to `string` with no `:limit`, changing to `citext` if not indexed
219
+ `time` | Increasing or removing `:precision`
220
+ `timestamptz` | Increasing or removing `:limit`, changing to `datetime` when session time zone is UTC in Postgres 12+
214
221
 
215
- And a few in MySQL and MariaDB:
222
+ And some in MySQL and MariaDB:
216
223
 
217
- - Increasing the length limit of a `varchar` column from under 255 up to 255
218
- - Increasing the length limit of a `varchar` column from over 255 to the max limit
224
+ Type | Safe Changes
225
+ --- | ---
226
+ `string` | Increasing `:limit` from under 255 up to 255, increasing `:limit` from over 255 to the max
219
227
 
220
228
  #### Good
221
229
 
@@ -235,7 +243,7 @@ A safer approach is to:
235
243
  Renaming a column that’s in use will cause errors in your application.
236
244
 
237
245
  ```ruby
238
- class RenameSomeColumn < ActiveRecord::Migration[6.1]
246
+ class RenameSomeColumn < ActiveRecord::Migration[7.0]
239
247
  def change
240
248
  rename_column :users, :some_column, :new_name
241
249
  end
@@ -260,7 +268,7 @@ A safer approach is to:
260
268
  Renaming a table that’s in use will cause errors in your application.
261
269
 
262
270
  ```ruby
263
- class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
271
+ class RenameUsersToCustomers < ActiveRecord::Migration[7.0]
264
272
  def change
265
273
  rename_table :users, :customers
266
274
  end
@@ -285,7 +293,7 @@ A safer approach is to:
285
293
  The `force` option can drop an existing table.
286
294
 
287
295
  ```ruby
288
- class CreateUsers < ActiveRecord::Migration[6.1]
296
+ class CreateUsers < ActiveRecord::Migration[7.0]
289
297
  def change
290
298
  create_table :users, force: true do |t|
291
299
  # ...
@@ -299,7 +307,7 @@ end
299
307
  Create tables without the `force` option.
300
308
 
301
309
  ```ruby
302
- class CreateUsers < ActiveRecord::Migration[6.1]
310
+ class CreateUsers < ActiveRecord::Migration[7.0]
303
311
  def change
304
312
  create_table :users do |t|
305
313
  # ...
@@ -319,7 +327,7 @@ If you intend to drop an existing table, run `drop_table` first.
319
327
  Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
320
328
 
321
329
  ```ruby
322
- class AddCheckConstraint < ActiveRecord::Migration[6.1]
330
+ class AddCheckConstraint < ActiveRecord::Migration[7.0]
323
331
  def change
324
332
  add_check_constraint :users, "price > 0", name: "price_check"
325
333
  end
@@ -331,7 +339,7 @@ end
331
339
  Add the check constraint without validating existing rows:
332
340
 
333
341
  ```ruby
334
- class AddCheckConstraint < ActiveRecord::Migration[6.1]
342
+ class AddCheckConstraint < ActiveRecord::Migration[7.0]
335
343
  def change
336
344
  add_check_constraint :users, "price > 0", name: "price_check", validate: false
337
345
  end
@@ -341,7 +349,7 @@ end
341
349
  Then validate them in a separate migration.
342
350
 
343
351
  ```ruby
344
- class ValidateCheckConstraint < ActiveRecord::Migration[6.1]
352
+ class ValidateCheckConstraint < ActiveRecord::Migration[7.0]
345
353
  def change
346
354
  validate_check_constraint :users, name: "price_check"
347
355
  end
@@ -352,92 +360,12 @@ end
352
360
 
353
361
  [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
362
 
355
- ### Setting NOT NULL on an existing column
356
-
357
- :turtle: Safe by default available
358
-
359
- #### Bad
360
-
361
- Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
362
-
363
- ```ruby
364
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
365
- def change
366
- change_column_null :users, :some_column, false
367
- end
368
- end
369
- ```
370
-
371
- #### Good - Postgres
372
-
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:
386
-
387
- ```ruby
388
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
389
- def change
390
- safety_assured do
391
- execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
392
- end
393
- end
394
- end
395
- ```
396
-
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:
414
-
415
- ```ruby
416
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
417
- def change
418
- safety_assured do
419
- execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
420
- end
421
-
422
- # in Postgres 12+, you can then safely set NOT NULL on the column
423
- change_column_null :users, :some_column, false
424
- safety_assured do
425
- execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
426
- end
427
- end
428
- end
429
- ```
430
-
431
- #### Good - MySQL and MariaDB
432
-
433
- [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this.
434
-
435
363
  ### Executing SQL directly
436
364
 
437
365
  Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
438
366
 
439
367
  ```ruby
440
- class ExecuteSQL < ActiveRecord::Migration[6.1]
368
+ class ExecuteSQL < ActiveRecord::Migration[7.0]
441
369
  def change
442
370
  safety_assured { execute "..." }
443
371
  end
@@ -453,7 +381,7 @@ end
453
381
  In Postgres, adding an index non-concurrently blocks writes.
454
382
 
455
383
  ```ruby
456
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
384
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
457
385
  def change
458
386
  add_index :users, :some_column
459
387
  end
@@ -465,7 +393,7 @@ end
465
393
  Add indexes concurrently.
466
394
 
467
395
  ```ruby
468
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
396
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
469
397
  disable_ddl_transaction!
470
398
 
471
399
  def change
@@ -491,7 +419,7 @@ rails g index table column
491
419
  Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
492
420
 
493
421
  ```ruby
494
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
422
+ class AddReferenceToUsers < ActiveRecord::Migration[7.0]
495
423
  def change
496
424
  add_reference :users, :city
497
425
  end
@@ -503,7 +431,7 @@ end
503
431
  Make sure the index is added concurrently.
504
432
 
505
433
  ```ruby
506
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
434
+ class AddReferenceToUsers < ActiveRecord::Migration[7.0]
507
435
  disable_ddl_transaction!
508
436
 
509
437
  def change
@@ -521,7 +449,7 @@ end
521
449
  In Postgres, adding a foreign key blocks writes on both tables.
522
450
 
523
451
  ```ruby
524
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
452
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0]
525
453
  def change
526
454
  add_foreign_key :users, :orders
527
455
  end
@@ -531,7 +459,7 @@ end
531
459
  or
532
460
 
533
461
  ```ruby
534
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
462
+ class AddReferenceToUsers < ActiveRecord::Migration[7.0]
535
463
  def change
536
464
  add_reference :users, :order, foreign_key: true
537
465
  end
@@ -540,74 +468,124 @@ end
540
468
 
541
469
  #### Good
542
470
 
543
- Add the foreign key without validating existing rows, then validate them in a separate migration.
544
-
545
- For Rails 5.2+, use:
471
+ Add the foreign key without validating existing rows:
546
472
 
547
473
  ```ruby
548
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
474
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[7.0]
549
475
  def change
550
476
  add_foreign_key :users, :orders, validate: false
551
477
  end
552
478
  end
553
479
  ```
554
480
 
555
- Then:
481
+ Then validate them in a separate migration.
556
482
 
557
483
  ```ruby
558
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
484
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[7.0]
559
485
  def change
560
486
  validate_foreign_key :users, :orders
561
487
  end
562
488
  end
563
489
  ```
564
490
 
565
- For Rails < 5.2, use:
491
+ ### Adding a json column
492
+
493
+ #### Bad
494
+
495
+ In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
566
496
 
567
497
  ```ruby
568
- class AddForeignKeyOnUsers < ActiveRecord::Migration[5.1]
498
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.0]
569
499
  def change
570
- safety_assured do
571
- execute 'ALTER TABLE "users" ADD CONSTRAINT "fk_rails_c1e9b98e31" FOREIGN KEY ("order_id") REFERENCES "orders" ("id") NOT VALID'
572
- end
500
+ add_column :users, :properties, :json
573
501
  end
574
502
  end
575
503
  ```
576
504
 
577
- Then:
505
+ #### Good
506
+
507
+ Use `jsonb` instead.
578
508
 
579
509
  ```ruby
580
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[5.1]
510
+ class AddPropertiesToUsers < ActiveRecord::Migration[7.0]
581
511
  def change
582
- safety_assured do
583
- execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "fk_rails_c1e9b98e31"'
584
- end
512
+ add_column :users, :properties, :jsonb
585
513
  end
586
514
  end
587
515
  ```
588
516
 
589
- ### Adding a json column
517
+ ### Setting NOT NULL on an existing column
518
+
519
+ :turtle: Safe by default available
590
520
 
591
521
  #### Bad
592
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
+ In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
594
524
 
595
525
  ```ruby
596
- class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
526
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.0]
597
527
  def change
598
- add_column :users, :properties, :json
528
+ change_column_null :users, :some_column, false
599
529
  end
600
530
  end
601
531
  ```
602
532
 
603
533
  #### Good
604
534
 
605
- Use `jsonb` instead.
535
+ Instead, add a check constraint.
536
+
537
+ For Rails 6.1, use:
606
538
 
607
539
  ```ruby
608
- class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
540
+ class SetSomeColumnNotNull < ActiveRecord::Migration[7.0]
609
541
  def change
610
- add_column :users, :properties, :jsonb
542
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
543
+ end
544
+ end
545
+ ```
546
+
547
+ For Rails < 6.1, use:
548
+
549
+ ```ruby
550
+ class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
551
+ def change
552
+ safety_assured do
553
+ execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID'
554
+ end
555
+ end
556
+ end
557
+ ```
558
+
559
+ 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.
560
+
561
+ For Rails 6.1, use:
562
+
563
+ ```ruby
564
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.0]
565
+ def change
566
+ validate_check_constraint :users, name: "users_some_column_null"
567
+
568
+ # in Postgres 12+, you can then safely set NOT NULL on the column
569
+ change_column_null :users, :some_column, false
570
+ remove_check_constraint :users, name: "users_some_column_null"
571
+ end
572
+ end
573
+ ```
574
+
575
+ For Rails < 6.1, use:
576
+
577
+ ```ruby
578
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
579
+ def change
580
+ safety_assured do
581
+ execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"'
582
+ end
583
+
584
+ # in Postgres 12+, you can then safely set NOT NULL on the column
585
+ change_column_null :users, :some_column, false
586
+ safety_assured do
587
+ execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"'
588
+ end
611
589
  end
612
590
  end
613
591
  ```
@@ -619,7 +597,7 @@ end
619
597
  Adding a non-unique index with more than three columns rarely improves performance.
620
598
 
621
599
  ```ruby
622
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
600
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
623
601
  def change
624
602
  add_index :users, [:a, :b, :c, :d]
625
603
  end
@@ -631,7 +609,7 @@ end
631
609
  Instead, start an index with columns that narrow down the results the most.
632
610
 
633
611
  ```ruby
634
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
612
+ class AddSomeIndexToUsers < ActiveRecord::Migration[7.0]
635
613
  def change
636
614
  add_index :users, [:b, :d]
637
615
  end
@@ -645,7 +623,7 @@ For Postgres, be sure to add them concurrently.
645
623
  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.
646
624
 
647
625
  ```ruby
648
- class MySafeMigration < ActiveRecord::Migration[6.1]
626
+ class MySafeMigration < ActiveRecord::Migration[7.0]
649
627
  def change
650
628
  safety_assured { remove_column :users, :some_column }
651
629
  end
@@ -721,7 +699,7 @@ To customize specific messages, create an initializer with:
721
699
  StrongMigrations.error_messages[:add_column_default] = "Your custom instructions"
722
700
  ```
723
701
 
724
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
702
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
725
703
 
726
704
  ## Migration Timeouts
727
705
 
@@ -743,6 +721,25 @@ ALTER ROLE myuser SET statement_timeout = '1h';
743
721
 
744
722
  Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
745
723
 
724
+ ## Lock Timeout Retries [experimental]
725
+
726
+ There’s the option to automatically retry statements when the lock timeout is reached. Here’s how it works:
727
+
728
+ - If a lock timeout happens outside a transaction, the statement is retried
729
+ - If it happens inside the DDL transaction, the entire migration is retried (only applicable to Postgres)
730
+
731
+ Add to `config/initializers/strong_migrations.rb`:
732
+
733
+ ```ruby
734
+ StrongMigrations.lock_timeout_retries = 3
735
+ ```
736
+
737
+ Set the delay between retries with:
738
+
739
+ ```ruby
740
+ StrongMigrations.lock_timeout_retry_delay = 10.seconds
741
+ ```
742
+
746
743
  ## App Timeouts
747
744
 
748
745
  We recommend adding timeouts to `config/database.yml` to prevent connections from hanging and individual queries from taking up too many resources in controllers, jobs, the Rails console, and other places.
@@ -0,0 +1,61 @@
1
+ module StrongMigrations
2
+ module Adapters
3
+ class AbstractAdapter
4
+ def initialize(checker)
5
+ @checker = checker
6
+ end
7
+
8
+ def name
9
+ "Unknown"
10
+ end
11
+
12
+ def min_version
13
+ end
14
+
15
+ def set_statement_timeout(timeout)
16
+ raise StrongMigrations::Error, "Statement timeout not supported for this database"
17
+ end
18
+
19
+ def set_lock_timeout(timeout)
20
+ raise StrongMigrations::Error, "Lock timeout not supported for this database"
21
+ end
22
+
23
+ def check_lock_timeout(limit)
24
+ # do nothing
25
+ end
26
+
27
+ def add_column_default_safe?
28
+ false
29
+ end
30
+
31
+ def change_type_safe?(table, column, type, options, existing_column, existing_type)
32
+ false
33
+ end
34
+
35
+ def rewrite_blocks
36
+ "reads and writes"
37
+ end
38
+
39
+ private
40
+
41
+ def connection
42
+ @checker.send(:connection)
43
+ end
44
+
45
+ def select_all(statement)
46
+ connection.select_all(statement)
47
+ end
48
+
49
+ def target_version(target_version)
50
+ target_version ||= StrongMigrations.target_version
51
+ version =
52
+ if target_version && StrongMigrations.developer_env?
53
+ target_version.to_s
54
+ else
55
+ yield
56
+ end
57
+ Gem::Version.new(version)
58
+ end
59
+ end
60
+ end
61
+ end