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:
+77
-3
@@ -11,6 +11,7 @@
|
||||
//! primary key`, junction-table convenience commands) emit into
|
||||
//! the same shape.
|
||||
|
||||
use crate::dsl::action::ReferentialAction;
|
||||
use crate::dsl::types::Type;
|
||||
|
||||
/// A column at table-creation time: a name and a user-facing
|
||||
@@ -42,6 +43,64 @@ pub enum Command {
|
||||
column: String,
|
||||
ty: Type,
|
||||
},
|
||||
/// Establish a 1:n relationship: parent_table.parent_column
|
||||
/// is the primary-key side; child_table.child_column is the
|
||||
/// foreign-key side. `name` is optional — when `None`, the
|
||||
/// executor auto-generates one (`<Child>_<column>_to_<Parent>`).
|
||||
/// `create_fk` requests the child column be created
|
||||
/// automatically with the appropriate type if it is missing.
|
||||
AddRelationship {
|
||||
name: Option<String>,
|
||||
parent_table: String,
|
||||
parent_column: String,
|
||||
child_table: String,
|
||||
child_column: String,
|
||||
on_delete: ReferentialAction,
|
||||
on_update: ReferentialAction,
|
||||
create_fk: bool,
|
||||
},
|
||||
/// Drop a relationship by either user-given/auto-generated
|
||||
/// name, or by positional reference to the FK endpoints.
|
||||
DropRelationship {
|
||||
selector: RelationshipSelector,
|
||||
},
|
||||
/// Re-display a table's structure in the output. Doesn't
|
||||
/// change schema; useful when the user wants to look at a
|
||||
/// table they aren't currently DDL'ing on.
|
||||
ShowTable {
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// How a `drop relationship` command identifies the relationship
|
||||
/// to remove. Both forms are accepted; the executor resolves to
|
||||
/// a single row in the metadata table.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RelationshipSelector {
|
||||
Named { name: String },
|
||||
Endpoints {
|
||||
parent_table: String,
|
||||
parent_column: String,
|
||||
child_table: String,
|
||||
child_column: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RelationshipSelector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Named { name } => write!(f, "{name}"),
|
||||
Self::Endpoints {
|
||||
parent_table,
|
||||
parent_column,
|
||||
child_table,
|
||||
child_column,
|
||||
} => write!(
|
||||
f,
|
||||
"from {parent_table}.{parent_column} to {child_table}.{child_column}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@@ -52,16 +111,31 @@ impl Command {
|
||||
Self::CreateTable { .. } => "create table",
|
||||
Self::DropTable { .. } => "drop table",
|
||||
Self::AddColumn { .. } => "add column",
|
||||
Self::AddRelationship { .. } => "add relationship",
|
||||
Self::DropRelationship { .. } => "drop relationship",
|
||||
Self::ShowTable { .. } => "show table",
|
||||
}
|
||||
}
|
||||
|
||||
/// The table this command targets — every Command in this
|
||||
/// iteration operates on exactly one table.
|
||||
/// The table whose structure most directly reflects the
|
||||
/// outcome of this command. For relationships this is the
|
||||
/// child table, since the FK constraint physically belongs
|
||||
/// there and our describe view shows both sides anyway.
|
||||
#[must_use]
|
||||
pub fn target_table(&self) -> &str {
|
||||
match self {
|
||||
Self::CreateTable { name, .. } | Self::DropTable { name } => name,
|
||||
Self::CreateTable { name, .. }
|
||||
| Self::DropTable { name }
|
||||
| Self::ShowTable { name } => name,
|
||||
Self::AddColumn { table, .. } => table,
|
||||
Self::AddRelationship { child_table, .. } => child_table,
|
||||
Self::DropRelationship { selector } => match selector {
|
||||
RelationshipSelector::Endpoints { child_table, .. } => child_table,
|
||||
// For a named drop we don't know the child table
|
||||
// until the executor resolves it; the verb is
|
||||
// still a sensible fallback for logging.
|
||||
RelationshipSelector::Named { name } => name,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user