Skip to content

Commit c91a10a

Browse files
authored
migration: Add CASCADE option to DROP command (#298)
1 parent bbdcf0b commit c91a10a

File tree

10 files changed

+197
-75
lines changed

10 files changed

+197
-75
lines changed

lib/ecto/adapter/migration.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ defmodule Ecto.Adapter.Migration do
1616
| {:create, Table.t(), [table_subcommand]}
1717
| {:create_if_not_exists, Table.t(), [table_subcommand]}
1818
| {:alter, Table.t(), [table_subcommand]}
19-
| {:drop, Table.t()}
20-
| {:drop_if_exists, Table.t()}
19+
| {:drop, Table.t(), :restrict | :cascade | nil}
20+
| {:drop_if_exists, Table.t(), :restrict | :cascade | nil}
2121
| {:create, Index.t()}
2222
| {:create_if_not_exists, Index.t()}
23-
| {:drop, Index.t()}
24-
| {:drop_if_exists, Index.t()}
23+
| {:drop, Index.t(), :restrict | :cascade | nil}
24+
| {:drop_if_exists, Index.t(), :restrict | :cascade | nil}
2525

2626
@typedoc "All commands allowed within the block passed to `table/2`"
2727
@type table_subcommand ::

lib/ecto/adapters/myxql/connection.ex

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,12 @@ if Code.ensure_loaded?(MyXQL) do
730730
engine_expr(table.engine), options_expr(table.options)]]
731731
end
732732

733-
def execute_ddl({command, %Table{} = table}) when command in [:drop, :drop_if_exists] do
733+
def execute_ddl({command, %Table{} = table, :cascade}) when command in [:drop, :drop_if_exists] do
734+
[cmd | _] = execute_ddl({command, %Table{} = table, nil})
735+
[cmd ++ [" CASCADE"]]
736+
end
737+
738+
def execute_ddl({command, %Table{} = table, _}) when command in [:drop, :drop_if_exists] do
734739
[["DROP TABLE ", if_do(command == :drop_if_exists, "IF EXISTS "),
735740
quote_table(table.prefix, table.name)]]
736741
end
@@ -762,20 +767,23 @@ if Code.ensure_loaded?(MyXQL) do
762767
def execute_ddl({:create, %Constraint{exclude: exclude}}) when is_binary(exclude),
763768
do: error!(nil, "MySQL adapter does not support exclusion constraints")
764769

765-
def execute_ddl({:drop, %Index{} = index}) do
770+
def execute_ddl({:drop, %Index{}, :cascade}),
771+
do: error!(nil, "MySQL adapter does not support cascade in drop index")
772+
773+
def execute_ddl({:drop, %Index{} = index, _}) do
766774
[["DROP INDEX ",
767775
quote_name(index.name),
768776
" ON ", quote_table(index.prefix, index.table),
769777
if_do(index.concurrently, " LOCK=NONE")]]
770778
end
771779

772-
def execute_ddl({:drop, %Constraint{}}),
780+
def execute_ddl({:drop, %Constraint{}, _}),
773781
do: error!(nil, "MySQL adapter does not support constraints")
774782

775-
def execute_ddl({:drop_if_exists, %Constraint{}}),
783+
def execute_ddl({:drop_if_exists, %Constraint{}, _}),
776784
do: error!(nil, "MySQL adapter does not support constraints")
777785

778-
def execute_ddl({:drop_if_exists, %Index{}}),
786+
def execute_ddl({:drop_if_exists, %Index{}, _}),
779787
do: error!(nil, "MySQL adapter does not support drop if exists for index")
780788

781789
def execute_ddl({:rename, %Table{} = current_table, %Table{} = new_table}) do

lib/ecto/adapters/postgres/connection.ex

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,12 @@ if Code.ensure_loaded?(Postgrex) do
845845
comments_for_columns(table_name, columns)
846846
end
847847

848-
def execute_ddl({command, %Table{} = table}) when command in @drops do
848+
def execute_ddl({command, %Table{} = table, :cascade}) when command in @drops do
849+
[cmd | _] = execute_ddl({command, table, nil})
850+
[cmd ++ [" CASCADE"]]
851+
end
852+
853+
def execute_ddl({command, %Table{} = table, _}) when command in @drops do
849854
[["DROP TABLE ", if_do(command == :drop_if_exists, "IF EXISTS "),
850855
quote_table(table.prefix, table.name)]]
851856
end
@@ -880,7 +885,12 @@ if Code.ensure_loaded?(Postgrex) do
880885
queries ++ comments_on("INDEX", quote_table(index.prefix, index.name), index.comment)
881886
end
882887

