From 7a213acf965b0a26af05ae9710772b51a23fe2ce Mon Sep 17 00:00:00 2001 From: Greg Rychlewski Date: Thu, 5 Oct 2023 03:40:37 -0400 Subject: [PATCH] format test/**/*.exs --- test/ecto/adapters/myxql_test.exs | 1470 +++++++++++------- test/ecto/adapters/postgres_test.exs | 2064 +++++++++++++++++--------- test/ecto/adapters/tds_test.exs | 210 ++- test/ecto/migration_test.exs | 326 ++-- test/ecto/migrator_repo_test.exs | 14 +- test/ecto/migrator_test.exs | 445 +++--- test/ecto/tenant_migrator_test.exs | 21 +- test/ecto/type_test.exs | 18 +- 8 files changed, 2914 insertions(+), 1654 deletions(-) diff --git a/test/ecto/adapters/myxql_test.exs b/test/ecto/adapters/myxql_test.exs index 8ac1cccb..a9f9f10b 100644 --- a/test/ecto/adapters/myxql_test.exs +++ b/test/ecto/adapters/myxql_test.exs @@ -19,6 +19,7 @@ defmodule Ecto.Adapters.MyXQLTest do has_many :comments, Ecto.Adapters.MyXQLTest.Schema2, references: :x, foreign_key: :z + has_one :permalink, Ecto.Adapters.MyXQLTest.Schema3, references: :y, foreign_key: :id @@ -44,25 +45,27 @@ defmodule Ecto.Adapters.MyXQLTest do end defp plan(query, operation \\ :all) do - {query, _cast_params, _dump_params} = Ecto.Adapter.Queryable.plan_query(operation, Ecto.Adapters.MyXQL, query) + {query, _cast_params, _dump_params} = + Ecto.Adapter.Queryable.plan_query(operation, Ecto.Adapters.MyXQL, query) + query end - defp all(query), do: query |> SQL.all |> IO.iodata_to_binary() - defp update_all(query), do: query |> SQL.update_all |> IO.iodata_to_binary() - defp delete_all(query), do: query |> SQL.delete_all |> IO.iodata_to_binary() - defp execute_ddl(query), do: query |> SQL.execute_ddl |> Enum.map(&IO.iodata_to_binary/1) + defp all(query), do: query |> SQL.all() |> IO.iodata_to_binary() + defp update_all(query), do: query |> SQL.update_all() |> IO.iodata_to_binary() + defp delete_all(query), do: query |> SQL.delete_all() |> IO.iodata_to_binary() + defp execute_ddl(query), do: query |> SQL.execute_ddl() |> Enum.map(&IO.iodata_to_binary/1) defp insert(prefx, table, header, rows, on_conflict, returning) do - IO.iodata_to_binary SQL.insert(prefx, table, header, rows, on_conflict, returning, []) + IO.iodata_to_binary(SQL.insert(prefx, table, header, rows, on_conflict, returning, [])) end defp update(prefx, table, fields, filter, returning) do - IO.iodata_to_binary SQL.update(prefx, table, fields, filter, returning) + IO.iodata_to_binary(SQL.update(prefx, table, fields, filter, returning)) end defp delete(prefx, table, filter, returning) do - IO.iodata_to_binary SQL.delete(prefx, table, filter, returning) + IO.iodata_to_binary(SQL.delete(prefx, table, filter, returning)) end test "from" do @@ -71,7 +74,9 @@ defmodule Ecto.Adapters.MyXQLTest do end test "from with hints" do - query = Schema |> from(hints: ["USE INDEX FOO", "USE INDEX BAR"]) |> select([r], r.x) |> plan() + query = + Schema |> from(hints: ["USE INDEX FOO", "USE INDEX BAR"]) |> select([r], r.x) |> plan() + assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 USE INDEX FOO USE INDEX BAR} end @@ -88,20 +93,31 @@ defmodule Ecto.Adapters.MyXQLTest do query = "0posts" |> select([:x]) |> plan() assert all(query) == ~s{SELECT t0.`x` FROM `0posts` AS t0} - assert_raise Ecto.QueryError, ~r"MySQL adapter does not support selecting all fields from `posts` without a schema", fn -> - all from(p in "posts", select: p) |> plan() - end + assert_raise Ecto.QueryError, + ~r"MySQL adapter does not support selecting all fields from `posts` without a schema", + fn -> + all(from(p in "posts", select: p) |> plan()) + end end test "from with subquery" do query = subquery("posts" |> select([r], %{x: r.x, y: r.y})) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0.`x` FROM (SELECT sp0.`x` AS `x`, sp0.`y` AS `y` FROM `posts` AS sp0) AS s0} + + assert all(query) == + ~s{SELECT s0.`x` FROM (SELECT sp0.`x` AS `x`, sp0.`y` AS `y` FROM `posts` AS sp0) AS s0} query = subquery("posts" |> select([r], %{x: r.x, z: r.y})) |> select([r], r) |> plan() - assert all(query) == ~s{SELECT s0.`x`, s0.`z` FROM (SELECT sp0.`x` AS `x`, sp0.`y` AS `z` FROM `posts` AS sp0) AS s0} - query = subquery(subquery("posts" |> select([r], %{x: r.x, z: r.y})) |> select([r], r)) |> select([r], r) |> plan() - assert all(query) == ~s{SELECT s0.`x`, s0.`z` FROM (SELECT ss0.`x` AS `x`, ss0.`z` AS `z` FROM (SELECT ssp0.`x` AS `x`, ssp0.`y` AS `z` FROM `posts` AS ssp0) AS ss0) AS s0} + assert all(query) == + ~s{SELECT s0.`x`, s0.`z` FROM (SELECT sp0.`x` AS `x`, sp0.`y` AS `z` FROM `posts` AS sp0) AS s0} + + query = + subquery(subquery("posts" |> select([r], %{x: r.x, z: r.y})) |> select([r], r)) + |> select([r], r) + |> plan() + + assert all(query) == + ~s{SELECT s0.`x`, s0.`z` FROM (SELECT ss0.`x` AS `x`, ss0.`z` AS `z` FROM (SELECT ssp0.`x` AS `x`, ssp0.`y` AS `z` FROM `posts` AS ssp0) AS ss0) AS s0} end test "from with fragment" do @@ -111,9 +127,11 @@ defmodule Ecto.Adapters.MyXQLTest do query = from(fragment("select ? as x", ^"abc"), select: fragment("x")) |> plan() assert all(query) == ~s{SELECT x FROM (select ? as x) AS f0} - assert_raise Ecto.QueryError, ~r"MySQL adapter does not support selecting all fields from fragment", fn -> - all from(f in fragment("select ? as x", ^"abc"), select: f) |> plan() - end + assert_raise Ecto.QueryError, + ~r"MySQL adapter does not support selecting all fields from fragment", + fn -> + all(from(f in fragment("select ? as x", ^"abc"), select: f) |> plan()) + end end test "CTE" do @@ -138,14 +156,14 @@ defmodule Ecto.Adapters.MyXQLTest do |> plan() assert all(query) == - ~s{WITH RECURSIVE `tree` AS } <> - ~s{(SELECT sc0.`id` AS `id`, 1 AS `depth` FROM `categories` AS sc0 WHERE (sc0.`parent_id` IS NULL) } <> - ~s{UNION ALL } <> - ~s{(SELECT sc0.`id`, st1.`depth` + 1 FROM `categories` AS sc0 } <> - ~s{INNER JOIN `tree` AS st1 ON st1.`id` = sc0.`parent_id`)) } <> - ~s{SELECT s0.`x`, t1.`id`, CAST(t1.`depth` AS unsigned) } <> - ~s{FROM `schema` AS s0 } <> - ~s{INNER JOIN `tree` AS t1 ON t1.`id` = s0.`category_id`} + ~s{WITH RECURSIVE `tree` AS } <> + ~s{(SELECT sc0.`id` AS `id`, 1 AS `depth` FROM `categories` AS sc0 WHERE (sc0.`parent_id` IS NULL) } <> + ~s{UNION ALL } <> + ~s{(SELECT sc0.`id`, st1.`depth` + 1 FROM `categories` AS sc0 } <> + ~s{INNER JOIN `tree` AS st1 ON st1.`id` = sc0.`parent_id`)) } <> + ~s{SELECT s0.`x`, t1.`id`, CAST(t1.`depth` AS unsigned) } <> + ~s{FROM `schema` AS s0 } <> + ~s{INNER JOIN `tree` AS t1 ON t1.`id` = s0.`category_id`} end @raw_sql_cte """ @@ -177,16 +195,16 @@ defmodule Ecto.Adapters.MyXQLTest do |> plan() assert all(query) == - ~s{WITH `comments_scope` AS (} <> - ~s{SELECT sc0.`entity_id` AS `entity_id`, sc0.`text` AS `text` } <> - ~s{FROM `comments` AS sc0 WHERE (sc0.`deleted_at` IS NULL)) } <> - ~s{SELECT p0.`title`, c1.`text` } <> - ~s{FROM `posts` AS p0 } <> - ~s{INNER JOIN `comments_scope` AS c1 ON c1.`entity_id` = p0.`guid` } <> - ~s{UNION ALL } <> - ~s{(SELECT v0.`title`, c1.`text` } <> - ~s{FROM `videos` AS v0 } <> - ~s{INNER JOIN `comments_scope` AS c1 ON c1.`entity_id` = v0.`guid`)} + ~s{WITH `comments_scope` AS (} <> + ~s{SELECT sc0.`entity_id` AS `entity_id`, sc0.`text` AS `text` } <> + ~s{FROM `comments` AS sc0 WHERE (sc0.`deleted_at` IS NULL)) } <> + ~s{SELECT p0.`title`, c1.`text` } <> + ~s{FROM `posts` AS p0 } <> + ~s{INNER JOIN `comments_scope` AS c1 ON c1.`entity_id` = p0.`guid` } <> + ~s{UNION ALL } <> + ~s{(SELECT v0.`title`, c1.`text` } <> + ~s{FROM `videos` AS v0 } <> + ~s{INNER JOIN `comments_scope` AS c1 ON c1.`entity_id` = v0.`guid`)} end test "fragment CTE" do @@ -199,15 +217,20 @@ defmodule Ecto.Adapters.MyXQLTest do |> plan() assert all(query) == - ~s{WITH RECURSIVE `tree` AS (#{@raw_sql_cte}) } <> - ~s{SELECT s0.`x` } <> - ~s{FROM `schema` AS s0 } <> - ~s{INNER JOIN `tree` AS t1 ON t1.`id` = s0.`category_id`} + ~s{WITH RECURSIVE `tree` AS (#{@raw_sql_cte}) } <> + ~s{SELECT s0.`x` } <> + ~s{FROM `schema` AS s0 } <> + ~s{INNER JOIN `tree` AS t1 ON t1.`id` = s0.`category_id`} end test "CTE update_all" do cte_query = - from(x in Schema, order_by: [asc: :id], limit: 10, lock: "FOR UPDATE SKIP LOCKED", select: %{id: x.id}) + from(x in Schema, + order_by: [asc: :id], + limit: 10, + lock: "FOR UPDATE SKIP LOCKED", + select: %{id: x.id} + ) query = Schema @@ -217,16 +240,21 @@ defmodule Ecto.Adapters.MyXQLTest do |> plan(:update_all) assert update_all(query) == - ~s{WITH `target_rows` AS } <> - ~s{(SELECT ss0.`id` AS `id` FROM `schema` AS ss0 ORDER BY ss0.`id` LIMIT 10 FOR UPDATE SKIP LOCKED) } <> - ~s{UPDATE `schema` AS s0, `target_rows` AS t1 } <> - ~s{SET s0.`x` = 123 } <> - ~s{WHERE (t1.`id` = s0.`id`)} + ~s{WITH `target_rows` AS } <> + ~s{(SELECT ss0.`id` AS `id` FROM `schema` AS ss0 ORDER BY ss0.`id` LIMIT 10 FOR UPDATE SKIP LOCKED) } <> + ~s{UPDATE `schema` AS s0, `target_rows` AS t1 } <> + ~s{SET s0.`x` = 123 } <> + ~s{WHERE (t1.`id` = s0.`id`)} end test "CTE delete_all" do cte_query = - from(x in Schema, order_by: [asc: :id], limit: 10, lock: "FOR UPDATE SKIP LOCKED", select: %{id: x.id}) + from(x in Schema, + order_by: [asc: :id], + limit: 10, + lock: "FOR UPDATE SKIP LOCKED", + select: %{id: x.id} + ) query = Schema @@ -235,11 +263,11 @@ defmodule Ecto.Adapters.MyXQLTest do |> plan(:delete_all) assert delete_all(query) == - ~s{WITH `target_rows` AS } <> - ~s{(SELECT ss0.`id` AS `id` FROM `schema` AS ss0 ORDER BY ss0.`id` LIMIT 10 FOR UPDATE SKIP LOCKED) } <> - ~s{DELETE s0.* } <> - ~s{FROM `schema` AS s0 } <> - ~s{INNER JOIN `target_rows` AS t1 ON t1.`id` = s0.`id`} + ~s{WITH `target_rows` AS } <> + ~s{(SELECT ss0.`id` AS `id` FROM `schema` AS ss0 ORDER BY ss0.`id` LIMIT 10 FOR UPDATE SKIP LOCKED) } <> + ~s{DELETE s0.* } <> + ~s{FROM `schema` AS s0 } <> + ~s{INNER JOIN `target_rows` AS t1 ON t1.`id` = s0.`id`} end test "parent binding subquery and CTE" do @@ -271,29 +299,32 @@ defmodule Ecto.Adapters.MyXQLTest do |> plan() assert all(query) == - ~s{SELECT c0.`id`, s1.`breadcrumbs` FROM `categories` AS c0 } <> - ~s{LEFT OUTER JOIN LATERAL } <> - ~s{(WITH RECURSIVE `tree` AS } <> - ~s{(SELECT ssc0.`id` AS `id`, ssc0.`parent_id` AS `parent_id` FROM `categories` AS ssc0 WHERE (ssc0.`id` = c0.`id`) } <> - ~s{UNION ALL } <> - ~s{(SELECT ssc0.`id`, ssc0.`parent_id` FROM `categories` AS ssc0 } <> - ~s{INNER JOIN `tree` AS sst1 ON sst1.`parent_id` = ssc0.`id`)) } <> - ~s{SELECT GROUP_CONCAT(st0.`id` SEPARATOR ' / ') AS `breadcrumbs` FROM `tree` AS st0) AS s1 ON TRUE} + ~s{SELECT c0.`id`, s1.`breadcrumbs` FROM `categories` AS c0 } <> + ~s{LEFT OUTER JOIN LATERAL } <> + ~s{(WITH RECURSIVE `tree` AS } <> + ~s{(SELECT ssc0.`id` AS `id`, ssc0.`parent_id` AS `parent_id` FROM `categories` AS ssc0 WHERE (ssc0.`id` = c0.`id`) } <> + ~s{UNION ALL } <> + ~s{(SELECT ssc0.`id`, ssc0.`parent_id` FROM `categories` AS ssc0 } <> + ~s{INNER JOIN `tree` AS sst1 ON sst1.`parent_id` = ssc0.`id`)) } <> + ~s{SELECT GROUP_CONCAT(st0.`id` SEPARATOR ' / ') AS `breadcrumbs` FROM `tree` AS st0) AS s1 ON TRUE} end test "parent binding subquery and combination" do right_query = from(c in "right_categories", where: c.id == parent_as(:c).id, select: c.id) left_query = from(c in "left_categories", where: c.id == parent_as(:c).id, select: c.id) union_query = union(left_query, ^right_query) - query = from(c in "categories", as: :c, where: c.id in subquery(union_query), select: c.id) |> plan() + + query = + from(c in "categories", as: :c, where: c.id in subquery(union_query), select: c.id) + |> plan() assert all(query) == - ~s{SELECT c0.`id` FROM `categories` AS c0 } <> - ~s{WHERE (} <> - ~s{c0.`id` IN } <> - ~s{(SELECT sl0.`id` FROM `left_categories` AS sl0 WHERE (sl0.`id` = c0.`id`) } <> - ~s{UNION } <> - ~s{(SELECT sr0.`id` FROM `right_categories` AS sr0 WHERE (sr0.`id` = c0.`id`))))} + ~s{SELECT c0.`id` FROM `categories` AS c0 } <> + ~s{WHERE (} <> + ~s{c0.`id` IN } <> + ~s{(SELECT sl0.`id` FROM `left_categories` AS sl0 WHERE (sl0.`id` = c0.`id`) } <> + ~s{UNION } <> + ~s{(SELECT sr0.`id` FROM `right_categories` AS sr0 WHERE (sr0.`id` = c0.`id`))))} end test "CTE with update statement" do @@ -309,9 +340,9 @@ defmodule Ecto.Adapters.MyXQLTest do |> select([c], %{id: c.id, desc: c.desc}) |> plan() - assert_raise Ecto.QueryError, ~r/MySQL adapter does not support data-modifying CTEs/, fn -> - all(query) - end + assert_raise Ecto.QueryError, ~r/MySQL adapter does not support data-modifying CTEs/, fn -> + all(query) + end end test "select" do @@ -332,9 +363,12 @@ defmodule Ecto.Adapters.MyXQLTest do test "aggregate filters" do query = Schema |> select([r], count(r.x) |> filter(r.x > 10)) |> plan() - assert_raise Ecto.QueryError, ~r/MySQL adapter does not support aggregate filters in query/, fn -> - all(query) - end + + assert_raise Ecto.QueryError, + ~r/MySQL adapter does not support aggregate filters in query/, + fn -> + all(query) + end end test "distinct" do @@ -350,10 +384,14 @@ defmodule Ecto.Adapters.MyXQLTest do query = Schema |> distinct(false) |> select([r], {r.x, r.y}) |> plan() assert all(query) == ~s{SELECT s0.`x`, s0.`y` FROM `schema` AS s0} - assert_raise Ecto.QueryError, ~r"DISTINCT with multiple columns is not supported by MySQL", fn -> - query = Schema |> distinct([r], [r.x, r.y]) |> select([r], {r.x, r.y}) |> plan() - all(query) - end + assert_raise Ecto.QueryError, + ~r"DISTINCT with multiple columns is not supported by MySQL", + fn -> + query = + Schema |> distinct([r], [r.x, r.y]) |> select([r], {r.x, r.y}) |> plan() + + all(query) + end end test "coalesce" do @@ -363,18 +401,31 @@ defmodule Ecto.Adapters.MyXQLTest do test "where" do query = Schema |> where([r], r.x == 42) |> where([r], r.y != 43) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE (s0.`x` = 42) AND (s0.`y` != 43)} + + assert all(query) == + ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE (s0.`x` = 42) AND (s0.`y` != 43)} query = Schema |> where([r], {r.x, r.y} > {1, 2}) |> select([r], r.x) |> plan() assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE ((s0.`x`,s0.`y`) > (1,2))} end test "or_where" do - query = Schema |> or_where([r], r.x == 42) |> or_where([r], r.y != 43) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE (s0.`x` = 42) OR (s0.`y` != 43)} + query = + Schema |> or_where([r], r.x == 42) |> or_where([r], r.y != 43) |> select([r], r.x) |> plan() + + assert all(query) == + ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE (s0.`x` = 42) OR (s0.`y` != 43)} + + query = + Schema + |> or_where([r], r.x == 42) + |> or_where([r], r.y != 43) + |> where([r], r.z == 44) + |> select([r], r.x) + |> plan() - query = Schema |> or_where([r], r.x == 42) |> or_where([r], r.y != 43) |> where([r], r.z == 44) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE ((s0.`x` = 42) OR (s0.`y` != 43)) AND (s0.`z` = 44)} + assert all(query) == + ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE ((s0.`x` = 42) OR (s0.`y` != 43)) AND (s0.`z` = 44)} end test "order by" do @@ -384,7 +435,7 @@ defmodule Ecto.Adapters.MyXQLTest do query = Schema |> order_by([r], [r.x, r.y]) |> select([r], r.x) |> plan() assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 ORDER BY s0.`x`, s0.`y`} - query = Schema |> order_by([r], [asc: r.x, desc: r.y]) |> select([r], r.x) |> plan() + query = Schema |> order_by([r], asc: r.x, desc: r.y) |> select([r], r.x) |> plan() assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 ORDER BY s0.`x`, s0.`y` DESC} query = Schema |> order_by([r], []) |> select([r], r.x) |> plan() @@ -398,7 +449,9 @@ defmodule Ecto.Adapters.MyXQLTest do end test "union and union all" do - base_query = Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + base_query = + Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + union_query1 = Schema |> select([r], r.y) |> order_by([r], r.y) |> offset(20) |> limit(40) union_query2 = Schema |> select([r], r.z) |> order_by([r], r.z) |> offset(30) |> limit(60) @@ -420,7 +473,9 @@ defmodule Ecto.Adapters.MyXQLTest do end test "except and except all" do - base_query = Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + base_query = + Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + except_query1 = Schema |> select([r], r.y) |> order_by([r], r.y) |> offset(20) |> limit(40) except_query2 = Schema |> select([r], r.z) |> order_by([r], r.z) |> offset(30) |> limit(60) @@ -442,7 +497,9 @@ defmodule Ecto.Adapters.MyXQLTest do end test "intersect and intersect all" do - base_query = Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + base_query = + Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + intersect_query1 = Schema |> select([r], r.y) |> order_by([r], r.y) |> offset(20) |> limit(40) intersect_query2 = Schema |> select([r], r.z) |> order_by([r], r.z) |> offset(30) |> limit(60) @@ -535,7 +592,12 @@ defmodule Ecto.Adapters.MyXQLTest do end test "order_by and types" do - query = "schema3" |> order_by([e], type(fragment("?", e.binary), ^:decimal)) |> select(true) |> plan() + query = + "schema3" + |> order_by([e], type(fragment("?", e.binary), ^:decimal)) + |> select(true) + |> plan() + assert all(query) == "SELECT TRUE FROM `schema3` AS s0 ORDER BY s0.`binary` + 0" end @@ -555,7 +617,12 @@ defmodule Ecto.Adapters.MyXQLTest do query = Schema |> select([r], fragment("? COLLATE ?", r.x, literal(^"es_ES"))) |> plan() assert all(query) == ~s{SELECT s0.`x` COLLATE `es_ES` FROM `schema` AS s0} - query = Schema |> select([r], r.x) |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) |> plan() + query = + Schema + |> select([r], r.x) + |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) + |> plan() + assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WHERE (s0.`x` in (?,?,?,?,?))} value = 13 @@ -563,6 +630,7 @@ defmodule Ecto.Adapters.MyXQLTest do assert all(query) == ~s{SELECT lcase(s0.`x`, ?) FROM `schema` AS s0} query = Schema |> select([], fragment(title: 2)) |> plan() + assert_raise Ecto.QueryError, fn -> all(query) end @@ -589,21 +657,39 @@ defmodule Ecto.Adapters.MyXQLTest do query = "schema" |> select([s], selected_as(s.x, :integer)) |> plan() assert all(query) == ~s{SELECT s0.`x` AS `integer` FROM `schema` AS s0} - query = "schema" |> select([s], s.x |> coalesce(0) |> sum() |> selected_as(:integer)) |> plan() + query = + "schema" |> select([s], s.x |> coalesce(0) |> sum() |> selected_as(:integer)) |> plan() + assert all(query) == ~s{SELECT sum(coalesce(s0.`x`, 0)) AS `integer` FROM `schema` AS s0} end test "group_by can reference the alias of a selected value with selected_as/1" do - query = "schema" |> select([s], selected_as(s.x, :integer)) |> group_by(selected_as(:integer)) |> plan() + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> group_by(selected_as(:integer)) + |> plan() + assert all(query) == ~s{SELECT s0.`x` AS `integer` FROM `schema` AS s0 GROUP BY `integer`} end test "order_by can reference the alias of a selected value with selected_as/1" do - query = "schema" |> select([s], selected_as(s.x, :integer)) |> order_by(selected_as(:integer)) |> plan() + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> order_by(selected_as(:integer)) + |> plan() + assert all(query) == ~s{SELECT s0.`x` AS `integer` FROM `schema` AS s0 ORDER BY `integer`} - query = "schema" |> select([s], selected_as(s.x, :integer)) |> order_by([desc: selected_as(:integer)]) |> plan() - assert all(query) == ~s{SELECT s0.`x` AS `integer` FROM `schema` AS s0 ORDER BY `integer` DESC} + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> order_by(desc: selected_as(:integer)) + |> plan() + + assert all(query) == + ~s{SELECT s0.`x` AS `integer` FROM `schema` AS s0 ORDER BY `integer` DESC} end test "having can reference the alias of a selected value with selected_as/1" do @@ -614,11 +700,14 @@ defmodule Ecto.Adapters.MyXQLTest do |> having(selected_as(:integer) > 0) |> plan() - assert all(query) == ~s{SELECT s0.`x` AS `integer` FROM `schema` AS s0 GROUP BY `integer` HAVING (`integer` > 0)} + assert all(query) == + ~s{SELECT s0.`x` AS `integer` FROM `schema` AS s0 GROUP BY `integer` HAVING (`integer` > 0)} end test "tagged type" do - query = Schema |> select([], type(^"601d74e4-a8d3-4b6e-8365-eddb4c893327", Ecto.UUID)) |> plan() + query = + Schema |> select([], type(^"601d74e4-a8d3-4b6e-8365-eddb4c893327", Ecto.UUID)) |> plan() + assert all(query) == ~s{SELECT CAST(? AS binary(16)) FROM `schema` AS s0} end @@ -653,7 +742,7 @@ defmodule Ecto.Adapters.MyXQLTest do test "nested expressions" do z = 123 - query = from(r in Schema, []) |> select([r], r.x > 0 and (r.y > ^(-z)) or true) |> plan() + query = from(r in Schema, []) |> select([r], (r.x > 0 and r.y > ^(-z)) or true) |> plan() assert all(query) == ~s{SELECT ((s0.`x` > 0) AND (s0.`y` > ?)) OR TRUE FROM `schema` AS s0} end @@ -661,7 +750,7 @@ defmodule Ecto.Adapters.MyXQLTest do query = Schema |> select([e], 1 in []) |> plan() assert all(query) == ~s{SELECT false FROM `schema` AS s0} - query = Schema |> select([e], 1 in [1,e.x,3]) |> plan() + query = Schema |> select([e], 1 in [1, e.x, 3]) |> plan() assert all(query) == ~s{SELECT 1 IN (1,s0.`x`,3) FROM `schema` AS s0} query = Schema |> select([e], 1 in ^[]) |> plan() @@ -677,37 +766,61 @@ defmodule Ecto.Adapters.MyXQLTest do assert all(query) == ~s{SELECT 1 = ANY(foo) FROM `schema` AS s0} query = Schema |> select([e], e.x == ^0 or e.x in ^[1, 2, 3] or e.x == ^4) |> plan() - assert all(query) == ~s{SELECT ((s0.`x` = ?) OR s0.`x` IN (?,?,?)) OR (s0.`x` = ?) FROM `schema` AS s0} + + assert all(query) == + ~s{SELECT ((s0.`x` = ?) OR s0.`x` IN (?,?,?)) OR (s0.`x` = ?) FROM `schema` AS s0} end test "in subquery" do posts = subquery("posts" |> where(title: ^"hello") |> select([p], p.id)) query = "comments" |> where([c], c.post_id in subquery(posts)) |> select([c], c.x) |> plan() + assert all(query) == - ~s{SELECT c0.`x` FROM `comments` AS c0 } <> - ~s{WHERE (c0.`post_id` IN (SELECT sp0.`id` FROM `posts` AS sp0 WHERE (sp0.`title` = ?)))} + ~s{SELECT c0.`x` FROM `comments` AS c0 } <> + ~s{WHERE (c0.`post_id` IN (SELECT sp0.`id` FROM `posts` AS sp0 WHERE (sp0.`title` = ?)))} posts = subquery("posts" |> where(title: parent_as(:comment).subtitle) |> select([p], p.id)) - query = "comments" |> from(as: :comment) |> where([c], c.post_id in subquery(posts)) |> select([c], c.x) |> plan() + + query = + "comments" + |> from(as: :comment) + |> where([c], c.post_id in subquery(posts)) + |> select([c], c.x) + |> plan() + assert all(query) == - ~s{SELECT c0.`x` FROM `comments` AS c0 } <> - ~s{WHERE (c0.`post_id` IN (SELECT sp0.`id` FROM `posts` AS sp0 WHERE (sp0.`title` = c0.`subtitle`)))} + ~s{SELECT c0.`x` FROM `comments` AS c0 } <> + ~s{WHERE (c0.`post_id` IN (SELECT sp0.`id` FROM `posts` AS sp0 WHERE (sp0.`title` = c0.`subtitle`)))} end test "having" do query = Schema |> having([p], p.x == p.x) |> select([p], p.x) |> plan() assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 HAVING (s0.`x` = s0.`x`)} - query = Schema |> having([p], p.x == p.x) |> having([p], p.y == p.y) |> select([p], [p.y, p.x]) |> plan() - assert all(query) == ~s{SELECT s0.`y`, s0.`x` FROM `schema` AS s0 HAVING (s0.`x` = s0.`x`) AND (s0.`y` = s0.`y`)} + query = + Schema + |> having([p], p.x == p.x) + |> having([p], p.y == p.y) + |> select([p], [p.y, p.x]) + |> plan() + + assert all(query) == + ~s{SELECT s0.`y`, s0.`x` FROM `schema` AS s0 HAVING (s0.`x` = s0.`x`) AND (s0.`y` = s0.`y`)} end test "or_having" do query = Schema |> or_having([p], p.x == p.x) |> select([p], p.x) |> plan() assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 HAVING (s0.`x` = s0.`x`)} - query = Schema |> or_having([p], p.x == p.x) |> or_having([p], p.y == p.y) |> select([p], [p.y, p.x]) |> plan() - assert all(query) == ~s{SELECT s0.`y`, s0.`x` FROM `schema` AS s0 HAVING (s0.`x` = s0.`x`) OR (s0.`y` = s0.`y`)} + query = + Schema + |> or_having([p], p.x == p.x) + |> or_having([p], p.y == p.y) + |> select([p], [p.y, p.x]) + |> plan() + + assert all(query) == + ~s{SELECT s0.`y`, s0.`x` FROM `schema` AS s0 HAVING (s0.`x` = s0.`x`) OR (s0.`y` = s0.`y`)} end test "group by" do @@ -729,47 +842,51 @@ defmodule Ecto.Adapters.MyXQLTest do union = "schema1" |> select([m], {m.id, ^true}) |> where([], fragment("?", ^5)) union_all = "schema2" |> select([m], {m.id, ^false}) |> where([], fragment("?", ^6)) - query = Schema - |> with_cte("cte1", as: ^cte1) - |> with_cte("cte2", as: fragment("SELECT * FROM schema WHERE ?", ^2)) - |> select([m], {m.id, ^0}) - |> join(:inner, [], Schema2, on: fragment("?", ^true)) - |> join(:inner, [], Schema2, on: fragment("?", ^false)) - |> where([], fragment("?", ^true)) - |> where([], fragment("?", ^false)) - |> having([], fragment("?", ^true)) - |> having([], fragment("?", ^false)) - |> group_by([], fragment("?", ^3)) - |> group_by([], fragment("?", ^4)) - |> union(^union) - |> union_all(^union_all) - |> order_by([], fragment("?", ^7)) - |> limit([], ^8) - |> offset([], ^9) - |> plan() + query = + Schema + |> with_cte("cte1", as: ^cte1) + |> with_cte("cte2", as: fragment("SELECT * FROM schema WHERE ?", ^2)) + |> select([m], {m.id, ^0}) + |> join(:inner, [], Schema2, on: fragment("?", ^true)) + |> join(:inner, [], Schema2, on: fragment("?", ^false)) + |> where([], fragment("?", ^true)) + |> where([], fragment("?", ^false)) + |> having([], fragment("?", ^true)) + |> having([], fragment("?", ^false)) + |> group_by([], fragment("?", ^3)) + |> group_by([], fragment("?", ^4)) + |> union(^union) + |> union_all(^union_all) + |> order_by([], fragment("?", ^7)) + |> limit([], ^8) + |> offset([], ^9) + |> plan() result = "WITH `cte1` AS (SELECT ss0.`id` AS `id`, ? AS `smth` FROM `schema1` AS ss0 WHERE (?)), " <> - "`cte2` AS (SELECT * FROM schema WHERE ?) " <> - "SELECT s0.`id`, ? FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON ? " <> - "INNER JOIN `schema2` AS s2 ON ? WHERE (?) AND (?) " <> - "GROUP BY ?, ? HAVING (?) AND (?) " <> - "UNION (SELECT s0.`id`, ? FROM `schema1` AS s0 WHERE (?)) " <> - "UNION ALL (SELECT s0.`id`, ? FROM `schema2` AS s0 WHERE (?)) " <> - "ORDER BY ? LIMIT ? OFFSET ?" + "`cte2` AS (SELECT * FROM schema WHERE ?) " <> + "SELECT s0.`id`, ? FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON ? " <> + "INNER JOIN `schema2` AS s2 ON ? WHERE (?) AND (?) " <> + "GROUP BY ?, ? HAVING (?) AND (?) " <> + "UNION (SELECT s0.`id`, ? FROM `schema1` AS s0 WHERE (?)) " <> + "UNION ALL (SELECT s0.`id`, ? FROM `schema2` AS s0 WHERE (?)) " <> + "ORDER BY ? LIMIT ? OFFSET ?" assert all(query) == String.trim(result) end test "fragments allow ? to be escaped with backslash" do query = - plan from(e in "schema", - where: fragment("? = \"query\\?\"", e.start_time), - select: true) + plan( + from(e in "schema", + where: fragment("? = \"query\\?\"", e.start_time), + select: true + ) + ) result = "SELECT TRUE FROM `schema` AS s0 " <> - "WHERE (s0.`start_time` = \"query?\")" + "WHERE (s0.`start_time` = \"query?\")" assert all(query) == String.trim(result) end @@ -783,31 +900,46 @@ defmodule Ecto.Adapters.MyXQLTest do test "update all" do query = from(m in Schema, update: [set: [x: 0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE `schema` AS s0 SET s0.`x` = 0} + ~s{UPDATE `schema` AS s0 SET s0.`x` = 0} query = from(m in Schema, update: [set: [x: 0], inc: [y: 1, z: -3]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE `schema` AS s0 SET s0.`x` = 0, s0.`y` = s0.`y` + 1, s0.`z` = s0.`z` + -3} + ~s{UPDATE `schema` AS s0 SET s0.`x` = 0, s0.`y` = s0.`y` + 1, s0.`z` = s0.`z` + -3} query = from(e in Schema, where: e.x == 123, update: [set: [x: 0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE `schema` AS s0 SET s0.`x` = 0 WHERE (s0.`x` = 123)} + ~s{UPDATE `schema` AS s0 SET s0.`x` = 0 WHERE (s0.`x` = 123)} query = from(m in Schema, update: [set: [x: ^0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE `schema` AS s0 SET s0.`x` = ?} + ~s{UPDATE `schema` AS s0 SET s0.`x` = ?} + + query = + Schema + |> join(:inner, [p], q in Schema2, on: p.x == q.z) + |> update([_], set: [x: 0]) + |> plan(:update_all) - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) - |> update([_], set: [x: 0]) |> plan(:update_all) assert update_all(query) == - ~s{UPDATE `schema` AS s0, `schema2` AS s1 SET s0.`x` = 0 WHERE (s0.`x` = s1.`z`)} + ~s{UPDATE `schema` AS s0, `schema2` AS s1 SET s0.`x` = 0 WHERE (s0.`x` = s1.`z`)} + + query = + from(e in Schema, + where: e.x == 123, + update: [set: [x: 0]], + join: q in Schema2, + on: e.x == q.z + ) + |> plan(:update_all) - query = from(e in Schema, where: e.x == 123, update: [set: [x: 0]], - join: q in Schema2, on: e.x == q.z) |> plan(:update_all) assert update_all(query) == - ~s{UPDATE `schema` AS s0, `schema2` AS s1 } <> - ~s{SET s0.`x` = 0 WHERE (s0.`x` = s1.`z`) AND (s0.`x` = 123)} + ~s{UPDATE `schema` AS s0, `schema2` AS s1 } <> + ~s{SET s0.`x` = 0 WHERE (s0.`x` = s1.`z`) AND (s0.`x` = 123)} assert_raise ArgumentError, ":select is not supported in update_all by MySQL", fn -> query = from(e in Schema, where: e.x == 123, select: e.x) @@ -825,35 +957,44 @@ defmodule Ecto.Adapters.MyXQLTest do |> plan(:update_all) assert update_all(query) == - ~s{UPDATE `schema` AS s0, } <> - ~s{(SELECT ss0.`id` AS `id`, ss0.`x` AS `x`, ss0.`y` AS `y`, ss0.`z` AS `z`, ss0.`meta` AS `meta` FROM `schema` AS ss0 WHERE (ss0.`x` > 10)) AS s1 } <> - ~s{SET s0.`x` = ? WHERE (s0.`id` = s1.`id`)} + ~s{UPDATE `schema` AS s0, } <> + ~s{(SELECT ss0.`id` AS `id`, ss0.`x` AS `x`, ss0.`y` AS `y`, ss0.`z` AS `z`, ss0.`meta` AS `meta` FROM `schema` AS ss0 WHERE (ss0.`x` > 10)) AS s1 } <> + ~s{SET s0.`x` = ? WHERE (s0.`id` = s1.`id`)} end test "update all with prefix" do - query = from(m in Schema, update: [set: [x: 0]]) |> Map.put(:prefix, "prefix") |> plan(:update_all) + query = + from(m in Schema, update: [set: [x: 0]]) |> Map.put(:prefix, "prefix") |> plan(:update_all) + assert update_all(query) == ~s{UPDATE `prefix`.`schema` AS s0 SET s0.`x` = 0} - query = from(m in Schema, prefix: "first", update: [set: [x: 0]]) |> Map.put(:prefix, "prefix") |> plan(:update_all) + query = + from(m in Schema, prefix: "first", update: [set: [x: 0]]) + |> Map.put(:prefix, "prefix") + |> plan(:update_all) + assert update_all(query) == ~s{UPDATE `first`.`schema` AS s0 SET s0.`x` = 0} end test "delete all" do - query = Schema |> Queryable.to_query |> plan() + query = Schema |> Queryable.to_query() |> plan() assert delete_all(query) == ~s{DELETE s0.* FROM `schema` AS s0} query = from(e in Schema, where: e.x == 123) |> plan() + assert delete_all(query) == - ~s{DELETE s0.* FROM `schema` AS s0 WHERE (s0.`x` = 123)} + ~s{DELETE s0.* FROM `schema` AS s0 WHERE (s0.`x` = 123)} query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> plan() + assert delete_all(query) == - ~s{DELETE s0.* FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z`} + ~s{DELETE s0.* FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z`} query = from(e in Schema, where: e.x == 123, join: q in Schema2, on: e.x == q.z) |> plan() + assert delete_all(query) == - ~s{DELETE s0.* FROM `schema` AS s0 } <> - ~s{INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z` WHERE (s0.`x` = 123)} + ~s{DELETE s0.* FROM `schema` AS s0 } <> + ~s{INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z` WHERE (s0.`x` = 123)} assert_raise ArgumentError, ":select is not supported in delete_all by MySQL", fn -> query = from(e in Schema, where: e.x == 123, select: e.x) @@ -862,7 +1003,7 @@ defmodule Ecto.Adapters.MyXQLTest do end test "delete all with prefix" do - query = Schema |> Queryable.to_query |> Map.put(:prefix, "prefix") |> plan() + query = Schema |> Queryable.to_query() |> Map.put(:prefix, "prefix") |> plan() assert delete_all(query) == ~s{DELETE s0.* FROM `prefix`.`schema` AS s0} query = Schema |> from(prefix: "first") |> Map.put(:prefix, "prefix") |> plan() @@ -873,102 +1014,142 @@ defmodule Ecto.Adapters.MyXQLTest do describe "windows" do test "one window" do - query = Schema - |> select([r], r.x) - |> windows([r], w: [partition_by: r.x]) - |> plan - - assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WINDOW `w` AS (PARTITION BY s0.`x`)} + query = + Schema + |> select([r], r.x) + |> windows([r], w: [partition_by: r.x]) + |> plan + + assert all(query) == + ~s{SELECT s0.`x` FROM `schema` AS s0 WINDOW `w` AS (PARTITION BY s0.`x`)} end test "two windows" do - query = Schema - |> select([r], r.x) - |> windows([r], w1: [partition_by: r.x], w2: [partition_by: r.y]) - |> plan() - assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WINDOW `w1` AS (PARTITION BY s0.`x`), `w2` AS (PARTITION BY s0.`y`)} + query = + Schema + |> select([r], r.x) + |> windows([r], w1: [partition_by: r.x], w2: [partition_by: r.y]) + |> plan() + + assert all(query) == + ~s{SELECT s0.`x` FROM `schema` AS s0 WINDOW `w1` AS (PARTITION BY s0.`x`), `w2` AS (PARTITION BY s0.`y`)} end test "count over window" do - query = Schema - |> windows([r], w: [partition_by: r.x]) - |> select([r], count(r.x) |> over(:w)) - |> plan() - assert all(query) == ~s{SELECT count(s0.`x`) OVER `w` FROM `schema` AS s0 WINDOW `w` AS (PARTITION BY s0.`x`)} + query = + Schema + |> windows([r], w: [partition_by: r.x]) + |> select([r], count(r.x) |> over(:w)) + |> plan() + + assert all(query) == + ~s{SELECT count(s0.`x`) OVER `w` FROM `schema` AS s0 WINDOW `w` AS (PARTITION BY s0.`x`)} end test "count over all" do - query = Schema - |> select([r], count(r.x) |> over) - |> plan() + query = + Schema + |> select([r], count(r.x) |> over) + |> plan() + assert all(query) == ~s{SELECT count(s0.`x`) OVER () FROM `schema` AS s0} end test "row_number over all" do - query = Schema - |> select(row_number |> over) - |> plan() + query = + Schema + |> select(row_number |> over) + |> plan() + assert all(query) == ~s{SELECT row_number() OVER () FROM `schema` AS s0} end test "nth_value over all" do - query = Schema - |> select([r], nth_value(r.x, 42) |> over) - |> plan() + query = + Schema + |> select([r], nth_value(r.x, 42) |> over) + |> plan() + assert all(query) == ~s{SELECT nth_value(s0.`x`, 42) OVER () FROM `schema` AS s0} end test "lag/2 over all" do - query = Schema - |> select([r], lag(r.x, 42) |> over) - |> plan() + query = + Schema + |> select([r], lag(r.x, 42) |> over) + |> plan() + assert all(query) == ~s{SELECT lag(s0.`x`, 42) OVER () FROM `schema` AS s0} end test "custom aggregation over all" do - query = Schema - |> select([r], fragment("custom_function(?)", r.x) |> over) - |> plan() + query = + Schema + |> select([r], fragment("custom_function(?)", r.x) |> over) + |> plan() + assert all(query) == ~s{SELECT custom_function(s0.`x`) OVER () FROM `schema` AS s0} end test "partition by and order by on window" do - query = Schema - |> windows([r], w: [partition_by: [r.x, r.z], order_by: r.x]) - |> select([r], r.x) - |> plan() - assert all(query) == ~s{SELECT s0.`x` FROM `schema` AS s0 WINDOW `w` AS (PARTITION BY s0.`x`, s0.`z` ORDER BY s0.`x`)} + query = + Schema + |> windows([r], w: [partition_by: [r.x, r.z], order_by: r.x]) + |> select([r], r.x) + |> plan() + + assert all(query) == + ~s{SELECT s0.`x` FROM `schema` AS s0 WINDOW `w` AS (PARTITION BY s0.`x`, s0.`z` ORDER BY s0.`x`)} end test "partition by and order by on over" do - query = Schema - |> select([r], count(r.x) |> over(partition_by: [r.x, r.z], order_by: r.x)) + query = select(Schema, [r], count(r.x) |> over(partition_by: [r.x, r.z], order_by: r.x)) query = query |> plan() - assert all(query) == ~s{SELECT count(s0.`x`) OVER (PARTITION BY s0.`x`, s0.`z` ORDER BY s0.`x`) FROM `schema` AS s0} + + assert all(query) == + ~s{SELECT count(s0.`x`) OVER (PARTITION BY s0.`x`, s0.`z` ORDER BY s0.`x`) FROM `schema` AS s0} end test "frame clause" do - query = Schema - |> select([r], count(r.x) |> over(partition_by: [r.x, r.z], order_by: r.x, frame: fragment("ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING"))) + query = + select( + Schema, + [r], + count(r.x) + |> over( + partition_by: [r.x, r.z], + order_by: r.x, + frame: fragment("ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING") + ) + ) query = query |> plan() - assert all(query) == ~s{SELECT count(s0.`x`) OVER (PARTITION BY s0.`x`, s0.`z` ORDER BY s0.`x` ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) FROM `schema` AS s0} + + assert all(query) == + ~s{SELECT count(s0.`x`) OVER (PARTITION BY s0.`x`, s0.`z` ORDER BY s0.`x` ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) FROM `schema` AS s0} end end ## Joins test "join" do - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> select([], true) |> plan() + query = + Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> select([], true) |> plan() + assert all(query) == - ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z`} + ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z`} + + query = + Schema + |> join(:inner, [p], q in Schema2, on: p.x == q.z) + |> join(:inner, [], Schema, on: true) + |> select([], true) + |> plan() - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) - |> join(:inner, [], Schema, on: true) |> select([], true) |> plan() assert all(query) == - ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z` } <> - ~s{INNER JOIN `schema` AS s2 ON TRUE} + ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s0.`x` = s1.`z` } <> + ~s{INNER JOIN `schema` AS s2 ON TRUE} end test "join with invalid qualifier" do @@ -986,141 +1167,204 @@ defmodule Ecto.Adapters.MyXQLTest do |> join(:inner, [p], q in Schema2, on: true, hints: ["USE INDEX FOO", "USE INDEX BAR"]) |> select([], true) |> plan() - |> all() == ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 USE INDEX FOO USE INDEX BAR ON TRUE} + |> all() == + ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 USE INDEX FOO USE INDEX BAR ON TRUE} end test "join with nothing bound" do query = Schema |> join(:inner, [], q in Schema2, on: q.z == q.z) |> select([], true) |> plan() + assert all(query) == - ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s1.`z` = s1.`z`} + ~s{SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s1.`z` = s1.`z`} end test "join without schema" do - query = "posts" |> join(:inner, [p], q in "comments", on: p.x == q.z) |> select([], true) |> plan() + query = + "posts" |> join(:inner, [p], q in "comments", on: p.x == q.z) |> select([], true) |> plan() + assert all(query) == - ~s{SELECT TRUE FROM `posts` AS p0 INNER JOIN `comments` AS c1 ON p0.`x` = c1.`z`} + ~s{SELECT TRUE FROM `posts` AS p0 INNER JOIN `comments` AS c1 ON p0.`x` = c1.`z`} end test "join with subquery" do posts = subquery("posts" |> where(title: ^"hello") |> select([r], %{x: r.x, y: r.y})) - query = "comments" |> join(:inner, [c], p in subquery(posts), on: true) |> select([_, p], p.x) |> plan() + + query = + "comments" + |> join(:inner, [c], p in subquery(posts), on: true) + |> select([_, p], p.x) + |> plan() + assert all(query) == - ~s{SELECT s1.`x` FROM `comments` AS c0 } <> - ~s{INNER JOIN (SELECT sp0.`x` AS `x`, sp0.`y` AS `y` FROM `posts` AS sp0 WHERE (sp0.`title` = ?)) AS s1 ON TRUE} + ~s{SELECT s1.`x` FROM `comments` AS c0 } <> + ~s{INNER JOIN (SELECT sp0.`x` AS `x`, sp0.`y` AS `y` FROM `posts` AS sp0 WHERE (sp0.`title` = ?)) AS s1 ON TRUE} posts = subquery("posts" |> where(title: ^"hello") |> select([r], %{x: r.x, z: r.y})) - query = "comments" |> join(:inner, [c], p in subquery(posts), on: true) |> select([_, p], p) |> plan() + + query = + "comments" + |> join(:inner, [c], p in subquery(posts), on: true) + |> select([_, p], p) + |> plan() + assert all(query) == - ~s{SELECT s1.`x`, s1.`z` FROM `comments` AS c0 } <> - ~s{INNER JOIN (SELECT sp0.`x` AS `x`, sp0.`y` AS `z` FROM `posts` AS sp0 WHERE (sp0.`title` = ?)) AS s1 ON TRUE} + ~s{SELECT s1.`x`, s1.`z` FROM `comments` AS c0 } <> + ~s{INNER JOIN (SELECT sp0.`x` AS `x`, sp0.`y` AS `z` FROM `posts` AS sp0 WHERE (sp0.`title` = ?)) AS s1 ON TRUE} + + posts = + subquery("posts" |> where(title: parent_as(:comment).subtitle) |> select([r], r.title)) + + query = + "comments" + |> from(as: :comment) + |> join(:inner, [c], p in subquery(posts), on: true) + |> select([_, p], p) + |> plan() - posts = subquery("posts" |> where(title: parent_as(:comment).subtitle) |> select([r], r.title)) - query = "comments" |> from(as: :comment) |> join(:inner, [c], p in subquery(posts), on: true) |> select([_, p], p) |> plan() assert all(query) == - "SELECT s1.`title` FROM `comments` AS c0 " <> - "INNER JOIN (SELECT sp0.`title` AS `title` FROM `posts` AS sp0 WHERE (sp0.`title` = c0.`subtitle`)) AS s1 ON TRUE" + "SELECT s1.`title` FROM `comments` AS c0 " <> + "INNER JOIN (SELECT sp0.`title` AS `title` FROM `posts` AS sp0 WHERE (sp0.`title` = c0.`subtitle`)) AS s1 ON TRUE" end test "join with prefix" do - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> select([], true) |> Map.put(:prefix, "prefix") |> plan() + query = + Schema + |> join(:inner, [p], q in Schema2, on: p.x == q.z) + |> select([], true) + |> Map.put(:prefix, "prefix") + |> plan() + assert all(query) == - ~s{SELECT TRUE FROM `prefix`.`schema` AS s0 INNER JOIN `prefix`.`schema2` AS s1 ON s0.`x` = s1.`z`} + ~s{SELECT TRUE FROM `prefix`.`schema` AS s0 INNER JOIN `prefix`.`schema2` AS s1 ON s0.`x` = s1.`z`} + + query = + Schema + |> from(prefix: "first") + |> join(:inner, [p], q in Schema2, on: p.x == q.z, prefix: "second") + |> select([], true) + |> Map.put(:prefix, "prefix") + |> plan() - query = Schema |> from(prefix: "first") |> join(:inner, [p], q in Schema2, on: p.x == q.z, prefix: "second") |> select([], true) |> Map.put(:prefix, "prefix") |> plan() assert all(query) == - ~s{SELECT TRUE FROM `first`.`schema` AS s0 INNER JOIN `second`.`schema2` AS s1 ON s0.`x` = s1.`z`} + ~s{SELECT TRUE FROM `first`.`schema` AS s0 INNER JOIN `second`.`schema2` AS s1 ON s0.`x` = s1.`z`} end test "join with fragment" do - query = Schema - |> join(:inner, [p], q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), on: true) - |> select([p], {p.id, ^0}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :inner, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), + on: true + ) + |> select([p], {p.id, ^0}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0.`id`, ? FROM `schema` AS s0 INNER JOIN } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.`x` AND s2.field = ?) AS f1 ON TRUE } <> - ~s{WHERE ((s0.`id` > 0) AND (s0.`id` < ?))} + ~s{SELECT s0.`id`, ? FROM `schema` AS s0 INNER JOIN } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.`x` AND s2.field = ?) AS f1 ON TRUE } <> + ~s{WHERE ((s0.`id` > 0) AND (s0.`id` < ?))} end test "join with fragment and on defined" do - query = Schema - |> join(:inner, [p], q in fragment("SELECT * FROM schema2"), on: q.id == p.id) - |> select([p], {p.id, ^0}) - |> plan() + query = + Schema + |> join(:inner, [p], q in fragment("SELECT * FROM schema2"), on: q.id == p.id) + |> select([p], {p.id, ^0}) + |> plan() + assert all(query) == - ~s{SELECT s0.`id`, ? FROM `schema` AS s0 INNER JOIN } <> - ~s{(SELECT * FROM schema2) AS f1 ON f1.`id` = s0.`id`} + ~s{SELECT s0.`id`, ? FROM `schema` AS s0 INNER JOIN } <> + ~s{(SELECT * FROM schema2) AS f1 ON f1.`id` = s0.`id`} end test "join with query interpolation" do inner = Ecto.Queryable.to_query(Schema2) + query = from(p in Schema, left_join: c in ^inner, on: true, select: {p.id, c.id}) |> plan() + assert all(query) == - "SELECT s0.`id`, s1.`id` FROM `schema` AS s0 LEFT OUTER JOIN `schema2` AS s1 ON TRUE" + "SELECT s0.`id`, s1.`id` FROM `schema` AS s0 LEFT OUTER JOIN `schema2` AS s1 ON TRUE" end test "lateral join with fragment" do - query = Schema - |> join(:inner_lateral, [p], - q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), - on: true - ) - |> select([p, q], {p.id, q.z}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :inner_lateral, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), + on: true + ) + |> select([p, q], {p.id, q.z}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0.`id`, f1.`z` FROM `schema` AS s0 INNER JOIN LATERAL } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.`x` AND s2.field = ?) AS f1 ON TRUE } <> - ~s{WHERE ((s0.`id` > 0) AND (s0.`id` < ?))} + ~s{SELECT s0.`id`, f1.`z` FROM `schema` AS s0 INNER JOIN LATERAL } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.`x` AND s2.field = ?) AS f1 ON TRUE } <> + ~s{WHERE ((s0.`id` > 0) AND (s0.`id` < ?))} end test "cross lateral join with fragment" do - query = Schema - |> join(:cross_lateral, [p], q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10)) - |> select([p, q], {p.id, q.z}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :cross_lateral, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10) + ) + |> select([p, q], {p.id, q.z}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0.`id`, f1.`z` FROM `schema` AS s0 CROSS JOIN LATERAL } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.`x` AND s2.field = ?) AS f1 } <> - ~s{WHERE ((s0.`id` > 0) AND (s0.`id` < ?))} + ~s{SELECT s0.`id`, f1.`z` FROM `schema` AS s0 CROSS JOIN LATERAL } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.`x` AND s2.field = ?) AS f1 } <> + ~s{WHERE ((s0.`id` > 0) AND (s0.`id` < ?))} end test "cross join" do query = from(p in Schema, cross_join: c in Schema2, select: {p.id, c.id}) |> plan() + assert all(query) == - "SELECT s0.`id`, s1.`id` FROM `schema` AS s0 CROSS JOIN `schema2` AS s1" + "SELECT s0.`id`, s1.`id` FROM `schema` AS s0 CROSS JOIN `schema2` AS s1" end test "join produces correct bindings" do query = from(p in Schema, join: c in Schema2, on: true) query = from(p in query, join: c in Schema2, on: true, select: {p.id, c.id}) query = plan(query) + assert all(query) == - "SELECT s0.`id`, s2.`id` FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON TRUE INNER JOIN `schema2` AS s2 ON TRUE" + "SELECT s0.`id`, s2.`id` FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON TRUE INNER JOIN `schema2` AS s2 ON TRUE" end ## Associations test "association join belongs_to" do query = Schema2 |> join(:inner, [c], p in assoc(c, :post)) |> select([], true) |> plan() + assert all(query) == - "SELECT TRUE FROM `schema2` AS s0 INNER JOIN `schema` AS s1 ON s1.`x` = s0.`z`" + "SELECT TRUE FROM `schema2` AS s0 INNER JOIN `schema` AS s1 ON s1.`x` = s0.`z`" end test "association join has_many" do query = Schema |> join(:inner, [p], c in assoc(p, :comments)) |> select([], true) |> plan() + assert all(query) == - "SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s1.`z` = s0.`x`" + "SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema2` AS s1 ON s1.`z` = s0.`x`" end test "association join has_one" do query = Schema |> join(:inner, [p], pp in assoc(p, :permalink)) |> select([], true) |> plan() + assert all(query) == - "SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema3` AS s1 ON s1.`id` = s0.`y`" + "SELECT TRUE FROM `schema` AS s0 INNER JOIN `schema3` AS s1 ON s1.`id` = s0.`y`" end # Schema based @@ -1145,36 +1389,61 @@ defmodule Ecto.Adapters.MyXQLTest do test "insert with on duplicate key" do query = insert(nil, "schema", [:x, :y], [[:x, :y]], {:nothing, [], []}, []) - assert query == ~s{INSERT INTO `schema` (`x`,`y`) VALUES (?,?) ON DUPLICATE KEY UPDATE `x` = `x`} + + assert query == + ~s{INSERT INTO `schema` (`x`,`y`) VALUES (?,?) ON DUPLICATE KEY UPDATE `x` = `x`} update = from("schema", update: [set: [z: "foo"]]) |> plan(:update_all) query = insert(nil, "schema", [:x, :y], [[:x, :y]], {update, [], []}, []) - assert query == ~s{INSERT INTO `schema` (`x`,`y`) VALUES (?,?) ON DUPLICATE KEY UPDATE `z` = 'foo'} + + assert query == + ~s{INSERT INTO `schema` (`x`,`y`) VALUES (?,?) ON DUPLICATE KEY UPDATE `z` = 'foo'} query = insert(nil, "schema", [:x, :y], [[:x, :y]], {[:x, :y], [], []}, []) - assert query == ~s{INSERT INTO `schema` (`x`,`y`) VALUES (?,?) ON DUPLICATE KEY UPDATE `x` = VALUES(`x`),`y` = VALUES(`y`)} - assert_raise ArgumentError, ":conflict_target is not supported in insert/insert_all by MySQL", fn -> - insert(nil, "schema", [:x, :y], [[:x, :y]], {[:x, :y], [], [:x]}, []) - end + assert query == + ~s{INSERT INTO `schema` (`x`,`y`) VALUES (?,?) ON DUPLICATE KEY UPDATE `x` = VALUES(`x`),`y` = VALUES(`y`)} - assert_raise ArgumentError, "Using a query with :where in combination with the :on_conflict option is not supported by MySQL", fn -> - update = from("schema", update: [set: [x: ^"foo"]], where: [z: "bar"]) |> plan(:update_all) - insert(nil, "schema", [:x, :y], [[:x, :y]], {update, [], []}, []) - end + assert_raise ArgumentError, + ":conflict_target is not supported in insert/insert_all by MySQL", + fn -> + insert(nil, "schema", [:x, :y], [[:x, :y]], {[:x, :y], [], [:x]}, []) + end + + assert_raise ArgumentError, + "Using a query with :where in combination with the :on_conflict option is not supported by MySQL", + fn -> + update = + from("schema", update: [set: [x: ^"foo"]], where: [z: "bar"]) + |> plan(:update_all) + + insert(nil, "schema", [:x, :y], [[:x, :y]], {update, [], []}, []) + end end test "insert with query" do select_query = from("schema", select: [:id]) |> plan(:all) - query = insert(nil, "schema", [:x, :y, :z], [[:x, {select_query, 2}, :z], [nil, nil, {select_query, 1}]], {:raise, [], []}, []) - assert query == ~s{INSERT INTO `schema` (`x`,`y`,`z`) VALUES (?,(SELECT s0.`id` FROM `schema` AS s0),?),(DEFAULT,DEFAULT,(SELECT s0.`id` FROM `schema` AS s0))} + + query = + insert( + nil, + "schema", + [:x, :y, :z], + [[:x, {select_query, 2}, :z], [nil, nil, {select_query, 1}]], + {:raise, [], []}, + [] + ) + + assert query == + ~s{INSERT INTO `schema` (`x`,`y`,`z`) VALUES (?,(SELECT s0.`id` FROM `schema` AS s0),?),(DEFAULT,DEFAULT,(SELECT s0.`id` FROM `schema` AS s0))} end test "insert with query as rows" do - query = from(s in "schema", select: %{ foo: fragment("3"), bar: s.bar }) |> plan(:all) + query = from(s in "schema", select: %{foo: fragment("3"), bar: s.bar}) |> plan(:all) query = insert(nil, "schema", [:foo, :bar], query, {:raise, [], []}, []) - assert query == ~s{INSERT INTO `schema` (`foo`,`bar`) (SELECT 3, s0.`bar` FROM `schema` AS s0)} + assert query == + ~s{INSERT INTO `schema` (`foo`,`bar`) (SELECT 3, s0.`bar` FROM `schema` AS s0)} end test "update" do @@ -1218,9 +1487,9 @@ defmodule Ecto.Adapters.MyXQLTest do assert query == ~s{SELECT v1.`bid`, v1.`num` } <> - ~s{FROM (VALUES ROW(CAST(? AS binary(16)),CAST(? AS unsigned)),ROW(CAST(? AS binary(16)),CAST(? AS unsigned))) AS v0 (`bid`,`num`) } <> - ~s{INNER JOIN (VALUES ROW(CAST(? AS binary(16)),CAST(? AS unsigned)),ROW(CAST(? AS binary(16)),CAST(? AS unsigned))) AS v1 (`bid`,`num`) ON v0.`bid` = v1.`bid` } <> - ~s{WHERE (v0.`num` = ?)} + ~s{FROM (VALUES ROW(CAST(? AS binary(16)),CAST(? AS unsigned)),ROW(CAST(? AS binary(16)),CAST(? AS unsigned))) AS v0 (`bid`,`num`) } <> + ~s{INNER JOIN (VALUES ROW(CAST(? AS binary(16)),CAST(? AS unsigned)),ROW(CAST(? AS binary(16)),CAST(? AS unsigned))) AS v1 (`bid`,`num`) ON v0.`bid` = v1.`bid` } <> + ~s{WHERE (v0.`num` = ?)} end test "values list: delete_all" do @@ -1235,8 +1504,8 @@ defmodule Ecto.Adapters.MyXQLTest do assert query == ~s{DELETE s0.* FROM `schema` AS s0 } <> - ~s{INNER JOIN (VALUES ROW(CAST(? AS binary(16)),CAST(? AS unsigned)),ROW(CAST(? AS binary(16)),CAST(? AS unsigned))) AS v1 (`bid`,`num`) } <> - ~s{ON s0.`x` = v1.`num` WHERE (v1.`num` = ?)} + ~s{INNER JOIN (VALUES ROW(CAST(? AS binary(16)),CAST(? AS unsigned)),ROW(CAST(? AS binary(16)),CAST(? AS unsigned))) AS v1 (`bid`,`num`) } <> + ~s{ON s0.`x` = v1.`num` WHERE (v1.`num` = ?)} end test "values list: update_all" do @@ -1262,237 +1531,316 @@ 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: 3] test "executing a string during migration" do assert execute_ddl("example") == ["example"] end test "create table" do - create = {:create, table(:posts), - [{:add, :name, :string, [default: "Untitled", size: 20, null: false]}, - {:add, :token, :binary, [size: 20, null: false]}, - {:add, :price, :numeric, [precision: 8, scale: 2, default: {:fragment, "expr"}]}, - {:add, :on_hand, :integer, [default: 0, null: true]}, - {:add, :likes, :"smallint unsigned", [default: 0, null: false]}, - {:add, :published_at, :"datetime(6)", [null: true]}, - {:add, :is_active, :boolean, [default: true]}]} - - assert execute_ddl(create) == [""" - CREATE TABLE `posts` (`name` varchar(20) DEFAULT 'Untitled' NOT NULL, - `token` varbinary(20) NOT NULL, - `price` numeric(8,2) DEFAULT expr, - `on_hand` integer DEFAULT 0 NULL, - `likes` smallint unsigned DEFAULT 0 NOT NULL, - `published_at` datetime(6) NULL, - `is_active` boolean DEFAULT true) ENGINE = INNODB - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :name, :string, [default: "Untitled", size: 20, null: false]}, + {:add, :token, :binary, [size: 20, null: false]}, + {:add, :price, :numeric, [precision: 8, scale: 2, default: {:fragment, "expr"}]}, + {:add, :on_hand, :integer, [default: 0, null: true]}, + {:add, :likes, :"smallint unsigned", [default: 0, null: false]}, + {:add, :published_at, :"datetime(6)", [null: true]}, + {:add, :is_active, :boolean, [default: true]} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` (`name` varchar(20) DEFAULT 'Untitled' NOT NULL, + `token` varbinary(20) NOT NULL, + `price` numeric(8,2) DEFAULT expr, + `on_hand` integer DEFAULT 0 NULL, + `likes` smallint unsigned DEFAULT 0 NOT NULL, + `published_at` datetime(6) NULL, + `is_active` boolean DEFAULT true) ENGINE = INNODB + """ + |> remove_newlines + ] end test "create empty table" do create = {:create, table(:posts), []} - assert execute_ddl(create) == [""" - CREATE TABLE `posts` ENGINE = INNODB - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with prefix" do - create = {:create, table(:posts, prefix: :foo), - [{:add, :category_0, %Reference{table: :categories}, []}]} + create = + {:create, table(:posts, prefix: :foo), + [{:add, :category_0, %Reference{table: :categories}, []}]} - assert execute_ddl(create) == [""" - CREATE TABLE `foo`.`posts` (`category_0` BIGINT UNSIGNED, - CONSTRAINT `posts_category_0_fkey` FOREIGN KEY (`category_0`) REFERENCES `foo`.`categories`(`id`)) ENGINE = INNODB - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE `foo`.`posts` (`category_0` BIGINT UNSIGNED, + CONSTRAINT `posts_category_0_fkey` FOREIGN KEY (`category_0`) REFERENCES `foo`.`categories`(`id`)) ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with comment on columns and table" do - create = {:create, table(:posts, comment: "comment", prefix: :foo), - [ - {:add, :category_0, %Reference{table: :categories}, [comment: "column comment"]}, - {:add, :created_at, :datetime, []}, - {:add, :updated_at, :datetime, [comment: "column comment 2"]} - ]} - assert execute_ddl(create) == [""" - CREATE TABLE `foo`.`posts` (`category_0` BIGINT UNSIGNED COMMENT 'column comment', - CONSTRAINT `posts_category_0_fkey` FOREIGN KEY (`category_0`) REFERENCES `foo`.`categories`(`id`), - `created_at` datetime, `updated_at` datetime COMMENT 'column comment 2') COMMENT = 'comment' ENGINE = INNODB - """ |> remove_newlines] + create = + {:create, table(:posts, comment: "comment", prefix: :foo), + [ + {:add, :category_0, %Reference{table: :categories}, [comment: "column comment"]}, + {:add, :created_at, :datetime, []}, + {:add, :updated_at, :datetime, [comment: "column comment 2"]} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `foo`.`posts` (`category_0` BIGINT UNSIGNED COMMENT 'column comment', + CONSTRAINT `posts_category_0_fkey` FOREIGN KEY (`category_0`) REFERENCES `foo`.`categories`(`id`), + `created_at` datetime, `updated_at` datetime COMMENT 'column comment 2') COMMENT = 'comment' ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with engine" do - create = {:create, table(:posts, engine: :myisam), - [{:add, :id, :serial, [primary_key: true]}]} + create = + {:create, table(:posts, engine: :myisam), [{:add, :id, :serial, [primary_key: true]}]} + assert execute_ddl(create) == - [~s|CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, PRIMARY KEY (`id`)) ENGINE = MYISAM|] + [ + ~s|CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, PRIMARY KEY (`id`)) ENGINE = MYISAM| + ] end test "create table with references" do - create = {:create, table(:posts), - [{:add, :id, :serial, [primary_key: true]}, - {:add, :category_0, %Reference{table: :categories}, []}, - {:add, :category_1, %Reference{table: :categories, name: :foo_bar}, []}, - {:add, :category_2, %Reference{table: :categories, on_delete: :nothing}, []}, - {:add, :category_3, %Reference{table: :categories, on_delete: :delete_all}, [null: false]}, - {:add, :category_4, %Reference{table: :categories, on_delete: :nilify_all}, []}, - {:add, :category_5, %Reference{table: :categories, prefix: :foo, on_delete: :nilify_all}, []}, - {:add, :category_6, %Reference{table: :categories, with: [here: :there], on_delete: :nilify_all}, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, - `category_0` BIGINT UNSIGNED, - CONSTRAINT `posts_category_0_fkey` FOREIGN KEY (`category_0`) REFERENCES `categories`(`id`), - `category_1` BIGINT UNSIGNED, - CONSTRAINT `foo_bar` FOREIGN KEY (`category_1`) REFERENCES `categories`(`id`), - `category_2` BIGINT UNSIGNED, - CONSTRAINT `posts_category_2_fkey` FOREIGN KEY (`category_2`) REFERENCES `categories`(`id`), - `category_3` BIGINT UNSIGNED NOT NULL, - CONSTRAINT `posts_category_3_fkey` FOREIGN KEY (`category_3`) REFERENCES `categories`(`id`) ON DELETE CASCADE, - `category_4` BIGINT UNSIGNED, - CONSTRAINT `posts_category_4_fkey` FOREIGN KEY (`category_4`) REFERENCES `categories`(`id`) ON DELETE SET NULL, - `category_5` BIGINT UNSIGNED, - CONSTRAINT `posts_category_5_fkey` FOREIGN KEY (`category_5`) REFERENCES `foo`.`categories`(`id`) ON DELETE SET NULL, - `category_6` BIGINT UNSIGNED, - CONSTRAINT `posts_category_6_fkey` FOREIGN KEY (`category_6`,`here`) REFERENCES `categories`(`id`,`there`) ON DELETE SET NULL, - PRIMARY KEY (`id`)) ENGINE = INNODB - """ |> remove_newlines] - - create = {:create, table(:posts), - [{:add, :category_1, %Reference{table: :categories, on_delete: {:nilify, [:category_1]}}, []}]} + create = + {:create, table(:posts), + [ + {:add, :id, :serial, [primary_key: true]}, + {:add, :category_0, %Reference{table: :categories}, []}, + {:add, :category_1, %Reference{table: :categories, name: :foo_bar}, []}, + {:add, :category_2, %Reference{table: :categories, on_delete: :nothing}, []}, + {:add, :category_3, %Reference{table: :categories, on_delete: :delete_all}, + [null: false]}, + {:add, :category_4, %Reference{table: :categories, on_delete: :nilify_all}, []}, + {:add, :category_5, %Reference{table: :categories, prefix: :foo, on_delete: :nilify_all}, + []}, + {:add, :category_6, + %Reference{table: :categories, with: [here: :there], on_delete: :nilify_all}, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, + `category_0` BIGINT UNSIGNED, + CONSTRAINT `posts_category_0_fkey` FOREIGN KEY (`category_0`) REFERENCES `categories`(`id`), + `category_1` BIGINT UNSIGNED, + CONSTRAINT `foo_bar` FOREIGN KEY (`category_1`) REFERENCES `categories`(`id`), + `category_2` BIGINT UNSIGNED, + CONSTRAINT `posts_category_2_fkey` FOREIGN KEY (`category_2`) REFERENCES `categories`(`id`), + `category_3` BIGINT UNSIGNED NOT NULL, + CONSTRAINT `posts_category_3_fkey` FOREIGN KEY (`category_3`) REFERENCES `categories`(`id`) ON DELETE CASCADE, + `category_4` BIGINT UNSIGNED, + CONSTRAINT `posts_category_4_fkey` FOREIGN KEY (`category_4`) REFERENCES `categories`(`id`) ON DELETE SET NULL, + `category_5` BIGINT UNSIGNED, + CONSTRAINT `posts_category_5_fkey` FOREIGN KEY (`category_5`) REFERENCES `foo`.`categories`(`id`) ON DELETE SET NULL, + `category_6` BIGINT UNSIGNED, + CONSTRAINT `posts_category_6_fkey` FOREIGN KEY (`category_6`,`here`) REFERENCES `categories`(`id`,`there`) ON DELETE SET NULL, + PRIMARY KEY (`id`)) ENGINE = INNODB + """ + |> remove_newlines + ] + + create = + {:create, table(:posts), + [ + {:add, :category_1, %Reference{table: :categories, on_delete: {:nilify, [:category_1]}}, + []} + ]} msg = "MySQL adapter does not support the `{:nilify, columns}` action for `:on_delete`" assert_raise ArgumentError, msg, fn -> execute_ddl(create) end end test "create table with options" do - create = {:create, table(:posts, options: "WITH FOO=BAR"), - [{:add, :id, :serial, [primary_key: true]}, - {:add, :created_at, :datetime, []}]} + create = + {:create, table(:posts, options: "WITH FOO=BAR"), + [{:add, :id, :serial, [primary_key: true]}, {:add, :created_at, :datetime, []}]} + assert execute_ddl(create) == - [~s|CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, `created_at` datetime, PRIMARY KEY (`id`)) ENGINE = INNODB WITH FOO=BAR|] + [ + ~s|CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, `created_at` datetime, PRIMARY KEY (`id`)) ENGINE = INNODB WITH FOO=BAR| + ] end test "create table with both engine and options" do - create = {:create, table(:posts, engine: :myisam, options: "WITH FOO=BAR"), - [{:add, :id, :serial, [primary_key: true]}, - {:add, :created_at, :datetime, []}]} + create = + {:create, table(:posts, engine: :myisam, options: "WITH FOO=BAR"), + [{:add, :id, :serial, [primary_key: true]}, {:add, :created_at, :datetime, []}]} + assert execute_ddl(create) == - [~s|CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, `created_at` datetime, PRIMARY KEY (`id`)) ENGINE = MYISAM WITH FOO=BAR|] + [ + ~s|CREATE TABLE `posts` (`id` bigint unsigned not null auto_increment, `created_at` datetime, PRIMARY KEY (`id`)) ENGINE = MYISAM WITH FOO=BAR| + ] end test "create table with composite key" do - create = {:create, table(:posts), - [{:add, :a, :integer, [primary_key: true]}, - {:add, :b, :integer, [primary_key: true]}, - {:add, :name, :string, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE `posts` (`a` integer, `b` integer, `name` varchar(255), PRIMARY KEY (`a`,`b`)) ENGINE = INNODB - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :a, :integer, [primary_key: true]}, + {:add, :b, :integer, [primary_key: true]}, + {:add, :name, :string, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` (`a` integer, `b` integer, `name` varchar(255), PRIMARY KEY (`a`,`b`)) ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with a map column, and a map default with values" do - create = {:create, table(:posts), - [ - {:add, :a, :map, [default: %{foo: "bar", baz: "boom"}]} - ] - } + create = + {:create, table(:posts), + [ + {:add, :a, :map, [default: %{foo: "bar", baz: "boom"}]} + ]} - assert execute_ddl(create) == [""" - CREATE TABLE `posts` (`a` json DEFAULT ('{\"baz\":\"boom\",\"foo\":\"bar\"}')) ENGINE = INNODB - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` (`a` json DEFAULT ('{\"baz\":\"boom\",\"foo\":\"bar\"}')) ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with time columns" do - create = {:create, table(:posts), - [{:add, :published_at, :time, [precision: 3]}, - {:add, :submitted_at, :time, []}]} + create = + {:create, table(:posts), + [{:add, :published_at, :time, [precision: 3]}, {:add, :submitted_at, :time, []}]} - assert execute_ddl(create) == [""" - CREATE TABLE `posts` - (`published_at` time, - `submitted_at` time) - ENGINE = INNODB - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` + (`published_at` time, + `submitted_at` time) + ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with time_usec columns" do - create = {:create, table(:posts), - [{:add, :published_at, :time_usec, [precision: 3]}, - {:add, :submitted_at, :time_usec, []}]} + create = + {:create, table(:posts), + [{:add, :published_at, :time_usec, [precision: 3]}, {:add, :submitted_at, :time_usec, []}]} - assert execute_ddl(create) == [""" - CREATE TABLE `posts` - (`published_at` time(3), - `submitted_at` time(6)) - ENGINE = INNODB - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` + (`published_at` time(3), + `submitted_at` time(6)) + ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with utc_datetime columns" do - create = {:create, table(:posts), - [{:add, :published_at, :utc_datetime, [precision: 3]}, - {:add, :submitted_at, :utc_datetime, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE `posts` - (`published_at` datetime, - `submitted_at` datetime) - ENGINE = INNODB - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :published_at, :utc_datetime, [precision: 3]}, + {:add, :submitted_at, :utc_datetime, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` + (`published_at` datetime, + `submitted_at` datetime) + ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with utc_datetime_usec columns" do - create = {:create, table(:posts), - [{:add, :published_at, :utc_datetime_usec, [precision: 3]}, - {:add, :submitted_at, :utc_datetime_usec, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE `posts` - (`published_at` datetime(3), - `submitted_at` datetime(6)) - ENGINE = INNODB - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :published_at, :utc_datetime_usec, [precision: 3]}, + {:add, :submitted_at, :utc_datetime_usec, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` + (`published_at` datetime(3), + `submitted_at` datetime(6)) + ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with naive_datetime columns" do - create = {:create, table(:posts), - [{:add, :published_at, :naive_datetime, [precision: 3]}, - {:add, :submitted_at, :naive_datetime, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE `posts` - (`published_at` datetime, - `submitted_at` datetime) - ENGINE = INNODB - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :published_at, :naive_datetime, [precision: 3]}, + {:add, :submitted_at, :naive_datetime, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` + (`published_at` datetime, + `submitted_at` datetime) + ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with naive_datetime_usec columns" do - create = {:create, table(:posts), - [{:add, :published_at, :naive_datetime_usec, [precision: 3]}, - {:add, :submitted_at, :naive_datetime_usec, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE `posts` - (`published_at` datetime(3), - `submitted_at` datetime(6)) - ENGINE = INNODB - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :published_at, :naive_datetime_usec, [precision: 3]}, + {:add, :submitted_at, :naive_datetime_usec, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE `posts` + (`published_at` datetime(3), + `submitted_at` datetime(6)) + ENGINE = INNODB + """ + |> remove_newlines + ] end test "create table with an unsupported type" do - create = {:create, table(:posts), - [ - {:add, :a, {:a, :b, :c}, [default: %{}]} - ] - } + create = + {:create, table(:posts), + [ + {:add, :a, {:a, :b, :c}, [default: %{}]} + ]} + assert_raise ArgumentError, "unsupported type `{:a, :b, :c}`. " <> - "The type can either be an atom, a string or a tuple of the form " <> - "`{:map, t}` where `t` itself follows the same conditions.", + "The type can either be an atom, a string or a tuple of the form " <> + "`{:map, t}` where `t` itself follows the same conditions.", fn -> execute_ddl(create) end end @@ -1508,109 +1856,141 @@ defmodule Ecto.Adapters.MyXQLTest do 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}) + 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}) + execute_ddl( + {:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: :foo), + :restrict} + ) end end test "alter table" do - alter = {:alter, table(:posts), - [{:add, :title, :string, [default: "Untitled", size: 100, null: false]}, - {:add, :author_id, %Reference{table: :author}, [after: :title]}, - {:add_if_not_exists, :subtitle, :string, [size: 100, null: false]}, - {:add_if_not_exists, :editor_id, %Reference{table: :editor}, [after: :subtitle]}, - {:modify, :price, :numeric, [precision: 8, scale: 2, null: true]}, - {:modify, :cost, :integer, [null: false, default: nil]}, - {:modify, :permalink_id, %Reference{table: :permalinks}, null: false}, - {:modify, :status, :string, from: :integer}, - {:modify, :user_id, :integer, from: %Reference{table: :users}}, - {:modify, :space_id, :integer, null: true, from: {%Reference{table: :author}, null: false}}, - {:modify, :group_id, %Reference{table: :groups, column: :gid}, from: %Reference{table: :groups}}, - {:modify, :status, :string, [null: false, size: 100, from: {:integer, null: true, size: 50}]}, - {:remove, :summary}, - {:remove, :body, :text, []}, - {:remove, :space_id, %Reference{table: :author}, []}, - {:remove_if_exists, :body, :text}, - {:remove_if_exists, :space_id, %Reference{table: :author}}]} - - assert execute_ddl(alter) == [""" - ALTER TABLE `posts` ADD `title` varchar(100) DEFAULT 'Untitled' NOT NULL, - ADD `author_id` BIGINT UNSIGNED AFTER `title`, - ADD CONSTRAINT `posts_author_id_fkey` FOREIGN KEY (`author_id`) REFERENCES `author`(`id`), - ADD IF NOT EXISTS `subtitle` varchar(100) NOT NULL, - ADD IF NOT EXISTS `editor_id` BIGINT UNSIGNED AFTER `subtitle`, - ADD CONSTRAINT `posts_editor_id_fkey` FOREIGN KEY IF NOT EXISTS (`editor_id`) REFERENCES `editor`(`id`), - MODIFY `price` numeric(8,2) NULL, MODIFY `cost` integer DEFAULT NULL NOT NULL, - MODIFY `permalink_id` BIGINT UNSIGNED NOT NULL, - ADD CONSTRAINT `posts_permalink_id_fkey` FOREIGN KEY (`permalink_id`) REFERENCES `permalinks`(`id`), - MODIFY `status` varchar(255), - DROP FOREIGN KEY `posts_user_id_fkey`, - MODIFY `user_id` integer, - DROP FOREIGN KEY `posts_space_id_fkey`, - MODIFY `space_id` integer NULL, - DROP FOREIGN KEY `posts_group_id_fkey`, - MODIFY `group_id` BIGINT UNSIGNED, - ADD CONSTRAINT `posts_group_id_fkey` FOREIGN KEY (`group_id`) REFERENCES `groups`(`gid`), - MODIFY `status` varchar(100) NOT NULL, - DROP `summary`, - DROP `body`, - DROP FOREIGN KEY `posts_space_id_fkey`, - DROP `space_id`, - DROP IF EXISTS `body`, - DROP FOREIGN KEY IF EXISTS `posts_space_id_fkey`, - DROP IF EXISTS `space_id` - """ |> remove_newlines] + alter = + {:alter, table(:posts), + [ + {:add, :title, :string, [default: "Untitled", size: 100, null: false]}, + {:add, :author_id, %Reference{table: :author}, [after: :title]}, + {:add_if_not_exists, :subtitle, :string, [size: 100, null: false]}, + {:add_if_not_exists, :editor_id, %Reference{table: :editor}, [after: :subtitle]}, + {:modify, :price, :numeric, [precision: 8, scale: 2, null: true]}, + {:modify, :cost, :integer, [null: false, default: nil]}, + {:modify, :permalink_id, %Reference{table: :permalinks}, null: false}, + {:modify, :status, :string, from: :integer}, + {:modify, :user_id, :integer, from: %Reference{table: :users}}, + {:modify, :space_id, :integer, + null: true, from: {%Reference{table: :author}, null: false}}, + {:modify, :group_id, %Reference{table: :groups, column: :gid}, + from: %Reference{table: :groups}}, + {:modify, :status, :string, + [null: false, size: 100, from: {:integer, null: true, size: 50}]}, + {:remove, :summary}, + {:remove, :body, :text, []}, + {:remove, :space_id, %Reference{table: :author}, []}, + {:remove_if_exists, :body, :text}, + {:remove_if_exists, :space_id, %Reference{table: :author}} + ]} + + assert execute_ddl(alter) == [ + """ + ALTER TABLE `posts` ADD `title` varchar(100) DEFAULT 'Untitled' NOT NULL, + ADD `author_id` BIGINT UNSIGNED AFTER `title`, + ADD CONSTRAINT `posts_author_id_fkey` FOREIGN KEY (`author_id`) REFERENCES `author`(`id`), + ADD IF NOT EXISTS `subtitle` varchar(100) NOT NULL, + ADD IF NOT EXISTS `editor_id` BIGINT UNSIGNED AFTER `subtitle`, + ADD CONSTRAINT `posts_editor_id_fkey` FOREIGN KEY IF NOT EXISTS (`editor_id`) REFERENCES `editor`(`id`), + MODIFY `price` numeric(8,2) NULL, MODIFY `cost` integer DEFAULT NULL NOT NULL, + MODIFY `permalink_id` BIGINT UNSIGNED NOT NULL, + ADD CONSTRAINT `posts_permalink_id_fkey` FOREIGN KEY (`permalink_id`) REFERENCES `permalinks`(`id`), + MODIFY `status` varchar(255), + DROP FOREIGN KEY `posts_user_id_fkey`, + MODIFY `user_id` integer, + DROP FOREIGN KEY `posts_space_id_fkey`, + MODIFY `space_id` integer NULL, + DROP FOREIGN KEY `posts_group_id_fkey`, + MODIFY `group_id` BIGINT UNSIGNED, + ADD CONSTRAINT `posts_group_id_fkey` FOREIGN KEY (`group_id`) REFERENCES `groups`(`gid`), + MODIFY `status` varchar(100) NOT NULL, + DROP `summary`, + DROP `body`, + DROP FOREIGN KEY `posts_space_id_fkey`, + DROP `space_id`, + DROP IF EXISTS `body`, + DROP FOREIGN KEY IF EXISTS `posts_space_id_fkey`, + DROP IF EXISTS `space_id` + """ + |> remove_newlines + ] end test "alter table with comments on table and columns" do - alter = {:alter, table(:posts, comment: "table comment"), - [{:add, :title, :string, [default: "Untitled", size: 100, null: false, comment: "column comment"]}, - {:modify, :price, :numeric, [precision: 8, scale: 2, null: true]}, - {:modify, :permalink_id, %Reference{table: :permalinks}, [null: false, comment: "column comment 2"]}, - {:remove, :summary}]} - - assert execute_ddl(alter) == [""" - ALTER TABLE `posts` - ADD `title` varchar(100) DEFAULT 'Untitled' NOT NULL COMMENT 'column comment', - MODIFY `price` numeric(8,2) NULL, - MODIFY `permalink_id` BIGINT UNSIGNED NOT NULL COMMENT 'column comment 2', - ADD CONSTRAINT `posts_permalink_id_fkey` FOREIGN KEY (`permalink_id`) REFERENCES `permalinks`(`id`), - DROP `summary` - """ |> remove_newlines, - ~s|ALTER TABLE `posts` COMMENT 'table comment'|] + alter = + {:alter, table(:posts, comment: "table comment"), + [ + {:add, :title, :string, + [default: "Untitled", size: 100, null: false, comment: "column comment"]}, + {:modify, :price, :numeric, [precision: 8, scale: 2, null: true]}, + {:modify, :permalink_id, %Reference{table: :permalinks}, + [null: false, comment: "column comment 2"]}, + {:remove, :summary} + ]} + + assert execute_ddl(alter) == [ + """ + ALTER TABLE `posts` + ADD `title` varchar(100) DEFAULT 'Untitled' NOT NULL COMMENT 'column comment', + MODIFY `price` numeric(8,2) NULL, + MODIFY `permalink_id` BIGINT UNSIGNED NOT NULL COMMENT 'column comment 2', + ADD CONSTRAINT `posts_permalink_id_fkey` FOREIGN KEY (`permalink_id`) REFERENCES `permalinks`(`id`), + DROP `summary` + """ + |> remove_newlines, + ~s|ALTER TABLE `posts` COMMENT 'table comment'| + ] end test "alter table with prefix" do - alter = {:alter, table(:posts, prefix: :foo), - [{:add, :author_id, %Reference{table: :author}, []}, - {:modify, :permalink_id, %Reference{table: :permalinks}, null: false}]} - - assert execute_ddl(alter) == [""" - ALTER TABLE `foo`.`posts` ADD `author_id` BIGINT UNSIGNED, - ADD CONSTRAINT `posts_author_id_fkey` FOREIGN KEY (`author_id`) REFERENCES `foo`.`author`(`id`), - MODIFY `permalink_id` BIGINT UNSIGNED NOT NULL, - ADD CONSTRAINT `posts_permalink_id_fkey` FOREIGN KEY (`permalink_id`) REFERENCES `foo`.`permalinks`(`id`) - """ |> remove_newlines] + alter = + {:alter, table(:posts, prefix: :foo), + [ + {:add, :author_id, %Reference{table: :author}, []}, + {:modify, :permalink_id, %Reference{table: :permalinks}, null: false} + ]} + + assert execute_ddl(alter) == [ + """ + ALTER TABLE `foo`.`posts` ADD `author_id` BIGINT UNSIGNED, + ADD CONSTRAINT `posts_author_id_fkey` FOREIGN KEY (`author_id`) REFERENCES `foo`.`author`(`id`), + MODIFY `permalink_id` BIGINT UNSIGNED NOT NULL, + ADD CONSTRAINT `posts_permalink_id_fkey` FOREIGN KEY (`permalink_id`) REFERENCES `foo`.`permalinks`(`id`) + """ + |> remove_newlines + ] end test "alter table with primary key" do - alter = {:alter, table(:posts), - [{:add, :my_pk, :serial, [primary_key: true]}]} + alter = {:alter, table(:posts), [{:add, :my_pk, :serial, [primary_key: true]}]} - assert execute_ddl(alter) == [""" - ALTER TABLE `posts` - ADD `my_pk` bigint unsigned not null auto_increment, - ADD PRIMARY KEY (`my_pk`) - """ |> remove_newlines] + assert execute_ddl(alter) == [ + """ + ALTER TABLE `posts` + ADD `my_pk` bigint unsigned not null auto_increment, + ADD PRIMARY KEY (`my_pk`) + """ + |> remove_newlines + ] end test "alter table with invalid reference opts" do - alter = {:alter, table(:posts), [{:add, :author_id, %Reference{table: :author, validate: false}, []}]} + 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) @@ -1619,24 +1999,32 @@ defmodule Ecto.Adapters.MyXQLTest do test "create index" do create = {:create, index(:posts, [:category_id, :permalink])} + assert execute_ddl(create) == - [~s|CREATE INDEX `posts_category_id_permalink_index` ON `posts` (`category_id`, `permalink`)|] + [ + ~s|CREATE INDEX `posts_category_id_permalink_index` ON `posts` (`category_id`, `permalink`)| + ] create = {:create, index(:posts, ["permalink(8)"], name: "posts$main")} + assert execute_ddl(create) == - [~s|CREATE INDEX `posts$main` ON `posts` (permalink(8))|] + [~s|CREATE INDEX `posts$main` ON `posts` (permalink(8))|] end test "create index with prefix" do create = {:create, index(:posts, [:category_id, :permalink], prefix: :foo)} + assert execute_ddl(create) == - [~s|CREATE INDEX `posts_category_id_permalink_index` ON `foo`.`posts` (`category_id`, `permalink`)|] + [ + ~s|CREATE INDEX `posts_category_id_permalink_index` ON `foo`.`posts` (`category_id`, `permalink`)| + ] end test "create unique index" do create = {:create, index(:posts, [:permalink], unique: true)} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX `posts_permalink_index` ON `posts` (`permalink`)|] + [~s|CREATE UNIQUE INDEX `posts_permalink_index` ON `posts` (`permalink`)|] end test "create unique index with condition" do @@ -1670,8 +2058,9 @@ defmodule Ecto.Adapters.MyXQLTest do test "create an index using a different type" do create = {:create, index(:posts, [:permalink], using: :hash)} + assert execute_ddl(create) == - [~s|CREATE INDEX `posts_permalink_index` ON `posts` (`permalink`) USING hash|] + [~s|CREATE INDEX `posts_permalink_index` ON `posts` (`permalink`) USING hash|] end test "drop index" do @@ -1686,7 +2075,10 @@ defmodule Ecto.Adapters.MyXQLTest do test "rename index" do rename = {:rename, index(:people, [:name], name: "persons_name_index"), "people_name_index"} - assert execute_ddl(rename) == [~s|ALTER TABLE `people` RENAME INDEX `persons_name_index` TO `people_name_index`|] + + assert execute_ddl(rename) == [ + ~s|ALTER TABLE `people` RENAME INDEX `persons_name_index` TO `people_name_index`| + ] end test "rename table" do @@ -1701,12 +2093,18 @@ defmodule Ecto.Adapters.MyXQLTest do test "rename column" do rename = {:rename, table(:posts), :given_name, :first_name} - assert execute_ddl(rename) == [~s|ALTER TABLE `posts` RENAME COLUMN `given_name` TO `first_name`|] + + assert execute_ddl(rename) == [ + ~s|ALTER TABLE `posts` RENAME COLUMN `given_name` TO `first_name`| + ] end test "rename column in prefixed table" do rename = {:rename, table(:posts, prefix: :foo), :given_name, :first_name} - assert execute_ddl(rename) == [~s|ALTER TABLE `foo`.`posts` RENAME COLUMN `given_name` TO `first_name`|] + + assert execute_ddl(rename) == [ + ~s|ALTER TABLE `foo`.`posts` RENAME COLUMN `given_name` TO `first_name`| + ] end # Unsupported types and clauses @@ -1719,6 +2117,6 @@ defmodule Ecto.Adapters.MyXQLTest do end defp remove_newlines(string) do - string |> String.trim |> String.replace("\n", " ") + string |> String.trim() |> String.replace("\n", " ") end end diff --git a/test/ecto/adapters/postgres_test.exs b/test/ecto/adapters/postgres_test.exs index 592965e9..1f5ff2bd 100644 --- a/test/ecto/adapters/postgres_test.exs +++ b/test/ecto/adapters/postgres_test.exs @@ -19,6 +19,7 @@ defmodule Ecto.Adapters.PostgresTest do has_many :comments, Ecto.Adapters.PostgresTest.Schema2, references: :x, foreign_key: :z + has_one :permalink, Ecto.Adapters.PostgresTest.Schema3, references: :y, foreign_key: :id @@ -46,14 +47,16 @@ defmodule Ecto.Adapters.PostgresTest do end defp plan(query, operation \\ :all) do - {query, _cast_params, _dump_params} = Ecto.Adapter.Queryable.plan_query(operation, Ecto.Adapters.Postgres, query) + {query, _cast_params, _dump_params} = + Ecto.Adapter.Queryable.plan_query(operation, Ecto.Adapters.Postgres, query) + query end - defp all(query), do: query |> SQL.all |> IO.iodata_to_binary() - defp update_all(query), do: query |> SQL.update_all |> IO.iodata_to_binary() - defp delete_all(query), do: query |> SQL.delete_all |> IO.iodata_to_binary() - defp execute_ddl(query), do: query |> SQL.execute_ddl |> Enum.map(&IO.iodata_to_binary/1) + defp all(query), do: query |> SQL.all() |> IO.iodata_to_binary() + defp update_all(query), do: query |> SQL.update_all() |> IO.iodata_to_binary() + defp delete_all(query), do: query |> SQL.delete_all() |> IO.iodata_to_binary() + defp execute_ddl(query), do: query |> SQL.execute_ddl() |> Enum.map(&IO.iodata_to_binary/1) defp insert(prefix, table, header, rows, on_conflict, returning, placeholders \\ []) do IO.iodata_to_binary( @@ -107,20 +110,31 @@ defmodule Ecto.Adapters.PostgresTest do query = "0posts" |> select([:x]) |> plan() assert all(query) == ~s{SELECT t0."x" FROM "0posts" AS t0} - assert_raise Ecto.QueryError, ~r"PostgreSQL adapter does not support selecting all fields from \"posts\" without a schema", fn -> - all from(p in "posts", select: p) |> plan() - end + assert_raise Ecto.QueryError, + ~r"PostgreSQL adapter does not support selecting all fields from \"posts\" without a schema", + fn -> + all(from(p in "posts", select: p) |> plan()) + end end test "from with subquery" do query = subquery("posts" |> select([r], %{x: r.x, y: r.y})) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0."x" FROM (SELECT sp0."x" AS "x", sp0."y" AS "y" FROM "posts" AS sp0) AS s0} + + assert all(query) == + ~s{SELECT s0."x" FROM (SELECT sp0."x" AS "x", sp0."y" AS "y" FROM "posts" AS sp0) AS s0} query = subquery("posts" |> select([r], %{x: r.x, z: r.y})) |> select([r], r) |> plan() - assert all(query) == ~s{SELECT s0."x", s0."z" FROM (SELECT sp0."x" AS "x", sp0."y" AS "z" FROM "posts" AS sp0) AS s0} - query = subquery(subquery("posts" |> select([r], %{x: r.x, z: r.y})) |> select([r], r)) |> select([r], r) |> plan() - assert all(query) == ~s{SELECT s0."x", s0."z" FROM (SELECT ss0."x" AS "x", ss0."z" AS "z" FROM (SELECT ssp0."x" AS "x", ssp0."y" AS "z" FROM "posts" AS ssp0) AS ss0) AS s0} + assert all(query) == + ~s{SELECT s0."x", s0."z" FROM (SELECT sp0."x" AS "x", sp0."y" AS "z" FROM "posts" AS sp0) AS s0} + + query = + subquery(subquery("posts" |> select([r], %{x: r.x, z: r.y})) |> select([r], r)) + |> select([r], r) + |> plan() + + assert all(query) == + ~s{SELECT s0."x", s0."z" FROM (SELECT ss0."x" AS "x", ss0."z" AS "z" FROM (SELECT ssp0."x" AS "x", ssp0."y" AS "z" FROM "posts" AS ssp0) AS ss0) AS s0} end test "from with fragment" do @@ -133,9 +147,11 @@ defmodule Ecto.Adapters.PostgresTest do query = from(f in fragment("select_rows(arg)"), select: f.x) |> plan() assert all(query) == ~s{SELECT f0."x" FROM select_rows(arg) AS f0} - assert_raise Ecto.QueryError, ~r"PostgreSQL adapter does not support selecting all fields from fragment", fn -> - all from(f in fragment("select ? as x", ^"abc"), select: f) |> plan() - end + assert_raise Ecto.QueryError, + ~r"PostgreSQL adapter does not support selecting all fields from fragment", + fn -> + all(from(f in fragment("select ? as x", ^"abc"), select: f) |> plan()) + end end test "CTE" do @@ -160,14 +176,14 @@ defmodule Ecto.Adapters.PostgresTest do |> plan() assert all(query) == - ~s{WITH RECURSIVE "tree" AS } <> - ~s{(SELECT sc0."id" AS "id", 1 AS "depth" FROM "categories" AS sc0 WHERE (sc0."parent_id" IS NULL) } <> - ~s{UNION ALL } <> - ~s{(SELECT sc0."id", st1."depth" + 1 FROM "categories" AS sc0 } <> - ~s{INNER JOIN "tree" AS st1 ON st1."id" = sc0."parent_id")) } <> - ~s{SELECT s0."x", t1."id", t1."depth"::bigint } <> - ~s{FROM "schema" AS s0 } <> - ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} + ~s{WITH RECURSIVE "tree" AS } <> + ~s{(SELECT sc0."id" AS "id", 1 AS "depth" FROM "categories" AS sc0 WHERE (sc0."parent_id" IS NULL) } <> + ~s{UNION ALL } <> + ~s{(SELECT sc0."id", st1."depth" + 1 FROM "categories" AS sc0 } <> + ~s{INNER JOIN "tree" AS st1 ON st1."id" = sc0."parent_id")) } <> + ~s{SELECT s0."x", t1."id", t1."depth"::bigint } <> + ~s{FROM "schema" AS s0 } <> + ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} end test "materialized CTE" do @@ -192,14 +208,14 @@ defmodule Ecto.Adapters.PostgresTest do |> plan() assert all(query) == - ~s{WITH RECURSIVE "tree" AS MATERIALIZED} <> - ~s{(SELECT sc0."id" AS "id", 1 AS "depth" FROM "categories" AS sc0 WHERE (sc0."parent_id" IS NULL) } <> - ~s{UNION ALL } <> - ~s{(SELECT sc0."id", st1."depth" + 1 FROM "categories" AS sc0 } <> - ~s{INNER JOIN "tree" AS st1 ON st1."id" = sc0."parent_id")) } <> - ~s{SELECT s0."x", t1."id", t1."depth"::bigint } <> - ~s{FROM "schema" AS s0 } <> - ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} + ~s{WITH RECURSIVE "tree" AS MATERIALIZED} <> + ~s{(SELECT sc0."id" AS "id", 1 AS "depth" FROM "categories" AS sc0 WHERE (sc0."parent_id" IS NULL) } <> + ~s{UNION ALL } <> + ~s{(SELECT sc0."id", st1."depth" + 1 FROM "categories" AS sc0 } <> + ~s{INNER JOIN "tree" AS st1 ON st1."id" = sc0."parent_id")) } <> + ~s{SELECT s0."x", t1."id", t1."depth"::bigint } <> + ~s{FROM "schema" AS s0 } <> + ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} end test "not materialized CTE" do @@ -224,14 +240,14 @@ defmodule Ecto.Adapters.PostgresTest do |> plan() assert all(query) == - ~s{WITH RECURSIVE "tree" AS NOT MATERIALIZED} <> - ~s{(SELECT sc0."id" AS "id", 1 AS "depth" FROM "categories" AS sc0 WHERE (sc0."parent_id" IS NULL) } <> - ~s{UNION ALL } <> - ~s{(SELECT sc0."id", st1."depth" + 1 FROM "categories" AS sc0 } <> - ~s{INNER JOIN "tree" AS st1 ON st1."id" = sc0."parent_id")) } <> - ~s{SELECT s0."x", t1."id", t1."depth"::bigint } <> - ~s{FROM "schema" AS s0 } <> - ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} + ~s{WITH RECURSIVE "tree" AS NOT MATERIALIZED} <> + ~s{(SELECT sc0."id" AS "id", 1 AS "depth" FROM "categories" AS sc0 WHERE (sc0."parent_id" IS NULL) } <> + ~s{UNION ALL } <> + ~s{(SELECT sc0."id", st1."depth" + 1 FROM "categories" AS sc0 } <> + ~s{INNER JOIN "tree" AS st1 ON st1."id" = sc0."parent_id")) } <> + ~s{SELECT s0."x", t1."id", t1."depth"::bigint } <> + ~s{FROM "schema" AS s0 } <> + ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} end @raw_sql_cte """ @@ -263,16 +279,16 @@ defmodule Ecto.Adapters.PostgresTest do |> plan() assert all(query) == - ~s{WITH "comments_scope" AS (} <> - ~s{SELECT sc0."entity_id" AS "entity_id", sc0."text" AS "text" } <> - ~s{FROM "comments" AS sc0 WHERE (sc0."deleted_at" IS NULL)) } <> - ~s{SELECT p0."title", c1."text" } <> - ~s{FROM "posts" AS p0 } <> - ~s{INNER JOIN "comments_scope" AS c1 ON c1."entity_id" = p0."guid" } <> - ~s{UNION ALL } <> - ~s{(SELECT v0."title", c1."text" } <> - ~s{FROM "videos" AS v0 } <> - ~s{INNER JOIN "comments_scope" AS c1 ON c1."entity_id" = v0."guid")} + ~s{WITH "comments_scope" AS (} <> + ~s{SELECT sc0."entity_id" AS "entity_id", sc0."text" AS "text" } <> + ~s{FROM "comments" AS sc0 WHERE (sc0."deleted_at" IS NULL)) } <> + ~s{SELECT p0."title", c1."text" } <> + ~s{FROM "posts" AS p0 } <> + ~s{INNER JOIN "comments_scope" AS c1 ON c1."entity_id" = p0."guid" } <> + ~s{UNION ALL } <> + ~s{(SELECT v0."title", c1."text" } <> + ~s{FROM "videos" AS v0 } <> + ~s{INNER JOIN "comments_scope" AS c1 ON c1."entity_id" = v0."guid")} end test "fragment CTE" do @@ -285,15 +301,20 @@ defmodule Ecto.Adapters.PostgresTest do |> plan() assert all(query) == - ~s{WITH RECURSIVE "tree" AS (#{@raw_sql_cte}) } <> - ~s{SELECT s0."x" } <> - ~s{FROM "schema" AS s0 } <> - ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} + ~s{WITH RECURSIVE "tree" AS (#{@raw_sql_cte}) } <> + ~s{SELECT s0."x" } <> + ~s{FROM "schema" AS s0 } <> + ~s{INNER JOIN "tree" AS t1 ON t1."id" = s0."category_id"} end test "CTE update_all" do cte_query = - from(x in Schema, order_by: [asc: :id], limit: 10, lock: "FOR UPDATE SKIP LOCKED", select: %{id: x.id}) + from(x in Schema, + order_by: [asc: :id], + limit: 10, + lock: "FOR UPDATE SKIP LOCKED", + select: %{id: x.id} + ) query = Schema @@ -304,18 +325,23 @@ defmodule Ecto.Adapters.PostgresTest do |> plan(:update_all) assert update_all(query) == - ~s{WITH "target_rows" AS } <> - ~s{(SELECT ss0."id" AS "id" FROM "schema" AS ss0 ORDER BY ss0."id" LIMIT 10 FOR UPDATE SKIP LOCKED) } <> - ~s{UPDATE "schema" AS s0 } <> - ~s{SET "x" = 123 } <> - ~s{FROM "target_rows" AS t1 } <> - ~s{WHERE (t1."id" = s0."id") } <> - ~s{RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} + ~s{WITH "target_rows" AS } <> + ~s{(SELECT ss0."id" AS "id" FROM "schema" AS ss0 ORDER BY ss0."id" LIMIT 10 FOR UPDATE SKIP LOCKED) } <> + ~s{UPDATE "schema" AS s0 } <> + ~s{SET "x" = 123 } <> + ~s{FROM "target_rows" AS t1 } <> + ~s{WHERE (t1."id" = s0."id") } <> + ~s{RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} end test "CTE delete_all" do cte_query = - from(x in Schema, order_by: [asc: :id], limit: 10, lock: "FOR UPDATE SKIP LOCKED", select: %{id: x.id}) + from(x in Schema, + order_by: [asc: :id], + limit: 10, + lock: "FOR UPDATE SKIP LOCKED", + select: %{id: x.id} + ) query = Schema @@ -325,12 +351,12 @@ defmodule Ecto.Adapters.PostgresTest do |> plan(:delete_all) assert delete_all(query) == - ~s{WITH "target_rows" AS } <> - ~s{(SELECT ss0."id" AS "id" FROM "schema" AS ss0 ORDER BY ss0."id" LIMIT 10 FOR UPDATE SKIP LOCKED) } <> - ~s{DELETE FROM "schema" AS s0 } <> - ~s{USING "target_rows" AS t1 } <> - ~s{WHERE (t1."id" = s0."id") } <> - ~s{RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} + ~s{WITH "target_rows" AS } <> + ~s{(SELECT ss0."id" AS "id" FROM "schema" AS ss0 ORDER BY ss0."id" LIMIT 10 FOR UPDATE SKIP LOCKED) } <> + ~s{DELETE FROM "schema" AS s0 } <> + ~s{USING "target_rows" AS t1 } <> + ~s{WHERE (t1."id" = s0."id") } <> + ~s{RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} end test "parent binding subquery and CTE" do @@ -362,65 +388,68 @@ defmodule Ecto.Adapters.PostgresTest do |> plan() assert all(query) == - ~s{SELECT c0."id", s1."breadcrumbs" FROM "categories" AS c0 } <> - ~s{LEFT OUTER JOIN LATERAL } <> - ~s{(WITH RECURSIVE "tree" AS } <> - ~s{(SELECT ssc0."id" AS "id", ssc0."parent_id" AS "parent_id" FROM "categories" AS ssc0 WHERE (ssc0."id" = c0."id") } <> - ~s{UNION ALL } <> - ~s{(SELECT ssc0."id", ssc0."parent_id" FROM "categories" AS ssc0 } <> - ~s{INNER JOIN "tree" AS sst1 ON sst1."parent_id" = ssc0."id")) } <> - ~s{SELECT STRING_AGG(st0."id", ' / ') AS "breadcrumbs" FROM "tree" AS st0) AS s1 ON TRUE} + ~s{SELECT c0."id", s1."breadcrumbs" FROM "categories" AS c0 } <> + ~s{LEFT OUTER JOIN LATERAL } <> + ~s{(WITH RECURSIVE "tree" AS } <> + ~s{(SELECT ssc0."id" AS "id", ssc0."parent_id" AS "parent_id" FROM "categories" AS ssc0 WHERE (ssc0."id" = c0."id") } <> + ~s{UNION ALL } <> + ~s{(SELECT ssc0."id", ssc0."parent_id" FROM "categories" AS ssc0 } <> + ~s{INNER JOIN "tree" AS sst1 ON sst1."parent_id" = ssc0."id")) } <> + ~s{SELECT STRING_AGG(st0."id", ' / ') AS "breadcrumbs" FROM "tree" AS st0) AS s1 ON TRUE} end test "parent binding subquery and combination" do right_query = from(c in "right_categories", where: c.id == parent_as(:c).id, select: c.id) left_query = from(c in "left_categories", where: c.id == parent_as(:c).id, select: c.id) union_query = union(left_query, ^right_query) - query = from(c in "categories", as: :c, where: c.id in subquery(union_query), select: c.id) |> plan() + + query = + from(c in "categories", as: :c, where: c.id in subquery(union_query), select: c.id) + |> plan() assert all(query) == - ~s{SELECT c0."id" FROM "categories" AS c0 } <> - ~s{WHERE (} <> - ~s{c0."id" IN } <> - ~s{(SELECT sl0."id" FROM "left_categories" AS sl0 WHERE (sl0."id" = c0."id") } <> - ~s{UNION } <> - ~s{(SELECT sr0."id" FROM "right_categories" AS sr0 WHERE (sr0."id" = c0."id"))))} + ~s{SELECT c0."id" FROM "categories" AS c0 } <> + ~s{WHERE (} <> + ~s{c0."id" IN } <> + ~s{(SELECT sl0."id" FROM "left_categories" AS sl0 WHERE (sl0."id" = c0."id") } <> + ~s{UNION } <> + ~s{(SELECT sr0."id" FROM "right_categories" AS sr0 WHERE (sr0."id" = c0."id"))))} end test "CTE with update statement" do - cte_query = - "categories" - |> where([c], is_nil(c.parent_id)) - |> update([c], set: [desc: "Root category"]) - |> select([c], %{id: c.id, desc: c.desc}) + cte_query = + "categories" + |> where([c], is_nil(c.parent_id)) + |> update([c], set: [desc: "Root category"]) + |> select([c], %{id: c.id, desc: c.desc}) - query = - "update_categories" - |> with_cte("search_categories", as: ^cte_query) - |> with_cte("delete_categories", as: ^cte_query, operation: :delete_all) - |> with_cte("update_categories", as: ^cte_query, operation: :update_all) - |> select([c], %{id: c.id, desc: c.desc}) - |> plan() + query = + "update_categories" + |> with_cte("search_categories", as: ^cte_query) + |> with_cte("delete_categories", as: ^cte_query, operation: :delete_all) + |> with_cte("update_categories", as: ^cte_query, operation: :update_all) + |> select([c], %{id: c.id, desc: c.desc}) + |> plan() - assert all(query) == - ~s{WITH "search_categories" AS } <> - ~s{(} <> - ~s{SELECT sc0."id" AS "id", sc0."desc" AS "desc" } <> - ~s{FROM "categories" AS sc0 } <> - ~s{WHERE (sc0."parent_id" IS NULL)} <> - ~s{), } <> - ~s{"delete_categories" AS (} <> - ~s{DELETE FROM "categories" AS c0 } <> - ~s{WHERE (c0."parent_id" IS NULL) } <> - ~s{RETURNING c0."id" AS "id", c0."desc" AS "desc"} <> - ~s{), } <> - ~s{"update_categories" AS (} <> - ~s{UPDATE "categories" AS c0 SET "desc" = 'Root category' } <> - ~s{WHERE (c0."parent_id" IS NULL) } <> - ~s{RETURNING c0."id" AS "id", c0."desc" AS "desc"} <> - ~s{) } <> - ~s{SELECT u0."id", u0."desc" } <> - ~s{FROM "update_categories" AS u0} + assert all(query) == + ~s{WITH "search_categories" AS } <> + ~s{(} <> + ~s{SELECT sc0."id" AS "id", sc0."desc" AS "desc" } <> + ~s{FROM "categories" AS sc0 } <> + ~s{WHERE (sc0."parent_id" IS NULL)} <> + ~s{), } <> + ~s{"delete_categories" AS (} <> + ~s{DELETE FROM "categories" AS c0 } <> + ~s{WHERE (c0."parent_id" IS NULL) } <> + ~s{RETURNING c0."id" AS "id", c0."desc" AS "desc"} <> + ~s{), } <> + ~s{"update_categories" AS (} <> + ~s{UPDATE "categories" AS c0 SET "desc" = 'Root category' } <> + ~s{WHERE (c0."parent_id" IS NULL) } <> + ~s{RETURNING c0."id" AS "id", c0."desc" AS "desc"} <> + ~s{) } <> + ~s{SELECT u0."id", u0."desc" } <> + ~s{FROM "update_categories" AS u0} end test "select" do @@ -450,7 +479,9 @@ defmodule Ecto.Adapters.PostgresTest do assert all(query) == ~s{SELECT count(s0."x") FILTER (WHERE s0."x" > 10) FROM "schema" AS s0} query = Schema |> select([r], count(r.x) |> filter(r.x > 10 and r.x < 50)) |> plan() - assert all(query) == ~s{SELECT count(s0."x") FILTER (WHERE (s0."x" > 10) AND (s0."x" < 50)) FROM "schema" AS s0} + + assert all(query) == + ~s{SELECT count(s0."x") FILTER (WHERE (s0."x" > 10) AND (s0."x" < 50)) FROM "schema" AS s0} query = Schema |> select([r], count() |> filter(r.x > 10)) |> plan() assert all(query) == ~s{SELECT count(*) FILTER (WHERE s0."x" > 10) FROM "schema" AS s0} @@ -467,13 +498,23 @@ defmodule Ecto.Adapters.PostgresTest do assert all(query) == ~s{SELECT DISTINCT ON (2) s0."x" FROM "schema" AS s0} query = Schema |> distinct([r], [r.x, r.y]) |> select([r], {r.x, r.y}) |> plan() - assert all(query) == ~s{SELECT DISTINCT ON (s0."x", s0."y") s0."x", s0."y" FROM "schema" AS s0} - query = Schema |> distinct([r], [asc: r.x, desc: r.y]) |> select([r], {r.x, r.y}) |> plan() - assert all(query) == ~s{SELECT DISTINCT ON (s0."x", s0."y") s0."x", s0."y" FROM "schema" AS s0} + assert all(query) == + ~s{SELECT DISTINCT ON (s0."x", s0."y") s0."x", s0."y" FROM "schema" AS s0} + + query = Schema |> distinct([r], asc: r.x, desc: r.y) |> select([r], {r.x, r.y}) |> plan() + + assert all(query) == + ~s{SELECT DISTINCT ON (s0."x", s0."y") s0."x", s0."y" FROM "schema" AS s0} + + query = + Schema + |> distinct([r], asc_nulls_first: r.x, desc_nulls_last: r.y) + |> select([r], {r.x, r.y}) + |> plan() - query = Schema |> distinct([r], [asc_nulls_first: r.x, desc_nulls_last: r.y]) |> select([r], {r.x, r.y}) |> plan() - assert all(query) == ~s{SELECT DISTINCT ON (s0."x", s0."y") s0."x", s0."y" FROM "schema" AS s0} + assert all(query) == + ~s{SELECT DISTINCT ON (s0."x", s0."y") s0."x", s0."y" FROM "schema" AS s0} query = Schema |> distinct([r], true) |> select([r], {r.x, r.y}) |> plan() assert all(query) == ~s{SELECT DISTINCT s0."x", s0."y" FROM "schema" AS s0} @@ -489,15 +530,28 @@ defmodule Ecto.Adapters.PostgresTest do end test "distinct with order by" do - query = Schema |> order_by([r], [r.y]) |> distinct([r], desc: r.x) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT DISTINCT ON (s0."x") s0."x" FROM "schema" AS s0 ORDER BY s0."x" DESC, s0."y"} + query = + Schema |> order_by([r], [r.y]) |> distinct([r], desc: r.x) |> select([r], r.x) |> plan() - query = Schema |> order_by([r], [r.y]) |> distinct([r], desc_nulls_last: r.x) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT DISTINCT ON (s0."x") s0."x" FROM "schema" AS s0 ORDER BY s0."x" DESC NULLS LAST, s0."y"} + assert all(query) == + ~s{SELECT DISTINCT ON (s0."x") s0."x" FROM "schema" AS s0 ORDER BY s0."x" DESC, s0."y"} + + query = + Schema + |> order_by([r], [r.y]) + |> distinct([r], desc_nulls_last: r.x) + |> select([r], r.x) + |> plan() + + assert all(query) == + ~s{SELECT DISTINCT ON (s0."x") s0."x" FROM "schema" AS s0 ORDER BY s0."x" DESC NULLS LAST, s0."y"} # Duplicates - query = Schema |> order_by([r], desc: r.x) |> distinct([r], desc: r.x) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT DISTINCT ON (s0."x") s0."x" FROM "schema" AS s0 ORDER BY s0."x" DESC} + query = + Schema |> order_by([r], desc: r.x) |> distinct([r], desc: r.x) |> select([r], r.x) |> plan() + + assert all(query) == + ~s{SELECT DISTINCT ON (s0."x") s0."x" FROM "schema" AS s0 ORDER BY s0."x" DESC} assert Schema |> order_by([r], desc: r.x) @@ -515,18 +569,31 @@ defmodule Ecto.Adapters.PostgresTest do test "where" do query = Schema |> where([r], r.x == 42) |> where([r], r.y != 43) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WHERE (s0."x" = 42) AND (s0."y" != 43)} + + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 WHERE (s0."x" = 42) AND (s0."y" != 43)} query = Schema |> where([r], {r.x, r.y} > {1, 2}) |> select([r], r.x) |> plan() assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WHERE ((s0."x",s0."y") > (1,2))} end test "or_where" do - query = Schema |> or_where([r], r.x == 42) |> or_where([r], r.y != 43) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WHERE (s0."x" = 42) OR (s0."y" != 43)} + query = + Schema |> or_where([r], r.x == 42) |> or_where([r], r.y != 43) |> select([r], r.x) |> plan() + + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 WHERE (s0."x" = 42) OR (s0."y" != 43)} + + query = + Schema + |> or_where([r], r.x == 42) + |> or_where([r], r.y != 43) + |> where([r], r.z == 44) + |> select([r], r.x) + |> plan() - query = Schema |> or_where([r], r.x == 42) |> or_where([r], r.y != 43) |> where([r], r.z == 44) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WHERE ((s0."x" = 42) OR (s0."y" != 43)) AND (s0."z" = 44)} + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 WHERE ((s0."x" = 42) OR (s0."y" != 43)) AND (s0."z" = 44)} end test "order by" do @@ -536,21 +603,35 @@ defmodule Ecto.Adapters.PostgresTest do query = Schema |> order_by([r], [r.x, r.y]) |> select([r], r.x) |> plan() assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 ORDER BY s0."x", s0."y"} - query = Schema |> order_by([r], [asc: r.x, desc: r.y]) |> select([r], r.x) |> plan() + query = Schema |> order_by([r], asc: r.x, desc: r.y) |> select([r], r.x) |> plan() assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 ORDER BY s0."x", s0."y" DESC} - query = Schema |> order_by([r], [asc_nulls_first: r.x, desc_nulls_first: r.y]) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 ORDER BY s0."x" ASC NULLS FIRST, s0."y" DESC NULLS FIRST} + query = + Schema + |> order_by([r], asc_nulls_first: r.x, desc_nulls_first: r.y) + |> select([r], r.x) + |> plan() + + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 ORDER BY s0."x" ASC NULLS FIRST, s0."y" DESC NULLS FIRST} + + query = + Schema + |> order_by([r], asc_nulls_last: r.x, desc_nulls_last: r.y) + |> select([r], r.x) + |> plan() - query = Schema |> order_by([r], [asc_nulls_last: r.x, desc_nulls_last: r.y]) |> select([r], r.x) |> plan() - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 ORDER BY s0."x" ASC NULLS LAST, s0."y" DESC NULLS LAST} + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 ORDER BY s0."x" ASC NULLS LAST, s0."y" DESC NULLS LAST} query = Schema |> order_by([r], []) |> select([r], r.x) |> plan() assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0} end test "union and union all" do - base_query = Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + base_query = + Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + union_query1 = Schema |> select([r], r.y) |> order_by([r], r.y) |> offset(20) |> limit(40) union_query2 = Schema |> select([r], r.z) |> order_by([r], r.z) |> offset(30) |> limit(60) @@ -572,7 +653,9 @@ defmodule Ecto.Adapters.PostgresTest do end test "except and except all" do - base_query = Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + base_query = + Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + except_query1 = Schema |> select([r], r.y) |> order_by([r], r.y) |> offset(20) |> limit(40) except_query2 = Schema |> select([r], r.z) |> order_by([r], r.z) |> offset(30) |> limit(60) @@ -594,7 +677,9 @@ defmodule Ecto.Adapters.PostgresTest do end test "intersect and intersect all" do - base_query = Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + base_query = + Schema |> select([r], r.x) |> order_by(fragment("rand")) |> offset(10) |> limit(5) + intersect_query1 = Schema |> select([r], r.y) |> order_by([r], r.y) |> offset(20) |> limit(40) intersect_query2 = Schema |> select([r], r.z) |> order_by([r], r.z) |> offset(30) |> limit(60) @@ -628,10 +713,20 @@ defmodule Ecto.Adapters.PostgresTest do end test "limit `:with_ties` option" do - query = Schema |> order_by([r], r.x) |> limit([r], 3) |> with_ties(true) |> select([], true) |> plan() - assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 ORDER BY s0."x" FETCH FIRST 3 ROWS WITH TIES} + query = + Schema + |> order_by([r], r.x) + |> limit([r], 3) + |> with_ties(true) + |> select([], true) + |> plan() + + assert all(query) == + ~s{SELECT TRUE FROM "schema" AS s0 ORDER BY s0."x" FETCH FIRST 3 ROWS WITH TIES} + + msg = + ~r"PostgreSQL adapter requires an `order_by` clause if the `:with_ties` limit option is `true`" - msg = ~r"PostgreSQL adapter requires an `order_by` clause if the `:with_ties` limit option is `true`" query = Schema |> limit([r], 3) |> with_ties(true) |> select([], true) |> plan() assert_raise Ecto.QueryError, msg, fn -> @@ -702,7 +797,12 @@ defmodule Ecto.Adapters.PostgresTest do query = Schema |> select([r], fragment("? COLLATE ?", r.x, literal(^"es_ES"))) |> plan() assert all(query) == ~s{SELECT s0."x" COLLATE "es_ES" FROM "schema" AS s0} - query = Schema |> select([r], r.x) |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) |> plan() + query = + Schema + |> select([r], r.x) + |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) + |> plan() + assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WHERE (s0."x" in ($1,$2,$3,$4,$5))} value = 13 @@ -710,6 +810,7 @@ defmodule Ecto.Adapters.PostgresTest do assert all(query) == ~s{SELECT downcase(s0."x", $1) FROM "schema" AS s0} query = Schema |> select([], fragment(title: 2)) |> plan() + assert_raise Ecto.QueryError, fn -> all(query) end @@ -725,8 +826,10 @@ defmodule Ecto.Adapters.PostgresTest do query = "schema" |> where(foo: "abc") |> select([], true) |> plan() assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo" = 'abc')} - query = "schema" |> where(foo: <<0,?a,?b,?c>>) |> select([], true) |> plan() - assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo" = '\\x00616263'::bytea)} + query = "schema" |> where(foo: <<0, ?a, ?b, ?c>>) |> select([], true) |> plan() + + assert all(query) == + ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo" = '\\x00616263'::bytea)} query = "schema" |> where(foo: 123) |> select([], true) |> plan() assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo" = 123)} @@ -739,39 +842,75 @@ defmodule Ecto.Adapters.PostgresTest do query = "schema" |> select([s], selected_as(s.x, :integer)) |> plan() assert all(query) == ~s{SELECT s0."x" AS "integer" FROM "schema" AS s0} - query = "schema" |> select([s], s.x |> coalesce(0) |> sum() |> selected_as(:integer)) |> plan() + query = + "schema" |> select([s], s.x |> coalesce(0) |> sum() |> selected_as(:integer)) |> plan() + assert all(query) == ~s{SELECT sum(coalesce(s0."x", 0)) AS "integer" FROM "schema" AS s0} end test "group_by can reference the alias of a selected value with selected_as/1" do - query = "schema" |> select([s], selected_as(s.x, :integer)) |> group_by(selected_as(:integer)) |> plan() + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> group_by(selected_as(:integer)) + |> plan() + assert all(query) == ~s{SELECT s0."x" AS "integer" FROM "schema" AS s0 GROUP BY "integer"} end test "order_by can reference the alias of a selected value with selected_as/1" do - query = "schema" |> select([s], selected_as(s.x, :integer)) |> order_by(selected_as(:integer)) |> plan() + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> order_by(selected_as(:integer)) + |> plan() + assert all(query) == ~s{SELECT s0."x" AS "integer" FROM "schema" AS s0 ORDER BY "integer"} - query = "schema" |> select([s], selected_as(s.x, :integer)) |> order_by([desc: selected_as(:integer)]) |> plan() - assert all(query) == ~s{SELECT s0."x" AS "integer" FROM "schema" AS s0 ORDER BY "integer" DESC} + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> order_by(desc: selected_as(:integer)) + |> plan() + + assert all(query) == + ~s{SELECT s0."x" AS "integer" FROM "schema" AS s0 ORDER BY "integer" DESC} end test "datetime_add" do - query = "schema" |> where([s], datetime_add(s.foo, 1, "month") > s.bar) |> select([], true) |> plan() - assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo"::timestamp + interval '1 month' > s0."bar")} + query = + "schema" + |> where([s], datetime_add(s.foo, 1, "month") > s.bar) + |> select([], true) + |> plan() + + assert all(query) == + ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo"::timestamp + interval '1 month' > s0."bar")} + + query = + "schema" + |> where([s], datetime_add(type(s.foo, :string), 1, "month") > s.bar) + |> select([], true) + |> plan() - query = "schema" |> where([s], datetime_add(type(s.foo, :string), 1, "month") > s.bar) |> select([], true) |> plan() - assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo"::varchar + interval '1 month' > s0."bar")} + assert all(query) == + ~s{SELECT TRUE FROM "schema" AS s0 WHERE (s0."foo"::varchar + interval '1 month' > s0."bar")} end test "tagged type" do query = Schema |> select([t], type(t.x + t.y, :integer)) |> plan() assert all(query) == ~s{SELECT (s0."x" + s0."y")::bigint FROM "schema" AS s0} - query = Schema |> select([], type(^"601d74e4-a8d3-4b6e-8365-eddb4c893327", Ecto.UUID)) |> plan() + query = + Schema |> select([], type(^"601d74e4-a8d3-4b6e-8365-eddb4c893327", Ecto.UUID)) |> plan() + assert all(query) == ~s{SELECT $1::uuid FROM "schema" AS s0} - query = Schema |> select([], type(^["601d74e4-a8d3-4b6e-8365-eddb4c893327"], {:array, Ecto.UUID})) |> plan() + query = + Schema + |> select([], type(^["601d74e4-a8d3-4b6e-8365-eddb4c893327"], {:array, Ecto.UUID})) + |> plan() + assert all(query) == ~s{SELECT $1::uuid[] FROM "schema" AS s0} end @@ -797,21 +936,27 @@ defmodule Ecto.Adapters.PostgresTest do assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"@>'{"id": "123"}'))| query = Schema |> where([s], s.meta["tags"][0]["name"] == "123") |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE (((s0."meta"#>'{"tags",0}')@>'{"name": "123"}'))| + + assert all(query) == + ~s|SELECT TRUE FROM "schema" AS s0 WHERE (((s0."meta"#>'{"tags",0}')@>'{"name": "123"}'))| query = Schema |> where([s], s.meta[0] == "123") |> select(true) |> plan() assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0.\"meta\"#>'{0}') = '123')| query = Schema |> where([s], s.meta["enabled"] == true) |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"@>'{"enabled": true}'))| + + assert all(query) == + ~s|SELECT TRUE FROM "schema" AS s0 WHERE ((s0."meta"@>'{"enabled": true}'))| query = Schema |> where([s], s.meta["extra"][0]["enabled"] == false) |> select(true) |> plan() - assert all(query) == ~s|SELECT TRUE FROM "schema" AS s0 WHERE (((s0."meta"#>'{"extra",0}')@>'{"enabled": false}'))| + + assert all(query) == + ~s|SELECT TRUE FROM "schema" AS s0 WHERE (((s0."meta"#>'{"extra",0}')@>'{"enabled": false}'))| end test "nested expressions" do z = 123 - query = from(r in Schema, []) |> select([r], r.x > 0 and (r.y > ^(-z)) or true) |> plan() + query = from(r in Schema, []) |> select([r], (r.x > 0 and r.y > ^(-z)) or true) |> plan() assert all(query) == ~s{SELECT ((s0."x" > 0) AND (s0."y" > $1)) OR TRUE FROM "schema" AS s0} end @@ -819,7 +964,7 @@ defmodule Ecto.Adapters.PostgresTest do query = Schema |> select([e], 1 in []) |> plan() assert all(query) == ~s{SELECT false FROM "schema" AS s0} - query = Schema |> select([e], 1 in [1,e.x,3]) |> plan() + query = Schema |> select([e], 1 in [1, e.x, 3]) |> plan() assert all(query) == ~s{SELECT 1 IN (1,s0."x",3) FROM "schema" AS s0} query = Schema |> select([e], 1 in ^[]) |> plan() @@ -844,37 +989,57 @@ defmodule Ecto.Adapters.PostgresTest do assert all(query) == ~s{SELECT 1 = ANY(foo) FROM "schema" AS s0} query = Schema |> select([e], e.x == ^0 or e.x in ^[1, 2, 3] or e.x == ^4) |> plan() - assert all(query) == ~s{SELECT ((s0."x" = $1) OR s0."x" = ANY($2)) OR (s0."x" = $3) FROM "schema" AS s0} + + assert all(query) == + ~s{SELECT ((s0."x" = $1) OR s0."x" = ANY($2)) OR (s0."x" = $3) FROM "schema" AS s0} end test "in subquery" do posts = subquery("posts" |> where(title: ^"hello") |> select([p], p.id)) query = "comments" |> where([c], c.post_id in subquery(posts)) |> select([c], c.x) |> plan() + assert all(query) == - ~s{SELECT c0."x" FROM "comments" AS c0 } <> - ~s{WHERE (c0."post_id" IN (SELECT sp0."id" FROM "posts" AS sp0 WHERE (sp0."title" = $1)))} + ~s{SELECT c0."x" FROM "comments" AS c0 } <> + ~s{WHERE (c0."post_id" IN (SELECT sp0."id" FROM "posts" AS sp0 WHERE (sp0."title" = $1)))} posts = subquery("posts" |> where(title: parent_as(:comment).subtitle) |> select([p], p.id)) - query = "comments" |> from(as: :comment) |> where([c], c.post_id in subquery(posts)) |> select([c], c.x) |> plan() + + query = + "comments" + |> from(as: :comment) + |> where([c], c.post_id in subquery(posts)) + |> select([c], c.x) + |> plan() + assert all(query) == - ~s{SELECT c0."x" FROM "comments" AS c0 } <> - ~s{WHERE (c0."post_id" IN (SELECT sp0."id" FROM "posts" AS sp0 WHERE (sp0."title" = c0."subtitle")))} + ~s{SELECT c0."x" FROM "comments" AS c0 } <> + ~s{WHERE (c0."post_id" IN (SELECT sp0."id" FROM "posts" AS sp0 WHERE (sp0."title" = c0."subtitle")))} end test "having" do query = Schema |> having([p], p.x == p.x) |> select([], true) |> plan() assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 HAVING (s0."x" = s0."x")} - query = Schema |> having([p], p.x == p.x) |> having([p], p.y == p.y) |> select([], true) |> plan() - assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 HAVING (s0."x" = s0."x") AND (s0."y" = s0."y")} + query = + Schema |> having([p], p.x == p.x) |> having([p], p.y == p.y) |> select([], true) |> plan() + + assert all(query) == + ~s{SELECT TRUE FROM "schema" AS s0 HAVING (s0."x" = s0."x") AND (s0."y" = s0."y")} end test "or_having" do query = Schema |> or_having([p], p.x == p.x) |> select([], true) |> plan() assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 HAVING (s0."x" = s0."x")} - query = Schema |> or_having([p], p.x == p.x) |> or_having([p], p.y == p.y) |> select([], true) |> plan() - assert all(query) == ~s{SELECT TRUE FROM "schema" AS s0 HAVING (s0."x" = s0."x") OR (s0."y" = s0."y")} + query = + Schema + |> or_having([p], p.x == p.x) + |> or_having([p], p.y == p.y) + |> select([], true) + |> plan() + + assert all(query) == + ~s{SELECT TRUE FROM "schema" AS s0 HAVING (s0."x" = s0."x") OR (s0."y" = s0."y")} end test "group by" do @@ -907,132 +1072,172 @@ defmodule Ecto.Adapters.PostgresTest do union = "schema1" |> select([m], {m.id, ^true}) |> where([], fragment("?", ^5)) union_all = "schema2" |> select([m], {m.id, ^false}) |> where([], fragment("?", ^6)) - query = "schema" - |> with_cte("cte1", as: ^cte1) - |> with_cte("cte2", as: fragment("SELECT * FROM schema WHERE ?", ^2)) - |> select([m], {m.id, ^true}) - |> join(:inner, [], Schema2, on: fragment("?", ^true)) - |> join(:inner, [], Schema2, on: fragment("?", ^false)) - |> where([], fragment("?", ^true)) - |> where([], fragment("?", ^false)) - |> having([], fragment("?", ^true)) - |> having([], fragment("?", ^false)) - |> group_by([], fragment("?", ^3)) - |> group_by([], fragment("?", ^4)) - |> union(^union) - |> union_all(^union_all) - |> order_by([], fragment("?", ^7)) - |> limit([], ^8) - |> offset([], ^9) - |> plan() + query = + "schema" + |> with_cte("cte1", as: ^cte1) + |> with_cte("cte2", as: fragment("SELECT * FROM schema WHERE ?", ^2)) + |> select([m], {m.id, ^true}) + |> join(:inner, [], Schema2, on: fragment("?", ^true)) + |> join(:inner, [], Schema2, on: fragment("?", ^false)) + |> where([], fragment("?", ^true)) + |> where([], fragment("?", ^false)) + |> having([], fragment("?", ^true)) + |> having([], fragment("?", ^false)) + |> group_by([], fragment("?", ^3)) + |> group_by([], fragment("?", ^4)) + |> union(^union) + |> union_all(^union_all) + |> order_by([], fragment("?", ^7)) + |> limit([], ^8) + |> offset([], ^9) + |> plan() result = "WITH \"cte1\" AS (SELECT ss0.\"id\" AS \"id\", $1 AS \"smth\" FROM \"schema1\" AS ss0 WHERE ($2)), " <> - "\"cte2\" AS (SELECT * FROM schema WHERE $3) " <> - "SELECT s0.\"id\", $4 FROM \"schema\" AS s0 INNER JOIN \"schema2\" AS s1 ON $5 " <> - "INNER JOIN \"schema2\" AS s2 ON $6 WHERE ($7) AND ($8) " <> - "GROUP BY $9, $10 HAVING ($11) AND ($12) " <> - "UNION (SELECT s0.\"id\", $13 FROM \"schema1\" AS s0 WHERE ($14)) " <> - "UNION ALL (SELECT s0.\"id\", $15 FROM \"schema2\" AS s0 WHERE ($16)) " <> - "ORDER BY $17 LIMIT $18 OFFSET $19" + "\"cte2\" AS (SELECT * FROM schema WHERE $3) " <> + "SELECT s0.\"id\", $4 FROM \"schema\" AS s0 INNER JOIN \"schema2\" AS s1 ON $5 " <> + "INNER JOIN \"schema2\" AS s2 ON $6 WHERE ($7) AND ($8) " <> + "GROUP BY $9, $10 HAVING ($11) AND ($12) " <> + "UNION (SELECT s0.\"id\", $13 FROM \"schema1\" AS s0 WHERE ($14)) " <> + "UNION ALL (SELECT s0.\"id\", $15 FROM \"schema2\" AS s0 WHERE ($16)) " <> + "ORDER BY $17 LIMIT $18 OFFSET $19" assert all(query) == String.trim(result) end test "order_by and types" do - query = "schema3" |> order_by([e], type(fragment("?", e.binary), ^:decimal)) |> select(true) |> plan() + query = + "schema3" + |> order_by([e], type(fragment("?", e.binary), ^:decimal)) + |> select(true) + |> plan() + assert all(query) == "SELECT TRUE FROM \"schema3\" AS s0 ORDER BY s0.\"binary\"::decimal" end test "fragments and types" do query = - plan from(e in "schema", - where: fragment("extract(? from ?) = ?", ^"month", e.start_time, type(^"4", :integer)), - where: fragment("extract(? from ?) = ?", ^"year", e.start_time, type(^"2015", :integer)), - select: true) + plan( + from(e in "schema", + where: fragment("extract(? from ?) = ?", ^"month", e.start_time, type(^"4", :integer)), + where: + fragment("extract(? from ?) = ?", ^"year", e.start_time, type(^"2015", :integer)), + select: true + ) + ) result = "SELECT TRUE FROM \"schema\" AS s0 " <> - "WHERE (extract($1 from s0.\"start_time\") = $2::bigint) " <> - "AND (extract($3 from s0.\"start_time\") = $4::bigint)" + "WHERE (extract($1 from s0.\"start_time\") = $2::bigint) " <> + "AND (extract($3 from s0.\"start_time\") = $4::bigint)" assert all(query) == String.trim(result) end test "fragments allow ? to be escaped with backslash" do query = - plan from(e in "schema", - where: fragment("? = \"query\\?\"", e.start_time), - select: true) + plan( + from(e in "schema", + where: fragment("? = \"query\\?\"", e.start_time), + select: true + ) + ) result = "SELECT TRUE FROM \"schema\" AS s0 " <> - "WHERE (s0.\"start_time\" = \"query?\")" + "WHERE (s0.\"start_time\" = \"query?\")" assert all(query) == String.trim(result) end test "build_explain_query" do - assert_raise(ArgumentError, "bad boolean value T", fn -> - SQL.build_explain_query("SELECT 1", analyze: "T") - end) + assert_raise(ArgumentError, "bad boolean value T", fn -> + SQL.build_explain_query("SELECT 1", analyze: "T") + end) assert SQL.build_explain_query("SELECT 1", []) == "EXPLAIN SELECT 1" assert SQL.build_explain_query("SELECT 1", analyze: nil, verbose: nil) == "EXPLAIN SELECT 1" assert SQL.build_explain_query("SELECT 1", analyze: true) == "EXPLAIN ANALYZE SELECT 1" - assert SQL.build_explain_query("SELECT 1", analyze: true, verbose: true) == "EXPLAIN ANALYZE VERBOSE SELECT 1" - assert SQL.build_explain_query("SELECT 1", analyze: true, costs: true) == "EXPLAIN ( ANALYZE TRUE, COSTS TRUE ) SELECT 1" + + assert SQL.build_explain_query("SELECT 1", analyze: true, verbose: true) == + "EXPLAIN ANALYZE VERBOSE SELECT 1" + + assert SQL.build_explain_query("SELECT 1", analyze: true, costs: true) == + "EXPLAIN ( ANALYZE TRUE, COSTS TRUE ) SELECT 1" end ## *_all test "update all" do query = from(m in Schema, update: [set: [x: 0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = 0} + ~s{UPDATE "schema" AS s0 SET "x" = 0} query = from(m in Schema, update: [set: [x: 0], inc: [y: 1, z: -3]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = 0, "y" = s0."y" + 1, "z" = s0."z" + -3} + ~s{UPDATE "schema" AS s0 SET "x" = 0, "y" = s0."y" + 1, "z" = s0."z" + -3} query = from(e in Schema, where: e.x == 123, update: [set: [x: 0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = 0 WHERE (s0."x" = 123)} + ~s{UPDATE "schema" AS s0 SET "x" = 0 WHERE (s0."x" = 123)} query = from(m in Schema, update: [set: [x: ^0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = $1} + ~s{UPDATE "schema" AS s0 SET "x" = $1} + + query = + Schema + |> join(:inner, [p], q in Schema2, on: p.x == q.z) + |> update([_], set: [x: 0]) + |> plan(:update_all) - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) - |> update([_], set: [x: 0]) |> plan(:update_all) assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = 0 FROM "schema2" AS s1 WHERE (s0."x" = s1."z")} + ~s{UPDATE "schema" AS s0 SET "x" = 0 FROM "schema2" AS s1 WHERE (s0."x" = s1."z")} + + query = + from(e in Schema, + where: e.x == 123, + update: [set: [x: 0]], + join: q in Schema2, + on: e.x == q.z + ) + |> plan(:update_all) - query = from(e in Schema, where: e.x == 123, update: [set: [x: 0]], - join: q in Schema2, on: e.x == q.z) |> plan(:update_all) assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = 0 FROM "schema2" AS s1 } <> - ~s{WHERE (s0."x" = s1."z") AND (s0."x" = 123)} + ~s{UPDATE "schema" AS s0 SET "x" = 0 FROM "schema2" AS s1 } <> + ~s{WHERE (s0."x" = s1."z") AND (s0."x" = 123)} end test "update all with returning" do query = from(m in Schema, update: [set: [x: 0]]) |> select([m], m) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = 0 RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} + ~s{UPDATE "schema" AS s0 SET "x" = 0 RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} + + query = + from(m in Schema, update: [set: [x: ^1]]) + |> where([m], m.x == ^2) + |> select([m], m.x == ^3) + |> plan(:update_all) - query = from(m in Schema, update: [set: [x: ^1]]) |> where([m], m.x == ^2) |> select([m], m.x == ^3) |> plan(:update_all) assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "x" = $1 WHERE (s0."x" = $2) RETURNING s0."x" = $3} + ~s{UPDATE "schema" AS s0 SET "x" = $1 WHERE (s0."x" = $2) RETURNING s0."x" = $3} end test "update all array ops" do query = from(m in Schema, update: [push: [w: 0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "w" = array_append(s0."w", 0)} + ~s{UPDATE "schema" AS s0 SET "w" = array_append(s0."w", 0)} query = from(m in Schema, update: [pull: [w: 0]]) |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "w" = array_remove(s0."w", 0)} + ~s{UPDATE "schema" AS s0 SET "w" = array_remove(s0."w", 0)} end test "update all with subquery" do @@ -1047,65 +1252,95 @@ defmodule Ecto.Adapters.PostgresTest do Ecto.Adapter.Queryable.plan_query(:update_all, Ecto.Adapters.Postgres, query) assert update_all(planned_query) == - ~s{UPDATE "schema" AS s0 SET "x" = $1 FROM } <> - ~s{(SELECT ss0."id" AS "id", ss0."x" AS "x", ss0."y" AS "y", ss0."z" AS "z", ss0."w" AS "w", ss0."meta" AS "meta" FROM "schema" AS ss0 WHERE (ss0."x" > $2)) } <> - ~s{AS s1 WHERE (s0."id" = s1."id")} + ~s{UPDATE "schema" AS s0 SET "x" = $1 FROM } <> + ~s{(SELECT ss0."id" AS "id", ss0."x" AS "x", ss0."y" AS "y", ss0."z" AS "z", ss0."w" AS "w", ss0."meta" AS "meta" FROM "schema" AS ss0 WHERE (ss0."x" > $2)) } <> + ~s{AS s1 WHERE (s0."id" = s1."id")} assert cast_params == [100, 10] assert dump_params == [100, 10] end test "update all with prefix" do - query = from(m in Schema, update: [set: [x: 0]]) |> Map.put(:prefix, "prefix") |> plan(:update_all) + query = + from(m in Schema, update: [set: [x: 0]]) |> Map.put(:prefix, "prefix") |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "prefix"."schema" AS s0 SET "x" = 0} + ~s{UPDATE "prefix"."schema" AS s0 SET "x" = 0} + + query = + from(m in Schema, prefix: "first", update: [set: [x: 0]]) + |> Map.put(:prefix, "prefix") + |> plan(:update_all) - query = from(m in Schema, prefix: "first", update: [set: [x: 0]]) |> Map.put(:prefix, "prefix") |> plan(:update_all) assert update_all(query) == - ~s{UPDATE "first"."schema" AS s0 SET "x" = 0} + ~s{UPDATE "first"."schema" AS s0 SET "x" = 0} end test "update all with left join" do - query = from(m in Schema, join: x in assoc(m, :comments), left_join: p in assoc(m, :permalink), update: [set: [w: m.list2]]) |> plan(:update_all) + query = + from(m in Schema, + join: x in assoc(m, :comments), + left_join: p in assoc(m, :permalink), + update: [set: [w: m.list2]] + ) + |> plan(:update_all) + assert update_all(query) == - ~s{UPDATE "schema" AS s0 SET "w" = s0."list2" FROM "schema2" AS s1 LEFT OUTER JOIN "schema3" AS s2 ON s2."id" = s0."y" WHERE (s1."z" = s0."x")} + ~s{UPDATE "schema" AS s0 SET "w" = s0."list2" FROM "schema2" AS s1 LEFT OUTER JOIN "schema3" AS s2 ON s2."id" = s0."y" WHERE (s1."z" = s0."x")} end test "update all with left join but no inner join" do - query = from(m in Schema, left_join: p in assoc(m, :permalink), left_join: x in assoc(m, :permalink), update: [set: [w: m.list2]]) |> plan(:update_all) - assert_raise Ecto.QueryError, ~r/Need at least one inner join at the beginning to use other joins with update_all/, fn -> - update_all(query) - end + query = + from(m in Schema, + left_join: p in assoc(m, :permalink), + left_join: x in assoc(m, :permalink), + update: [set: [w: m.list2]] + ) + |> plan(:update_all) + + assert_raise Ecto.QueryError, + ~r/Need at least one inner join at the beginning to use other joins with update_all/, + fn -> + update_all(query) + end end test "delete all" do - query = Schema |> Queryable.to_query |> plan() + query = Schema |> Queryable.to_query() |> plan() assert delete_all(query) == ~s{DELETE FROM "schema" AS s0} query = from(e in Schema, where: e.x == 123) |> plan() + assert delete_all(query) == - ~s{DELETE FROM "schema" AS s0 WHERE (s0."x" = 123)} + ~s{DELETE FROM "schema" AS s0 WHERE (s0."x" = 123)} query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> plan() + assert delete_all(query) == - ~s{DELETE FROM "schema" AS s0 USING "schema2" AS s1 WHERE (s0."x" = s1."z")} + ~s{DELETE FROM "schema" AS s0 USING "schema2" AS s1 WHERE (s0."x" = s1."z")} query = from(e in Schema, where: e.x == 123, join: q in Schema2, on: e.x == q.z) |> plan() + assert delete_all(query) == - ~s{DELETE FROM "schema" AS s0 USING "schema2" AS s1 WHERE (s0."x" = s1."z") AND (s0."x" = 123)} + ~s{DELETE FROM "schema" AS s0 USING "schema2" AS s1 WHERE (s0."x" = s1."z") AND (s0."x" = 123)} + + query = + from(e in Schema, where: e.x == 123, join: assoc(e, :comments), join: assoc(e, :permalink)) + |> plan() - query = from(e in Schema, where: e.x == 123, join: assoc(e, :comments), join: assoc(e, :permalink)) |> plan() assert delete_all(query) == - ~s{DELETE FROM "schema" AS s0 USING "schema2" AS s1, "schema3" AS s2 WHERE (s1."z" = s0."x") AND (s2."id" = s0."y") AND (s0."x" = 123)} + ~s{DELETE FROM "schema" AS s0 USING "schema2" AS s1, "schema3" AS s2 WHERE (s1."z" = s0."x") AND (s2."id" = s0."y") AND (s0."x" = 123)} end test "delete all with returning" do - query = Schema |> Queryable.to_query |> select([m], m) |> plan() - assert delete_all(query) == ~s{DELETE FROM "schema" AS s0 RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} + query = Schema |> Queryable.to_query() |> select([m], m) |> plan() + + assert delete_all(query) == + ~s{DELETE FROM "schema" AS s0 RETURNING s0."id", s0."x", s0."y", s0."z", s0."w", s0."meta"} end test "delete all with prefix" do - query = Schema |> Queryable.to_query |> Map.put(:prefix, "prefix") |> plan() + query = Schema |> Queryable.to_query() |> Map.put(:prefix, "prefix") |> plan() assert delete_all(query) == ~s{DELETE FROM "prefix"."schema" AS s0} query = Schema |> from(prefix: "first") |> Map.put(:prefix, "prefix") |> plan() @@ -1116,102 +1351,144 @@ defmodule Ecto.Adapters.PostgresTest do describe "windows and partitions" do test "one window" do - query = Schema - |> select([r], r.x) - |> windows([r], w: [partition_by: r.x]) - |> plan + query = + Schema + |> select([r], r.x) + |> windows([r], w: [partition_by: r.x]) + |> plan - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WINDOW "w" AS (PARTITION BY s0."x")} + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 WINDOW "w" AS (PARTITION BY s0."x")} end test "two windows" do - query = Schema - |> select([r], r.x) - |> windows([r], w1: [partition_by: r.x], w2: [partition_by: r.y]) - |> plan() - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WINDOW "w1" AS (PARTITION BY s0."x"), "w2" AS (PARTITION BY s0."y")} + query = + Schema + |> select([r], r.x) + |> windows([r], w1: [partition_by: r.x], w2: [partition_by: r.y]) + |> plan() + + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 WINDOW "w1" AS (PARTITION BY s0."x"), "w2" AS (PARTITION BY s0."y")} end test "count over window" do - query = Schema - |> windows([r], w: [partition_by: r.x]) - |> select([r], count(r.x) |> over(:w)) - |> plan() - assert all(query) == ~s{SELECT count(s0."x") OVER "w" FROM "schema" AS s0 WINDOW "w" AS (PARTITION BY s0."x")} + query = + Schema + |> windows([r], w: [partition_by: r.x]) + |> select([r], count(r.x) |> over(:w)) + |> plan() + + assert all(query) == + ~s{SELECT count(s0."x") OVER "w" FROM "schema" AS s0 WINDOW "w" AS (PARTITION BY s0."x")} end test "count over all" do - query = Schema - |> select([r], count(r.x) |> over) - |> plan() + query = + Schema + |> select([r], count(r.x) |> over) + |> plan() + assert all(query) == ~s{SELECT count(s0."x") OVER () FROM "schema" AS s0} end test "row_number over all" do - query = Schema - |> select(row_number |> over) - |> plan() + query = + Schema + |> select(row_number |> over) + |> plan() + assert all(query) == ~s{SELECT row_number() OVER () FROM "schema" AS s0} end test "nth_value over all" do - query = Schema - |> select([r], nth_value(r.x, 42) |> over) - |> plan() + query = + Schema + |> select([r], nth_value(r.x, 42) |> over) + |> plan() + assert all(query) == ~s{SELECT nth_value(s0."x", 42) OVER () FROM "schema" AS s0} end test "lag/2 over all" do - query = Schema - |> select([r], lag(r.x, 42) |> over) - |> plan() + query = + Schema + |> select([r], lag(r.x, 42) |> over) + |> plan() + assert all(query) == ~s{SELECT lag(s0."x", 42) OVER () FROM "schema" AS s0} end test "custom aggregation over all" do - query = Schema - |> select([r], fragment("custom_function(?)", r.x) |> over) - |> plan() + query = + Schema + |> select([r], fragment("custom_function(?)", r.x) |> over) + |> plan() + assert all(query) == ~s{SELECT custom_function(s0."x") OVER () FROM "schema" AS s0} end test "partition by and order by on window" do - query = Schema - |> windows([r], w: [partition_by: [r.x, r.z], order_by: r.x]) - |> select([r], r.x) - |> plan() - assert all(query) == ~s{SELECT s0."x" FROM "schema" AS s0 WINDOW "w" AS (PARTITION BY s0."x", s0."z" ORDER BY s0."x")} + query = + Schema + |> windows([r], w: [partition_by: [r.x, r.z], order_by: r.x]) + |> select([r], r.x) + |> plan() + + assert all(query) == + ~s{SELECT s0."x" FROM "schema" AS s0 WINDOW "w" AS (PARTITION BY s0."x", s0."z" ORDER BY s0."x")} end test "partition by ond order by over" do - query = Schema - |> select([r], count(r.x) |> over(partition_by: [r.x, r.z], order_by: r.x)) + query = + Schema + |> select([r], count(r.x) |> over(partition_by: [r.x, r.z], order_by: r.x)) query = query |> plan() - assert all(query) == ~s{SELECT count(s0."x") OVER (PARTITION BY s0."x", s0."z" ORDER BY s0."x") FROM "schema" AS s0} + + assert all(query) == + ~s{SELECT count(s0."x") OVER (PARTITION BY s0."x", s0."z" ORDER BY s0."x") FROM "schema" AS s0} end test "frame clause" do - query = Schema - |> select([r], count(r.x) |> over(partition_by: [r.x, r.z], order_by: r.x, frame: fragment("ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING"))) + query = + select( + Schema, + [r], + count(r.x) + |> over( + partition_by: [r.x, r.z], + order_by: r.x, + frame: fragment("ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING") + ) + ) query = query |> plan() - assert all(query) == ~s{SELECT count(s0."x") OVER (PARTITION BY s0."x", s0."z" ORDER BY s0."x" ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) FROM "schema" AS s0} + + assert all(query) == + ~s{SELECT count(s0."x") OVER (PARTITION BY s0."x", s0."z" ORDER BY s0."x" ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) FROM "schema" AS s0} end end ## Joins test "join" do - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> select([], true) |> plan() + query = + Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> select([], true) |> plan() + assert all(query) == - ~s{SELECT TRUE FROM "schema" AS s0 INNER JOIN "schema2" AS s1 ON s0."x" = s1."z"} + ~s{SELECT TRUE FROM "schema" AS s0 INNER JOIN "schema2" AS s1 ON s0."x" = s1."z"} + + query = + Schema + |> join(:inner, [p], q in Schema2, on: p.x == q.z) + |> join(:inner, [], Schema, on: true) + |> select([], true) + |> plan() - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) - |> join(:inner, [], Schema, on: true) |> select([], true) |> plan() assert all(query) == - ~s{SELECT TRUE FROM "schema" AS s0 INNER JOIN "schema2" AS s1 ON s0."x" = s1."z" } <> - ~s{INNER JOIN "schema" AS s2 ON TRUE} + ~s{SELECT TRUE FROM "schema" AS s0 INNER JOIN "schema2" AS s1 ON s0."x" = s1."z" } <> + ~s{INNER JOIN "schema" AS s2 ON TRUE} end test "join with invalid qualifier" do @@ -1236,158 +1513,217 @@ defmodule Ecto.Adapters.PostgresTest do test "join with nothing bound" do query = Schema |> join(:inner, [], q in Schema2, on: q.z == q.z) |> select([], true) |> plan() + assert all(query) == - ~s{SELECT TRUE FROM "schema" AS s0 INNER JOIN "schema2" AS s1 ON s1."z" = s1."z"} + ~s{SELECT TRUE FROM "schema" AS s0 INNER JOIN "schema2" AS s1 ON s1."z" = s1."z"} end test "join without schema" do - query = "posts" |> join(:inner, [p], q in "comments", on: p.x == q.z) |> select([], true) |> plan() + query = + "posts" |> join(:inner, [p], q in "comments", on: p.x == q.z) |> select([], true) |> plan() + assert all(query) == - ~s{SELECT TRUE FROM "posts" AS p0 INNER JOIN "comments" AS c1 ON p0."x" = c1."z"} + ~s{SELECT TRUE FROM "posts" AS p0 INNER JOIN "comments" AS c1 ON p0."x" = c1."z"} end test "join with subquery" do posts = subquery("posts" |> where(title: ^"hello") |> select([r], %{x: r.x, y: r.y})) - query = "comments" |> join(:inner, [c], p in subquery(posts), on: true) |> select([_, p], p.x) |> plan() + + query = + "comments" + |> join(:inner, [c], p in subquery(posts), on: true) + |> select([_, p], p.x) + |> plan() + assert all(query) == - ~s{SELECT s1."x" FROM "comments" AS c0 } <> - ~s{INNER JOIN (SELECT sp0."x" AS "x", sp0."y" AS "y" FROM "posts" AS sp0 WHERE (sp0."title" = $1)) AS s1 ON TRUE} + ~s{SELECT s1."x" FROM "comments" AS c0 } <> + ~s{INNER JOIN (SELECT sp0."x" AS "x", sp0."y" AS "y" FROM "posts" AS sp0 WHERE (sp0."title" = $1)) AS s1 ON TRUE} posts = subquery("posts" |> where(title: ^"hello") |> select([r], %{x: r.x, z: r.y})) - query = "comments" |> join(:inner, [c], p in subquery(posts), on: true) |> select([_, p], p) |> plan() + + query = + "comments" + |> join(:inner, [c], p in subquery(posts), on: true) + |> select([_, p], p) + |> plan() + assert all(query) == - ~s{SELECT s1."x", s1."z" FROM "comments" AS c0 } <> - ~s{INNER JOIN (SELECT sp0."x" AS "x", sp0."y" AS "z" FROM "posts" AS sp0 WHERE (sp0."title" = $1)) AS s1 ON TRUE} - - posts = subquery("posts" |> where(title: parent_as(:comment).subtitle) |> select([r], r.title)) - query = "comments" - |> from(as: :comment) - |> join(:inner, [c], p in subquery(posts), on: true) - |> select([_, p], p) - |> plan() + ~s{SELECT s1."x", s1."z" FROM "comments" AS c0 } <> + ~s{INNER JOIN (SELECT sp0."x" AS "x", sp0."y" AS "z" FROM "posts" AS sp0 WHERE (sp0."title" = $1)) AS s1 ON TRUE} + + posts = + subquery("posts" |> where(title: parent_as(:comment).subtitle) |> select([r], r.title)) + + query = + "comments" + |> from(as: :comment) + |> join(:inner, [c], p in subquery(posts), on: true) + |> select([_, p], p) + |> plan() + assert all(query) == - ~s{SELECT s1."title" FROM "comments" AS c0 } <> - ~s{INNER JOIN (SELECT sp0."title" AS "title" FROM "posts" AS sp0 WHERE (sp0."title" = c0."subtitle")) AS s1 ON TRUE} + ~s{SELECT s1."title" FROM "comments" AS c0 } <> + ~s{INNER JOIN (SELECT sp0."title" AS "title" FROM "posts" AS sp0 WHERE (sp0."title" = c0."subtitle")) AS s1 ON TRUE} end test "join with prefix" do - query = Schema |> join(:inner, [p], q in Schema2, on: p.x == q.z) |> select([], true) |> Map.put(:prefix, "prefix") |> plan() + query = + Schema + |> join(:inner, [p], q in Schema2, on: p.x == q.z) + |> select([], true) + |> Map.put(:prefix, "prefix") + |> plan() + assert all(query) == - ~s{SELECT TRUE FROM "prefix"."schema" AS s0 INNER JOIN "prefix"."schema2" AS s1 ON s0."x" = s1."z"} + ~s{SELECT TRUE FROM "prefix"."schema" AS s0 INNER JOIN "prefix"."schema2" AS s1 ON s0."x" = s1."z"} + + query = + Schema + |> from(prefix: "first") + |> join(:inner, [p], q in Schema2, on: p.x == q.z, prefix: "second") + |> select([], true) + |> Map.put(:prefix, "prefix") + |> plan() - query = Schema |> from(prefix: "first") |> join(:inner, [p], q in Schema2, on: p.x == q.z, prefix: "second") |> select([], true) |> Map.put(:prefix, "prefix") |> plan() assert all(query) == - ~s{SELECT TRUE FROM "first"."schema" AS s0 INNER JOIN "second"."schema2" AS s1 ON s0."x" = s1."z"} + ~s{SELECT TRUE FROM "first"."schema" AS s0 INNER JOIN "second"."schema2" AS s1 ON s0."x" = s1."z"} end test "join with fragment" do - query = Schema - |> join(:inner, - [p], - q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), - on: true - ) - |> select([p], {p.id, ^0}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :inner, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), + on: true + ) + |> select([p], {p.id, ^0}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0."id", $1 FROM "schema" AS s0 INNER JOIN } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0."x" AND s2.field = $2) AS f1 ON TRUE } <> - ~s{WHERE ((s0."id" > 0) AND (s0."id" < $3))} + ~s{SELECT s0."id", $1 FROM "schema" AS s0 INNER JOIN } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0."x" AND s2.field = $2) AS f1 ON TRUE } <> + ~s{WHERE ((s0."id" > 0) AND (s0."id" < $3))} end test "join with fragment and on defined" do - query = Schema - |> join(:inner, [p], q in fragment("SELECT * FROM schema2"), on: q.id == p.id) - |> select([p], {p.id, ^0}) - |> plan() + query = + Schema + |> join(:inner, [p], q in fragment("SELECT * FROM schema2"), on: q.id == p.id) + |> select([p], {p.id, ^0}) + |> plan() + assert all(query) == - ~s{SELECT s0."id", $1 FROM "schema" AS s0 INNER JOIN } <> - ~s{(SELECT * FROM schema2) AS f1 ON f1."id" = s0."id"} + ~s{SELECT s0."id", $1 FROM "schema" AS s0 INNER JOIN } <> + ~s{(SELECT * FROM schema2) AS f1 ON f1."id" = s0."id"} end test "join with query interpolation" do inner = Ecto.Queryable.to_query(Schema2) query = from(p in Schema, left_join: c in ^inner, on: true, select: {p.id, c.id}) |> plan() + assert all(query) == - "SELECT s0.\"id\", s1.\"id\" FROM \"schema\" AS s0 LEFT OUTER JOIN \"schema2\" AS s1 ON TRUE" + "SELECT s0.\"id\", s1.\"id\" FROM \"schema\" AS s0 LEFT OUTER JOIN \"schema2\" AS s1 ON TRUE" end test "lateral join with fragment" do - query = Schema - |> join(:inner_lateral, [p], q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), on: true) - |> select([p, q], {p.id, q.z}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :inner_lateral, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), + on: true + ) + |> select([p, q], {p.id, q.z}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0."id", f1."z" FROM "schema" AS s0 INNER JOIN LATERAL } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0."x" AND s2.field = $1) AS f1 ON TRUE } <> - ~s{WHERE ((s0."id" > 0) AND (s0."id" < $2))} + ~s{SELECT s0."id", f1."z" FROM "schema" AS s0 INNER JOIN LATERAL } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0."x" AND s2.field = $1) AS f1 ON TRUE } <> + ~s{WHERE ((s0."id" > 0) AND (s0."id" < $2))} end test "cross lateral join with fragment" do - query = Schema - |> join(:cross_lateral, [p], q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10)) - |> select([p, q], {p.id, q.z}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :cross_lateral, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10) + ) + |> select([p, q], {p.id, q.z}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0."id", f1."z" FROM "schema" AS s0 CROSS JOIN LATERAL } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0."x" AND s2.field = $1) AS f1 } <> - ~s{WHERE ((s0."id" > 0) AND (s0."id" < $2))} + ~s{SELECT s0."id", f1."z" FROM "schema" AS s0 CROSS JOIN LATERAL } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0."x" AND s2.field = $1) AS f1 } <> + ~s{WHERE ((s0."id" > 0) AND (s0."id" < $2))} end test "cross join" do query = from(p in Schema, cross_join: c in Schema2, select: {p.id, c.id}) |> plan() + assert all(query) == - "SELECT s0.\"id\", s1.\"id\" FROM \"schema\" AS s0 CROSS JOIN \"schema2\" AS s1" + "SELECT s0.\"id\", s1.\"id\" FROM \"schema\" AS s0 CROSS JOIN \"schema2\" AS s1" end test "cross join with fragment" do - query = from(p in Schema, cross_join: fragment("jsonb_each(?)", p.j), select: {p.id}) |> plan() + query = + from(p in Schema, cross_join: fragment("jsonb_each(?)", p.j), select: {p.id}) |> plan() + assert all(query) == - ~s{SELECT s0."id" FROM "schema" AS s0 CROSS JOIN jsonb_each(s0."j") AS f1} + ~s{SELECT s0."id" FROM "schema" AS s0 CROSS JOIN jsonb_each(s0."j") AS f1} end test "join produces correct bindings" do query = from(p in Schema, join: c in Schema2, on: true) query = from(p in query, join: c in Schema2, on: true, select: {p.id, c.id}) query = plan(query) + assert all(query) == - "SELECT s0.\"id\", s2.\"id\" FROM \"schema\" AS s0 INNER JOIN \"schema2\" AS s1 ON TRUE INNER JOIN \"schema2\" AS s2 ON TRUE" + "SELECT s0.\"id\", s2.\"id\" FROM \"schema\" AS s0 INNER JOIN \"schema2\" AS s1 ON TRUE INNER JOIN \"schema2\" AS s2 ON TRUE" end describe "query interpolation parameters" do test "self join on subquery" do subquery = select(Schema, [r], %{x: r.x, y: r.y}) query = subquery |> join(:inner, [c], p in subquery(subquery), on: true) |> plan() + assert all(query) == - ~s{SELECT s0."x", s0."y" FROM "schema" AS s0 INNER JOIN } <> - ~s{(SELECT ss0."x" AS "x", ss0."y" AS "y" FROM "schema" AS ss0) } <> - ~s{AS s1 ON TRUE} + ~s{SELECT s0."x", s0."y" FROM "schema" AS s0 INNER JOIN } <> + ~s{(SELECT ss0."x" AS "x", ss0."y" AS "y" FROM "schema" AS ss0) } <> + ~s{AS s1 ON TRUE} end test "self join on subquery with fragment" do subquery = select(Schema, [r], %{string: fragment("downcase(?)", ^"string")}) query = subquery |> join(:inner, [c], p in subquery(subquery), on: true) |> plan() + assert all(query) == - ~s{SELECT downcase($1) FROM "schema" AS s0 INNER JOIN } <> - ~s{(SELECT downcase($2) AS "string" FROM "schema" AS ss0) } <> - ~s{AS s1 ON TRUE} + ~s{SELECT downcase($1) FROM "schema" AS s0 INNER JOIN } <> + ~s{(SELECT downcase($2) AS "string" FROM "schema" AS ss0) } <> + ~s{AS s1 ON TRUE} end test "join on subquery with simple select" do subquery = select(Schema, [r], %{x: ^999, w: ^888}) - query = Schema - |> select([r], %{y: ^666}) - |> join(:inner, [c], p in subquery(subquery), on: true) - |> where([a, b], a.x == ^111) - |> plan() + + query = + Schema + |> select([r], %{y: ^666}) + |> join(:inner, [c], p in subquery(subquery), on: true) + |> where([a, b], a.x == ^111) + |> plan() assert all(query) == - ~s{SELECT $1 FROM "schema" AS s0 INNER JOIN } <> - ~s{(SELECT $2 AS "x", $3 AS "w" FROM "schema" AS ss0) AS s1 ON TRUE } <> - ~s{WHERE (s0."x" = $4)} + ~s{SELECT $1 FROM "schema" AS s0 INNER JOIN } <> + ~s{(SELECT $2 AS "x", $3 AS "w" FROM "schema" AS ss0) AS s1 ON TRUE } <> + ~s{WHERE (s0."x" = $4)} end end @@ -1395,20 +1731,23 @@ defmodule Ecto.Adapters.PostgresTest do test "association join belongs_to" do query = Schema2 |> join(:inner, [c], p in assoc(c, :post)) |> select([], true) |> plan() + assert all(query) == - "SELECT TRUE FROM \"schema2\" AS s0 INNER JOIN \"schema\" AS s1 ON s1.\"x\" = s0.\"z\"" + "SELECT TRUE FROM \"schema2\" AS s0 INNER JOIN \"schema\" AS s1 ON s1.\"x\" = s0.\"z\"" end test "association join has_many" do query = Schema |> join(:inner, [p], c in assoc(p, :comments)) |> select([], true) |> plan() + assert all(query) == - "SELECT TRUE FROM \"schema\" AS s0 INNER JOIN \"schema2\" AS s1 ON s1.\"z\" = s0.\"x\"" + "SELECT TRUE FROM \"schema\" AS s0 INNER JOIN \"schema2\" AS s1 ON s1.\"z\" = s0.\"x\"" end test "association join has_one" do query = Schema |> join(:inner, [p], pp in assoc(p, :permalink)) |> select([], true) |> plan() + assert all(query) == - "SELECT TRUE FROM \"schema\" AS s0 INNER JOIN \"schema3\" AS s1 ON s1.\"id\" = s0.\"y\"" + "SELECT TRUE FROM \"schema\" AS s0 INNER JOIN \"schema3\" AS s1 ON s1.\"id\" = s0.\"y\"" end # Schema based @@ -1420,7 +1759,9 @@ defmodule Ecto.Adapters.PostgresTest do query = insert(nil, "schema", [:x, :y], [[:x, :y], [nil, :z]], {:raise, [], []}, [:id]) assert query == ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2),(DEFAULT,$3) RETURNING "id"} - query = insert(nil, "schema", [:x, :y], [[:x, :y], [nil, :z]], {:raise, [], []}, [:id], [1, 2]) + query = + insert(nil, "schema", [:x, :y], [[:x, :y], [nil, :z]], {:raise, [], []}, [:id], [1, 2]) + assert query == ~s{INSERT INTO "schema" ("x","y") VALUES ($3,$4),(DEFAULT,$5) RETURNING "id"} query = insert(nil, "schema", [], [[]], {:raise, [], []}, [:id]) @@ -1439,37 +1780,66 @@ defmodule Ecto.Adapters.PostgresTest do assert query == ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2) ON CONFLICT DO NOTHING} query = insert(nil, "schema", [:x, :y], [[:x, :y]], {:nothing, [], [:x, :y]}, []) - assert query == ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2) ON CONFLICT ("x","y") DO NOTHING} + + assert query == + ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2) ON CONFLICT ("x","y") DO NOTHING} # For :update update = from("schema", update: [set: [z: "foo"]]) |> plan(:update_all) query = insert(nil, "schema", [:x, :y], [[:x, :y]], {update, [], [:x, :y]}, [:z]) - assert query == ~s{INSERT INTO "schema" AS s0 ("x","y") VALUES ($1,$2) ON CONFLICT ("x","y") DO UPDATE SET "z" = 'foo' RETURNING "z"} + + assert query == + ~s{INSERT INTO "schema" AS s0 ("x","y") VALUES ($1,$2) ON CONFLICT ("x","y") DO UPDATE SET "z" = 'foo' RETURNING "z"} # For :replace_all query = insert(nil, "schema", [:x, :y], [[:x, :y]], {[:x, :y], [], [:id]}, []) - assert query == ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2) ON CONFLICT ("id") DO UPDATE SET "x" = EXCLUDED."x","y" = EXCLUDED."y"} - query = insert(nil, "schema", [:x, :y], [[:x, :y]], {[:x, :y], [], {:unsafe_fragment, "(\"id\")"}}, []) - assert query == ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2) ON CONFLICT (\"id\") DO UPDATE SET "x" = EXCLUDED."x","y" = EXCLUDED."y"} + assert query == + ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2) ON CONFLICT ("id") DO UPDATE SET "x" = EXCLUDED."x","y" = EXCLUDED."y"} - assert_raise ArgumentError, "the :conflict_target option is required on upserts by PostgreSQL", fn -> - insert(nil, "schema", [:x, :y], [[:x, :y]], {[:x, :y], [], []}, []) - end + query = + insert( + nil, + "schema", + [:x, :y], + [[:x, :y]], + {[:x, :y], [], {:unsafe_fragment, "(\"id\")"}}, + [] + ) + + assert query == + ~s{INSERT INTO "schema" ("x","y") VALUES ($1,$2) ON CONFLICT (\"id\") DO UPDATE SET "x" = EXCLUDED."x","y" = EXCLUDED."y"} + + assert_raise ArgumentError, + "the :conflict_target option is required on upserts by PostgreSQL", + fn -> + insert(nil, "schema", [:x, :y], [[:x, :y]], {[:x, :y], [], []}, []) + end end test "insert with query" do query = from("schema", select: [:id]) |> plan(:all) - query = insert(nil, "schema", [:x, :y, :z], [[:x, {query, 3}, :z], [nil, {query, 2}, :z]], {:raise, [], []}, [:id]) - assert query == ~s{INSERT INTO "schema" ("x","y","z") VALUES ($1,(SELECT s0."id" FROM "schema" AS s0),$5),(DEFAULT,(SELECT s0."id" FROM "schema" AS s0),$8) RETURNING "id"} + query = + insert( + nil, + "schema", + [:x, :y, :z], + [[:x, {query, 3}, :z], [nil, {query, 2}, :z]], + {:raise, [], []}, + [:id] + ) + + assert query == + ~s{INSERT INTO "schema" ("x","y","z") VALUES ($1,(SELECT s0."id" FROM "schema" AS s0),$5),(DEFAULT,(SELECT s0."id" FROM "schema" AS s0),$8) RETURNING "id"} end test "insert with query as rows" do - query = from(s in "schema", select: %{ foo: fragment("3"), bar: s.bar }) |> plan(:all) + query = from(s in "schema", select: %{foo: fragment("3"), bar: s.bar}) |> plan(:all) query = insert(nil, "schema", [:foo, :bar], query, {:raise, [], []}, [:foo]) - assert query == ~s{INSERT INTO "schema" ("foo","bar") (SELECT 3, s0."bar" FROM "schema" AS s0) RETURNING "foo"} + assert query == + ~s{INSERT INTO "schema" ("foo","bar") (SELECT 3, s0."bar" FROM "schema" AS s0) RETURNING "foo"} end test "update" do @@ -1483,7 +1853,9 @@ defmodule Ecto.Adapters.PostgresTest do assert query == ~s{UPDATE "prefix"."schema" SET "x" = $1, "y" = $2 WHERE "id" = $3} query = update("prefix", "schema", [:x, :y], [id: 1, updated_at: nil], []) - assert query == ~s{UPDATE "prefix"."schema" SET "x" = $1, "y" = $2 WHERE "id" = $3 AND "updated_at" IS NULL} + + assert query == + ~s{UPDATE "prefix"."schema" SET "x" = $1, "y" = $2 WHERE "id" = $3 AND "updated_at" IS NULL} end test "delete" do @@ -1519,9 +1891,9 @@ defmodule Ecto.Adapters.PostgresTest do assert query == ~s{SELECT v1."bid", v1."num" } <> - ~s{FROM (VALUES ($1::uuid,$2::bigint),($3::uuid,$4::bigint)) AS v0 ("bid","num") } <> - ~s{INNER JOIN (VALUES ($5::uuid,$6::bigint),($7::uuid,$8::bigint)) AS v1 ("bid","num") ON v0."bid" = v1."bid" } <> - ~s{WHERE (v0."num" = $9)} + ~s{FROM (VALUES ($1::uuid,$2::bigint),($3::uuid,$4::bigint)) AS v0 ("bid","num") } <> + ~s{INNER JOIN (VALUES ($5::uuid,$6::bigint),($7::uuid,$8::bigint)) AS v1 ("bid","num") ON v0."bid" = v1."bid" } <> + ~s{WHERE (v0."num" = $9)} end test "values list: delete_all" do @@ -1536,8 +1908,8 @@ defmodule Ecto.Adapters.PostgresTest do assert query == ~s{DELETE FROM "schema" AS s0 } <> - ~s{USING (VALUES ($1::uuid,$2::bigint),($3::uuid,$4::bigint)) AS v1 ("bid","num") } <> - ~s{WHERE (s0."x" = v1."num") AND (v1."num" = $5)} + ~s{USING (VALUES ($1::uuid,$2::bigint),($3::uuid,$4::bigint)) AS v1 ("bid","num") } <> + ~s{WHERE (s0."x" = v1."num") AND (v1."num" = $5)} end test "values list: update_all" do @@ -1564,85 +1936,108 @@ defmodule Ecto.Adapters.PostgresTest do # DDL alias Ecto.Migration.Reference - import Ecto.Migration, only: [table: 1, table: 2, index: 2, index: 3, - constraint: 2, 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"] end test "create table" do - create = {:create, table(:posts), - [{:add, :name, :string, [default: "Untitled", size: 20, null: false]}, - {:add, :price, :numeric, [precision: 8, scale: 2, default: {:fragment, "expr"}]}, - {:add, :on_hand, :integer, [default: 0, null: true]}, - {:add, :published_at, :"time without time zone", [null: true]}, - {:add, :is_active, :boolean, [default: true]}, - {:add, :tags, {:array, :string}, [default: []]}, - {:add, :languages, {:array, :string}, [default: ["pt", "es"]]}, - {:add, :limits, {:array, :integer}, [default: [100, 30_000]]}]} - - assert execute_ddl(create) == [""" - CREATE TABLE "posts" ("name" varchar(20) DEFAULT 'Untitled' NOT NULL, - "price" numeric(8,2) DEFAULT expr, - "on_hand" integer DEFAULT 0 NULL, - "published_at" time without time zone NULL, - "is_active" boolean DEFAULT true, - "tags" varchar(255)[] DEFAULT ARRAY[]::varchar[], - "languages" varchar(255)[] DEFAULT ARRAY['pt','es']::varchar[], - "limits" integer[] DEFAULT ARRAY[100,30000]::integer[]) - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :name, :string, [default: "Untitled", size: 20, null: false]}, + {:add, :price, :numeric, [precision: 8, scale: 2, default: {:fragment, "expr"}]}, + {:add, :on_hand, :integer, [default: 0, null: true]}, + {:add, :published_at, :"time without time zone", [null: true]}, + {:add, :is_active, :boolean, [default: true]}, + {:add, :tags, {:array, :string}, [default: []]}, + {:add, :languages, {:array, :string}, [default: ["pt", "es"]]}, + {:add, :limits, {:array, :integer}, [default: [100, 30_000]]} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE "posts" ("name" varchar(20) DEFAULT 'Untitled' NOT NULL, + "price" numeric(8,2) DEFAULT expr, + "on_hand" integer DEFAULT 0 NULL, + "published_at" time without time zone NULL, + "is_active" boolean DEFAULT true, + "tags" varchar(255)[] DEFAULT ARRAY[]::varchar[], + "languages" varchar(255)[] DEFAULT ARRAY['pt','es']::varchar[], + "limits" integer[] DEFAULT ARRAY[100,30000]::integer[]) + """ + |> remove_newlines + ] end test "create table with prefix" do - create = {:create, table(:posts, prefix: :foo), - [{:add, :category_0, %Reference{table: :categories}, []}]} + create = + {:create, table(:posts, prefix: :foo), + [{:add, :category_0, %Reference{table: :categories}, []}]} - assert execute_ddl(create) == [""" - CREATE TABLE "foo"."posts" - ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "foo"."categories"("id")) - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE "foo"."posts" + ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "foo"."categories"("id")) + """ + |> remove_newlines + ] end test "create table with comment on columns and table" do - create = {:create, table(:posts, comment: "comment"), - [ - {:add, :category_0, %Reference{table: :categories}, [comment: "column comment"]}, - {:add, :created_at, :timestamp, []}, - {:add, :updated_at, :timestamp, [comment: "column comment 2"]} - ]} - assert execute_ddl(create) == [remove_newlines(""" - CREATE TABLE "posts" - ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "categories"("id"), "created_at" timestamp, "updated_at" timestamp) - """), - ~s|COMMENT ON TABLE "posts" IS 'comment'|, - ~s|COMMENT ON COLUMN "posts"."category_0" IS 'column comment'|, - ~s|COMMENT ON COLUMN "posts"."updated_at" IS 'column comment 2'|] + create = + {:create, table(:posts, comment: "comment"), + [ + {:add, :category_0, %Reference{table: :categories}, [comment: "column comment"]}, + {:add, :created_at, :timestamp, []}, + {:add, :updated_at, :timestamp, [comment: "column comment 2"]} + ]} + + assert execute_ddl(create) == [ + remove_newlines(""" + CREATE TABLE "posts" + ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "categories"("id"), "created_at" timestamp, "updated_at" timestamp) + """), + ~s|COMMENT ON TABLE "posts" IS 'comment'|, + ~s|COMMENT ON COLUMN "posts"."category_0" IS 'column comment'|, + ~s|COMMENT ON COLUMN "posts"."updated_at" IS 'column comment 2'| + ] end test "create table with comment on table" do - create = {:create, table(:posts, comment: "table comment", prefix: "foo"), - [{:add, :category_0, %Reference{table: :categories}, []}]} - assert execute_ddl(create) == [remove_newlines(""" - CREATE TABLE "foo"."posts" - ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "foo"."categories"("id")) - """), - ~s|COMMENT ON TABLE "foo"."posts" IS 'table comment'|] + create = + {:create, table(:posts, comment: "table comment", prefix: "foo"), + [{:add, :category_0, %Reference{table: :categories}, []}]} + + assert execute_ddl(create) == [ + remove_newlines(""" + CREATE TABLE "foo"."posts" + ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "foo"."categories"("id")) + """), + ~s|COMMENT ON TABLE "foo"."posts" IS 'table comment'| + ] end test "create table with comment on columns" do - create = {:create, table(:posts, prefix: "foo"), - [ - {:add, :category_0, %Reference{table: :categories}, [comment: "column comment"]}, - {:add, :created_at, :timestamp, []}, - {:add, :updated_at, :timestamp, [comment: "column comment 2"]} - ]} - assert execute_ddl(create) == [remove_newlines(""" - CREATE TABLE "foo"."posts" - ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "foo"."categories"("id"), "created_at" timestamp, "updated_at" timestamp) - """), - ~s|COMMENT ON COLUMN "foo"."posts"."category_0" IS 'column comment'|, - ~s|COMMENT ON COLUMN "foo"."posts"."updated_at" IS 'column comment 2'|] + create = + {:create, table(:posts, prefix: "foo"), + [ + {:add, :category_0, %Reference{table: :categories}, [comment: "column comment"]}, + {:add, :created_at, :timestamp, []}, + {:add, :updated_at, :timestamp, [comment: "column comment 2"]} + ]} + + assert execute_ddl(create) == [ + remove_newlines(""" + CREATE TABLE "foo"."posts" + ("category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "foo"."categories"("id"), "created_at" timestamp, "updated_at" timestamp) + """), + ~s|COMMENT ON COLUMN "foo"."posts"."category_0" IS 'column comment'|, + ~s|COMMENT ON COLUMN "foo"."posts"."updated_at" IS 'column comment 2'| + ] end test "removing a column does not add a comment" do @@ -1651,91 +2046,133 @@ defmodule Ecto.Adapters.PostgresTest do end test "create table with references" do - create = {:create, table(:posts), - [{:add, :id, :serial, [primary_key: true]}, - {:add, :category_0, %Reference{table: :categories}, []}, - {:add, :category_1, %Reference{table: :categories, name: :foo_bar}, []}, - {:add, :category_2, %Reference{table: :categories, on_delete: :nothing}, []}, - {:add, :category_3, %Reference{table: :categories, on_delete: :delete_all}, [null: false]}, - {:add, :category_4, %Reference{table: :categories, on_delete: :nilify_all}, []}, - {:add, :category_5, %Reference{table: :categories, on_update: :nothing}, []}, - {:add, :category_6, %Reference{table: :categories, on_update: :update_all}, [null: false]}, - {:add, :category_7, %Reference{table: :categories, on_update: :nilify_all}, []}, - {:add, :category_8, %Reference{table: :categories, on_delete: :nilify_all, on_update: :update_all}, [null: false]}, - {:add, :category_9, %Reference{table: :categories, on_delete: :restrict}, []}, - {:add, :category_10, %Reference{table: :categories, on_update: :restrict}, []}, - {:add, :category_11, %Reference{table: :categories, prefix: "foo", on_update: :restrict}, []}, - {:add, :category_12, %Reference{table: :categories, with: [here: :there]}, []}, - {:add, :category_13, %Reference{table: :categories, on_update: :restrict, with: [here: :there], match: :full}, []}, - {:add, :category_14, %Reference{table: :categories, with: [here: :there, here2: :there2], on_delete: {:nilify, [:here, :here2]}}, []},]} - - assert execute_ddl(create) == [""" - CREATE TABLE "posts" ("id" serial, - "category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "categories"("id"), - "category_1" bigint, CONSTRAINT "foo_bar" FOREIGN KEY ("category_1") REFERENCES "categories"("id"), - "category_2" bigint, CONSTRAINT "posts_category_2_fkey" FOREIGN KEY ("category_2") REFERENCES "categories"("id"), - "category_3" bigint NOT NULL, CONSTRAINT "posts_category_3_fkey" FOREIGN KEY ("category_3") REFERENCES "categories"("id") ON DELETE CASCADE, - "category_4" bigint, CONSTRAINT "posts_category_4_fkey" FOREIGN KEY ("category_4") REFERENCES "categories"("id") ON DELETE SET NULL, - "category_5" bigint, CONSTRAINT "posts_category_5_fkey" FOREIGN KEY ("category_5") REFERENCES "categories"("id"), - "category_6" bigint NOT NULL, CONSTRAINT "posts_category_6_fkey" FOREIGN KEY ("category_6") REFERENCES "categories"("id") ON UPDATE CASCADE, - "category_7" bigint, CONSTRAINT "posts_category_7_fkey" FOREIGN KEY ("category_7") REFERENCES "categories"("id") ON UPDATE SET NULL, - "category_8" bigint NOT NULL, CONSTRAINT "posts_category_8_fkey" FOREIGN KEY ("category_8") REFERENCES "categories"("id") ON DELETE SET NULL ON UPDATE CASCADE, - "category_9" bigint, CONSTRAINT "posts_category_9_fkey" FOREIGN KEY ("category_9") REFERENCES "categories"("id") ON DELETE RESTRICT, - "category_10" bigint, CONSTRAINT "posts_category_10_fkey" FOREIGN KEY ("category_10") REFERENCES "categories"("id") ON UPDATE RESTRICT, - "category_11" bigint, CONSTRAINT "posts_category_11_fkey" FOREIGN KEY ("category_11") REFERENCES "foo"."categories"("id") ON UPDATE RESTRICT, - "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"), - PRIMARY KEY ("id")) - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :id, :serial, [primary_key: true]}, + {:add, :category_0, %Reference{table: :categories}, []}, + {:add, :category_1, %Reference{table: :categories, name: :foo_bar}, []}, + {:add, :category_2, %Reference{table: :categories, on_delete: :nothing}, []}, + {:add, :category_3, %Reference{table: :categories, on_delete: :delete_all}, + [null: false]}, + {:add, :category_4, %Reference{table: :categories, on_delete: :nilify_all}, []}, + {:add, :category_5, %Reference{table: :categories, on_update: :nothing}, []}, + {:add, :category_6, %Reference{table: :categories, on_update: :update_all}, + [null: false]}, + {:add, :category_7, %Reference{table: :categories, on_update: :nilify_all}, []}, + {:add, :category_8, + %Reference{table: :categories, on_delete: :nilify_all, on_update: :update_all}, + [null: false]}, + {:add, :category_9, %Reference{table: :categories, on_delete: :restrict}, []}, + {:add, :category_10, %Reference{table: :categories, on_update: :restrict}, []}, + {:add, :category_11, %Reference{table: :categories, prefix: "foo", on_update: :restrict}, + []}, + {:add, :category_12, %Reference{table: :categories, with: [here: :there]}, []}, + {:add, :category_13, + %Reference{ + table: :categories, + on_update: :restrict, + with: [here: :there], + match: :full + }, []}, + {:add, :category_14, + %Reference{ + table: :categories, + with: [here: :there, here2: :there2], + on_delete: {:nilify, [:here, :here2]} + }, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE "posts" ("id" serial, + "category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "categories"("id"), + "category_1" bigint, CONSTRAINT "foo_bar" FOREIGN KEY ("category_1") REFERENCES "categories"("id"), + "category_2" bigint, CONSTRAINT "posts_category_2_fkey" FOREIGN KEY ("category_2") REFERENCES "categories"("id"), + "category_3" bigint NOT NULL, CONSTRAINT "posts_category_3_fkey" FOREIGN KEY ("category_3") REFERENCES "categories"("id") ON DELETE CASCADE, + "category_4" bigint, CONSTRAINT "posts_category_4_fkey" FOREIGN KEY ("category_4") REFERENCES "categories"("id") ON DELETE SET NULL, + "category_5" bigint, CONSTRAINT "posts_category_5_fkey" FOREIGN KEY ("category_5") REFERENCES "categories"("id"), + "category_6" bigint NOT NULL, CONSTRAINT "posts_category_6_fkey" FOREIGN KEY ("category_6") REFERENCES "categories"("id") ON UPDATE CASCADE, + "category_7" bigint, CONSTRAINT "posts_category_7_fkey" FOREIGN KEY ("category_7") REFERENCES "categories"("id") ON UPDATE SET NULL, + "category_8" bigint NOT NULL, CONSTRAINT "posts_category_8_fkey" FOREIGN KEY ("category_8") REFERENCES "categories"("id") ON DELETE SET NULL ON UPDATE CASCADE, + "category_9" bigint, CONSTRAINT "posts_category_9_fkey" FOREIGN KEY ("category_9") REFERENCES "categories"("id") ON DELETE RESTRICT, + "category_10" bigint, CONSTRAINT "posts_category_10_fkey" FOREIGN KEY ("category_10") REFERENCES "categories"("id") ON UPDATE RESTRICT, + "category_11" bigint, CONSTRAINT "posts_category_11_fkey" FOREIGN KEY ("category_11") REFERENCES "foo"."categories"("id") ON UPDATE RESTRICT, + "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"), + PRIMARY KEY ("id")) + """ + |> remove_newlines + ] end test "create table with options" do - create = {:create, table(:posts, [options: "WITH FOO=BAR"]), - [{:add, :id, :serial, [primary_key: true]}, - {:add, :created_at, :naive_datetime, []}]} + create = + {:create, table(:posts, options: "WITH FOO=BAR"), + [{:add, :id, :serial, [primary_key: true]}, {:add, :created_at, :naive_datetime, []}]} + assert execute_ddl(create) == - [~s|CREATE TABLE "posts" ("id" serial, "created_at" timestamp(0), PRIMARY KEY ("id")) WITH FOO=BAR|] + [ + ~s|CREATE TABLE "posts" ("id" serial, "created_at" timestamp(0), PRIMARY KEY ("id")) WITH FOO=BAR| + ] end test "create table with composite key" do - create = {:create, table(:posts), - [{:add, :a, :integer, [primary_key: true]}, - {:add, :b, :integer, [primary_key: true]}, - {:add, :name, :string, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE "posts" ("a" integer, "b" integer, "name" varchar(255), PRIMARY KEY ("a","b")) - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :a, :integer, [primary_key: true]}, + {:add, :b, :integer, [primary_key: true]}, + {:add, :name, :string, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE "posts" ("a" integer, "b" integer, "name" varchar(255), PRIMARY KEY ("a","b")) + """ + |> remove_newlines + ] end test "create table with identity key and references" do - create = {:create, table(:posts), - [{:add, :id, :identity, [primary_key: true]}, - {:add, :category_0, %Reference{table: :categories, type: :identity}, []}, - {:add, :name, :string, []}]} - - assert execute_ddl(create) == [""" - CREATE TABLE "posts" ("id" bigint GENERATED BY DEFAULT AS IDENTITY, - "category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "categories"("id"), - "name" varchar(255), - PRIMARY KEY ("id")) - """ |> remove_newlines] + create = + {:create, table(:posts), + [ + {:add, :id, :identity, [primary_key: true]}, + {:add, :category_0, %Reference{table: :categories, type: :identity}, []}, + {:add, :name, :string, []} + ]} + + assert execute_ddl(create) == [ + """ + CREATE TABLE "posts" ("id" bigint GENERATED BY DEFAULT AS IDENTITY, + "category_0" bigint, CONSTRAINT "posts_category_0_fkey" FOREIGN KEY ("category_0") REFERENCES "categories"("id"), + "name" varchar(255), + PRIMARY KEY ("id")) + """ + |> remove_newlines + ] end test "create table with identity key options" do - create = {:create, table(:posts), - [{:add, :id, :identity, [primary_key: true, start_value: 1_000, increment: 10]}, - {:add, :name, :string, []}]} + create = + {:create, table(:posts), + [ + {:add, :id, :identity, [primary_key: true, start_value: 1_000, increment: 10]}, + {:add, :name, :string, []} + ]} - assert execute_ddl(create) == [""" - CREATE TABLE "posts" ("id" bigint GENERATED BY DEFAULT AS IDENTITY(START WITH 1000 INCREMENT BY 10) , "name" varchar(255), PRIMARY KEY ("id")) - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE "posts" ("id" bigint GENERATED BY DEFAULT AS IDENTITY(START WITH 1000 INCREMENT BY 10) , "name" varchar(255), PRIMARY KEY ("id")) + """ + |> remove_newlines + ] end test "create table with binary column and null-byte default" do - create = {:create, table(:blobs), - [{:add, :blob, :binary, [default: <<0>>]}]} + create = {:create, table(:blobs), [{:add, :blob, :binary, [default: <<0>>]}]} assert_raise ArgumentError, ~r/"\\x00"/, fn -> execute_ddl(create) @@ -1743,8 +2180,7 @@ defmodule Ecto.Adapters.PostgresTest do end test "create table with binary column and null-byte-containing default" do - create = {:create, table(:blobs), - [{:add, :blob, :binary, [default: "foo" <> <<0>>]}]} + create = {:create, table(:blobs), [{:add, :blob, :binary, [default: "foo" <> <<0>>]}]} assert_raise ArgumentError, ~r/"\\x666f6f00"/, fn -> execute_ddl(create) @@ -1752,117 +2188,155 @@ defmodule Ecto.Adapters.PostgresTest do end test "create table with binary column and UTF-8 default" do - create = {:create, table(:blobs), - [{:add, :blob, :binary, [default: "foo"]}]} + create = {:create, table(:blobs), [{:add, :blob, :binary, [default: "foo"]}]} - assert execute_ddl(create) == [""" - CREATE TABLE "blobs" ("blob" bytea DEFAULT 'foo') - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE "blobs" ("blob" bytea DEFAULT 'foo') + """ + |> remove_newlines + ] end test "create table with binary column and hex bytea literal default" do - create = {:create, table(:blobs), - [{:add, :blob, :binary, [default: "\\x666F6F"]}]} + create = {:create, table(:blobs), [{:add, :blob, :binary, [default: "\\x666F6F"]}]} - assert execute_ddl(create) == [""" - CREATE TABLE "blobs" ("blob" bytea DEFAULT '\\x666F6F') - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE "blobs" ("blob" bytea DEFAULT '\\x666F6F') + """ + |> remove_newlines + ] end test "create table with binary column and hex bytea literal null-byte" do - create = {:create, table(:blobs), - [{:add, :blob, :binary, [default: "\\x00"]}]} + create = {:create, table(:blobs), [{:add, :blob, :binary, [default: "\\x00"]}]} - assert execute_ddl(create) == [""" - CREATE TABLE "blobs" ("blob" bytea DEFAULT '\\x00') - """ |> remove_newlines] + assert execute_ddl(create) == [ + """ + CREATE TABLE "blobs" ("blob" bytea DEFAULT '\\x00') + """ + |> remove_newlines + ] end test "create table with a map column, and an empty map default" do - create = {:create, table(:posts), - [ - {:add, :a, :map, [default: %{}]} - ] - } + create = + {:create, table(:posts), + [ + {:add, :a, :map, [default: %{}]} + ]} + assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("a" jsonb DEFAULT '{}')|] end test "create table with a map column, and a map default with values" do - create = {:create, table(:posts), - [ - {:add, :a, :map, [default: %{foo: "bar", baz: "boom"}]} - ] - } - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("a" jsonb DEFAULT '{"baz":"boom","foo":"bar"}')|] + create = + {:create, table(:posts), + [ + {:add, :a, :map, [default: %{foo: "bar", baz: "boom"}]} + ]} + + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("a" jsonb DEFAULT '{"baz":"boom","foo":"bar"}')| + ] end test "create table with a map column, and a string default" do - create = {:create, table(:posts), - [ - {:add, :a, :map, [default: ~s|{"foo":"bar","baz":"boom"}|]} - ] - } - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("a" jsonb DEFAULT '{"foo":"bar","baz":"boom"}')|] + create = + {:create, table(:posts), + [ + {:add, :a, :map, [default: ~s|{"foo":"bar","baz":"boom"}|]} + ]} + + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("a" jsonb DEFAULT '{"foo":"bar","baz":"boom"}')| + ] end test "create table with time columns" do - create = {:create, table(:posts), - [{:add, :published_at, :time, [precision: 3]}, - {:add, :submitted_at, :time, []}]} + create = + {:create, table(:posts), + [{:add, :published_at, :time, [precision: 3]}, {:add, :submitted_at, :time, []}]} - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("published_at" time(0), "submitted_at" time(0))|] + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("published_at" time(0), "submitted_at" time(0))| + ] end test "create table with time_usec columns" do - create = {:create, table(:posts), - [{:add, :published_at, :time_usec, [precision: 3]}, - {:add, :submitted_at, :time_usec, []}]} + create = + {:create, table(:posts), + [{:add, :published_at, :time_usec, [precision: 3]}, {:add, :submitted_at, :time_usec, []}]} - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("published_at" time(3), "submitted_at" time)|] + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("published_at" time(3), "submitted_at" time)| + ] end test "create table with utc_datetime columns" do - create = {:create, table(:posts), - [{:add, :published_at, :utc_datetime, [precision: 3]}, - {:add, :submitted_at, :utc_datetime, []}]} + create = + {:create, table(:posts), + [ + {:add, :published_at, :utc_datetime, [precision: 3]}, + {:add, :submitted_at, :utc_datetime, []} + ]} - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("published_at" timestamp(0), "submitted_at" timestamp(0))|] + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("published_at" timestamp(0), "submitted_at" timestamp(0))| + ] end test "create table with utc_datetime_usec columns" do - create = {:create, table(:posts), - [{:add, :published_at, :utc_datetime_usec, [precision: 3]}, - {:add, :submitted_at, :utc_datetime_usec, []}]} + create = + {:create, table(:posts), + [ + {:add, :published_at, :utc_datetime_usec, [precision: 3]}, + {:add, :submitted_at, :utc_datetime_usec, []} + ]} - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("published_at" timestamp(3), "submitted_at" timestamp)|] + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("published_at" timestamp(3), "submitted_at" timestamp)| + ] end test "create table with naive_datetime columns" do - create = {:create, table(:posts), - [{:add, :published_at, :naive_datetime, [precision: 3]}, - {:add, :submitted_at, :naive_datetime, []}]} + create = + {:create, table(:posts), + [ + {:add, :published_at, :naive_datetime, [precision: 3]}, + {:add, :submitted_at, :naive_datetime, []} + ]} - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("published_at" timestamp(0), "submitted_at" timestamp(0))|] + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("published_at" timestamp(0), "submitted_at" timestamp(0))| + ] end test "create table with naive_datetime_usec columns" do - create = {:create, table(:posts), - [{:add, :published_at, :naive_datetime_usec, [precision: 3]}, - {:add, :submitted_at, :naive_datetime_usec, []}]} + create = + {:create, table(:posts), + [ + {:add, :published_at, :naive_datetime_usec, [precision: 3]}, + {:add, :submitted_at, :naive_datetime_usec, []} + ]} - assert execute_ddl(create) == [~s|CREATE TABLE "posts" ("published_at" timestamp(3), "submitted_at" timestamp)|] + assert execute_ddl(create) == [ + ~s|CREATE TABLE "posts" ("published_at" timestamp(3), "submitted_at" timestamp)| + ] end test "create table with an unsupported type" do - create = {:create, table(:posts), - [ - {:add, :a, {:a, :b, :c}, [default: %{}]} - ] - } + create = + {:create, table(:posts), + [ + {:add, :a, {:a, :b, :c}, [default: %{}]} + ]} + assert_raise ArgumentError, "unsupported type `{:a, :b, :c}`. " <> - "The type can either be an atom, a string or a tuple of the form " <> - "`{:map, t}` or `{:array, t}` where `t` itself follows the same conditions.", + "The type can either be an atom, a string or a tuple of the form " <> + "`{:map, t}` or `{:array, t}` where `t` itself follows the same conditions.", fn -> execute_ddl(create) end end @@ -1885,216 +2359,290 @@ defmodule Ecto.Adapters.PostgresTest do end test "alter table" 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]}, - {:modify, :cost, :integer, [null: false, default: nil]}, - {:modify, :permalink_id, %Reference{table: :permalinks}, null: false}, - {:modify, :status, :string, from: :integer}, - {:modify, :user_id, :integer, from: %Reference{table: :users}}, - {:modify, :space_id, :integer, null: true, from: {%Reference{table: :author}, null: false}}, - {:modify, :group_id, %Reference{table: :groups, column: :gid}, from: %Reference{table: :groups}}, - {:modify, :status, :string, [null: false, size: 100, from: {:integer, null: true, size: 50}]}, - {:remove, :summary}, - {:remove, :body, :text, []}, - {:remove, :space_id, %Reference{table: :author}, []}, - {:remove_if_exists, :body, :text}, - {:remove_if_exists, :space_id, %Reference{table: :author}}]} - - assert execute_ddl(alter) == [""" - ALTER TABLE "posts" - ADD COLUMN "title" varchar(100) DEFAULT 'Untitled' NOT NULL, - ADD COLUMN "author_id" bigint, - ADD CONSTRAINT "posts_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "author"("id"), - ADD COLUMN "category_id" bigint, - ADD CONSTRAINT "posts_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "categories"("id") NOT VALID, - ADD COLUMN IF NOT EXISTS "subtitle" varchar(100) NOT NULL, - ADD COLUMN IF NOT EXISTS "editor_id" bigint, - ADD CONSTRAINT "posts_editor_id_fkey" FOREIGN KEY ("editor_id") REFERENCES "editor"("id"), - ALTER COLUMN "price" TYPE numeric(8,2), - ALTER COLUMN "price" DROP NOT NULL, - ALTER COLUMN "cost" TYPE integer, - ALTER COLUMN "cost" SET NOT NULL, - ALTER COLUMN "cost" SET DEFAULT NULL, - ALTER COLUMN "permalink_id" TYPE bigint, - ADD CONSTRAINT "posts_permalink_id_fkey" FOREIGN KEY ("permalink_id") REFERENCES "permalinks"("id"), - ALTER COLUMN "permalink_id" SET NOT NULL, - ALTER COLUMN "status" TYPE varchar(255), - DROP CONSTRAINT "posts_user_id_fkey", - ALTER COLUMN "user_id" TYPE integer, - DROP CONSTRAINT "posts_space_id_fkey", - ALTER COLUMN "space_id" TYPE integer, - ALTER COLUMN "space_id" DROP NOT NULL, - DROP CONSTRAINT "posts_group_id_fkey", - ALTER COLUMN "group_id" TYPE bigint, - ADD CONSTRAINT "posts_group_id_fkey" FOREIGN KEY ("group_id") REFERENCES "groups"("gid"), - ALTER COLUMN "status" TYPE varchar(100), - ALTER COLUMN "status" SET NOT NULL, - DROP COLUMN "summary", - DROP COLUMN "body", - DROP CONSTRAINT "posts_space_id_fkey", - DROP COLUMN "space_id", - DROP COLUMN IF EXISTS "body", - DROP CONSTRAINT IF EXISTS "posts_space_id_fkey", - DROP COLUMN IF EXISTS "space_id" - """ |> remove_newlines] + 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]}, + {:modify, :cost, :integer, [null: false, default: nil]}, + {:modify, :permalink_id, %Reference{table: :permalinks}, null: false}, + {:modify, :status, :string, from: :integer}, + {:modify, :user_id, :integer, from: %Reference{table: :users}}, + {:modify, :space_id, :integer, + null: true, from: {%Reference{table: :author}, null: false}}, + {:modify, :group_id, %Reference{table: :groups, column: :gid}, + from: %Reference{table: :groups}}, + {:modify, :status, :string, + [null: false, size: 100, from: {:integer, null: true, size: 50}]}, + {:remove, :summary}, + {:remove, :body, :text, []}, + {:remove, :space_id, %Reference{table: :author}, []}, + {:remove_if_exists, :body, :text}, + {:remove_if_exists, :space_id, %Reference{table: :author}} + ]} + + assert execute_ddl(alter) == [ + """ + ALTER TABLE "posts" + ADD COLUMN "title" varchar(100) DEFAULT 'Untitled' NOT NULL, + ADD COLUMN "author_id" bigint, + ADD CONSTRAINT "posts_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "author"("id"), + ADD COLUMN "category_id" bigint, + ADD CONSTRAINT "posts_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "categories"("id") NOT VALID, + ADD COLUMN IF NOT EXISTS "subtitle" varchar(100) NOT NULL, + ADD COLUMN IF NOT EXISTS "editor_id" bigint, + ADD CONSTRAINT "posts_editor_id_fkey" FOREIGN KEY ("editor_id") REFERENCES "editor"("id"), + ALTER COLUMN "price" TYPE numeric(8,2), + ALTER COLUMN "price" DROP NOT NULL, + ALTER COLUMN "cost" TYPE integer, + ALTER COLUMN "cost" SET NOT NULL, + ALTER COLUMN "cost" SET DEFAULT NULL, + ALTER COLUMN "permalink_id" TYPE bigint, + ADD CONSTRAINT "posts_permalink_id_fkey" FOREIGN KEY ("permalink_id") REFERENCES "permalinks"("id"), + ALTER COLUMN "permalink_id" SET NOT NULL, + ALTER COLUMN "status" TYPE varchar(255), + DROP CONSTRAINT "posts_user_id_fkey", + ALTER COLUMN "user_id" TYPE integer, + DROP CONSTRAINT "posts_space_id_fkey", + ALTER COLUMN "space_id" TYPE integer, + ALTER COLUMN "space_id" DROP NOT NULL, + DROP CONSTRAINT "posts_group_id_fkey", + ALTER COLUMN "group_id" TYPE bigint, + ADD CONSTRAINT "posts_group_id_fkey" FOREIGN KEY ("group_id") REFERENCES "groups"("gid"), + ALTER COLUMN "status" TYPE varchar(100), + ALTER COLUMN "status" SET NOT NULL, + DROP COLUMN "summary", + DROP COLUMN "body", + DROP CONSTRAINT "posts_space_id_fkey", + DROP COLUMN "space_id", + DROP COLUMN IF EXISTS "body", + DROP CONSTRAINT IF EXISTS "posts_space_id_fkey", + DROP COLUMN IF EXISTS "space_id" + """ + |> remove_newlines + ] end test "alter table with comments on table and columns" do - alter = {:alter, table(:posts, comment: "table comment"), - [{:add, :title, :string, [default: "Untitled", size: 100, null: false, comment: "column comment"]}, - {:modify, :price, :numeric, [precision: 8, scale: 2, null: true]}, - {:modify, :permalink_id, %Reference{table: :permalinks}, [null: false, comment: "column comment"]}, - {:remove, :summary}]} - - assert execute_ddl(alter) == [remove_newlines(""" - ALTER TABLE "posts" - ADD COLUMN "title" varchar(100) DEFAULT 'Untitled' NOT NULL, - ALTER COLUMN "price" TYPE numeric(8,2), - ALTER COLUMN "price" DROP NOT NULL, - ALTER COLUMN "permalink_id" TYPE bigint, - ADD CONSTRAINT "posts_permalink_id_fkey" FOREIGN KEY ("permalink_id") REFERENCES "permalinks"("id"), - ALTER COLUMN "permalink_id" SET NOT NULL, - DROP COLUMN "summary" - """), - ~s|COMMENT ON TABLE \"posts\" IS 'table comment'|, - ~s|COMMENT ON COLUMN \"posts\".\"title\" IS 'column comment'|, - ~s|COMMENT ON COLUMN \"posts\".\"permalink_id\" IS 'column comment'|] - + alter = + {:alter, table(:posts, comment: "table comment"), + [ + {:add, :title, :string, + [default: "Untitled", size: 100, null: false, comment: "column comment"]}, + {:modify, :price, :numeric, [precision: 8, scale: 2, null: true]}, + {:modify, :permalink_id, %Reference{table: :permalinks}, + [null: false, comment: "column comment"]}, + {:remove, :summary} + ]} + + assert execute_ddl(alter) == [ + remove_newlines(""" + ALTER TABLE "posts" + ADD COLUMN "title" varchar(100) DEFAULT 'Untitled' NOT NULL, + ALTER COLUMN "price" TYPE numeric(8,2), + ALTER COLUMN "price" DROP NOT NULL, + ALTER COLUMN "permalink_id" TYPE bigint, + ADD CONSTRAINT "posts_permalink_id_fkey" FOREIGN KEY ("permalink_id") REFERENCES "permalinks"("id"), + ALTER COLUMN "permalink_id" SET NOT NULL, + DROP COLUMN "summary" + """), + ~s|COMMENT ON TABLE \"posts\" IS 'table comment'|, + ~s|COMMENT ON COLUMN \"posts\".\"title\" IS 'column comment'|, + ~s|COMMENT ON COLUMN \"posts\".\"permalink_id\" IS 'column comment'| + ] end test "alter table with prefix" do - alter = {:alter, table(:posts, prefix: :foo), - [{:add, :author_id, %Reference{table: :author}, []}, - {:modify, :permalink_id, %Reference{table: :permalinks}, null: false}]} - - assert execute_ddl(alter) == [""" - ALTER TABLE "foo"."posts" - ADD COLUMN "author_id" bigint, ADD CONSTRAINT "posts_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "foo"."author"("id"), - ALTER COLUMN \"permalink_id\" TYPE bigint, - ADD CONSTRAINT "posts_permalink_id_fkey" FOREIGN KEY ("permalink_id") REFERENCES "foo"."permalinks"("id"), - ALTER COLUMN "permalink_id" SET NOT NULL - """ |> remove_newlines] + alter = + {:alter, table(:posts, prefix: :foo), + [ + {:add, :author_id, %Reference{table: :author}, []}, + {:modify, :permalink_id, %Reference{table: :permalinks}, null: false} + ]} + + assert execute_ddl(alter) == [ + """ + ALTER TABLE "foo"."posts" + ADD COLUMN "author_id" bigint, ADD CONSTRAINT "posts_author_id_fkey" FOREIGN KEY ("author_id") REFERENCES "foo"."author"("id"), + ALTER COLUMN \"permalink_id\" TYPE bigint, + ADD CONSTRAINT "posts_permalink_id_fkey" FOREIGN KEY ("permalink_id") REFERENCES "foo"."permalinks"("id"), + ALTER COLUMN "permalink_id" SET NOT NULL + """ + |> remove_newlines + ] end test "alter table with serial primary key" do - alter = {:alter, table(:posts), - [{:add, :my_pk, :serial, [primary_key: true]}]} + alter = {:alter, table(:posts), [{:add, :my_pk, :serial, [primary_key: true]}]} - assert execute_ddl(alter) == [""" - ALTER TABLE "posts" - ADD COLUMN "my_pk" serial, - ADD PRIMARY KEY ("my_pk") - """ |> remove_newlines] + assert execute_ddl(alter) == [ + """ + ALTER TABLE "posts" + ADD COLUMN "my_pk" serial, + ADD PRIMARY KEY ("my_pk") + """ + |> remove_newlines + ] end test "alter table with bigserial primary key" do - alter = {:alter, table(:posts), - [{:add, :my_pk, :bigserial, [primary_key: true]}]} + alter = {:alter, table(:posts), [{:add, :my_pk, :bigserial, [primary_key: true]}]} - assert execute_ddl(alter) == [""" - ALTER TABLE "posts" - ADD COLUMN "my_pk" bigserial, - ADD PRIMARY KEY ("my_pk") - """ |> remove_newlines] + assert execute_ddl(alter) == [ + """ + ALTER TABLE "posts" + ADD COLUMN "my_pk" bigserial, + ADD PRIMARY KEY ("my_pk") + """ + |> remove_newlines + ] end test "create index" do create = {:create, index(:posts, [:category_id, :permalink])} + assert execute_ddl(create) == - [~s|CREATE INDEX "posts_category_id_permalink_index" ON "posts" ("category_id", "permalink")|] + [ + ~s|CREATE INDEX "posts_category_id_permalink_index" ON "posts" ("category_id", "permalink")| + ] create = {:create, index(:posts, ["lower(permalink)"], name: "posts$main")} + assert execute_ddl(create) == - [~s|CREATE INDEX "posts$main" ON "posts" (lower(permalink))|] + [~s|CREATE INDEX "posts$main" ON "posts" (lower(permalink))|] end test "create index with prefix" do create = {:create, index(:posts, [:category_id, :permalink], prefix: :foo)} + assert execute_ddl(create) == - [~s|CREATE INDEX "posts_category_id_permalink_index" ON "foo"."posts" ("category_id", "permalink")|] + [ + ~s|CREATE INDEX "posts_category_id_permalink_index" ON "foo"."posts" ("category_id", "permalink")| + ] create = {:create, index(:posts, ["lower(permalink)"], name: "posts$main", prefix: :foo)} + assert execute_ddl(create) == - [~s|CREATE INDEX "posts$main" ON "foo"."posts" (lower(permalink))|] + [~s|CREATE INDEX "posts$main" ON "foo"."posts" (lower(permalink))|] end test "create index with comment" do - create = {:create, index(:posts, [:category_id, :permalink], prefix: :foo, comment: "comment")} - assert execute_ddl(create) == [remove_newlines(""" - CREATE INDEX "posts_category_id_permalink_index" ON "foo"."posts" ("category_id", "permalink") - """), - ~s|COMMENT ON INDEX "foo"."posts_category_id_permalink_index" IS 'comment'|] + create = + {:create, index(:posts, [:category_id, :permalink], prefix: :foo, comment: "comment")} + + assert execute_ddl(create) == [ + remove_newlines(""" + CREATE INDEX "posts_category_id_permalink_index" ON "foo"."posts" ("category_id", "permalink") + """), + ~s|COMMENT ON INDEX "foo"."posts_category_id_permalink_index" IS 'comment'| + ] end test "create unique index" do create = {:create, index(:posts, [:permalink], unique: true)} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink")|] + [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink")|] end test "create unique index with condition" do create = {:create, index(:posts, [:permalink], unique: true, where: "public IS TRUE")} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") WHERE public IS TRUE|] + [ + ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") WHERE public IS TRUE| + ] create = {:create, index(:posts, [:permalink], unique: true, where: :public)} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") WHERE public|] + [ + ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") WHERE public| + ] end test "create index with include fields" do create = {:create, index(:posts, [:permalink], unique: true, include: [:public])} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") INCLUDE ("public")|] + [ + ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") INCLUDE ("public")| + ] + + create = + {:create, + index(:posts, [:permalink], unique: true, include: [:public], where: "public IS TRUE")} - create = {:create, index(:posts, [:permalink], unique: true, include: [:public], where: "public IS TRUE")} assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") INCLUDE ("public") WHERE public IS TRUE|] + [ + ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") INCLUDE ("public") WHERE public IS TRUE| + ] end test "create unique index with nulls_distinct option" do create = {:create, index(:posts, [:permalink], unique: true, nulls_distinct: true)} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") NULLS DISTINCT|] + [ + ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") NULLS DISTINCT| + ] create = {:create, index(:posts, [:permalink], unique: true, nulls_distinct: false)} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") NULLS NOT DISTINCT|] + [ + ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") NULLS NOT DISTINCT| + ] + + create = + {:create, + index(:posts, [:permalink], + unique: true, + nulls_distinct: false, + include: [:public], + where: "public IS TRUE" + )} - create = {:create, index(:posts, [:permalink], unique: true, nulls_distinct: false, include: [:public], where: "public IS TRUE")} assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") INCLUDE ("public") NULLS NOT DISTINCT WHERE public IS TRUE|] + [ + ~s|CREATE UNIQUE INDEX "posts_permalink_index" ON "posts" ("permalink") INCLUDE ("public") NULLS NOT DISTINCT WHERE public IS TRUE| + ] end test "create index concurrently" do index = index(:posts, [:permalink]) create = {:create, %{index | concurrently: true}} + assert execute_ddl(create) == - [~s|CREATE INDEX CONCURRENTLY "posts_permalink_index" ON "posts" ("permalink")|] + [~s|CREATE INDEX CONCURRENTLY "posts_permalink_index" ON "posts" ("permalink")|] end test "create unique index concurrently" do index = index(:posts, [:permalink], unique: true) create = {:create, %{index | concurrently: true}} + assert execute_ddl(create) == - [~s|CREATE UNIQUE INDEX CONCURRENTLY "posts_permalink_index" ON "posts" ("permalink")|] + [ + ~s|CREATE UNIQUE INDEX CONCURRENTLY "posts_permalink_index" ON "posts" ("permalink")| + ] end test "create an index using a different type" do create = {:create, index(:posts, [:permalink], using: :hash)} + assert execute_ddl(create) == - [~s|CREATE INDEX "posts_permalink_index" ON "posts" USING hash ("permalink")|] + [~s|CREATE INDEX "posts_permalink_index" ON "posts" USING hash ("permalink")|] end test "create an index without recursively creating indexes on partitions" do create = {:create, index(:posts, [:permalink], only: true)} + assert execute_ddl(create) == - [~s|CREATE INDEX "posts_permalink_index" ON ONLY "posts" ("permalink")|] + [~s|CREATE INDEX "posts_permalink_index" ON ONLY "posts" ("permalink")|] end test "drop index" do @@ -2123,64 +2671,109 @@ defmodule Ecto.Adapters.PostgresTest do test "rename index" do rename = {:rename, index(:people, [:name], name: "persons_name_index"), "people_name_index"} - assert execute_ddl(rename) == [~s|ALTER INDEX "persons_name_index" RENAME TO "people_name_index"|] + + assert execute_ddl(rename) == [ + ~s|ALTER INDEX "persons_name_index" RENAME TO "people_name_index"| + ] end test "create check constraint" do create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0")} + assert execute_ddl(create) == - [~s|ALTER TABLE "products" ADD CONSTRAINT "price_must_be_positive" CHECK (price > 0)|] + [ + ~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")} - 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)|] + [ + ~s|ALTER TABLE "foo"."products" ADD CONSTRAINT "price_must_be_positive" CHECK (price > 0)| + ] end test "create exclusion constraint" do - create = {:create, constraint(:products, "price_must_be_positive", exclude: ~s|gist (int4range("from", "to", '[]') WITH &&)|)} + create = + {:create, + constraint(:products, "price_must_be_positive", + exclude: ~s|gist (int4range("from", "to", '[]') WITH &&)| + )} + assert execute_ddl(create) == - [~s|ALTER TABLE "products" ADD CONSTRAINT "price_must_be_positive" EXCLUDE USING gist (int4range("from", "to", '[]') WITH &&)|] + [ + ~s|ALTER TABLE "products" ADD CONSTRAINT "price_must_be_positive" EXCLUDE USING gist (int4range("from", "to", '[]') WITH &&)| + ] end test "create constraint with comment" do - create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0", prefix: "foo", comment: "comment")} - assert execute_ddl(create) == [remove_newlines(""" - ALTER TABLE "foo"."products" ADD CONSTRAINT "price_must_be_positive" CHECK (price > 0) - """), - ~s|COMMENT ON CONSTRAINT "price_must_be_positive" ON "foo"."products" IS 'comment'|] + create = + {:create, + constraint(:products, "price_must_be_positive", + check: "price > 0", + prefix: "foo", + comment: "comment" + )} + + assert execute_ddl(create) == [ + remove_newlines(""" + ALTER TABLE "foo"."products" ADD CONSTRAINT "price_must_be_positive" CHECK (price > 0) + """), + ~s|COMMENT ON CONSTRAINT "price_must_be_positive" ON "foo"."products" IS 'comment'| + ] end test "create invalid constraint" do - 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 VALID|] + 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 VALID| + ] 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"|] + [~s|ALTER TABLE "products" DROP CONSTRAINT "price_must_be_positive"|] drop = {:drop, constraint(:products, "price_must_be_positive"), :cascade} + assert execute_ddl(drop) == - [~s|ALTER TABLE "products" DROP CONSTRAINT "price_must_be_positive" CASCADE|] + [~s|ALTER TABLE "products" DROP CONSTRAINT "price_must_be_positive" CASCADE|] 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"|] + [~s|ALTER TABLE "foo"."products" DROP CONSTRAINT "price_must_be_positive"|] end test "drop_if_exists constraint" do drop = {:drop_if_exists, constraint(:products, "price_must_be_positive"), :restrict} + assert execute_ddl(drop) == - [~s|ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "price_must_be_positive"|] + [~s|ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "price_must_be_positive"|] drop = {:drop_if_exists, constraint(:products, "price_must_be_positive"), :cascade} + assert execute_ddl(drop) == - [~s|ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "price_must_be_positive" CASCADE|] + [ + ~s|ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "price_must_be_positive" CASCADE| + ] + + drop = + {:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: "foo"), :restrict} - drop = {:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: "foo"), :restrict} assert execute_ddl(drop) == - [~s|ALTER TABLE "foo"."products" DROP CONSTRAINT IF EXISTS "price_must_be_positive"|] + [~s|ALTER TABLE "foo"."products" DROP CONSTRAINT IF EXISTS "price_must_be_positive"|] end test "rename table" do @@ -2200,7 +2793,10 @@ defmodule Ecto.Adapters.PostgresTest do test "rename column in prefixed table" do rename = {:rename, table(:posts, prefix: :foo), :given_name, :first_name} - assert execute_ddl(rename) == [~s|ALTER TABLE "foo"."posts" RENAME "given_name" TO "first_name"|] + + assert execute_ddl(rename) == [ + ~s|ALTER TABLE "foo"."posts" RENAME "given_name" TO "first_name"| + ] end test "logs DDL notices" do @@ -2226,6 +2822,6 @@ defmodule Ecto.Adapters.PostgresTest do end defp remove_newlines(string) do - string |> String.trim |> String.replace("\n", " ") + string |> String.trim() |> String.replace("\n", " ") end end diff --git a/test/ecto/adapters/tds_test.exs b/test/ecto/adapters/tds_test.exs index 045ea8a6..6fd3a7d2 100644 --- a/test/ecto/adapters/tds_test.exs +++ b/test/ecto/adapters/tds_test.exs @@ -97,7 +97,12 @@ defmodule Ecto.Adapters.TdsTest do end test "from with 3-part prefix" do - query = Schema |> select([r], r.x) |> Map.put(:prefix, {"server", "database", "db_schema"}) |> plan() + query = + Schema + |> select([r], r.x) + |> Map.put(:prefix, {"server", "database", "db_schema"}) + |> plan() + assert all(query) == ~s{SELECT s0.[x] FROM [server].[database].[db_schema].[schema] AS s0} end @@ -152,9 +157,11 @@ defmodule Ecto.Adapters.TdsTest do query = from(f in fragment("select_rows(arg)"), select: f.x) |> plan() assert all(query) == ~s{SELECT f0.[x] FROM select_rows(arg) AS f0} - assert_raise Ecto.QueryError, ~r"Tds adapter does not support selecting all fields from fragment", fn -> - all from(f in fragment("select ? as x", ^"abc"), select: f) |> plan() - end + assert_raise Ecto.QueryError, + ~r"Tds adapter does not support selecting all fields from fragment", + fn -> + all(from(f in fragment("select ? as x", ^"abc"), select: f) |> plan()) + end end test "join with subquery" do @@ -354,29 +361,32 @@ defmodule Ecto.Adapters.TdsTest do |> plan() assert all(query) == - ~s{SELECT c0.[id], s1.[breadcrumbs] FROM [categories] AS c0 } <> - ~s{OUTER APPLY } <> - ~s{(WITH [tree] ([id],[parent_id]) AS } <> - ~s{(SELECT ssc0.[id] AS [id], ssc0.[parent_id] AS [parent_id] FROM [categories] AS ssc0 WHERE (ssc0.[id] = c0.[id]) } <> - ~s{UNION ALL } <> - ~s{(SELECT ssc0.[id], ssc0.[parent_id] FROM [categories] AS ssc0 } <> - ~s{INNER JOIN [tree] AS sst1 ON sst1.[parent_id] = ssc0.[id])) } <> - ~s{SELECT STRING_AGG(st0.[id], ' / ') AS [breadcrumbs] FROM [tree] AS st0) AS s1} + ~s{SELECT c0.[id], s1.[breadcrumbs] FROM [categories] AS c0 } <> + ~s{OUTER APPLY } <> + ~s{(WITH [tree] ([id],[parent_id]) AS } <> + ~s{(SELECT ssc0.[id] AS [id], ssc0.[parent_id] AS [parent_id] FROM [categories] AS ssc0 WHERE (ssc0.[id] = c0.[id]) } <> + ~s{UNION ALL } <> + ~s{(SELECT ssc0.[id], ssc0.[parent_id] FROM [categories] AS ssc0 } <> + ~s{INNER JOIN [tree] AS sst1 ON sst1.[parent_id] = ssc0.[id])) } <> + ~s{SELECT STRING_AGG(st0.[id], ' / ') AS [breadcrumbs] FROM [tree] AS st0) AS s1} end test "parent binding subquery and combination" do right_query = from(c in "right_categories", where: c.id == parent_as(:c).id, select: c.id) left_query = from(c in "left_categories", where: c.id == parent_as(:c).id, select: c.id) union_query = union(left_query, ^right_query) - query = from(c in "categories", as: :c, where: c.id in subquery(union_query), select: c.id) |> plan() + + query = + from(c in "categories", as: :c, where: c.id in subquery(union_query), select: c.id) + |> plan() assert all(query) == - ~s{SELECT c0.[id] FROM [categories] AS c0 } <> - ~s{WHERE (} <> - ~s{c0.[id] IN } <> - ~s{(SELECT sl0.[id] FROM [left_categories] AS sl0 WHERE (sl0.[id] = c0.[id]) } <> - ~s{UNION } <> - ~s{(SELECT sr0.[id] FROM [right_categories] AS sr0 WHERE (sr0.[id] = c0.[id]))))} + ~s{SELECT c0.[id] FROM [categories] AS c0 } <> + ~s{WHERE (} <> + ~s{c0.[id] IN } <> + ~s{(SELECT sl0.[id] FROM [left_categories] AS sl0 WHERE (sl0.[id] = c0.[id]) } <> + ~s{UNION } <> + ~s{(SELECT sr0.[id] FROM [right_categories] AS sr0 WHERE (sr0.[id] = c0.[id]))))} end test "CTE with update statement" do @@ -392,9 +402,9 @@ defmodule Ecto.Adapters.TdsTest do |> select([c], %{id: c.id, desc: c.desc}) |> plan() - assert_raise Ecto.QueryError, ~r/Tds adapter does not support data-modifying CTEs/, fn -> - all(query) - end + assert_raise Ecto.QueryError, ~r/Tds adapter does not support data-modifying CTEs/, fn -> + all(query) + end end test "select" do @@ -664,7 +674,12 @@ defmodule Ecto.Adapters.TdsTest do query = Schema |> select([r], fragment("? COLLATE ?", r.x, literal(^"es_ES"))) |> plan() assert all(query) == ~s{SELECT s0.[x] COLLATE [es_ES] FROM [schema] AS s0} - query = Schema |> select([r], r.x) |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) |> plan() + query = + Schema + |> select([r], r.x) + |> where([r], fragment("? in (?,?,?)", r.x, ^1, splice(^[2, 3, 4]), ^5)) + |> plan() + assert all(query) == ~s{SELECT s0.[x] FROM [schema] AS s0 WHERE (s0.[x] in (@1,@2,@3,@4,@5))} value = 13 @@ -706,16 +721,29 @@ defmodule Ecto.Adapters.TdsTest do query = "schema" |> select([s], selected_as(s.x, :integer)) |> plan() assert all(query) == ~s{SELECT s0.[x] AS [integer] FROM [schema] AS s0} - query = "schema" |> select([s], s.x |> coalesce(0) |> sum() |> selected_as(:integer)) |> plan() + query = + "schema" |> select([s], s.x |> coalesce(0) |> sum() |> selected_as(:integer)) |> plan() + assert all(query) == ~s{SELECT sum(coalesce(s0.[x], 0)) AS [integer] FROM [schema] AS s0} end test "order_by can reference the alias of a selected value with selected_as/1s" do - query = "schema" |> select([s], selected_as(s.x, :integer)) |> order_by(selected_as(:integer)) |> plan() + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> order_by(selected_as(:integer)) + |> plan() + assert all(query) == ~s{SELECT s0.[x] AS [integer] FROM [schema] AS s0 ORDER BY [integer]} - query = "schema" |> select([s], selected_as(s.x, :integer)) |> order_by([desc: selected_as(:integer)]) |> plan() - assert all(query) == ~s{SELECT s0.[x] AS [integer] FROM [schema] AS s0 ORDER BY [integer] DESC} + query = + "schema" + |> select([s], selected_as(s.x, :integer)) + |> order_by(desc: selected_as(:integer)) + |> plan() + + assert all(query) == + ~s{SELECT s0.[x] AS [integer] FROM [schema] AS s0 ORDER BY [integer] DESC} end test "tagged type" do @@ -924,12 +952,13 @@ defmodule Ecto.Adapters.TdsTest do |> join(:inner, [p], p2 in subquery(sub), on: p.id == p2.id) |> update([_], set: [x: ^100]) - {planned_query, cast_params, dump_params} = Ecto.Adapter.Queryable.plan_query(:update_all, Ecto.Adapters.Tds, query) + {planned_query, cast_params, dump_params} = + Ecto.Adapter.Queryable.plan_query(:update_all, Ecto.Adapters.Tds, query) assert update_all(planned_query) == - ~s{UPDATE s0 SET s0.[x] = @1 FROM [schema] AS s0 INNER JOIN } <> - ~S{(SELECT ss0.[id] AS [id], ss0.[x] AS [x], ss0.[y] AS [y], ss0.[z] AS [z], ss0.[w] AS [w] FROM [schema] AS ss0 WHERE (ss0.[x] > @2)) } <> - ~S{AS s1 ON s0.[id] = s1.[id]} + ~s{UPDATE s0 SET s0.[x] = @1 FROM [schema] AS s0 INNER JOIN } <> + ~S{(SELECT ss0.[id] AS [id], ss0.[x] AS [x], ss0.[y] AS [y], ss0.[z] AS [z], ss0.[w] AS [w] FROM [schema] AS ss0 WHERE (ss0.[x] > @2)) } <> + ~S{AS s1 ON s0.[id] = s1.[id]} assert cast_params == [100, 10] assert dump_params == [100, 10] @@ -1084,11 +1113,15 @@ defmodule Ecto.Adapters.TdsTest do |> join( :inner, [p], - q in fragment(~S""" - SELECT * - FROM schema2 AS s2 - WHERE s2.id = ? AND s2.field = ? - """, p.x, ^10), + q in fragment( + ~S""" + SELECT * + FROM schema2 AS s2 + WHERE s2.id = ? AND s2.field = ? + """, + p.x, + ^10 + ), on: true ) |> select([p], {p.id, ^0}) @@ -1104,27 +1137,41 @@ defmodule Ecto.Adapters.TdsTest do end test "inner lateral join with fragment" do - query = Schema - |> join(:inner_lateral, [p], q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), on: true) - |> select([p, q], {p.id, q.z}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :inner_lateral, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), + on: true + ) + |> select([p, q], {p.id, q.z}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0.[id], f1.[z] FROM [schema] AS s0 CROSS APPLY } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.[x] AND s2.field = @1) AS f1 } <> - ~s{WHERE ((s0.[id] > 0) AND (s0.[id] < @2))} + ~s{SELECT s0.[id], f1.[z] FROM [schema] AS s0 CROSS APPLY } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.[x] AND s2.field = @1) AS f1 } <> + ~s{WHERE ((s0.[id] > 0) AND (s0.[id] < @2))} end test "left lateral join with fragment" do - query = Schema - |> join(:left_lateral, [p], q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), on: true) - |> select([p, q], {p.id, q.z}) - |> where([p], p.id > 0 and p.id < ^100) - |> plan() + query = + Schema + |> join( + :left_lateral, + [p], + q in fragment("SELECT * FROM schema2 AS s2 WHERE s2.id = ? AND s2.field = ?", p.x, ^10), + on: true + ) + |> select([p, q], {p.id, q.z}) + |> where([p], p.id > 0 and p.id < ^100) + |> plan() + assert all(query) == - ~s{SELECT s0.[id], f1.[z] FROM [schema] AS s0 OUTER APPLY } <> - ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.[x] AND s2.field = @1) AS f1 } <> - ~s{WHERE ((s0.[id] > 0) AND (s0.[id] < @2))} + ~s{SELECT s0.[id], f1.[z] FROM [schema] AS s0 OUTER APPLY } <> + ~s{(SELECT * FROM schema2 AS s2 WHERE s2.id = s0.[x] AND s2.field = @1) AS f1 } <> + ~s{WHERE ((s0.[id] > 0) AND (s0.[id] < @2))} end test "join with fragment and on defined" do @@ -1216,10 +1263,11 @@ defmodule Ecto.Adapters.TdsTest do end test "insert with query as rows" do - query = from(s in "schema", select: %{ foo: fragment("3"), bar: s.bar }) |> plan(:all) + query = from(s in "schema", select: %{foo: fragment("3"), bar: s.bar}) |> plan(:all) query = insert(nil, "schema", [:foo, :bar], query, {:raise, [], []}, [:foo]) - assert query == ~s{INSERT INTO [schema] ([foo],[bar]) OUTPUT INSERTED.[foo] SELECT 3, s0.[bar] FROM [schema] AS s0} + assert query == + ~s{INSERT INTO [schema] ([foo],[bar]) OUTPUT INSERTED.[foo] SELECT 3, s0.[bar] FROM [schema] AS s0} end test "update" do @@ -1263,9 +1311,9 @@ defmodule Ecto.Adapters.TdsTest do assert query == ~s{SELECT v1.[bid], v1.[num] } <> - ~s{FROM (VALUES (CAST(@1 AS uniqueidentifier),CAST(@2 AS integer)),(CAST(@3 AS uniqueidentifier),CAST(@4 AS integer))) AS v0 ([bid],[num]) } <> - ~s{INNER JOIN (VALUES (CAST(@5 AS uniqueidentifier),CAST(@6 AS integer)),(CAST(@7 AS uniqueidentifier),CAST(@8 AS integer))) AS v1 ([bid],[num]) ON v0.[bid] = v1.[bid] } <> - ~s{WHERE (v0.[num] = @9)} + ~s{FROM (VALUES (CAST(@1 AS uniqueidentifier),CAST(@2 AS integer)),(CAST(@3 AS uniqueidentifier),CAST(@4 AS integer))) AS v0 ([bid],[num]) } <> + ~s{INNER JOIN (VALUES (CAST(@5 AS uniqueidentifier),CAST(@6 AS integer)),(CAST(@7 AS uniqueidentifier),CAST(@8 AS integer))) AS v1 ([bid],[num]) ON v0.[bid] = v1.[bid] } <> + ~s{WHERE (v0.[num] = @9)} end test "values list: delete_all" do @@ -1280,8 +1328,8 @@ defmodule Ecto.Adapters.TdsTest do assert query == ~s{DELETE s0 FROM [schema] AS s0 } <> - ~s{INNER JOIN (VALUES (CAST(@1 AS uniqueidentifier),CAST(@2 AS integer)),(CAST(@3 AS uniqueidentifier),CAST(@4 AS integer))) AS v1 ([bid],[num]) } <> - ~s{ON s0.[x] = v1.[num] WHERE (v1.[num] = @5)} + ~s{INNER JOIN (VALUES (CAST(@1 AS uniqueidentifier),CAST(@2 AS integer)),(CAST(@3 AS uniqueidentifier),CAST(@4 AS integer))) AS v1 ([bid],[num]) } <> + ~s{ON s0.[x] = v1.[num] WHERE (v1.[num] = @5)} end test "values list: update_all" do @@ -1374,7 +1422,8 @@ defmodule Ecto.Adapters.TdsTest do {:add, :category_4, %Reference{table: :categories, on_delete: :nilify_all}, []}, {:add, :category_5, %Reference{table: :categories, prefix: :foo, on_delete: :nilify_all}, []}, - {:add, :category_6, %Reference{table: :categories, with: [here: :there], on_delete: :nilify_all}, []} + {:add, :category_6, + %Reference{table: :categories, with: [here: :there], on_delete: :nilify_all}, []} ]} assert execute_ddl(create) == [ @@ -1400,8 +1449,12 @@ defmodule Ecto.Adapters.TdsTest do |> Kernel.<>(" ") ] - create = {:create, table(:posts), - [{:add, :category_1, %Reference{table: :categories, on_delete: {:nilify, [:category_1]}}, []}]} + create = + {:create, table(:posts), + [ + {:add, :category_1, %Reference{table: :categories, on_delete: {:nilify, [:category_1]}}, + []} + ]} msg = "Tds adapter does not support the `{:nilify, columns}` action for `:on_delete`" assert_raise ArgumentError, msg, fn -> execute_ddl(create) end @@ -1501,7 +1554,8 @@ defmodule Ecto.Adapters.TdsTest do {:modify, :permalink_id, %Reference{table: :permalinks}, null: false}, {:modify, :status, :string, from: :integer}, {:modify, :user_id, :integer, from: %Reference{table: :users}}, - {:modify, :space_id, :integer, null: true, from: {%Reference{table: :author}, null: false}}, + {:modify, :space_id, :integer, + null: true, from: {%Reference{table: :author}, null: false}}, {:modify, :group_id, %Reference{table: :groups, column: :gid}, from: %Reference{table: :groups}}, {:remove, :summary} @@ -1606,9 +1660,11 @@ defmodule Ecto.Adapters.TdsTest do drop_cascade = {:drop, constraint(:products, "price_must_be_positive"), :cascade} - assert_raise ArgumentError, ~r"MSSQL does not support `CASCADE` in DROP CONSTRAINT commands", fn -> - execute_ddl(drop_cascade) - end + assert_raise ArgumentError, + ~r"MSSQL does not support `CASCADE` in DROP CONSTRAINT commands", + fn -> + execute_ddl(drop_cascade) + end end test "drop_if_exists constraint" do @@ -1622,7 +1678,8 @@ defmodule Ecto.Adapters.TdsTest do "ALTER TABLE [products] DROP CONSTRAINT [price_must_be_positive]; " ] - drop = {:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: "foo"), :restrict} + drop = + {:drop_if_exists, constraint(:products, "price_must_be_positive", prefix: "foo"), :restrict} assert execute_ddl(drop) == [ @@ -1635,9 +1692,11 @@ defmodule Ecto.Adapters.TdsTest do drop_cascade = {:drop_if_exists, constraint(:products, "price_must_be_positive"), :cascade} - assert_raise ArgumentError, ~r"MSSQL does not support `CASCADE` in DROP CONSTRAINT commands", fn -> - execute_ddl(drop_cascade) - end + assert_raise ArgumentError, + ~r"MSSQL does not support `CASCADE` in DROP CONSTRAINT commands", + fn -> + execute_ddl(drop_cascade) + end end test "rename table" do @@ -1745,7 +1804,9 @@ defmodule Ecto.Adapters.TdsTest do end test "drop index with prefix" do - drop = {:drop, index(:posts, [:id], name: "posts_category_id_permalink_index", prefix: :foo), :restrict} + drop = + {:drop, index(:posts, [:id], name: "posts_category_id_permalink_index", prefix: :foo), + :restrict} assert execute_ddl(drop) == [~s|DROP INDEX [posts_category_id_permalink_index] ON [foo].[posts]; |] @@ -1770,7 +1831,7 @@ defmodule Ecto.Adapters.TdsTest do drop_cascade = {:drop_if_exists, - index(:posts, [:id], name: "posts_category_id_permalink_index", prefix: :foo), :cascade} + index(:posts, [:id], name: "posts_category_id_permalink_index", prefix: :foo), :cascade} assert_raise ArgumentError, ~r"MSSQL does not support `CASCADE` in DROP INDEX commands", fn -> execute_ddl(drop_cascade) @@ -1784,7 +1845,10 @@ defmodule Ecto.Adapters.TdsTest do test "rename index" do rename = {:rename, index(:people, [:name], name: "persons_name_index"), "people_name_index"} - assert execute_ddl(rename) == [~s|sp_rename N'people.persons_name_index', N'people_name_index', N'INDEX'|] + + assert execute_ddl(rename) == [ + ~s|sp_rename N'people.persons_name_index', N'people_name_index', N'INDEX'| + ] end defp remove_newlines(string) when is_binary(string) do diff --git a/test/ecto/migration_test.exs b/test/ecto/migration_test.exs index 3a1b2938..b54ae524 100644 --- a/test/ecto/migration_test.exs +++ b/test/ecto/migration_test.exs @@ -15,7 +15,7 @@ defmodule Ecto.MigrationTest do setup meta do config = Application.get_env(:ecto_sql, TestRepo, []) Application.put_env(:ecto_sql, TestRepo, Keyword.merge(config, meta[:repo_config] || [])) - on_exit fn -> Application.put_env(:ecto_sql, TestRepo, config) end + on_exit(fn -> Application.put_env(:ecto_sql, TestRepo, config) end) end setup meta do @@ -53,29 +53,58 @@ defmodule Ecto.MigrationTest do test "creates an index" do assert index(:posts, [:title]) == - %Index{table: "posts", unique: false, name: :posts_title_index, columns: [:title]} + %Index{table: "posts", unique: false, name: :posts_title_index, columns: [:title]} + assert index("posts", [:title]) == - %Index{table: "posts", unique: false, name: :posts_title_index, columns: [:title]} + %Index{table: "posts", unique: false, name: :posts_title_index, columns: [:title]} + assert index(:posts, :title) == - %Index{table: "posts", unique: false, name: :posts_title_index, columns: [:title]} + %Index{table: "posts", unique: false, name: :posts_title_index, columns: [:title]} + assert index(:posts, ["lower(title)"]) == - %Index{table: "posts", unique: false, name: :posts_lower_title_index, columns: ["lower(title)"]} + %Index{ + table: "posts", + unique: false, + name: :posts_lower_title_index, + columns: ["lower(title)"] + } + assert index(:posts, [:title], name: :foo, unique: true) == - %Index{table: "posts", unique: true, name: :foo, columns: [:title]} - assert index(:posts, [:title], where: "status = 'published'", name: :published_posts_title_index, unique: true) == - %Index{table: "posts", unique: true, where: "status = 'published'", name: :published_posts_title_index, columns: [:title]} + %Index{table: "posts", unique: true, name: :foo, columns: [:title]} + + assert index(:posts, [:title], + where: "status = 'published'", + name: :published_posts_title_index, + unique: true + ) == + %Index{ + table: "posts", + unique: true, + where: "status = 'published'", + name: :published_posts_title_index, + columns: [:title] + } + assert unique_index(:posts, [:title], name: :foo) == - %Index{table: "posts", unique: true, name: :foo, columns: [:title]} + %Index{table: "posts", unique: true, name: :foo, columns: [:title]} + assert unique_index(:posts, :title, name: :foo) == - %Index{table: "posts", unique: true, name: :foo, columns: [:title]} + %Index{table: "posts", unique: true, name: :foo, columns: [:title]} + assert unique_index(:table_one__table_two, :title) == - %Index{table: "table_one__table_two", unique: true, name: :table_one__table_two_title_index, columns: [:title]} + %Index{ + table: "table_one__table_two", + unique: true, + name: :table_one__table_two_title_index, + columns: [:title] + } end test "raises if nulls_distinct is set for a non-unique index" do assert_raise ArgumentError, fn -> index(:posts, [:title], unique: false, nulls_distinct: false) end + assert_raise ArgumentError, fn -> index(:posts, [:title], unique: false, nulls_distinct: true) end @@ -89,65 +118,100 @@ defmodule Ecto.MigrationTest do test "creates a reference" do assert references(:posts) == - %Reference{table: "posts", column: :id, type: :bigserial} + %Reference{table: "posts", column: :id, type: :bigserial} + assert references(:posts, type: :identity) == - %Reference{table: "posts", column: :id, type: :identity} + %Reference{table: "posts", column: :id, type: :identity} + assert references("posts") == - %Reference{table: "posts", column: :id, type: :bigserial} + %Reference{table: "posts", column: :id, type: :bigserial} + assert references(:posts, type: :uuid, column: :other) == - %Reference{table: "posts", column: :other, type: :uuid, prefix: nil} + %Reference{table: "posts", column: :other, type: :uuid, prefix: nil} + assert references(:posts, type: :uuid, column: :other, prefix: :blog) == - %Reference{table: "posts", column: :other, type: :uuid, prefix: :blog} + %Reference{table: "posts", column: :other, type: :uuid, prefix: :blog} end @tag repo_config: [migration_foreign_key: [type: :uuid, column: :other, prefix: :blog]] test "create a reference with using the foreign key repo config" do assert references(:posts) == - %Reference{table: "posts", column: :other, type: :uuid, prefix: :blog} + %Reference{table: "posts", column: :other, type: :uuid, prefix: :blog} end @tag repo_config: [migration_primary_key: [type: :binary_id]] test "creates a reference with a foreign key type default to the primary key type" do assert references(:posts) == - %Reference{table: "posts", column: :id, type: :binary_id} + %Reference{table: "posts", column: :id, type: :binary_id} end @tag repo_config: [migration_primary_key: [type: :identity]] test "creates a reference with a foreign key type of identity" do assert references(:posts) == - %Reference{table: "posts", column: :id, type: :identity} + %Reference{table: "posts", column: :id, type: :identity} end test ":migration_cast_version_column option" do - {_repo, query, _options} = SchemaMigration.versions(TestRepo, [migration_cast_version_column: true], "") + {_repo, query, _options} = + SchemaMigration.versions(TestRepo, [migration_cast_version_column: true], "") + assert Macro.to_string(query.select.expr) == "type(&0.version(), :integer)" - {_repo, query, _options} = SchemaMigration.versions(TestRepo, [migration_cast_version_column: false], "") + {_repo, query, _options} = + SchemaMigration.versions(TestRepo, [migration_cast_version_column: false], "") + assert Macro.to_string(query.select.expr) == "&0.version()" end test "creates a reference without validating" do assert references(:posts, validate: false) == - %Reference{table: "posts", column: :id, type: :bigserial, 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", validate: true} + %Constraint{ + table: "posts", + name: :price_is_positive, + check: "price > 0", + validate: true + } + assert constraint("posts", :price_is_positive, check: "price > 0") == - %Constraint{table: "posts", name: :price_is_positive, check: "price > 0", validate: true} + %Constraint{ + table: "posts", + name: :price_is_positive, + check: "price > 0", + validate: true + } + assert constraint(:posts, :exclude_price, exclude: "price") == - %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: true} + %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: true} + assert constraint("posts", :exclude_price, exclude: "price") == - %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: true} + %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: true} + assert constraint(:posts, :price_is_positive, check: "price > 0", validate: false) == - %Constraint{table: "posts", name: :price_is_positive, check: "price > 0", validate: false} + %Constraint{ + table: "posts", + name: :price_is_positive, + check: "price > 0", + validate: false + } + assert constraint("posts", :price_is_positive, check: "price > 0", validate: false) == - %Constraint{table: "posts", name: :price_is_positive, check: "price > 0", validate: false} + %Constraint{ + table: "posts", + name: :price_is_positive, + check: "price > 0", + validate: false + } + assert constraint(:posts, :exclude_price, exclude: "price", validate: false) == - %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: false} + %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: false} + assert constraint("posts", :exclude_price, exclude: "price", validate: false) == - %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: false} + %Constraint{table: "posts", name: :exclude_price, exclude: "price", validate: false} end test "runs a reversible command" do @@ -185,45 +249,50 @@ defmodule Ecto.MigrationTest do end test "forward: creates a table" do - result = create(table = table(:posts)) do - add :title, :string - add :cost, :decimal, precision: 3 - add :likes, :"int UNSIGNED", default: 0 - add :author_id, references(:authors) - timestamps() - end + result = + create(table = table(:posts)) do + add :title, :string + add :cost, :decimal, precision: 3 + add :likes, :"int UNSIGNED", default: 0 + add :author_id, references(:authors) + timestamps() + end + flush() assert last_command() == - {:create, table, - [{:add, :id, :bigserial, [primary_key: true]}, - {:add, :title, :string, []}, - {:add, :cost, :decimal, [precision: 3]}, - {:add, :likes, :"int UNSIGNED", [default: 0]}, - {:add, :author_id, %Reference{table: "authors"}, []}, - {:add, :inserted_at, :naive_datetime, [null: false]}, - {:add, :updated_at, :naive_datetime, [null: false]}]} + {:create, table, + [ + {:add, :id, :bigserial, [primary_key: true]}, + {:add, :title, :string, []}, + {:add, :cost, :decimal, [precision: 3]}, + {:add, :likes, :"int UNSIGNED", [default: 0]}, + {:add, :author_id, %Reference{table: "authors"}, []}, + {:add, :inserted_at, :naive_datetime, [null: false]}, + {:add, :updated_at, :naive_datetime, [null: false]} + ]} assert result == table(:posts) create table = table(:posts, primary_key: false, timestamps: false) do add :title, :string end + flush() assert last_command() == - {:create, table, - [{:add, :title, :string, []}]} + {:create, table, [{:add, :title, :string, []}]} end @tag repo_config: [migration_primary_key: [name: :uuid, type: :uuid]] test "forward: create a table with custom primary key" do create(table = table(:posts)) do end + flush() assert last_command() == - {:create, table, [{:add, :uuid, :uuid, [primary_key: true]}]} + {:create, table, [{:add, :uuid, :uuid, [primary_key: true]}]} end @tag repo_config: [migration_primary_key: [name: :uuid, type: :uuid]] @@ -232,7 +301,7 @@ defmodule Ecto.MigrationTest do flush() assert last_command() == - {:create, table, [{:add, :uuid, :uuid, [primary_key: true]}]} + {:create, table, [{:add, :uuid, :uuid, [primary_key: true]}]} end test "forward: create a table with only custom primary key via table options" do @@ -240,34 +309,45 @@ defmodule Ecto.MigrationTest do flush() assert last_command() == - {:create, table, [{:add, :uuid, :uuid, [primary_key: true]}]} + {:create, table, [{:add, :uuid, :uuid, [primary_key: true]}]} end - @tag repo_config: [migration_primary_key: [type: :uuid, default: {:fragment, "gen_random_uuid()"}]] + @tag repo_config: [ + migration_primary_key: [type: :uuid, default: {:fragment, "gen_random_uuid()"}] + ] test "forward: create a table with custom primary key options" do create(table = table(:posts)) do end + flush() assert last_command() == - {:create, table, [{:add, :id, :uuid, [primary_key: true, default: {:fragment, "gen_random_uuid()"}]}]} + {:create, table, + [{:add, :id, :uuid, [primary_key: true, default: {:fragment, "gen_random_uuid()"}]}]} end test "forward: create a table with custom primary key options via table options" do - create(table = table(:posts, primary_key: [type: :uuid, default: {:fragment, "gen_random_uuid()"}])) do + create( + table = table(:posts, primary_key: [type: :uuid, default: {:fragment, "gen_random_uuid()"}]) + ) do end + flush() assert last_command() == - {:create, table, [{:add, :id, :uuid, [primary_key: true, default: {:fragment, "gen_random_uuid()"}]}]} + {:create, table, + [{:add, :id, :uuid, [primary_key: true, default: {:fragment, "gen_random_uuid()"}]}]} end test "forward: passing a value other than a bool to :primary_key on table/2 raises" do - assert_raise ArgumentError, ":primary_key option must be either a boolean or a keyword list of options", fn -> - create(table(:posts, primary_key: "not a valid value")) do - end - flush() - end + assert_raise ArgumentError, + ":primary_key option must be either a boolean or a keyword list of options", + fn -> + create(table(:posts, primary_key: "not a valid value")) do + end + + flush() + end end @tag repo_config: [migration_primary_key: false] @@ -282,6 +362,7 @@ defmodule Ecto.MigrationTest do test "forward: create a table block without a primary key by default via repo config" do create(table = table(:posts)) do end + flush() assert last_command() == {:create, table, []} @@ -292,13 +373,16 @@ defmodule Ecto.MigrationTest do create(table = table(:posts)) do timestamps() end + flush() assert last_command() == - {:create, table, [ - {:add, :id, :bigserial, [primary_key: true]}, - {:add, :inserted_at, :utc_datetime, [null: true]}, - {:add, :updated_at, :utc_datetime, [null: true]}]} + {:create, table, + [ + {:add, :id, :bigserial, [primary_key: true]}, + {:add, :inserted_at, :utc_datetime, [null: true]}, + {:add, :updated_at, :utc_datetime, [null: true]} + ]} end test "forward: creates a table without precision option for numeric type" do @@ -308,6 +392,7 @@ defmodule Ecto.MigrationTest do add :cost, :decimal, scale: 3 timestamps() end + flush() end end @@ -316,35 +401,41 @@ defmodule Ecto.MigrationTest do create table = table(:posts, primary_key: false) do timestamps(inserted_at: :created_at, updated_at: false) end + flush() assert last_command() == - {:create, table, - [{:add, :created_at, :naive_datetime, [null: false]}]} + {:create, table, [{:add, :created_at, :naive_datetime, [null: false]}]} end test "forward: creates a table with timestamps of type date" do create table = table(:posts, primary_key: false) do timestamps(inserted_at: :inserted_on, updated_at: :updated_on, type: :date) end + flush() assert last_command() == - {:create, table, - [{:add, :inserted_on, :date, [null: false]}, - {:add, :updated_on, :date, [null: false]}]} + {:create, table, + [ + {:add, :inserted_on, :date, [null: false]}, + {:add, :updated_on, :date, [null: false]} + ]} end test "forward: creates a table with timestamps of database specific type" do create table = table(:posts, primary_key: false) do timestamps(type: :"datetime(6)") end + flush() assert last_command() == - {:create, table, - [{:add, :inserted_at, :"datetime(6)", [null: false]}, - {:add, :updated_at, :"datetime(6)", [null: false]}]} + {:create, table, + [ + {:add, :inserted_at, :"datetime(6)", [null: false]}, + {:add, :updated_at, :"datetime(6)", [null: false]} + ]} end test "forward: creates an empty table" do @@ -352,7 +443,7 @@ defmodule Ecto.MigrationTest do flush() assert last_command() == - {:create, table, [{:add, :id, :bigserial, [primary_key: true]}]} + {:create, table, [{:add, :id, :bigserial, [primary_key: true]}]} end test "forward: alters a table" do @@ -364,33 +455,41 @@ defmodule Ecto.MigrationTest do remove :status, :string remove_if_exists :status, :string end + flush() assert last_command() == - {:alter, %Table{name: "posts"}, - [{:add, :summary, :text, []}, - {:add_if_not_exists, :summary, :text, []}, - {:modify, :title, :text, []}, - {:remove, :views}, - {:remove, :status, :string, []}, - {:remove_if_exists, :status, :string}] - } + {:alter, %Table{name: "posts"}, + [ + {:add, :summary, :text, []}, + {:add_if_not_exists, :summary, :text, []}, + {:modify, :title, :text, []}, + {:remove, :views}, + {:remove, :status, :string, []}, + {:remove_if_exists, :status, :string} + ]} end test "forward: removing a reference column (remove/3 called)" do alter table(:posts) do remove :author_id, references(:authors), [] end + flush() - assert {:alter, %Table{name: "posts"}, [{:remove, :author_id, %Reference{table: "authors"}, []}]} = last_command() + + assert {:alter, %Table{name: "posts"}, + [{:remove, :author_id, %Reference{table: "authors"}, []}]} = last_command() end test "forward: removing a reference if column (remove_if_exists/2 called)" do alter table(:posts) do remove_if_exists :author_id, references(:authors) end + flush() - assert {:alter, %Table{name: "posts"}, [{:remove_if_exists, :author_id, %Reference{table: "authors"}}]} = last_command() + + assert {:alter, %Table{name: "posts"}, + [{:remove_if_exists, :author_id, %Reference{table: "authors"}}]} = last_command() end test "forward: alter numeric column without specifying precision" do @@ -398,6 +497,7 @@ defmodule Ecto.MigrationTest do alter table(:posts) do modify :cost, :decimal, scale: 5 end + flush() end end @@ -407,12 +507,14 @@ defmodule Ecto.MigrationTest do alter table(:posts) do add_if_not_exists :cost, :decimal, scale: 5 end + flush() end end test "forward: alter datetime column invoke argument error" do - msg = "the :datetime type in migrations is not supported, please use :utc_datetime or :naive_datetime instead" + msg = + "the :datetime type in migrations is not supported, please use :utc_datetime or :naive_datetime instead" assert_raise ArgumentError, msg, fn -> alter table(:posts) do @@ -426,6 +528,7 @@ defmodule Ecto.MigrationTest do alter table(:posts) do modify(:hello, Ecto.DateTime) end + flush() end end @@ -594,10 +697,11 @@ defmodule Ecto.MigrationTest do @tag prefix: :bar test "forward: raise error when prefixes don't match" do assert_raise Ecto.MigrationError, - "the :prefix option `foo` does not match the migrator prefix `bar`", fn -> - create(table(:posts, prefix: "foo")) - flush() - end + "the :prefix option `foo` does not match the migrator prefix `bar`", + fn -> + create(table(:posts, prefix: "foo")) + flush() + end end test "forward: drops a table with prefix from migration" do @@ -705,13 +809,13 @@ defmodule Ecto.MigrationTest do end test "forward: executes a command from a file" do - in_tmp fn _path -> + in_tmp(fn _path -> up_sql = ~s(CREATE TABLE IF NOT EXISTS "execute_file_table" \(i integer\)) File.write!("up.sql", up_sql) - execute_file "up.sql" + execute_file("up.sql") flush() assert up_sql == last_command() - end + end) end test "fails gracefully with nested create" do @@ -719,6 +823,7 @@ defmodule Ecto.MigrationTest do create table(:posts) do create index(:posts, [:foo]) end + flush() end @@ -727,6 +832,7 @@ defmodule Ecto.MigrationTest do create table(:foo) do end end + flush() end end @@ -746,6 +852,7 @@ defmodule Ecto.MigrationTest do add :title, :string add :cost, :decimal, precision: 3 end + flush() assert last_command() == {:drop, table, :restrict} @@ -756,6 +863,7 @@ defmodule Ecto.MigrationTest do add :title, :string add :cost, :decimal, precision: 3 end + flush() assert last_command() == {:drop_if_exists, table, :restrict} @@ -774,24 +882,32 @@ defmodule Ecto.MigrationTest do modify :extension, :text, from: :string modify :author, :string, null: false, from: :text modify :title, :string, null: false, size: 100, from: {:text, null: true, size: 255} - modify :author_id, references(:authors), null: true, from: {references(:authors), null: false} + + modify :author_id, references(:authors), + null: true, + from: {references(:authors), null: false} end + flush() assert last_command() == - {:alter, %Table{name: "posts"}, [ - {:modify, :author_id, %Reference{table: "authors"}, [from: {%Reference{table: "authors"}, null: true}, null: false]}, - {:modify, :title, :text, [from: {:string, null: false, size: 100}, null: true, size: 255]}, - {:modify, :author, :text, [from: :string, null: false]}, - {:modify, :extension, :string, from: :text}, - {:remove, :summary, :text, []} - ]} + {:alter, %Table{name: "posts"}, + [ + {:modify, :author_id, %Reference{table: "authors"}, + [from: {%Reference{table: "authors"}, null: true}, null: false]}, + {:modify, :title, :text, + [from: {:string, null: false, size: 100}, null: true, size: 255]}, + {:modify, :author, :text, [from: :string, null: false]}, + {:modify, :extension, :string, from: :text}, + {:remove, :summary, :text, []} + ]} assert_raise Ecto.MigrationError, ~r/cannot reverse migration command/, fn -> alter table(:posts) do add :summary, :text modify :summary, :string end + flush() end @@ -800,6 +916,7 @@ defmodule Ecto.MigrationTest do add :summary, :text remove :summary end + flush() end end @@ -808,6 +925,7 @@ defmodule Ecto.MigrationTest do alter table(:posts) do remove :title, :string, [] end + flush() assert {:alter, %Table{name: "posts"}, [{:add, :title, :string, []}]} = last_command() end @@ -816,6 +934,7 @@ defmodule Ecto.MigrationTest do alter table(:posts) do remove :title, :string end + flush() assert {:alter, %Table{name: "posts"}, [{:add, :title, :string, []}]} = last_command() end @@ -824,8 +943,11 @@ defmodule Ecto.MigrationTest do alter table(:posts) do remove :author_id, references(:authors), [] end + flush() - assert {:alter, %Table{name: "posts"}, [{:add, :author_id, %Reference{table: "authors"}, []}]} = last_command() + + assert {:alter, %Table{name: "posts"}, [{:add, :author_id, %Reference{table: "authors"}, []}]} = + last_command() end test "backward: rename column" do @@ -880,17 +1002,17 @@ defmodule Ecto.MigrationTest do end test "backward: reverses a command from a file" do - in_tmp fn _path -> + in_tmp(fn _path -> up_sql = ~s(CREATE TABLE IF NOT EXISTS "execute_file_table" \(i integer\)) File.write!("up.sql", up_sql) down_sql = ~s(DROP TABLE IF EXISTS "execute_file_table") File.write!("down.sql", down_sql) - execute_file "up.sql", "down.sql" + execute_file("up.sql", "down.sql") flush() assert down_sql == last_command() - end + end) end test "backward: adding a reference column (column type is returned)" do diff --git a/test/ecto/migrator_repo_test.exs b/test/ecto/migrator_repo_test.exs index 9eadc2d1..6a5107c7 100644 --- a/test/ecto/migrator_repo_test.exs +++ b/test/ecto/migrator_repo_test.exs @@ -36,7 +36,7 @@ defmodule Ecto.MigratorRepoTest do use Ecto.Repo, otp_app: :ecto_sql, adapter: EctoSQL.TestAdapter end - Application.put_env(:ecto_sql, MainRepo, [migration_repo: MigrationRepo]) + Application.put_env(:ecto_sql, MainRepo, migration_repo: MigrationRepo) setup do {:ok, _} = start_supervised({MigrationsAgent, [{1, nil}, {2, nil}, {3, nil}]}) @@ -46,9 +46,9 @@ defmodule Ecto.MigratorRepoTest do def put_test_adapter_config(config) do Application.put_env(:ecto_sql, EctoSQL.TestAdapter, config) - on_exit fn -> + on_exit(fn -> Application.delete_env(:ecto, EctoSQL.TestAdapter) - end + end) end setup_all do @@ -60,7 +60,9 @@ defmodule Ecto.MigratorRepoTest do describe "migration_repo option" do test "upwards and downwards migrations" do assert run(MainRepo, [{3, ChangeMigration}, {4, Migration}], :up, to: 4, log: false) == [4] - assert run(MainRepo, [{2, ChangeMigration}, {3, Migration}], :down, all: true, log: false) == [3, 2] + + assert run(MainRepo, [{2, ChangeMigration}, {3, Migration}], :down, all: true, log: false) == + [3, 2] end test "down invokes the repository adapter with down commands" do @@ -74,13 +76,13 @@ defmodule Ecto.MigratorRepoTest do end test "migrations run inside a transaction if the adapter supports ddl transactions when configuring a migration repo" do - capture_log fn -> + capture_log(fn -> put_test_adapter_config(supports_ddl_transaction?: true, test_process: self()) up(MainRepo, 0, Migration) assert_receive {:transaction, %{repo: MainRepo}, _} assert_receive {:lock_for_migrations, %{repo: MigrationRepo}, _, _} - end + end) end end end diff --git a/test/ecto/migrator_test.exs b/test/ecto/migrator_test.exs index a638c93a..ac7d21bb 100644 --- a/test/ecto/migrator_test.exs +++ b/test/ecto/migrator_test.exs @@ -98,15 +98,14 @@ defmodule Ecto.MigratorTest do end defp flushed?() do - %{commands: commands} = - Agent.get(runner(), fn state -> state end) + %{commands: commands} = Agent.get(runner(), fn state -> state end) Enum.empty?(commands) end defp runner do case Process.get(:ecto_migration) do %{runner: runner} -> runner - _ -> raise "could not find migration runner process for #{inspect self()}" + _ -> raise "could not find migration runner process for #{inspect(self())}" end end end @@ -197,15 +196,15 @@ defmodule Ecto.MigratorTest do def put_test_adapter_config(config) do Application.put_env(:ecto_sql, EctoSQL.TestAdapter, config) - on_exit fn -> + on_exit(fn -> Application.delete_env(:ecto, EctoSQL.TestAdapter) - end + end) end defp create_migration(name, opts \\ []) do - module = name |> Path.basename |> Path.rootname + module = name |> Path.basename() |> Path.rootname() - File.write! name, """ + File.write!(name, """ defmodule Ecto.MigrationTest.S#{module} do use Ecto.Migration @@ -219,15 +218,15 @@ defmodule Ecto.MigratorTest do execute "down" end end - """ + """) end test "execute one anonymous function" do module = ExecuteOneAnonymousFunctionMigration num = System.unique_integer([:positive]) - capture_log(fn -> :ok = up(TestRepo, num, module, [log: false]) end) + capture_log(fn -> :ok = up(TestRepo, num, module, log: false) end) message = "no function clause matching in Ecto.Migration.Runner.command/1" - assert_raise(FunctionClauseError, message, fn -> down(TestRepo, num, module, [log: false]) end) + assert_raise(FunctionClauseError, message, fn -> down(TestRepo, num, module, log: false) end) end test "execute two anonymous functions" do @@ -252,6 +251,7 @@ defmodule Ecto.MigratorTest do assert :ok == down(TestRepo, num, EmptyUpDownMigration, log: false) assert :ok == up(TestRepo, num, EmptyChangeMigration, log: false) message = "calling flush() inside change when doing rollback is not supported." + assert_raise(RuntimeError, message, fn -> down(TestRepo, num, EmptyChangeMigration, log: false) end) @@ -264,7 +264,7 @@ defmodule Ecto.MigratorTest do assert [{10, :custom} | _] = MigrationsAgent.get() - Process.put(:repo_default_options, [prefix: nil]) + Process.put(:repo_default_options, prefix: nil) capture_log(fn -> :ok = up(TestRepo, 11, ChangeMigration, prefix: :custom) @@ -280,53 +280,59 @@ defmodule Ecto.MigratorTest do end test "logs migrations" do - output = capture_log fn -> - :ok = up(TestRepo, 10, ChangeMigration) - end + output = + capture_log(fn -> + :ok = up(TestRepo, 10, ChangeMigration) + end) assert output =~ "== Running 10 Ecto.MigratorTest.ChangeMigration.change/0 forward" assert output =~ "create table posts" assert output =~ "create index posts_title_index" assert output =~ ~r"== Migrated 10 in \d.\ds" - output = capture_log fn -> - :ok = down(TestRepo, 10, ChangeMigration) - end + output = + capture_log(fn -> + :ok = down(TestRepo, 10, ChangeMigration) + end) assert output =~ "== Running 10 Ecto.MigratorTest.ChangeMigration.change/0 backward" assert output =~ "drop table posts" assert output =~ "drop index posts_title_index" assert output =~ ~r"== Migrated 10 in \d.\ds" - output = capture_log fn -> - :ok = up(TestRepo, 11, ChangeMigrationPrefix) - end + output = + capture_log(fn -> + :ok = up(TestRepo, 11, ChangeMigrationPrefix) + end) assert output =~ "== Running 11 Ecto.MigratorTest.ChangeMigrationPrefix.change/0 forward" assert output =~ "create table foo.comments" assert output =~ "create index foo.posts_title_index" assert output =~ ~r"== Migrated 11 in \d.\ds" - output = capture_log fn -> - :ok = down(TestRepo, 11, ChangeMigrationPrefix) - end + output = + capture_log(fn -> + :ok = down(TestRepo, 11, ChangeMigrationPrefix) + end) assert output =~ "== Running 11 Ecto.MigratorTest.ChangeMigrationPrefix.change/0 backward" assert output =~ "drop table foo.comments" assert output =~ "drop index foo.posts_title_index" assert output =~ ~r"== Migrated 11 in \d.\ds" - output = capture_log fn -> - :ok = up(TestRepo, 12, UpDownMigration) - end + output = + capture_log(fn -> + :ok = up(TestRepo, 12, UpDownMigration) + end) assert output =~ "== Running 12 Ecto.MigratorTest.UpDownMigration.up/0 forward" assert output =~ "alter table posts" assert output =~ ~r"== Migrated 12 in \d.\ds" - output = capture_log fn -> - :ok = down(TestRepo, 12, UpDownMigration) - end + output = + capture_log(fn -> + :ok = down(TestRepo, 12, UpDownMigration) + end) assert output =~ "== Running 12 Ecto.MigratorTest.UpDownMigration.down/0 forward" assert output =~ "execute \"foo\"" @@ -334,26 +340,34 @@ defmodule Ecto.MigratorTest do end test "logs ddl notices" do - output = capture_log fn -> - :ok = up(TestRepo, 10, ChangeMigration) - end + output = + capture_log(fn -> + :ok = up(TestRepo, 10, ChangeMigration) + end) + assert output =~ "execute ddl" - output = capture_log fn -> - :ok = down(TestRepo, 10, ChangeMigration) - end + output = + capture_log(fn -> + :ok = down(TestRepo, 10, ChangeMigration) + end) + assert output =~ "execute ddl" end test "silences ddl notices when log is set to false" do - output = capture_log fn -> - :ok = up(TestRepo, 10, ChangeMigration, log: false) - end + output = + capture_log(fn -> + :ok = up(TestRepo, 10, ChangeMigration, log: false) + end) + refute output =~ "execute ddl" - output = capture_log fn -> - :ok = down(TestRepo, 10, ChangeMigration, log: false) - end + output = + capture_log(fn -> + :ok = down(TestRepo, 10, ChangeMigration, log: false) + end) + refute output =~ "execute ddl" end @@ -365,8 +379,9 @@ defmodule Ecto.MigratorTest do test "up invokes the repository adapter with up commands" do assert capture_log(fn -> - assert up(TestRepo, 0, Migration, log: false) == :ok - end) =~ "You are running migration 0 but an older migration with version 3 has already run" + assert up(TestRepo, 0, Migration, log: false) == :ok + end) =~ + "You are running migration 0 but an older migration with version 3 has already run" assert up(TestRepo, 1, Migration, log: false) == :already_up assert up(TestRepo, 10, ChangeMigration, log: false) == :ok @@ -423,8 +438,8 @@ defmodule Ecto.MigratorTest do end test "on run" do - in_tmp fn path -> - create_migration "13_sample.exs" + in_tmp(fn path -> + create_migration("13_sample.exs") assert run(TestRepo, path, :up, all: true, log: false) == [13] # One lock for fetching versions, another for running assert_receive {:lock_for_migrations, _, _, opts} @@ -432,13 +447,13 @@ defmodule Ecto.MigratorTest do assert_receive {:lock_for_migrations, _, _, opts} assert opts[:migration_source] == "schema_migrations" - create_migration "14_sample.exs", [:disable_migration_lock] + create_migration("14_sample.exs", [:disable_migration_lock]) assert run(TestRepo, path, :up, all: true, log: false) == [14] # One lock for fetching versions, another from running assert_receive {:lock_for_migrations, _, _, opts} assert opts[:migration_source] == "schema_migrations" refute_received {:lock_for_migrations, _, _, _} - end + end) end end @@ -451,8 +466,8 @@ defmodule Ecto.MigratorTest do end test "skip schema migrations table creation" do - in_tmp fn path -> - create_migration "15_sample.exs" + in_tmp(fn path -> + create_migration("15_sample.exs") expected_result = [{:up, 15, "sample"}] assert migrations(TestRepo, path, skip_table_creation: true) == expected_result @@ -462,12 +477,12 @@ defmodule Ecto.MigratorTest do assert opts[:skip_table_creation] == true assert match?(nil, last_command()) - end + end) end test "default to create schema migrations table" do - in_tmp fn path -> - create_migration "15_sample.exs" + in_tmp(fn path -> + create_migration("15_sample.exs") expected_result = [{:up, 15, "sample"}] assert migrations(TestRepo, path) == expected_result @@ -476,153 +491,166 @@ defmodule Ecto.MigratorTest do refute_received {:lock_for_migrations, _, _, _} assert match?({:create_if_not_exists, %_{name: :schema_migrations}, _}, last_command()) - end + end) end end describe "run" do test "expects files starting with an integer" do - in_tmp fn path -> - create_migration "a_sample.exs" + in_tmp(fn path -> + create_migration("a_sample.exs") assert run(TestRepo, path, :up, all: true, log: false) == [] - end + end) end test "fails if there is no migration in file" do - in_tmp fn path -> - File.write! "13_sample.exs", ":ok" - assert_raise Ecto.MigrationError, "file 13_sample.exs does not define an Ecto.Migration", fn -> - run(TestRepo, path, :up, all: true, log: false) - end - end + in_tmp(fn path -> + File.write!("13_sample.exs", ":ok") + + assert_raise Ecto.MigrationError, + "file 13_sample.exs does not define an Ecto.Migration", + fn -> + run(TestRepo, path, :up, all: true, log: false) + end + end) end test "fails if there are duplicated versions" do - in_tmp fn path -> - create_migration "13_hello.exs" - create_migration "13_other.exs" - assert_raise Ecto.MigrationError, "migrations can't be executed, migration version 13 is duplicated", fn -> - run(TestRepo, path, :up, all: true, log: false) - end - end + in_tmp(fn path -> + create_migration("13_hello.exs") + create_migration("13_other.exs") + + assert_raise Ecto.MigrationError, + "migrations can't be executed, migration version 13 is duplicated", + fn -> + run(TestRepo, path, :up, all: true, log: false) + end + end) end test "fails if there are duplicated name" do - in_tmp fn path -> - create_migration "13_hello.exs" - create_migration "14_hello.exs" - assert_raise Ecto.MigrationError, "migrations can't be executed, migration name hello is duplicated", fn -> - run(TestRepo, path, :up, all: true, log: false) - end - end + in_tmp(fn path -> + create_migration("13_hello.exs") + create_migration("14_hello.exs") + + assert_raise Ecto.MigrationError, + "migrations can't be executed, migration name hello is duplicated", + fn -> + run(TestRepo, path, :up, all: true, log: false) + end + end) end test "upwards migrations skips migrations that are already up" do - in_tmp fn path -> - create_migration "1_sample.exs" + in_tmp(fn path -> + create_migration("1_sample.exs") assert run(TestRepo, path, :up, all: true, log: false) == [] - end + end) end test "downwards migrations skips migrations that are already down" do - in_tmp fn path -> - create_migration "1_sample1.exs" - create_migration "4_sample2.exs" + in_tmp(fn path -> + create_migration("1_sample1.exs") + create_migration("4_sample2.exs") assert run(TestRepo, path, :down, all: true, log: false) == [1] - end + end) end test "stepwise migrations stop before all have been run" do - in_tmp fn path -> - create_migration "13_step_premature_end1.exs" - create_migration "14_step_premature_end2.exs" + in_tmp(fn path -> + create_migration("13_step_premature_end1.exs") + create_migration("14_step_premature_end2.exs") assert run(TestRepo, path, :up, step: 1, log: false) == [13] - end + end) end test "stepwise migrations stop at the number of available migrations" do - in_tmp fn path -> - create_migration "13_step_to_the_end1.exs" - create_migration "14_step_to_the_end2.exs" + in_tmp(fn path -> + create_migration("13_step_to_the_end1.exs") + create_migration("14_step_to_the_end2.exs") assert run(TestRepo, path, :up, step: 2, log: false) == [13, 14] - end + end) end test "stepwise migrations stop even if asked to exceed available" do - in_tmp fn path -> - create_migration "13_step_past_the_end1.exs" - create_migration "14_step_past_the_end2.exs" + in_tmp(fn path -> + create_migration("13_step_past_the_end1.exs") + create_migration("14_step_past_the_end2.exs") assert run(TestRepo, path, :up, step: 3, log: false) == [13, 14] - end + end) end test "version migrations stop before all have been run" do - in_tmp fn path -> - create_migration "13_version_premature_end1.exs" - create_migration "14_version_premature_end2.exs" + in_tmp(fn path -> + create_migration("13_version_premature_end1.exs") + create_migration("14_version_premature_end2.exs") assert run(TestRepo, path, :up, to: 13, log: false) == [13] - end + end) end test "version migrations stop at the number of available migrations" do - in_tmp fn path -> - create_migration "13_version_to_the_end1.exs" - create_migration "14_version_to_the_end2.exs" + in_tmp(fn path -> + create_migration("13_version_to_the_end1.exs") + create_migration("14_version_to_the_end2.exs") assert run(TestRepo, path, :up, to: 14, log: false) == [13, 14] - end + end) end test "version migrations stop even if asked to exceed available" do - in_tmp fn path -> - create_migration "13_version_past_the_end1.exs" - create_migration "14_version_past_the_end2.exs" + in_tmp(fn path -> + create_migration("13_version_past_the_end1.exs") + create_migration("14_version_past_the_end2.exs") assert run(TestRepo, path, :up, to: 15, log: false) == [13, 14] - end + end) end test "version migrations work inside directories" do - in_tmp fn path -> + in_tmp(fn path -> File.mkdir_p!("foo") - create_migration "foo/13_version_in_dir.exs" + create_migration("foo/13_version_in_dir.exs") assert run(TestRepo, Path.join(path, "foo"), :up, to: 15, log: false) == [13] - end + end) end test "migrations from multiple paths are executed in order" do in_tmp(fn path -> File.mkdir_p!("a") File.mkdir_p!("b") - create_migration "a/10_migration10.exs" - create_migration "a/12_migration12.exs" - create_migration "b/11_migration11.exs" + create_migration("a/10_migration10.exs") + create_migration("a/12_migration12.exs") + create_migration("b/11_migration11.exs") paths = [Path.join([path, "a"]), Path.join([path, "b"])] assert run(TestRepo, paths, :up, to: 12, log: false) == [10, 11, 12] end) end test "raises if target is not integer" do - in_tmp fn path -> + in_tmp(fn path -> message_base = "no function clause matching in " assert_raise(FunctionClauseError, message_base <> "Ecto.Migrator.pending_to/4", fn -> run(TestRepo, path, :up, to: "123") end) - assert_raise(FunctionClauseError, message_base <> "Ecto.Migrator.pending_to_exclusive/4", fn -> - run(TestRepo, path, :up, to_exclusive: "123") - end) - end + assert_raise( + FunctionClauseError, + message_base <> "Ecto.Migrator.pending_to_exclusive/4", + fn -> + run(TestRepo, path, :up, to_exclusive: "123") + end + ) + end) end end describe "migrations" do test "give the up and down migration status" do - in_tmp fn path -> - create_migration "1_up_migration_1.exs" - create_migration "2_up_migration_2.exs" - create_migration "3_up_migration_3.exs" - create_migration "4_down_migration_1.exs" - create_migration "5_down_migration_2.exs" + in_tmp(fn path -> + create_migration("1_up_migration_1.exs") + create_migration("2_up_migration_2.exs") + create_migration("3_up_migration_3.exs") + create_migration("4_down_migration_1.exs") + create_migration("5_down_migration_2.exs") expected_result = [ {:up, 1, "up_migration_1"}, @@ -633,27 +661,27 @@ defmodule Ecto.MigratorTest do ] assert migrations(TestRepo, path) == expected_result - end + end) end test "are picked up from subdirs" do in_tmp(fn path -> File.mkdir_p!("foo") - create_migration "foo/6_up_migration_1.exs" - create_migration "7_up_migration_2.exs" - create_migration "8_up_migration_3.exs" + create_migration("foo/6_up_migration_1.exs") + create_migration("7_up_migration_2.exs") + create_migration("8_up_migration_3.exs") assert run(TestRepo, path, :up, all: true, log: false) == [6, 7, 8] end) end test "give the migration status while file is deleted" do - in_tmp fn path -> - create_migration "1_up_migration_1.exs" - create_migration "2_up_migration_2.exs" - create_migration "3_up_migration_3.exs" - create_migration "4_down_migration_1.exs" + in_tmp(fn path -> + create_migration("1_up_migration_1.exs") + create_migration("2_up_migration_2.exs") + create_migration("3_up_migration_3.exs") + create_migration("4_down_migration_1.exs") File.rm("2_up_migration_2.exs") @@ -661,83 +689,97 @@ defmodule Ecto.MigratorTest do {:up, 1, "up_migration_1"}, {:up, 2, "** FILE NOT FOUND **"}, {:up, 3, "up_migration_3"}, - {:down, 4, "down_migration_1"}, + {:down, 4, "down_migration_1"} ] assert migrations(TestRepo, path) == expected_result - end + end) end test "multiple paths" do in_tmp(fn path -> File.mkdir_p!("a") File.mkdir_p!("b") - create_migration "a/1_up_migration_1.exs" - create_migration "b/2_up_migration_2.exs" - create_migration "a/3_up_migration_3.exs" - - assert migrations(TestRepo, [Path.join([path, "a"]), Path.join([path, "b"])]) == [ - {:up, 1, "up_migration_1"}, - {:up, 2, "up_migration_2"}, - {:up, 3, "up_migration_3"}, - ] - - assert migrations(TestRepo, [Path.join([path, "a"])]) == [ - {:up, 1, "up_migration_1"}, - {:up, 2, "** FILE NOT FOUND **"}, - {:up, 3, "up_migration_3"}, - ] + create_migration("a/1_up_migration_1.exs") + create_migration("b/2_up_migration_2.exs") + create_migration("a/3_up_migration_3.exs") + + assert migrations(TestRepo, [Path.join([path, "a"]), Path.join([path, "b"])]) == [ + {:up, 1, "up_migration_1"}, + {:up, 2, "up_migration_2"}, + {:up, 3, "up_migration_3"} + ] + + assert migrations(TestRepo, [Path.join([path, "a"])]) == [ + {:up, 1, "up_migration_1"}, + {:up, 2, "** FILE NOT FOUND **"}, + {:up, 3, "up_migration_3"} + ] end) end test "run inside a transaction if the adapter supports ddl transactions" do - capture_log fn -> + capture_log(fn -> put_test_adapter_config(supports_ddl_transaction?: true, test_process: self()) up(TestRepo, 0, ChangeMigration) assert_receive {:transaction, _, _} - end + end) end test "can be forced to run outside a transaction" do - capture_log fn -> + capture_log(fn -> put_test_adapter_config(supports_ddl_transaction?: true, test_process: self()) up(TestRepo, 0, NoTransactionMigration) refute_received {:transaction, _} - end + end) end test "does not run inside a transaction if the adapter does not support ddl transactions" do - capture_log fn -> + capture_log(fn -> put_test_adapter_config(supports_ddl_transaction?: false, test_process: self()) up(TestRepo, 0, ChangeMigration) refute_received {:transaction, _} - end + end) end end describe "alternate migration source format" do test "fails if there is no migration in file" do - assert_raise Ecto.MigrationError, "module Ecto.MigratorTest.EmptyModule is not an Ecto.Migration", fn -> - run(TestRepo, [{13, EmptyModule}], :up, all: true, log: false) - end + assert_raise Ecto.MigrationError, + "module Ecto.MigratorTest.EmptyModule is not an Ecto.Migration", + fn -> + run(TestRepo, [{13, EmptyModule}], :up, all: true, log: false) + end end test "fails if the module does not define migrations" do - assert_raise Ecto.MigrationError, "Ecto.MigratorTest.InvalidMigration does not implement a `up/0` or `change/0` function", fn -> - run(TestRepo, [{13, InvalidMigration}], :up, all: true, log: false) - end + assert_raise Ecto.MigrationError, + "Ecto.MigratorTest.InvalidMigration does not implement a `up/0` or `change/0` function", + fn -> + run(TestRepo, [{13, InvalidMigration}], :up, all: true, log: false) + end end test "fails if there are duplicated versions" do - assert_raise Ecto.MigrationError, "migrations can't be executed, migration version 13 is duplicated", fn -> - run(TestRepo, [{13, ChangeMigration}, {13, UpDownMigration}], :up, all: true, log: false) - end + assert_raise Ecto.MigrationError, + "migrations can't be executed, migration version 13 is duplicated", + fn -> + run(TestRepo, [{13, ChangeMigration}, {13, UpDownMigration}], :up, + all: true, + log: false + ) + end end test "fails if there are duplicated name" do - assert_raise Ecto.MigrationError, "migrations can't be executed, migration name Elixir.Ecto.MigratorTest.ChangeMigration is duplicated", fn -> - run(TestRepo, [{13, ChangeMigration}, {14, ChangeMigration}], :up, all: true, log: false) - end + assert_raise Ecto.MigrationError, + "migrations can't be executed, migration name Elixir.Ecto.MigratorTest.ChangeMigration is duplicated", + fn -> + run(TestRepo, [{13, ChangeMigration}, {14, ChangeMigration}], :up, + all: true, + log: false + ) + end end test "upwards migrations skips migrations that are already up" do @@ -745,31 +787,46 @@ defmodule Ecto.MigratorTest do end test "downwards migrations skips migrations that are already down" do - assert run(TestRepo, [{1, ChangeMigration}, {4, UpDownMigration}], :down, all: true, log: false) == [1] + assert run(TestRepo, [{1, ChangeMigration}, {4, UpDownMigration}], :down, + all: true, + log: false + ) == [1] end test "stepwise migrations stop before all have been run" do - assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, step: 1, log: false) == [13] + assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, + step: 1, + log: false + ) == [13] end test "stepwise migrations stop at the number of available migrations" do - assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, step: 2, log: false) == [13, 14] + assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, + step: 2, + log: false + ) == [13, 14] end test "stepwise migrations stop even if asked to exceed available" do - assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, step: 3, log: false) == [13, 14] + assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, + step: 3, + log: false + ) == [13, 14] end test "version migrations stop before all have been run" do - assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, to: 13, log: false) == [13] + assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, to: 13, log: false) == + [13] end test "version migrations stop at the number of available migrations" do - assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, to: 14, log: false) == [13, 14] + assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, to: 14, log: false) == + [13, 14] end test "version migrations stop even if asked to exceed available" do - assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, to: 15, log: false) == [13, 14] + assert run(TestRepo, [{13, ChangeMigration}, {14, UpDownMigration}], :up, to: 15, log: false) == + [13, 14] end end @@ -779,9 +836,10 @@ defmodule Ecto.MigratorTest do end test "both run when in a transaction going up" do - log = capture_log(fn -> - assert up(TestRepo, 10, MigrationWithCallbacks) == :ok - end) + log = + capture_log(fn -> + assert up(TestRepo, 10, MigrationWithCallbacks) == :ok + end) assert log =~ "after_begin" assert log =~ "before_commit" @@ -790,18 +848,20 @@ defmodule Ecto.MigratorTest do test "are both run in a transaction going down" do assert up(TestRepo, 10, MigrationWithCallbacks, log: false) == :ok - log = capture_log(fn -> - assert down(TestRepo, 10, MigrationWithCallbacks) == :ok - end) + log = + capture_log(fn -> + assert down(TestRepo, 10, MigrationWithCallbacks) == :ok + end) assert log =~ "after_begin_down" assert log =~ "before_commit_down" end test "are not run when the transaction is disabled" do - log = capture_log(fn -> - assert up(TestRepo, 10, MigrationWithCallbacksAndNoTransaction) == :ok - end) + log = + capture_log(fn -> + assert up(TestRepo, 10, MigrationWithCallbacksAndNoTransaction) == :ok + end) refute log =~ "after_begin" refute log =~ "before_commit" @@ -868,7 +928,8 @@ defmodule Ecto.MigratorTest do [] end - assert {:ok, :undefined} = start_supervised({Ecto.Migrator, [repos: [TestRepo], migrator: migrator]}) + assert {:ok, :undefined} = + start_supervised({Ecto.Migrator, [repos: [TestRepo], migrator: migrator]}) end test "runs the migrator with extra opts" do @@ -878,7 +939,11 @@ defmodule Ecto.MigratorTest do [] end - assert {:ok, :undefined} = start_supervised({Ecto.Migrator, [repos: [TestRepo], migrator: migrator, skip: false, prefix: "foo"]}) + assert {:ok, :undefined} = + start_supervised( + {Ecto.Migrator, + [repos: [TestRepo], migrator: migrator, skip: false, prefix: "foo"]} + ) end test "skip is set" do @@ -887,7 +952,10 @@ defmodule Ecto.MigratorTest do [] end - assert {:ok, :undefined} = start_supervised({Ecto.Migrator, [repos: [TestRepo], migrator: migrator, skip: true]}) + assert {:ok, :undefined} = + start_supervised( + {Ecto.Migrator, [repos: [TestRepo], migrator: migrator, skip: true]} + ) end test "migrations fail" do @@ -897,7 +965,8 @@ defmodule Ecto.MigratorTest do [] end - assert {:error, {{%RuntimeError{message: "boom"}, _}, _}} = start_supervised({Ecto.Migrator, [repos: [TestRepo], migrator: migrator]}) + assert {:error, {{%RuntimeError{message: "boom"}, _}, _}} = + start_supervised({Ecto.Migrator, [repos: [TestRepo], migrator: migrator]}) end end diff --git a/test/ecto/tenant_migrator_test.exs b/test/ecto/tenant_migrator_test.exs index 1902db4c..7a01fddf 100644 --- a/test/ecto/tenant_migrator_test.exs +++ b/test/ecto/tenant_migrator_test.exs @@ -38,15 +38,24 @@ defmodule Ecto.TenantMigratorTest do def put_test_adapter_config(config) do Application.put_env(:ecto_sql, EctoSQL.TestAdapter, config) - on_exit fn -> + on_exit(fn -> Application.delete_env(:ecto, EctoSQL.TestAdapter) - end + end) end describe "dynamic_repo option" do test "upwards and downwards migrations" do - assert run(TestRepo, [{3, ChangeMigration}, {4, Migration}], :up, to: 4, log: false, dynamic_repo: :tenant_db) == [4] - assert run(TestRepo, [{2, ChangeMigration}, {3, Migration}], :down, all: true, log: false, dynamic_repo: :tenant_db) == [3, 2] + assert run(TestRepo, [{3, ChangeMigration}, {4, Migration}], :up, + to: 4, + log: false, + dynamic_repo: :tenant_db + ) == [4] + + assert run(TestRepo, [{2, ChangeMigration}, {3, Migration}], :down, + all: true, + log: false, + dynamic_repo: :tenant_db + ) == [3, 2] end test "down invokes the repository adapter with down commands" do @@ -60,11 +69,11 @@ defmodule Ecto.TenantMigratorTest do end test "migrations run inside a transaction if the adapter supports ddl transactions" do - capture_log fn -> + capture_log(fn -> put_test_adapter_config(supports_ddl_transaction?: true, test_process: self()) up(TestRepo, 0, Migration, dynamic_repo: :tenant_db) assert_receive {:transaction, _, _} - end + end) end end end diff --git a/test/ecto/type_test.exs b/test/ecto/type_test.exs index 19c999c0..3f18deb7 100644 --- a/test/ecto/type_test.exs +++ b/test/ecto/type_test.exs @@ -11,33 +11,33 @@ defmodule Ecto.TypeTest do # We don't effectively dump because we need to keep JSON encoding test "dumps through the adapter" do assert adapter_dump(MyXQL, {:map, Ecto.UUID}, %{"a" => @uuid_string}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} assert adapter_dump(Postgres, {:map, Ecto.UUID}, %{"a" => @uuid_string}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} assert adapter_dump(Tds, {:map, Elixir.Tds.Ecto.UUID}, %{"a" => @uuid_string}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} end # Therefore we need to support both binaries and strings when loading test "loads through the adapter" do assert adapter_load(MyXQL, {:map, Ecto.UUID}, %{"a" => @uuid_binary}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} assert adapter_load(Postgres, {:map, Ecto.UUID}, %{"a" => @uuid_binary}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} assert adapter_load(Tds, {:map, Elixir.Tds.Ecto.UUID}, %{"a" => @mssql_uuid_binary}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} assert adapter_load(MyXQL, {:map, Ecto.UUID}, %{"a" => @uuid_string}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} assert adapter_load(Postgres, {:map, Ecto.UUID}, %{"a" => @uuid_string}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} assert adapter_load(Tds, {:map, Elixir.Tds.Ecto.UUID}, %{"a" => @uuid_string}) == - {:ok, %{"a" => @uuid_string}} + {:ok, %{"a" => @uuid_string}} end end