Skip to content

Commit

Permalink
Support parent_as for MSSQL
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Apr 14, 2020
1 parent 60b3da0 commit c9a7cc2
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 52 deletions.
26 changes: 13 additions & 13 deletions lib/ecto/adapters/postgres/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ if Code.ensure_loaded?(Postgrex) do
alias Ecto.Query.{BooleanExpr, JoinExpr, QueryExpr, WithExpr}

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

cte = cte(query, sources)
Expand Down Expand Up @@ -702,37 +702,37 @@ if Code.ensure_loaded?(Postgrex) do
defp returning(returning),
do: [" RETURNING " | intersperse_map(returning, ", ", &quote_name/1)]

defp create_names(%{sources: sources}, prefix) do
create_names(sources, 0, tuple_size(sources), prefix) |> 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, prefix) when pos < limit do
[create_name(sources, pos, prefix) | create_names(sources, pos + 1, limit, prefix)]
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, _prefix) do
defp create_names(_sources, pos, pos, _as_prefix) do
[]
end

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

{table, schema, prefix} ->
name = alias_prefix ++ [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, alias_prefix ++ [?s | Integer.to_string(pos)], nil}
{nil, as_prefix ++ [?s | Integer.to_string(pos)], nil}
end
end

defp create_alias(<<first, _rest::binary>>) when first in ?a..?z when first in ?A..?Z do
<<first>>
first
end
defp create_alias(_) do
"t"
?t
end

# DDL
Expand Down
80 changes: 43 additions & 37 deletions lib/ecto/adapters/tds/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@ if Code.ensure_loaded?(Tds) do
alias Ecto.Query.BooleanExpr
alias Ecto.Query.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)
Expand All @@ -160,7 +162,7 @@ if Code.ensure_loaded?(Tds) do

@impl true
def update_all(query) do
sources = create_names(query)
sources = create_names(query, [])
cte = cte(query, sources)
{table, name, _model} = elem(sources, 0)

Expand All @@ -185,7 +187,7 @@ if Code.ensure_loaded?(Tds) do

@impl true
def delete_all(query) do
sources = create_names(query)
sources = create_names(query, [])
cte = cte(query, sources)
{table, name, _model} = elem(sources, 0)

Expand Down Expand Up @@ -437,15 +439,14 @@ if Code.ensure_loaded?(Tds) do

defp update_op(:set, key, value, sources, query) do
{_table, name, _model} = elem(sources, 0)
name <> "." <> quote_name(key) <> " = " <> expr(value, sources, query)
[name, ?., quote_name(key), " = " | expr(value, sources, query)]
end

defp update_op(:inc, key, value, sources, query) do
{_table, name, _model} = elem(sources, 0)
quoted = quote_name(key)

name <>
"." <> quoted <> " = " <> name <> "." <> quoted <> " + " <> expr(value, sources, query)
[name, ?., quoted, " = ", name, ?., quoted, " + " | expr(value, sources, query)]
end

defp update_op(command, _key, _value, _sources, query) do
Expand All @@ -461,7 +462,7 @@ if Code.ensure_loaded?(Tds) do
%JoinExpr{on: %QueryExpr{expr: expr}, qual: qual, ix: ix, source: source, hints: hints} ->
{join, name} = get_source(query, sources, ix, source)
qual_text = join_qual(qual)
join = join || "(" <> expr(source, sources, query) <> ")"
join = join || ["(", expr(source, sources, query) | ")"]
[qual_text, join, " AS ", name, hints(hints) | join_on(qual, expr, sources, query)]
end)
]
Expand Down Expand Up @@ -616,11 +617,16 @@ if Code.ensure_loaded?(Tds) do
"@#{idx + 1}"
end

# :. - attribure, table alias name can be get from sources by passing index
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) or is_binary(field) do
{_, name, _} = elem(sources, idx)
"#{name}.#{quote_name(field)}"
[name, ?., quote_name(field)]
end

defp expr({:&, _, [idx]}, sources, _query) do
Expand Down Expand Up @@ -651,7 +657,7 @@ if Code.ensure_loaded?(Tds) do
# example from(p in Post, where: p.id in [1,2, ^some_id])
defp expr({:in, _, [left, right]}, sources, query) when is_list(right) do
args = Enum.map_join(right, ",", &expr(&1, sources, query))
expr(left, sources, query) <> " IN (" <> args <> ")"
[expr(left, sources, query), " IN (", args | ")"]
end

# example from(p in Post, where: p.id in [])
Expand All @@ -661,27 +667,28 @@ if Code.ensure_loaded?(Tds) do
# or from(p in Post, where: p.id in ^[])
defp expr({:in, _, [left, {:^, _, [idx, length]}]}, sources, query) do
args = list_param_to_args(idx, length)
expr(left, sources, query) <> " IN (" <> args <> ")"
[expr(left, sources, query), " IN (", args | ")"]
end

defp expr({:in, _, [left, right]}, sources, query) do
expr(left, sources, query) <> " = ANY(" <> expr(right, sources, query) <> ")"
[expr(left, sources, query), " = ANY(", expr(right, sources, query) | ")"]
end

defp expr({:is_nil, _, [arg]}, sources, query) do
"#{expr(arg, sources, query)} IS NULL"
end

defp expr({:not, _, [expr]}, sources, query) do
"NOT (" <> expr(expr, sources, query) <> ")"
["NOT (", expr(expr, sources, query) | ")"]
end

defp expr({:filter, _, _}, _sources, query) do
error!(query, "Tds 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
Expand All @@ -708,16 +715,15 @@ if Code.ensure_loaded?(Tds) do
end

defp expr({:date_add, _, [date, count, interval]}, sources, query) do
"CAST(DATEADD(" <>
interval <>
", " <>
interval_count(count, sources, query) <>
", CAST(" <>
expr(
date,
sources,
query
) <> " AS datetime2(6))" <> ") AS date)"
[
"CAST(DATEADD(",
interval,
", ",
interval_count(count, sources, query),
", CAST(",
expr(date, sources, query),
" AS datetime2(6))" | ") AS date)"
]
end

defp expr({:count, _, []}, _sources, _query), do: "count(*)"
Expand Down Expand Up @@ -874,38 +880,38 @@ if Code.ensure_loaded?(Tds) do
end)
]

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, model, prefix} ->
name = create_alias(table) <> Integer.to_string(pos)
name = as_prefix ++ [create_alias(table) | Integer.to_string(pos)]
{quote_table(prefix, table), name, model}

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

defp create_alias(<<first, _rest::binary>>) when first in ?a..?z when first in ?A..?Z do
<<first>>
first
end

defp create_alias(_) do
"t"
?t
end

# DDL
Expand Down
24 changes: 22 additions & 2 deletions test/ecto/adapters/tds_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,32 @@ defmodule Ecto.Adapters.TdsTest 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}
~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}
~s{SELECT s0.[x], s0.[z] FROM (SELECT sp0.[x] AS [x], sp0.[y] AS [z] FROM [posts] AS sp0) 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()
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"

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

test "CTE" do
Expand Down

0 comments on commit c9a7cc2

Please sign in to comment.