883-
def execute_ddl({command, %Index{} = index}) when command in @drops do
888+
def execute_ddl({command, %Index{} = index, :cascade}) when command in @drops do
889+
[cmd | _] = execute_ddl({command, index, nil})
890+
[cmd ++ [" CASCADE"]]
891+
end
892+
893+
def execute_ddl({command, %Index{} = index, _}) when command in @drops do
884894
[["DROP INDEX ",
885895
if_do(index.concurrently, "CONCURRENTLY "),
886896
if_do(command == :drop_if_exists, "IF EXISTS "),
@@ -905,12 +915,12 @@ if Code.ensure_loaded?(Postgrex) do
905915
queries ++ comments_on("CONSTRAINT", constraint.name, constraint.comment, table_name)
906916
end
907917

908-
def execute_ddl({:drop, %Constraint{} = constraint}) do
918+
def execute_ddl({:drop, %Constraint{} = constraint, _}) do
909919
[["ALTER TABLE ", quote_table(constraint.prefix, constraint.table),
910920
" DROP CONSTRAINT ", quote_name(constraint.name)]]
911921
end
912922

913-
def execute_ddl({:drop_if_exists, %Constraint{} = constraint}) do
923+
def execute_ddl({:drop_if_exists, %Constraint{} = constraint, _}) do
914924
[["ALTER TABLE ", quote_table(constraint.prefix, constraint.table),
915925
" DROP CONSTRAINT IF EXISTS ", quote_name(constraint.name)]]
916926
end

lib/ecto/adapters/tds/connection.ex

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,10 @@ if Code.ensure_loaded?(Tds) do
10091009
]
10101010
end
10111011

1012-
def execute_ddl({command, %Table{} = table}) when command in [:drop, :drop_if_exists] do
1012+
def execute_ddl({command, %Table{}, :cascade}) when command in [:drop, :drop_if_exists],
1013+
do: error!(nil, "MSSQL does not support `CASCADE` in DROP TABLE commands")
1014+
1015+
def execute_ddl({command, %Table{} = table, _}) when command in [:drop, :drop_if_exists] do
10131016
prefix = table.prefix
10141017

10151018
[
@@ -1124,7 +1127,10 @@ if Code.ensure_loaded?(Tds) do
11241127
]
11251128
end
11261129

1127-
def execute_ddl({command, %Index{} = index}) when command in [:drop, :drop_if_exists] do
1130+
def execute_ddl({command, %Index{}, :cascade}) when command in [:drop, :drop_if_exists],
1131+
do: error!(nil, "MSSQL does not support `CASCADE` in DROP INDEX commands")
1132+
1133+
def execute_ddl({command, %Index{} = index, _}) when command in [:drop, :drop_if_exists] do
11281134
prefix = index.prefix
11291135

11301136
[
@@ -1144,7 +1150,10 @@ if Code.ensure_loaded?(Tds) do
11441150
]
11451151
end
11461152

1147-
def execute_ddl({command, %Constraint{} = constraint})
1153+
def execute_ddl({command, %Constraint{}, :cascade}) when command in [:drop, :drop_if_exists],
1154+
do: error!(nil, "MSSQL does not support `CASCADE` in DROP CONSTRAINT commands")
1155+
1156+
def execute_ddl({command, %Constraint{} = constraint, _})
11481157
when command in [:drop, :drop_if_exists] do
11491158
table_name = quote_table(constraint.prefix, constraint.table)
11501159

lib/ecto/migration.ex

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -575,10 +575,20 @@ defmodule Ecto.Migration do
575575
drop index("posts", [:name])
576576
drop table("posts")
577577
drop constraint("products", "price_must_be_positive")
578+
drop index("posts", [:name]), cascade: true
579+
drop table("posts"), cascade: true
580+
581+
## Options
582+
583+
* `:cascade` - when `true`, automatically drop objects that depend
584+
- on the index, and in turn all objects that depend on those objects
585+
- on the table
586+
Default is `false`
578587
579588
"""
580-
def drop(%{} = index_or_table_or_constraint) do
581-
Runner.execute {:drop, __prefix__(index_or_table_or_constraint)}
589+
def drop(%{} = index_or_table_or_constraint, opts \\ []) when is_list(opts) do
590+
Runner.execute {:drop, __prefix__(index_or_table_or_constraint), Keyword.get(opts, :mode)}
591+
582592
index_or_table_or_constraint
583593
end
584594

@@ -591,10 +601,19 @@ defmodule Ecto.Migration do
591601
592602
drop_if_exists index("posts", [:name])
593603
drop_if_exists table("posts")
604+
drop_if_exists index("posts, [:name]), mode: :cascade
605+
drop_if_exists table("posts"), mode: :cascade
594606
607+
## Options
608+
609+
* `:mode` - when set to `:cascade`, automatically drop objects that depend
610+
- on the index, and in turn all objects that depend on those objects
611+
- on the table
612+
Default is `:restrict`
595613
"""
596-
def drop_if_exists(%{} = index_or_table) do
597-
Runner.execute {:drop_if_exists, __prefix__(index_or_table)}
614+
def drop_if_exists(%{} = index_or_table, opts \\ []) when is_list(opts) do
615+
Runner.execute {:drop_if_exists, __prefix__(index_or_table), Keyword.get(opts, :mode)}
616+
598617
index_or_table
599618
end
600619

lib/ecto/migration/runner.ex

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -217,18 +217,18 @@ defmodule Ecto.Migration.Runner do
217217
end
218218

219219
defp reverse({:create, %Index{} = index}),
220-
do: {:drop, index}
220+
do: {:drop, index, nil}
221221
defp reverse({:create_if_not_exists, %Index{} = index}),
222-
do: {:drop_if_exists, index}
223-
defp reverse({:drop, %Index{} = index}),
222+
do: {:drop_if_exists, index, nil}
223+
defp reverse({:drop, %Index{} = index, _}),
224224
do: {:create, index}
225-
defp reverse({:drop_if_exists, %Index{} = index}),
225+
defp reverse({:drop_if_exists, %Index{} = index, _}),
226226
do: {:create_if_not_exists, index}
227227

228228
defp reverse({:create, %Table{} = table, _columns}),
229-
do: {:drop, table}
229+
do: {:drop, table, nil}
230230
defp reverse({:create_if_not_exists, %Table{} = table, _columns}),
231-
do: {:drop_if_exists, table}
231+
do: {:drop_if_exists, table, nil}
232232
defp reverse({:rename, %Table{} = table_current, %Table{} = table_new}),
233233
do: {:rename, table_new, table_current}
234234
defp reverse({:rename, %Table{} = table, current_column, new_column}),
@@ -242,9 +242,9 @@ defmodule Ecto.Migration.Runner do
242242
# It is not a good idea to reverse constraints because
243243
# we can't guarantee data integrity when applying them back.
244244
defp reverse({:create_if_not_exists, %Constraint{} = constraint}),
245-
do: {:drop_if_exists, constraint}
245+
do: {:drop_if_exists, constraint, nil}
246246
defp reverse({:create, %Constraint{} = constraint}),
247-
do: {:drop, constraint}
247+
do: {:drop, constraint, nil}
248248

249249
defp reverse(_command), do: false
250250

@@ -361,18 +361,26 @@ defmodule Ecto.Migration.Runner do
361361
do: "create table if not exists #{quote_name(table.prefix, table.name)}"
362362
defp command({:alter, %Table{} = table, _}),
363363
do: "alter table #{quote_name(table.prefix, table.name)}"
364-
defp command({:drop, %Table{} = table}),
364+
defp command({:drop, %Table{} = table, :cascade}),
365+
do: command({:drop, table, nil}) <> " cascade"
366+
defp command({:drop, %Table{} = table, _}),
365367
do: "drop table #{quote_name(table.prefix, table.name)}"
366-
defp command({:drop_if_exists, %Table{} = table}),
368+
defp command({:drop_if_exists, %Table{} = table, :cascade}),
369+
do: command({:drop_if_exists, table, nil}) <> " cascade"
370+
defp command({:drop_if_exists, %Table{} = table, _}),
367371
do: "drop table if exists #{quote_name(table.prefix, table.name)}"
368372

369373
defp command({:create, %Index{} = index}),
370374
do: "create index #{quote_name(index.prefix, index.name)}"
371375
defp command({:create_if_not_exists, %Index{} = index}),
372376
do: "create index if not exists #{quote_name(index.prefix, index.name)}"
373-
defp command({:drop, %Index{} = index}),
377+
defp command({:drop, %Index{} = index, :cascade}),
378+
do: command({:drop, index, nil}) <> " cascade"
379+
defp command({:drop, %Index{} = index, _}),
374380
do: "drop index #{quote_name(index.prefix, index.name)}"
375-
defp command({:drop_if_exists, %Index{} = index}),
381+
defp command({:drop_if_exists, %Index{} = index, :cascade}),
382+
do: command({:drop_if_exists, index, nil}) <> " cascade"
383+
defp command({:drop_if_exists, %Index{} = index, _}),
376384
do: "drop index if exists #{quote_name(index.prefix, index.name)}"
377385
defp command({:rename, %Table{} = current_table, %Table{} = new_table}),
378386
do: "rename table #{quote_name(current_table.prefix, current_table.name)} to #{quote_name(new_table.prefix, new_table.name)}"
@@ -387,9 +395,9 @@ defmodule Ecto.Migration.Runner do
387395
do: "create check constraint #{constraint.name} on table #{quote_name(constraint.prefix, constraint.table)}"
388396
defp command({:create, %Constraint{exclude: exclude} = constraint}) when is_binary(exclude),
389397
do: "create exclude constraint #{constraint.name} on table #{quote_name(constraint.prefix, constraint.table)}"
390-
defp command({:drop, %Constraint{} = constraint}),
398+
defp command({:drop, %Constraint{} = constraint, _}),
391399
do: "drop constraint #{constraint.name} from table #{quote_name(constraint.prefix, constraint.table)}"
392-
defp command({:drop_if_exists, %Constraint{} = constraint}),
400+
defp command({:drop_if_exists, %Constraint{} = constraint, _}),
393401
do: "drop constraint if exists #{constraint.name} from table #{quote_name(constraint.prefix, constraint.table)}"
394402

395403
defp quote_name(nil, name), do: quote_name(name)

test/ecto/adapters/myxql_test.exs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,24 +1270,24 @@ defmodule Ecto.Adapters.MyXQLTest do
12701270
end
12711271

12721272
test "drop table" do
1273-
drop = {:drop, table(:posts)}
1273+
drop = {:drop, table(:posts), nil}
12741274
assert execute_ddl(drop) == [~s|DROP TABLE `posts`|]
12751275
end
12761276

12771277
test "drop table with prefixes" do
1278-
drop = {:drop, table(:posts, prefix: :foo)}
1278+
drop = {:drop, table(:posts, prefix: :foo), nil}
12791279
assert execute_ddl(drop) == [~s|DROP TABLE `foo`.`posts`|]
12801280
end
12811281

12821282
test "drop constraint" do
12831283
assert_raise ArgumentError, ~r/MySQL adapter does not support constraints/, fn ->
1284-
execute_ddl({:drop, constraint(:products, "price_must_be_positive", prefix: :foo)})
1284+
execute_ddl({:drop, constraint(:products, "price_must_be_positive", prefix: :foo), nil})
12851285
end
12861286
end
12871287

12881288
test "drop_if_exists constraint" do
12891289
assert_raise ArgumentError, ~r/MySQL adapter does not support constraints/, fn ->
1290-
execute_ddl({:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: :foo)})
1290+
execute_ddl({:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: :foo), nil})
12911291
end
12921292
end
12931293

