Files
rdbms-playground/tests/it/sql_drop_index.rs
T
claude@clouddev1 4aeea55984 feat(history): mode-tagged history + top-of-chain journaling (#30)
Record the submission mode per history entry so advanced commands are
reusable in simple mode, and fix the bug where a ':'-one-shot command
lost its ':' across sessions (ADR-0052, closing #30).

Format: the history.log status token gains an optional ':adv' suffix
(ok / ok:adv / err / err:adv); 'source' stays last and canonical, so
replay is unaffected. The in-memory ring (still Vec<String>) stores
advanced entries ': '-prefixed; recall strips the ':' in advanced mode
and keeps it in simple; hydration reconstructs the prefix from the tag.

Journaling moved from the worker to the dispatch layer (spawn_dsl_-
dispatch / run_replay / app-command sites), where the mode is in scope
with no worker plumbing; finalize_persistence writes only yaml/csv
(commit-db-last still atomic for state). The journal write is now
best-effort (command already committed), consistent with the failure
path. App commands journal simple, so they recall bare. Journaling is
now uniform (every successful command, per ADR-0034) — closing a gap
where show tables/relationships/explain didn't journal.

Amends ADR-0034 (status tag + journaling location), ADR-0015 §6
(history.log out of the worker tx), ADR-0040 (journal-write best-effort).
15 worker-level journaling tests retired, re-covered at the new layer
(history.rs format, app.rs recall matrix, iteration6 cross-session
regression, replay). 2471 pass / 0 fail / 0 skip, clippy clean.
2026-06-14 11:20:55 +00:00

124 lines
4.5 KiB
Rust

//! Sub-phase 4d integration tests for advanced-mode SQL
//! `DROP INDEX [IF EXISTS]` (ADR-0035 §4d).
//!
//! `SqlDropIndex` executes through the same `do_drop_index` machinery as
//! the simple `drop index <name>`; the only new behaviour is `IF EXISTS`
//! as a no-op-with-note (`DropIndexOutcome::Skipped`). These drive the
//! worker directly; parsing (text → `Command::SqlDropIndex`) is covered
//! by the `sql_drop_index_tests` in `src/dsl/grammar/ddl.rs`.
use rdbms_playground::db::{Database, DropIndexOutcome};
use rdbms_playground::dsl::{ColumnSpec, Type};
use rdbms_playground::persistence::Persistence;
use rdbms_playground::project;
fn rt() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("tokio rt")
}
fn open(undo: bool) -> (project::Project, Database, tempfile::TempDir) {
let dir = tempfile::tempdir().expect("create tempdir");
let project =
project::open_or_create(None, Some(dir.path())).expect("open or create project");
let persistence = Persistence::new(project.path().to_path_buf());
let db = Database::open_with_persistence_and_undo(project.db_path(), persistence, undo)
.expect("open db with persistence");
(project, db, dir)
}
/// Create `T (id int primary key, email text)` and an index on `email`.
fn make_t_with_index(db: &Database, r: &tokio::runtime::Runtime) -> String {
r.block_on(db.sql_create_table(
"T".to_string(),
vec![ColumnSpec::new("id", Type::Int), ColumnSpec::new("email", Type::Text)],
vec!["id".to_string()],
vec![],
vec![],
vec![],
false,
Some("create table T (id int primary key, email text)".to_string()),
))
.expect("create T");
let desc = r
.block_on(db.add_index(
Some("T_email_idx".to_string()),
"T".to_string(),
vec!["email".to_string()],
Some("add index as T_email_idx on T (email)".to_string()),
))
.expect("add index");
assert_eq!(desc.indexes.len(), 1, "index created");
"T_email_idx".to_string()
}
fn index_names(db: &Database, r: &tokio::runtime::Runtime) -> Vec<String> {
r.block_on(db.describe_table("T".to_string(), None))
.expect("describe")
.indexes
.into_iter()
.map(|i| i.name)
.collect()
}
#[test]
fn drop_index_removes_an_existing_index_and_shows_the_table() {
let (_p, db, _d) = open(false);
let r = rt();
let name = make_t_with_index(&db, &r);
let out = r
.block_on(db.sql_drop_index(name, false, Some("drop index T_email_idx".to_string())))
.expect("drop index");
// Dropped carries the de-indexed table's structure (auto-show).
match out {
DropIndexOutcome::Dropped(desc) => {
assert_eq!(desc.name, "T");
assert!(desc.indexes.is_empty(), "the index is gone from the structure");
}
DropIndexOutcome::Skipped => panic!("expected Dropped, got Skipped"),
}
assert!(index_names(&db, &r).is_empty());
}
#[test]
fn if_exists_on_an_absent_index_is_a_noop_and_journalled() {
let (_p, db, _d) = open(false);
let r = rt();
let line = "drop index if exists ghost_idx";
let out = r
.block_on(db.sql_drop_index("ghost_idx".to_string(), true, Some(line.to_string())))
.expect("IF EXISTS on an absent index succeeds as a no-op");
assert!(matches!(out, DropIndexOutcome::Skipped));
// The no-op is still journalled (ADR-0034), like the create-skip.
// ADR-0052: journaling moved to the dispatch layer; this test now
// asserts only the no-op `Skipped` outcome.
}
#[test]
fn plain_drop_of_an_absent_index_errors() {
let (_p, db, _d) = open(false);
let r = rt();
let res = r.block_on(db.sql_drop_index(
"ghost_idx".to_string(),
false,
Some("drop index ghost_idx".to_string()),
));
assert!(res.is_err(), "plain DROP INDEX on an absent index errors (no IF EXISTS)");
}
#[test]
fn drop_index_is_one_undo_step_and_restores_the_index() {
let (_p, db, _d) = open(true); // undo enabled
let r = rt();
let name = make_t_with_index(&db, &r);
r.block_on(db.sql_drop_index(name.clone(), false, Some("drop index T_email_idx".to_string())))
.expect("drop index");
assert!(index_names(&db, &r).is_empty());
// One undo brings the index back.
assert!(r.block_on(db.undo()).expect("undo").is_some(), "the drop was one undo step");
assert_eq!(index_names(&db, &r), vec![name], "undo restored the index");
}