Skip to content

Commit

Permalink
Added check for add_column with auto-incrementing columns
Browse files Browse the repository at this point in the history
  • Loading branch information
ankane committed Mar 11, 2024
1 parent bfde0b9 commit 9eea4ee
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 1.7.1 (unreleased)
## 1.8.0 (unreleased)

- Added check for `add_column` with auto-incrementing columns
- Updated instructions for removing a column to append to `ignored_columns`
- Fixed check for adding a column with a default value for MySQL and MariaDB

Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Potentially dangerous operations:
- [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 a stored generated column](#adding-a-stored-generated-column)
- [adding a check constraint](#adding-a-check-constraint)
- [executing SQL directly](#executing-SQL-directly)
Expand Down Expand Up @@ -256,6 +257,26 @@ end

If you intend to drop an existing table, run `drop_table` first.

### Adding an auto-incrementing column

#### Bad

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.

```ruby
class AddIdToCitiesUsers < ActiveRecord::Migration[7.1]
def change
add_column :cities_users, :id, :primary_key
end
end
```

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) when using statement-based replication.

#### Good

Create a new table and migrate the data with the same steps as [renaming a table](#renaming-a-table).

### Adding a stored generated column

#### Bad
Expand Down
4 changes: 4 additions & 0 deletions lib/strong_migrations/adapters/abstract_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ def rewrite_blocks
"reads and writes"
end

def auto_incrementing_types
["primary_key"]
end

private

def connection
Expand Down
4 changes: 4 additions & 0 deletions lib/strong_migrations/adapters/postgresql_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ def default_volatile?(default)
rows.empty? || rows.any? { |r| r["provolatile"] == "v" }
end

def auto_incrementing_types
["primary_key", "serial", "bigserial"]
end

private

def set_timeout(setting, timeout)
Expand Down
4 changes: 4 additions & 0 deletions lib/strong_migrations/checks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def check_add_column(*args)
if type.to_s == "virtual" && options[:stored]
raise_error :add_column_generated_stored, rewrite_blocks: adapter.rewrite_blocks
end

if adapter.auto_incrementing_types.include?(type.to_s)
raise_error :add_column_auto_incrementing, rewrite_blocks: adapter.rewrite_blocks
end
end

def check_add_exclusion_constraint(*args)
Expand Down
3 changes: 3 additions & 0 deletions lib/strong_migrations/error_messages.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def change
add_column_generated_stored:
"Adding a stored generated column blocks %{rewrite_blocks} while the entire table is rewritten.",

add_column_auto_incrementing:
"Adding an auto-incrementing column blocks %{rewrite_blocks} while the entire table is rewritten.",

change_column:
"Changing the type of an existing column blocks %{rewrite_blocks}
while the entire table is rewritten. A safer approach is to:
Expand Down
14 changes: 14 additions & 0 deletions test/add_column_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,18 @@ def test_generated_virtual
skip if postgresql?
assert_safe AddColumnGeneratedVirtual
end

def test_primary_key
assert_unsafe AddColumnPrimaryKey
end

def test_serial
skip unless postgresql?
assert_unsafe AddColumnSerial
end

def test_bigserial
skip unless postgresql?
assert_unsafe AddColumnBigserial
end
end
18 changes: 18 additions & 0 deletions test/migrations/add_column.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,21 @@ def change
add_column :users, :nice, :virtual, type: :string, as: "LOWER(city)"
end
end

class AddColumnPrimaryKey < TestMigration
def change
add_column :users, :nice, :primary_key
end
end

class AddColumnSerial < TestMigration
def change
add_column :users, :nice, :serial
end
end

class AddColumnBigserial < TestMigration
def change
add_column :users, :nice, :bigserial
end
end

0 comments on commit 9eea4ee

Please sign in to comment.