Skip to content

feat(query): existence-test error surfaces and docs — the feature's edges#322

Closed
0x054 wants to merge 1 commit into
feat/existence-tests-316-m2m-existsfrom
feat/existence-tests-317-errors-docs
Closed

feat(query): existence-test error surfaces and docs — the feature's edges#322
0x054 wants to merge 1 commit into
feat/existence-tests-316-m2m-existsfrom
feat/existence-tests-317-errors-docs

Conversation

@0x054

@0x054 0x054 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Closes #317, closes #311, closes #307, closes #308

Stacked on #321 (slice 3/4); base retargets as the stack merges. Final slice of the existence-tests PRD (#311, ADR-0007) — when this merges, the PRD and the originating Pinch PRDs (#307, #308) close.

Error surfaces

Every remaining place a reverse or M2M relation can be named now answers with the supported spelling:

  • join()/left_join() on a reverse/M2M edge stays rejected — the pinned "a join never multiplies root rows" property is preserved by rejection, not implicit DISTINCT — and the error now names .exists() (previously the opaque generic "must return a relation path"). Pinned for reverse-FK, M2M, and both join verbs.
  • in_() with a query RHS stays a TypeError, and the message now names the existence test when the RHS is a Query/ProjectedQuery (the feat Reverse-relation membership predicates: EXISTS from the root query (PRD from Pinch) #307 repro's second guess: t.id.in_(subquery)). A non-query RHS keeps the plain message — no hint where none applies. in_(subquery) remains declined, not deferred (ADR-0007).
  • include() rejection verified unchanged for M2M edges too — population stays a separate future mechanism; membership filtering and population remain distinct axes.

Docs

  • New guide section — "Existence Tests on Reverse & Many-to-Many Relations": real models with Assignment + Annotated tabs, lambda-style predicates throughout, rendered SQL for both the feat Reverse-relation membership predicates: EXISTS from the root query (PRD from Pinch) #307 is_transfer shape and the feat OR-composition across a left-joined reverse child table (PRD from Pinch) #308 line-aware category filter, the explicit grouping contrast (exists(A & B) vs exists(A) & exists(B)), negation, forward traversal inside the subquery, nesting, M2M from both sides, the loud dead ends, and the exists-test vs await query.exists() terminal disambiguation. Backed by two runnable example scripts (existence_tests.py + annotated twin) exercised by the docs-example harness.
  • Operators table gains the .exists(...) row; the forward existence/absence section and the instance-side reverse-relation section cross-link the reverse direction.
  • Reference (api/queries.md) states the one-verb rule and its rejections.
  • Roadmap + "Not Yet Supported" now distinguish reverse predicates (shipped — the existence test) from reverse population (still future — include() rejection stays), and track cross-scope correlation as feat Cross-scope correlation in existence tests (deferred from ADR-0007) #309.

Acceptance criteria (#317)

  • left_join() on a reverse/M2M edge raises naming .exists() (pinned by test)
  • in_() with a query RHS raises naming .exists() (pinned by test)
  • include() on a reverse/M2M edge unchanged (existing rejection verified by test)
  • Guide section: existence tests with rendered SQL, grouping contrast, negation, M2M; Assignment + Annotated tabs on model declarations; lambda predicates throughout
  • Reference + roadmap/"not yet supported" docs updated (reverse predicates shipped; reverse population still future)
  • Docs examples covered by the docs-example test harness

Testing

  • pytest --db-backends=sqlite,postgres: 1679 passed (full matrix, local Postgres), including the new pinned error surfaces and both docs example scripts
  • ty check and ruff check: clean
  • No Rust changes in this slice

…dges

Every remaining surface where a reverse or M2M relation can be named
now answers with the supported spelling, and the docs teach existence
tests end to end.

Error surfaces beyond the proxy's own (those shipped in the tracer
bullet):

- join()/left_join() on a reverse/M2M edge stays rejected — the pinned
  "a join never multiplies root rows" property is preserved by
  rejection, not implicit DISTINCT — with the error naming .exists()
  (previously an opaque "must return a relation path" TypeError).
- in_() with a query RHS stays a TypeError whose message names the
  existence test when the RHS is a query (the #307 repro's second
  guess); a non-query RHS keeps the plain message.
- include() rejection verified unchanged for M2M edges too —
  population remains a separate future mechanism.

Docs:

- New guide section "Existence Tests on Reverse & Many-to-Many
  Relations": real models (Assignment + Annotated tabs), lambda-style
  predicates throughout, rendered SQL for the #307/#308 shapes, the
  explicit grouping contrast, negation, traversal-inside-the-test,
  nesting, M2M, the loud dead ends, and the exists-test vs
  query-terminal disambiguation. Backed by two new runnable example
  scripts exercised by the docs harness.
- Operators table gains the .exists() row; the forward
  existence/absence section and the reverse-relation filtering section
  cross-link the reverse direction.
- Reference (api/queries.md) states the one-verb rule and its
  rejections.
- Roadmap and "Not Yet Supported" now distinguish reverse predicates
  (shipped — the existence test) from reverse population (still
  future; the include() rejection stays), and track cross-scope
  correlation as #309.

Refs #311, #317
@0x054

0x054 commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #323 (slices 2–4 combined into one rebase-mergeable PR; content identical).

@0x054
0x054 deleted the branch feat/existence-tests-316-m2m-exists July 18, 2026 15:41
@0x054 0x054 closed this Jul 18, 2026
@0x054
0x054 deleted the feat/existence-tests-317-errors-docs branch July 18, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant