From eda193965ef112cd0e8f584c58a2efa751655a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 27 Apr 2020 20:58:20 +0200 Subject: [PATCH] Support in subquery --- lib/ecto/adapters/myxql/connection.ex | 4 ++++ lib/ecto/adapters/postgres/connection.ex | 4 ++++ lib/ecto/adapters/tds/connection.ex | 19 ++++++++++++++++--- mix.lock | 2 +- test/ecto/adapters/myxql_test.exs | 14 ++++++++++++++ test/ecto/adapters/postgres_test.exs | 14 ++++++++++++++ test/ecto/adapters/tds_test.exs | 14 ++++++++++++++ 7 files changed, 67 insertions(+), 4 deletions(-) diff --git a/lib/ecto/adapters/myxql/connection.ex b/lib/ecto/adapters/myxql/connection.ex index d1bcf029..cfa14093 100644 --- a/lib/ecto/adapters/myxql/connection.ex +++ b/lib/ecto/adapters/myxql/connection.ex @@ -487,6 +487,10 @@ if Code.ensure_loaded?(MyXQL) do [expr(left, sources, query), " IN (", args, ?)] end + defp expr({:in, _, [left, %Ecto.SubQuery{} = subquery]}, sources, query) do + [expr(left, sources, query), " IN ", expr(subquery, sources, query)] + end + defp expr({:in, _, [left, right]}, sources, query) do [expr(left, sources, query), " = ANY(", expr(right, sources, query), ?)] end diff --git a/lib/ecto/adapters/postgres/connection.ex b/lib/ecto/adapters/postgres/connection.ex index fbbb1fa7..0ddafd64 100644 --- a/lib/ecto/adapters/postgres/connection.ex +++ b/lib/ecto/adapters/postgres/connection.ex @@ -538,6 +538,10 @@ if Code.ensure_loaded?(Postgrex) do [expr(left, sources, query), " = ANY($", Integer.to_string(ix + 1), ?)] end + defp expr({:in, _, [left, %Ecto.SubQuery{} = subquery]}, sources, query) do + [expr(left, sources, query), " IN ", expr(subquery, sources, query)] + end + defp expr({:in, _, [left, right]}, sources, query) do [expr(left, sources, query), " = ANY(", expr(right, sources, query), ?)] end diff --git a/lib/ecto/adapters/tds/connection.ex b/lib/ecto/adapters/tds/connection.ex index 284da950..782fb91d 100644 --- a/lib/ecto/adapters/tds/connection.ex +++ b/lib/ecto/adapters/tds/connection.ex @@ -597,6 +597,14 @@ if Code.ensure_loaded?(Tds) do defp operator_to_boolean(:and), do: " AND " defp operator_to_boolean(:or), do: " OR " + defp parens_for_select([first_expr | _] = expr) do + if is_binary(first_expr) and String.starts_with?(first_expr, ["SELECT", "select"]) do + [?(, expr, ?)] + else + expr + end + end + defp paren_expr(true, _sources, _query) do ["(1 = 1)"] end @@ -667,6 +675,10 @@ if Code.ensure_loaded?(Tds) do [expr(left, sources, query), " IN (", args | ")"] end + defp expr({:in, _, [left, %Ecto.SubQuery{} = subquery]}, sources, query) do + [expr(left, sources, query), " IN ", expr(subquery, sources, query)] + end + defp expr({:in, _, [left, right]}, sources, query) do [expr(left, sources, query), " = ANY(", expr(right, sources, query) | ")"] end @@ -685,7 +697,7 @@ if Code.ensure_loaded?(Tds) do defp expr(%Ecto.SubQuery{query: query}, sources, _query) do query = put_in(query.aliases[@parent_as], sources) - all(query, [?s]) + [?(, all(query, [?s]), ?)] end defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do @@ -693,10 +705,11 @@ if Code.ensure_loaded?(Tds) do end defp expr({:fragment, _, parts}, sources, query) do - Enum.map_join(parts, "", fn + Enum.map(parts, fn {:raw, part} -> part {:expr, expr} -> expr(expr, sources, query) end) + |> parens_for_select end defp expr({:datetime_add, _, [datetime, count, interval]}, sources, query) do @@ -1410,7 +1423,7 @@ if Code.ensure_loaded?(Tds) do defp get_source(query, sources, ix, source) do {expr, name, _schema} = elem(sources, ix) - {expr || paren_expr(source, sources, query), name} + {expr || expr(source, sources, query), name} end defp quote_name(name) when is_atom(name) do diff --git a/mix.lock b/mix.lock index 74b2cbfe..7553a03f 100644 --- a/mix.lock +++ b/mix.lock @@ -6,7 +6,7 @@ "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm", "e3bf435a54ed27b0ba3a01eb117ae017988804e136edcbe8a6a14c310daa966e"}, "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"}, - "ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "bfdca5ae8745af06940c46a55a9220cb83c5d97e", []}, + "ecto": {:git, "https://github.com/elixir-ecto/ecto.git", "e4219cc82993675d3ae50d0f86a262a8dd829255", []}, "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, "jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"}, diff --git a/test/ecto/adapters/myxql_test.exs b/test/ecto/adapters/myxql_test.exs index 3477a92d..84b11190 100644 --- a/test/ecto/adapters/myxql_test.exs +++ b/test/ecto/adapters/myxql_test.exs @@ -536,6 +536,20 @@ defmodule Ecto.Adapters.MyXQLTest do 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` AS `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() + assert all(query) == + ~s{SELECT c0.`x` FROM `comments` AS c0 } <> + ~s{WHERE (c0.`post_id` IN (SELECT sp0.`id` AS `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`)} diff --git a/test/ecto/adapters/postgres_test.exs b/test/ecto/adapters/postgres_test.exs index 38472bcd..bbeca279 100644 --- a/test/ecto/adapters/postgres_test.exs +++ b/test/ecto/adapters/postgres_test.exs @@ -585,6 +585,20 @@ defmodule Ecto.Adapters.PostgresTest do 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" AS "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() + assert all(query) == + ~s{SELECT c0."x" FROM "comments" AS c0 } <> + ~s{WHERE (c0."post_id" IN (SELECT sp0."id" AS "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")} diff --git a/test/ecto/adapters/tds_test.exs b/test/ecto/adapters/tds_test.exs index 1bff652e..e75e42c6 100644 --- a/test/ecto/adapters/tds_test.exs +++ b/test/ecto/adapters/tds_test.exs @@ -602,6 +602,20 @@ defmodule Ecto.Adapters.TdsTest do ~s{SELECT ((s0.[x] = @1) OR s0.[x] IN (@2,@3,@4)) OR (s0.[x] = @5) 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] AS [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() + assert all(query) == + ~s{SELECT c0.[x] FROM [comments] AS c0 } <> + ~s{WHERE (c0.[post_id] IN (SELECT sp0.[id] AS [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])}