sqlitegraph 2.2.2

Embedded graph database with full ACID transactions, HNSW vector search, dual backend support, and comprehensive graph algorithms library
Documentation
//! Snapshot Isolation Tests for GraphBackend
//!
//! These tests verify that concurrent transactions properly isolate reads
//! from uncommitted writes, ensuring ACID compliance.
//!
//! Phase 38-04: MVCC Filtering Implementation

use sqlitegraph::backend::{EdgeSpec, GraphBackend, NodeSpec};
use sqlitegraph::snapshot::SnapshotId;
use std::sync::Arc;

/// Test helper: Create a test node spec
fn test_node(name: &str, kind: &str) -> NodeSpec {
    NodeSpec {
        name: name.to_string(),
        kind: kind.to_string(),
        file_path: None,
        data: serde_json::json!({}),
    }
}

/// Test 1: Uncommitted writes should not be visible to other transactions
#[test]
fn test_uncommitted_writes_not_visible() {
    let temp_dir = tempfile::tempdir().unwrap();
    let backend = sqlitegraph::backend::native::NativeGraphBackend::create(
        temp_dir.path().join("test.graph"),
    )
    .unwrap();
    let backend = Arc::new(backend);

    // Get initial snapshot before any writes
    let initial_snapshot = SnapshotId::current();

    // Insert a node
    let node_id = backend.insert_node(test_node("node1", "test")).unwrap();

    // Get current snapshot after insert
    let after_insert_snapshot = SnapshotId::current();

    // Read with initial snapshot - should NOT see the new node
    let result = backend.get_node(initial_snapshot, node_id);
    println!("Initial snapshot read result: {:?}", result);

    // Read with current snapshot - should see the new node
    let result_current = backend.get_node(after_insert_snapshot, node_id);
    println!("Current snapshot read result: {:?}", result_current);
    assert!(
        result_current.is_ok(),
        "Current snapshot should see committed data"
    );
}

/// Test 2: BFS should respect snapshot isolation
#[test]
fn test_bfs_snapshot_isolation() {
    let temp_dir = tempfile::tempdir().unwrap();
    let backend = Arc::new(
        sqlitegraph::backend::native::NativeGraphBackend::create(
            temp_dir.path().join("test.graph"),
        )
        .unwrap(),
    );

    // Create a chain: A -> B -> C
    let node_a = backend.insert_node(test_node("A", "chain")).unwrap();
    let node_b = backend.insert_node(test_node("B", "chain")).unwrap();

    backend
        .insert_edge(EdgeSpec {
            from: node_a,
            to: node_b,
            edge_type: "links".to_string(),
            data: serde_json::json!({}),
        })
        .unwrap();

    let snapshot_before_c = SnapshotId::current();

    let node_c = backend.insert_node(test_node("C", "chain")).unwrap();
    backend
        .insert_edge(EdgeSpec {
            from: node_b,
            to: node_c,
            edge_type: "links".to_string(),
            data: serde_json::json!({}),
        })
        .unwrap();

    let result = backend.bfs(snapshot_before_c, node_a, 2);
    println!("BFS with old snapshot (depth 2 from A): {:?}", result);
}

/// Test 3: Shortest path should respect snapshot isolation
#[test]
fn test_shortest_path_snapshot_isolation() {
    let temp_dir = tempfile::tempdir().unwrap();
    let backend = Arc::new(
        sqlitegraph::backend::native::NativeGraphBackend::create(
            temp_dir.path().join("test.graph"),
        )
        .unwrap(),
    );

    let node_a = backend.insert_node(test_node("A", "path")).unwrap();
    let node_b = backend.insert_node(test_node("B", "path")).unwrap();

    backend
        .insert_edge(EdgeSpec {
            from: node_a,
            to: node_b,
            edge_type: "connects".to_string(),
            data: serde_json::json!({}),
        })
        .unwrap();

    let snapshot_before_c = SnapshotId::current();

    let node_c = backend.insert_node(test_node("C", "path")).unwrap();
    backend
        .insert_edge(EdgeSpec {
            from: node_b,
            to: node_c,
            edge_type: "connects".to_string(),
            data: serde_json::json!({}),
        })
        .unwrap();

    let result = backend.shortest_path(snapshot_before_c, node_a, node_c);
    println!("Shortest path with old snapshot (A to C): {:?}", result);
}