strong_migrations 0.3.1 → 0.7.6

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: bbb45da961375e9a3499c6b651da754c164f05b0e33f9b5061a856ce508663b6
4
- data.tar.gz: 1718eb87c7eb8b60553b1e800e8a4866cdbc134ae9c062e012caa6e8f3d4be6b
3
+ metadata.gz: f16481df30c6b961477b4a537034097aa1d41a262bd7f2741c40b7e6dae9cb9e
4
+ data.tar.gz: 816bc9a4f9810bce6ac6d5e0a52b185800515794ca5e3d27b5f4d512a9190115
5
5
  SHA512:
6
- metadata.gz: 63dde3000e42dbe433914159a815a1dc4a1ebca89e78f3f342e9aa664bd1e921bbb3a1ee33140d6e56d50aef400b171e27a24509fd369da0350b0f548a108a25
7
- data.tar.gz: d187c338c085b86ad7498ea09253a9d2015415740b4c09d78ec1f9f37abbc20261255b1590210d7bbf473fcb2ecfa1bad57a9e356a9e2480ec2f90d9fb355404
6
+ metadata.gz: 2ac9f7c38958c1cc2e80efa3f895730087ac92a3675be31c308f9f8df827ced3323ca8bc3e69c99e2a79b92ef0a5a8b595a76715bf55c45b5698fcdd43b5a586
7
+ data.tar.gz: 2cee8d3013ae8780f0bcbce33f22a219375c29f059335902cdfe2285ec557307229b7ec4544e6be59ea1ee7cd039f2b23e9b3693efc41f11f677cbbed1495896
data/CHANGELOG.md CHANGED
@@ -1,78 +1,186 @@
1
- ## 0.3.1
1
+ ## 0.7.6 (2021-01-17)
2
+
3
+ - Fixed `NOT NULL` constraint check for quoted columns
4
+ - Fixed deprecation warning with Active Record 6.1
5
+
6
+ ## 0.7.5 (2021-01-12)
7
+
8
+ - Added checks for `add_check_constraint` and `validate_check_constraint`
9
+
10
+ ## 0.7.4 (2020-12-16)
11
+
12
+ - Added `safe_by_default` option to install generator
13
+ - Fixed warnings with Active Record 6.1
14
+
15
+ ## 0.7.3 (2020-11-24)
16
+
17
+ - Added `safe_by_default` option
18
+
19
+ ## 0.7.2 (2020-10-25)
20
+
21
+ - Added support for float timeouts
22
+
23
+ ## 0.7.1 (2020-07-27)
24
+
25
+ - Added `target_version` option to replace database-specific options
26
+
27
+ ## 0.7.0 (2020-07-22)
28
+
29
+ - Added `check_down` option
30
+ - Added check for `change_column` with `null: false`
31
+ - Added check for `validate_foreign_key`
32
+ - Improved error messages
33
+ - Made auto analyze less verbose in Postgres
34
+ - Decreasing the length limit of a `varchar` column or adding a limit is not safe in Postgres
35
+ - Removed safety checks for `db` rake tasks (Rails 5+ handles this)
36
+
37
+ ## 0.6.8 (2020-05-13)
38
+
39
+ - `change_column_null` on a column with a `NOT NULL` constraint is safe in Postgres 12+
40
+
41
+ ## 0.6.7 (2020-05-13)
42
+
43
+ - Improved comments in initializer
44
+ - Fixed string timeouts for Postgres
45
+
46
+ ## 0.6.6 (2020-05-08)
47
+
48
+ - Added warnings for missing and long lock timeouts
49
+ - Added install generator
50
+
51
+ ## 0.6.5 (2020-05-06)
52
+
53
+ - Fixed deprecation warnings with Ruby 2.7
54
+
55
+ ## 0.6.4 (2020-04-16)
56
+
57
+ - Added check for `add_reference` with `foreign_key: true`
58
+
59
+ ## 0.6.3 (2020-04-04)
60
+
61
+ - Increasing precision of `decimal` or `numeric` column is safe in Postgres 9.2+
62
+ - Making `decimal` or `numeric` column unconstrained is safe in Postgres 9.2+
63
+ - Changing between `timestamp` and `timestamptz` when session time zone is UTC in Postgres 12+
64
+ - Increasing the length of a `varchar` column from under 255 up to 255 in MySQL and MariaDB
65
+ - Increasing the length of a `varchar` column over 255 in MySQL and MariaDB
66
+
67
+ ## 0.6.2 (2020-02-03)
68
+
69
+ - Fixed PostgreSQL version check
70
+
71
+ ## 0.6.1 (2020-01-28)
72
+
73
+ - Fixed timeouts for PostgreSQL
74
+
75
+ ## 0.6.0 (2020-01-24)
76
+
77
+ - Added `statement_timeout` and `lock_timeout`
78
+ - Adding a column with a non-null default value is safe in MySQL 8.0.12+ and MariaDB 10.3.2+
79
+ - Added `change_column_null` check for MySQL and MariaDB
80
+ - Added `auto_analyze` for MySQL and MariaDB
81
+ - Added `target_mysql_version` and `target_mariadb_version`
82
+ - Switched to `up` for backfilling
83
+
84
+ ## 0.5.1 (2019-12-17)
85
+
86
+ - Fixed migration name in error messages
87
+
88
+ ## 0.5.0 (2019-12-05)
89
+
90
+ - Added ability to disable checks
91
+ - Added Postgres-specific check for `change_column_null`
92
+ - Added optional remove index check
93
+
94
+ ## 0.4.2 (2019-10-27)
95
+
96
+ - Allow `add_reference` with concurrent indexes
97
+
98
+ ## 0.4.1 (2019-07-12)
99
+
100
+ - Added `target_postgresql_version`
101
+ - Added `unscoped` to backfill instructions
102
+
103
+ ## 0.4.0 (2019-05-27)
104
+
105
+ - Added check for `add_foreign_key`
106
+ - Fixed instructions for adding default value with NOT NULL constraint
107
+ - Removed support for Rails 4.2
108
+
109
+ ## 0.3.1 (2018-10-18)
2
110
 
