diff --git a/src/reference/specs/autopopulate.md b/src/reference/specs/autopopulate.md index 7ae06b2f..276cc621 100644 --- a/src/reference/specs/autopopulate.md +++ b/src/reference/specs/autopopulate.md @@ -357,8 +357,8 @@ DataJoint 2.3 introduced `self.upstream` (and the underlying [`Diagram.trace`](d `self.upstream` behavior: -- **Lifecycle:** `self.upstream` is set to `Diagram.trace(self & key)` immediately before `make()` is invoked and cleared afterward — including when `make()` raises. Accessing it outside `make()` raises `DataJointError`. -- **Lazy:** the trace diagram is built once per `make()` call; no SQL fires until an ancestor is accessed and fetched. Fetch results are not cached — reading the same ancestor twice issues two queries. +- **Lifecycle:** the framework records the current key before invoking `make()`; `self.upstream` is built as `Diagram.trace(self & key)` on first access and cleared afterward — including when `make()` raises. Accessing it outside `make()` raises `DataJointError`. +- **Lazy:** the trace diagram is built at most once per `make()` call — on first `self.upstream` access. `make()` implementations that never read `self.upstream` never build the trace, and no SQL fires until an ancestor is accessed and fetched. Fetch results are not cached — reading the same ancestor twice issues two queries. - **Tripartite:** `self.upstream` is available across all tripartite phases (`make_fetch`, `make_compute`, `make_insert`) of the same `make()` call. - **Scope:** it exposes declared ancestors only. A table's own Parts are descendants, not ancestors — read them directly as `self.PartName`. diff --git a/src/reference/specs/trace.md b/src/reference/specs/trace.md index bff3180f..48f3823d 100644 --- a/src/reference/specs/trace.md +++ b/src/reference/specs/trace.md @@ -181,9 +181,9 @@ For a renamed-FK case (paralleling [Cascade Spec §Worked Example 1](cascade.md# ### Where it's set -The framework constructs `self.upstream = Diagram.trace(self & key)` immediately before invoking the user-defined `make(self, key)`. The construction happens once per `make()` call; `self.upstream` is a regular attribute on the bound `self` instance for the duration of the call. +Before invoking `make(self, key)`, the framework records the current key but defers constructing the trace. `self.upstream = Diagram.trace(self & key)` is built the first time the user accesses `self.upstream` inside `make()` and memoized for the rest of the call; `make()` implementations that never read `self.upstream` never build the trace. Both the key and the memoized trace are cleared after `make()` returns (including when it raises). -The construction is **lazy** in the sense that the underlying SQL is not issued until the user accesses an ancestor: `self.upstream[Session]` builds a `QueryExpression`, and `.fetch1()` on that expression triggers a SELECT. The trace diagram is built once per `make()` call, but fetch results are not cached — each `self.upstream[T]` access returns a fresh `QueryExpression`, so reading the same ancestor twice issues two SELECTs. +Once built, the trace diagram is reused for the remainder of the call. The underlying SQL is not issued until the user indexes into an ancestor: `self.upstream[Session]` builds a `QueryExpression`, and `.fetch1()` on that expression triggers a SELECT. Fetch results are not cached — each `self.upstream[T]` access returns a fresh `QueryExpression`, so reading the same ancestor twice issues two SELECTs. ### What it returns