INSERT/UPDATE/DELETE + value model + auto-show, with polish
DSL data operations (ADR-0014): - insert into T [(cols)] values (vals); short form insert into T (vals) omits values keyword for friendlier syntax. - update T set ... where col=val | --all-rows; delete from T where col=val | --all-rows; show data T. - Value AST (Number/Text/Bool/Null) with per-column-type validation in the executor: int/real/decimal/bool/date/ datetime/shortid each accept a documented literal shape and produce friendly format errors naming the column. - INSERT short form fills non-auto-generated columns in schema order; auto-fills serial via SQLite and shortid via the new generator (T2). - `add column [to table] T: c (type)` -- `to table` now optional. Database: - insert/update/delete via prepared statements with bound rusqlite::types::Value parameters. - InsertResult/UpdateResult/DeleteResult: writes return rows_affected plus the affected row(s) only (not the whole table), so users see exactly what changed. - INSERT shows the just-inserted row via last_insert_rowid. - UPDATE captures matching rowids up-front and fetches them post-update -- works even if the UPDATE changed the WHERE column. - DELETE reports per-relationship cascade effects by row- count diffing inbound child tables; UPDATE-side cascades are not yet detected (would need value diffing). - query_data formats cells (booleans true/false, NULLs as None). FK error enrichment: - Now lists both outbound (INSERT/UPDATE relevance) and inbound (DELETE/UPDATE on parent relevance) FKs from the metadata, so RESTRICT errors point at the children blocking the delete. - RelationshipSelector has a proper Display impl -- "no such relationship" reads cleanly. Relationship display: - target_table for AddRelationship/DropRelationship now returns the parent (1-side); structure rendering after add/drop shows that side's "Referenced by:" entry, matching the `from <Parent>` direction of the command. - [ok] summary uses display_subject so relationship commands show both endpoints (`from P.col to C.col`) rather than a single misleading table name. - Auto-name format `<Parent>_<pcol>_to_<Child>_<ccol>` (matches the from..to direction). Output rendering and scrolling: - Wrap-aware scroll: renderer reports both visible-row count and total wrapped-row count to App; scroll math caps against actual displayable rows. Long lines wrap; the bottom line is always reachable; PageUp/PageDown work correctly even after paging past the buffer top. - Multi-line messages (FK error enrichment, cascade summary) split into single-line OutputLines at creation time so wrap/scroll math agree. Runtime / events: - New AppEvent variants for Insert/Update/Delete success carrying typed result structs; DslDataSucceeded reserved for show-data queries. Docs: - ADR-0014 covers data-op grammar, value model, --all-rows safety, auto-show. - requirements.md: C5 done, T2 done, V2 partial (basic data view), V5 partial (show data added). New entries: C5a complex WHERE expressions; H1 progress note for FK enrichment; H1a (strong syntax-help in parse errors). Tests: 200 passing (183 lib + 17 integration), 0 skipped. Includes parser, type-validation, DB write/read, FK-failure enrichment, cascade-delete propagation, focused-auto-show behaviour, scroll-cap invariants. Clippy clean with nursery enabled.
This commit is contained in:
+81
-7
@@ -13,6 +13,7 @@
|
||||
|
||||
use crate::dsl::action::ReferentialAction;
|
||||
use crate::dsl::types::Type;
|
||||
use crate::dsl::value::Value;
|
||||
|
||||
/// A column at table-creation time: a name and a user-facing
|
||||
/// type. Constraints beyond `PRIMARY KEY` (NOT NULL, UNIQUE,
|
||||
@@ -70,6 +71,38 @@ pub enum Command {
|
||||
ShowTable {
|
||||
name: String,
|
||||
},
|
||||
/// Insert a single row. `columns` is `None` for the natural-
|
||||
/// order short form (`insert into T values (...)`); the
|
||||
/// executor fills in the column list by walking the schema.
|
||||
Insert {
|
||||
table: String,
|
||||
columns: Option<Vec<String>>,
|
||||
values: Vec<Value>,
|
||||
},
|
||||
/// Update rows matching the WHERE clause (or all rows when
|
||||
/// `all_rows` is set, per ADR-0009 opt-in convention).
|
||||
Update {
|
||||
table: String,
|
||||
assignments: Vec<(String, Value)>,
|
||||
filter: RowFilter,
|
||||
},
|
||||
Delete {
|
||||
table: String,
|
||||
filter: RowFilter,
|
||||
},
|
||||
/// Render the rows of a table as a data view in the output.
|
||||
ShowData {
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// How an UPDATE / DELETE selects which rows to operate on.
|
||||
/// `Where` is the default safe form. `AllRows` is the explicit
|
||||
/// `--all-rows` flag opt-in for unfiltered operations.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RowFilter {
|
||||
Where { column: String, value: Value },
|
||||
AllRows,
|
||||
}
|
||||
|
||||
/// How a `drop relationship` command identifies the relationship
|
||||
@@ -114,6 +147,10 @@ impl Command {
|
||||
Self::AddRelationship { .. } => "add relationship",
|
||||
Self::DropRelationship { .. } => "drop relationship",
|
||||
Self::ShowTable { .. } => "show table",
|
||||
Self::Insert { .. } => "insert into",
|
||||
Self::Update { .. } => "update",
|
||||
Self::Delete { .. } => "delete from",
|
||||
Self::ShowData { .. } => "show data",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,16 +163,53 @@ impl Command {
|
||||
match self {
|
||||
Self::CreateTable { name, .. }
|
||||
| Self::DropTable { name }
|
||||
| Self::ShowTable { name } => name,
|
||||
Self::AddColumn { table, .. } => table,
|
||||
Self::AddRelationship { child_table, .. } => child_table,
|
||||
| Self::ShowTable { name }
|
||||
| Self::ShowData { name } => name,
|
||||
Self::AddColumn { table, .. }
|
||||
| Self::Insert { table, .. }
|
||||
| Self::Update { table, .. }
|
||||
| Self::Delete { table, .. } => table,
|
||||
// For relationships we focus on the parent (1-side):
|
||||
// the structure rendering after add/drop shows that
|
||||
// table's "Referenced by" entry, which is what the
|
||||
// user looks at to confirm the relationship.
|
||||
Self::AddRelationship { parent_table, .. } => parent_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::Endpoints { parent_table, .. } => parent_table,
|
||||
// For a named drop we don't know the parent table
|
||||
// until the executor resolves it; the name itself
|
||||
// is a sensible fallback for logging.
|
||||
RelationshipSelector::Named { name } => name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Human-readable subject for the `[ok] <verb> <subject>`
|
||||
/// summary line. Most commands target a single table, but
|
||||
/// relationship commands are better described by their
|
||||
/// endpoints than by either side alone.
|
||||
#[must_use]
|
||||
pub fn display_subject(&self) -> String {
|
||||
match self {
|
||||
Self::AddRelationship {
|
||||
parent_table,
|
||||
parent_column,
|
||||
child_table,
|
||||
child_column,
|
||||
..
|
||||
} => format!("from {parent_table}.{parent_column} to {child_table}.{child_column}"),
|
||||
Self::DropRelationship { selector } => match selector {
|
||||
RelationshipSelector::Named { name } => name.clone(),
|
||||
RelationshipSelector::Endpoints {
|
||||
parent_table,
|
||||
parent_column,
|
||||
child_table,
|
||||
child_column,
|
||||
} => format!(
|
||||
"from {parent_table}.{parent_column} to {child_table}.{child_column}"
|
||||
),
|
||||
},
|
||||
_ => self.target_table().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user