3
111
  - Fixed error with `remove_column` and `type` argument
4
112
  - Improved message customization
5
113
 
6
- ## 0.3.0
114
+ ## 0.3.0 (2018-10-15)
7
115
 
8
116
  - Added support for custom checks
9
117
  - Adding a column with a non-null default value is safe in Postgres 11+
10
118
  - Added checks for `add_belongs_to`, `remove_belongs_to`, `remove_columns`, and `remove_reference`
11
119
  - Customized messages
12
120
 
13
- ## 0.2.3
121
+ ## 0.2.3 (2018-07-22)
14
122
 
15
123
  - Added check for `change_column_null`
16
124
  - Added support for alphabetize columns with Makara
17
125
  - Fixed migration reversibility with `auto_analyze`
18
126
 
19
- ## 0.2.2
127
+ ## 0.2.2 (2018-02-14)
20
128
 
21
129
  - Friendlier output
22
130
  - Better method of hooking into ActiveRecord
23
131
 
24
- ## 0.2.1
132
+ ## 0.2.1 (2018-02-07)
25
133
 
26
134
  - Recommend `disable_ddl_transaction!` over `commit_db_transaction`
27
135
  - Suggest `jsonb` over `json` in Postgres 9.4+
28
136
  - Changing `varchar` to `text` is safe in Postgres 9.1+
29
137
  - Do not check number of columns for unique indexes
30
138
 
31
- ## 0.2.0
139
+ ## 0.2.0 (2018-01-07)
32
140
 
33
141
  - Added customizable error messages
34
142
  - Updated instructions for adding a column with a default value
35
143
 
36
- ## 0.1.9
144
+ ## 0.1.9 (2017-06-14)
37
145
 
38
146
  - Added `start_after` option
39
147
 
40
- ## 0.1.8
148
+ ## 0.1.8 (2017-05-31)
41
149
 
42
150
  - Fixed error with `create_table`
43
151
  - Added check for executing arbitrary SQL
44
152
 
45
- ## 0.1.7
153
+ ## 0.1.7 (2017-05-29)
46
154
 
47
155
  - Added check for `force` option with `create_table`
48
156
  - Added `auto_analyze` option
49
157
 
50
- ## 0.1.6
158
+ ## 0.1.6 (2017-03-23)
51
159
 
52
160
  - Adding an index to a newly created table is now safe
53
161
 
54
- ## 0.1.5
162
+ ## 0.1.5 (2016-07-23)
55
163
 
56
164
  - Fixed error with Ruby 2.3 frozen strings
57
165
 
58
- ## 0.1.4
166
+ ## 0.1.4 (2016-03-22)
59
167
 
60
168
  - Added alphabetize columns
61
169
 
62
- ## 0.1.3
170
+ ## 0.1.3 (2016-03-12)
63
171
 
64
172
  - Disabled dangerous rake tasks in production
65
173
  - Added ability to use `SAFETY_ASSURED` env var
66
174
 
67
- ## 0.1.2
175
+ ## 0.1.2 (2016-02-24)
68
176
 
69
177
  - Skip checks on down migrations and rollbacks
70
178
  - Added check for indexes with more than 3 columns
71
179
 
72
- ## 0.1.1
180
+ ## 0.1.1 (2015-11-29)
73
181
 
74
182
  - Fixed `add_index` check for MySQL
