Loading the same relationship twice with different queries silently keeps only the last filter
- Dominant language
- Elixir
- Stars
- 2.5k
- Forks
- 426
- Avg merge
- 23h 26m
- Merged PRs (30d)
- 46
Description
**Describe the bug**
When one load statement names the same relationship twice with two different `Ash.Query` values, `Ash.Query.load/2` merges them by keeping the second query's `filter` (and `sort` / `limit`) and dropping the first, without an error or warning. Loads that come together from several places (rule modules, calculations, plugins) can therefore end up with one of them silently ignored.
`load_relationship/4` sees the relationship already in `query.load` and calls `load(existing, new)`; for a `%Ash.Query{}` statement that is `merge_load(new, existing)`, which merges `load`, `calculations`, `aggregates`, `select` and `timeout` (the `select` part since #2626) and keeps `new` as the base, so `filter`, `sort` and `limit` of `existing` are lost.
**To Reproduce**
```elixir
open = Ash.Query.filter(MyApp.Comment, is_nil(resolved_at))
resolved = Ash.Query.filter(MyApp.Comment, not is_nil(resolved_at))
query = Ash.Query.load(MyApp.Post, comments: open, comments: resolved)
query.load[:comments].filter
#=> #Ash.Filter (the `open` filter is gone)
query.errors
#=> []
```
The same happens with `Ash.load/2` on records, and with a filtered query followed by a plain atom load (`comments: open, :comments`), where the query survives because the atom adds nothing, so the outcome depends on argument order.
**Expected behavior**
Either an error (an `Ash.Error.Query.InvalidLoad` naming the relationship and both queries), or a documented rule. Silently discarding one filter is the one outcome a caller cannot detect. If merging is intended, the relationships guide should say which query wins; it currently does not mention duplicate loads at all.
**Runtime**
- Elixir 1.20.2, Erlang/OTP 28
- Ash 3.33.1, AshPostgres 2.13.1
**Context**
We assemble the load statement for a rules engine from several rule modules that each bound the same relationship with a time filter. We now dedupe and raise on conflicting queries ourselves before calling `Ash.load/2`; an error from Ash would let us drop that guard.
Contributor guide
Research direction
Start with Ash.Query.load/2, load_relationship/4, and merge_load, then reproduce the duplicate filtered loads described in the issue and compare the behavior with Ash.load/2 on records. Done means conflicting duplicate relationship queries no longer silently lose filters, with the chosen behavior represented by an Ash.Error.Query.InvalidLoad or documented in the relationships guide.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- elixir
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100