//! # SledStorage transaction tests
//!
//! REPEATABLE READ or SNAPSHOT ISOLATION is a transaction level which SledStorage provides.
//! Therefore, SledStorage is safe from READ UNCOMMITTED or READ COMMITTED concurrency conflict
//! scenarios, but not PHANTOM READ safe.

use {
    gluesql_core::{
        executor::FetchError,
        prelude::{Value::*, *},
        store::StoreMut,
        *,
    },
    gluesql_sled_storage::{self, SledStorage, State},
    std::{
        fs,
        time::{SystemTime, UNIX_EPOCH},
    },
    test_suite::*,
};

const PATH_PREFIX: &str = "tmp/gluesql";

macro_rules! exec {
    ($glue: ident $sql: literal) => {
        $glue.execute($sql).await.unwrap();
    };
}

macro_rules! test {
    ($glue: ident $sql: literal, $result: expr) => {
        let actual = $glue.execute($sql).await;
        assert_eq!(actual, $result.map(|payload| vec![payload]));
    };
}

macro_rules! test_idx {
    ($glue: ident $sql: literal, $idx: expr, $result: expr) => {
        let statements = $glue.plan($sql).await.unwrap();
        assert_eq!(statements.len(), 1);
        let first = &statements[0];
        test_indexes(first, Some($idx));
        assert_eq!($glue.execute_stmt(first).await, $result);
    };
}

#[tokio::test]
async fn sled_transaction_basic() {
    let path = &format!("{}/basic", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage = SledStorage::new(path).unwrap();
    let storage2 = storage.clone();
    let mut glue = Glue::new(storage);
    let mut glue2 = Glue::new(storage2);

    exec!(glue "BEGIN");
    test!(glue "BEGIN", Err(Error::StorageMsg("nested transaction is not supported".to_owned())));
    exec!(glue "COMMIT;");

    test!(glue "ROLLBACK", Err(Error::StorageMsg("no transaction to rollback".to_owned())));
    test!(glue "COMMIT", Err(Error::StorageMsg("no transaction to commit".to_owned())));

    exec!(glue "BEGIN;");
    exec!(glue "CREATE TABLE AcquireLock (id INTEGER);");
    test!(
        glue2 "CREATE TABLE MeTooTheLock (id INTEGER);",
        Err(Error::StorageMsg("database is locked".to_owned()))
    );

    exec!(glue2 "BEGIN;");
    exec!(glue2 "COMMIT;");
    test!(
        glue2 "SELECT * FROM AcquireLock;",
        Err(FetchError::TableNotFound("AcquireLock".to_owned()).into())
    );
}

#[tokio::test]
async fn sled_transaction_read_uncommitted() {
    let path = &format!("{}/read_uncommitted", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage1 = SledStorage::new(path).unwrap();
    let storage2 = storage1.clone();
    let mut glue1 = Glue::new(storage1);
    let mut glue2 = Glue::new(storage2);

    exec!(glue1 "BEGIN;");
    exec!(glue1 "CREATE TABLE Sample (id INTEGER);");
    exec!(glue1 "INSERT INTO Sample VALUES (30);");

    test!(
        glue2 "SELECT * FROM Sample",
        Err(FetchError::TableNotFound("Sample".to_owned()).into())
    );
    exec!(glue2 "BEGIN;");
    test!(
        glue2 "SELECT * FROM Sample",
        Err(FetchError::TableNotFound("Sample".to_owned()).into())
    );
    exec!(glue2 "COMMIT;");
    exec!(glue1 "COMMIT;");
}

#[tokio::test]
async fn sled_transaction_read_committed() {
    let path = &format!("{}/read_committed", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage1 = SledStorage::new(path).unwrap();
    let storage2 = storage1.clone();
    let mut glue1 = Glue::new(storage1);
    let mut glue2 = Glue::new(storage2);

    exec!(glue2 "BEGIN;");

    exec!(glue1 "BEGIN;");
    exec!(glue1 "CREATE TABLE Sample (id INTEGER);");
    exec!(glue1 "INSERT INTO Sample VALUES (30);");
    exec!(glue1 "COMMIT;");

    test!(
        glue2 "SELECT * FROM Sample",
        Err(FetchError::TableNotFound("Sample".to_owned()).into())
    );
    exec!(glue2 "COMMIT;");

    test!(
        glue2 "SELECT * FROM Sample",
        Ok(select!(id I64; 30))
    );
}

#[tokio::test]
async fn sled_transaction_schema_mut() {
    let path = &format!("{}/transaction_schema_mut", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage1 = SledStorage::new(path).unwrap();
    let storage2 = storage1.clone();
    let mut glue1 = Glue::new(storage1);
    let mut glue2 = Glue::new(storage2);

    exec!(glue1 "CREATE TABLE Sample (id INTEGER);");
    exec!(glue1 "INSERT INTO Sample VALUES (1);");

    exec!(glue2 "BEGIN;");
    exec!(glue1 "BEGIN;");
    exec!(glue1 "DROP TABLE Sample;");
    test!(
        glue1 "SELECT * FROM Sample;",
        Err(FetchError::TableNotFound("Sample".to_owned()).into())
    );
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 1))
    );

    exec!(glue1 "COMMIT;");
    exec!(glue1 "CREATE TABLE Sample (new_id INTEGER);");
    exec!(glue1 "INSERT INTO Sample VALUES (5);");
    test!(
        glue1 "SELECT * FROM Sample;",
        Ok(select!(new_id I64; 5))
    );
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 1))
    );
    exec!(glue2 "COMMIT;");
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(new_id I64; 5))
    );
}

