Skip to content

Commit 0948cee

Browse files
committed
Revert "Make subquery merging more consistent"
This reverts commit aa26cd1. There are regressions in ecto_sql suite.
1 parent aa26cd1 commit 0948cee

File tree

3 files changed

+166
-122
lines changed

3 files changed

+166
-122
lines changed

lib/ecto/query/planner.ex

Lines changed: 124 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ defmodule Ecto.Query.Planner do
99
end
1010

1111
@parent_as __MODULE__
12-
@aggs ~w(count avg min max sum row_number rank dense_rank percent_rank cume_dist ntile lag lead first_value last_value nth_value)a
1312

1413
@doc """
1514
Converts a query to a list of joins.
@@ -300,110 +299,107 @@ defmodule Ecto.Query.Planner do
300299
defp normalize_subquery_select(query, adapter, source?) do
301300
{expr, %{select: select} = query} = rewrite_subquery_select_expr(query, source?)
302301
{expr, _} = prewalk(expr, :select, query, select, 0, adapter)
303-
{source, fields, _from} = collect_fields(expr, [], :never, query, select.take, true)
304-
{source, fields, []} = normalize_subquery_source(source, Enum.reverse(fields), query)
305-
{put_in(query.select.fields, fields), source}
302+
{meta, _fields, _from} = collect_fields(expr, [], :never, query, select.take, true)
303+
{query, meta}
306304
end
307305

308-
# Convert single field lookups into a map
309-
defp rewrite_subquery_select_expr(
310-
%{select: %{expr: {{:., _, [{:&, _, [_]}, field]}, _, []} = expr}} = query,
311-
_source?
312-
) do
313-
expr = {:%{}, [], [{field, expr}]}
314-
{expr, put_in(query.select.expr, expr)}
306+
# If we are selecting a source, we keep it as is.
307+
# Otherwise we normalize the select, which converts them into structs.
308+
# This means that `select: p` in subqueries will be nullable in a join.
309+
defp rewrite_subquery_select_expr(%{select: %{expr: {:&, _, [_]} = expr}} = query, _source?) do
310+
{expr, query}
315311
end
316312

317-
defp rewrite_subquery_select_expr(
318-
%{select: %{expr: {agg, _, [{{:., _, [{:&, _, [_]}, field]}, _, []} | _]} = expr}} = query,
319-
_source?
320-
) when agg in @aggs do
321-
expr = {:%{}, [], [{field, expr}]}
322-
{expr, put_in(query.select.expr, expr)}
323-
end
313+
defp rewrite_subquery_select_expr(%{select: select} = query, source?) do
314+
%{expr: expr, take: take} = select
324315

325-
defp rewrite_subquery_select_expr(query, false) do
326-
error!(query, "subquery must return a single field in order to be used on the right-side of `in`")
327-
end
316+
expr =
317+
case subquery_select(expr, take, query) do
318+
{nil, fields} ->
319+
{:%{}, [], fields}
328320

329-
defp rewrite_subquery_select_expr(query, _source?) do
330-
{query.select.expr, query}
331-
end
321+
{struct, fields} ->
322+
{:%, [], [struct, {:%{}, [], fields}]}
332323

333-
defp normalize_subquery_source({:map, left, extra}, rest, query) do
334-
normalize_subquery_source({:merge, left, {:map, extra}}, rest, query)
335-
end
324+
:error when source? ->
325+
error!(query, "subquery/cte must select a source (t), a field (t.field) or a map, got: `#{Macro.to_string(expr)}`")
336326

337-
defp normalize_subquery_source({:merge, left, right}, rest, query) do
338-
{left, left_fields, rest} = normalize_subquery_source(left, rest, query)
339-
{right, right_fields, rest} = normalize_subquery_source(right, rest, query)
340-
{merge_subquery_source(left, right, query), Keyword.merge(left_fields, right_fields), rest}
341-
end
327+
:error ->
328+
expr
329+
end
342330

343-
defp normalize_subquery_source({:source, _, _, types} = source, rest, _query) do
344-
{fields, rest} = zip_fields(types, rest, [])
345-
{source, fields, rest}
331+
{expr, put_in(query.select.expr, expr)}
346332
end
347333

348-
defp normalize_subquery_source({:struct, _name, types} = struct, rest, _query) do
349-
{fields, rest} = zip_fields(types, rest, [])
350-
{struct, fields, rest}
351-
end
334+
defp subquery_select({:merge, _, [left, right]}, take, query) do
335+
{left_struct, left_fields} = subquery_select(left, take, query)
336+
{right_struct, right_fields} = subquery_select(right, take, query)
352337

353-
defp normalize_subquery_source({:map, types} = map, rest, _query) do
354-
{fields, rest} = zip_fields(types, rest, [])
355-
{map, fields, rest}
356-
end
338+
unless is_nil(left_struct) or is_nil(right_struct) or left_struct == right_struct do
339+
error!(query, "cannot merge #{inspect(left_struct)} and #{inspect(right_struct)} because they are different structs")
340+
end
357341

358-
defp normalize_subquery_source(_source, _fields, query) do
359-
error!(query, "subquery/cte must select a source (t), a field (t.field) or a map, got: `#{Macro.to_string(query.select.expr)}`")
342+
{left_struct || right_struct, Keyword.merge(left_fields, right_fields)}
360343
end
344+
defp subquery_select({:%, _, [name, map]}, take, query) do
345+
{_, fields} = subquery_select(map, take, query)
346+
{name, fields}
347+
end
348+
defp subquery_select({:%{}, _, [{:|, _, [{:&, [], [ix]}, pairs]}]} = expr, take, query) do
349+
assert_subquery_fields!(query, expr, pairs)
350+
{source, _} = source_take!(:select, query, take, ix, ix)
351+
{struct, fields} = subquery_struct_and_fields(source)
361352

362-
defp merge_subquery_source({:source, schema, prefix, types1}, {:source, schema, prefix, types2}, _query),
363-
do: {:source, schema, prefix, Keyword.merge(types1, types2)}
364-
365-
defp merge_subquery_source({:source, schema, prefix, types}, {:map, fields}, _query),
366-
do: {:source, schema, prefix, merge_types_and_fields(types, fields, [])}
367-
368-
defp merge_subquery_source({:struct, name, fields1}, {:struct, name, fields2}, _query),
369-
do: {:struct, name, Keyword.merge(fields1, fields2)}
370-
371-
defp merge_subquery_source({:struct, name, fields1}, {:map, fields2}, _query),
372-
do: {:struct, name, Keyword.merge(fields1, fields2)}
373-
374-
defp merge_subquery_source({:map, fields1}, {:map, fields2}, _query),
375-
do: {:map, Keyword.merge(fields1, fields2)}
376-
377-
defp merge_subquery_source(left, right, query),
378-
do: error!(query, "cannot merge #{inspect(left)} and #{inspect(right)} in subquery")
353+
# Map updates may contain virtual fields, so we need to consider those
354+
valid_keys = if struct, do: Map.keys(struct.__struct__), else: fields
355+
update_keys = Keyword.keys(pairs)
379356

380-
# If the field exists in the schema, then its type is the same as in the schema.
381-
# If the field does not exist, then its type is any.
382-
defp merge_types_and_fields(types, [{field, _} | extra], acc) do
383-
case List.keytake(types, field, 0) do
384-
{{field, type}, types} -> merge_types_and_fields(types, extra, [{field, type} | acc])
385-
nil -> merge_types_and_fields(types, extra, [{field, :any} | acc])
357+
case update_keys -- valid_keys do
358+
[] -> :ok
359+
[key | _] -> error!(query, "invalid key `#{inspect key}` for `#{inspect struct}` on map update in subquery/cte")
386360
end
387-
end
388361

389-
defp merge_types_and_fields(types, [], acc) do
390-
types ++ Enum.reverse(acc)
362+
# In case of map updates, we need to remove duplicated fields
363+
# at query time because we use the field names as aliases and
364+
# duplicate aliases will lead to invalid queries.
365+
kept_keys = fields -- update_keys
366+
{struct, subquery_fields(kept_keys, ix) ++ pairs}
367+
end
368+
defp subquery_select({:%{}, _, pairs} = expr, _take, query) do
369+
assert_subquery_fields!(query, expr, pairs)
370+
{nil, pairs}
371+
end
372+
defp subquery_select({:&, _, [ix]}, take, query) do
373+
{source, _} = source_take!(:select, query, take, ix, ix)
374+
{struct, fields} = subquery_struct_and_fields(source)
375+
{struct, subquery_fields(fields, ix)}
376+
end
377+
defp subquery_select({{:., _, [{:&, _, [ix]}, field]}, _, []}, _take, _query) do
378+
{nil, subquery_fields([field], ix)}
379+
end
380+
defp subquery_select(_expr, _take, _query) do
381+
:error
391382
end
392383

393-
defp zip_fields([{field, _type} | types], [value | values], acc),
394-
do: zip_fields(types, values, [{field, value} | acc])
395-
396-
defp zip_fields([], values, acc),
397-
do: {Enum.reverse(acc), values}
398-
399-
defp subquery_type_for({:source, _, _, fields}, field),
400-
do: Keyword.fetch(fields, field)
384+
defp subquery_struct_and_fields({:source, {_, schema}, _, types}) do
385+
{schema, Keyword.keys(types)}
386+
end
387+
defp subquery_struct_and_fields({:struct, name, types}) do
388+
{name, Keyword.keys(types)}
389+
end
390+
defp subquery_struct_and_fields({:map, types}) do
391+
{nil, Keyword.keys(types)}
392+
end
401393

402-
defp subquery_type_for({:struct, _name, types}, field),
403-
do: subquery_type_for_value(types, field)
394+
defp subquery_fields(fields, ix) do
395+
for field <- fields do
396+
{field, {{:., [], [{:&, [], [ix]}, field]}, [], []}}
397+
end
398+
end
404399

405-
defp subquery_type_for({:map, types}, field),
406-
do: subquery_type_for_value(types, field)
400+
defp subquery_type_for({:source, _, _, fields}, field), do: Keyword.fetch(fields, field)
401+
defp subquery_type_for({:struct, _name, types}, field), do: subquery_type_for_value(types, field)
402+
defp subquery_type_for({:map, types}, field), do: subquery_type_for_value(types, field)
407403

408404
defp subquery_type_for_value(types, field) do
409405
case Keyword.fetch(types, field) do
@@ -413,6 +409,25 @@ defmodule Ecto.Query.Planner do
413409
end
414410
end
415411

412+
defp assert_subquery_fields!(query, expr, pairs) do
413+
Enum.each(pairs, fn
414+
{key, _} when not is_atom(key) ->
415+
error!(query, "only atom keys are allowed when selecting a map in subquery, got: `#{Macro.to_string(expr)}`")
416+
{key, value} ->
417+
if valid_subquery_value?(value) do
418+
{key, value}
419+
else
420+
error!(query, "maps, lists, tuples and sources are not allowed as map values in subquery, got: `#{Macro.to_string(expr)}`")
421+
end
422+
end)
423+
end
424+
425+
defp valid_subquery_value?({_, _}), do: false
426+
defp valid_subquery_value?(args) when is_list(args), do: false
427+
defp valid_subquery_value?({container, _, args})
428+
when container in [:{}, :%{}, :&] and is_list(args), do: false
429+
defp valid_subquery_value?(_), do: true
430+
416431
defp plan_joins(query, sources, offset, adapter) do
417432
plan_joins(query.joins, query, [], sources, [], 1, offset, adapter)
418433
end
@@ -984,16 +999,17 @@ defmodule Ecto.Query.Planner do
984999

9851000
# We don't want to use normalize_subquery_select because we are
9861001
# going to prepare the whole query ourselves next.
987-
{_expr, inner_query} = rewrite_subquery_select_expr(inner_query, true)
1002+
{_, inner_query} = rewrite_subquery_select_expr(inner_query, true)
9881003
{inner_query, counter} = traverse_exprs(inner_query, :all, counter, fun)
9891004

9901005
# Now compute the fields as keyword lists so we emit AS in Ecto query.
9911006
%{select: %{expr: expr, take: take}} = inner_query
9921007
{source, fields, _from} = collect_fields(expr, [], :never, inner_query, take, true)
993-
{_source, fields, []} = normalize_subquery_source(source, Enum.reverse(fields), query)
1008+
{_, keys} = subquery_struct_and_fields(source)
1009+
inner_query = put_in(inner_query.select.fields, Enum.zip(keys, Enum.reverse(fields)))
9941010

995-
inner_query = put_in(inner_query.select.fields, fields)
9961011
{_, inner_query} = pop_in(inner_query.aliases[@parent_as])
1012+
9971013
{[{name, inner_query} | queries], counter}
9981014

9991015
{name, %QueryExpr{expr: {:fragment, _, _} = fragment} = query_expr}, {queries, counter} ->
@@ -1066,12 +1082,23 @@ defmodule Ecto.Query.Planner do
10661082
{fragments, acc} = prewalk(fragments, kind, query, expr, acc, adapter)
10671083
{{:fragment, meta, fragments}, acc}
10681084
end
1069-
defp prewalk_source(%Ecto.SubQuery{query: inner_query} = subquery, _kind, query, _expr, counter, adapter) do
1085+
defp prewalk_source(%Ecto.SubQuery{query: inner_query} = subquery, kind, query, _expr, counter, adapter) do
10701086
try do
10711087
inner_query = put_in inner_query.aliases[@parent_as], query
10721088
{inner_query, counter} = normalize_query(inner_query, :all, adapter, counter)
10731089
{inner_query, _} = normalize_select(inner_query, true)
10741090
{_, inner_query} = pop_in(inner_query.aliases[@parent_as])
1091+
1092+
inner_query =
1093+
# If the subquery comes from a select, we are not really interested on the fields
1094+
if kind == :where do
1095+
inner_query
1096+
else
1097+
update_in(inner_query.select.fields, fn fields ->
1098+
subquery.select |> subquery_struct_and_fields() |> elem(1) |> Enum.zip(fields)
1099+
end)
1100+
end
1101+
10751102
{%{subquery | query: inner_query}, counter}
10761103
rescue
10771104
e -> raise Ecto.SubQueryError, query: query, exception: e
@@ -1240,7 +1267,11 @@ defmodule Ecto.Query.Planner do
12401267
end
12411268
end
12421269

1243-
defp normalize_select(%{select: %{fields: nil}} = query, keep_literals?) do
1270+
defp normalize_select(%{select: nil} = query, _keep_literals?) do
1271+
{query, nil}
1272+
end
1273+
1274+
defp normalize_select(query, keep_literals?) do
12441275
%{assocs: assocs, preloads: preloads, select: select} = query
12451276
%{take: take, expr: expr} = select
12461277
{tag, from_take} = Map.get(take, 0, {:any, []})
@@ -1288,10 +1319,6 @@ defmodule Ecto.Query.Planner do
12881319
{put_in(query.select.fields, fields), select}
12891320
end
12901321

1291-
defp normalize_select(query, _keep_literals?) do
1292-
{query, nil}
1293-
end
1294-
12951322
# Handling of source
12961323

12971324
defp collect_fields({:merge, _, [{:&, _, [0]}, right]}, fields, :none, query, take, keep_literals?) do
@@ -1321,6 +1348,8 @@ defmodule Ecto.Query.Planner do
13211348

13221349
# Expression handling
13231350

1351+
@aggs ~w(count avg min max sum row_number rank dense_rank percent_rank cume_dist ntile lag lead first_value last_value nth_value)a
1352+
13241353
defp collect_fields({agg, _, [{{:., dot_meta, [{:&, _, [_]}, _]}, _, []} | _]} = expr,
13251354
fields, from, _query, _take, _keep_literals?)
13261355
when agg in @aggs do
@@ -1565,8 +1594,9 @@ defmodule Ecto.Query.Planner do
15651594
{types, fields} = select_dump(schema.__schema__(:query_fields), schema.__schema__(:dump), ix)
15661595
{{:source, {source, schema}, prefix || query.prefix, types}, fields}
15671596

1568-
{:error, %Ecto.SubQuery{select: select, query: inner_query}} ->
1569-
{select, Enum.map(inner_query.select.fields, &select_field(elem(&1, 0), ix))}
1597+
{:error, %Ecto.SubQuery{select: select}} ->
1598+
{_, fields} = subquery_struct_and_fields(select)
1599+
{select, Enum.map(fields, &select_field(&1, ix))}
15701600
end
15711601
end
15721602

test/ecto/query/planner_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,17 +1459,17 @@ defmodule Ecto.Query.PlannerTest do
14591459
test "raises a runtime error if more than 1 field is selected" do
14601460
s = from(p in Post, select: [p.visits, p.id])
14611461

1462-
assert_raise Ecto.SubQueryError, fn ->
1462+
assert_raise Ecto.QueryError, fn ->
14631463
from(p in Post, where: p.id in subquery(s))
14641464
|> normalize()
14651465
end
14661466

1467-
assert_raise Ecto.SubQueryError, fn ->
1467+
assert_raise Ecto.QueryError, fn ->
14681468
from(p in Post, where: p.id > any(s))
14691469
|> normalize()
14701470
end
14711471

1472-
assert_raise Ecto.SubQueryError, fn ->
1472+
assert_raise Ecto.QueryError, fn ->
14731473
from(p in Post, where: p.id > all(s))
14741474
|> normalize()
14751475
end

0 commit comments

Comments
 (0)