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
@@ -14,6 +14,10 @@ Assessment {
text: "column",
kind: Keyword,
},
Candidate {
text: "index",
kind: Keyword,
},
Candidate {
text: "1:n",
kind: Keyword,
@@ -34,6 +38,10 @@ Assessment {
text: "column",
kind: Keyword,
},
Candidate {
text: "index",
kind: Keyword,
},
Candidate {
text: "1:n",
kind: Keyword,
@@ -0,0 +1,47 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"add index on \" cursor=13"
expression: "& a"
---
Assessment {
input: "add index on ",
cursor: 13,
state: IncompleteAtEof,
hint: Some(
Candidates {
items: [
Candidate {
text: "Customers",
kind: Identifier,
},
Candidate {
text: "Orders",
kind: Identifier,
},
],
selected: None,
},
),
completion: Some(
Completion {
replaced_range: (
13,
13,
),
partial_prefix: "",
candidates: [
Candidate {
text: "Customers",
kind: Identifier,
},
Candidate {
text: "Orders",
kind: Identifier,
},
],
},
),
parse_result: Err(
"Invalid(at_eof)",
),
}
@@ -0,0 +1,55 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"add index on Orders (\" cursor=21"
expression: "& a"
---
Assessment {
input: "add index on Orders (",
cursor: 21,
state: IncompleteAtEof,
hint: Some(
Candidates {
items: [
Candidate {
text: "CustId",
kind: Identifier,
},
Candidate {
text: "OrderId",
kind: Identifier,
},
Candidate {
text: "Total",
kind: Identifier,
},
],
selected: None,
},
),
completion: Some(
Completion {
replaced_range: (
21,
21,
),
partial_prefix: "",
candidates: [
Candidate {
text: "CustId",
kind: Identifier,
},
Candidate {
text: "OrderId",
kind: Identifier,
},
Candidate {
text: "Total",
kind: Identifier,
},
],
},
),
parse_result: Err(
"Invalid(at_eof)",
),
}
@@ -0,0 +1,55 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"add \" cursor=4"
expression: "& a"
---
Assessment {
input: "add ",
cursor: 4,
state: IncompleteAtEof,
hint: Some(
Candidates {
items: [
Candidate {
text: "column",
kind: Keyword,
},
Candidate {
text: "index",
kind: Keyword,
},
Candidate {
text: "1:n",
kind: Keyword,
},
],
selected: None,
},
),
completion: Some(
Completion {
replaced_range: (
4,
4,
),
partial_prefix: "",
candidates: [
Candidate {
text: "column",
kind: Keyword,
},
Candidate {
text: "index",
kind: Keyword,
},
Candidate {
text: "1:n",
kind: Keyword,
},
],
},
),
parse_result: Err(
"Invalid(at_eof)",
),
}
@@ -0,0 +1,63 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"drop \" cursor=5"
expression: "& a"
---
Assessment {
input: "drop ",
cursor: 5,
state: IncompleteAtEof,
hint: Some(
Candidates {
items: [
Candidate {
text: "column",
kind: Keyword,
},
Candidate {
text: "relationship",
kind: Keyword,
},
Candidate {
text: "table",
kind: Keyword,
},
Candidate {
text: "index",
kind: Keyword,
},
],
selected: None,
},
),
completion: Some(
Completion {
replaced_range: (
5,
5,
),
partial_prefix: "",
candidates: [
Candidate {
text: "column",
kind: Keyword,
},
Candidate {
text: "relationship",
kind: Keyword,
},
Candidate {
text: "table",
kind: Keyword,
},
Candidate {
text: "index",
kind: Keyword,
},
],
},
),
parse_result: Err(
"Invalid(at_eof)",
),
}
@@ -0,0 +1,19 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"add index as ord_cust on Orders (CustId)\" cursor=40"
expression: "& a"
---
Assessment {
input: "add index as ord_cust on Orders (CustId)",
cursor: 40,
state: Valid,
hint: Some(
Prose(
"Submit with Enter",
),
),
completion: None,
parse_result: Ok(
"AddIndex",
),
}
@@ -0,0 +1,19 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"add index on Orders (CustId)\" cursor=28"
expression: "& a"
---
Assessment {
input: "add index on Orders (CustId)",
cursor: 28,
state: Valid,
hint: Some(
Prose(
"Submit with Enter",
),
),
completion: None,
parse_result: Ok(
"AddIndex",
),
}
@@ -0,0 +1,19 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"drop index on Orders (CustId)\" cursor=29"
expression: "& a"
---
Assessment {
input: "drop index on Orders (CustId)",
cursor: 29,
state: Valid,
hint: Some(
Prose(
"Submit with Enter",
),
),
completion: None,
parse_result: Ok(
"DropIndex",
),
}
@@ -0,0 +1,19 @@
---
source: tests/typing_surface/index_ops.rs
description: "input=\"drop index some_idx\" cursor=19"
expression: "& a"
---
Assessment {
input: "drop index some_idx",
cursor: 19,
state: Valid,
hint: Some(
Prose(
"No such identifier: `some_idx`",
),
),
completion: None,
parse_result: Ok(
"DropIndex",
),
}