#[tokio::test]
async fn sled_transaction_data_mut() {
    let path = &format!("{}/transaction_data_mut", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage1 = SledStorage::new(path).unwrap();
    let storage2 = storage1.clone();
    let mut glue1 = Glue::new(storage1);
    let mut glue2 = Glue::new(storage2);

    exec!(glue1 "CREATE TABLE Sample (id INTEGER);");
    exec!(glue1 "INSERT INTO Sample VALUES (1);");

    exec!(glue2 "BEGIN;");
    exec!(glue1 "BEGIN;");

    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 1))
    );

    exec!(glue1 "DELETE FROM Sample;");
    test!(
        glue1 "SELECT * FROM Sample;",
        Ok(Payload::Select {
            labels: vec!["id".to_owned()],
            rows: vec![],
        })
    );
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 1))
    );

    exec!(glue1 "INSERT INTO Sample VALUES (3), (5);");
    test!(
        glue1 "SELECT * FROM Sample;",
        Ok(select!(id I64; 3; 5))
    );
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 1))
    );

    exec!(glue1 "UPDATE Sample SET id = id + 1;");
    test!(
        glue1 "SELECT * FROM Sample;",
        Ok(select!(id I64; 4; 6))
    );
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 1))
    );

    exec!(glue1 "COMMIT;");
    test!(
        glue1 "SELECT * FROM Sample;",
        Ok(select!(id I64; 4; 6))
    );
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 1))
    );

    exec!(glue2 "COMMIT;");
    test!(
        glue1 "SELECT * FROM Sample;",
        Ok(select!(id I64; 4; 6))
    );
    test!(
        glue2 "SELECT * FROM Sample;",
        Ok(select!(id I64; 4; 6))
    );
}

