Files
rdbms-playground/docs/adr/0014-data-operations-and-value-model.md
T
claude@clouddev1 305e5083d5 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.
2026-05-07 16:33:25 +00:00

173 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0014: Data operations, value literals, and the auto-show pattern
## Status
Accepted
## Context
Schema operations (ADRs 0002, 0005, 0011, 0013) gave us tables,
columns, and relationships. Without INSERT / UPDATE / DELETE,
foreign-key behaviour is observable but not demonstrable — a
learner can't yet trigger a CASCADE or watch a constraint catch
a bad write. C5 closes that gap.
Several coupled questions:
- **Value literals.** How does the user write `'2025-01-15'` for
a date column versus `42` for an int column? What gets
validated where?
- **Safe defaults for destructive operations.** UPDATE and
DELETE without WHERE are classic foot-guns.
- **Auto-generation of `shortid`.** T2 commits to client-side
generation at insert time, which now becomes load-bearing.
- **FK error clarity.** SQLite reports `FOREIGN KEY constraint
failed` with no detail; pedagogically that's nearly useless.
- **Showing data back to the user.** Without a `SELECT`-like
surface, the user has no way to see what changed.
## Decision
### Grammar
```
insert into <Table> [(<col>, <col>...)] values (<val>, <val>...)
update <Table> set <col>=<val>[, <col>=<val>...] (where <col>=<val> | --all-rows)
delete from <Table> (where <col>=<val> | --all-rows)
show data <Table>
```
- **INSERT short form** (`insert into T values (...)`): values
apply to non-auto-generated columns in schema declaration
order. Serial columns are filled by SQLite; shortid columns
are auto-generated by the executor.
- **INSERT long form** (with explicit column list): user
controls exactly which columns receive values; auto-generated
columns the user didn't list are still auto-filled.
- **WHERE clause** is required for UPDATE and DELETE by default.
The `--all-rows` flag is the explicit opt-in to unfiltered
operations, following ADR-0009 (`--` reserved for opt-in
flags). Specifying both WHERE and `--all-rows` is a parse
error.
- **WHERE this iteration** is exactly `<col>=<val>`. Richer
WHERE expressions (AND/OR/comparison/LIKE) are deferred — they
are tracked as a future iteration and are intended as the
bridge from DSL into real SQL fluency.
- **`show data <Table>`** joins the V5 show-family (with
`show table <Name>`); auto-show after writes (below) means
most users won't need to call it explicitly.
### Value literals
The parser produces a small `Value` AST (`Number(String)`,
`Text(String)`, `Bool(bool)`, `Null`). Per-column-type validation
lives in the executor where the schema is known:
| User-facing type | Accepted literal |
|------------------|-------------------------------------------------------------|
| `text` | single-quoted string `'hello'` (`''` escapes a quote) |
| `int` | integer literal `42`, `-7` |
| `real` | numeric literal `3.14`, `-0.5` |
| `decimal` | numeric literal; stored as text to preserve precision |
| `bool` | `true` / `false` |
| `date` | quoted `'YYYY-MM-DD'` (validated) |
| `datetime` | quoted `'YYYY-MM-DDTHH:MM:SS[.fff][Z|±HH:MM]'` (validated) |
| `blob` | DSL literal not supported this iteration |
| `serial` | normally omitted (auto-fill); explicit integer accepted |
| `shortid` | normally omitted (auto-generated); explicit base58 1012 |
| `null` | keyword `null` |
Validation produces friendly errors that name the column and
expected shape — e.g. "column `Name` expects a quoted string for
`text`, got number".
### Auto-generation for `shortid`
When an INSERT (in either form) does not provide a value for a
`shortid` column, the executor calls the shortid generator and
fills in a 10-character base58 value (no `0`/`O`/`I`/`l`).
Explicit values are accepted but validated against the same
alphabet and length range (1012 chars). The `rand` crate is
the source of randomness.
### FK error enrichment
SQLite reports `FOREIGN KEY constraint failed` without naming
the offending constraint or value. The executor catches this
class of error and appends the table's outbound relationships
(via the metadata table from ADR-0013) to the message:
```
FOREIGN KEY constraint failed. Foreign keys on this table:
- Orders.CustId → Customers.id
Check that each referenced value exists in the parent table.
```
Identifying the *exact* offending row is left to the H1 friendly
error layer when that lands.
### Auto-show after writes
INSERT, UPDATE, and DELETE successfully completing fetch the
target table's full data and emit a `DslDataSucceeded` event
carrying both `rows_affected: Some(n)` and the data view. The
App renders both. Users see the result immediately without
needing a follow-up `show data` command.
`show data <Table>` follows the same path with
`rows_affected: None`.
The auto-show convention is reserved for DSL data ops. When
advanced-mode SQL lands (Q1), arbitrary `SELECT` statements may
opt out — large result sets shouldn't be implicitly inserted into
the session log.
### Tabular rendering
The data view is rendered as simple aligned-column text:
```
id | Name | Email
----+-----------+-----------------
1 | Alice | a@b.com
2 | Bob | (null)
```
Pretty box-drawing renderings (with truncation, scroll
indicators, wide-table handling) are deferred to V4. NULL cells
render as `(null)` to be explicitly visible; booleans render as
`true`/`false` despite their integer storage.
## Consequences
- C5 is satisfied: INSERT/UPDATE/DELETE operate end-to-end with
validation, auto-generation, FK enforcement, and visible
feedback.
- T2 is satisfied: shortid auto-generation runs on insert.
- V2 partial: a usable tabular data view exists, with the
pretty-rendering iteration still ahead.
- V5 partial: `show data` joins `show table` in the show family.
- H1 partial: FK-failure messages are enriched without
introducing the full friendly-error layer.
- The `--all-rows` opt-in convention is now established for
destructive-without-filter operations — future commands of the
same shape (`drop relationship --cascade`?) follow the
pattern.
- The runtime's `CommandOutcome` enum is extended with a `Data`
variant. New data-emitting commands plug in there without
reshaping the dispatch.
- Complex WHERE expressions (AND/OR/comparison/LIKE), bulk
INSERT, ORDER BY, LIMIT, JOIN, and SELECT in advanced mode are
explicitly out of scope for this iteration; richer DSL WHERE
is the bridge iteration toward Q1's full SQL handling.
## See also
- ADR-0005 (column types — value-literal mappings here mirror
the storage choices)
- ADR-0009 (DSL command syntax conventions — `--` flag rule)
- ADR-0011 (FK column type compatibility — used during the
validation that runs before writes)
- ADR-0013 (relationships and rebuild-table — the FK metadata
used by the error-enrichment path)