Skip to content

Commit

Permalink
Keep field types when using a subquery with source
Browse files Browse the repository at this point in the history
Closes #3438.
  • Loading branch information
josevalim committed Oct 8, 2020
1 parent 7dd9aee commit c51bbca
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 14 deletions.
32 changes: 18 additions & 14 deletions lib/ecto/query/planner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,17 @@ defmodule Ecto.Query.Planner do
end
end

defp subquery_types({:source, _, _, types}), do: types
defp subquery_types({:map, types}), do: types
defp subquery_types({:struct, _name, types}), do: types
defp subquery_type_for({:source, _, _, fields}, field), do: Keyword.fetch(fields, field)
defp subquery_type_for({:struct, _name, types}, field), do: subquery_type_for_value(types, field)
defp subquery_type_for({:map, types}, field), do: subquery_type_for_value(types, field)

defp subquery_type_for_value(types, field) do
case Keyword.fetch(types, field) do
{:ok, {:value, type}} -> {:ok, type}
{:ok, _} -> {:ok, :any}
:error -> :error
end
end

defp assert_subquery_fields!(query, expr, pairs) do
Enum.each(pairs, fn
Expand Down Expand Up @@ -965,7 +973,7 @@ defmodule Ecto.Query.Planner do
{name, %Ecto.Query{} = query}, {queries, counter} ->
{query, counter} = traverse_exprs(query, :all, counter, fun)
{query, source, fields} = normalize_subquery_select(query, adapter, true)
keys = source |> subquery_types() |> Keyword.keys()
{_, keys} = subquery_struct_and_fields(source)
query = put_in(query.select.fields, Enum.zip(keys, fields))
{[{name, query} | queries], counter}

Expand Down Expand Up @@ -1052,7 +1060,7 @@ defmodule Ecto.Query.Planner do
inner_query
else
update_in(inner_query.select.fields, fn fields ->
subquery.select |> subquery_types() |> Keyword.keys() |> Enum.zip(fields)
subquery.select |> subquery_struct_and_fields() |> elem(1) |> Enum.zip(fields)
end)
end

Expand Down Expand Up @@ -1525,8 +1533,8 @@ defmodule Ecto.Query.Planner do
{{:source, {source, schema}, prefix || query.prefix, types}, fields}

{:error, %Ecto.SubQuery{select: select}} ->
fields = for {field, _} <- subquery_types(select), do: select_field(field, ix)
{select, fields}
{_, fields} = subquery_struct_and_fields(select)
{select, Enum.map(fields, &select_field(&1, ix))}
end
end

Expand Down Expand Up @@ -1632,13 +1640,9 @@ defmodule Ecto.Query.Planner do
type!(kind, query, expr, schema, field)

%Ecto.SubQuery{select: select} ->
case Keyword.fetch(subquery_types(select), field) do
{:ok, {:value, type}} ->
type
{:ok, _} ->
:any
:error ->
error!(query, expr, "field `#{field}` does not exist in subquery")
case subquery_type_for(select, field) do
{:ok, type} -> type
:error -> error!(query, expr, "field `#{field}` does not exist in subquery")
end
end
end
Expand Down
17 changes: 17 additions & 0 deletions test/ecto/query/subquery_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,23 @@ defmodule Ecto.Query.SubqueryTest do
end

describe "normalize: source subqueries" do
test "keeps field types" do
query = from p in subquery(Post), select: p.title

assert normalize(query).select.fields ==
[{{:., [type: :string], [{:&, [], [0]}, :title]}, [], []}]

query = from p in subquery(from p in Post, select: p.title), select: p.title

assert normalize(query).select.fields ==
[{{:., [type: :string], [{:&, [], [0]}, :title]}, [], []}]

query = from p in subquery(from p in Post, select: %{title: p.title}), select: p.title

assert normalize(query).select.fields ==
[{{:., [type: :string], [{:&, [], [0]}, :title]}, [], []}]
end

test "with params in from" do
query = from p in Post,
where: [title: ^"hello"],
Expand Down

0 comments on commit c51bbca

Please sign in to comment.