From 62967b2dfef66f8efb9c3bd70b2bbb0f483a3848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 14 Apr 2020 17:30:13 +0200 Subject: [PATCH] Add parent_as support to MyXQL --- lib/ecto/adapters/myxql/connection.ex | 43 ++++++++++++++++----------- lib/ecto/adapters/tds/connection.ex | 9 ++---- test/ecto/adapters/myxql_test.exs | 14 ++++++--- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/lib/ecto/adapters/myxql/connection.ex b/lib/ecto/adapters/myxql/connection.ex index a41bb45e..d1bcf029 100644 --- a/lib/ecto/adapters/myxql/connection.ex +++ b/lib/ecto/adapters/myxql/connection.ex @@ -73,9 +73,11 @@ if Code.ensure_loaded?(MyXQL) do alias Ecto.Query.{BooleanExpr, JoinExpr, QueryExpr, WithExpr} + @parent_as 0 + @impl true - def all(query) do - sources = create_names(query) + def all(query, as_prefix \\ []) do + sources = create_names(query, as_prefix) cte = cte(query, sources) from = from(query, sources) @@ -102,7 +104,7 @@ if Code.ensure_loaded?(MyXQL) do error!(nil, ":select is not supported in update_all by MySQL") end - sources = create_names(query) + sources = create_names(query, []) cte = cte(query, sources) {from, name} = get_source(query, sources, 0, source) @@ -125,7 +127,7 @@ if Code.ensure_loaded?(MyXQL) do error!(nil, ":select is not supported in delete_all by MySQL") end - sources = create_names(query) + sources = create_names(query, []) cte = cte(query, sources) {_, name, _} = elem(sources, 0) @@ -450,6 +452,12 @@ if Code.ensure_loaded?(MyXQL) do '?' end + defp expr({{:., _, [{:parent_as, _, [{:&, _, [idx]}]}, field]}, _, []}, _sources, query) + when is_atom(field) do + {_, name, _} = elem(query.aliases[@parent_as], idx) + [name, ?. | quote_name(field)] + end + defp expr({{:., _, [{:&, _, [idx]}, field]}, _, []}, sources, _query) when is_atom(field) do {_, name, _} = elem(sources, idx) @@ -495,8 +503,9 @@ if Code.ensure_loaded?(MyXQL) do error!(query, "MySQL adapter does not support aggregate filters") 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 @@ -627,37 +636,37 @@ if Code.ensure_loaded?(MyXQL) do defp op_to_binary(expr, sources, query), do: expr(expr, sources, query) - defp create_names(%{sources: sources}) do - create_names(sources, 0, tuple_size(sources)) |> List.to_tuple() + defp create_names(%{sources: sources}, as_prefix) do + create_names(sources, 0, tuple_size(sources), as_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, as_prefix) when pos < limit do + [create_name(sources, pos, as_prefix) | create_names(sources, pos + 1, limit, as_prefix)] end - defp create_names(_sources, pos, pos) do + defp create_names(_sources, pos, pos, _as_prefix) do [] end - defp create_name(sources, pos) do + defp create_name(sources, pos, as_prefix) do case elem(sources, pos) do {:fragment, _, _} -> - {nil, [?f | Integer.to_string(pos)], nil} + {nil, as_prefix ++ [?f | Integer.to_string(pos)], nil} {table, schema, prefix} -> - name = [create_alias(table) | Integer.to_string(pos)] + name = as_prefix ++ [create_alias(table) | Integer.to_string(pos)] {quote_table(prefix, table), name, schema} %Ecto.SubQuery{} -> - {nil, [?s | Integer.to_string(pos)], nil} + {nil, as_prefix ++ [?s | Integer.to_string(pos)], nil} end end defp create_alias(<>) when first in ?a..?z when first in ?A..?Z do - <> + first end defp create_alias(_) do - "t" + ?t end ## DDL diff --git a/lib/ecto/adapters/tds/connection.ex b/lib/ecto/adapters/tds/connection.ex index 6a97ea3e..284da950 100644 --- a/lib/ecto/adapters/tds/connection.ex +++ b/lib/ecto/adapters/tds/connection.ex @@ -129,10 +129,7 @@ if Code.ensure_loaded?(Tds) do ## Query alias Ecto.Query - alias Ecto.Query.QueryExpr - alias Ecto.Query.JoinExpr - alias Ecto.Query.BooleanExpr - alias Ecto.Query.WithExpr + alias Ecto.Query.{BooleanExpr, JoinExpr, QueryExpr, WithExpr} @parent_as 0 @@ -620,13 +617,13 @@ if Code.ensure_loaded?(Tds) do defp expr({{:., _, [{:parent_as, _, [{:&, _, [idx]}]}, field]}, _, []}, _sources, query) when is_atom(field) do {_, name, _} = elem(query.aliases[@parent_as], idx) - [name, ?., quote_name(field)] + [name, ?. | quote_name(field)] end defp expr({{:., _, [{:&, _, [idx]}, field]}, _, []}, sources, _query) when is_atom(field) or is_binary(field) do {_, name, _} = elem(sources, idx) - [name, ?., quote_name(field)] + [name, ?. | quote_name(field)] end defp expr({:&, _, [idx]}, sources, _query) do diff --git a/test/ecto/adapters/myxql_test.exs b/test/ecto/adapters/myxql_test.exs index 8f0e24da..3477a92d 100644 --- a/test/ecto/adapters/myxql_test.exs +++ b/test/ecto/adapters/myxql_test.exs @@ -95,10 +95,10 @@ defmodule Ecto.Adapters.MyXQLTest 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 @@ -818,13 +818,19 @@ defmodule Ecto.Adapters.MyXQLTest 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` = ?)) AS s1 ON TRUE} + ~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() 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` = ?)) AS s1 ON TRUE} + ~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)) |> 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" end test "join with prefix" do