feat: ADR-0035 4d — CREATE [UNIQUE] INDEX / DROP INDEX
Advanced-mode SQL CREATE [UNIQUE] INDEX [IF NOT EXISTS] [<name>] ON <T> (cols) -> SqlCreateIndex and DROP INDEX [IF EXISTS] <name> -> SqlDropIndex, both reusing the ADR-0025 executors (do_add_index / do_drop_index), like 4c reused do_drop_table. - CREATE UNIQUE INDEX admitted in advanced mode (ADR-0025 Amendment 1): ADR-0025 deferred UNIQUE indexes for the simple-mode DSL, but advanced mode trusts the user like SQL does. Adds an additive IndexSchema.unique flag (project.yaml, serde-default, version stays 1); rebuild re-emits CREATE UNIQUE INDEX; the redundant-set guard keys on (columns, unique). Simple-mode `add unique index` stays deferred. - IF [NOT] EXISTS on both forms reuses the 4c no-op-with-note skip (journalled, not snapshotted) via CreateIndexOutcome / DropIndexOutcome. - Unnamed CREATE INDEX auto-named (ADR-0025 convention); the [UNIQUE] prefix is a concrete-keyword Choice and the optional name an on-led-first selector (the drop-index selector precedent) — trap-safe. - create/drop each gain a second advanced node; the existing all-candidates dispatch handles it (locked by parse tests). - Unique indexes marked [unique] in the structure view and items panel. - do_add_index refuses internal __rdbms_* tables as "no such table", closing a latent exposure on both the simple `add index` and the new SQL CREATE INDEX surfaces (ADR-0025 Amendment 1). Docs: ADR-0035 status + §13 4d + 4i; ADR-0025 Amendment 1; ADR README; requirements.md Q1/C3. Plan: docs/plans/20260525-adr-0035-sql-ddl-4d.md. Tests: 1834 passing / 0 failing / 0 skipped / 1 ignored; clippy clean.
This commit is contained in:
+29
-2
@@ -137,12 +137,15 @@ pub fn render_structure(desc: &TableDescription) -> Vec<String> {
|
||||
}
|
||||
|
||||
// Indexes section (ADR-0025), shown only when the table
|
||||
// carries at least one user-created index.
|
||||
// carries at least one user-created index. A UNIQUE index is
|
||||
// marked `[unique]` so a learner can tell a uniqueness-enforcing
|
||||
// index from a performance-only one (ADR-0035 §4d).
|
||||
if !desc.indexes.is_empty() {
|
||||
out.push("Indexes:".to_string());
|
||||
for index in &desc.indexes {
|
||||
let unique = if index.unique { " [unique]" } else { "" };
|
||||
out.push(format!(
|
||||
" {} ({})",
|
||||
" {} ({}){unique}",
|
||||
index.name,
|
||||
index.columns.join(", "),
|
||||
));
|
||||
@@ -797,6 +800,30 @@ mod tests {
|
||||
let out = render_structure(&desc).join("\n");
|
||||
assert!(out.contains("Indexes:"), "got:\n{out}");
|
||||
assert!(out.contains("idx_email (Email)"), "got:\n{out}");
|
||||
// A plain index carries no uniqueness marker.
|
||||
assert!(!out.contains("[unique]"), "plain index unmarked; got:\n{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_structure_marks_a_unique_index() {
|
||||
// ADR-0035 §4d: a UNIQUE index is marked `[unique]` so a learner
|
||||
// can tell it from a performance-only index.
|
||||
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: "uidx_email".to_string(),
|
||||
columns: vec!["Email".to_string()],
|
||||
unique: true,
|
||||
}],
|
||||
};
|
||||
let out = render_structure(&desc).join("\n");
|
||||
assert!(out.contains("uidx_email (Email) [unique]"), "got:\n{out}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user