Skip to content

Commit e26a63b

Browse files
authored
fix: has_many with limit and sort not respected in exists query filters (#198)
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 ash-project/ash_postgres#667 - this code makes that test pass.
1 parent 4618a1c commit e26a63b

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

lib/expr.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2414,7 +2414,7 @@ defmodule AshSql.Expr do
24142414
AshSql.Join.related_subquery(first_relationship, query,
24152415
filter: filter,
24162416
filter_subquery?: true,
2417-
sort?: Map.get(first_relationship, :from_many?),
2417+
sort?: Map.get(first_relationship, :from_many?) || not is_nil(first_relationship.sort),
24182418
start_bindings_at: 1,
24192419
select_star?: !Map.get(first_relationship, :manual),
24202420
in_group?: true,

lib/join.ex

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ defmodule AshSql.Join do
426426
|> Ash.Query.set_context(relationship.context)
427427
|> Ash.Query.do_filter(relationship.filter, parent_stack: parent_resources)
428428
|> then(fn query ->
429-
if Map.get(relationship, :from_many?) && filter_subquery? do
429+
if (Map.get(relationship, :from_many?) || not is_nil(Map.get(relationship, :limit))) &&
430+
filter_subquery? do
430431
query
431432
else
432433
Ash.Query.do_filter(query, filter, parent_stack: parent_resources)
@@ -575,6 +576,48 @@ defmodule AshSql.Join do
575576
end
576577
end
577578

579+
defp limit_from_many(
580+
query,
581+
%{limit: limit, destination: destination},
582+
filter,
583+
filter_subquery?,
584+
opts
585+
)
586+
when is_integer(limit) do
587+
# Check if query has parent expressions - if so, we can't wrap in a non-lateral subquery
588+
# because parent references won't resolve across the subquery boundary
589+
has_parent_expr? = !!query.__ash_bindings__.context[:data_layer][:has_parent_expr?]
590+
591+
if filter_subquery? && !has_parent_expr? do
592+
# Wrap the limited query in a subquery, then apply filter on top
593+
query =
594+
from(row in Ecto.Query.subquery(from(row in query, limit: ^limit)),
595+
as: ^query.__ash_bindings__.root_binding
596+
)
597+
|> Map.put(:__ash_bindings__, query.__ash_bindings__)
598+
|> AshSql.Bindings.default_bindings(
599+
destination,
600+
query.__ash_bindings__.sql_behaviour
601+
)
602+
603+
{:ok, query} = AshSql.Filter.filter(query, filter, query.__ash_bindings__.resource)
604+
605+
if opts[:select_star?] do
606+
from(row in Ecto.Query.exclude(query, :select), select: 1)
607+
else
608+
query
609+
end
610+
else
611+
# When has_parent_expr?, we can't apply the limit in exists subquery
612+
# Fall through to the default clause which just applies select_star if needed
613+
if opts[:select_star?] do
614+
from(row in Ecto.Query.exclude(query, :select), select: 1)
615+
else
616+
query
617+
end
618+
end
619+
end
620+
578621
defp limit_from_many(query, _, _, _, opts) do
579622
if opts[:select_star?] do
580623
from(row in Ecto.Query.exclude(query, :select), select: 1)

0 commit comments

Comments
 (0)