Skip to content

Bug: $null on relation paths is silently broken; $none untested on direct OneToMany/ManyToMany #1140

Description

@SIMPL-RouvenKruse

Summary

After investigating the codebase, we found that:

  1. $none on a bare relation path already works for nested to-many paths (e.g. filter[home.pillows]=$none) — producing a bare NOT EXISTS correlated subquery with no inner column conditions.
  2. $none on direct OneToMany / ManyToMany relation paths is untested — e.g. filter[toys]=$none, filter[friends]=$none.
  3. $null on a relation path does NOT work correctly — it silently produces a bare EXISTS (same as $any), not NOT EXISTS, because the $null operator is placed into the findOperator of an empty sub-filter that never gets applied. The $null/$not:$null distinction is effectively ignored.

Actual Gaps

Gap 1: Missing tests for $none on direct OneToMany / ManyToMany

The $none quantifier on a bare relation path (no sub-column) produces an empty sub-filter, which results in a bare NOT EXISTS correlated subquery — exactly the right behavior for "no related records". This is already implemented and tested for home.pillows (nested path), but there are no tests verifying it works for:

  • filter[toys]=$none (direct OneToMany)
  • filter[friends]=$none (direct ManyToMany, owning side)
  • filter[friendOf]=$none (direct ManyToMany, inverse side)

Gap 2: $null / $not:$null on relation paths is misleading / broken

When filter[toys]=$null is used:

  • token.operator = $null, token.quantifier = $any (default)
  • The sub-filter is empty (no sub-column)
  • addWhereCondition is never called — the IsNull() findOperator is ignored
  • Quantifier $any → bare EXISTS → "cats WITH toys" (wrong — user expected "cats with NO toys")

When filter[toys]=$not:$null:

  • Same result — also bare EXISTS — the $not suffix is also ignored

Neither maps to the expected behavior. To get "cats with no toys", users must use $none, not $null.

What Needs to Be Done

Option A (Minimal — recommended): Test + document existing behavior

  1. Add tests confirming $none works on direct OneToMany (toys) and ManyToMany (friends, friendOf) bare relation paths
  2. If any tests fail, fix the underlying issue
  3. Document in README that $none on a bare relation path means "no related records"

Option B (Extended): Also support $null as an alias for $none on relation paths

Make filter[toys]=$null behave like filter[toys]=$none when the filter key is a to-many relation (not a column). This requires:

  1. In addToManySubFilters() — detect when a filter key equals the to-many path exactly and the operator is $null; override the quantifier to $none (NOT EXISTS)
  2. For $not:$null → override to $any (EXISTS)
  3. Add tests for both syntaxes

Current Behavior Reference

Filter SQL Correct?
filter[home.pillows]=$none NOT EXISTS (pillows correlated) ✅ works, tested
filter[toys]=$none NOT EXISTS (toys correlated) ✅ likely works, untested
filter[friends]=$none NOT EXISTS (junction correlated) ✅ likely works, untested
filter[toys]=$null bare EXISTS (wrong!) ❌ broken
filter[toys]=$not:$null bare EXISTS (same as above!) ❌ broken

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions