From b688592b4c066afbea030593c095e2b6562f0da0 Mon Sep 17 00:00:00 2001 From: "claude@clouddev1" Date: Tue, 9 Jun 2026 17:11:01 +0000 Subject: [PATCH] docs: ADR-0043 implementation-readiness notes from /runda DA pass DA pass found three change sites the first sketch missed (teaching-echo renderers, --create-fk per-column creation, the auto-name generator) and made explicit the rules the forks left implicit: SQLite FK precondition (compound PK provides the unique index), explicit parent cols must be the PK set (any order, positional), arity/empty/inline-rejection wording, single-in-parens accepted, --create-fk per-column typed to fk_target_type. Expanded the test plan to cover enforcement, auto-expand, undo, round-trip. Fixed a stale 'legacy yaml loads' test line (no back-compat). --- ...0043-compound-pk-foreign-key-references.md | 81 +++++++++++++++++-- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/docs/adr/0043-compound-pk-foreign-key-references.md b/docs/adr/0043-compound-pk-foreign-key-references.md index a7c01cc..b996a19 100644 --- a/docs/adr/0043-compound-pk-foreign-key-references.md +++ b/docs/adr/0043-compound-pk-foreign-key-references.md @@ -221,14 +221,79 @@ Grouped; each lands behind tests. No migration step. column lists; `schema_to_ddl` emits multi-column `FOREIGN KEY (…) REFERENCES P(…)`; `check_fk_type_compat` loops pairs; bare-reference paths auto-expand to the full PK (D4) or refuse - with the improved message (`db.rs`). -6. **Display** — `RelationshipEnd` → column lists; `describe` / - echo render `(a, b) → (x, y)` (`db.rs`, `echo.rs`). -7. **Tests** — parse (DSL + SQL, single still works, multi parses, - arity mismatch errors); worker round-trip (declare a 2-col FK, - rebuild, FK enforced, type-mismatch refused); persistence - round-trip (yaml `columns:` reads + writes; a legacy - single-column yaml still loads); display. + with the improved message; the default relationship-name + generator (`db.rs:6850`) joins the column lists; `--create-fk` + creates one child column per parent PK column (`db.rs`). +6. **Display** — `RelationshipEnd` → column lists; `describe` + renders `(a, b) → (x, y)` symmetrically (outbound + inbound, + ADR-0013) (`db.rs`, `output_render.rs`). +7. **Teaching echo (ADR-0038)** — `render_add_relationship` and + `render_add_relationship_create_fk` (`echo.rs`) go multi-column: + the FK line emits `FOREIGN KEY (a, b) REFERENCES P (x, y)`, and + `--create-fk` emits **one `ADD COLUMN` line per newly-created + child column** (each typed to the matching parent PK column's + `fk_target_type`) before the FK line. Copy-paste contract + (ADR-0038) holds: every echoed line is runnable advanced SQL. +8. **Tests** — parse (DSL + SQL: single-col still works; multi + parses; arity mismatch errors; empty `()` rejected; inline + `col REFERENCES P(a,b)` rejected with the table-level pointer); + worker round-trip (declare a 2-col FK, rebuild, the FK is + **enforced** — an insert violating it is refused; per-pair + type-mismatch refused; bare-FK **auto-expand** to the parent PK; + `--create-fk` creates both child columns); persistence + round-trip (a single-col relationship writes `columns: [id]` and + reads back; a 2-col writes `columns: [a, b]` and reads back; + full save→rebuild reconstructs the FK); **undo** (add a 2-col + relationship, undo, it is gone — one step); display + (`describe` shows `(a, b) → (x, y)` both directions). + +## Implementation-readiness notes (DA pass, 2026-06-09) + +Verified against the code before build; folded in so the plan is +complete. + +- **SQLite precondition holds.** A FK's parent columns must be a + PK or a UNIQUE-indexed set. A SQLite `PRIMARY KEY (a, b)` creates + the requisite unique index, so `FOREIGN KEY (x, y) REFERENCES + P(a, b)` is valid against a compound PK with no extra index. + STRICT tables do not change FK rules. (F-A's "full PK" therefore + always targets a valid key; a subset would not be unique — the + reason F-A excludes it.) +- **Explicit parent columns must be exactly the PK set.** Under + F-A, `REFERENCES P()` is accepted iff `` is the + parent's PK column **set**; any ordering is accepted and maps + positionally to the child list (SQLite matches the set to the + unique index; the child↔parent pairing is positional). A + non-PK, partial, or super-set list is refused with a friendly + message naming the parent's actual PK (subset/UNIQUE targets are + OOS). +- **Arity + emptiness.** Child and parent lists must be equal, + non-zero length; a mismatch reports both counts + ("N child column(s) but M in `P`'s key"). An empty `()` list is + a parse error. Inline single-column `col REFERENCES P(a, b)` is + refused (one inline column can't satisfy a 2-column key) with a + pointer to the table-level `FOREIGN KEY (…)` form (D4). +- **DSL `from P.(a)` (single in parens)** is accepted — equivalent + to bare `from P.a` — so the parenthesized form is uniform across + arities; the bare form stays the idiomatic single-column + spelling. +- **`--create-fk` is per-column.** When child columns are missing, + one is created per parent PK column, each typed to that parent + column's `fk_target_type` (ADR-0011) — generalising today's + single-column behaviour; the echo mirrors this (sketch step 7). +- **Metadata identity unchanged.** `PRIMARY KEY (child_table, + child_column)` still holds with the JSON-array string as the + key — so a child column **set** still participates in at most one + relationship (pre-existing behaviour, now per-set). Distinct + sets on the same child table are distinct keys. +- **Auto-name generation** (`db.rs:6850`, the `[as ]`-less + default) is single-column today + (`{parent_table}_{parent_column}_to_{child_table}_{child_column}`) + — it must join the column lists (e.g. + `Orders_a_b_to_Customers_x_y`). A found change site the first + sketch missed; added to the executor step. +- **Undo / batch unchanged.** One `add 1:n relationship` is one + rebuild = one undo step (ADR-0013/0006), independent of arity. ## Consequences