Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion integration_test/myxql/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,11 @@ excludes = [
:map_boolean_in_expression,
# MySQL doesn't support indexed parameters
:placeholders,
# MySQL doesn't support specifying columns for ON DELETE SET NULL
# MySQL doesn't support ON DELETE SET DEFAULT
:on_delete_default_all,
# MySQL doesn't support specifying columns for ON DELETE SET NULL or ON DELETE SET DEFAULT
:on_delete_nilify_column_list,
:on_delete_default_column_list,
# MySQL doesnt' support anything except a single column in DISTINCT
:multicolumn_distinct,
# uncertain whether we can support this. needs more exploring
Expand Down
2 changes: 1 addition & 1 deletion integration_test/pg/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ excludes = [:selected_as_with_having, :selected_as_with_order_by_expression]
excludes_above_9_5 = [:without_conflict_target]
excludes_below_9_6 = [:add_column_if_not_exists, :no_error_on_conditional_column_migration]
excludes_below_12_0 = [:plan_cache_mode]
excludes_below_15_0 = [:on_delete_nilify_column_list]
excludes_below_15_0 = [:on_delete_nilify_column_list, :on_delete_default_column_list]

exclude_list = excludes ++ excludes_above_9_5

Expand Down
89 changes: 89 additions & 0 deletions integration_test/sql/migration.exs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,64 @@ defmodule Ecto.Integration.MigrationTest do
end
end

defmodule OnDeleteDefaultAllMigration do
use Ecto.Migration

def up do
create table(:parent, primary_key: [type: :bigint]) do
add :col1, :integer
add :col2, :integer
end

create unique_index(:parent, [:id, :col1, :col2])

create table(:ref) do
add :col1, :integer, default: 2
add :col2, :integer, default: 3

add :parent_id,
references(:parent,
with: [col1: :col1, col2: :col2],
on_delete: :default_all
), default: 1
end
end

def down do
drop table(:ref)
drop table(:parent)
end
end

defmodule OnDeleteDefaultColumnsMigration do
use Ecto.Migration

def up do
create table(:parent, primary_key: [type: :bigint]) do
add :col1, :integer
add :col2, :integer
end

create unique_index(:parent, [:id, :col1, :col2])

create table(:ref) do
add :col1, :integer, default: 2
add :col2, :integer, default: 3

add :parent_id,
references(:parent,
with: [col1: :col1, col2: :col2],
on_delete: {:default, [:parent_id, :col2]}
), default: 1
end
end

def down do
drop table(:ref)
drop table(:parent)
end
end

defmodule CompositeForeignKeyMigration do
use Ecto.Migration

Expand Down Expand Up @@ -683,4 +741,35 @@ defmodule Ecto.Integration.MigrationTest do

:ok = down(PoolRepo, num, OnDeleteNilifyColumnsMigration, log: false)
end

@tag :on_delete_default_all
test "default all on_delete constraint", %{migration_number: num} do
assert :ok == up(PoolRepo, num, OnDeleteDefaultAllMigration, log: false)

PoolRepo.insert_all("parent", [%{id: 1, col1: 2, col2: 3}])
{id, col1, col2} = {Enum.random(10..1000), Enum.random(10..1000), Enum.random(10..1000)}

PoolRepo.insert_all("parent", [%{id: id, col1: col1, col2: col2}])
PoolRepo.insert_all("ref", [%{parent_id: id, col1: col1, col2: col2}])
PoolRepo.delete_all(from p in "parent", where: p.id == ^id)
assert [{1, 2, 3}] == PoolRepo.all from r in "ref", select: {r.parent_id, r.col1, r.col2}

:ok = down(PoolRepo, num, OnDeleteDefaultAllMigration, log: false)
end

@tag :on_delete_default_column_list
test "default list of columns on_delete constraint", %{migration_number: num} do
assert :ok == up(PoolRepo, num, OnDeleteDefaultColumnsMigration, log: false)

PoolRepo.insert_all("parent", [%{id: 1, col1: 20, col2: 3}])

{id, col2} = {Enum.random(10..1000), Enum.random(10..1000)}

PoolRepo.insert_all("parent", [%{id: id, col1: 20, col2: col2}])
PoolRepo.insert_all("ref", [%{parent_id: id, col1: 20, col2: col2}])
PoolRepo.delete_all(from p in "parent", where: p.id == ^id)
assert [{1, 20, 3}] == PoolRepo.all from r in "ref", select: {r.parent_id, r.col1, r.col2}

:ok = down(PoolRepo, num, OnDeleteDefaultColumnsMigration, log: false)
end
end
3 changes: 2 additions & 1 deletion integration_test/tds/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ ExUnit.start(
:selected_as_with_having,
# MSSQL can't reference aliased columns in ORDER BY expressions
:selected_as_with_order_by_expression,
# MSSQL doesn't support specifying columns for ON DELETE SET NULL
# MSSQL doesn't support specifying columns for ON DELETE SET NULL or ON DELETE SET DEFAULT
:on_delete_nilify_column_list,
:on_delete_default_column_list,
# MSSQL doesnt' support anything except a single column in DISTINCT
:multicolumn_distinct,
# MSSQL doesnt' support subqueries in group by or in distinct
Expand Down
14 changes: 14 additions & 0 deletions lib/ecto/adapters/myxql/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,20 @@ if Code.ensure_loaded?(MyXQL) do
)
end

defp reference_on_delete(:default_all) do
error!(
nil,
"MySQL adapter does not support the `:default_all` action for `:on_delete`"
)
end

defp reference_on_delete({:default, _columns}) do
error!(
nil,
"MySQL adapter does not support the `{:default, columns}` action for `:on_delete`"
)
end

defp reference_on_delete(:delete_all), do: " ON DELETE CASCADE"
defp reference_on_delete(:restrict), do: " ON DELETE RESTRICT"
defp reference_on_delete(_), do: []
Expand Down
5 changes: 5 additions & 0 deletions lib/ecto/adapters/postgres/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,11 @@ if Code.ensure_loaded?(Postgrex) do
defp reference_on_delete({:nilify, columns}),
do: [" ON DELETE SET NULL (", quote_names(columns), ")"]

defp reference_on_delete(:default_all), do: " ON DELETE SET DEFAULT"

defp reference_on_delete({:default, columns}),
do: [" ON DELETE SET DEFAULT (", quote_names(columns), ")"]

defp reference_on_delete(:delete_all), do: " ON DELETE CASCADE"
defp reference_on_delete(:restrict), do: " ON DELETE RESTRICT"
defp reference_on_delete(_), do: []
Expand Down
9 changes: 9 additions & 0 deletions lib/ecto/adapters/tds/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,15 @@ if Code.ensure_loaded?(Tds) do
error!(nil, "Tds adapter does not support the `{:nilify, columns}` action for `:on_delete`")
end

defp reference_on_delete(:default_all), do: " ON DELETE SET DEFAULT"

defp reference_on_delete({:default, _columns}) do
error!(
nil,
"Tds adapter does not support the `{:default, columns}` action for `:on_delete`"
)
end

defp reference_on_delete(:delete_all), do: " ON DELETE CASCADE"
defp reference_on_delete(:nothing), do: " ON DELETE NO ACTION"
defp reference_on_delete(_), do: []
Expand Down
11 changes: 6 additions & 5 deletions lib/ecto/migration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1515,8 +1515,8 @@ defmodule Ecto.Migration do
the example above), or `nil`.
* `:type` - The foreign key type, which defaults to `:bigserial`.
* `:on_delete` - What to do if the referenced entry is deleted. May be
`:nothing` (default), `:delete_all`, `:nilify_all`, `{:nilify, columns}`,
or `:restrict`. `{:nilify, columns}` expects a list of atoms for `columns`
`:nothing` (default), `:delete_all`, `:nilify_all`, `{:nilify, columns}`, `:default_all`, `{:default, columns}`
or `:restrict`. `{:nilify, columns}` and `{:default, columns}` expect a list of atoms for `columns`
and is not supported by all databases.
* `:on_update` - What to do if the referenced entry is updated. May be
`:nothing` (default), `:update_all`, `:nilify_all`, or `:restrict`.
Expand Down Expand Up @@ -1561,13 +1561,14 @@ defmodule Ecto.Migration do
end