75
183
 
76
- ## 0.1.0
184
+ ## 0.1.0 (2015-11-22)
77
185
 
78
186
  - First release
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Bob Remeika and David Waller, 2015-2018 Andrew Kane
1
+ Copyright (c) 2013 Bob Remeika and David Waller, 2015-2021 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,10 +1,14 @@
1
1
  # Strong Migrations
2
2
 
3
- Catch unsafe migrations at dev time
3
+ Catch unsafe migrations in development
4
+
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
+
7
+ Supports for PostgreSQL, MySQL, and MariaDB
4
8
 
5
9
  :tangerine: Battle-tested at [Instacart](https://www.instacart.com/opensource)
6
10
 
7
- [![Build Status](https://travis-ci.org/ankane/strong_migrations.svg?branch=master)](https://travis-ci.org/ankane/strong_migrations)
11
+ [![Build Status](https://github.com/ankane/strong_migrations/workflows/build/badge.svg?branch=master)](https://github.com/ankane/strong_migrations/actions)
8
12
 
9
13
  ## Installation
10
14
 
@@ -14,57 +18,130 @@ Add this line to your application’s Gemfile:
14
18
  gem 'strong_migrations'
15
19
  ```
16
20
 
21
+ And run:
22
+
23
+ ```sh
24
+ bundle install
25
+ rails generate strong_migrations:install
26
+ ```
27
+
28
+ Strong Migrations sets a long statement timeout for migrations so you can set a [short statement timeout](#app-timeouts) for your application.
29
+
17
30
  ## How It Works
18
31
 
19
- Strong Migrations detects potentially dangerous operations in migrations, prevents them from running by default, and provides instructions on safer ways to do what you want. Here’s an example:
32
+ When you run a migration that’s potentially dangerous, you’ll see an error message like:
20
33
 
21
- ```
34
+ ```txt
22
35
  === Dangerous operation detected #strong_migrations ===
23
36
 
24
- ActiveRecord caches attributes which causes problems
37
+ Active Record caches attributes, which causes problems
25
38
  when removing columns. Be sure to ignore the column:
26
39
 
27
40
  class User < ApplicationRecord
28
- self.ignored_columns = ["some_column"]
41
+ self.ignored_columns = ["name"]
29
42
  end
30
43
 
31
44
  Deploy the code, then wrap this step in a safety_assured { ... } block.
32
45
 
33
- class RemoveColumn < ActiveRecord::Migration[5.2]
46
+ class RemoveColumn < ActiveRecord::Migration[6.1]
34
47
  def change
35
- safety_assured { remove_column :users, :some_column }
48
+ safety_assured { remove_column :users, :name }
49
+ end
50
+ end
51
+ ```
52
+
53
+ An operation is classified as dangerous if it either:
54
+
55
+ - Blocks reads or writes for more than a few seconds (after a lock is acquired)
56
+ - Has a good chance of causing application errors
57
+
58
+ ## Checks
59
+
60
+ Potentially dangerous operations:
61
+
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
+ - [changing the type of a column](#changing-the-type-of-a-column)
66
+ - [renaming a column](#renaming-a-column)
67
+ - [renaming a table](#renaming-a-table)
68
+ - [creating a table with the force option](#creating-a-table-with-the-force-option)
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
+ - [executing SQL directly](#executing-SQL-directly)
72
+
73
+ Postgres-specific checks:
74
+
75
+ - [adding an index non-concurrently](#adding-an-index-non-concurrently)
76
+ - [adding a reference](#adding-a-reference)
77
+ - [adding a foreign key](#adding-a-foreign-key)
78
+ - [adding a json column](#adding-a-json-column)
79
+
80
+ Best practices:
81
+
82
+ - [keeping non-unique indexes to three columns or less](#keeping-non-unique-indexes-to-three-columns-or-less)
83
+
84
+ You can also add [custom checks](#custom-checks) or [disable specific checks](#disable-checks).
85
+
86
+ ### Removing a column
87
+
88
+ #### Bad
89
+
90
+ Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
91
+
92
+ ```ruby
93
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
94
+ def change
95
+ remove_column :users, :some_column
36
96
  end
37
97
  end
38
98
  ```
39
99
 
40
- ## Dangerous Operations
100
+ #### Good
41
101
 
42
- The following operations can cause downtime or errors:
102
+ 1. Tell Active Record to ignore the column from its cache
43
103
 
44
- - adding a column with a non-null default value to an existing table
45
- - removing a column
46
- - changing the type of a column
47
- - setting a `NOT NULL` constraint with a default value
48
- - renaming a column
49
- - renaming a table
50
- - creating a table with the `force` option
51
- - adding an index non-concurrently (Postgres only)
52
- - adding a `json` column to an existing table (Postgres only)
104
+ ```ruby
105
+ class User < ApplicationRecord
106
+ self.ignored_columns = ["some_column"]
107
+ end
108
+ ```
53
109
 
54
- Also checks for best practices:
110
+ 2. Deploy code
111
+ 3. Write a migration to remove the column (wrap in `safety_assured` block)
55
112
 
56
- - keeping non-unique indexes to three columns or less
113
+ ```ruby
114
+ class RemoveSomeColumnFromUsers < ActiveRecord::Migration[6.1]
115
+ def change
116
+ safety_assured { remove_column :users, :some_column }
117
+ end
118
+ end
119
+ ```
57
120
 
58
- ## The Zero Downtime Way
121
+ 4. Deploy and run migration
59
122
 
60
123
  ### Adding a column with a default value
61
124
 
62
- Adding a column with a non-null default causes the entire table to be rewritten.
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
63
140
 
64
141
  Instead, add the column without a default value, then change the default.
65
142
 
66
143
  ```ruby
67
- class AddSomeColumnToUsers < ActiveRecord::Migration[5.2]
144
+ class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
68
145
  def up
69
146
  add_column :users, :some_column, :text
70
147
  change_column_default :users, :some_column, "default_value"
@@ -76,64 +153,71 @@ class AddSomeColumnToUsers < ActiveRecord::Migration[5.2]
76
153
  end
77
154
  ```
78
155
 
79
- Don’t backfill existing rows in this migration, as it can cause downtime. See the next section for how to do it safely.
80
-
81
- > With Postgres, this operation is safe as of Postgres 11
156
+ See the next section for how to backfill.
82
157
 
83
158
  ### Backfilling data
84
159
 
85
- To backfill data, use the Rails console or a separate migration with `disable_ddl_transaction!`. Avoid backfilling in a transaction, especially one that alters a table. See [this great article](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/) on why.
160
+ #### Bad
86
161
 
87
- ```ruby
88
- class BackfillSomeColumn < ActiveRecord::Migration[5.2]
89
- disable_ddl_transaction!
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/).
90
163
 
164
+ ```ruby
165
+ class AddSomeColumnToUsers < ActiveRecord::Migration[6.1]
91
166
  def change
92
- # Rails 5+
93
- User.in_batches.update_all some_column: "default_value"
94
-
95
- # Rails < 5
96
- User.find_in_batches do |records|
97
- User.where(id: records.map(&:id)).update_all some_column: "default_value"
98
- end
167
+ add_column :users, :some_column, :text
168
+ User.update_all some_column: "default_value"
99
169
  end
100
170
  end
101
171
  ```
102
172
 
103
- ### Removing a column
173
+ Also, running a single query to update data can cause issues for large tables.
104
174
 
105
- ActiveRecord caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots. To prevent this:
175
+ #### Good
106
176
 
107
- 1. Tell ActiveRecord to ignore the column from its cache
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!`.
108
178
 
109
- ```ruby
110
- # For Rails 5+
111
- class User < ApplicationRecord
112
- self.ignored_columns = ["some_column"]
113
- end
179
+ ```ruby
180
+ class BackfillSomeColumn < ActiveRecord::Migration[6.1]
181
+ disable_ddl_transaction!
114
182
 
115
- # For Rails < 5
116
- class User < ActiveRecord::Base
117
- def self.columns
118
- super.reject { |c| c.name == "some_column" }
183
+ def up
184
+ User.unscoped.in_batches do |relation|
185
+ relation.update_all some_column: "default_value"
186
+ sleep(0.01) # throttle
119
187
  end
120
188
  end
121
- ```
189
+ end
190
+ ```
122
191
 
123
- 2. Deploy code
124
- 3. Write a migration to remove the column (wrap in `safety_assured` block)
192
+ ### Changing the type of a column
125
193
 
126
- ```ruby
127
- class RemoveSomeColumnFromUsers < ActiveRecord::Migration[5.2]
128
- def change
129
- safety_assured { remove_column :users, :some_column }
130
- end
194
+ #### Bad
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.
197
+
198
+ ```ruby
199
+ class ChangeSomeColumnType < ActiveRecord::Migration[6.1]
200
+ def change
201
+ change_column :users, :some_column, :new_type
131
202
  end
132
- ```
203
+ end
204
+ ```
133
205
 
134
- 4. Deploy and run migration
206
+ A few changes don’t require a table rewrite (and are safe) in Postgres:
207
+
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+
214
+
215
+ And a few in MySQL and MariaDB:
135
216
 
136
- ### Renaming or changing the type of a column
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
219
+
220
+ #### Good
137
221
 
138
222
  A safer approach is to:
139
223
 
@@ -144,10 +228,47 @@ A safer approach is to:
144
228
  5. Stop writing to the old column
145
229
  6. Drop the old column
146
230
 
147
- One exception is changing a `varchar` column to `text`, which is safe in Postgres 9.1+.
231
+ ### Renaming a column
232
+
233
+ #### Bad
234
+
235
+ Renaming a column that’s in use will cause errors in your application.
236
+
237
+ ```ruby
238
+ class RenameSomeColumn < ActiveRecord::Migration[6.1]
239
+ def change
240
+ rename_column :users, :some_column, :new_name
241
+ end
242
+ end
243
+ ```
244
+
245
+ #### Good
246
+
247
+ A safer approach is to:
248
+
249
+ 1. Create a new column
250
+ 2. Write to both columns
251
+ 3. Backfill data from the old column to the new column
252
+ 4. Move reads from the old column to the new column
253
+ 5. Stop writing to the old column
254
+ 6. Drop the old column
148
255
 
149
256
  ### Renaming a table
150
257
 
258
+ #### Bad
259
+
260
+ Renaming a table that’s in use will cause errors in your application.
261
+
262
+ ```ruby
263
+ class RenameUsersToCustomers < ActiveRecord::Migration[6.1]
264
+ def change
265
+ rename_table :users, :customers
266
+ end
267
+ end
268
+ ```
269
+
270
+ #### Good
271
+
151
272
  A safer approach is to:
152
273
 
153
274
  1. Create a new table
@@ -157,12 +278,194 @@ A safer approach is to:
157
278
  5. Stop writing to the old table
158
279
  6. Drop the old table
159
280
 
160
- ### Adding an index (Postgres)
281
+ ### Creating a table with the force option
282
+
283
+ #### Bad
284
+
285
+ The `force` option can drop an existing table.
286
+
287
+ ```ruby
288
+ class CreateUsers < ActiveRecord::Migration[6.1]
289
+ def change
290
+ create_table :users, force: true do |t|
291
+ # ...
292
+ end
293
+ end
294
+ end
295
+ ```
296
+
297
+ #### Good
298
+
299
+ Create tables without the `force` option.
300
+
301
+ ```ruby
302
+ class CreateUsers < ActiveRecord::Migration[6.1]
303
+ def change
304
+ create_table :users do |t|
305
+ # ...
306
+ end
307
+ end
308
+ end
309
+ ```
310
+
311
+ If you intend to drop an existing table, run `drop_table` first.
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
+
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
+ ### Executing SQL directly
436
+
437
+ Strong Migrations can’t ensure safety for raw SQL statements. Make really sure that what you’re doing is safe, then use:
438
+
439
+ ```ruby
440
+ class ExecuteSQL < ActiveRecord::Migration[6.1]
441
+ def change
442
+ safety_assured { execute "..." }
443
+ end
444
+ end
445
+ ```
446
+
447
+ ### Adding an index non-concurrently
448
+
449
+ :turtle: Safe by default available
450
+
451
+ #### Bad
452
+
453
+ In Postgres, adding an index non-concurrently blocks writes.
454
+
455
+ ```ruby
456
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
457
+ def change
458
+ add_index :users, :some_column
459
+ end
460
+ end
461
+ ```
462
+
463
+ #### Good
161
464
 
162
465
  Add indexes concurrently.
163
466
 
164
467
  ```ruby
165
- class AddSomeIndexToUsers < ActiveRecord::Migration[5.2]
468
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
166
469
  disable_ddl_transaction!
167
470
 
168
471
  def change
@@ -171,59 +474,201 @@ class AddSomeIndexToUsers < ActiveRecord::Migration[5.2]
171
474
  end
172
475
  ```
173
476
 
174
- If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this. Check out [gindex](https://github.com/ankane/gindex) to quickly generate index migrations without memorizing the syntax.
477
+ If you forget `disable_ddl_transaction!`, the migration will fail. Also, note that indexes on new tables (those created in the same migration) don’t require this.
175
478
 
176
- Rails 5+ adds an index to references by default. To make sure this happens concurrently, use:
479
+ With [gindex](https://github.com/ankane/gindex), you can generate an index migration instantly with:
480
+
481
+ ```sh
482
+ rails g index table column
483
+ ```
484
+
485
+ ### Adding a reference
486
+
487
+ :turtle: Safe by default available
488
+
489
+ #### Bad
490
+
491
+ Rails adds an index non-concurrently to references by default, which blocks writes in Postgres.
177
492
 
178
493
  ```ruby
179
- class AddSomeReferenceToUsers < ActiveRecord::Migration[5.2]
494
+ class AddReferenceToUsers < ActiveRecord::Migration[6.1]
495
+ def change
496
+ add_reference :users, :city
497
+ end
498
+ end
499
+ ```
500
+
501
+ #### Good
502
+
503
+ Make sure the index is added concurrently.
504
+
505
+ ```ruby
506
+ class AddReferenceToUsers < ActiveRecord::Migration[6.1]
180
507
  disable_ddl_transaction!
181
508
 
182
509
  def change
183
- add_reference :users, :reference, index: false
184
- add_index :users, :reference_id, algorithm: :concurrently
510
+ add_reference :users, :city, index: {algorithm: :concurrently}
511
+ end
512
+ end
513
+ ```
514
+
515
+ ### Adding a foreign key
516
+
517
+ :turtle: Safe by default available
518
+
519
+ #### Bad
520
+
521
+ In Postgres, adding a foreign key blocks writes on both tables.
522
+
523
+ ```ruby
524
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
525
+ def change
526
+ add_foreign_key :users, :orders
185
527
  end
186
528
  end
187
529
  ```
188
530
 
189
- For polymorphic references, add a compound index on type and id.
531
+ or
190
532
 
191
- ### Adding a json column (Postgres)
533
+ ```ruby
534
+ class AddReferenceToUsers < ActiveRecord::Migration[6.1]
535
+ def change
536
+ add_reference :users, :order, foreign_key: true
537
+ end
538
+ end
539
+ ```
192
540
 
193
- There’s no equality operator for the `json` column type, which causes issues for `SELECT DISTINCT` queries.
541
+ #### Good
194
542
 
195
- If you’re on Postgres 9.4+, use `jsonb` instead.
543
+ Add the foreign key without validating existing rows, then validate them in a separate migration.
196
544
 
197
- If you must use `json`, replace all calls to `uniq` with a custom scope.
545
+ For Rails 5.2+, use:
198
546
 
199
547
  ```ruby
200
- class User < ApplicationRecord
201
- scope :uniq_on_id, -> { select("DISTINCT ON (users.id) users.*") }
548
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[6.1]
549
+ def change
550
+ add_foreign_key :users, :orders, validate: false
551
+ end
552
+ end
553
+ ```
554
+
555
+ Then:
556
+
557
+ ```ruby
558
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[6.1]
559
+ def change
560
+ validate_foreign_key :users, :orders
561
+ end
562
+ end
563
+ ```
564
+
565
+ For Rails < 5.2, use:
566
+
567
+ ```ruby
568
+ class AddForeignKeyOnUsers < ActiveRecord::Migration[5.1]
569
+ 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
573
+ end
202
574
  end
203
575
  ```
204
576
 
205
- Then add the column:
577
+ Then:
206
578
 
207
579
  ```ruby
208
- class AddJsonColumnToUsers < ActiveRecord::Migration[5.2]
580
+ class ValidateForeignKeyOnUsers < ActiveRecord::Migration[5.1]
209
581
  def change
210
- safety_assured { add_column :users, :some_column, :json }
582
+ safety_assured do
583
+ execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "fk_rails_c1e9b98e31"'
584
+ end
211
585
  end
212
586
  end
213
587
  ```
214
588
 
589
+ ### Adding a json column
590
+
591
+ #### Bad
592
+
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.
594
+
595
+ ```ruby
596
+ class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
597
+ def change
598
+ add_column :users, :properties, :json
599
+ end
600
+ end
601
+ ```
602
+
603
+ #### Good
604
+
605
+ Use `jsonb` instead.
606
+
607
+ ```ruby
608
+ class AddPropertiesToUsers < ActiveRecord::Migration[6.1]
609
+ def change
610
+ add_column :users, :properties, :jsonb
611
+ end
612
+ end
613
+ ```
614
+
615
+ ### Keeping non-unique indexes to three columns or less
616
+
617
+ #### Bad
618
+
619
+ Adding a non-unique index with more than three columns rarely improves performance.
620
+
621
+ ```ruby
622
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
623
+ def change
624
+ add_index :users, [:a, :b, :c, :d]
625
+ end
626
+ end
627
+ ```
628
+
629
+ #### Good
630
+
631
+ Instead, start an index with columns that narrow down the results the most.
632
+
633
+ ```ruby
634
+ class AddSomeIndexToUsers < ActiveRecord::Migration[6.1]
635
+ def change
636
+ add_index :users, [:b, :d]
637
+ end
638
+ end
639
+ ```
640
+
641
+ For Postgres, be sure to add them concurrently.
642
+
215
643
  ## Assuring Safety
216
644
 
217
- To mark a step in the migration as safe, despite using method that might otherwise be dangerous, wrap it in a `safety_assured` block.
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.
218
646
 
219
647
  ```ruby
220
- class MySafeMigration < ActiveRecord::Migration[5.2]
648
+ class MySafeMigration < ActiveRecord::Migration[6.1]
221
649
  def change
222
650
  safety_assured { remove_column :users, :some_column }
223
651
  end
224
652
  end
225
653
  ```
226
654
 
655
+ Certain methods like `execute` and `change_table` cannot be inspected and are prevented from running by default. Make sure what you’re doing is really safe and use this pattern.
656
+
657
+ ## Safe by Default
658
+
659
+ Make operations safe by default.
660
+
661
+ - adding and removing an index
662
+ - adding a foreign key
663
+ - adding a check constraint
664
+ - setting NOT NULL on an existing column
665
+
666
+ Add to `config/initializers/strong_migrations.rb`:
667
+
668
+ ```ruby
669
+ StrongMigrations.safe_by_default = true
670
+ ```
671
+
227
672
  ## Custom Checks
228
673
 
229
674
  Add your own custom checks with:
@@ -238,39 +683,34 @@ end
238
683
 
239
684
  Use the `stop!` method to stop migrations.
240
685
 
241
- ## Existing Migrations
242
-
243
- To mark migrations as safe that were created before installing this gem, create an initializer with:
244
-
245
- ```ruby
246
- StrongMigrations.start_after = 20170101000000
247
- ```
686
+ Note: Since `remove_column` always requires a `safety_assured` block, it’s not possible to add a custom check for `remove_column` operations.
248
687
 
249
- Use the version from your latest migration.
688
+ ## Opt-in Checks
250
689
 
251
- ## Dangerous Tasks
690
+ ### Removing an index non-concurrently
252
691
 
253
- For safety, dangerous rake tasks are disabled in production - `db:drop`, `db:reset`, `db:schema:load`, and `db:structure:load`. To get around this, use:
692
+ Postgres supports removing indexes concurrently, but removing them non-concurrently shouldn’t be an issue for most applications. You can enable this check with:
254
693
 
255
- ```sh
256
- SAFETY_ASSURED=1 rake db:drop
694
+ ```ruby
695
+ StrongMigrations.enable_check(:remove_index)
257
696
  ```
258
697
 
259
- ## Faster Migrations
698
+ ## Disable Checks
260
699
 
261
- Only dump the schema when adding a new migration. If you use Git, create an initializer with:
700
+ Disable specific checks with:
262
701
 
263
702
  ```ruby
264
- ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
265
- `git status db/migrate/ --porcelain`.present?
703
+ StrongMigrations.disable_check(:add_index)
266
704
  ```
267
705
 
268
- ## Schema Sanity
706
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
707
+
708
+ ## Down Migrations / Rollbacks
269
709
 
270
- 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/13/alphabetize-schema-rb-columns/). Add to the end of your `Rakefile`:
710
+ By default, checks are disabled when migrating down. Enable them with:
271
711
 
272
712
  ```ruby
273
- task "db:schema:dump": "strong_migrations:alphabetize_columns"
713
+ StrongMigrations.check_down = true
274
714
  ```
275
715
 
276
716
  ## Custom Messages
@@ -283,7 +723,92 @@ StrongMigrations.error_messages[:add_column_default] = "Your custom instructions
283
723
 
284
724
  Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
285
725
 
286
- ## Analyze Tables (Postgres)
726
+ ## Migration Timeouts
727
+
728
+ It’s extremely important to set a short lock timeout for migrations. This way, if a migration can’t acquire a lock in a timely manner, other statements won’t be stuck behind it. We also recommend setting a long statement timeout so migrations can run for a while.
729
+
730
+ Create `config/initializers/strong_migrations.rb` with:
731
+
732
+ ```ruby
733
+ StrongMigrations.lock_timeout = 10.seconds
734
+ StrongMigrations.statement_timeout = 1.hour
735
+ ```
736
+
737
+ Or set the timeouts directly on the database user that runs migrations. For Postgres, use:
738
+
739
+ ```sql
740
+ ALTER ROLE myuser SET lock_timeout = '10s';
741
+ ALTER ROLE myuser SET statement_timeout = '1h';
742
+ ```
743
+
744
+ Note: If you use PgBouncer in transaction mode, you must set timeouts on the database user.
745
+
746
+ ## App Timeouts
747
+
748
+ 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.
749
+
750
+ For Postgres:
751
+
752
+ ```yml
753
+ production:
754
+ connect_timeout: 5
755
+ variables:
756
+ statement_timeout: 15s
757
+ lock_timeout: 10s
758
+ ```
759
+
760
+ Note: If you use PgBouncer in transaction mode, you must set the statement and lock timeouts on the database user as shown above.
761
+
762
+ For MySQL:
763
+
764
+ ```yml
765
+ production:
766
+ connect_timeout: 5
767
+ read_timeout: 5
768
+ write_timeout: 5
769
+ variables:
770
+ max_execution_time: 15000 # ms
771
+ lock_wait_timeout: 10 # sec
772
+
773
+ ```
774
+
775
+ For MariaDB:
776
+
777
+ ```yml
778
+ production:
779
+ connect_timeout: 5
780
+ read_timeout: 5
781
+ write_timeout: 5
782
+ variables:
783
+ max_statement_time: 15 # sec
784
+ lock_wait_timeout: 10 # sec
785
+ ```
786
+
787
+ For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
788
+
789
+ ## Existing Migrations
790
+
791
+ To mark migrations as safe that were created before installing this gem, create an initializer with:
792
+
793
+ ```ruby
794
+ StrongMigrations.start_after = 20170101000000
795
+ ```
796
+
797
+ Use the version from your latest migration.
798
+
799
+ ## Target Version
800
+
801
+ If your development database version is different from production, you can specify the production version so the right checks run in development.
802
+
803
+ ```ruby
804
+ StrongMigrations.target_version = 10 # or "8.0.12", "10.3.2", etc
805
+ ```
806
+
807
+ The major version works well for Postgres, while the full version is recommended for MySQL and MariaDB.
808
+
809
+ For safety, this option only affects development and test environments. In other environments, the actual server version is always used.
810
+
811
+ ## Analyze Tables
287
812
 
288
813
  Analyze tables automatically (to update planner statistics) after an index is added. Create an initializer with:
289
814
 
@@ -291,28 +816,41 @@ Analyze tables automatically (to update planner statistics) after an index is ad
291
816
  StrongMigrations.auto_analyze = true
292
817
  ```
293
818
 
294
- ## Lock Timeout (Postgres)
819
+ ## Faster Migrations
295
820
 
296
- It’s a good idea to set a lock timeout for the database user that runs migrations. This way, if migrations can’t acquire a lock in a timely manner, other statements won’t be stuck behind it. Here’s a great explanation of [how lock queues work](https://www.citusdata.com/blog/2018/02/15/when-postgresql-blocks/).
821
+ Only dump the schema when adding a new migration. If you use Git, create an initializer with:
297
822
 
298
- ```sql
299
- ALTER ROLE myuser SET lock_timeout = '10s';
823
+ ```ruby
824
+ ActiveRecord::Base.dump_schema_after_migration = Rails.env.development? &&
825
+ `git status db/migrate/ --porcelain`.present?
826
+ ```
827
+
828
+ ## Schema Sanity
829
+
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`:
831
+
832
+ ```ruby
833
+ task "db:schema:dump": "strong_migrations:alphabetize_columns"
300
834
  ```
301
835
 
302
- There’s also [a gem](https://github.com/gocardless/activerecord-safer_migrations) you can use for this.
836
+ ## Permissions
303
837
 
304
- ## Bigint Primary Keys (Postgres & MySQL)
838
+ We recommend using a [separate database user](https://ankane.org/postgres-users) for migrations when possible so you don’t need to grant your app user permission to alter tables.
305
839
 
306
- Rails 5.1+ uses `bigint` for primary keys to keep you from running out of ids. To get this in earlier versions of Rails, check out [rails-bigint-primarykey](https://github.com/Shopify/rails-bigint-primarykey).
840
+ ## Smaller Projects
841
+
842
+ You probably don’t need this gem for smaller projects, as operations that are unsafe at scale can be perfectly safe on smaller, low-traffic tables.
307
843
 
308
844
  ## Additional Reading
309
845
 
310
846
  - [Rails Migrations with No Downtime](https://pedro.herokuapp.com/past/2011/7/13/rails_migrations_with_no_downtime/)
311
- - [Safe Operations For High Volume PostgreSQL](https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/)
847
+ - [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/)
849
+ - [MariaDB InnoDB Online DDL Overview](https://mariadb.com/kb/en/innodb-online-ddl-overview/)
312
850
 
313
851
  ## Credits
314
852
 
315
- Thanks to Bob Remeika and David Waller for the [original code](https://github.com/foobarfighter/safe-migrations).
853
+ 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.
316
854
 
317
855
  ## Contributing
318
856
 
@@ -323,11 +861,18 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
323
861
  - Write, clarify, or fix documentation
324
862
  - Suggest or add new features
325
863
 
326
- To get started with development and testing:
864
+ To get started with development:
327
865
 
328
866
  ```sh
329
867
  git clone https://github.com/ankane/strong_migrations.git
330
868
  cd strong_migrations
331
869
  bundle install
870
+
871
+ # Postgres
872
+ createdb strong_migrations_test
332
873
  bundle exec rake test
874
+
875
+ # MySQL and MariaDB
876
+ mysqladmin create strong_migrations_test
877
+ ADAPTER=mysql2 bundle exec rake test
333
878
  ```