Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions lib/ecto/query/planner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2258,8 +2258,8 @@ defmodule Ecto.Query.Planner do
{{:ok, {:struct, _}}, {:fragment, _, _}} ->
error!(query, "it is not possible to return a struct subset of a fragment")

{{:ok, {:struct, fields}}, %Ecto.SubQuery{select: select}} ->
subquery_select_fields(select, fields, ix, query)
{{:ok, {kind, fields}}, %Ecto.SubQuery{select: select}} ->
subquery_select_fields(kind, select, fields, ix, query)

{{:ok, {_, []}}, {_, _, _}} ->
error!(
Expand Down Expand Up @@ -2336,16 +2336,21 @@ defmodule Ecto.Query.Planner do
end)
end

defp subquery_select_fields(select, requested_fields, ix, query) do
defp subquery_select_fields(kind, select, requested_fields, ix, query) do
kind = if kind == :any, do: :struct, else: kind
available_fields = subquery_source_fields(select)
requested_fields = List.wrap(requested_fields)

schema =
case select do
{:source, {_, schema}, _, _} when not is_nil(schema) -> schema
case {kind, select} do
{:struct, {:source, {_, schema}, _, _}} when not is_nil(schema) ->
schema

_ ->
error!(query, "it is not possible to return a struct subset of a subquery that does not return a schema struct")
{:map, _} ->
nil

{:struct, _} ->
error!(query, "it is not possible to return a struct subset of a subquery that does not return a schema struct")
end

types =
Expand All @@ -2355,7 +2360,7 @@ defmodule Ecto.Query.Planner do
{field, type}

:error ->
error!(query, "field `#{field}` in struct/2 is not available in the subquery. " <>
error!(query, "field `#{field}` is not available in the subquery. " <>
"Subquery only returns fields: #{inspect(available_fields)}")
end
end)
Expand Down
14 changes: 14 additions & 0 deletions test/ecto/query/planner_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2670,6 +2670,20 @@ defmodule Ecto.Query.PlannerTest do
] = query.select.fields
end

test "normalze: select list of fields from subquery source" do
{_, _, _, select} = subquery(Post) |> select([p], [:title]) |> normalize_with_params()
%{from: {_, {:source, {_, postprocess_schema}, _, types}}} = select
assert postprocess_schema == Post
assert types == [title: :string]
end

test "normalze: select map/2 from subquery source" do
{_, _, _, select} = subquery(Post) |> select([p], map(p, [:title])) |> normalize_with_params()
%{from: {_, {:source, {_, postprocess_schema}, _, types}}} = select
assert postprocess_schema == nil
assert types == [title: :string]
end

test "normalize: select with :%{}" do
query = Post |> select([p], %{p | title: "foo"}) |> normalize()
assert query.select.expr == {:%{}, [], [{:|, [], [{:&, [], [0]}, [title: "foo"]]}]}
Expand Down
2 changes: 1 addition & 1 deletion test/ecto/query/subquery_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ defmodule Ecto.Query.SubqueryTest do
query = normalize(from(c in Comment, join: p in subquery(subquery), on: true, select: p.title))
assert query.select.fields == [{{:., [type: :string], [{:&, [], [1]}, :title]}, [], []}]

subquery = from p in Post, select: %{id: p.id, title: p.title}
subquery = from p in Post, select: struct(p, [:id, :title])

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josevalim The fix was surfacing an error in this test. Basically it fails now because select: [atom()] is considered the same as struct/2 more strictly.

Just want to make sure this is still the right interpretation of the list of atoms. It's in the docs but not sure if it's out of date

It is also possible to select a struct and limit the returned fields at the same time:

from(City, select: [:name])

The syntax above is equivalent to:

from(city in City, select: struct(city, [:name]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, select: [:foo, :bar] keeps whatever the from clause is. Structs if there are source+schema, maps if we only have the source.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok I believe what I just pushed now is the correct solution then. sorry for the back and forth. would you mind taking a look once more?

query = normalize(from(p in subquery(subquery), select: [:title]))
assert [{{:., _, [{:&, [], [0]}, :title]}, [], []}] = query.select.fields

Expand Down
Loading