Repository: elixir-ecto/ecto
Pull request #4282 (2023-09-20 04:12:53)

Pass parent aliases to combination queries
Closes https://github.com/elixir-ecto/ecto/issues/4281

The issue is that the right hand side of combinations queries are stored in the `:combinations` list of the main query and they weren't being passed the parent aliases.

This only matters during normalization because that is when the parent index is checked. Then after the query is normalized the parent alias is popped out again and is handled later in Ecto SQL. 

Companion Ecto SQL PR: https://github.com/elixir-ecto/ecto_sql/pull/561

greg-rychlewski: Sorry for the update. I realized I could do it this way instead. I think this is a bit nicer but what do you guys think?
greg-rychlewski: @josevalim Sorry just wanted to double check before merging. Are you ok with the small refactor I did? 

Patch:
diff --git a/lib/ecto/query/planner.ex b/lib/ecto/query/planner.ex
index cd767915..8eb55d91 100644
--- a/lib/ecto/query/planner.ex
+++ b/lib/ecto/query/planner.ex
@@ -1096,13 +1096,16 @@ defmodule Ecto.Query.Planner do
     {Enum.reverse(exprs), counter}
   end
 
-  defp validate_and_increment(:combination, _query, combinations, counter, operation, adapter) do
+  defp validate_and_increment(:combination, query, combinations, counter, operation, adapter) do
     fun = &validate_and_increment(&1, &2, &3, &4, operation, adapter)
+    parent_aliases = query.aliases[@parent_as]
 
     {combinations, counter} =
       Enum.reduce combinations, {[], counter}, fn {type, combination_query}, {combinations, counter} ->
+        combination_query = put_in(combination_query.aliases[@parent_as], parent_aliases)
         {combination_query, counter} = traverse_exprs(combination_query, operation, counter, fun)
         {combination_query, _} = combination_query |> normalize_select(true)
+        {_, combination_query} = pop_in(combination_query.aliases[@parent_as])
         {[{type, combination_query} | combinations], counter}
       end
 
@@ -1167,7 +1170,7 @@ defmodule Ecto.Query.Planner do
   end
   defp prewalk_source(%Ecto.SubQuery{query: inner_query} = subquery, kind, query, _expr, counter, adapter) do
     try do
-      inner_query = put_in inner_query.aliases[@parent_as], query
+      inner_query = put_in(inner_query.aliases[@parent_as], query)
       {inner_query, counter} = normalize_query(inner_query, :all, adapter, counter)
       {inner_query, _} = normalize_select(inner_query, true)
       {_, inner_query} = pop_in(inner_query.aliases[@parent_as])


Test patch:
diff --git a/test/ecto/query/subquery_test.exs b/test/ecto/query/subquery_test.exs
index 1e8824d8..9ad35657 100644
--- a/test/ecto/query/subquery_test.exs
+++ b/test/ecto/query/subquery_test.exs
@@ -534,5 +534,12 @@ defmodule Ecto.Query.SubqueryTest do
         from(c in Comment, where: c.post_id in subquery(p)) |> normalize()
       end
     end
+
+    test "with combinations and parent_as/1" do
+      right_query = from(c in Comment, where: c.id == parent_as(:c).id, select: c.id)
+      left_query = from(c in Comment, where: c.id == parent_as(:c).id, select: c.id)
+      union_query = union(left_query, ^right_query)
+      from(c in Comment, as: :c, where: c.id in subquery(union_query)) |> normalize()
+    end
   end
 end