From 702a2635285cb45e98d66a9758f0307eb73e83c0 Mon Sep 17 00:00:00 2001 From: Rebecca Le Date: Fri, 26 Dec 2025 16:52:21 +0800 Subject: [PATCH] fix: `has_many` with `limit` and `sort` not respected in `exists` query filters Previously, the relationship limit was not being applied when using the relationship in an `exists` query. After fixing that, then the limit was being applied in the wrong place - it needs to be *inside* the exists check pre-filtering, instead of outside it. The test for this feature is in AshPostgres https://github.com/ash-project/ash_postgres/pull/667 - this code makes that test pass. --- lib/expr.ex | 2 +- lib/join.ex | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/expr.ex b/lib/expr.ex index eb98150..2774380 100644 --- a/lib/expr.ex +++ b/lib/expr.ex @@ -2373,7 +2373,7 @@ defmodule AshSql.Expr do AshSql.Join.related_subquery(first_relationship, query, filter: filter, filter_subquery?: true, - sort?: Map.get(first_relationship, :from_many?), + sort?: Map.get(first_relationship, :from_many?) || not is_nil(first_relationship.sort), start_bindings_at: 1, select_star?: !Map.get(first_relationship, :manual), in_group?: true, diff --git a/lib/join.ex b/lib/join.ex index 14c8de8..c9d9e73 100644 --- a/lib/join.ex +++ b/lib/join.ex @@ -428,7 +428,8 @@ defmodule AshSql.Join do |> Ash.Query.set_context(relationship.context) |> Ash.Query.do_filter(relationship.filter, parent_stack: parent_resources) |> then(fn query -> - if Map.get(relationship, :from_many?) && filter_subquery? do + if (Map.get(relationship, :from_many?) || not is_nil(Map.get(relationship, :limit))) && + filter_subquery? do query else Ash.Query.do_filter(query, filter, parent_stack: parent_resources) @@ -576,6 +577,48 @@ defmodule AshSql.Join do end end + defp limit_from_many( + query, + %{limit: limit, destination: destination}, + filter, + filter_subquery?, + opts + ) + when is_integer(limit) do + # Check if query has parent expressions - if so, we can't wrap in a non-lateral subquery + # because parent references won't resolve across the subquery boundary + has_parent_expr? = !!query.__ash_bindings__.context[:data_layer][:has_parent_expr?] + + if filter_subquery? && !has_parent_expr? do + # Wrap the limited query in a subquery, then apply filter on top + query = + from(row in Ecto.Query.subquery(from(row in query, limit: ^limit)), + as: ^query.__ash_bindings__.root_binding + ) + |> Map.put(:__ash_bindings__, query.__ash_bindings__) + |> AshSql.Bindings.default_bindings( + destination, + query.__ash_bindings__.sql_behaviour + ) + + {:ok, query} = AshSql.Filter.filter(query, filter, query.__ash_bindings__.resource) + + if opts[:select_star?] do + from(row in Ecto.Query.exclude(query, :select), select: 1) + else + query + end + else + # When has_parent_expr?, we can't apply the limit in exists subquery + # Fall through to the default clause which just applies select_star if needed + if opts[:select_star?] do + from(row in Ecto.Query.exclude(query, :select), select: 1) + else + query + end + end + end + defp limit_from_many(query, _, _, _, opts) do if opts[:select_star?] do from(row in Ecto.Query.exclude(query, :select), select: 1)