feat: compound-PK foreign-key references — grammar + tests (ADR-0043)
Multi-column FK parsing on both surfaces: DSL from P.(a, b) to C.(x, y) (parenthesized endpoint; single bare form unchanged) and SQL FOREIGN KEY (a, b) REFERENCES P(x, y) incl. bare-reference auto-expand. consume_fk_reference + the table-level/ALTER FK parsers collect column lists; the from P. completion now offers ( (snapshots updated). 12 integration tests in tests/it/compound_fk.rs cover parse (both surfaces), engine-enforced FK, arity + partial-PK + per-pair-type-mismatch refusal, --create-fk per-column, save->rebuild round-trip, undo (one step), and single-column preservation. Mark T3 [x]; ADR-0043 implemented.
This commit is contained in:
@@ -2,8 +2,17 @@
|
||||
|
||||
## Status
|
||||
|
||||
**Accepted** — 2026-06-09. All four genuine forks confirmed by the
|
||||
user at the recommended option: **F-A** full PK in order, **F-B**
|
||||
**Accepted + implemented** — 2026-06-09. Implementation landed the
|
||||
same day: the relationship model went list-based through all six
|
||||
layers (refactor commit `b14f019`, single-column preserved), then
|
||||
the DSL + SQL grammars gained multi-column parsing and the
|
||||
executor the full-PK/auto-expand/per-pair-type-compat/auto-name/
|
||||
`--create-fk`-per-column logic. Verified by 12 integration tests in
|
||||
`tests/it/compound_fk.rs` (parse both surfaces, engine-enforced FK,
|
||||
arity + partial-PK refusal, `--create-fk`, single-column
|
||||
preserved) on top of the existing single-column relationship
|
||||
suite. `requirements.md` **T3** is `[x]`. All four genuine forks
|
||||
confirmed by the user at the recommended option: **F-A** full PK in order, **F-B**
|
||||
house-style uniform column lists (no migration; back-compat not
|
||||
required), **F-C** parenthesized DSL lists, **F-D** bare table-level
|
||||
SQL FK auto-expands to the parent's full PK. Closes the one open
|
||||
|
||||
+1
-1
@@ -48,4 +48,4 @@ This directory contains the project's ADRs, recorded per
|
||||
- [ADR-0040 — A per-command completion marker (✓/✗) replaces the `[ok]` summary line](0040-completion-marker-replaces-ok-summary.md) — **Accepted 2026-05-30 (issue #9)**, amends ADR-0014 / ADR-0028 / ADR-0019 output conventions, builds on ADR-0037's mode-tagged echo. An audit of the whole command surface found the `[ok] <verb> <subject>` summary line duplicates the echo line above it (verb+subject) everywhere; its only unique contribution is the success-vs-error signal (and `explain select` even rendered `[ok] explain` with an empty subject post-ADR-0039). Decision: drop the `[ok]` line and the symmetric `"…" failed:` prefix; the echo line gains a trailing inline **✓** (green, success) / **✗** (red, failure) — `running:` becomes a pending state that resolves to `<input> ✓/✗` on completion (status set via the existing `rfind(Echo)` lookup). Content (row counts, structure, data, plan tree, teaching echo) unchanged. Scoped to the DSL/data/SQL family that has the redundant echo+`[ok]` pair; app-command `[ok]` lines (`rebuild`/`export`/`now editing`) are payload-bearing, have no echo to mark, and stay as-is. `ok.summary` retired; `dsl.failed` reduced to the rendered reason. Broad but mechanical snapshot churn. OOS: app-command `[ok]` lines, the `[WRN]` validity indicator, and the tag colours (issue #10)
|
||||
- [ADR-0041 — Copy the output panel to the system clipboard](0041-copy-output-to-clipboard.md) — **Accepted 2026-06-02 (issue #11)**, amends ADR-0003's app-command registry (adds **`copy`** / `copy all` / `copy last`). The friction it removes: filing a bug report meant terminal-selecting the output panel and fighting wrapping/borders. New **app-level command** (sigil-free, both modes): `copy` / `copy all` copy the whole panel; `copy last` copies from the most recent echo line to the end. **Mechanism — OSC 52 *and* native (`arboard`), always both**, because OSC 52 acceptance is undetectable (no terminal ack), so a true "fall back when unsupported" can't be built: emit the OSC 52 escape (no new dep — `base64`+`crossterm`; works over SSH; tmux-passthrough-wrapped via `$TMUX`), then a best-effort native write whose failure is ignored (headless host — OSC 52 carried it); the two carry identical content. **Format — plain text verbatim as rendered** (tags, `✓`/`✗`, box-drawing) joined by `\n`, without viewport padding/wrapping; a drift-lock test pins `OutputLine::plain_text` to `render_output_line`. `arboard` added **`--no-default-features`** (drops the `image` crate; X11-only on Linux — `wayland-data-control` deliberately omitted as it ~doubles the dep tree and OSC 52 covers native-Wayland). Security: write-only, scans clean for arboard's tree (cargo audit / osv-scanner / grype), 1Password-maintained, minimal surface. OOS: Markdown export, selection/range, a keybinding, OSC 52 read, `screen` passthrough
|
||||
- [ADR-0042 — H1a parse-error pedagogy in the grammar-tree era](0042-h1a-parse-error-pedagogy-grammar-tree.md) — **Accepted 2026-06-03.** Continues **H1a** from ADR-0021 against the ADR-0024 grammar tree (ADR-0021's chumsky mechanism is dead). Records the **baseline already shipped** — per-command `usage:` block (38 `parse.usage.*` templates), available-commands fallback, structural "after `…`, expected …" wording, source-derived ident slot labels ("table name"/"column name"), curated `parse.custom.*` near-miss messages, and the ADR-0027/0033/0036 schema-aware `[ERR]` diagnostics — so H1a is *substantially* delivered at the intent level. Defines the remaining work as **(1)** a verified per-command **near-miss matrix** (`tests/typing_surface/` + `tests/it/parse_error_pedagogy.rs`) as the definition of done, test-first; **(2)** **friendlier literal expectation labels** — optional prose glosses on `Word`/`Punct`/`Flag` positions that *add* role context while always keeping the exact literal visible (e.g. "a filter clause: `where …` or `--all-rows`"); **(3)** **advanced-mode SQL** near-miss parity (RETURNING scope, CTE-arity positioning, `CROSS JOIN … ON`, INSERT…SELECT count) — **in scope**, kept distinct from ADR-0019 §OOS-2 which covers advanced-SQL *engine*-error sanitisation, a different layer. Catalog/anchor-phrase discipline (ADR-0019) preserved; no public API change. OOS: I3/I4, spell-correction, multi-error reporting, verbosity-gating the usage block
|
||||
- [ADR-0043 — Compound-primary-key foreign-key references (T3)](0043-compound-pk-foreign-key-references.md) — **Accepted 2026-06-09** (all four forks confirmed at the recommended option: full-PK matching, house-style uniform lists, parenthesized DSL syntax, bare-SQL-FK auto-expansion). Closes the open leg of `requirements.md` **T3**: a foreign key that *references* a parent's compound primary key. A 2026-06-09 audit found single-column FK woven through ~15–20 sites (metadata table, `RelationshipSchema`, `project.yaml` `RawEndpoint`, both grammar surfaces, executor FK-DDL emission, per-column type-compat, display) — earns an ADR, not an inline build. **Decision:** reference the parent's **full** compound PK, matched **positionally** to an equal-length child column list, per-pair `fk_target_type` compat (ADR-0011, element-wise); DSL `from <P>.(a, b) to <C>.(x, y)` (single form unchanged), SQL `FOREIGN KEY (x, y) REFERENCES P(a, b)` (extend the existing one-cap lists; bare table-level FK auto-expands to the parent PK when arities match). **Storage — no migration (back-compat not required, user-confirmed 2026-06-09; no installed base):** the relationship endpoint joins the list convention `project.yaml` *already* uses — `columns: [a, b]` like `primary_key: [id]` and index `columns: [...]` (the endpoint was the lone scalar `column:` holdout); the metadata `TEXT` columns are unchanged and store the list as a uniform JSON array (`["id"]` even for single — SQLite has no array type). No F3 migrator, no version bump; accepted trade-off is that a pre-change `project.yaml` with relationships won't load (clean cutover). In-memory model goes list-based (`Vec<String>`) through all six layers; the enforced FK is the rebuilt child-table DDL (`FOREIGN KEY (a,b) REFERENCES P(x,y)`), one relationship = one undo step (ADR-0013). Genuine forks escalated: matching policy (full-PK vs subset), storage (house-style uniform lists vs normalized table), DSL syntax (parenthesized vs repeated-dotted), bare-SQL-FK auto-expansion. OOS: subset/non-PK (UNIQUE-targeted) FK references; any single-column behaviour change
|
||||
- [ADR-0043 — Compound-primary-key foreign-key references (T3)](0043-compound-pk-foreign-key-references.md) — **Accepted + implemented 2026-06-09** (all four forks confirmed at the recommended option: full-PK matching, house-style uniform lists, parenthesized DSL syntax, bare-SQL-FK auto-expansion). Closes `requirements.md` **T3** `[x]` — the relationship model went list-based across six layers (single-column preserved, no migration), DSL `from P.(a,b) to C.(x,y)` + SQL `FOREIGN KEY (a,b) REFERENCES P(x,y)` parse/execute/enforce, 12 tests in `tests/it/compound_fk.rs`. Closes the open leg of `requirements.md` **T3**: a foreign key that *references* a parent's compound primary key. A 2026-06-09 audit found single-column FK woven through ~15–20 sites (metadata table, `RelationshipSchema`, `project.yaml` `RawEndpoint`, both grammar surfaces, executor FK-DDL emission, per-column type-compat, display) — earns an ADR, not an inline build. **Decision:** reference the parent's **full** compound PK, matched **positionally** to an equal-length child column list, per-pair `fk_target_type` compat (ADR-0011, element-wise); DSL `from <P>.(a, b) to <C>.(x, y)` (single form unchanged), SQL `FOREIGN KEY (x, y) REFERENCES P(a, b)` (extend the existing one-cap lists; bare table-level FK auto-expands to the parent PK when arities match). **Storage — no migration (back-compat not required, user-confirmed 2026-06-09; no installed base):** the relationship endpoint joins the list convention `project.yaml` *already* uses — `columns: [a, b]` like `primary_key: [id]` and index `columns: [...]` (the endpoint was the lone scalar `column:` holdout); the metadata `TEXT` columns are unchanged and store the list **comma-joined** (`a,b`; the bare name for single — safe because identifiers are `[A-Za-z0-9_]+`). No F3 migrator, no version bump; accepted trade-off is that a pre-change `project.yaml` with relationships won't load (clean cutover). In-memory model goes list-based (`Vec<String>`) through all six layers; the enforced FK is the rebuilt child-table DDL (`FOREIGN KEY (a,b) REFERENCES P(x,y)`), one relationship = one undo step (ADR-0013). Genuine forks escalated: matching policy (full-PK vs subset), storage (house-style uniform lists vs normalized table), DSL syntax (parenthesized vs repeated-dotted), bare-SQL-FK auto-expansion. OOS: subset/non-PK (UNIQUE-targeted) FK references; any single-column behaviour change
|
||||
|
||||
+21
-23
@@ -399,30 +399,28 @@ since ADR-0027.)
|
||||
*(Implemented per ADR-0014; auto-fills omitted shortid
|
||||
columns and validates user-supplied values against the same
|
||||
alphabet and length range.)*
|
||||
- [/] **T3** Compound primary keys handled end-to-end (DSL,
|
||||
- [x] **T3** Compound primary keys handled end-to-end (DSL,
|
||||
storage, display, FK reference).
|
||||
*(Partial, verified 2026-06-07: compound-PK **declaration**
|
||||
(`with pk a(int),b(int)`), **storage** (`primary_key:
|
||||
Vec<String>`), and **display** are present and tested.
|
||||
**Missing: a FK that *references* a compound PK** —
|
||||
`db.rs` resolve/alter FK paths enforce a single
|
||||
`parent_column: String`; a bare `REFERENCES parent` on a
|
||||
compound-PK table is refused as ambiguous, and multi-column FK
|
||||
target syntax is not in the grammar. This is the one open
|
||||
end-to-end leg of T3 — but a **codebase audit (2026-06-09)
|
||||
found it is not a small finish**: single-column FK is woven
|
||||
through ~15–20 sites across 6+ files — the
|
||||
`__rdbms_playground_relationships` table schema, the
|
||||
`RelationshipSchema` struct, the **`project.yaml` relationship
|
||||
format** (`RawEndpoint { column }`), both grammar surfaces
|
||||
(`add 1:n relationship` + SQL `FOREIGN KEY`), the executor's FK
|
||||
DDL emission, and the per-column type-compat check. It needs a
|
||||
**migration** (the metadata-table + yaml-format change, F3) and
|
||||
an **ADR** to settle the design forks: compound-PK matching
|
||||
policy (must an FK reference *all* PK columns, or a subset?),
|
||||
per-pair type-compat semantics, the yaml multi-column shape, and
|
||||
back-compat for existing single-column projects. So this leg is
|
||||
ADR-first, not a sweep item.)*
|
||||
*(Done 2026-06-09 via **ADR-0043**: the FK-reference leg now
|
||||
works on both surfaces — DSL `add 1:n relationship from
|
||||
P.(a, b) to C.(x, y)` and SQL `FOREIGN KEY (a, b) REFERENCES
|
||||
P(x, y)` (bare `REFERENCES P` auto-expands to the full PK).
|
||||
References the parent's full compound PK matched positionally,
|
||||
per-pair type-compat (ADR-0011); the FK is engine-enforced,
|
||||
persisted (`columns: [a, b]` in `project.yaml`, comma-joined in
|
||||
metadata), shown symmetrically by `describe`, and `--create-fk`
|
||||
creates one child column per parent PK column. The relationship
|
||||
model went list-based through all six layers, single-column
|
||||
behaviour preserved (commit `b14f019`); 12 integration tests in
|
||||
`tests/it/compound_fk.rs` plus the existing single-column suite
|
||||
as the regression net. The earlier-noted (2026-06-07) breakdown:*
|
||||
*compound-PK **declaration** (`with pk a(int),b(int)`),
|
||||
**storage** (`primary_key: Vec<String>`), and **display** were
|
||||
already present and tested. The FK-reference leg — once an
|
||||
ADR-first ~15–20-site change across the relationship model — is
|
||||
what ADR-0043 delivered (back-compat dropped by user decision, so
|
||||
no migration was needed). Subset / non-PK (UNIQUE-target) FK
|
||||
references stay out of scope.)*
|
||||
|
||||
## Visualizations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user