cartel-sqlite 0.22.0

SQLite driver for the dope runtime
Documentation
use crate::common::plan;
use cartel_sqlite::runtime::{self, Runtime};
use cartel_sqlite::{Catalog as _, OpenOptions, Shape};
use common::{assert_reclaimed, assert_retirement, engine::layout::*};
use dope_core::driver::life::recovery::Policy;
use dope_test::fibers;
use std::sync::OnceLock;
use std::{mem, ops};

pub mod common;

static RUNTIME: OnceLock<Result<Runtime, runtime::Error>> = OnceLock::new();

#[test]
fn catalog_marker_identity() {
    use common::database::{A, AB};

    assert_eq!(
        [A::STATEMENTS[0].sql(), AB::STATEMENTS[0].sql()],
        ["SELECT 1", "SELECT 2"]
    );
}

#[test]
fn catalog_shape() {
    let shape = Shape::of::<LayoutDatabase>();
    assert_eq!(shape.statements, 4);
    assert_eq!(
        shape.max_sql_bytes,
        "INSERT INTO items (id) VALUES (?1)".len()
    );
    let reserved = (try_memory()
        .expect("SQLite memory")
        .reservation::<LayoutDatabase>())
    .expect("catalog reserved overflowed");
    assert!(reserved.statements.catalog == shape);
    assert_eq!(reserved.statements.items, 10);
    assert_eq!(reserved.statements.sql_bytes, shape.max_sql_bytes);
}

