Skip to content

Commit f2817c0

Browse files
committed
support selecting a subset of a subquery as a struct
1 parent 12dd3d0 commit f2817c0

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

lib/ecto/query/planner.ex

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2207,8 +2207,8 @@ defmodule Ecto.Query.Planner do
22072207
{{:ok, {:struct, _}}, {:fragment, _, _}} ->
22082208
error!(query, "it is not possible to return a struct subset of a fragment")
22092209

2210-
{{:ok, {:struct, _}}, %Ecto.SubQuery{}} ->
2211-
error!(query, "it is not possible to return a struct subset of a subquery")
2210+
{{:ok, {:struct, fields}}, %Ecto.SubQuery{select: select}} ->
2211+
handle_subquery_struct(select, fields, ix, query)
22122212

22132213
{{:ok, {_, []}}, {_, _, _}} ->
22142214
error!(
@@ -2280,6 +2280,39 @@ defmodule Ecto.Query.Planner do
22802280
end)
22812281
end
22822282

2283+
defp handle_subquery_struct(select, requested_fields, ix, query) do
2284+
available_fields = subquery_source_fields(select)
2285+
requested_fields = List.wrap(requested_fields)
2286+
2287+
Enum.each(requested_fields, fn field ->
2288+
unless field in available_fields do
2289+
error!(query, "field `#{field}` in struct/2 is not available in the subquery. " <>
2290+
"Subquery only returns fields: #{inspect(available_fields)}")
2291+
end
2292+
end)
2293+
2294+
schema =
2295+
case select do
2296+
{:struct, schema, _} -> schema
2297+
{:source, {_, schema}, _, _} when not is_nil(schema) -> schema
2298+
_ ->
2299+
error!(query, "it is not possible to return a struct subset of a subquery that does not return a schema or a struct")
2300+
end
2301+
2302+
types =
2303+
Enum.map(requested_fields, fn field ->
2304+
{:ok, type} = subquery_type_for(select, field)
2305+
{field, type}
2306+
end)
2307+
2308+
field_exprs =
2309+
Enum.map(types, fn {field, type} ->
2310+
{{:., [type: type], [{:&, [], [ix]}, field]}, [], []}
2311+
end)
2312+
2313+
{{:source, {nil, schema}, nil, types}, field_exprs}
2314+
end
2315+
22832316
defp select_field(field, ix, writable) do
22842317
{{:., [writable: writable], [{:&, [], [ix]}, field]}, [], []}
22852318
end

test/ecto/query/subquery_test.exs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,21 @@ defmodule Ecto.Query.SubqueryTest do
447447
subquery = from p in Post, select: %{id: p.id, title: p.title}
448448
query = normalize(from(p in subquery(subquery), select: map(p, [:title])))
449449
assert [{{:., _, [{:&, [], [0]}, :title]}, [], []}] = query.select.fields
450+
end
451+
452+
test "struct/2 with subqueries" do
453+
subquery = from p in Post, select: p
454+
query = normalize(from(p in subquery(subquery), select: struct(p, [:id, :title])))
455+
assert query.select.fields == [
456+
{{:., [type: CustomPermalink], [{:&, [], [0]}, :id]}, [], []},
457+
{{:., [type: :string], [{:&, [], [0]}, :title]}, [], []}
458+
]
459+
460+
subquery = from p in Post, select: p
461+
query = normalize(from(c in Comment, join: p in subquery(subquery), on: true, select: struct(p, [:title])))
462+
assert query.select.fields == [{{:., [type: :string], [{:&, [], [1]}, :title]}, [], []}]
450463

451-
assert_raise Ecto.QueryError, ~r/it is not possible to return a struct subset of a subquery/, fn ->
464+
assert_raise Ecto.QueryError, ~r/it is not possible to return a struct subset of a subquery that does not return a schema or a struct/, fn ->
452465
subquery = from p in Post, select: %{id: p.id, title: p.title}
453466
normalize(from(p in subquery(subquery), select: struct(p, [:title])))
454467
end

0 commit comments

Comments
 (0)