@@ -1425,12 +1425,12 @@ defmodule Ecto.Adapters.MyXQLTest do
14251425
end
14261426

14271427
test "drop index" do
1428-
drop = {:drop, index(:posts, [:id], name: "posts$main")}
1428+
drop = {:drop, index(:posts, [:id], name: "posts$main"), nil}
14291429
assert execute_ddl(drop) == [~s|DROP INDEX `posts$main` ON `posts`|]
14301430
end
14311431

14321432
test "drop index with prefix" do
1433-
drop = {:drop, index(:posts, [:id], name: "posts$main", prefix: :foo)}
1433+
drop = {:drop, index(:posts, [:id], name: "posts$main", prefix: :foo), nil}
14341434
assert execute_ddl(drop) == [~s|DROP INDEX `posts$main` ON `foo`.`posts`|]
14351435
end
14361436

test/ecto/adapters/postgres_test.exs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,15 +1532,23 @@ defmodule Ecto.Adapters.PostgresTest do
15321532
end
15331533

15341534
test "drop table" do
1535-
drop = {:drop, table(:posts)}
1535+
drop = {:drop, table(:posts), nil}
15361536
assert execute_ddl(drop) == [~s|DROP TABLE "posts"|]
15371537
end
15381538

15391539
test "drop table with prefix" do
1540-
drop = {:drop, table(:posts, prefix: :foo)}
1540+
drop = {:drop, table(:posts, prefix: :foo), nil}
15411541
assert execute_ddl(drop) == [~s|DROP TABLE "foo"."posts"|]
15421542
end
15431543

