Files
rdbms-playground/docs/handoff/20260517-handoff-15.md
T
claude@clouddev1 f4eedf3dd2 chore: handoff
2026-05-17 09:22:49 +00:00

11 KiB

Session handoff — 2026-05-17 (15)

Fifteenth handover. This session implemented ADR-0025 (Indexes) end to end — a fresh feature off the docs/requirements.md roadmap — and fixed an input-history navigation bug found while testing it.

Headline: indexes are a real DSL feature now. add index / drop index, rebuild-safe, persisted in project.yaml, shown in the structure view and a new nested items panel. ADR-0025 is Accepted and fully implemented. The natural next job is QA1 — EXPLAIN QUERY PLAN — now unblocked because there is finally an index for a plan to use; see §4.

State at handoff

Branch: main. Working tree clean. origin/main is at 0dc159f (the ADR-0025 commit — the user pushed it); local HEAD is 1 commit ahead (4e4dbbf, the history fix — unpushed; the user pushes asynchronously).

Commits since handoff-14:

4e4dbbf Input history: reset the nav cursor on every submit
0dc159f Indexes: add index / drop index, persistence, display (ADR-0025)

Tests: 1039 passing, 0 failing, 1 ignored (cargo test — up from 1006/1007). The ignored test is the long-standing ```ignore doc-test in src/friendly/mod.rs. The typing-surface matrix is now 152 cells (was 144).

Clippy: clean with nursery lints + -D warnings.

§1. What shipped

ADR-0025 — indexes (0dc159f)

A new ADR (docs/adr/0025-indexes.md, Accepted) and its full implementation. The four design forks were settled by the user before drafting:

  • Naming: optional, add index [as <name>] on <T> (<cols>); auto-name <Table>_<col…>_idx when omitted. Mirrors the ADR-0013 as-name convention.
  • UNIQUE: out of scope — plain non-unique indexes only; UNIQUE stays a separate C3 sub-item.
  • Items list (S2): the left panel is now a nested tree — each table with its index names indented beneath it.
  • drop column: refuses an indexed column by default; a new --cascade flag opts into dropping the covering indexes.

What landed:

  • Grammar (src/dsl/grammar/ddl.rs): add index is a third branch of add; drop index a fourth branch of drop; both drop forms (drop index <name> / drop index on <T> (<cols>)). drop column gained an optional --cascade flag.
  • AST (src/dsl/command.rs): Command::AddIndex / Command::DropIndex, an IndexSelector enum (Named / Columns, mirroring RelationshipSelector), and a cascade: bool field on Command::DropColumn.
  • db layer (src/db.rs): do_add_index / do_drop_index; index reads via the engine's native catalog (pragma_index_list / pragma_index_info, origin c only) — no metadata table, unlike relationships; IndexInfo added to TableDescription; friendly errors for duplicate name, redundant column set, missing table/column, unknown / ambiguous index.
  • Rebuild safety: rebuild_table_with_copy now captures a table's indexes before the DROP TABLE and recreates them after the rename — so change column and the relationship operations no longer silently drop indexes.
  • drop column --cascade: do_drop_column detects covering indexes, refuses without the flag, drops them with it. Returns a new DropColumnResult { description, dropped_indexes }; the runtime reports each dropped index.
  • Persistence: an additive indexes: list in project.yaml (version: unchanged — older files default it empty). read_schema_snapshot writes it, do_rebuild_from_text recreates indexes on rebuild; export / import carry them.
  • Display: an Indexes: section in render_structure; the nested items panel via a new SchemaCache::table_indexes map.
  • Tests: 30 new across tiers — 6 parser, 13 db integration (real in-memory SQLite), 2 output_render, 1 full project rebuild round-trip (tests/iteration3_rebuild.rs), 8 typing-surface matrix cells (tests/typing_surface/index_ops.rs).

Input-history cursor fix (4e4dbbf)

See §2.

§2. Bug found and fixed this session

Input-history cursor stranded on re-submit. App::push_history did its history_cursor / history_draft reset after the consecutive-duplicate early-return. So recalling a command with Up and re-submitting it unchanged (a duplicate → not appended → reset skipped) left the cursor pinned at that entry; the next Up then stepped backwards from there (Some(i) => i-1) instead of restarting at the newest entry — the next Up appeared to "skip" an entry.

Fixed by moving the reset ahead of the early-return guards — submitting a command always ends history navigation. Tier-1 regression test app::tests::resubmitting_a_recalled_command_does_not_strand_the_cursor drives the recall/resubmit keystroke sequence.

§3. Notes for the next agent

Two ADR-0025 deviations are recorded in that ADR's ## Implementation notes section — read them, the decision text and the code agree there but the sketch above it does not:

  • rename column / drop column use native ALTER TABLE, not the rebuild primitive. SQLite's RENAME COLUMN already rewrites index definitions, so rename needs no index code; drop column handles covering indexes directly. Only change column (and the relationship ops) go through the rebuild primitive, where the column set is unchanged, so indexes are recreated verbatim — no rename-map needed.
  • The S2 panel carries per-table index names in SchemaCache::table_indexes rather than restructuring app.tables and the TablesRefreshed event.

One defensively-unreachable branch. do_drop_index's "more than one index matches these columns" arm cannot be reached via the app's own commands — do_add_index refuses a redundant index over an identical column set. It is reachable only via a hand-edited project.yaml, which the project does defend against elsewhere. Left in, untested.

§4. What's next — QA1 is unblocked

docs/requirements.md was reconciled this session: the index portion of C3 is done, S2 moved to [x], the QA1 note updated, test baseline refreshed to 1039.

The natural next pick is QA1EXPLAIN QUERY PLAN. Until this session there was no index for a plan to be interesting about; now show data <T> where <col> = <val> (the C5 filtered form, already implemented) is a query whose plan visibly flips between a full scan and an index search. But QA1 has an unmet dependency of its own: QA2 (plan rendering — tree layout, annotation taxonomy, colour scheme) is [~] and needs an ADR before any implementation. So QA1 is "write the QA2 ADR, then build" — not a self-contained drop-in. Prioritisation is a user product decision — ask, don't assume.

Other open clusters in requirements.md (unchanged from handoff-14 §4, minus indexes):

  • Complex WHERE expressions (C5a, [~]) — AND/OR / comparison / LIKE in UPDATE/DELETE/show-data filters. Needs an ADR. The bridge from DSL toward real SQL.
  • SQL in advanced mode (Q1/Q4, [~]) — sqlparser-rs
    • a defined subset. Needs an ADR.
  • Snapshot / undo (U1/U2) — designed in ADR-0006, not built.
  • m:n convenience (C4), modify relationship (C3a, [~]), table rename (C1).
  • Friendly error layer (H1) — partial. Syntax-help in parse errors (H1a) — piecemeal.
  • Session log + Markdown export (V4, [~]), multi-line input (I1), readline shortcuts (I1b), seeding (SD1), CI (TT5), tutorial system (TU1, [~]).

§5. Architectural delta (vs. handoff-14)

New Command variants / fields

  • Command::AddIndex { name: Option<String>, table, columns }.
  • Command::DropIndex { selector: IndexSelector }.
  • IndexSelector enum — Named / Columns.
  • Command::DropColumn gained cascade: bool.

New grammar surface

  • IdentSource::Indexes — completion source for existing index names.
  • add / drop Choices gained index branches; drop column gained the --cascade flag node.

New db surface

  • IndexInfo (public) — on TableDescription::indexes.
  • DropColumnResult { description, dropped_indexes } — the new return type of Database::drop_column / Request::DropColumn / do_drop_column.
  • Request::AddIndex / DropIndex; Database::add_index / drop_index.
  • read_table_indexes / read_index_columns helpers; do_add_index / do_drop_index.
  • rebuild_table_with_copy is now index-preserving.

New event / outcome / friendly surface

  • AppEvent::DslDropColumnSucceeded { command, result }; App::handle_dsl_drop_column_success.
  • CommandOutcome::DropColumn(DropColumnResult).
  • friendly::Operation::AddIndex / DropIndex.

SchemaCache

  • indexes: Vec<String> — flat index-name list (for drop index <name> completion).
  • table_indexes: HashMap<String, Vec<String>> — per-table index names (for the S2 panel).

Persistence

  • IndexSchema { name, table, columns }; SchemaSnapshot.indexes; a indexes: block in the project.yaml writer + an optional #[serde(default)] field in the reader.

Catalog

  • Added: parse.usage.add_index, parse.usage.drop_index, ok.index_dropped_with_column. Modified: help.ddl.add, help.ddl.drop. New keys registered in src/friendly/keys.rs.

Tests

  • New matrix file tests/typing_surface/index_ops.rs (8 cells) → 152 matrix cells total.

§6. How to take over

  1. Read this file, then handoff-14, then 13 for the chain.
  2. Read CLAUDE.md — the working-style rules. This session escalated all four index design forks to the user before drafting the ADR.
  3. Read docs/requirements.md — reconciled this session; the authoritative "what's open" tracker.
  4. Read docs/adr/0025-indexes.md — including its ## Implementation notes (§3 above).
  5. Run cargo test — 1039 passing, 0 failing, 1 ignored.
  6. Run cargo clippy --all-targets -- -D warnings — clean.
  7. 4e4dbbf is unpushed — the user pushes; don't treat it as blocking.
  8. Pick the next work from §4QA1 is the natural pick but it needs the QA2 rendering ADR first, and prioritisation is a user product decision. Ask, don't assume.

Note on the typing-surface matrix

tests/typing_surface/ (now 152 cells) is the regression net for everything walker/hint/completion. After any grammar or walker change: a failing cell with correct new behaviour → update its snapshot (INSTA_UPDATE=always cargo test --test typing_surface_matrix <family>); a failing cell with wrong behaviour → the cell earned its keep. Adding add index shifted one add_relationship cell's snapshot this session (the add candidate set gained index) — that was a correct update.