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

267 lines
11 KiB
Markdown

# 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 `QA1` — `EXPLAIN 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` `Choice`s 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 §4** — `QA1` 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.