Repository navigation
docs(specs): normative spec of Diagram.expand, with a traversal shape catalog - #249
Open
dimitri-yatsenko wants to merge 13 commits into
Open
dimitri-yatsenko wants to merge 13 commits into
dimitri-yatsenko wants to merge 13 commits into
Conversation
Consolidate the expand/restrict traversal algebra (derived from first principles: the one primitive, edge rule R1, group rule R2, the additive expand and subtractive restrict, renamed-FK relabel fast-path, and the master-part group rule) into diagram.md as a 'Traversal algebra' design rationale — the single source of truth. cascade.md and trace.md now point to it as the downstream and upstream cases (expand down/up), keeping only their operation-specific content. Relocated from datajoint-python's DESIGN-expand-restrict.md (PR #1524), which is closed; the derivation and spec live with the other specs in datajoint-docs.
This was referenced Aug 10, 2026
Drop restrict and the additive/subtractive split from the diagram.md derivation.
The model is now one operation, expand(seed, direction in {up,down,both}): cascade
= down, trace = up, export = both, over the two rules R1 (edge) and R2 (group).
A filter over several independent tables is not a second operation — it either
seeds a common descendant (which inherits both keys) and expands both ways, or,
lacking one, is a union of expansions; the per-table 'AND of upstream conditions'
carving is a UI-layer composition of expands, not a core primitive.
Member
Author
|
Revised the derivation to expand-only: dropped |
cascade.md: DiGraph -> MultiDiGraph; the part-to-master walk enumerates all simple FK paths (not shortest_path) and defers the edge rule to diagram.md's R1. trace.md: recast the convergence table as the two directions of expand (cascade = down, trace = up), drop the restrict/AND row, and defer the rules to diagram.md (R1 + R2). Fixes statements that were stale after the MultiDiGraph migration and the expand-only model.
De-duplicate cascade.md's F1-3/U1-3 per-rule enumerations against the unified edge rule R1 in diagram.md#traversal-algebra, keeping the cascade-specific behavior on top. Rewrite worked examples to name R1's copy/rename/project cases instead of the old rule codes; drop the subset-symbol notation. Reframe part_integrity per the group rule R2: with cascade the master-part group is one item — master-first pulls in all parts, part-first lifts to the master then re-expands to all parts; ignore treats master and part as regular tables. Update trace.md's allowed-table-set contract so an ancestor master's whole part-group is in scope (R2 upstream), replacing the old non-descending note. Sweep residual unicode arrows to ASCII.
Downward expansion for delete cannot leave a restriction referencing a downstream table: reverse-topological delete empties it first, so the subquery matches zero rows and strands what should be deleted (#1496). State this as a general Delete-time materialization section — seed-referencing-descendant and the part-to-master master-key freeze are its instances — applied on every backend by delete-order reason, not as a MySQL-only (error 1093) concern. Preview/counts pays none of it.
dimitri-yatsenko
marked this pull request as draft
August 12, 2026 18:54
MilagrosMarin
approved these changes
Aug 14, 2026
MilagrosMarin
previously approved these changes
Aug 17, 2026
Replace the draft "Traversal algebra" section of diagram.md with the normative traversal specification: base principles P1-P4, the row model (foreign-key references, NULLs, parallel foreign keys, the owning reference A1), the operations D, U and U(D(S)) with the delete modes, trace and self.upstream, derived results T1-T8 with justifications, the computation (edge rule, group rule, one pass over the group quotient, groups that iterate, seeds, the plan, delete-time materialization, concurrency), the contract for expanded diagrams, and the retirement of restrict with migration recipes. Remove the restrict/AND-convergence text and the Rule 1-3 propagation section from Operational Methods, which now documents expand, cascade, trace, indexing, counts, prune, iteration and the deprecated restrict. Add traversal-shapes.md: one worked example per schema shape (S1-S7, G1-G6, K1-K6, X1-X2) with the exact rows expand returns.
…pand cascade.md now specifies the delete in terms of D and D1: what each part_integrity mode deletes, the row-level enforce check before any DELETE, the symbolic preview, the delete steps, delete-time materialization and concurrency. This removes the contradiction about whether preview pays for materialization: it does not. trace.md now defines a trace as U(S): upward crossings are always restrictions, a trace includes the parts of ancestor masters and their ancestors, and self.upstream is built from the parents of the row being computed. The published user-facing material is kept.
A part belongs to its master's group when a foreign key in its primary key references the master, or an owned part of the same master, with renames allowed. Its owning attributes, the ones that chain maps onto the master's key, define its master row and its instance. A part that is not owned is a plain table in traversal, ordering and the declaration check, and gets a warning. - R2's lift renames the owning attributes onto the master's key. - A group iterates exactly when a foreign key inside it does not map owning attributes onto owning attributes; the one-pass condition is stated the same way. - R1 defines the attributes a condition names (every dict key, the whole heading of a query expression) and notes that 2.3 copies the compiled predicate. - Relabeling materialized keys downward gets an exact condition, with two counterexamples to the looser wording. - T7, T8 and X2 follow the ownership rule of the declaration check. - The catalog's G6 is now a part with a secondary reference to its master, and G7 adds the owned part-of-part chain with renamed attributes. - cascade.md and trace.md follow, state the enforce preview, and relate delete-time materialization to the subquery-seed fix.
dimitri-yatsenko
marked this pull request as ready for review
October 17, 2026 22:48
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR replaces the draft "Traversal algebra" in
reference/specs/diagram.mdwith a normative specification ofDiagram.expand, the one traversal behindcascadeandtrace, and adds a catalog of worked examples that the test suite will mirror one to one.What the spec defines
The new Traversal specification section of
diagram.md:ref_e, with SQL's MATCH SIMPLE rule for NULLs (A2); parallel foreign keys as separate references; owned parts (A1): a part is owned when a foreign key in its primary key references the master or an owned part of the same master, renames allowed, and its owning attributes (what that chain maps onto the master's key) define its master row and its instance; the row relationsdown = child ∪ instandup = parent ∪ inst. A part that is not owned is a plain table, with a warning at declaration.D(S)andU(S)as closures underdownandup;expand(seed, "both") = U(D(S)), and why neitherD(U(S))norD(S) ∪ U(S)works for export; the threepart_integritymodes in terms ofDandD1;trace = U;self.upstreamfor a row that does not exist yet.DandU, delete safety, the read boundary, the properties ofboth, theenforceequivalenceD1 lift-closed ⇔ D1 = D, andtopo_sortsucceeding exactly when the group quotient is acyclic.d[T],counts(), iteration, copying,+, the operators that raise, andprune().restrict(deprecated in 2.4, removed in 3.0), with migration recipes.New and changed 2.4 behavior is marked with
version-added/version-changed/version-deprecatedadmonitions.What it removes
restrictas an operation of the model, AND-convergence, and the "Rule 1–3" propagation section in Operational Methods.cascade.md: the multi-pass loop, the part-to-master path walk, the old worked examples built on that walk, and the internal function names. It also removes the contradiction about cost: the preview never materializes keys; materialization happens only at delete time.Shape catalog
New page
reference/specs/traversal-shapes.md(added to the nav under Query Algebra, after Diagram). One subsection per shape, each with a tiny schema, a few rows, seeds, and the exact rowsexpandreturns down, up, and both where that is informative:NOT INNULL trap,dj.Top, empty, unrestricted);Every result was checked against a small executable model of
DandUwritten from the definitions, and independently against the reference model in datajoint/datajoint-python#1621, whose shape tests copy each row from this catalog by hand.cascade.mdandtrace.mdcascade.mdis now thedowncase: what eachpart_integritymode deletes, the row-levelenforcecheck before any DELETE, the symbolic preview, the delete steps, delete-time materialization, concurrency, and a worked example of a seed restricted by a descendant.trace.mdis now theupcase: upward crossings are always restrictions, a trace includes the parts of ancestor masters and their ancestors (Diagram.trace: descend through master-part boundaries (Merge-table upstream tracing) datajoint-python#1481), andself.upstreamis built from the parents of the row being computed. The published user-facing material is kept.What reviewers should check
down,up,D,U, andD1say what a delete, a trace, and an export should select? Do the ownership rule (A1) and its owning attributes match the declaration check and ordering in fix(declare): refuse a part reference that closes a master-part group cycle datajoint-python#1620?self.upstreaminclude the parts of ancestor masters and their ancestors;enforceis checked per part row before deleting, which is stricter;+on expanded diagrams takes the union of their rows, and the other operators raise;restrictis deprecated, andexpandresults are larger thanrestrictresults because they take unions;&) throughout. Terms that are not DataJoint canon (closure, least fixed point, group quotient, lift-closed) are defined where first used.Decisions for reviewers to confirm
The
enforcepreview.Diagram.cascade(seed)withenforceholdsD1(S)and does not raise;delete()runs the check and raises before deleting anything.+on expanded diagrams.d1 + d2takes the union of rows for two expanded diagrams of the same direction. It raises for different directions, for an expanded diagram and one that is not expanded, and for twocascade()diagrams with differentpart_integritymodes.Owning attributes when a part has several owning chains. They come from an unrenamed
-> masterif the part has one, and otherwise from the first owning reference in its definition.Groups that iterate. A group iterates exactly when some foreign key inside it does not map the referencing part's owning attributes onto the referenced table's owning attributes. This covers both a renamed sibling reference (G4) and a part's second, renamed reference to its own master (G3).
When a condition may be copied downward. The attributes a condition names are every key of a dict and the whole heading of a query expression. With that reading, a dict with keys the parent lacks, or a query expression that shares more attributes with the child than with the parent, is never copied.
Relabeling materialized keys downward. Every key names, among the parent's attributes, only attributes the foreign key carries; if the foreign key has a nullable column, every key names all of its attributes, none of them NULL.
The warning for a dict seed that names no attribute (K6).
restrict()discards a dict that names none of the table's attributes before the seed reaches the traversal, so the planner (feat(diagram): Diagram.expand on a traversal plan datajoint-python#1637) cannot see it. Raising the warning needsrestrict()to record that it discarded the dict. Should the spec keep the warning, or drop it?Pages that use the old wording elsewhere (the delete how-to, the master-part spec §4,
autopopulate.md§4.4, what's new) are updated in a follow-up PR once the engine changes merge.