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:
@@ -420,20 +420,27 @@ fn render_items_panel(app: &App, theme: &Theme, frame: &mut Frame<'_>, area: Rec
|
||||
.as_ref()
|
||||
.map(|t| t.name.as_str())
|
||||
.unwrap_or_default();
|
||||
let lines: Vec<Line<'_>> = app
|
||||
.tables
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let style = if name == highlight {
|
||||
Style::default()
|
||||
.fg(theme.fg)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(theme.fg)
|
||||
};
|
||||
Line::from(Span::styled(name.as_str(), style))
|
||||
})
|
||||
.collect();
|
||||
// Nested tables / per-table indexes (S2, ADR-0025): each
|
||||
// table line, with its index names indented beneath it.
|
||||
let mut lines: Vec<Line<'_>> = Vec::new();
|
||||
for name in &app.tables {
|
||||
let style = if name == highlight {
|
||||
Style::default()
|
||||
.fg(theme.fg)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(theme.fg)
|
||||
};
|
||||
lines.push(Line::from(Span::styled(name.as_str(), style)));
|
||||
if let Some(indexes) = app.schema_cache.table_indexes.get(name) {
|
||||
for index in indexes {
|
||||
lines.push(Line::from(Span::styled(
|
||||
format!(" {index}"),
|
||||
Style::default().fg(theme.muted),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let paragraph = Paragraph::new(lines).block(block);
|
||||
frame.render_widget(paragraph, area);
|
||||
}
|
||||
@@ -1013,6 +1020,7 @@ mod tests {
|
||||
],
|
||||
outbound_relationships: Vec::new(),
|
||||
inbound_relationships: Vec::new(),
|
||||
indexes: Vec::new(),
|
||||
};
|
||||
app.current_table = Some(desc);
|
||||
// Mirror what the App writes when a DSL command succeeds.
|
||||
@@ -1041,4 +1049,21 @@ mod tests {
|
||||
let snapshot = render_to_string(&mut app, &theme, 80, 24);
|
||||
insta::assert_snapshot!("populated_with_table_dark", snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn items_panel_nests_indexes_under_their_table() {
|
||||
// S2 (ADR-0025): the items panel renders each table
|
||||
// with its index names indented beneath it.
|
||||
let mut app = App::new();
|
||||
app.tables = vec!["Customers".to_string(), "Orders".to_string()];
|
||||
app.schema_cache.table_indexes.insert(
|
||||
"Customers".to_string(),
|
||||
vec!["idx_email".to_string()],
|
||||
);
|
||||
let theme = Theme::dark();
|
||||
let out = render_to_string(&mut app, &theme, 80, 24);
|
||||
assert!(out.contains("Customers"), "table listed:\n{out}");
|
||||
assert!(out.contains("Orders"), "table listed:\n{out}");
|
||||
assert!(out.contains("idx_email"), "index nested in panel:\n{out}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user