modelvault-core 0.16.0

Core engine for ModelVault — application-focused embedded storage with model schemas, validation, and migrations.
Documentation
use std::borrow::Cow;
use std::collections::BTreeMap;

use modelvault_core::schema::{FieldDef, FieldPath, IndexDef, IndexKind, Type};
use modelvault_core::{Database, RowValue, ScalarValue};

fn path_field(name: &str, ty: Type) -> FieldDef {
    FieldDef {
        path: FieldPath(vec![Cow::Owned(name.to_string())]),
        ty,
        constraints: vec![],
    }
}

#[test]
fn compact_to_preserves_rows_and_indexes() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("src.modelvault");
    let compacted = dir.path().join("dst.modelvault");

    let mut db = Database::open(&path).unwrap();
    let fields = vec![
        path_field("id", Type::Int64),
        path_field("tag", Type::String),
    ];
    let indexes = vec![IndexDef {
        name: "tag_idx".to_string(),
        path: FieldPath(vec![Cow::Owned("tag".to_string())]),
        kind: IndexKind::NonUnique,
    }];
    let (cid, _v) = db
        .register_collection_with_indexes("t", fields, indexes, "id")
        .unwrap();

    for (id, tag) in [(1i64, "a"), (2, "b"), (3, "a")] {
        let mut row = BTreeMap::new();
        row.insert("id".to_string(), RowValue::Int64(id));
        row.insert("tag".to_string(), RowValue::String(tag.to_string()));
        db.insert(cid, row).unwrap();
    }

    // Ensure index lookup works pre-compaction.
    let q = modelvault_core::query::Query {
        collection: cid,
        predicate: Some(modelvault_core::query::Predicate::Eq {
            path: FieldPath(vec![Cow::Owned("tag".to_string())]),
            value: ScalarValue::String("a".to_string()),
        }),
        limit: None,
        order_by: None,
    };
    assert_eq!(db.query(&q).unwrap().len(), 2);

    db.compact_to(&compacted).unwrap();

    let db2 = Database::open(&compacted).unwrap();
    assert_eq!(db2.query(&q).unwrap().len(), 2);
    let got = db2.get(cid, &ScalarValue::Int64(2)).unwrap().unwrap();
    assert_eq!(got.get("tag"), Some(&RowValue::String("b".to_string())));
}

#[test]
fn compact_in_place_roundtrips() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("inplace.modelvault");

    let mut db = Database::open(&path).unwrap();
    let fields = vec![path_field("id", Type::Int64), path_field("x", Type::Int64)];
    db.register_collection("t", fields, "id").unwrap();
    let cid = modelvault_core::CollectionId(1);
    db.insert(
        cid,
        BTreeMap::from([
            ("id".to_string(), RowValue::Int64(1)),
            ("x".to_string(), RowValue::Int64(10)),
        ]),
    )
    .unwrap();

    db.compact_in_place().unwrap();
    let row = db.get(cid, &ScalarValue::Int64(1)).unwrap().unwrap();
    assert_eq!(row.get("x"), Some(&RowValue::Int64(10)));
}