Skip to content

Commit

Permalink
add migration support for check constraints to MyXQL adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
petermueller committed Jul 1, 2024
1 parent bd4014b commit b64712c
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 44 deletions.
11 changes: 1 addition & 10 deletions integration_test/myxql/constraints_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,8 @@ defmodule Ecto.Integration.ConstraintsTest do
add :to, :integer
end

execute(&positive_price_up/0, &positive_price_down/0)
end

defp positive_price_up do
# Only valid after MySQL 8.0.19
repo().query!("ALTER TABLE #{@table.name} ADD CONSTRAINT positive_price CHECK (price > 0);", [], [log: :info])
end

defp positive_price_down do
# Only valid after MySQL 8.0.19
repo().query!("ALTER TABLE #{@table.name} DROP CONSTRAINT positive_price;", [], [log: :info])
create constraint(@table.name, :positive_price, check: "price > 0")
end
end

Expand Down
47 changes: 39 additions & 8 deletions lib/ecto/adapters/myxql/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1038,12 +1038,31 @@ if Code.ensure_loaded?(MyXQL) do
def execute_ddl({:create_if_not_exists, %Index{}}),
do: error!(nil, "MySQL adapter does not support create if not exists for index")

def execute_ddl({:create, %Constraint{check: check}}) when is_binary(check),
do: error!(nil, "MySQL adapter does not support check constraints")
def execute_ddl({:create, %Constraint{check: check} = constraint}) when is_binary(check) do
table_name = quote_name(constraint.prefix, constraint.table)
[["ALTER TABLE ", table_name, " ADD ", new_constraint_expr(constraint)]]
end

def execute_ddl({:create, %Constraint{exclude: exclude}}) when is_binary(exclude),
do: error!(nil, "MySQL adapter does not support exclusion constraints")

def execute_ddl({:drop, %Constraint{}, :cascade}),
do: error!(nil, "MySQL does not support `CASCADE` in `DROP CONSTRAINT` commands")

def execute_ddl({:drop, %Constraint{} = constraint, _}) do
[
[
"ALTER TABLE ",
quote_name(constraint.prefix, constraint.table),
" DROP CONSTRAINT ",
quote_name(constraint.name)
]
]
end

def execute_ddl({:drop_if_exists, %Constraint{}, _}),
do: error!(nil, "MySQL adapter does not support `drop_if_exists` for constraints")

def execute_ddl({:drop, %Index{}, :cascade}),
do: error!(nil, "MySQL adapter does not support cascade in drop index")

Expand All @@ -1059,12 +1078,6 @@ if Code.ensure_loaded?(MyXQL) do
]
end

def execute_ddl({:drop, %Constraint{}, _}),
do: error!(nil, "MySQL adapter does not support constraints")

def execute_ddl({:drop_if_exists, %Constraint{}, _}),
do: error!(nil, "MySQL adapter does not support constraints")

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

Expand Down Expand Up @@ -1256,6 +1269,17 @@ if Code.ensure_loaded?(MyXQL) do
defp null_expr(true), do: " NULL"
defp null_expr(_), do: []

defp new_constraint_expr(%Constraint{check: check} = constraint) when is_binary(check) do
[
"CONSTRAINT ",
quote_name(constraint.name),
" CHECK (",
check,
")",
validate(constraint.validate)
]
end

defp default_expr({:ok, nil}),
do: " DEFAULT NULL"

Expand Down Expand Up @@ -1413,6 +1437,9 @@ if Code.ensure_loaded?(MyXQL) do
defp reference_on_update(:restrict), do: " ON UPDATE RESTRICT"
defp reference_on_update(_), do: []

defp validate(false), do: " NOT ENFORCED"
defp validate(_), do: []

## Helpers

defp get_source(query, sources, ix, source) do
Expand All @@ -1435,6 +1462,10 @@ if Code.ensure_loaded?(MyXQL) do

defp maybe_add_column_names(_, name), do: name

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

defp quote_name(prefix, name), do: [quote_name(prefix), ?., quote_name(name)]

