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:
+52
-1
@@ -132,6 +132,19 @@ pub fn render_structure(desc: &TableDescription) -> Vec<String> {
|
||||
}
|
||||
}
|
||||
|
||||
// Indexes section (ADR-0025), shown only when the table
|
||||
// carries at least one user-created index.
|
||||
if !desc.indexes.is_empty() {
|
||||
out.push("Indexes:".to_string());
|
||||
for index in &desc.indexes {
|
||||
out.push(format!(
|
||||
" {} ({})",
|
||||
index.name,
|
||||
index.columns.join(", "),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
@@ -331,7 +344,7 @@ fn content_row(cells: &[String], widths: &[usize], alignments: &[Alignment]) ->
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::{ColumnDescription, RelationshipEnd};
|
||||
use crate::db::{ColumnDescription, IndexInfo, RelationshipEnd};
|
||||
use crate::dsl::ReferentialAction;
|
||||
use insta::assert_snapshot;
|
||||
|
||||
@@ -548,6 +561,7 @@ mod tests {
|
||||
],
|
||||
outbound_relationships: Vec::new(),
|
||||
inbound_relationships: Vec::new(),
|
||||
indexes: Vec::new(),
|
||||
};
|
||||
assert_snapshot!(render_structure(&desc).join("\n"));
|
||||
}
|
||||
@@ -566,6 +580,7 @@ mod tests {
|
||||
on_delete: ReferentialAction::Cascade,
|
||||
on_update: ReferentialAction::NoAction,
|
||||
}],
|
||||
indexes: Vec::new(),
|
||||
};
|
||||
let out = render_structure(&desc).join("\n");
|
||||
assert!(
|
||||
@@ -590,6 +605,7 @@ mod tests {
|
||||
],
|
||||
outbound_relationships: Vec::new(),
|
||||
inbound_relationships: Vec::new(),
|
||||
indexes: Vec::new(),
|
||||
};
|
||||
let out = render_structure(&desc).join("\n");
|
||||
// PK appears for id, NOT NULL for name, blank for nick.
|
||||
@@ -597,6 +613,40 @@ mod tests {
|
||||
assert!(out.contains("│ name │ text │ NOT NULL"), "got:\n{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_structure_shows_indexes_section() {
|
||||
let desc = TableDescription {
|
||||
name: "Customers".to_string(),
|
||||
columns: vec![
|
||||
col("id", Type::Serial, true, false),
|
||||
col("Email", Type::Text, false, false),
|
||||
],
|
||||
outbound_relationships: Vec::new(),
|
||||
inbound_relationships: Vec::new(),
|
||||
indexes: vec![IndexInfo {
|
||||
name: "idx_email".to_string(),
|
||||
columns: vec!["Email".to_string()],
|
||||
unique: false,
|
||||
}],
|
||||
};
|
||||
let out = render_structure(&desc).join("\n");
|
||||
assert!(out.contains("Indexes:"), "got:\n{out}");
|
||||
assert!(out.contains("idx_email (Email)"), "got:\n{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_structure_omits_indexes_section_when_none() {
|
||||
let desc = TableDescription {
|
||||
name: "T".to_string(),
|
||||
columns: vec![col("id", Type::Serial, true, false)],
|
||||
outbound_relationships: Vec::new(),
|
||||
inbound_relationships: Vec::new(),
|
||||
indexes: Vec::new(),
|
||||
};
|
||||
let out = render_structure(&desc).join("\n");
|
||||
assert!(!out.contains("Indexes:"), "got:\n{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_structure_falls_back_to_sqlite_type_when_user_type_missing() {
|
||||
let mut desc = TableDescription {
|
||||
@@ -610,6 +660,7 @@ mod tests {
|
||||
}],
|
||||
outbound_relationships: Vec::new(),
|
||||
inbound_relationships: Vec::new(),
|
||||
indexes: Vec::new(),
|
||||
};
|
||||
let out = render_structure(&desc).join("\n");
|
||||
// The lowercase form of the SQLite type should appear.
|
||||
|
||||
Reference in New Issue
Block a user