Skip to content

Commit ec287ff

Browse files
docs(design): section 7 — group rule (R2) via the relabel fast-path
Derive how a materialized key crosses a master<->part boundary: R2 needs no new key machinery. The existential lift (part->master) is relabel-drop, with sibling part keys collapsing onto one master key by dedup; the atomic re-expansion (master->all parts) is a fresh downstream relabel from the recovered master key that widens by dropping the part-specific constraint. The round trip is lossy on the part-specific attribute, and that loss is compositional atomicity in key terms. Corollaries: materialized seeds stay materialized (delete-safe for free); one mechanism serves cascade/trace/restrict.
1 parent 8e966d2 commit ec287ff

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

DESIGN-expand-restrict.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,85 @@ yields legible per-table keys, and is delete-safe — this is the "update the ke
323323
names as we traverse" behavior. A live seed can be materialized up front to get
324324
all of that.
325325

326+
## 7. The group rule and the relabel fast-path — how a key crosses master↔part
327+
328+
Section 6 handled ordinary foreign keys. Part tables are the other complication.
329+
This section works out how a materialized key crosses a master↔part boundary,
330+
and shows that the group rule (R2) needs **no new key machinery** — it is the
331+
existing relabel fast-path, run twice, with the part-specific attribute
332+
deliberately lost in between.
333+
334+
**Setup.** Take a master `Session` with primary key `session_id`, and its part
335+
`Session.Trial` with primary key `(session_id, trial_id)`. A part always carries
336+
its master's full primary key inside its own — the `-> master` reference is a
337+
*primary, non-renamed* foreign key, so on that edge the relabel is the identity
338+
(names are shared). The part adds its own key attribute (`trial_id`) on top. R2:
339+
a master and its parts are one entity — a restriction touching any part lifts to
340+
the master, and the master brings in *all* its parts.
341+
342+
### master to part (downstream): nothing new
343+
344+
`Session & {'session_id': 5}` crosses down to `Session.Trial` by the ordinary
345+
downstream relabel: keep `session_id`, leave `trial_id` unconstrained. The result
346+
is the partial key `{'session_id': 5}`, which selects *every* trial of session 5.
347+
"All parts follow the master" falls straight out of the down rule.
348+
349+
### part to master (upstream): the existential lift is relabel-drop
350+
351+
`Session.Trial & {'session_id': 5, 'trial_id': 2}` going up: the master's full
352+
primary key (`session_id`) is present, so the fast-path fires — **relabel-drop**:
353+
keep `session_id`, drop the part-specific `trial_id` (absent on the master) →
354+
`{'session_id': 5}`.
355+
356+
The "existential" needs no extra mechanism: a *sequence* of part keys spanning
357+
several trials of session 5 all drop to the same `{'session_id': 5}`, and the
358+
master key-set **de-duplicates**. Dropping the part-specific attribute collapses
359+
sibling-part keys onto one master key — the OR-over-siblings is free.
360+
361+
### master to all parts (re-expansion): not a relabel of the seed
362+
363+
R2's atomicity requires that once the master is in, *all* its parts come —
364+
including trials the seed never named. That is not obtainable by relabelling the
365+
seed key; it is a **fresh downstream step from the recovered master key**:
366+
367+
`{'session_id': 5}` on `Session` → (down relabel) → `{'session_id': 5}` on
368+
`Session.Trial`, which drops the `trial_id` constraint and so *widens* from
369+
"trial 2" to all trials of session 5.
370+
371+
### The signature: the round trip is lossy, and the loss is atomicity
372+
373+
Follow `trial_id` around the loop:
374+
375+
1. seed part key `{'session_id': 5, 'trial_id': 2}`
376+
2. lift (up, drop) → master `{'session_id': 5}``trial_id` gone
377+
3. re-expand (down) → part `{'session_id': 5}``trial_id` cannot be restored
378+
379+
The part-specific constraint is destroyed by the lift and cannot be recovered on
380+
the way back down. Stated as a rule: **a key that reaches a part via its master
381+
carries no part-specific constraint.** That loss *is* compositional atomicity in
382+
key terms — the whole part-group comes along precisely because the returning key
383+
no longer distinguishes trial 2 from its siblings. The lift *narrows* the key (to
384+
the master); the re-expansion *widens* it (to all parts); the asymmetry is the
385+
point.
386+
387+
### Two corollaries
388+
389+
- **Materialized stays materialized.** Relabel-drop of literal values yields
390+
literal values, and the re-expansion is a relabel of that literal master key.
391+
So a materialized-key seed stays materialized through the entire
392+
part→master→parts round trip, and is therefore **delete-safe for free** — the
393+
delete-order materialization (#1496) only ever has to fire for *live*
394+
(subquery/string) seeds.
395+
- **One mechanism, three call sites.** The same round trip fires in cascade-down
396+
(`part_integrity="cascade"`, #1429), in trace-up (#1481, a master drags in its
397+
parts), and in `restrict` (#1501, where a part *predicate* is promoted to its
398+
master and the whole group is kept — not carved part-by-part). R2 is
399+
direction-agnostic; only what *triggers* it differs.
400+
401+
So R2 adds no new key machinery: relabel-drop (lift) plus a downstream relabel
402+
(re-expand) over the existing fast-path. The only non-relabel act is recognizing
403+
that the re-expansion is new growth from the master, not a transform of the seed.
404+
326405
## Summary
327406

328407
| | additive (grow) | subtractive (carve) |

0 commit comments

Comments
 (0)