defp quote_name(name) when is_atom(name) do
quote_name(Atom.to_string(name))
end
Expand Down
86 changes: 60 additions & 26 deletions test/ecto/adapters/myxql_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1621,7 +1621,8 @@ defmodule Ecto.Adapters.MyXQLTest do

# DDL

import Ecto.Migration, only: [table: 1, table: 2, index: 2, index: 3, constraint: 3]
import Ecto.Migration,
only: [table: 1, table: 2, index: 2, index: 3, constraint: 2, constraint: 3]

test "executing a string during migration" do
assert execute_ddl("example") == ["example"]
Expand Down Expand Up @@ -1963,23 +1964,6 @@ defmodule Ecto.Adapters.MyXQLTest do
assert execute_ddl(drop) == [~s|DROP TABLE `foo`.`posts`|]
end

test "drop constraint" do
assert_raise ArgumentError, ~r/MySQL adapter does not support constraints/, fn ->
execute_ddl(
{:drop, constraint(:products, "price_must_be_positive", prefix: "foo"), :restrict}
)
end
end

test "drop_if_exists constraint" do
assert_raise ArgumentError, ~r/MySQL adapter does not support constraints/, fn ->
execute_ddl(
{:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: "foo"),
:restrict}
)
end
end

test "alter table" do
alter =
{:alter, table(:posts),
Expand Down Expand Up @@ -2152,15 +2136,34 @@ defmodule Ecto.Adapters.MyXQLTest do
end

test "create constraints" do
assert_raise ArgumentError, "MySQL adapter does not support check constraints", fn ->
create = {:create, constraint(:products, "foo", check: "price")}
assert execute_ddl(create)
end
create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0")}

assert_raise ArgumentError, "MySQL adapter does not support check constraints", fn ->
create = {:create, constraint(:products, "foo", check: "price", validate: false)}
assert execute_ddl(create)
end
assert execute_ddl(create) ==
[
~s|ALTER TABLE `products` ADD CONSTRAINT `price_must_be_positive` CHECK (price > 0)|
]

create =
{:create,
constraint(:products, "price_must_be_positive", check: "price > 0", prefix: "foo")}

assert execute_ddl(create) ==
[
~s|ALTER TABLE `foo`.`products` ADD CONSTRAINT `price_must_be_positive` CHECK (price > 0)|
]

create =
{:create,
constraint(:products, "price_must_be_positive",
check: "price > 0",
prefix: "foo",
validate: false
)}

assert execute_ddl(create) ==
[
~s|ALTER TABLE `foo`.`products` ADD CONSTRAINT `price_must_be_positive` CHECK (price > 0) NOT ENFORCED|
]

assert_raise ArgumentError, "MySQL adapter does not support exclusion constraints", fn ->
create = {:create, constraint(:products, "bar", exclude: "price")}
Expand All @@ -2173,6 +2176,37 @@ defmodule Ecto.Adapters.MyXQLTest do
end
end

test "drop constraint" do
drop = {:drop, constraint(:products, "price_must_be_positive"), :restrict}

assert execute_ddl(drop) ==
[~s|ALTER TABLE `products` DROP CONSTRAINT `price_must_be_positive`|]

drop = {:drop, constraint(:products, "price_must_be_positive", prefix: "foo"), :restrict}

assert execute_ddl(drop) ==
[~s|ALTER TABLE `foo`.`products` DROP CONSTRAINT `price_must_be_positive`|]

drop_cascade = {:drop, constraint(:products, "price_must_be_positive"), :cascade}

assert_raise ArgumentError,
~r/MySQL does not support `CASCADE` in `DROP CONSTRAINT` commands/,
fn ->
execute_ddl(drop_cascade)
end
end

test "drop_if_exists constraint" do
assert_raise ArgumentError,
~r/MySQL adapter does not support `drop_if_exists` for constraints/,
fn ->
execute_ddl(
{:drop_if_exists,
constraint(:products, "price_must_be_positive", prefix: "foo"), :restrict}
)
end
end

test "create an index using a different type" do
create = {:create, index(:posts, [:permalink], using: :hash)}

Expand Down

0 comments on commit b64712c

Please sign in to comment.