alopex-embedded 0.6.0

Embedded database interface for Alopex DB
use alopex_embedded::{Database, Error, TxnMode};
use alopex_sql::ExecutionResult;
use alopex_sql::SqlValue;

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_database_execute_sql_ddl() {
    let db = Database::new();
    let result = db
        .execute_sql("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);")
        .unwrap();
    assert!(matches!(result, ExecutionResult::Success));

    let result = db.execute_sql("SELECT id, name FROM users;").unwrap();
    match result {
        ExecutionResult::Query(q) => assert!(q.rows.is_empty()),
        other => panic!("expected query result, got {other:?}"),
    }
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_database_execute_sql_dml() {
    let db = Database::new();
    let result = db
        .execute_sql(
            r#"
            CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
            INSERT INTO users (id, name) VALUES (1, 'alice');
            "#,
        )
        .unwrap();
    assert!(matches!(result, ExecutionResult::RowsAffected(1)));

    let result = db
        .execute_sql("SELECT id, name FROM users ORDER BY id;")
        .unwrap();
    match result {
        ExecutionResult::Query(q) => {
            assert_eq!(q.rows.len(), 1);
            assert_eq!(q.rows[0][0], SqlValue::Integer(1));
            assert_eq!(q.rows[0][1], SqlValue::Text("alice".into()));
        }
        other => panic!("expected query result, got {other:?}"),
    }
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_database_execute_sql_query() {
    let db = Database::new();
    db.execute_sql(
        r#"
        CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
        INSERT INTO users (id, name) VALUES (1, 'alice'), (2, 'bob');
        "#,
    )
    .unwrap();

    let result = db
        .execute_sql("SELECT name FROM users WHERE id = 2;")
        .unwrap();
    match result {
        ExecutionResult::Query(q) => {
            assert_eq!(q.rows.len(), 1);
            assert_eq!(q.rows[0][0], SqlValue::Text("bob".into()));
        }
        other => panic!("expected query result, got {other:?}"),
    }
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_database_execute_sql_error() {
    let db = Database::new();
    let err = db
        .execute_sql("INSERT INTO missing (id) VALUES (1);")
        .unwrap_err();
    assert!(matches!(err, Error::Sql(_)));
    assert_eq!(err.sql_error_code(), Some("ALOPEX-C001"));
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_transaction_execute_sql_shares_kv_changes() {
    let db = Database::new();
    {
        let mut txn = db.begin(TxnMode::ReadWrite).unwrap();
        txn.put(b"custom", b"v1").unwrap();
        txn.execute_sql("CREATE TABLE t (id INTEGER PRIMARY KEY);")
            .unwrap();
        txn.commit().unwrap();
    }

    {
        let mut ro = db.begin(TxnMode::ReadOnly).unwrap();
        assert_eq!(ro.get(b"custom").unwrap(), Some(b"v1".to_vec()));
    }

    db.execute_sql("SELECT id FROM t;").unwrap();
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_transaction_rollback_discards_sql_changes() {
    let db = Database::new();
    {
        let mut txn = db.begin(TxnMode::ReadWrite).unwrap();
        txn.execute_sql("CREATE TABLE t (id INTEGER PRIMARY KEY);")
            .unwrap();
        txn.rollback().unwrap();
    }

    let err = db.execute_sql("SELECT id FROM t;").unwrap_err();
    assert_eq!(err.sql_error_code(), Some("ALOPEX-C001"));
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_readonly_txn_rejects_ddl() {
    let db = Database::new();
    let mut ro = db.begin(TxnMode::ReadOnly).unwrap();
    let err = ro
        .execute_sql("CREATE TABLE t (id INTEGER PRIMARY KEY);")
        .unwrap_err();
    assert_eq!(err.sql_error_code(), Some("ALOPEX-E002"));
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_readonly_txn_rejects_dml() {
    let db = Database::new();
    db.execute_sql("CREATE TABLE t (id INTEGER PRIMARY KEY);")
        .unwrap();

    let mut ro = db.begin(TxnMode::ReadOnly).unwrap();
    let err = ro
        .execute_sql("INSERT INTO t (id) VALUES (1);")
        .unwrap_err();
    assert_eq!(err.sql_error_code(), Some("ALOPEX-E002"));
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_readonly_txn_allows_select() {
    let db = Database::new();
    db.execute_sql(
        r#"
        CREATE TABLE t (id INTEGER PRIMARY KEY);
        INSERT INTO t (id) VALUES (1);
        "#,
    )
    .unwrap();

    let mut ro = db.begin(TxnMode::ReadOnly).unwrap();
    let result = ro.execute_sql("SELECT id FROM t;").unwrap();
    match result {
        ExecutionResult::Query(q) => {
            assert_eq!(q.rows.len(), 1);
            assert_eq!(q.rows[0][0], SqlValue::Integer(1));
        }
        other => panic!("expected query result, got {other:?}"),
    }
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_multiple_execute_sql_in_same_txn() {
    let db = Database::new();
    let mut txn = db.begin(TxnMode::ReadWrite).unwrap();

    txn.execute_sql("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);")
        .unwrap();
    txn.execute_sql("INSERT INTO t (id, name) VALUES (1, 'a'), (2, 'b');")
        .unwrap();
    let result = txn.execute_sql("SELECT name FROM t ORDER BY id;").unwrap();

    match result {
        ExecutionResult::Query(q) => {
            assert_eq!(q.rows.len(), 2);
            assert_eq!(q.rows[0][0], SqlValue::Text("a".into()));
            assert_eq!(q.rows[1][0], SqlValue::Text("b".into()));
        }
        other => panic!("expected query result, got {other:?}"),
    }

    txn.commit().unwrap();
}

#[cfg_attr(not(feature = "lane_ci"), ignore)]
#[test]
fn sql_integration_create_then_insert_in_same_txn() {
    let db = Database::new();
    {
        let mut txn = db.begin(TxnMode::ReadWrite).unwrap();
        txn.execute_sql("CREATE TABLE t (id INTEGER PRIMARY KEY);")
            .unwrap();
        txn.execute_sql("INSERT INTO t (id) VALUES (1);").unwrap();
        txn.commit().unwrap();
    }

    let result = db.execute_sql("SELECT id FROM t;").unwrap();
    match result {
        ExecutionResult::Query(q) => assert_eq!(q.rows.len(), 1),
        other => panic!("expected query result, got {other:?}"),
    }
}