sim-kernel 0.1.0

SIM workspace package for sim kernel.
Documentation
use crate::{Error, Expr, Symbol};

use super::{CatalogRow, CatalogStore, CatalogTableSpec, CatalogTx, CatalogWritePolicy};

fn sym(name: &str) -> Symbol {
    Symbol::new(name)
}

fn required_field() -> Symbol {
    sym("required")
}

fn spec(name: &str, policy: CatalogWritePolicy) -> CatalogTableSpec {
    CatalogTableSpec::new(sym(name), policy).with_required_fields(vec![required_field()])
}

fn row(table: &Symbol, key: &str, value: bool) -> CatalogRow {
    CatalogRow::new(table.clone(), sym(key)).with_data(required_field(), Expr::Bool(value))
}

fn store_with_mutable_table(name: &str) -> CatalogStore {
    let mut store = CatalogStore::new();
    store
        .install_table(spec(name, CatalogWritePolicy::Mutable))
        .unwrap();
    store
}

#[test]
fn overlay_success_commits_rows_sequences_and_journal() {
    let table = sym("overlay-success");
    let key = sym("row");
    let sequence = sym("registry-seq/test");
    let mut store = store_with_mutable_table("overlay-success");

    store
        .with_overlay(|store| {
            let mut tx = CatalogTx::new();
            tx.put_row(row(&table, "row", true));
            tx.bump_sequence(sequence.clone(), 3);
            tx.commit(store)?;

            assert_eq!(
                store.row(&table, &key).unwrap().data.get(&required_field()),
                Some(&Expr::Bool(true))
            );
            assert_eq!(store.sequence(&sequence), Some(3));
            assert_eq!(store.journal().len(), 2);
            Ok(())
        })
        .unwrap();

    assert_eq!(store.epoch(), 1);
    assert_eq!(
        store.row(&table, &key).unwrap().data.get(&required_field()),
        Some(&Expr::Bool(true))
    );
    assert_eq!(store.sequence(&sequence), Some(3));
    assert_eq!(store.journal().len(), 2);
}

#[test]
fn overlay_error_rolls_back_rows_sequences_and_journal() {
    let table = sym("overlay-rollback");
    let key = sym("row");
    let sequence = sym("registry-seq/test");
    let mut store = store_with_mutable_table("overlay-rollback");

    let err = store
        .with_overlay(|store| {
            let mut tx = CatalogTx::new();
            tx.put_row(row(&table, "row", true));
            tx.bump_sequence(sequence.clone(), 9);
            tx.commit(store)?;

            assert!(store.row(&table, &key).is_some());
            assert_eq!(store.sequence(&sequence), Some(9));
            Err::<(), Error>(Error::Eval("rollback overlay".to_owned()))
        })
        .unwrap_err();

    assert!(matches!(err, Error::Eval(message) if message == "rollback overlay"));
    assert!(store.row(&table, &key).is_none());
    assert_eq!(store.sequence(&sequence), None);
    assert_eq!(store.epoch(), 0);
    assert!(store.journal().is_empty());
}

#[test]
fn overlay_delete_is_local_until_commit() {
    let table = sym("overlay-delete");
    let key = sym("row");
    let mut store = store_with_mutable_table("overlay-delete");
    let mut seed = CatalogTx::new();
    seed.put_row(row(&table, "row", true));
    seed.commit(&mut store).unwrap();

    let err = store
        .with_overlay(|store| {
            let mut tx = CatalogTx::new();
            tx.delete_row(table.clone(), key.clone());
            tx.commit(store)?;
            assert!(store.row(&table, &key).is_none());
            Err::<(), Error>(Error::Eval("rollback delete".to_owned()))
        })
        .unwrap_err();

    assert!(matches!(err, Error::Eval(message) if message == "rollback delete"));
    assert!(store.row(&table, &key).is_some());

    store
        .with_overlay(|store| {
            let mut tx = CatalogTx::new();
            tx.delete_row(table.clone(), key.clone());
            tx.commit(store)?;
            Ok(())
        })
        .unwrap();
    assert!(store.row(&table, &key).is_none());
}