diff --git a/lib/ecto/adapters/myxql/connection.ex b/lib/ecto/adapters/myxql/connection.ex index 5165c464..991d6782 100644 --- a/lib/ecto/adapters/myxql/connection.ex +++ b/lib/ecto/adapters/myxql/connection.ex @@ -854,6 +854,10 @@ if Code.ensure_loaded?(MyXQL) do intersperse_map(columns, ", ", &column_change(table, &1)) end + defp column_change(_table, {_command, _name, %Reference{validate: false}, _opts}) do + error!(nil, "validate: false on references is not supported in MyXQL") + end + defp column_change(table, {:add, name, %Reference{} = ref, opts}) do ["ADD ", quote_name(name), ?\s, reference_column_type(ref.type, opts), column_options(opts), constraint_expr(ref, table, name)] diff --git a/lib/ecto/adapters/postgres/connection.ex b/lib/ecto/adapters/postgres/connection.ex index 187612ae..327e224f 100644 --- a/lib/ecto/adapters/postgres/connection.ex +++ b/lib/ecto/adapters/postgres/connection.ex @@ -1121,13 +1121,13 @@ if Code.ensure_loaded?(Postgrex) do defp reference_expr(%Reference{} = ref, table, name), do: [" CONSTRAINT ", reference_name(ref, table, name), " REFERENCES ", quote_table(ref.prefix || table.prefix, ref.table), ?(, quote_name(ref.column), ?), - reference_on_delete(ref.on_delete), reference_on_update(ref.on_update)] + reference_on_delete(ref.on_delete), reference_on_update(ref.on_update), validate(ref.validate)] defp constraint_expr(%Reference{} = ref, table, name), do: [", ADD CONSTRAINT ", reference_name(ref, table, name), ?\s, "FOREIGN KEY (", quote_name(name), ") REFERENCES ", quote_table(ref.prefix || table.prefix, ref.table), ?(, quote_name(ref.column), ?), - reference_on_delete(ref.on_delete), reference_on_update(ref.on_update)] + reference_on_delete(ref.on_delete), reference_on_update(ref.on_update), validate(ref.validate)] defp drop_constraint_expr(%Reference{} = ref, table, name), do: ["DROP CONSTRAINT ", reference_name(ref, table, name), ", "] @@ -1158,6 +1158,9 @@ if Code.ensure_loaded?(Postgrex) do defp reference_on_update(:restrict), do: " ON UPDATE RESTRICT" defp reference_on_update(_), do: [] + defp validate(false), do: " NOT VALID" + defp validate(_), do: [] + ## Helpers defp get_source(query, sources, ix, source) do diff --git a/lib/ecto/adapters/tds/connection.ex b/lib/ecto/adapters/tds/connection.ex index d407327f..861111ef 100644 --- a/lib/ecto/adapters/tds/connection.ex +++ b/lib/ecto/adapters/tds/connection.ex @@ -1195,6 +1195,10 @@ if Code.ensure_loaded?(Tds) do end end + defp column_change(_statement_prefix, _table, {_command, _name, %Reference{validate: false}, _opts}) do + error!(nil, "validate: false on references is not supported in Tds") + end + defp column_change(statement_prefix, table, {:add, name, %Reference{} = ref, opts}) do [ [ diff --git a/lib/ecto/migration.ex b/lib/ecto/migration.ex index 8547c8a4..6ac9b492 100644 --- a/lib/ecto/migration.ex +++ b/lib/ecto/migration.ex @@ -370,8 +370,8 @@ defmodule Ecto.Migration do To define a reference in a migration, see `Ecto.Migration.references/2`. """ - defstruct name: nil, prefix: nil, table: nil, column: :id, type: :bigserial, on_delete: :nothing, on_update: :nothing - @type t :: %__MODULE__{table: String.t, prefix: atom | nil, column: atom, type: atom, on_delete: atom, on_update: atom} + defstruct name: nil, prefix: nil, table: nil, column: :id, type: :bigserial, on_delete: :nothing, on_update: :nothing, validate: true + @type t :: %__MODULE__{table: String.t, prefix: atom | nil, column: atom, type: atom, on_delete: atom, on_update: atom, validate: boolean} end defmodule Constraint do @@ -1094,6 +1094,9 @@ defmodule Ecto.Migration do `:nothing` (default), `:delete_all`, `:nilify_all`, or `:restrict`. * `:on_update` - What to do if the referenced entry is updated. May be `:nothing` (default), `:update_all`, `:nilify_all`, or `:restrict`. + * `:validate` - Whether or not to validate the foreign key constraint on + creation or not. Only available in PostgreSQL, and should be followed by + a command to validate the foreign key in a following migration if false. """ def references(table, opts \\ []) diff --git a/test/ecto/adapters/myxql_test.exs b/test/ecto/adapters/myxql_test.exs index 4b5cf2c0..32519136 100644 --- a/test/ecto/adapters/myxql_test.exs +++ b/test/ecto/adapters/myxql_test.exs @@ -1283,6 +1283,14 @@ defmodule Ecto.Adapters.MyXQLTest do """ |> remove_newlines] end + test "alter table with invalid reference opts" do + alter = {:alter, table(:posts), [{:add, :author_id, %Reference{table: :author, validate: false}, []}]} + + assert_raise ArgumentError, "validate: false on references is not supported in MyXQL", fn -> + execute_ddl(alter) + end + end + test "create index" do create = {:create, index(:posts, [:category_id, :permalink])} assert execute_ddl(create) == diff --git a/test/ecto/adapters/postgres_test.exs b/test/ecto/adapters/postgres_test.exs index 35e6cb82..f069b88e 100644 --- a/test/ecto/adapters/postgres_test.exs +++ b/test/ecto/adapters/postgres_test.exs @@ -1435,6 +1435,7 @@ defmodule Ecto.Adapters.PostgresTest do alter = {:alter, table(:posts), [{:add, :title, :string, [default: "Untitled", size: 100, null: false]}, {:add, :author_id, %Reference{table: :author}, []}, + {:add, :category_id, %Reference{table: :categories, validate: false}, []}, {:add_if_not_exists, :subtitle, :string, [size: 100, null: false]}, {:add_if_not_exists, :editor_id, %Reference{table: :editor}, []}, {:modify, :price, :numeric, [precision: 8, scale: 2, null: true]}, @@ -1453,6 +1454,7 @@ defmodule Ecto.Adapters.PostgresTest do ALTER TABLE "posts" ADD COLUMN "title" varchar(100) DEFAULT 'Untitled' NOT NULL, ADD COLUMN "author_id" bigint CONSTRAINT "posts_author_id_fkey" REFERENCES "author"("id"), + ADD COLUMN "category_id" bigint CONSTRAINT "posts_category_id_fkey" REFERENCES "categories"("id") NOT VALID, ADD COLUMN IF NOT EXISTS "subtitle" varchar(100) NOT NULL, ADD COLUMN IF NOT EXISTS "editor_id" bigint CONSTRAINT "posts_editor_id_fkey" REFERENCES "editor"("id"), ALTER COLUMN "price" TYPE numeric(8,2), diff --git a/test/ecto/adapters/tds_test.exs b/test/ecto/adapters/tds_test.exs index e75e42c6..99d3b6b5 100644 --- a/test/ecto/adapters/tds_test.exs +++ b/test/ecto/adapters/tds_test.exs @@ -1214,6 +1214,14 @@ defmodule Ecto.Adapters.TdsTest do ] end + test "alter table with invalid reference opts" do + alter = {:alter, table(:posts), [{:add, :author_id, %Reference{table: :author, validate: false}, []}]} + + assert_raise ArgumentError, "validate: false on references is not supported in Tds", fn -> + execute_ddl(alter) + end + end + test "create check constraint" do create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0")} diff --git a/test/ecto/migration_test.exs b/test/ecto/migration_test.exs index 0e2ea251..f80a1a52 100644 --- a/test/ecto/migration_test.exs +++ b/test/ecto/migration_test.exs @@ -100,6 +100,11 @@ defmodule Ecto.MigrationTest do %Reference{table: "posts", column: :id, type: :binary_id} end + test "creates a reference without validating" do + assert references(:posts, validate: false) == + %Reference{table: "posts", column: :id, type: :bigserial, validate: false} + end + test "creates a constraint" do assert constraint(:posts, :price_is_positive, check: "price > 0") == %Constraint{table: "posts", name: :price_is_positive, check: "price > 0"}