From 354a354861ba9adfb8571ea4b90673ebf80a3dc3 Mon Sep 17 00:00:00 2001 From: Alan Heywood Date: Wed, 3 Dec 2025 12:52:49 +1000 Subject: [PATCH] fix: aggregate with parent ref in relationship filter and sorting on relationship field A test covering this fix has been created in ash_postgres --- lib/query.ex | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/query.ex b/lib/query.ex index 504345a..553a91c 100644 --- a/lib/query.ex +++ b/lib/query.ex @@ -298,8 +298,19 @@ defmodule AshSql.Query do {calculations_require_rewrite, aggregates_require_rewrite, query} = rewrite_nested_selects(query) + parent_ref_attrs = + query.__ash_bindings__[:load_aggregates] + |> List.wrap() + |> extract_aggregate_parent_ref_attributes(resource) + + query_with_parent_refs = + Enum.reduce(parent_ref_attrs, query, fn attr, query -> + root_binding = query.__ash_bindings__.root_binding + from(row in query, select_merge: %{^attr => field(as(^root_binding), ^attr)}) + end) + query_with_order = - from(row in query, select_merge: %{__order__: over(row_number(), :order)}) + from(row in query_with_parent_refs, select_merge: %{__order__: over(row_number(), :order)}) query_without_limit_and_offset = query_with_order @@ -619,4 +630,44 @@ defmodule AshSql.Query do end) end) end + + defp extract_aggregate_parent_ref_attributes(aggregates, resource) do + aggregates + |> Enum.flat_map(fn aggregate -> + case aggregate.relationship_path do + [first_rel_name | _] -> + relationship = Ash.Resource.Info.relationship(resource, first_rel_name) + + if relationship && relationship.filter do + extract_parent_attrs_from_filter(relationship.filter) + else + [] + end + + _ -> + [] + end + end) + |> Enum.uniq() + end + + defp extract_parent_attrs_from_filter(filter) do + Ash.Filter.flat_map(filter, fn + %Ash.Query.Parent{expr: expr} -> + expr + |> Ash.Filter.list_refs() + |> Enum.filter(&Enum.empty?(&1.relationship_path)) + |> Enum.map(fn ref -> + case ref.attribute do + %{name: name} -> name + name when is_atom(name) -> name + _ -> nil + end + end) + |> Enum.reject(&is_nil/1) + + _other -> + [] + end) + end end