Indexes: add index / drop index, persistence, display (ADR-0025)

Implement ADR-0025 — indexes as a DSL DDL feature.

- Grammar: `add index [as <name>] on <T> (<cols>)`, `drop index
  <name>` / `drop index on <T> (<cols>)`, plus a `--cascade`
  flag on `drop column`.
- db.rs: index operations over the engine's native index
  catalog (no metadata table). The rebuild-table primitive now
  captures and recreates indexes, so `change column` and the
  relationship operations no longer silently drop them.
- `drop column` refuses an indexed column unless `--cascade`,
  which drops the covering indexes and reports each.
- Persistence: additive `indexes:` list in `project.yaml`
  (version unchanged); round-trips through rebuild/export/import.
- Display: an `Indexes:` section in the structure view and a
  nested tables/indexes items panel (S2).

Reconciles requirements.md (C3 index portion, S2 satisfied)
and CLAUDE.md. 1038 tests passing (+31), clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-16 00:15:55 +00:00
parent 41043d686b
commit 0dc159fd7e
35 changed files with 2155 additions and 73 deletions
+48 -1
View File
@@ -46,10 +46,14 @@ pub enum Command {
},
/// Remove a column from a table. Refused if the column is
/// part of the primary key or is involved in a declared
/// relationship — drop the relationship first.
/// relationship — drop the relationship first. Refused, too,
/// when an index covers the column, unless `cascade` is set
/// (the `--cascade` flag), in which case the covering
/// indexes are dropped alongside the column (ADR-0025).
DropColumn {
table: String,
column: String,
cascade: bool,
},
/// Rename a column. SQLite handles cascading renames in
/// FK references on other tables; the executor mirrors
@@ -96,6 +100,19 @@ pub enum Command {
DropRelationship {
selector: RelationshipSelector,
},
/// Create an index on one or more columns of a table
/// (ADR-0025). `name` is optional — when `None`, the
/// executor auto-generates `<Table>_<col…>_idx`.
AddIndex {
name: Option<String>,
table: String,
columns: Vec<String>,
},
/// Drop an index by name, or by positional reference to its
/// table and exact column set (ADR-0025).
DropIndex {
selector: IndexSelector,
},
/// Re-display a table's structure in the output. Doesn't
/// change schema; useful when the user wants to look at a
/// table they aren't currently DDL'ing on.
@@ -253,6 +270,26 @@ impl std::fmt::Display for RelationshipSelector {
}
}
/// How a `drop index` command identifies the index to remove
/// (ADR-0025). Both forms are accepted; the executor resolves to
/// a single index.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IndexSelector {
Named { name: String },
Columns { table: String, columns: Vec<String> },
}
impl std::fmt::Display for IndexSelector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Named { name } => write!(f, "{name}"),
Self::Columns { table, columns } => {
write!(f, "on {table} ({})", columns.join(", "))
}
}
}
}
impl Command {
/// Short label for log output and result rendering.
#[must_use]
@@ -266,6 +303,8 @@ impl Command {
Self::ChangeColumnType { .. } => "change column",
Self::AddRelationship { .. } => "add relationship",
Self::DropRelationship { .. } => "drop relationship",
Self::AddIndex { .. } => "add index",
Self::DropIndex { .. } => "drop index",
Self::ShowTable { .. } => "show table",
Self::Insert { .. } => "insert into",
Self::Update { .. } => "update",
@@ -318,6 +357,14 @@ impl Command {
// is a sensible fallback for logging.
RelationshipSelector::Named { name } => name,
},
Self::AddIndex { table, .. } => table,
Self::DropIndex { selector } => match selector {
IndexSelector::Columns { table, .. } => table,
// A named drop doesn't name the table until the
// executor resolves it; the index name is a
// sensible fallback for logging.
IndexSelector::Named { name } => name,
},
// Replay isn't tied to a single table; the path is
// the most identifying thing for log output.
Self::Replay { path } => path,