From a85fd9ba09a150a7e91f25743df88a3d272baf3e Mon Sep 17 00:00:00 2001 From: MilagrosMarin Date: Fri, 17 Jul 2026 03:10:05 +0200 Subject: [PATCH] docs(trace): describe self.upstream as lazily built on first access Post-#1499 (v2.3.1), self.upstream is no longer constructed eagerly before make(). The framework records the key; Diagram.trace(self & key) is built the first time the user accesses self.upstream and memoized for the rest of the call. make() implementations that never read upstream pay nothing. Updates two lifecycle descriptions to match: - trace.md 'Where it's set' - autopopulate.md 4.4 Lifecycle / Lazy bullets Docs-only. --- src/reference/specs/autopopulate.md | 4 ++-- src/reference/specs/trace.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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