[DMS-1129] Design SQL Server foreign-key pruning for identity-update cascades#1088
[DMS-1129] Design SQL Server foreign-key pruning for identity-update cascades#1088samuellugo-ship-it wants to merge 14 commits into
Conversation
2c9a252 to
fee06af
Compare
|
Rebased onto main (picked up #1086). |
|
Addressed the review findings in 9c49e3c: rewrote |
|
Round-2 review findings addressed in a3358f9: added |
a3358f9 to
d954c76
Compare
|
Addressed the PASS-WITH-NITS finding: the |
|
Addressed the re-review findings in Two things not visible in the diff:
Also fixed: column order (identity-first, DocumentId-last), precise "origin" definition, global invariant for overlapping diamonds, pass-ordering/metadata home, softened PG cycle wording, abstract-FK qualifier, expanded NoPropagation/fail-fast diagnostics, historical side-doc subsections marked superseded, and swept the "surviving edge" shorthand to accurate wording repo-wide. |
…h, align stale PG subsection Docs-only (no code): - Overlapping diamonds: replace the "pick a local first-fit survivor per diamond, then fail if the global invariant breaks" framing in mssql-cascading.md with a real deterministic GLOBAL survivor-selection contract — a backtracking search over overlapping diamonds in stable order that accepts the first complete assignment whose retained NativeCascade graph satisfies the invariant (one mode per FK, acyclic, multitree, every NoPropagation edge covered by the FINAL graph), and fails fast only when NO global assignment satisfies it. This stops the design from falsely rejecting schemas that are representable under a different (backtracked) assignment. - key-unification.md: rewrite the stale "PostgreSQL (no special mitigation required)" subsection that still claimed PostgreSQL "supports cycles or multiple cascade paths". Now aligned with mssql-cascading.md: full-composite ON UPDATE CASCADE on every eligible edge, never physically pruned, derivation still fails on BOTH dialects for cycles/SCCs and uncovered diamonds as a cross-engine portability policy (not a PostgreSQL correctness need), and no claim about PostgreSQL runtime cycle behavior. Kept the key-unification mid-cascade / single-writable-column content. Also updated (not in diff): PR #1088 body bullet 6 (PG "cascades multiple paths and cycles natively" -> no-1785-restriction + both-dialect portability fail-fast, no runtime-cycle claim); Jira DMS-1128 supersession banner rewritten to drop TriggerFallback/MssqlFkShape/DocumentId-only/one-surviving-edge-per- referenced-table; DMS-1258 AC + tasks aligned to the deterministic global search. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the FAIL findings in
Off-diff:
Verified the remaining |
…ocs; column-order stragglers Docs-only (no code). Propagates the round-7 two-condition / global-search model into the remaining normative surfaces and fixes two stragglers: - Derived-model contract (mssql-cascading.md) + compiled-mapping-set.md: the fail-fast error contract now covers BOTH flavors of the second condition — a single uncovered diamond, AND globally infeasible overlapping diamonds (no global assignment satisfies the retained-NativeCascade invariant), with diagnostics for the overlapping case: involved diamonds, origins/roots, receivers, the shared edge/mode conflict, and the invariant clause that failed. - key-unification.md normative SQL Server rules: replaced "for each diamond, keep deterministic winner" (local first-fit) with covered candidate breaks feeding the deterministic GLOBAL survivor selection in mssql-cascading.md §5; fail-fast only when no complete assignment satisfies the invariant. Kept the key-unification point (covered pruning when paths share unified canonical cols). - mssql-cascading.md dialect-scope: "PostgreSQL alone would create and run such a graph" -> "has no 1785 DDL restriction and would accept it at create time" + explicit no-runtime-cycle-claim (no runtime behavior asserted for cycles). - data-model.md:974,981: column order "DocumentId + storage identity" -> identity storage columns first, DocumentId last (aligns with data-model.md:584/593). PR #1088 body fail-fast bullet updated to the two-condition framing (both flavors). No rejected behavior reintroduced: verified no TriggerFallback / DocumentId-only / MssqlFkShape / per-referenced-table / raw-in-degree-pruning outside removal/historical context; no PG runtime-cycle claims; no local-first-fit normative wording remains. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Open decision for the team — PostgreSQL fail-fast policy. The recurring PG-wording findings trace back to one unratified policy call: should DMS fail derivation on both dialects for a SQL-Server-unrepresentable graph (current design, a portability policy), or only on SQL Server with PostgreSQL recording a diagnostic? I've written up the context, both options with trade-offs, and a recommendation (Option A) as a decision record on DMS-1258 for team ratification: https://edfi.atlassian.net/browse/DMS-1258?focusedCommentId=94649 — the docs, DMS-1258 ACs, and METAED-1667 will be aligned to whichever the team picks. |
…agate overlapping-diamond fail flavor
Docs-only (no code). The team ratified the open PostgreSQL question with an
engine-specific policy — do NOT force parity between engines:
- Pruning, unsafe-graph detection, and fail-fast are now scoped to SQL Server
ONLY (they exist for SQL Server's 1785 DDL restriction). PostgreSQL is left
as-is: full-composite ON UPDATE CASCADE on every eligible edge, never pruned
or failed by DMS. Cross-engine safety for authors lives in the MetaEd
authoring guard (METAED-1667). Replaced the "fail derivation on both dialects
/ cross-engine portability policy" (Option A) framing everywhere it appeared:
mssql-cascading.md (decision 5, dialect-scope section, step 6, DMS-1258
alignment, intro), key-unification.md PG subsection, transactions:275.
- Removed the last PG runtime-cycle phrasings ("PostgreSQL alone could run it",
"create and run such a graph"); PG "accepts the DDL at create time" only, with
an explicit no-runtime-cycle-claim.
- Propagated the "globally infeasible overlapping diamonds" fail flavor into the
implementation-facing fail-fast shorthands (data-model, ddl-generation,
overview, summary, transactions, flattening-reconstitution, key-unification,
epics 07 + 09) so none hides the overlapping case; MetaEd follow-up +
METAED-1667 include it too.
- Column-order straggler fixed in key-unification-design-change-proposal.md
(identity storage columns first, DocumentId last).
Jira/PR (not in diff): METAED-1667 + DMS-1258 descriptions aligned to
engine-specific and the overlapping flavor; DMS-1258 decision comment 94649
marked resolved; DMS-1129 AC-bullet-2 decision recorded as a comment; PR #1088
body updated (engine-specific PG policy).
No rejected behavior reintroduced: verified no TriggerFallback / MssqlFkShape /
DocumentId-only / per-referenced-table / raw-in-degree as live; no both-dialect
overstatement; no PG runtime-cycle claims.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Latest review findings addressed in
Off-diff: METAED-1667 + DMS-1258 aligned to engine-specific + the overlapping flavor; the DMS-1258 decision record (comment 94649) is marked resolved; DMS-1129 AC-bullet-2 decision recorded; this PR body updated. Verified no regressions: no |
…cascades Spike deliverable (design + analysis; no production code). Replaces the SQL Server "all reference composite FKs use ON UPDATE NO ACTION + MssqlIdentity- PropagationTrigger" strategy with ODS-style foreign-key pruning, plus a coverage classification and fail-fast for the case ODS mis-prunes: - keep ON UPDATE CASCADE (full composite, identity columns restored) on one surviving live edge per referenced table; - prune redundant edges to ON UPDATE NO ACTION (full composite when covered by a surviving cascade, else DocumentId-only + MssqlIdentityPropagationTrigger); - fail derivation fast when a table has >= 2 uncovered live cascade paths. Physical FK-shape pruning is SQL Server only; PostgreSQL keeps full-composite ON UPDATE CASCADE. The unsafe-graph detection is dialect-agnostic. Add consolidated design note design-docs/mssql-cascading.md (the path DMS-1128 referenced but that was absent on main) and revise overview, strengths-risks, transactions-and-concurrency, key-unification, data-model, ddl-generation, summary, and epics/09-identity-concurrency/03-identity-propagation to point at it. Implementation is tracked by DMS-1258; MetaEd authoring guard by METAED-1667; DMS-1128 is superseded by DMS-1258. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrites the DMS-1129 cascade design to be internally consistent and
implementation-ready for DMS-1258:
- Model the cascade graph in propagation direction (referenced/parent ->
referrer/child); analyze SQL Server 1785 at the cascade receiver
(child reached by multiple cascade paths), not the referenced parent.
- Add explicit cycle/SCC fail-fast handling.
- Remove TriggerFallback: every SQL Server reference FK keeps the full
composite key; a pruned edge is covered (NO ACTION) or fail-fast. This
also removes the DocumentId-only shape that reintroduced the DMS-1002
stale-identity race and retires the identity-value propagation trigger.
- Use transitive identity mutability consistently for eligibility and
coverage classification.
- Reframe cross-dialect detection as a SQL Server portability/authoring
guard; PostgreSQL keeps full-composite ON UPDATE CASCADE (unchanged).
- Fix FK/RefKey column order to identity parts first, DocumentId last
(matches ReferenceConstraintPass and the change-queries *_RefKey
rationale); correct the concrete DDL examples in data-model.md.
- Specify the derived contract DMS-1258 must add (MssqlPropagationMode
{NativeCascade, NoPropagation}; drop MssqlFkShape); add a "Resolved
design decisions" section and a note that the DMS-1258 ticket wording
("per referenced table") must be corrected before implementation.
Sweeps stale trigger-only / DocumentId-first wording out of overview,
summary, strengths-risks, transactions-and-concurrency, key-unification,
data-model, ddl-generation, compiled-mapping-set, update-tracking, the
index/trigger-inventory and identity-propagation epics, and the two
presentations; adds superseded banners to the two key-unification
children/cross-scope analysis docs. Docs-only; no production code change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agrams Round-2 review fixes on the DMS-1129 design docs: - Derived contract now models genuinely immutable SQL Server NO ACTION FKs. Adds a third MssqlPropagationMode value, ImmutableNoAction, so NO ACTION no longer implies NoPropagation (it is either a pruned+covered edge or an immutable reference). Classification is total for SQL Server reference FKs; coverage/carrier diagnostics apply only to NoPropagation. Updated the outcome table, contract, migration, and DMS-1258-correction sections in mssql-cascading.md and the contract note in compiled-mapping-set.md. - Replaced remaining direct-mutability wording with transitive identity mutability (IsAbstract || TransitivelyAllowIdentityUpdates) in data-model.md and flattening-reconstitution.md (cascade-eligibility contexts only; direct-flag scenarios left intact). - Fixed local FK column order to identity-parts-first / DocumentId-last in ddl-generation.md and key-unification.md, with explicit positional-alignment notes; corrected a DocumentId-first FK example in key-unification-children-problem.md. - Replaced stale "SQL Server propagation triggers" wording in strengths-risks.md (native cascade on surviving edges; maintenance triggers unaffected) and the "multi-row propagation-trigger updates" note in summary.md. - Added a Diagrams section to mssql-cascading.md (Mermaid): object/relationship map, propagation-direction diamond, safe covered pruning, unsafe fail-fast, cycle/SCC fail-fast, and before/after RI improvement; cross-referenced from overview.md and summary.md. Docs-only; no production code change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hability Addresses the PR re-review blocker: the design over-approximated SQL Server error 1785 as raw cascade in-degree > 1. SQL Server 1785 forbids a table from appearing more than once in a single UPDATE/DELETE's cascade action tree — a diamond (two distinct paths from one origin) or a cycle — i.e. the legal graph is a multitree. Reworked mssql-cascading.md accordingly: - Detection is now per-origin duplicate reachability (shared cascade ancestry), not in-degree. Independent parents into one receiver are legal and are never pruned; in-degree > 1 is only a candidate signal. Pruning an independent live edge would reintroduce error 547, so it is explicitly disallowed. - Coverage is defined per originating tree/canonical columns; NoPropagation applies only to a covered reconverging edge of a diamond. Uncovered diamonds and cycles/SCCs fail fast. Diagnostics now carry origin/root, receiver, the distinct paths, candidate/pruned edges, and coverage columns. - Reworked the diagrams: added "legal independent parents" and "do not prune an independent edge (547)"; reframed the illegal-convergence, covered-pruning, and fail-fast diagrams around same-origin diamonds; kept cycle/SCC and before/after. Updated decisions, probe-scope note, algorithm steps, contract, ODS comparison, migration, and DMS-1258/MetaEd follow-ups. Swept the same conceptual drift out of the secondary docs (the "one surviving edge per receiver" phrasing implied a forest, not a multitree): overview, summary, data-model, transactions-and-concurrency, key-unification, ddl-generation, update-tracking, strengths-risks, compiled-mapping-set, flattening-reconstitution, the two key-unification analysis banners, the index/trigger-inventory and identity-propagation epics, and the presentation. Also: transitive-mutability wording in key-unification.md (was "baseline allowIdentityUpdates rule") and the relational-model presentation label (was "based on allowIdentityUpdates"). Docs-only; no production code change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed-receiver edges The MssqlPropagationMode.NativeCascade contract definitions in mssql-cascading.md and compiled-mapping-set.md described the mode only as the "surviving"/"sole" edge, which left legal independent multi-parent CASCADE edges ambiguous in a total classification. Restate both to enumerate all three cascade-eligible-and-not-pruned cases (independent edge, diamond survivor, sole edge), matching the algorithm and outcome table. Docs-only; no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ant/pass-ordering, doc+Jira sweeps Docs-only cleanup resolving the re-review findings (no code changes): - Column order: key-unification.md referenced-key UNIQUE rule now orders identity storage columns first, DocumentId last, positionally aligned with the local FK (was DocumentId-first, contradicting the canonical order). - PostgreSQL fail-fast policy: resolved the ambiguity/contradiction. Derivation now fails on BOTH dialects for a SQL-Server-unrepresentable graph as an explicit cross-engine portability/authoring policy (not a PostgreSQL correctness need); removed the stale "future decision to also fail on PostgreSQL" hedge. - "origin" defined precisely: any cascade-graph ancestor (transitive mutability), not only AllowIdentityUpdates roots — 1785 is a static DDL check over the whole graph and 547 trips on any referenced-key change. - Global invariant added for the retained NativeCascade graph (one mode per FK, acyclic, multitree, NoPropagation covered by the FINAL graph) so overlapping diamonds are validated globally, not per local snapshot. - Pass ordering + metadata storage pinned relative to TransitiveIdentityMutabilityPass and ReferenceConstraintPass.ResolveOnUpdate. - PostgreSQL cycle wording softened to "no 1785 DDL restriction" (no unproven cyclic-runtime claim) in mssql-cascading.md and transactions-and-concurrency.md. - Abstract identity-table FK text qualified with SQL Server pruning/fail-fast. - Historical side-doc subsections (children-problem, design-change-proposal) marked inline as superseded/non-actionable trigger work. - DMS-1258 "must be corrected" note retired to a past-tense "DMS-1258 alignment" section; stale correction framing removed. - compiled-mapping-set NoPropagation + fail-fast diagnostics expanded to match mssql-cascading.md; "prunes to the surviving edge" and the "on the surviving edge" shorthands swept to accurate "eligible edges" / diamond-scoped wording. Jira (edited via MCP): METAED-1667 rewritten to per-origin duplicate reachability (dropping "one surviving edge per referenced table"); DMS-1127 retargeted from the removed fallback/nested-trigger model to native-cascade update-tracking validation; DMS-1258 aligned to the both-dialect fail-fast policy and global invariant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h, align stale PG subsection Docs-only (no code): - Overlapping diamonds: replace the "pick a local first-fit survivor per diamond, then fail if the global invariant breaks" framing in mssql-cascading.md with a real deterministic GLOBAL survivor-selection contract — a backtracking search over overlapping diamonds in stable order that accepts the first complete assignment whose retained NativeCascade graph satisfies the invariant (one mode per FK, acyclic, multitree, every NoPropagation edge covered by the FINAL graph), and fails fast only when NO global assignment satisfies it. This stops the design from falsely rejecting schemas that are representable under a different (backtracked) assignment. - key-unification.md: rewrite the stale "PostgreSQL (no special mitigation required)" subsection that still claimed PostgreSQL "supports cycles or multiple cascade paths". Now aligned with mssql-cascading.md: full-composite ON UPDATE CASCADE on every eligible edge, never physically pruned, derivation still fails on BOTH dialects for cycles/SCCs and uncovered diamonds as a cross-engine portability policy (not a PostgreSQL correctness need), and no claim about PostgreSQL runtime cycle behavior. Kept the key-unification mid-cascade / single-writable-column content. Also updated (not in diff): PR #1088 body bullet 6 (PG "cascades multiple paths and cycles natively" -> no-1785-restriction + both-dialect portability fail-fast, no runtime-cycle claim); Jira DMS-1128 supersession banner rewritten to drop TriggerFallback/MssqlFkShape/DocumentId-only/one-surviving-edge-per- referenced-table; DMS-1258 AC + tasks aligned to the deterministic global search. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h rewrite Regression audit of the round-6 global survivor-search change found two self-contradictions in mssql-cascading.md (docs-only, no code): - Step 4 stated "Uncovered -> fail fast" as an absolute per-edge rule, which contradicted step 5's global backtracking (coverage is a property of the (edge, chosen survivor) pair; an uncovered edge makes that candidate break inadmissible and the search tries another survivor). Reworded step 4 so fail-fast is reached only when no admissible break exists under any survivor. - Step 6 said derivation fails in "exactly two situations" but the global-search rewrite implied a third (overlapping diamonds with no consistent global assignment). Reframed step 6 as TWO fundamental conditions — cycle/SCC, and "diamonds that cannot be jointly broken" — with the single-uncovered-diamond and overlapping-infeasible cases as two flavors of the second. This keeps the count consistent with compiled-mapping-set.md and DMS-1258 (both say two), avoiding a further propagation/churn round. Outcome table row updated to match. No oscillation found in the git history: every prior concept (1785 model, TriggerFallback removal, surviving-edge sweep, PG policy) moved in a single monotonic transition, not A->B->A. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ocs; column-order stragglers Docs-only (no code). Propagates the round-7 two-condition / global-search model into the remaining normative surfaces and fixes two stragglers: - Derived-model contract (mssql-cascading.md) + compiled-mapping-set.md: the fail-fast error contract now covers BOTH flavors of the second condition — a single uncovered diamond, AND globally infeasible overlapping diamonds (no global assignment satisfies the retained-NativeCascade invariant), with diagnostics for the overlapping case: involved diamonds, origins/roots, receivers, the shared edge/mode conflict, and the invariant clause that failed. - key-unification.md normative SQL Server rules: replaced "for each diamond, keep deterministic winner" (local first-fit) with covered candidate breaks feeding the deterministic GLOBAL survivor selection in mssql-cascading.md §5; fail-fast only when no complete assignment satisfies the invariant. Kept the key-unification point (covered pruning when paths share unified canonical cols). - mssql-cascading.md dialect-scope: "PostgreSQL alone would create and run such a graph" -> "has no 1785 DDL restriction and would accept it at create time" + explicit no-runtime-cycle-claim (no runtime behavior asserted for cycles). - data-model.md:974,981: column order "DocumentId + storage identity" -> identity storage columns first, DocumentId last (aligns with data-model.md:584/593). PR #1088 body fail-fast bullet updated to the two-condition framing (both flavors). No rejected behavior reintroduced: verified no TriggerFallback / DocumentId-only / MssqlFkShape / per-referenced-table / raw-in-degree-pruning outside removal/historical context; no PG runtime-cycle claims; no local-first-fit normative wording remains. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agate overlapping-diamond fail flavor
Docs-only (no code). The team ratified the open PostgreSQL question with an
engine-specific policy — do NOT force parity between engines:
- Pruning, unsafe-graph detection, and fail-fast are now scoped to SQL Server
ONLY (they exist for SQL Server's 1785 DDL restriction). PostgreSQL is left
as-is: full-composite ON UPDATE CASCADE on every eligible edge, never pruned
or failed by DMS. Cross-engine safety for authors lives in the MetaEd
authoring guard (METAED-1667). Replaced the "fail derivation on both dialects
/ cross-engine portability policy" (Option A) framing everywhere it appeared:
mssql-cascading.md (decision 5, dialect-scope section, step 6, DMS-1258
alignment, intro), key-unification.md PG subsection, transactions:275.
- Removed the last PG runtime-cycle phrasings ("PostgreSQL alone could run it",
"create and run such a graph"); PG "accepts the DDL at create time" only, with
an explicit no-runtime-cycle-claim.
- Propagated the "globally infeasible overlapping diamonds" fail flavor into the
implementation-facing fail-fast shorthands (data-model, ddl-generation,
overview, summary, transactions, flattening-reconstitution, key-unification,
epics 07 + 09) so none hides the overlapping case; MetaEd follow-up +
METAED-1667 include it too.
- Column-order straggler fixed in key-unification-design-change-proposal.md
(identity storage columns first, DocumentId last).
Jira/PR (not in diff): METAED-1667 + DMS-1258 descriptions aligned to
engine-specific and the overlapping flavor; DMS-1258 decision comment 94649
marked resolved; DMS-1129 AC-bullet-2 decision recorded as a comment; PR #1088
body updated (engine-specific PG policy).
No rejected behavior reintroduced: verified no TriggerFallback / MssqlFkShape /
DocumentId-only / per-referenced-table / raw-in-degree as live; no both-dialect
overstatement; no PG runtime-cycle claims.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing everywhere; engine-specific banner Docs-only (no code). Finishes propagating the settled fail-fast model into the last shorthand sites the prior rounds missed: - strengths-risks.md: "fail derivation fast on an uncovered diamond" -> full condition (cascade cycle/SCC/self-loop, or diamonds that cannot be jointly broken: single uncovered, or globally infeasible overlapping). - key-unification.md DDL checklist: same full-condition wording, tied to the retained-NativeCascade global invariant. - key-unification-children-problem.md banner: dropped "largely like PostgreSQL"; now says SQL-Server-only pruning/fail-fast, PostgreSQL never pruned/failed by DMS, MetaEd is the cross-engine authoring guard; includes overlapping diamonds. - key-unification-design-change-proposal.md banner + both presentation decks: same alignment (caught by the whole-tree validation sweep, not just the listed files, so they don't reintroduce stale narrowing). - relational-model presentation: abstract-identity-table node clarified to "PK DocumentId, identity cols, Discriminator last" (was a false-positive for the FK/RefKey-order sweep; it is the table PK layout, not FK order). Jira: DMS-1128 "Current authoritative design" updated to engine-specific (SQL-Server-only pruning/detection/fail-fast; PostgreSQL never pruned/failed; MetaEd cross-engine authoring guard; overlapping diamonds in the no-safe-pruning condition). Still "superseded, do not implement from this ticket". Validation sweep clean: no stale fail-fast narrowing; no both-dialect fail-fast; no PG runtime-cycle claims (only the explicit no-claim statement remains); no stale FK/RefKey order; no TriggerFallback/MssqlFkShape/DocumentId-only/ per-referenced-table/raw-in-degree as live behavior (remaining hits are historical/removal/correction); global-overlap wording present across 14 files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…to the full two-condition model Docs-only (no code). Two implementation-facing summaries still narrowed fail-fast to "a cascade cycle, or a diamond whose reconverging edge cannot be covered" / "hard derivation errors for cascade cycles and uncovered diamonds", omitting the globally-infeasible-overlapping-diamonds case and contradicting the contract (§ derived-model contract) and algorithm (§ 5-6): - "Design: pruning with a safety classification" intro (~386) - "Migration from the current code" bullet (~738) Both now read: a cascade cycle/SCC/self-loop, or diamonds that cannot be jointly broken (a single uncovered diamond, or globally infeasible overlapping diamonds where no global survivor assignment satisfies the retained-NativeCascade invariant). No retired concepts reintroduced; PostgreSQL remains engine-specific (never pruned or failed by DMS). Verification sweep clean: no narrowed fail-fast wording; forbidden-term hits are historical/removal/correction only; the sole PG-runtime match is the explicit no-claim statement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Docs-only. summary.md:32 said "SQL Server + PostgreSQL parity is required", which is too broad and could push DMS-1258 back toward both-dialect fail-fast/pruning. Reworded to the overview.md:25 meaning: both engines must be supported (design implementable on both), shared behavior where practical and explicit engine-specific behavior where they diverge — parity is NOT forced; SQL Server cascade pruning/detection/fail-fast are SQL-Server-only, PostgreSQL is left as-is (full-composite ON UPDATE CASCADE, never pruned or failed by DMS). Also softened the identical bare "PostgreSQL + SQL Server parity" bullet in extensions.md:47 (extension binding tables participate in the SQL-Server-only cascade pruning, so a blanket parity claim there is likewise incompatible). No cascade-policy change; no reintroduction of both-dialect fail-fast, PostgreSQL pruning/fail-fast, TriggerFallback, MssqlFkShape, DocumentId-only, per-referenced-table or raw-in-degree pruning, or PG runtime-cycle claims. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Docs-only. overview.md principle #5 label was still the broad "SQL Server + PostgreSQL parity" shorthand that was removed from summary.md/extensions.md. Renamed to "Cross-engine support" and clarified the body: implementable on both PostgreSQL and SQL Server, shared behavior where practical, explicit engine-specific behavior where they diverge. Target-platform sub-bullet unchanged. No cascade-policy change; no reintroduction of forced parity, both-dialect fail-fast, PostgreSQL pruning/fail-fast, or any retired concept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0e2afa9 to
cb0f496
Compare
|
Rebased onto latest main (docs-only, no conflicts). |
Summary
Spike DMS-1129 — Design foreign key pruning. Design/analysis only; no production code. This PR is documentation: it replaces the SQL Server "every reference composite FK uses
ON UPDATE NO ACTION+MssqlIdentityPropagationTrigger" strategy with ODS-style foreign-key pruning, and fixes the flaw ODS has (silently pruning when no edge is safe to prune).Design note:
reference/design/backend-redesign/design-docs/mssql-cascading.md(includes diagrams of the objects, propagation direction, and each pruning/fail-fast decision point).The approach
UPDATE/DELETE's cascade action tree (the legal graph is a multitree): the conflict is a diamond (a receiver reached by two distinct paths from one origin) or a cycle — not raw cascade in-degree. Direct in-degree > 1 is only a candidate signal: independent parents into one receiver are legal and are never pruned (pruning an independent live edge would only reintroduce error 547). Detection is per-origin duplicate reachability (shared cascade ancestry), not per referenced parent.IsAbstract || TransitivelyAllowIdentityUpdates), matchingTransitiveIdentityMutabilityPass/ReferenceConstraintPass.MssqlPropagationMode):NativeCascade— an eligible edge (an independent edge, the surviving path of a diamond, or the sole edge into a receiver);ON UPDATE CASCADE.NoPropagation— a reconverging edge of a diamond that is pruned but covered by the surviving path for the same origin and canonical columns;ON UPDATE NO ACTION.ImmutableNoAction— a reference to a genuinely immutable target (not in the cascade graph);ON UPDATE NO ACTION. (SoNO ACTIONno longer implies "pruned/covered"; the mode is carried explicitly and is not derivable from the action alone.)TriggerFallbackand noDocumentId-only FK shape. The retiredMssqlIdentityPropagationTriggeris gone for identity-value propagation; nativeON UPDATE CASCADE(which follows FKs on child/_extbinding tables) replaces it.NativeCascadeinvariant because overlapping diamonds share edges). Diagnostics name the origin/root(s), receiver(s), distinct paths, candidate/pruned edges, and coverage columns — plus, for the overlapping case, the shared edge/mode conflict and the invariant clause that failed. No permissive fallback remains, so the MetaEd authoring guard (METAED-1667) is load-bearing and the DMS fail-fast is the backstop.ON UPDATE CASCADEon every eligible edge and is left as-is: never pruned or failed by DMS (it has no SQL Server 1785 DDL restriction; no claim is made about PostgreSQL runtime behavior for a cascade cycle). Cross-engine safety for authors is handled at authoring time by MetaEd (METAED-1667), which flags a cascade graph SQL Server cannot represent — rather than forcing PostgreSQL derivation to reject graphs it can represent.*_RefKeycolumn order is identity storage columns first,DocumentIdlast (matchesReferenceConstraintPassand the change-queries/deletesseek-path rationale; local and target lists are positionally aligned). No column-order code change is required.MssqlPropagationMode(three values above), coverage/carrier diagnostics forNoPropagationedges only, and hard fail-fast derivation errors. TheMssqlFkShapeaxis is dropped (every FK is full composite).Empirical validation (SQL Server 2022)
A throwaway container and a real populated ODS database (
EdFi_Ods_Populated_Template, all writes rolled back) confirmed the behaviors the design rests on:Msg 1785— a table reached by multiple cascade paths (or a cascade cycle) rejected at DDL time; pruning one edge into the receiver toNO ACTIONmakes the graph legal.Msg 547— aNO ACTIONcomposite FK that includes identity columns blocks a parent identity update, and an AFTER trigger cannot rescue it (the FK check precedes the trigger). This is why pruned edges must be covered, and why aDocumentId-only + trigger fallback cannot preserve value-level RI — this design removes that fallback.Msg 2113— noINSTEAD OF UPDATEon a table with a cascading FK (forecloses the "reorder children-first" alternative).ON UPDATE CASCADEcomposite FKs propagate identity changes natively; redundant pruning is safe when a key-unified column is covered by a surviving cascade.edfi.Sessionrename cascaded to hundreds of rows in ~1.2 s (identity-update fan-out is the real operational cost).Changes
mssql-cascading.md— consolidated design note with a "Resolved design decisions" section and a Mermaid Diagrams section (object map, propagation-direction diamond, safe covered pruning, unsafe fail-fast, cycle/SCC fail-fast, before/after RI improvement).DocumentId-first claims:overview,strengths-risks,transactions-and-concurrency,key-unification,data-model,ddl-generation,summary,compiled-mapping-set,update-tracking,flattening-reconstitution, the01-relational-model/07-index-and-trigger-inventoryand09-identity-concurrency/03-identity-propagationepic docs, and the two presentations. Added "superseded" banners tokey-unification-children-problemandkey-unification-design-change-proposal(their non-root-referrer concern is solved natively by cascade).Follow-ups
TriggerFallbackframing to the receiver / propagation-direction / three-mode model.allow primary key updateswhen no safe pruning strategy exists (now load-bearing, since there is no permissive fallback).Acceptance criteria
allow primary key updateswith no safe pruning → METAED-1667.🤖 Generated with Claude Code