defp check_on_delete!(on_delete)
when on_delete in [:nothing, :delete_all, :nilify_all, :restrict],
when on_delete in [:nothing, :delete_all, :nilify_all, :default_all, :restrict],
do: :ok

defp check_on_delete!({:nilify, columns}) when is_list(columns) do
defp check_on_delete!({option, columns})
when option in [:nilify, :default] and is_list(columns) do
unless Enum.all?(columns, &is_atom/1) do
raise ArgumentError,
"expected `columns` in `{:nilify, columns}` to be a list of atoms, got: #{inspect(columns)}"
"expected `columns` in `{#{inspect(option)}, columns}` to be a list of atoms, got: #{inspect(columns)}"
end

:ok
Expand Down
19 changes: 19 additions & 0 deletions test/ecto/adapters/myxql_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,25 @@ defmodule Ecto.Adapters.MyXQLTest do

msg = "MySQL adapter does not support the `{:nilify, columns}` action for `:on_delete`"
assert_raise ArgumentError, msg, fn -> execute_ddl(create) end

create =
{:create, table(:posts),
[
{:add, :category_1, %Reference{table: :categories, on_delete: :default_all}, []}
]}

msg = "MySQL adapter does not support the `:default_all` action for `:on_delete`"
assert_raise ArgumentError, msg, fn -> execute_ddl(create) end

