diff --git a/integration_test/myxql/constraints_test.exs b/integration_test/myxql/constraints_test.exs index ef48a20a..dd38efb7 100644 --- a/integration_test/myxql/constraints_test.exs +++ b/integration_test/myxql/constraints_test.exs @@ -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 diff --git a/lib/ecto/adapters/myxql/connection.ex b/lib/ecto/adapters/myxql/connection.ex index 26f0b944..4f1fb57c 100644 --- a/lib/ecto/adapters/myxql/connection.ex +++ b/lib/ecto/adapters/myxql/connection.ex @@ -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") @@ -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") @@ -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" @@ -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 @@ -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 diff --git a/test/ecto/adapters/myxql_test.exs b/test/ecto/adapters/myxql_test.exs index 4baac56d..89da3fb4 100644 --- a/test/ecto/adapters/myxql_test.exs +++ b/test/ecto/adapters/myxql_test.exs @@ -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"] @@ -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), @@ -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")} @@ -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)}