iridium-db 0.4.0

A high-performance vector-graph hybrid storage and indexing engine
use super::*;

fn open(base: &std::path::Path) -> StorageHandle {
    open_store(StorageConfig {
        buffer_pool_pages: 8,
        wal_dir: base.join("wal"),
        wal_segment_max_bytes: 1 << 20,
        manifest_path: base.join("ir.manifest"),
        sstable_dir: base.join("sst"),
    })
    .unwrap()
}

/// Tombstoned node must not appear in HNSW ANN results.
#[test]
fn tombstoned_node_excluded_from_ann_results() {
    let base = temp_dir("tombstone_ann_exclusion");
    let mut handle = open(&base);

    put_full_node(&mut handle, 1, 1, &[]).unwrap();
    put_full_node(&mut handle, 2, 1, &[]).unwrap();

    let delta1 = encode_delta(
        1,
        2,
        &encode_vector_payload_f32(1, VectorMetric::Cosine, &[1.0, 0.0], false),
    );
    let delta2 = encode_delta(
        2,
        2,
        &encode_vector_payload_f32(1, VectorMetric::Cosine, &[0.9, 0.1_f32], false),
    );
    put_vector_delta(&mut handle, &delta1).unwrap();
    put_vector_delta(&mut handle, &delta2).unwrap();

    put_tombstone(&mut handle, 2, 3).unwrap();
    assert!(node_is_tombstoned(&handle, 2));

    // Query for a vector close to node 2; tombstone must filter it out.
    let results = hnsw_search_in_space(&handle, 1, &[0.9, 0.1_f32], 10);
    let result_ids: Vec<u64> = results.into_iter().map(|(id, _)| id).collect();

    assert!(
        !result_ids.contains(&2),
        "tombstoned node 2 must not appear in ANN results"
    );
    assert!(
        result_ids.contains(&1),
        "non-tombstoned node 1 should appear in ANN results"
    );
}

/// WAL replay must reconstruct the tombstone set.
#[test]
fn wal_replay_reconstructs_tombstone_set() {
    let base = temp_dir("tombstone_wal_replay");
    {
        let mut handle = open(&base);
        put_full_node(&mut handle, 10, 1, &[]).unwrap();
        put_tombstone(&mut handle, 10, 2).unwrap();
        put_full_node(&mut handle, 11, 1, &[]).unwrap();
        // node 11 is NOT tombstoned.
    }

    let mut handle = open(&base);
    recover_from_wal(&mut handle).unwrap();

    assert!(
        node_is_tombstoned(&handle, 10),
        "node 10 should be tombstoned after WAL replay"
    );
    assert!(
        !node_is_tombstoned(&handle, 11),
        "node 11 was not tombstoned, must not appear in tombstone set"
    );
}

/// Non-tombstoned node must not be flagged.
#[test]
fn non_tombstoned_node_not_marked() {
    let base = temp_dir("tombstone_not_marked");
    let mut handle = open(&base);
    put_full_node(&mut handle, 99, 1, &[]).unwrap();
    assert!(!node_is_tombstoned(&handle, 99));
}

/// Unknown node must not be flagged.
#[test]
fn tombstone_flag_absent_for_unknown_node() {
    let base = temp_dir("tombstone_unknown");
    let handle = open(&base);
    assert!(!node_is_tombstoned(&handle, 12345));
}