Implements the `export` and `import` app-level commands per
ADR-0015 §11 + ADR-0007 amendment 1.
- `export [<path>]` writes a zip of project.yaml + data/ to
<data-root>/YYYYMMDD-<projectname>-export-NN.zip by default,
preserving the project's directory name as the single
top-level folder inside the archive.
- `import <zip> [as <target>]` extracts an exported zip into
a new named project and switches to it. Target name is
derived from the zip's top-level folder by default; on
collision the destination auto-suffixes -02, -03, ... up
to -99 instead of refusing (deviates from §2's refuse-on-
collision rule for save/save as; recorded as an amendment
to ADR-0015 §11).
- Excludes playground.db and history.log from the zip.
- Path-traversal protection via zip::enclosed_name + post-
resolution check that the extraction path stays inside
the target directory.
Adds the zip = "5" dep with default-features = false +
features = ["deflate"] to keep the binary-size cost modest.
Test baseline: 370 passing, 0 failing, 0 skipped.
When the runtime opens a project whose playground.db is missing,
it now rebuilds the database from project.yaml + data/<table>.csv
per ADR-0015 §7. The rebuild path:
1. Parses project.yaml (serde_yml). Unknown versions / types /
actions surface as PersistenceFatal.
2. Recreates each user table with FK constraints inline
(PRAGMA foreign_keys=OFF), then populates the column-type,
relationship, and project metadata tables.
3. Loads each table's CSV via a hand-rolled reader that
preserves the NULL-vs-empty distinction (the csv crate
doesn't expose whether a field was quoted; ours does).
4. Runs PRAGMA foreign_key_check before commit; any violation
aborts.
5. Restores foreign_keys=ON regardless of success.
Row-level failures get DbError::RebuildRowFailed with row
number, file, table, and a friendly per-type detail. They land
in the runtime as a fatal stderr message ("unable to load row N
from `data/T.csv` into table `T`: ...") before the alternate
screen is entered.
created_at from project.yaml overwrites the configure-time
placeholder so timestamps round-trip stably.
Tests: 307 passing (267 lib + 9 + 5 new + 9 + 17), 0 failing,
0 skipped. Clippy clean with nursery lints.
Every successful user command now persists through to YAML, the
affected CSVs, and history.log inside the same SQLite transaction,
with the commit-db-last ordering from ADR-0015 §6: validate ->
mutate -> stage text + fsync -> atomic rename -> append history ->
commit. A failure in any text-write step rolls back the SQLite tx,
so disk state is unchanged on failure. Persistence failures are
routed through a new AppEvent::PersistenceFatal which sets a
fatal_message on the App, emits Action::Quit, and is printed to
stderr after terminal teardown so the banner remains above the
shell prompt (ADR-0015 §8).
New persistence module owns the file formats: hand-rolled YAML
schema writer, per-type CSV encoder (RFC 4180, NULL distinct from
empty string, base64 blobs), append-only history.log with ISO-8601
timestamps and successful-only entries. Atomic per-file writes via
tmp + fsync + rename.
The db worker holds an Option<Persistence>; tests still use
Database::open(":memory:") with no persistence. Action::ExecuteDsl
gains a source field carrying the user-typed text, threaded
through to history.log.
Tests: 289 passing (256 lib + 7 new integration + 9 lifecycle + 17
walking-skeleton), 0 failing, 0 skipped. Clippy clean with nursery
lints.
Replaces the in-memory database with an on-disk project. Startup either
opens a project at the positional CLI path (L1) or creates an auto-named
temp project (<YYYYMMDD>-<word>-<word>-<word>) under the OS-standard
data directory or a --data-dir override. The new project::Project type
owns the directory skeleton and a PID+hostname lock file with
stale-lock takeover via sysinfo. The status bar now shows
"Project: <Display Name>", derived by a small kebab/snake/camel
prettifier. Per-command persistence to YAML/CSV/history.log is NOT
yet wired -- that's Iteration 2; for now playground.db carries the
state across quits.
Tests: 257 passing (231 lib + 9 new integration + 17 existing),
0 failing, 0 skipped. Clippy clean with nursery lints.
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.
First implementation milestone: Cargo project, dependencies,
and a minimal but functional TUI shell built on Ratatui +
Crossterm + Tokio in the Elm-style update/view pattern
(Candidate A from Phase 2/3 selection).
Includes:
- Three-region layout: items list (left), output + input + hint
(right), bottom status bar with mode-aware shortcuts.
- Two themes (light, dark) plus COLORFGBG auto-detect, per
NFR-7. CLI: --theme {light,dark}, --log-file <path>.
- Input modes per ADR-0003: simple (default), advanced, with
the `:` one-shot escape including immediate prompt reaction
("Advanced:" label, advanced border) and auto-inserted space
after a leading `:` in simple mode.
- App-level commands: `quit`/`q`, `mode simple`/`mode advanced`
(canonical list per ADR-0003 — remaining commands land in
later iterations).
- File logging via tracing, defaulting to ~/.rdbms-playground/
playground.log so the TUI is not corrupted by stdio.
Testing per ADR-0008:
- Tier 1: 29 unit tests covering input handling, mode switch,
one-shot escape, auto-space, output buffering, CLI parsing.
- Tier 2: 4 insta snapshots (default simple/advanced/light,
one-shot active) of TestBackend frames.
- Tier 3: 7 integration tests driving synthetic events through
App::update + render path.
All green: 36 tests, 0 failures, 0 skips. Clippy clean with
nursery lints enabled.