Skip to content

Commit

Permalink
Support lazy named bindings in PG
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Apr 14, 2020
1 parent 9c94e7f commit 729527e
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 23 deletions.
39 changes: 23 additions & 16 deletions lib/ecto/adapters/postgres/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ if Code.ensure_loaded?(Postgrex) do
Postgrex.stream(conn, sql, params, opts)
end

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

@impl true
def all(query) do
sources = create_names(query)
def all(query, prefix \\ []) do
sources = create_names(query, prefix)
{select_distinct, order_by_distinct} = distinct(query.distinct, sources, query)

cte = cte(query, sources)
Expand All @@ -126,7 +127,7 @@ if Code.ensure_loaded?(Postgrex) do

@impl true
def update_all(%{from: %{source: source}} = query, prefix \\ nil) do
sources = create_names(query)
sources = create_names(query, [])
cte = cte(query, sources)
{from, name} = get_source(query, sources, 0, source)

Expand All @@ -140,7 +141,7 @@ if Code.ensure_loaded?(Postgrex) do

@impl true
def delete_all(%{from: from} = query) do
sources = create_names(query)
sources = create_names(query, [])
cte = cte(query, sources)
{from, name} = get_source(query, sources, 0, from)

Expand All @@ -164,7 +165,7 @@ if Code.ensure_loaded?(Postgrex) do
end

defp insert_as({%{sources: sources}, _, _}) do
{_expr, name, _schema} = create_name(sources, 0)
{_expr, name, _schema} = create_name(sources, 0, [])
[" AS " | name]
end
defp insert_as({_, _, _}) do
Expand Down Expand Up @@ -510,6 +511,11 @@ if Code.ensure_loaded?(Postgrex) do
[?$ | Integer.to_string(ix + 1)]
end

defp expr({{:., _, [{:parent_as, _, [{:&, _, [idx]}]}, field]}, _, []}, _sources, query)
when is_atom(field) do
quote_qualified_name(field, query.aliases[@parent_as], idx)
end

defp expr({{:., _, [{:&, _, [idx]}, field]}, _, []}, sources, _query) when is_atom(field) do
quote_qualified_name(field, sources, idx)
end
Expand Down Expand Up @@ -544,8 +550,9 @@ if Code.ensure_loaded?(Postgrex) do
["NOT (", expr(expr, sources, query), ?)]
end

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

defp expr({:fragment, _, [kw]}, _sources, query) when is_list(kw) or tuple_size(kw) == 3 do
Expand Down Expand Up @@ -695,29 +702,29 @@ if Code.ensure_loaded?(Postgrex) do
defp returning(returning),
do: [" RETURNING " | intersperse_map(returning, ", ", &quote_name/1)]

defp create_names(%{sources: sources}) do
create_names(sources, 0, tuple_size(sources)) |> List.to_tuple()
defp create_names(%{sources: sources}, prefix) do
create_names(sources, 0, tuple_size(sources), prefix) |> List.to_tuple()
end

defp create_names(sources, pos, limit) when pos < limit do
[create_name(sources, pos) | create_names(sources, pos + 1, limit)]
defp create_names(sources, pos, limit, prefix) when pos < limit do
[create_name(sources, pos, prefix) | create_names(sources, pos + 1, limit, prefix)]
end

defp create_names(_sources, pos, pos) do
defp create_names(_sources, pos, pos, _prefix) do
[]
end

defp create_name(sources, pos) do
defp create_name(sources, pos, alias_prefix) do
case elem(sources, pos) do
{:fragment, _, _} ->
{nil, [?f | Integer.to_string(pos)], nil}
{nil, alias_prefix ++ [?f | Integer.to_string(pos)], nil}

{table, schema, prefix} ->
name = [create_alias(table) | Integer.to_string(pos)]
name = alias_prefix ++ [create_alias(table) | Integer.to_string(pos)]
{quote_table(prefix, table), name, schema}

%Ecto.SubQuery{} ->
{nil, [?s | Integer.to_string(pos)], nil}
{nil, alias_prefix ++ [?s | Integer.to_string(pos)], nil}
end
end

Expand Down
20 changes: 13 additions & 7 deletions test/ecto/adapters/postgres_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ defmodule Ecto.Adapters.PostgresTest do

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 p0."x" AS "x", p0."y" AS "y" FROM "posts" AS p0) 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 p0."x" AS "x", p0."y" AS "z" FROM "posts" AS p0) 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}
end

test "CTE" do
Expand Down Expand Up @@ -917,13 +917,19 @@ defmodule Ecto.Adapters.PostgresTest do
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 p0."x" AS "x", p0."y" AS "y" FROM "posts" AS p0 WHERE (p0."title" = $1)) AS s1 ON TRUE}
~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()
assert all(query) ==
~s{SELECT s1."x", s1."z" FROM "comments" AS c0 } <>
~s{INNER JOIN (SELECT p0."x" AS "x", p0."y" AS "z" FROM "posts" AS p0 WHERE (p0."title" = $1)) AS s1 ON TRUE}
~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)) |> 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}
end

test "join with prefix" do
Expand Down Expand Up @@ -1003,7 +1009,7 @@ defmodule Ecto.Adapters.PostgresTest do
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 s0."x" AS "x", s0."y" AS "y" FROM "schema" AS s0) } <>
~s{(SELECT ss0."x" AS "x", ss0."y" AS "y" FROM "schema" AS ss0) } <>
~s{AS s1 ON TRUE}
end

Expand All @@ -1012,7 +1018,7 @@ defmodule Ecto.Adapters.PostgresTest do
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 s0) } <>
~s{(SELECT downcase($2) AS "string" FROM "schema" AS ss0) } <>
~s{AS s1 ON TRUE}
end

Expand All @@ -1026,7 +1032,7 @@ defmodule Ecto.Adapters.PostgresTest do

assert all(query) ==
~s{SELECT $1 FROM "schema" AS s0 INNER JOIN } <>
~s{(SELECT $2 AS "x", $3 AS "w" FROM "schema" AS s0) AS s1 ON TRUE } <>
~s{(SELECT $2 AS "x", $3 AS "w" FROM "schema" AS ss0) AS s1 ON TRUE } <>
~s{WHERE (s0."x" = $4)}
end
end
Expand Down

0 comments on commit 729527e

Please sign in to comment.