README.md in strong_migrations-1.8.0 vs README.md in strong_migrations-2.0.0

- old
+ new

@@ -62,11 +62,11 @@ - [removing a column](#removing-a-column) - [changing the type of a column](#changing-the-type-of-a-column) - [renaming a column](#renaming-a-column) - [renaming a table](#renaming-a-table) - [creating a table with the force option](#creating-a-table-with-the-force-option) -- [adding an auto-incrementing column](#adding-an-auto-incrementing-column) [unreleased] +- [adding an auto-incrementing column](#adding-an-auto-incrementing-column) - [adding a stored generated column](#adding-a-stored-generated-column) - [adding a check constraint](#adding-a-check-constraint) - [executing SQL directly](#executing-SQL-directly) - [backfilling data](#backfilling-data) @@ -77,11 +77,11 @@ - [adding a foreign key](#adding-a-foreign-key) - [adding a unique constraint](#adding-a-unique-constraint) - [adding an exclusion constraint](#adding-an-exclusion-constraint) - [adding a json column](#adding-a-json-column) - [setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column) -- [adding a column with a default value](#adding-a-column-with-a-default-value) +- [adding a column with a volatile default value](#adding-a-column-with-a-volatile-default-value) Config-specific checks: - [changing the default value of a column](#changing-the-default-value-of-a-column) @@ -504,11 +504,11 @@ #### Bad In Postgres, adding a unique constraint creates a unique index, which blocks reads and writes. ```ruby -class AddUniqueContraint < ActiveRecord::Migration[7.1] +class AddUniqueConstraint < ActiveRecord::Migration[7.1] def change add_unique_constraint :users, :some_column end end ``` @@ -516,11 +516,11 @@ #### Good Create a unique index concurrently, then use it for the constraint. ```ruby -class AddUniqueContraint < ActiveRecord::Migration[7.1] +class AddUniqueConstraint < ActiveRecord::Migration[7.1] disable_ddl_transaction! def up add_index :users, :some_column, unique: true, algorithm: :concurrently add_unique_constraint :users, using_index: "index_users_on_some_column" @@ -537,11 +537,11 @@ #### Bad In Postgres, adding an exclusion constraint blocks reads and writes while every row is checked. ```ruby -class AddExclusionContraint < ActiveRecord::Migration[7.1] +class AddExclusionConstraint < ActiveRecord::Migration[7.1] def change add_exclusion_constraint :users, "number WITH =", using: :gist end end ``` @@ -594,91 +594,53 @@ #### Good Instead, add a check constraint. -For Rails 6.1+, use: - ```ruby class SetSomeColumnNotNull < ActiveRecord::Migration[7.1] def change add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false end end ``` -For Rails < 6.1, use: +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. ```ruby -class SetSomeColumnNotNull < ActiveRecord::Migration[6.0] - def change - safety_assured do - execute 'ALTER TABLE "users" ADD CONSTRAINT "users_some_column_null" CHECK ("some_column" IS NOT NULL) NOT VALID' - end - end -end -``` - -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. - -For Rails 6.1+, use: - -```ruby class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.1] def change validate_check_constraint :users, name: "users_some_column_null" - - # in Postgres 12+, you can then safely set NOT NULL on the column change_column_null :users, :some_column, false remove_check_constraint :users, name: "users_some_column_null" end end ``` -For Rails < 6.1, use: +### Adding a column with a volatile default value -```ruby -class ValidateSomeColumnNotNull < ActiveRecord::Migration[6.0] - def change - safety_assured do - execute 'ALTER TABLE "users" VALIDATE CONSTRAINT "users_some_column_null"' - end - - # in Postgres 12+, you can then safely set NOT NULL on the column - change_column_null :users, :some_column, false - safety_assured do - execute 'ALTER TABLE "users" DROP CONSTRAINT "users_some_column_null"' - end - end -end -``` - -### Adding a column with a default value - #### Bad -In earlier versions of Postgres, 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. +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. ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[7.1] def change - add_column :users, :some_column, :text, default: "default_value" + add_column :users, :some_column, :uuid, default: "gen_random_uuid()" end end ``` -In Postgres 11+, this no longer requires a table rewrite and is safe (except for volatile functions like `gen_random_uuid()`). - #### Good Instead, add the column without a default value, then change the default. ```ruby class AddSomeColumnToUsers < ActiveRecord::Migration[7.1] def up - add_column :users, :some_column, :text - change_column_default :users, :some_column, "default_value" + add_column :users, :some_column, :uuid + change_column_default :users, :some_column, from: nil, to: "gen_random_uuid()" end def down remove_column :users, :some_column end @@ -736,11 +698,11 @@ Instead, start an index with columns that narrow down the results the most. ```ruby class AddSomeIndexToUsers < ActiveRecord::Migration[7.1] def change - add_index :users, [:b, :d] + add_index :users, [:d, :b] end end ``` For Postgres, be sure to add them concurrently. @@ -923,17 +885,17 @@ ## Target Version If your development database version is different from production, you can specify the production version so the right checks run in development. ```ruby -StrongMigrations.target_version = 10 # or "8.0.12", "10.3.2", etc +StrongMigrations.target_version = 10 # or 8.0, 10.5, etc ``` -The major version works well for Postgres, while the full version is recommended for MySQL and MariaDB. +The major version works well for Postgres, while the major and minor version is recommended for MySQL and MariaDB. For safety, this option only affects development and test environments. In other environments, the actual server version is always used. -If your app has multiple databases with different versions, with Rails 6.1+, you can use: +If your app has multiple databases with different versions, you can use: ```ruby StrongMigrations.target_version = {primary: 13, catalog: 15} ```