//! 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 `; 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 { 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. let log = std::fs::read_to_string(p.path().join("history.log")).expect("read history.log"); assert!(log.contains(line), "the skipped drop should be journalled; log:\n{log}"); } #[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"); }