strong_migrations 0.7.7 → 2.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.
data/README.md CHANGED
@@ -4,18 +4,18 @@ 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
 
11
- [![Build Status](https://github.com/ankane/strong_migrations/workflows/build/badge.svg?branch=master)](https://github.com/ankane/strong_migrations/actions)
11
+ [![Build Status](https://github.com/ankane/strong_migrations/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/strong_migrations/actions)
12
12
 
13
13
  ## Installation
14
14
 
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:
@@ -38,12 +38,12 @@ Active Record caches attributes, which causes problems
38
38
  when removing columns. Be sure to ignore the column:
39
39
 
40
40
  class User < ApplicationRecord
41
- self.ignored_columns = ["name"]
41
+ self.ignored_columns += ["name"]
42
42
  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[8.0]
47
47
  def change
48
48
  safety_assured { remove_column :users, :name }
49
49
  end
@@ -60,22 +60,30 @@ An operation is classified as dangerous if it either:
60
60
  Potentially dangerous operations:
61
61
 
62
62
  - [removing a column](#removing-a-column)
63
- - [adding a column with a default value](#adding-a-column-with-a-default-value)
64
- - [backfilling data](#backfilling-data)
65
63
  - [changing the type of a column](#changing-the-type-of-a-column)
66
64
  - [renaming a column](#renaming-a-column)
67
65
  - [renaming a table](#renaming-a-table)
68
66
  - [creating a table with the force option](#creating-a-table-with-the-force-option)
67
+ - [adding an auto-incrementing column](#adding-an-auto-incrementing-column)
68
+ - [adding a stored generated column](#adding-a-stored-generated-column)
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)
71
+ - [backfilling data](#backfilling-data)
72
72
 
73
73
  Postgres-specific checks:
74
74
 
75
75
  - [adding an index non-concurrently](#adding-an-index-non-concurrently)
76
76
  - [adding a reference](#adding-a-reference)
77
77
  - [adding a foreign key](#adding-a-foreign-key)
78
+ - [adding a unique constraint](#adding-a-unique-constraint)
79
+ - [adding an exclusion constraint](#adding-an-exclusion-constraint)
78
80
  - [adding a json column](#adding-a-json-column)
81
+ - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)
82
+ - [adding a column with a volatile default value](#adding-a-column-with-a-volatile-default-value)
83
+
84
+ Config-specific checks:
85
+
86
+ - [changing the default value of a column](#changing-the-default-value-of-a-column)
79
87
 
80
88
  Best practices:
81
89
 
@@ -90,7 +98,7 @@ You can also add [custom checks](#custom-checks) or [disable specific checks](#d
90
98
  Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
91
99
 
92
100
  ```ruby
93
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
101
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
94
102
  def change
95
103
  remove_column :users, :some_column
96
104
  end
@@ -103,91 +111,23 @@ end
103
111
 
104
112
  ```ruby
105
113
  class User < ApplicationRecord
106
- self.ignored_columns = ["some_column"]
114
+ self.ignored_columns += ["some_column"]
107
115
  end
108
116
  ```
109
117
 
110
- 2. Deploy code
118
+ 2. Deploy the code
111
119
  3. Write a migration to remove the column (wrap in `safety_assured` block)
112
120
 
113
121
  ```ruby
114
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
122
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[8.0]
115
123
  def change
116
124
  safety_assured { remove_column :users, :some_column }
117
125
  end
118
126
  end
119
127
  ```
120
128
 
121
- 4. Deploy and run migration
122
-
123
- ### Adding a column with a default value
124
-
125
- #### Bad
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.
128
-
129
- ```ruby
130
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
131
- def change
132
- add_column :users, :some_column, :text, default: "default_value"
133
- end
134
- end
135
- ```
136
-
137
- In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a table rewrite and is safe.
138
-
139
- #### Good
140
-
141
- Instead, add the column without a default value, then change the default.
142
-
143
- ```ruby
144
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
145
- def up
146
- add_column :users, :some_column, :text
147
- change_column_default :users, :some_column, "default_value"
148
- end
149
-
150
- def down
151
- remove_column :users, :some_column
152
- end
153
- end
154
- ```
155
-
156
- See the next section for how to backfill.
157
-
158
- ### Backfilling data
159
-
160
- #### Bad
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/).
163
-
164
- ```ruby
165
- class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
166
- def change
167
- add_column :users, :some_column, :text
168
- User.update_all some_column: "default_value"
169
- end
170
- end
171
- ```
172
-
173
- Also, running a single query to update data can cause issues for large tables.
174
-
175
- #### Good
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!`.
178
-
179
- ```ruby
180
- class BackfillSomeColumn < ActiveRecord::Migration[6.1]
181
- disable_ddl_transaction!
182
-
183
- def up
184
- User.unscoped.in_batches do |relation|
185
- relation.update_all some_column: "default_value"
186
- sleep(0.01) # throttle
187
- end
188
- end
189
- end
190
- ```
129
+ 4. Deploy and run the migration
130
+ 5. Remove the line added in step 1
191
131
 
192
132
  ### Changing the type of a column
193
133
 
@@ -196,26 +136,33 @@ end
196
136
  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
137
 
198
138
  ```ruby
199
- class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
139
+ class ChangeSomeColumnType < ActiveRecord::Migration[8.0]
200
140
  def change
201
141
  change_column :users, :some_column, :new_type
202
142
  end
203
143
  end
204
144
  ```
205
145
 
206
- A few changes don’t require a table rewrite (and are safe) in Postgres:
146
+ Some changes don’t require a table rewrite and are safe in Postgres:
207
147
 
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+
148
+ Type | Safe Changes
149
+ --- | ---
150
+ `cidr` | Changing to `inet`
151
+ `citext` | Changing to `text` if not indexed, changing to `string` with no `:limit` if not indexed
152
+ `datetime` | Increasing or removing `:precision`, changing to `timestamptz` when session time zone is UTC in Postgres 12+
153
+ `decimal` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
154
+ `interval` | Increasing or removing `:precision`
155
+ `numeric` | Increasing `:precision` at same `:scale`, removing `:precision` and `:scale`
156
+ `string` | Increasing or removing `:limit`, changing to `text`, changing `citext` if not indexed
157
+ `text` | Changing to `string` with no `:limit`, changing to `citext` if not indexed
158
+ `time` | Increasing or removing `:precision`
159
+ `timestamptz` | Increasing or removing `:limit`, changing to `datetime` when session time zone is UTC in Postgres 12+
214
160
 
215
- And a few in MySQL and MariaDB:
161
+ And some in MySQL and MariaDB:
216
162
 
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
163
+ Type | Safe Changes
164
+ --- | ---
165
+ `string` | Increasing `:limit` from under 63 up to 63, increasing `:limit` from over 63 to the max (the threshold can be different if using an encoding other than `utf8mb4` - for instance, it’s 85 for `utf8mb3` and 255 for `latin1`)
219
166
 
220
167
  #### Good
221
168
 
@@ -235,7 +182,7 @@ A safer approach is to:
235
182
  Renaming a column that’s in use will cause errors in your application.
236
183
 
237
184
  ```ruby
238
- class RenameSomeColumn < ActiveRecord::Migration[6.1]
185
+ class RenameSomeColumn < ActiveRecord::Migration[8.0]
239
186
  def change
240
187
  rename_column :users, :some_column, :new_name
241
188
  end
@@ -260,7 +207,7 @@ A safer approach is to:
260
207
  Renaming a table that’s in use will cause errors in your application.
261
208
 
262
209
  ```ruby
263
- class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
210
+ class RenameUsersToCustomers < ActiveRecord::Migration[8.0]
264
211
  def change
265
212
  rename_table :users, :customers
266
213
  end
@@ -273,7 +220,7 @@ A safer approach is to:
273
220
 
274
221
  1. Create a new table
275
222
  2. Write to both tables
276
- 3. Backfill data from the old table to new table
223
+ 3. Backfill data from the old table to the new table
277
224
  4. Move reads from the old table to the new table
278
225
  5. Stop writing to the old table
279
226
  6. Drop the old table
@@ -285,7 +232,7 @@ A safer approach is to:
285
232
  The `force` option can drop an existing table.
286
233
 
287
234
  ```ruby
288
- class CreateUsers < ActiveRecord::Migration[6.1]
235
+ class CreateUsers < ActiveRecord::Migration[8.0]
289
236
  def change
290
237
  create_table :users, force: true do |t|
291
238
  # ...
@@ -299,7 +246,7 @@ end
299
246
  Create tables without the `force` option.
300
247
 
301
248
  ```ruby
302
- class CreateUsers < ActiveRecord::Migration[6.1]
249
+ class CreateUsers < ActiveRecord::Migration[8.0]
303
250
  def change
304
251
  create_table :users do |t|
305
252
  # ...
@@ -310,140 +257,136 @@ end
310
257
 
311
258
  If you intend to drop an existing table, run `drop_table` first.
312
259
 
313
- ### Adding a check constraint
314
-
315
- :turtle: Safe by default available
260
+ ### Adding an auto-incrementing column
316
261
 
317
262
  #### Bad
318
263
 
319
- Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
264
+ Adding an auto-incrementing column (`serial`/`bigserial` in Postgres and `AUTO_INCREMENT` in MySQL and MariaDB) 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.
320
265
 
321
266
  ```ruby
322
- class AddCheckConstraint < ActiveRecord::Migration[6.1]
267
+ class AddIdToCitiesUsers < ActiveRecord::Migration[8.0]
323
268
  def change
324
- add_check_constraint :users, "price > 0", name: "price_check"
269
+ add_column :cities_users, :id, :primary_key
325
270
  end
326
271
  end
327
272
  ```
328
273
 
329
- #### Good - Postgres
274
+ With MySQL and MariaDB, this can also [generate different values on replicas](https://dev.mysql.com/doc/mysql-replication-excerpt/8.0/en/replication-features-auto-increment.html) if using statement-based replication.
330
275
 
331
- Add the check constraint without validating existing rows:
276
+ #### Good
332
277
 
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
- ```
278
+ Create a new table and migrate the data with the same steps as [renaming a table](#renaming-a-table).
340
279
 
341
- Then validate them in a separate migration.
280
+ ### Adding a stored generated column
281
+
282
+ #### Bad
283
+
284
+ Adding a stored generated 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.
342
285
 
343
286
  ```ruby
344
- class ValidateCheckConstraint < ActiveRecord::Migration[6.1]
287
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
345
288
  def change
346
- validate_check_constraint :users, name: "price_check"
289
+ add_column :users, :some_column, :virtual, type: :string, as: "...", stored: true
347
290
  end
348
291
  end
349
292
  ```
350
293
 
351
- #### Good - MySQL and MariaDB
294
+ #### Good
352
295
 
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).
296
+ Add a non-generated column and use callbacks or triggers instead (or a virtual generated column with MySQL and MariaDB).
354
297
 
355
- ### Setting NOT NULL on an existing column
298
+ ### Adding a check constraint
356
299
 
357
300
  :turtle: Safe by default available
358
301
 
359
302
  #### Bad
360
303
 
361
- Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
304
+ Adding a check constraint blocks reads and writes in Postgres and blocks writes in MySQL and MariaDB while every row is checked.
362
305
 
363
306
  ```ruby
364
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
307
+ class AddCheckConstraint < ActiveRecord::Migration[8.0]
365
308
  def change
366
- change_column_null :users, :some_column, false
309
+ add_check_constraint :users, "price > 0", name: "price_check"
367
310
  end
368
311
  end
369
312
  ```
370
313
 
371
314
  #### Good - Postgres
372
315
 
373
- Instead, add a check constraint.
374
-
375
- For Rails 6.1, use:
316
+ Add the check constraint without validating existing rows:
376
317
 
377
318
  ```ruby
378
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.1]
319
+ class AddCheckConstraint < ActiveRecord::Migration[8.0]
379
320
  def change
380
- add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
321
+ add_check_constraint :users, "price > 0", name: "price_check", validate: false
381
322
  end
382
323
  end
383
324
  ```
384
325
 
385
- For Rails < 6.1, use:
326
+ Then validate them in a separate migration.
386
327
 
387
328
  ```ruby
388
- class SetSomeColumnNotNull < ActiveRecord::Migration[6.0]
329
+ class ValidateCheckConstraint < ActiveRecord::Migration[8.0]
389
330
  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
331
+ validate_check_constraint :users, name: "price_check"
393
332
  end
394
333
  end
395
334
  ```
396
335
 
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.
336
+ #### Good - MySQL and MariaDB
398
337
 
399
- For Rails 6.1, use:
338
+ [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).
339
+
340
+ ### Executing SQL directly
341
+
342
+ Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
400
343
 
401
344
  ```ruby
402
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.1]
345
+ class ExecuteSQL < ActiveRecord::Migration[8.0]
403
346
  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"
347
+ safety_assured { execute "..." }
409
348
  end
410
349
  end
411
350
  ```
412
351
 
413
- For Rails < 6.1, use:
352
+ ### Backfilling data
353
+
354
+ Note: Strong Migrations does not detect dangerous backfills.
355
+
356
+ #### Bad
357
+
358
+ 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/).
414
359
 
415
360
  ```ruby
416
- class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0]
361
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
417
362
  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
363
+ add_column :users, :some_column, :text
364
+ User.update_all some_column: "default_value"
427
365
  end
428
366
  end
429
367
  ```
430
368
 
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.
369
+ Also, running a single query to update data can cause issues for large tables.
434
370
 
435
- ### Executing SQL directly
371
+ #### Good
436
372
 
437
- Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
373
+ 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!`.
438
374
 
439
375
  ```ruby
440
- class ExecuteSQL < ActiveRecord::Migration[6.1]
441
- def change
442
- safety_assured { execute "..." }
376
+ class BackfillSomeColumn < ActiveRecord::Migration[8.0]
377
+ disable_ddl_transaction!
378
+
379
+ def up
380
+ User.unscoped.in_batches do |relation|
381
+ relation.update_all some_column: "default_value"
382
+ sleep(0.01) # throttle
383
+ end
443
384
  end
444
385
  end
445
386
  ```
446
387
 
388
+ Note: If backfilling with a method other than `update_all`, use `User.reset_column_information` to ensure the model has up-to-date column information.
389
+
447
390
  ### Adding an index non-concurrently
448
391
 
449
392
  :turtle: Safe by default available
@@ -453,7 +396,7 @@ end
453
396
  In Postgres, adding an index non-concurrently blocks writes.
454
397
 
455
398
  ```ruby
456
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
399
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
457
400
  def change
458
401
  add_index :users, :some_column
459
402
  end
@@ -465,7 +408,7 @@ end
465
408
  Add indexes concurrently.
466
409
 
467
410
  ```ruby
468
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
411
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
469
412
  disable_ddl_transaction!
470
413
 
471
414
  def change
@@ -491,7 +434,7 @@ rails g index table column
491
434
  Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
492
435
 
493
436
  ```ruby
494
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
437
+ class AddReferenceToUsers < ActiveRecord::Migration[8.0]
495
438
  def change
496
439
  add_reference :users, :city
497
440
  end
@@ -503,7 +446,7 @@ end
503
446
  Make sure the index is added concurrently.
504
447
 
505
448
  ```ruby
506
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
449
+ class AddReferenceToUsers < ActiveRecord::Migration[8.0]
507
450
  disable_ddl_transaction!
508
451
 
509
452
  def change
@@ -521,7 +464,7 @@ end
521
464
  In Postgres, adding a foreign key blocks writes on both tables.
522
465
 
523
466
  ```ruby
524
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
467
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
525
468
  def change
526
469
  add_foreign_key :users, :orders
527
470
  end
@@ -531,7 +474,7 @@ end
531
474
  or
532
475
 
533
476
  ```ruby
534
- class AddReferenceToUsers < ActiveRecord::Migration[6.1]
477
+ class AddReferenceToUsers < ActiveRecord::Migration[8.0]
535
478
  def change
536
479
  add_reference :users, :order, foreign_key: true
537
480
  end
@@ -540,52 +483,77 @@ end
540
483
 
541
484
  #### Good
542
485
 
543
- Add the foreign key without validating existing rows, then validate them in a separate migration.
544
-
545
- For Rails 5.2+, use:
486
+ Add the foreign key without validating existing rows:
546
487
 
547
488
  ```ruby
548
- class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
489
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[8.0]
549
490
  def change
550
491
  add_foreign_key :users, :orders, validate: false
551
492
  end
552
493
  end
553
494
  ```
554
495
 
555
- Then:
496
+ Then validate them in a separate migration.
556
497
 
557
498
  ```ruby
558
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
499
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[8.0]
559
500
  def change
560
501
  validate_foreign_key :users, :orders
561
502
  end
562
503
  end
563
504
  ```
564
505
 
565
- For Rails < 5.2, use:
506
+ ### Adding a unique constraint
507
+
508
+ #### Bad
509
+
510
+ In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes.
566
511
 
567
512
  ```ruby
568
- class AddForeignKeyOnUsers < ActiveRecord::Migration[5.1]
513
+ class AddUniqueConstraint < ActiveRecord::Migration[8.0]
569
514
  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
515
+ add_unique_constraint :users, :some_column
516
+ end
517
+ end
518
+ ```
519
+
520
+ #### Good
521
+
522
+ Create a unique index concurrently, then use it for the constraint.
523
+
524
+ ```ruby
525
+ class AddUniqueConstraint < ActiveRecord::Migration[8.0]
526
+ disable_ddl_transaction!
527
+
528
+ def up
529
+ add_index :users, :some_column, unique: true, algorithm: :concurrently
530
+ add_unique_constraint :users, using_index: "index_users_on_some_column"
531
+ end
532
+
533
+ def down
534
+ remove_unique_constraint :users, :some_column
573
535
  end
574
536
  end
575
537
  ```
576
538
 
577
- Then:
539
+ ### Adding an exclusion constraint
540
+
541
+ #### Bad
542
+
543
+ In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked.
578
544
 
579
545
  ```ruby
580
- class ValidateForeignKeyOnUsers < ActiveRecord::Migration[5.1]
546
+ class AddExclusionConstraint < ActiveRecord::Migration[8.0]
581
547
  def change
582
- safety_assured do
583
- execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "fk_rails_c1e9b98e31"'
584
- end
548
+ add_exclusion_constraint :users, "number WITH =", using: :gist
585
549
  end
586
550
  end
587
551
  ```
588
552
 
553
+ #### Good
554
+
555
+ [Let us know](https://github.com/ankane/strong_migrations/issues/new) if you have a safe way to do this (exclusion constraints cannot be marked `NOT VALID`).
556
+
589
557
  ### Adding a json column
590
558
 
591
559
  #### Bad
@@ -593,7 +561,7 @@ end
593
561
  In Postgres, there’s no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
594
562
 
595
563
  ```ruby
596
- class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
564
+ class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
597
565
  def change
598
566
  add_column :users, :properties, :json
599
567
  end
@@ -605,13 +573,121 @@ end
605
573
  Use `jsonb` instead.
606
574
 
607
575
  ```ruby
608
- class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
576
+ class AddPropertiesToUsers < ActiveRecord::Migration[8.0]
609
577
  def change
610
578
  add_column :users, :properties, :jsonb
611
579
  end
612
580
  end
613
581
  ```
614
582
 
583
+ ### Setting NOT NULL on an existing column
584
+
585
+ :turtle: Safe by default available
586
+
587
+ #### Bad
588
+
589
+ In Postgres, setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
590
+
591
+ ```ruby
592
+ class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
593
+ def change
594
+ change_column_null :users, :some_column, false
595
+ end
596
+ end
597
+ ```
598
+
599
+ #### Good
600
+
601
+ Instead, add a check constraint.
602
+
603
+ ```ruby
604
+ class SetSomeColumnNotNull < ActiveRecord::Migration[8.0]
605
+ def change
606
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
607
+ end
608
+ end
609
+ ```
610
+
611
+ Then validate it in a separate migration. Once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
612
+
613
+ ```ruby
614
+ class ValidateSomeColumnNotNull < ActiveRecord::Migration[8.0]
615
+ def up
616
+ validate_check_constraint :users, name: "users_some_column_null"
617
+ change_column_null :users, :some_column, false
618
+ remove_check_constraint :users, name: "users_some_column_null"
619
+ end
620
+
621
+ def down
622
+ add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
623
+ change_column_null :users, :some_column, true
624
+ end
625
+ end
626
+ ```
627
+
628
+ ### Adding a column with a volatile default value
629
+
630
+ #### Bad
631
+
632
+ Adding a column with a volatile default value to an existing table causes the entire table to be rewritten. During this time, reads and writes are blocked.
633
+
634
+ ```ruby
635
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
636
+ def change
637
+ add_column :users, :some_column, :uuid, default: "gen_random_uuid()"
638
+ end
639
+ end
640
+ ```
641
+
642
+ #### Good
643
+
644
+ Instead, add the column without a default value, then change the default.
645
+
646
+ ```ruby
647
+ class AddSomeColumnToUsers < ActiveRecord::Migration[8.0]
648
+ def up
649
+ add_column :users, :some_column, :uuid
650
+ change_column_default :users, :some_column, from: nil, to: "gen_random_uuid()"
651
+ end
652
+
653
+ def down
654
+ remove_column :users, :some_column
655
+ end
656
+ end
657
+ ```
658
+
659
+ Then [backfill the data](#backfilling-data).
660
+
661
+ ### Changing the default value of a column
662
+
663
+ #### Bad
664
+
665
+ Rails < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
666
+
667
+ ```ruby
668
+ class ChangeSomeColumnDefault < ActiveRecord::Migration[6.1]
669
+ def change
670
+ change_column_default :users, :some_column, from: "old", to: "new"
671
+ end
672
+ end
673
+
674
+ User.create!(some_column: "old") # can insert "new"
675
+ ```
676
+
677
+ #### Good
678
+
679
+ Disable partial writes in `config/application.rb`. For Rails < 7, use:
680
+
681
+ ```ruby
682
+ config.active_record.partial_writes = false
683
+ ```
684
+
685
+ For Rails 7+, use:
686
+
687
+ ```ruby
688
+ config.active_record.partial_inserts = false
689
+ ```
690
+
615
691
  ### Keeping non-unique indexes to three columns or less
616
692
 
617
693
  #### Bad
@@ -619,7 +695,7 @@ end
619
695
  Adding a non-unique index with more than three columns rarely improves performance.
620
696
 
621
697
  ```ruby
622
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
698
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
623
699
  def change
624
700
  add_index :users, [:a, :b, :c, :d]
625
701
  end
@@ -631,9 +707,9 @@ end
631
707
  Instead, start an index with columns that narrow down the results the most.
632
708
 
633
709
  ```ruby
634
- class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
710
+ class AddSomeIndexToUsers < ActiveRecord::Migration[8.0]
635
711
  def change
636
- add_index :users, [:b, :d]
712
+ add_index :users, [:d, :b]
637
713
  end
638
714
  end
639
715
  ```
@@ -645,7 +721,7 @@ For Postgres, be sure to add them concurrently.
645
721
  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
722
 
647
723
  ```ruby
648
- class MySafeMigration < ActiveRecord::Migration[6.1]
724
+ class MySafeMigration < ActiveRecord::Migration[8.0]
649
725
  def change
650
726
  safety_assured { remove_column :users, :some_column }
651
727
  end
@@ -656,7 +732,7 @@ Certain methods like `execute` and `change_table` cannot be inspected and are pr
656
732
 
657
733
  ## Safe by Default
658
734
 
659
- Make operations safe by default.
735
+ Make certain operations safe by default. This allows you to write the code under the "Bad" section, but the migration will be performed as if you had written the "Good" version.
660
736
 
661
737
  - adding and removing an index
662
738
  - adding a foreign key
@@ -703,7 +779,17 @@ Disable specific checks with:
703
779
  StrongMigrations.disable_check(:add_index)
704
780
  ```
705
781
 
706
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
782
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
783
+
784
+ ## Skip Databases
785
+
786
+ Skip checks and other functionality for specific databases with:
787
+
788
+ ```ruby
789
+ StrongMigrations.skip_database(:catalog)
790
+ ```
791
+
792
+ Note: This does not affect `alphabetize_schema`.
707
793
 
708
794
  ## Down Migrations / Rollbacks
709
795
 
@@ -721,7 +807,7 @@ To customize specific messages, create an initializer with:
721
807
  StrongMigrations.error_messages[:add_column_default] = "Your custom instructions"
722
808
  ```
723
809
 
724
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
810
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
725
811
 
726
812
  ## Migration Timeouts
727
813
 
@@ -786,12 +872,45 @@ production:
786
872
 
787
873
  For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
788
874
 
875
+ ## Invalid Indexes
876
+
877
+ In Postgres, adding an index non-concurrently can leave behind an invalid index if the lock timeout is reached. Running the migration again can result in an error.
878
+
879
+ To automatically remove the invalid index when the migration runs again, use:
880
+
881
+ ```ruby
882
+ StrongMigrations.remove_invalid_indexes = true
883
+ ```
884
+
885
+ Note: This feature is experimental.
886
+
887
+ ## Lock Timeout Retries
888
+
889
+ Note: This feature is experimental.
890
+
891
+ There’s the option to automatically retry statements for migrations when the lock timeout is reached. Here’s how it works:
892
+
893
+ - If a lock timeout happens outside a transaction, the statement is retried
894
+ - If it happens inside the DDL transaction, the entire migration is retried (only applicable to Postgres)
895
+
896
+ Add to `config/initializers/strong_migrations.rb`:
897
+
898
+ ```ruby
899
+ StrongMigrations.lock_timeout_retries = 3
900
+ ```
901
+
902
+ Set the delay between retries with:
903
+
904
+ ```ruby
905
+ StrongMigrations.lock_timeout_retry_delay = 10.seconds
906
+ ```
907
+
789
908
  ## Existing Migrations
790
909
 
791
910
  To mark migrations as safe that were created before installing this gem, create an initializer with:
792
911
 
793
912
  ```ruby
794
- StrongMigrations.start_after = 20170101000000
913
+ StrongMigrations.start_after = 20250101000000
795
914
  ```
796
915
 
797
916
  Use the version from your latest migration.
@@ -801,13 +920,19 @@ Use the version from your latest migration.
801
920
  If your development database version is different from production, you can specify the production version so the right checks run in development.
802
921
 
803
922
  ```ruby
804
- StrongMigrations.target_version = 10 # or "8.0.12", "10.3.2", etc
923
+ StrongMigrations.target_version = 10 # or 8.0, 10.5, etc
805
924
  ```
806
925
 
807
- The major version works well for Postgres, while the full version is recommended for MySQL and MariaDB.
926
+ The major version works well for Postgres, while the major and minor version is recommended for MySQL and MariaDB.
808
927
 
809
928
  For safety, this option only affects development and test environments. In other environments, the actual server version is always used.
810
929
 
930
+ If your app has multiple databases with different versions, you can use:
931
+
932
+ ```ruby
933
+ StrongMigrations.target_version = {primary: 13, catalog: 15}
934
+ ```
935
+
811
936
  ## Analyze Tables
812
937
 
813
938
  Analyze tables automatically (to update planner statistics) after an index is added. Create an initializer with:
@@ -818,19 +943,18 @@ StrongMigrations.auto_analyze = true
818
943
 
819
944
  ## Faster Migrations
820
945
 
821
- Only dump the schema when adding a new migration. If you use Git, create an initializer with:
946
+ Only dump the schema when adding a new migration. If you use Git, add to `config/environments/development.rb`:
822
947
 
823
- ```ruby
824
- ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
825
- `git status db/migrate/ --porcelain`.present?
948
+ ```rb
949
+ config.active_record.dump_schema_after_migration = `git status db/migrate/ --porcelain`.present?
826
950
  ```
827
951
 
828
952
  ## Schema Sanity
829
953
 
830
- Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/). Add to the end of your `Rakefile`:
954
+ Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/). Add to `config/initializers/strong_migrations.rb`:
831
955
 
832
956
  ```ruby
833
- task "db:schema:dump": "strong_migrations:alphabetize_columns"
957
+ StrongMigrations.alphabetize_schema = true
834
958
  ```
835
959
 
836
960
  ## Permissions
@@ -843,15 +967,18 @@ You probably don’t need this gem for smaller projects, as operations that are
843
967
 
844
968
  ## Additional Reading
845
969
 
846
- - [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
847
970
  - [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/braintree-product-technology/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680)
848
- - [An Overview of DDL Algorithms in MySQL](https://mydbops.wordpress.com/2020/03/04/an-overview-of-ddl-algorithms-in-mysql-covers-mysql-8/)
971
+ - [MySQL InnoDB Online DDL Operations](https://dev.mysql.com/doc/refman/en/innodb-online-ddl-operations.html)
849
972
  - [MariaDB InnoDB Online DDL Overview](https://mariadb.com/kb/en/innodb-online-ddl-overview/)
850
973
 
851
974
  ## Credits
852
975
 
853
976
  Thanks to Bob Remeika and David Waller for the [original code](https://github.com/foobarfighter/safe-migrations) and [Sean Huber](https://github.com/LendingHome/zero_downtime_migrations) for the bad/good readme format.
854
977
 
978
+ ## History
979
+
980
+ View the [changelog](https://github.com/ankane/strong_migrations/blob/master/CHANGELOG.md)
981
+
855
982
  ## Contributing
856
983
 
857
984
  Everyone is encouraged to help improve this project. Here are a few ways you can help: