sqlitex 0.4.3

An ergonomic sqlite library with compile time guarantees
Documentation
#[warn(unused)]
use sqlitex::sqlitex;

#[sqlitex]
pub struct NestedTxDao {
    create_table: sql!(
        "CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY NOT NULL, val TEXT NOT NULL)"
    ),
    insert: sql!("INSERT INTO test (id, val) VALUES (?, ?)"),
    count: sql!("SELECT COUNT(*) as count FROM test"),
    clear: sql!("DELETE FROM test"),
}

#[cfg(test)]
mod nested_tests {
    use std::sync::Arc;

    use super::*;

    fn setup() -> (NestedTxDao, Arc<sqlitex::Connection>) {
        let conn = sqlitex::Connection::open_memory().unwrap();
        let runtime_conn = conn.clone();
        let mut db = NestedTxDao::new(conn);
        db.create_table().unwrap();
        (db, runtime_conn)
    }

    #[test]
    fn test_macro_tx_inner_fails_outer_succeeds() -> Result<(), Box<dyn std::error::Error>> {
        let (mut db, _) = setup();

        db.transaction(|tx1| {
            tx1.insert(1, "Outer layer")?;

            let inner_result = tx1.transaction(|tx2| {
                tx2.insert(2, "Inner layer")?;
                tx2.insert(2, "Duplicate - will crash!")?;
                Ok(())
            });

            assert!(inner_result.is_err());
            Ok(())
        })?;

        let count = db.count()?;
        assert_eq!(count, 1);
        Ok(())
    }

    #[test]
    fn test_insert_bulk_inside_macro_tx() -> Result<(), Box<dyn std::error::Error>> {
        let (mut db, _) = setup();

        db.transaction(|tx| {
            tx.insert(1, "Singular insert")?;
            tx.insert_bulk(&[
                (2, "Batch item 1".to_string()),
                (3, "Batch item 2".to_string()),
            ])?;
            Ok(())
        })?;

        let count = db.count()?;
        assert_eq!(count, 3);
        Ok(())
    }

    #[test]
    fn test_runtime_inner_rollback_outer_commit() -> Result<(), Box<dyn std::error::Error>> {
        let (mut db, runtime_conn) = setup();

        runtime_conn.transaction(|conn1| {
            conn1.execute("INSERT INTO test (id, val) VALUES (1, 'Runtime Outer')")?;

            let inner_result = conn1.transaction(|conn2| {
                conn2.execute("INSERT INTO test (id, val) VALUES (2, 'Runtime Inner')")?;
                conn2.execute("INSERT INTO test (id, val) VALUES (2, 'Duplicate')")?;
                Ok(())
            });

            assert!(inner_result.is_err());
            Ok(())
        })?;

        let count = db.count()?;
        assert_eq!(count, 1);
        Ok(())
    }

    #[test]
    fn test_mixed_macro_and_runtime_tx() -> Result<(), Box<dyn std::error::Error>> {
        let (mut db, runtime_conn) = setup();

        db.transaction(|tx| {
            tx.insert(1, "Macro Outer")?;
            runtime_conn.transaction(|conn| {
                conn.execute("INSERT INTO test (id, val) VALUES (2, 'Runtime Inner')")?;
                Ok(())
            })?;
            Ok(())
        })?;

        let count = db.count()?;
        assert_eq!(count, 2);
        Ok(())
    }

    #[test]
    fn test_four_layer_deep_nesting() -> Result<(), Box<dyn std::error::Error>> {
        let (mut db, runtime_conn) = setup();

        db.transaction(|t1| {
            t1.insert(1, "L1")?;

            t1.transaction(|t2| {
                t2.insert(2, "L2")?;

                runtime_conn.transaction(|conn| {
                    conn.execute("INSERT INTO test (id, val) VALUES (3, 'L3')")?;
                    t2.insert_bulk(&[(4, "L4A".to_string()), (5, "L4B".to_string())])?;
                    Ok(())
                })?;
                Ok(())
            })?;
            Ok(())
        })?;

        let count = db.count()?;
        assert_eq!(count, 5);
        Ok(())
    }
}