Skip to content

Commit dd488c0

Browse files
authored
Fix reversible migration modify/3 (#384)
1 parent 0b8b25b commit dd488c0

File tree

6 files changed

+43
-9
lines changed

6 files changed

+43
-9
lines changed

integration_test/sql/migration.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,17 @@ defmodule Ecto.Integration.MigrationTest do
7676
def change do
7777
create table(:modify_from_products) do
7878
add :value, :integer
79+
add :nullable, :integer, null: false
7980
end
8081

8182
if direction() == :up do
8283
flush()
83-
PoolRepo.insert_all "modify_from_products", [[value: 1]]
84+
PoolRepo.insert_all "modify_from_products", [[value: 1, nullable: 1]]
8485
end
8586

8687
alter table(:modify_from_products) do
8788
modify :value, :bigint, from: :integer
89+
modify :nullable, :bigint, null: true, from: {:integer, null: false}
8890
end
8991
end
9092
end

lib/ecto/migration.ex

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,10 +1035,12 @@ defmodule Ecto.Migration do
10351035
Modifies the type of a column when altering a table.
10361036
10371037
This command is not reversible unless the `:from` option is provided.
1038-
If the `:from` value is a `%Reference{}`, the adapter will try to drop
1038+
When the `:from` option is set, the adapter will try to drop
10391039
the corresponding foreign key constraints before modifying the type.
1040-
Note `:from` cannot be used to modify primary keys, as those are
1041-
generally trickier to make reversible.
1040+
Generally speaking, you want to pass the type and each option
1041+
you are modifying to `:from`, so the column can be rolled back properly.
1042+
However, note that `:from` cannot be be used to modify primary keys,
1043+
as those are generally trickier to revert.
10421044
10431045
See `add/3` for more information on supported types.
10441046
@@ -1054,11 +1056,21 @@ defmodule Ecto.Migration do
10541056
modify :title, :text
10551057
end
10561058
1059+
# Self rollback when using the :from option
1060+
alter table("posts") do
1061+
modify :title, :text, from: :string
1062+
end
1063+
1064+
# Modify column with rollback options
1065+
alter table("posts") do
1066+
modify :title, :text, null: false, from: {:string, null: true}
1067+
end
1068+
10571069
## Options
10581070
10591071
* `:null` - determines whether the column accepts null values.
10601072
* `:default` - changes the default value of the column.
1061-
* `:from` - specifies the current type of the column.
1073+
* `:from` - specifies the current type and options of the column.
10621074
* `:size` - specifies the size of the type (for example, the number of characters).
10631075
The default is no size.
10641076
* `:precision` - the precision for a numeric type. Required when `:scale` is

lib/ecto/migration/runner.ex

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,17 @@ defmodule Ecto.Migration.Runner do
253253
end
254254
defp table_reverse([{:modify, name, type, opts} | t], acc) do
255255
case opts[:from] do
256-
nil -> false
257-
from -> table_reverse(t, [{:modify, name, from, Keyword.put(opts, :from, type)} | acc])
256+
nil ->
257+
false
258+
259+
{reverse_type, from_opts} when is_list(from_opts) ->
260+
reverse_from = {type, Keyword.delete(opts, :from)}
261+
reverse_opts = Keyword.put(from_opts, :from, reverse_from)
262+
table_reverse(t, [{:modify, name, reverse_type, reverse_opts} | acc])
263+
264+
reverse_type ->
265+
reverse_opts = Keyword.put(opts, :from, type)
266+
table_reverse(t, [{:modify, name, reverse_type, reverse_opts} | acc])
258267
end
259268
end
260269
defp table_reverse([{:add, name, _type, _opts} | t], acc) do

test/ecto/adapters/myxql_test.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,7 @@ defmodule Ecto.Adapters.MyXQLTest do
13351335
{:modify, :status, :string, from: :integer},
13361336
{:modify, :user_id, :integer, from: %Reference{table: :users}},
13371337
{:modify, :group_id, %Reference{table: :groups, column: :gid}, from: %Reference{table: :groups}},
1338+
{:modify, :status, :string, [null: false, size: 100, from: {:integer, null: true, size: 50}]},
13381339
{:remove, :summary},
13391340
{:remove, :body, :text, []},
13401341
{:remove, :space_id, %Reference{table: :author}, []},
@@ -1357,6 +1358,7 @@ defmodule Ecto.Adapters.MyXQLTest do
13571358
DROP FOREIGN KEY `posts_group_id_fkey`,
13581359
MODIFY `group_id` BIGINT UNSIGNED,
13591360
ADD CONSTRAINT `posts_group_id_fkey` FOREIGN KEY (`group_id`) REFERENCES `groups`(`gid`),
1361+
MODIFY `status` varchar(100) NOT NULL,
13601362
DROP `summary`,
13611363
DROP `body`,
13621364
DROP FOREIGN KEY `posts_space_id_fkey`,

test/ecto/adapters/postgres_test.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,6 +1595,7 @@ defmodule Ecto.Adapters.PostgresTest do
15951595
{:modify, :status, :string, from: :integer},
15961596
{:modify, :user_id, :integer, from: %Reference{table: :users}},
15971597
{:modify, :group_id, %Reference{table: :groups, column: :gid}, from: %Reference{table: :groups}},
1598+
{:modify, :status, :string, [null: false, size: 100, from: {:integer, null: true, size: 50}]},
15981599
{:remove, :summary},
15991600
{:remove, :body, :text, []},
16001601
{:remove, :space_id, %Reference{table: :author}, []},
@@ -1625,6 +1626,8 @@ defmodule Ecto.Adapters.PostgresTest do
16251626
DROP CONSTRAINT "posts_group_id_fkey",
16261627
ALTER COLUMN "group_id" TYPE bigint,
16271628
ADD CONSTRAINT "posts_group_id_fkey" FOREIGN KEY ("group_id") REFERENCES "groups"("gid"),
1629+
ALTER COLUMN "status" TYPE varchar(100),
1630+
ALTER COLUMN "status" SET NOT NULL,
16281631
DROP COLUMN "summary",
16291632
DROP COLUMN "body",
16301633
DROP CONSTRAINT "posts_space_id_fkey",

test/ecto/migration_test.exs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -718,12 +718,18 @@ defmodule Ecto.MigrationTest do
718718
alter table(:posts) do
719719
add :summary, :text
720720
modify :extension, :text, from: :string
721+
modify :author, :string, null: false, from: :text
722+
modify :title, :string, null: false, size: 100, from: {:text, null: true, size: 255}
721723
end
722724
flush()
723725

724726
assert last_command() ==
725-
{:alter, %Table{name: "posts"},
726-
[{:modify, :extension, :string, from: :text}, {:remove, :summary}]}
727+
{:alter, %Table{name: "posts"}, [
728+
{:modify, :title, :text, [from: {:string, null: false, size: 100}, null: true, size: 255]},
729+
{:modify, :author, :text, [from: :string, null: false]},
730+
{:modify, :extension, :string, from: :text},
731+
{:remove, :summary}
732+
]}
727733

728734
assert_raise Ecto.MigrationError, ~r/cannot reverse migration command/, fn ->
729735
alter table(:posts) do

0 commit comments

Comments
 (0)