Foreign-key relationships, rebuild-table, polish round

DSL:
- add 1:n relationship [as <name>] from <P>.<col> to <C>.<col>
  [on delete <action>] [on update <action>] [--create-fk]
- drop relationship <name> | from <P>.<col> to <C>.<col>
- show table <name> for re-displaying a structure on demand

Database (ADR-0013):
- Rebuild-table primitive following SQLite's
  ALTER-via-rebuild recipe (foreign_keys=OFF outside tx,
  copy-by-name, foreign_key_check before commit). Reusable for
  B2 (column drops/renames/type changes).
- ReferentialAction enum (no action / restrict / set null /
  cascade); SET DEFAULT awaits column DEFAULTs.
- __rdbms_playground_relationships metadata table -- names,
  auto-generated as <Parent>_<pcol>_to_<Child>_<ccol>.
- Type::fk_target_type() validation at declaration; friendly
  errors for type mismatch, non-PK target, missing column,
  duplicate name.
- describe_table populates symmetric outbound + inbound
  relationship lists. drop_table refuses while inbound
  references exist; outbound metadata cleaned up alongside drop.

App / UI:
- In-line cursor editing in the input field: Left, Right,
  Home, End, Delete, Backspace honoring UTF-8 boundaries.
- PageUp / PageDown scrolls the output buffer; viewport row
  count fed back from the renderer via App::note_output_viewport
  so scroll is capped against the actual visible area
  (regression-tested) and snaps to the bottom on new output.
- Failure messages quote the command portion ("verb target"
  failed: ...) for visual clarity; RelationshipSelector has a
  proper Display impl so "no such relationship" reads cleanly.
- Structure rendering shows References / Referenced by sections.

Docs:
- ADR-0013 covers naming, metadata table, symmetric view, and
  the rebuild-table strategy.
- requirements.md updates: C3 (FK done), B2 (primitive in),
  T3 (compound-PK FK still pending). New entries: I1a (cursor
  editing -- landed), I1b (Ctrl-A/E and readline shortcuts --
  pending), V4 partial scroll, V5 (show family), C3a (modify
  relationship -- deferred).

Tests: 154 passing (140 lib + 14 integration), 0 skipped.
Clippy clean with nursery enabled.
This commit is contained in:
claude@clouddev1
2026-05-07 14:52:51 +00:00
parent c1e52920eb
commit 165068269b
12 changed files with 2632 additions and 56 deletions
+34 -2
View File
@@ -68,7 +68,7 @@ async fn run_loop(
seed_initial_tables(&database, &event_tx).await;
terminal
.draw(|f| ui::render(&app, &theme, f))
.draw(|f| ui::render(&mut app, &theme, f))
.context("initial draw")?;
info!("entering main event loop");
@@ -87,7 +87,7 @@ async fn run_loop(
}
}
terminal
.draw(|f| ui::render(&app, &theme, f))
.draw(|f| ui::render(&mut app, &theme, f))
.context("redraw")?;
if should_quit {
break;
@@ -170,6 +170,38 @@ async fn execute_command(
.await
.map(Some)
.map_err(friendly),
Command::AddRelationship {
name,
parent_table,
parent_column,
child_table,
child_column,
on_delete,
on_update,
create_fk,
} => database
.add_relationship(
name,
parent_table,
parent_column,
child_table,
child_column,
on_delete,
on_update,
create_fk,
)
.await
.map(Some)
.map_err(friendly),
Command::DropRelationship { selector } => database
.drop_relationship(selector)
.await
.map_err(friendly),
Command::ShowTable { name } => database
.describe_table(name)
.await
.map(Some)
.map_err(friendly),
}
}