Skip to content

Commit

Permalink
Support nested subqueries
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Oct 3, 2020
1 parent c16f724 commit babc554
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 38 deletions.
13 changes: 8 additions & 5 deletions lib/ecto/adapters/myxql/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ if Code.ensure_loaded?(MyXQL) do

## Query

@parent_as __MODULE__
alias Ecto.Query.{BooleanExpr, JoinExpr, QueryExpr, WithExpr}

@parent_as 0

@impl true
def all(query, as_prefix \\ []) do
sources = create_names(query, as_prefix)
Expand Down Expand Up @@ -571,7 +570,7 @@ if Code.ensure_loaded?(MyXQL) do

defp expr(%Ecto.SubQuery{query: query}, sources, _query) do
query = put_in(query.aliases[@parent_as], sources)
[?(, all(query, [?s]), ?)]
[?(, all(query, subquery_as_prefix(sources)), ?)]
end

defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do
Expand Down Expand Up @@ -710,8 +709,12 @@ if Code.ensure_loaded?(MyXQL) do
[create_name(sources, pos, as_prefix) | create_names(sources, pos + 1, limit, as_prefix)]
end

defp create_names(_sources, pos, pos, _as_prefix) do
[]
defp create_names(_sources, pos, pos, as_prefix) do
[as_prefix]
end

defp subquery_as_prefix(sources) do
[?s | :erlang.element(tuple_size(sources), sources)]
end

defp create_name(sources, pos, as_prefix) do
Expand Down
12 changes: 8 additions & 4 deletions lib/ecto/adapters/postgres/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ if Code.ensure_loaded?(Postgrex) do
Postgrex.stream(conn, sql, params, opts)
end

@parent_as 0
@parent_as __MODULE__
alias Ecto.Query.{BooleanExpr, JoinExpr, QueryExpr, WithExpr}

@impl true
Expand Down Expand Up @@ -606,7 +606,7 @@ if Code.ensure_loaded?(Postgrex) do

defp expr(%Ecto.SubQuery{query: query}, sources, _query) do
query = put_in(query.aliases[@parent_as], sources)
[?(, all(query, [?s]), ?)]
[?(, all(query, subquery_as_prefix(sources)), ?)]
end

defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do
Expand Down Expand Up @@ -764,8 +764,12 @@ if Code.ensure_loaded?(Postgrex) do
[create_name(sources, pos, as_prefix) | create_names(sources, pos + 1, limit, as_prefix)]
end

defp create_names(_sources, pos, pos, _as_prefix) do
[]
defp create_names(_sources, pos, pos, as_prefix) do
[as_prefix]
end

defp subquery_as_prefix(sources) do
[?s | :erlang.element(tuple_size(sources), sources)]
end

defp create_name(sources, pos, as_prefix) do
Expand Down
13 changes: 8 additions & 5 deletions lib/ecto/adapters/tds/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,10 @@ if Code.ensure_loaded?(Tds) do

## Query

@parent_as __MODULE__
alias Ecto.Query
alias Ecto.Query.{BooleanExpr, JoinExpr, QueryExpr, WithExpr}

@parent_as 0

@impl true
def all(query, as_prefix \\ []) do
sources = create_names(query, as_prefix)
Expand Down Expand Up @@ -702,7 +701,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, subquery_as_prefix(sources)), ?)]
end

defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do
Expand Down Expand Up @@ -903,8 +902,12 @@ if Code.ensure_loaded?(Tds) do
[create_name(sources, pos, as_prefix) | create_names(sources, pos + 1, limit, as_prefix)]
end

defp create_names(_sources, pos, pos, _as_prefix) do
[]
defp create_names(_sources, pos, pos, as_prefix) do
[as_prefix]
end

defp subquery_as_prefix(sources) do
[?s | :erlang.element(tuple_size(sources), sources)]
end

defp create_name(sources, pos, as_prefix) do
Expand Down
3 changes: 3 additions & 0 deletions test/ecto/adapters/myxql_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ defmodule Ecto.Adapters.MyXQLTest do

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}
end

test "CTE" do
Expand Down
3 changes: 3 additions & 0 deletions test/ecto/adapters/postgres_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ defmodule Ecto.Adapters.PostgresTest do

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}
end

test "CTE" do
Expand Down
89 changes: 65 additions & 24 deletions test/ecto/adapters/tds_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -120,26 +120,54 @@ defmodule Ecto.Adapters.TdsTest do

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 "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) ==
"SELECT s1.[x] FROM [comments] AS c0 " <>
"INNER JOIN (SELECT sp0.[x] AS [x], sp0.[y] AS [y] FROM [posts] AS sp0 WHERE (sp0.[title] = @1)) AS s1 ON 1 = 1"
"SELECT s1.[x] FROM [comments] AS c0 " <>
"INNER JOIN (SELECT sp0.[x] AS [x], sp0.[y] AS [y] FROM [posts] AS sp0 WHERE (sp0.[title] = @1)) AS s1 ON 1 = 1"

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) ==
"SELECT s1.[x], s1.[z] FROM [comments] AS c0 " <>
"INNER JOIN (SELECT sp0.[x] AS [x], sp0.[y] AS [z] FROM [posts] AS sp0 WHERE (sp0.[title] = @1)) AS s1 ON 1 = 1"
"SELECT s1.[x], s1.[z] FROM [comments] AS c0 " <>
"INNER JOIN (SELECT sp0.[x] AS [x], sp0.[y] AS [z] FROM [posts] AS sp0 WHERE (sp0.[title] = @1)) AS s1 ON 1 = 1"

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))
|> 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)) |> 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 1 = 1"
"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 1 = 1"
end

test "CTE" do
Expand Down Expand Up @@ -605,15 +633,23 @@ defmodule Ecto.Adapters.TdsTest do
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
Expand Down Expand Up @@ -1142,15 +1178,16 @@ defmodule Ecto.Adapters.TdsTest do
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

Expand Down Expand Up @@ -1228,7 +1265,9 @@ defmodule Ecto.Adapters.TdsTest do
end

test "alter table with invalid reference opts" do
alter = {:alter, table(:posts), [{:add, :author_id, %Reference{table: :author, validate: false}, []}]}
alter =
{:alter, table(:posts),
[{:add, :author_id, %Reference{table: :author, validate: false}, []}]}

assert_raise ArgumentError, "validate: false on references is not supported in Tds", fn ->
execute_ddl(alter)
Expand All @@ -1254,7 +1293,9 @@ defmodule Ecto.Adapters.TdsTest do
end

test "create check constraint with invalid validate opts" do
create = {:create, constraint(:products, "price_must_be_positive", check: "price > 0", validate: false)}
create =
{:create,
constraint(:products, "price_must_be_positive", check: "price > 0", validate: false)}

assert_raise ArgumentError, "`:validate` is not supported by the Tds adapter", fn ->
execute_ddl(create)
Expand Down

0 comments on commit babc554

Please sign in to comment.