aedb 0.3.1

Embedded Rust storage engine with transactional commits, WAL durability, and snapshot-consistent reads
Documentation
use super::check_invariants;
use crate::catalog::schema::{ColumnDef, IndexDef, IndexType, TableSchema};
use crate::catalog::types::{ColumnType, Row, Value};
use crate::recovery::RecoveredState;
use crate::storage::encoded_key::EncodedKey;
use crate::storage::keyspace::{
    Keyspace, Namespace, NamespaceId, SecondaryIndex, SecondaryIndexStore, TableData,
};
use im::{HashMap as ImHashMap, OrdMap};
use std::collections::HashMap;

#[test]
fn check_invariants_detects_secondary_index_mismatch() {
    let namespace = "p::app".to_string();
    let pk = EncodedKey::from_values(&[Value::Integer(1)]);
    let row = Row::from_values(vec![Value::Integer(1), Value::Text("alice".into())]);

    let mut table = TableData {
        rows: OrdMap::new(),
        row_versions: OrdMap::new(),
        structural_version: 0,
        indexes: ImHashMap::new(),
        ..Default::default()
    };
    table.rows.insert(pk.clone(), row.clone().into());
    table.row_versions.insert(pk.clone(), 1);
    table.indexes.insert(
        "by_owner".into(),
        SecondaryIndex {
            store: SecondaryIndexStore::BTree(OrdMap::new()),
            columns_bitmask: 0,
            partial_filter: None,
            ..Default::default()
        },
    );

    let mut ns = Namespace {
        id: NamespaceId::Project(namespace.clone()),
        ..Namespace::default()
    };
    ns.tables.insert("users".into(), table);

    let mut keyspace = Keyspace::default();
    keyspace.insert_namespace(NamespaceId::Project(namespace.clone()), ns);

    let mut catalog = crate::catalog::Catalog::default();
    catalog.tables.insert(
        (namespace.clone(), "users".into()),
        TableSchema {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            owner_id: None,
            columns: vec![
                ColumnDef {
                    name: "id".into(),
                    col_type: ColumnType::Integer,
                    nullable: false,
                },
                ColumnDef {
                    name: "owner".into(),
                    col_type: ColumnType::Text,
                    nullable: false,
                },
            ],
            primary_key: vec!["id".into()],
            constraints: Vec::new(),
            foreign_keys: Vec::new(),
        },
    );
    catalog.indexes.insert(
        (namespace, "users".into(), "by_owner".into()),
        IndexDef {
            project_id: "p".into(),
            scope_id: "app".into(),
            table_name: "users".into(),
            index_name: "by_owner".into(),
            columns: vec!["owner".into()],
            index_type: IndexType::BTree,
            columns_bitmask: 0,
            partial_filter: None,
        },
    );

    let report = check_invariants(&RecoveredState {
        keyspace,
        catalog,
        current_seq: 1,
        idempotency: HashMap::new(),
    });

    assert!(!report.ok);
    assert!(
        report
            .violations
            .iter()
            .any(|violation| violation.contains("secondary index mismatch"))
    );
}