create =
{:create, table(:posts),
[
{:add, :category_1, %Reference{table: :categories, on_delete: {:default, [:category_1]}},
[]}
]}

msg = "MySQL adapter does not support the `{:default, columns}` action for `:on_delete`"
assert_raise ArgumentError, msg, fn -> execute_ddl(create) end
end

test "create table with options" do
Expand Down
9 changes: 9 additions & 0 deletions test/ecto/adapters/postgres_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,13 @@ defmodule Ecto.Adapters.PostgresTest do
table: :categories,
with: [here: :there, here2: :there2],
on_delete: {:nilify, [:here, :here2]}
}, []},
{:add, :category_15, %Reference{table: :categories, on_delete: :default_all}, []},
{:add, :category_16,
%Reference{
table: :categories,
with: [here: :there, here2: :there2],
on_delete: {:default, [:here, :here2]}
}, []}
]}

Expand All @@ -2252,6 +2259,8 @@ defmodule Ecto.Adapters.PostgresTest do
"category_12" bigint, CONSTRAINT "posts_category_12_fkey" FOREIGN KEY ("category_12","here") REFERENCES "categories"("id","there"),
"category_13" bigint, CONSTRAINT "posts_category_13_fkey" FOREIGN KEY ("category_13","here") REFERENCES "categories"("id","there") MATCH FULL ON UPDATE RESTRICT,
"category_14" bigint, CONSTRAINT "posts_category_14_fkey" FOREIGN KEY ("category_14","here","here2") REFERENCES "categories"("id","there","there2") ON DELETE SET NULL ("here","here2"),
"category_15" bigint, CONSTRAINT "posts_category_15_fkey" FOREIGN KEY ("category_15") REFERENCES "categories"("id") ON DELETE SET DEFAULT,
"category_16" bigint, CONSTRAINT "posts_category_16_fkey" FOREIGN KEY ("category_16","here","here2") REFERENCES "categories"("id","there","there2") ON DELETE SET DEFAULT ("here","here2"),
PRIMARY KEY ("id"))
"""
|> remove_newlines
Expand Down
10 changes: 10 additions & 0 deletions test/ecto/adapters/tds_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,16 @@ defmodule Ecto.Adapters.TdsTest do

msg = "Tds adapter does not support the `{:nilify, columns}` action for `:on_delete`"
assert_raise ArgumentError, msg, fn -> execute_ddl(create) end

create =
{:create, table(:posts),
[
{:add, :category_1, %Reference{table: :categories, on_delete: {:default, [:category_1]}},
[]}
]}

msg = "Tds adapter does not support the `{:default, columns}` action for `:on_delete`"
assert_raise ArgumentError, msg, fn -> execute_ddl(create) end
end

test "create table with options" do
Expand Down
Loading