#[test]
fn bounded_fiber_state() -> Result<(), plan::Error> {
    let runtime = (*RUNTIME.get_or_init(|| {
        // SAFETY: no SQLite call can pass this binary's initializer before it returns.
        // Only Cartel uses installed SQLite; Core handles and allocations remain
        // exclusively owned by their originating worker.
        Runtime::install(unsafe { runtime::raw::Permit::exclusive() })
    }))
    .map_err(plan::Error::Runtime)?;
    let database = OpenOptions::new()
        .open_memory(runtime)
        .map_err(plan::Error::Client)?
        .commands(COMMANDS)
        .map_err(plan::Error::Client)?;
    let result = plan::Plan::<LayoutDatabase>::try_new(database, try_limits()?, try_memory()?)?
        .try_run(
            |driver, mut database| {
                let command = database
                    .query()
                    .run_no_rows(LayoutDatabase::INSERT_ITEM, (2_i64, ()));
                let cmd_size = mem::size_of_val(&command);
                assert!(cmd_size <= COMMAND_WORDS * mem::size_of::<usize>());
                fibers::TEST
                    .try_run(driver, command)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                let plain = database
                    .query()
                    .run_one::<i64, _, _>(LayoutDatabase::SELECT_ITEM, ());
                let row_size = mem::size_of_val(&plain);
                assert!(row_size <= PLAIN_WORDS * mem::size_of::<usize>());
                assert_eq!(
                    fibers::TEST
                        .try_run(driver, plain)
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?,
                    1
                );

                let first = database
                    .query()
                    .run_first::<i64, _, _>(LayoutDatabase::SELECT_ITEM, ());
                let one_size = mem::size_of_val(&first);
                assert!(one_size <= PLAIN_WORDS * mem::size_of::<usize>());
                assert_eq!(
                    fibers::TEST
                        .try_run(driver, first)
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?,
                    Some(1)
                );

                let borrowed = database
                    .query()
                    .run_one::<String, _, _>(LayoutDatabase::SELECT_TEXT, ());
                let ref_size = mem::size_of_val(&borrowed);
                assert!(ref_size <= ARENA_WORDS * mem::size_of::<usize>());
                assert_eq!(
                    fibers::TEST
                        .try_run(driver, borrowed)
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?,
                    "value"
                );

                let pair = database
                    .query()
                    .run_one::<(i64, String), _, _>(LayoutDatabase::SELECT_PAIR, ());
                let two_size = mem::size_of_val(&pair);
                assert!(two_size <= ARENA_WORDS * mem::size_of::<usize>());
                assert_eq!(
                    fibers::TEST
                        .try_run(driver, pair)
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?,
                    (7, "value")
                );

                let rows = database
                    .query()
                    .run_all::<i64, _, _>(LayoutDatabase::SELECT_ITEM, ());
                let all_size = mem::size_of_val(&rows);
                assert!(all_size <= COLLECT_WORDS * mem::size_of::<usize>());
                assert_eq!(
                    fibers::TEST
                        .try_run(driver, rows)
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?
                        .as_slice(),
                    &[1]
                );

                let views = database
                    .query()
                    .run_all::<String, _, _>(LayoutDatabase::SELECT_TEXT, ());
                let view_len = mem::size_of_val(&views);
                assert!(view_len <= COLLECT_WORDS * mem::size_of::<usize>());
                assert_eq!(
                    fibers::TEST
                        .try_run(driver, views)
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?
                        .as_slice(),
                    &["value"]
                );

                let scan = database
                    .query()
                    .stream::<i64, _, _>(LayoutDatabase::SELECT_ITEM, ())
                    .visit(|_| ops::ControlFlow::Continue(()));
                let scan_len = mem::size_of_val(&scan);
                assert!(scan_len <= SCAN_WORDS * mem::size_of::<usize>());
                fibers::TEST
                    .try_run(driver, scan)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                let tx = database.transaction();
                let tx_size = mem::size_of_val(&tx);
                assert!(tx_size <= BEGIN_WORDS * mem::size_of::<usize>());
                let mut tx = fibers::TEST
                    .try_run(driver, tx)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;
                assert!(mem::size_of_val(&tx) <= CAPABILITY_WORDS * mem::size_of::<usize>());

                let save = tx.savepoint();
                assert!(mem::size_of_val(&save) <= TRANSITION_WORDS * mem::size_of::<usize>());
                let mut save = fibers::TEST
                    .try_run(driver, save)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;
                assert!(mem::size_of_val(&save) <= SAVEPOINT_WORDS * mem::size_of::<usize>());

                let nested = save.savepoint();
                assert!(mem::size_of_val(&nested) <= TRANSITION_WORDS * mem::size_of::<usize>());
                let nested = fibers::TEST
                    .try_run(driver, nested)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                let release = nested.commit();
                assert!(
                    mem::size_of_val(&release) <= TRANSITION_WORDS * mem::size_of::<usize>(),
                    "savepoint commit: {} bytes, limit {}",
                    mem::size_of_val(&release),
                    TRANSITION_WORDS * mem::size_of::<usize>(),
                );
                fibers::TEST
                    .try_run(driver, release)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                let rollback = save.rollback();
                assert!(
                    mem::size_of_val(&rollback) <= TRANSITION_WORDS * mem::size_of::<usize>(),
                    "savepoint rollback: {} bytes, limit {}",
                    mem::size_of_val(&rollback),
                    TRANSITION_WORDS * mem::size_of::<usize>(),
                );
                fibers::TEST
                    .try_run(driver, rollback)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                let release = save.commit();
                assert!(
                    mem::size_of_val(&release) <= TRANSITION_WORDS * mem::size_of::<usize>(),
                    "savepoint commit: {} bytes, limit {}",
                    mem::size_of_val(&release),
                    TRANSITION_WORDS * mem::size_of::<usize>(),
                );
                fibers::TEST
                    .try_run(driver, release)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                let rollback = tx.rollback();
                assert!(mem::size_of_val(&rollback) <= TRANSITION_WORDS * mem::size_of::<usize>());
                fibers::TEST
                    .try_run(driver, rollback)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                let tx = fibers::TEST
                    .try_run(driver, database.transaction())
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;
                let commit = tx.commit();
                assert!(mem::size_of_val(&commit) <= TRANSITION_WORDS * mem::size_of::<usize>());
                fibers::TEST
                    .try_run(driver, commit)
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;

                {
                    let tx = fibers::TEST
                        .try_run(driver, database.transaction())
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?;
                    assert!(mem::size_of_val(&tx) <= CAPABILITY_WORDS * mem::size_of::<usize>());
                }

                let mut tx = fibers::TEST
                    .try_run(driver, database.transaction())
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;
                {
                    let save = fibers::TEST
                        .try_run(driver, tx.savepoint())
                        .map_err(plan::Error::Run)?
                        .map_err(plan::Error::Call)?;
                    assert!(mem::size_of_val(&save) <= SAVEPOINT_WORDS * mem::size_of::<usize>());
                }
                fibers::TEST
                    .try_run(driver, tx.rollback())
                    .map_err(plan::Error::Run)?
                    .map_err(plan::Error::Call)?;
                Ok(())
            },
            Policy::new(assert_reclaimed, assert_retirement),
            |result| result.expect("SQLite scope"),
        );
    result.expect("SQLite layout scope failed")
}