#[tokio::test]
async fn sled_transaction_index_mut() {
    use ast::IndexOperator::Eq;

    let path = &format!("{}/transaction_index_mut", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage1 = SledStorage::new(path).unwrap();
    let storage2 = storage1.clone();
    let mut glue1 = Glue::new(storage1);
    let mut glue2 = Glue::new(storage2);

    exec!(glue1 "CREATE TABLE Sample (id INTEGER);");
    exec!(glue1 "INSERT INTO Sample VALUES (1);");

    exec!(glue2 "BEGIN;");
    exec!(glue1 "BEGIN;");

    exec!(glue1 "CREATE INDEX idx_id ON Sample (id);");

    test_idx!(
        glue1 "SELECT * FROM Sample WHERE id = 1;",
        idx!(idx_id, Eq, "1"),
        Ok(select!(id I64; 1))
    );
    test_idx!(
        glue2 "SELECT * FROM Sample WHERE id = 1;",
        idx!(),
        Ok(select!(id I64; 1))
    );

    exec!(glue1 "COMMIT;");
    test_idx!(
        glue2 "SELECT * FROM Sample WHERE id = 1;",
        idx!(),
        Ok(select!(id I64; 1))
    );

    exec!(glue2 "COMMIT;");
    test_idx!(
        glue1 "SELECT * FROM Sample WHERE id = 1;",
        idx!(idx_id, Eq, "1"),
        Ok(select!(id I64; 1))
    );

    exec!(glue2 "BEGIN;");
    exec!(glue1 "BEGIN;");

    exec!(glue1 "DROP INDEX Sample.idx_id;");

    test_idx!(
        glue2 "SELECT * FROM Sample WHERE id = 1;",
        idx!(idx_id, Eq, "1"),
        Ok(select!(id I64; 1))
    );
    test_idx!(
        glue1 "SELECT * FROM Sample WHERE id = 1;",
        idx!(),
        Ok(select!(id I64; 1))
    );

    exec!(glue1 "COMMIT;");
    test_idx!(
        glue2 "SELECT * FROM Sample WHERE id = 1;",
        idx!(idx_id, Eq, "1"),
        Ok(select!(id I64; 1))
    );

    exec!(glue2 "COMMIT;");
    test_idx!(
        glue1 "SELECT * FROM Sample WHERE id = 1;",
        idx!(),
        Ok(select!(id I64; 1))
    );
    test_idx!(
        glue2 "SELECT * FROM Sample WHERE id = 1;",
        idx!(),
        Ok(select!(id I64; 1))
    );
}

#[tokio::test]
async fn sled_transaction_gc() {
    let path = &format!("{}/transaction_gc", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage1 = SledStorage::new(path).unwrap();
    let storage2 = storage1.clone();
    let tree = storage1.clone().tree;

    let mut glue1 = Glue::new(storage1);
    let mut glue2 = Glue::new(storage2);

    macro_rules! assert_some {
        () => {
            assert!(tree.scan_prefix("temp_").next().is_some());
        };
    }
    macro_rules! assert_none {
        () => {
            assert!(tree.scan_prefix("temp_").next().is_none());
        };
    }

    // COMMIT runs GC and all temp_ data must be removed.
    exec!(glue1 "BEGIN;");
    exec!(glue1 "CREATE TABLE Garlic (id INTEGER);");
    assert_some!();
    exec!(glue1 "CREATE INDEX idx_id ON Garlic (id);");
    exec!(glue1 "INSERT INTO Garlic VALUES (1), (2);");
    exec!(glue1 "CREATE INDEX idx_gc ON Garlic (id + 2);");
    exec!(glue1 "ALTER TABLE Garlic ADD COLUMN num INTEGER NULL;");
    assert_some!();
    exec!(glue1 "COMMIT;");
    assert_none!();

    // Though glue1 COMMIT, glue2 transaction is still alive.
    // Until glue2 COMMIT, temp_ must survive.
    exec!(glue2 "BEGIN;");
    exec!(glue1 "BEGIN;");
    exec!(glue1 "CREATE TABLE NewGarlic (gar BOOLEAN);");
    exec!(glue1 "INSERT INTO NewGarlic VALUES (True);");
    assert_some!();
    exec!(glue1 "COMMIT;");
    assert_some!();
    exec!(glue2 "COMMIT;");
    assert_none!();

    // force change, txid -> 0
    exec!(glue1 "BEGIN;");

    glue1.storage.state = State::Transaction {
        txid: 0,
        created_at: SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_millis(),
        autocommit: false,
    };

    test!(glue1 "SELECT * FROM NewGarlic", Err(Error::StorageMsg("fetch failed - expired transaction has used (txid)".to_owned())));
    assert_eq!(
        glue1
            .storage
            .insert_data("NewGarlic", vec![])
            .await
            .unwrap_err(),
        Error::StorageMsg("acquire failed - expired transaction has used (txid)".to_owned()),
    );
}

#[cfg(not(any(target_os = "macos", target_os = "ios")))]
mod timeout_tests {
    use {super::*, std::time::Duration};

    const TX_TIMEOUT: Option<u128> = Some(200);
    const TX_SLEEP_TICK: Duration = Duration::from_millis(201);

    fn sleep() {
        std::thread::sleep(TX_SLEEP_TICK);
    }

    #[tokio::test]
    async fn sled_transaction_timeout_store() {
        let path = &format!("{}/transaction_timeout_store", PATH_PREFIX);
        fs::remove_dir_all(path).unwrap_or(());

        let mut storage1 = SledStorage::new(path).unwrap();
        storage1.set_transaction_timeout(TX_TIMEOUT);
        let storage2 = storage1.clone();

        let mut glue1 = Glue::new(storage1);
        let mut glue2 = Glue::new(storage2);

        exec!(glue1 "BEGIN;");

        // glue1 acquires lock
        exec!(glue1 "CREATE TABLE TxGarlic (id INTEGER);");

        // glue1 lock gets expired due to the timeout
        sleep();

        // timeout errors
        test!(glue1 "COMMIT;", Err(Error::StorageMsg("fetch failed - expired transaction has used (timeout)".to_owned())));
        test!(glue1 "SELECT * FROM TxGarlic", Err(Error::StorageMsg("fetch failed - expired transaction has used (timeout)".to_owned())));
        assert_eq!(
            glue1
                .storage
                .clone()
                .insert_data("TxGarlic", vec![])
                .await
                .unwrap_err(),
            Error::StorageMsg("acquire failed - expired transaction has used (timeout)".to_owned()),
        );

        exec!(glue2 "BEGIN;");
        exec!(glue2 "CREATE TABLE RealGarlic (id INTEGER);");
        exec!(glue1 "ROLLBACK;");
        exec!(glue2 "ROLLBACK;");

        // glue2 lock gets expired
        sleep();

        // glue1 must succeed to create tables: TxGarlic & RealGarlic
        exec!(glue1 "CREATE TABLE TxGarlic (id2 INTEGER);");
        exec!(glue1 "CREATE TABLE RealGarlic (id2 INTEGER);");

        exec!(glue1 "BEGIN;");
        exec!(glue1 "INSERT INTO TxGarlic VALUES (10);");
        sleep();
        exec!(glue2 "INSERT INTO TxGarlic VALUES (20);");
        test!(glue1 "COMMIT;", Err(Error::StorageMsg("fetch failed - expired transaction has used (timeout)".to_owned())));
        exec!(glue1 "ROLLBACK;");

        // glue1 lock has expired, so TxGarlic table has only a single row (20)
        test!(
            glue1 "SELECT * FROM TxGarlic;",
            Ok(select!(id2 I64; 20))
        );

        exec!(glue1 "BEGIN;");
        exec!(glue1 "UPDATE TxGarlic SET id2 = id2 * 2;");
        test!(
            glue1 "SELECT * FROM TxGarlic;",
            Ok(select!(id2 I64; 40))
        );

        // glue1 lock gets expired
        sleep();

        // glue1 tx must rollback
        test!(
            glue2 "SELECT * FROM TxGarlic;",
            Ok(select!(id2 I64; 20))
        );

        exec!(glue1 "ROLLBACK;");
        test!(
            glue1 "SELECT * FROM TxGarlic;",
            Ok(select!(id2 I64; 20))
        );
        test!(
            glue2 "SELECT * FROM TxGarlic;",
            Ok(select!(id2 I64; 20))
        );

        // UPDATE
        exec!(glue1 "BEGIN;");
        exec!(glue1 "UPDATE TxGarlic SET id2 = id2 + 1;");
        sleep();
        exec!(glue2 "BEGIN;");
        exec!(glue2 "UPDATE TxGarlic SET id2 = id2 + 1;");
        exec!(glue2 "ROLLBACK;");
        exec!(glue1 "ROLLBACK;");

        // DELETE
        exec!(glue1 "BEGIN;");
        exec!(glue1 "DELETE FROM TxGarlic;");
        test!(glue1 "SELECT * FROM TxGarlic;", Ok(select!(id2)));
        sleep();
        test!(glue2 "SELECT * FROM TxGarlic;", Ok(select!(id2 I64; 20)));
        exec!(glue2 "BEGIN;");
        exec!(glue2 "DELETE FROM TxGarlic");
        exec!(glue1 "ROLLBACK;");
        exec!(glue2 "ROLLBACK;");
        test!(glue1 "SELECT * FROM TxGarlic;", Ok(select!(id2 I64; 20)));
        test!(glue2 "SELECT * FROM TxGarlic;", Ok(select!(id2 I64; 20)));

        // DROP TABLE
        exec!(glue2 "BEGIN;");
        exec!(glue2 "DROP TABLE TxGarlic;");
        sleep();
        test!(glue2 "COMMIT;", Err(Error::StorageMsg("fetch failed - expired transaction has used (timeout)".to_owned())));
        exec!(glue1 "DROP TABLE TxGarlic;");
        test!(
            glue1 "SELECT * FROM TxGarlic;",
            Err(FetchError::TableNotFound("TxGarlic".to_owned()).into())
        );
        exec!(glue2 "ROLLBACK;");
        test!(
            glue2 "SELECT * FROM TxGarlic;",
            Err(FetchError::TableNotFound("TxGarlic".to_owned()).into())
        );
    }

    #[tokio::test]
    async fn sled_transaction_timeout_alter() {
        let path = &format!("{}/transaction_timeout_alter", PATH_PREFIX);
        fs::remove_dir_all(path).unwrap_or(());

        let mut storage1 = SledStorage::new(path).unwrap();
        storage1.set_transaction_timeout(TX_TIMEOUT);
        let storage2 = storage1.clone();

        let mut glue1 = Glue::new(storage1);
        let mut glue2 = Glue::new(storage2);

        exec!(glue1 "CREATE TABLE TxAlter (id INTEGER, num INTEGER);");
        exec!(glue1 "INSERT INTO TxAlter VALUES (1, 100);");

        // DROP COLUMN
        exec!(glue1 "BEGIN;");
        exec!(glue1 "ALTER TABLE TxAlter DROP COLUMN num;");
        test!(glue1 "SELECT * FROM TxAlter;", Ok(select!(id I64; 1)));
        test!(glue2 "SELECT * FROM TxAlter;", Ok(select!(id | num I64 | I64; 1 100)));
        sleep();
        test!(glue2 "SELECT * FROM TxAlter;", Ok(select!(id | num I64 | I64; 1 100)));
        exec!(glue2 "BEGIN;");
        exec!(glue2 "ALTER TABLE TxAlter DROP COLUMN num;");
        exec!(glue2 "ROLLBACK;");
        test!(glue2 "SELECT * FROM TxAlter;", Ok(select!(id | num I64 | I64; 1 100)));

        // ADD COLUMN
        exec!(glue2 "BEGIN;");
        exec!(glue2 "ALTER TABLE TxAlter ADD COLUMN flag BOOLEAN DEFAULT TRUE;");
        test!(glue2 "SELECT * FROM TxAlter;", Ok(select!(id | num | flag I64 | I64 | Bool; 1 100 true)));

        exec!(glue1 "ROLLBACK;");
        test!(glue1 "SELECT * FROM TxAlter;", Ok(select!(id | num I64 | I64; 1 100)));
        sleep();
        test!(glue1 "SELECT * FROM TxAlter;", Ok(select!(id | num I64 | I64; 1 100)));
        exec!(glue2 "ROLLBACK;");
        test!(glue2 "SELECT * FROM TxAlter;", Ok(select!(id | num I64 | I64; 1 100)));

        // RENAME COLUMN
        exec!(glue1 "BEGIN;");
        exec!(glue1 "ALTER TABLE TxAlter RENAME COLUMN id TO jd;");
        test!(glue1 "SELECT * FROM TxAlter;", Ok(select!(jd | num I64 | I64; 1 100)));
        test!(glue2 "SELECT * FROM TxAlter;", Ok(select!(id | num I64 | I64; 1 100)));
        sleep();
        exec!(glue2 "BEGIN;");
        exec!(glue2 "ALTER TABLE TxAlter RENAME COLUMN id TO kd;");
        exec!(glue2 "COMMIT;");
        exec!(glue1 "ROLLBACK;");
        test!(glue1 "SELECT * FROM TxAlter;", Ok(select!(kd | num I64 | I64; 1 100)));
        test!(glue2 "SELECT * FROM TxAlter;", Ok(select!(kd | num I64 | I64; 1 100)));

        // RENAME TABLE
        exec!(glue2 "BEGIN;");
        exec!(glue2 "ALTER TABLE TxAlter RENAME TO TxAltericano;");
        test!(glue2 "SELECT * FROM TxAltericano;", Ok(select!(kd | num I64 | I64; 1 100)));
        test!(
            glue2 "SELECT * FROM TxAlter;",
            Err(FetchError::TableNotFound("TxAlter".to_owned()).into())
        );
        test!(glue1 "SELECT * FROM TxAlter;", Ok(select!(kd | num I64 | I64; 1 100)));
        test!(
            glue1 "SELECT * FROM TxAlterericano;",
            Err(FetchError::TableNotFound("TxAlterericano".to_owned()).into())
        );
        sleep();
        exec!(glue1 "ALTER TABLE TxAlter RENAME TO TxSoprano;");
        test!(glue1 "SELECT * FROM TxSoprano;", Ok(select!(kd | num I64 | I64; 1 100)));
        exec!(glue2 "ROLLBACK;");
        test!(glue2 "SELECT * FROM TxSoprano;", Ok(select!(kd | num I64 | I64; 1 100)));
    }

    #[tokio::test]
    async fn sled_transaction_timeout_index() {
        use crate::ast::IndexOperator::Eq;

        let path = &format!("{}/transaction_timeout_index", PATH_PREFIX);
        fs::remove_dir_all(path).unwrap_or(());

        let mut storage1 = SledStorage::new(path).unwrap();
        storage1.set_transaction_timeout(TX_TIMEOUT);
        let storage2 = storage1.clone();

        let mut glue1 = Glue::new(storage1);
        let mut glue2 = Glue::new(storage2);

        exec!(glue1 "CREATE TABLE TxIndex (id INTEGER);");
        exec!(glue1 "INSERT INTO TxIndex VALUES (1);");

        // CREATE INDEX
        exec!(glue1 "BEGIN;");
        exec!(glue1 "CREATE INDEX idx_id ON TxIndex (id);");
        test_idx!(
            glue1 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        sleep();
        exec!(glue2 "CREATE INDEX idx_id ON TxIndex (id);");
        test_idx!(
            glue2 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        exec!(glue1 "ROLLBACK;");
        test_idx!(
            glue1 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );

        // DROP INDEX
        exec!(glue1 "BEGIN;");
        exec!(glue1 "DROP INDEX TxIndex.idx_id;");
        test_idx!(
            glue2 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        sleep();
        test_idx!(
            glue2 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        exec!(glue1 "ROLLBACK;");
        test_idx!(
            glue1 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        test_idx!(
            glue2 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );

        // DROP AND DROP INDEX
        exec!(glue1 "BEGIN;");
        exec!(glue1 "DROP INDEX TxIndex.idx_id;");
        exec!(glue1 "CREATE INDEX idx_id ON TxIndex (id);");
        sleep();
        test_idx!(
            glue2 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        exec!(glue1 "ROLLBACK;");
        test_idx!(
            glue2 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        test_idx!(
            glue1 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );

        exec!(glue1 "BEGIN;");
        exec!(glue1 "DROP INDEX TxIndex.idx_id;");
        sleep();
        exec!(glue2 "DROP INDEX TxIndex.idx_id;");
        sleep();
        exec!(glue1 "ROLLBACK;");
        exec!(glue1 "CREATE INDEX idx_id ON TxIndex (id);");
        test_idx!(
            glue2 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
        test_idx!(
            glue1 "SELECT * FROM TxIndex WHERE id = 1;",
            idx!(idx_id, Eq, "1"),
            Ok(select!(id I64; 1))
        );
    }
}

#[tokio::test]
async fn sled_transaction_dictionary() {
    macro_rules! test_tables {
        ($glue: ident $( $table_name: literal )*) => {
            let expected = Payload::ShowVariable(PayloadVariable::Tables(
                vec![$( $table_name.to_owned() ),*]
            ));

            assert_eq!($glue.execute("SHOW TABLES").await, Ok(vec![expected]));
        };
    }

    let path = &format!("{}/dictionary", PATH_PREFIX);
    fs::remove_dir_all(path).unwrap_or(());

    let storage = SledStorage::new(path).unwrap();
    let mut glue1 = Glue::new(storage.clone());
    let mut glue2 = Glue::new(storage.clone());
    let mut glue3 = Glue::new(storage);

    exec!(glue2 "BEGIN");
    exec!(glue1 "BEGIN");

    exec!(glue1 "CREATE TABLE Foo (id INTEGER);");
    test_tables!(glue1 "Foo");
    test_tables!(glue2);
    test_tables!(glue3);

    exec!(glue1 "COMMIT");
    test_tables!(glue1 "Foo");
    test_tables!(glue2);
    test_tables!(glue3 "Foo");

    exec!(glue2 "CREATE TABLE Bar (id INTEGER);");
    exec!(glue2 "CREATE TABLE Qux (id INTEGER);");
    test_tables!(glue1 "Foo");
    test_tables!(glue2 "Bar" "Qux");
    test_tables!(glue3 "Foo");

    exec!(glue2 "ROLLBACK");
    test_tables!(glue1 "Foo");
    test_tables!(glue2 "Foo");
    test_tables!(glue3 "Foo");
}
