Parent
#311 (feat PRD: Existence tests — .exists() on reverse and M2M relations)
What to build
Scoped existence tests: the optional inner lambda, resolved as a full ferro predicate over the child model through the existing predicate resolver.
Everything a root predicate can do works inside the test: every operator, forward-FK traversal (rendered as joins inside the EXISTS subquery under unchanged ADR-0006 semantics — INNER at every hop, narrowing), &/|/~ composition, and nested existence tests to arbitrary depth (the exists node's inner tree is an ordinary condition tree, recursion for free).
Cross-scope references are rejected in this slice, the moment inner lambdas exist: an inner-tree leaf built from any proxy other than the inner lambda's own parameter, or a field proxy appearing as a comparison right-hand side, is a build-time error pointing at the deferred capability (#309). Silent misrendering is the failure mode this guard exists to prevent.
Demo: the full #308 query —
Txn.where(lambda t: t.category_id.in_(ids)
| t.lines.exists(lambda l: l.category_id.in_(ids)))
— a three-line split matches once, child-less roots survive the OR through the root branch, and keyset order_by + limit compose unchanged, on both backends.
Acceptance criteria
Blocked by
Parent
#311 (
featPRD: Existence tests —.exists()on reverse and M2M relations)What to build
Scoped existence tests: the optional inner lambda, resolved as a full ferro predicate over the child model through the existing predicate resolver.
Everything a root predicate can do works inside the test: every operator, forward-FK traversal (rendered as joins inside the EXISTS subquery under unchanged ADR-0006 semantics — INNER at every hop, narrowing),
&/|/~composition, and nested existence tests to arbitrary depth (the exists node's inner tree is an ordinary condition tree, recursion for free).Cross-scope references are rejected in this slice, the moment inner lambdas exist: an inner-tree leaf built from any proxy other than the inner lambda's own parameter, or a field proxy appearing as a comparison right-hand side, is a build-time error pointing at the deferred capability (#309). Silent misrendering is the failure mode this guard exists to prevent.
Demo: the full #308 query —
— a three-line split matches once, child-less roots survive the OR through the root branch, and keyset
order_by+limitcompose unchanged, on both backends.Acceptance criteria
&/|/~over the child modelfeatOR-composition across a left-joined reverse child table (PRD from Pinch) #308 shape passes e2e on both backends: multi-line match → one root row; child-less root survives the OR; keyset compositiont.lines.exists(lambda l: A & B)(one child matches both) vst.lines.exists(A-test) & t.lines.exists(B-test)(some child each) return different, correct row setsfeatCross-scope correlation in existence tests (deferred from ADR-0007) #309 (messages pinned by tests)Blocked by
featExistence tests 1/4: bare.exists()on reverse FK — the tracer bullet #314