1544+
test "drop table with cascade" do
1545+
drop = {:drop, table(:posts), :cascade}
1546+
assert execute_ddl(drop) == [~s|DROP TABLE "posts" CASCADE|]
1547+
1548+
drop = {:drop, table(:posts, prefix: :foo), :cascade}
1549+
assert execute_ddl(drop) == [~s|DROP TABLE "foo"."posts" CASCADE|]
1550+
end
1551+
15441552
test "alter table" do
15451553
alter = {:alter, table(:posts),
15461554
[{:add, :title, :string, [default: "Untitled", size: 100, null: false]},
@@ -1728,21 +1736,29 @@ defmodule Ecto.Adapters.PostgresTest do
17281736
end
17291737

17301738
test "drop index" do
1731-
drop = {:drop, index(:posts, [:id], name: "posts$main")}
1739+
drop = {:drop, index(:posts, [:id], name: "posts$main"), nil}
17321740
assert execute_ddl(drop) == [~s|DROP INDEX "posts$main"|]
17331741
end
17341742

17351743
test "drop index with prefix" do
1736-
drop = {:drop, index(:posts, [:id], name: "posts$main", prefix: :foo)}
1744+
drop = {:drop, index(:posts, [:id], name: "posts$main", prefix: :foo), nil}
17371745
assert execute_ddl(drop) == [~s|DROP INDEX "foo"."posts$main"|]
17381746
end
17391747

17401748
test "drop index concurrently" do
17411749
index = index(:posts, [:id], name: "posts$main")
1742-
drop = {:drop, %{index | concurrently: true}}
1750+
drop = {:drop, %{index | concurrently: true}, nil}
17431751
assert execute_ddl(drop) == [~s|DROP INDEX CONCURRENTLY "posts$main"|]
17441752
end
17451753

1754+
test "drop index with cascade" do
1755+
drop = {:drop, index(:posts, [:id], name: "posts$main"), :cascade}
1756+
assert execute_ddl(drop) == [~s|DROP INDEX "posts$main" CASCADE|]
1757+
1758+
drop = {:drop, index(:posts, [:id], name: "posts$main", prefix: :foo), :cascade}
1759+
assert execute_ddl(drop) == [~s|DROP INDEX "foo"."posts$main" CASCADE|]
1760+
end
1761+
17461762
test "create check constraint" do
17471763
create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0")}
17481764
assert execute_ddl(create) ==
@@ -1773,21 +1789,21 @@ defmodule Ecto.Adapters.PostgresTest do
17731789
end
17741790

17751791
test "drop constraint" do
1776-
drop = {:drop, constraint(:products, "price_must_be_positive")}
1792+
drop = {:drop, constraint(:products, "price_must_be_positive"), nil}
17771793
assert execute_ddl(drop) ==
17781794
[~s|ALTER TABLE "products" DROP CONSTRAINT "price_must_be_positive"|]
17791795

1780-
drop = {:drop, constraint(:products, "price_must_be_positive", prefix: "foo")}
1796+
drop = {:drop, constraint(:products, "price_must_be_positive", prefix: "foo"), nil}
17811797
assert execute_ddl(drop) ==
17821798
[~s|ALTER TABLE "foo"."products" DROP CONSTRAINT "price_must_be_positive"|]
17831799
end
17841800

17851801
test "drop_if_exists constraint" do
1786-
drop = {:drop_if_exists, constraint(:products, "price_must_be_positive")}
1802+
drop = {:drop_if_exists, constraint(:products, "price_must_be_positive"), nil}
17871803
assert execute_ddl(drop) ==
17881804
[~s|ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "price_must_be_positive"|]
17891805

1790-
drop = {:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: "foo")}
1806+
drop = {:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: "foo"), nil}
17911807
assert execute_ddl(drop) ==
17921808
[~s|ALTER TABLE "foo"."products" DROP CONSTRAINT IF EXISTS "price_must_be_positive"|]
17931809
end

0 commit comments

Comments
 (0)