use sqlitegraph::{
GraphEdgeCreate, GraphEntityCreate, SqliteGraph, SqliteGraphError, add_label, add_property,
cache_stats,
};
use std::time::{Duration, Instant};
fn node_count(graph: &SqliteGraph) -> Result<i64, SqliteGraphError> {
let ids = graph.list_entity_ids()?;
Ok(ids.len() as i64)
}
fn edge_count(graph: &SqliteGraph) -> Result<i64, SqliteGraphError> {
let entity_ids = graph.list_entity_ids()?;
let mut total_edges = 0;
for &id in &entity_ids {
let outgoing = graph.query().outgoing(id)?;
total_edges += outgoing.len();
}
Ok(total_edges as i64)
}
fn get_neighbors(graph: &SqliteGraph, id: i64) -> Result<Vec<i64>, SqliteGraphError> {
graph.query().outgoing(id)
}
fn get_incoming(graph: &SqliteGraph, id: i64) -> Result<Vec<i64>, SqliteGraphError> {
graph.query().incoming(id)
}
fn warm_cache(graph: &SqliteGraph) -> Result<(), SqliteGraphError> {
let entity_ids = graph.list_entity_ids()?;
for &id in &entity_ids {
let _ = graph.query().outgoing(id);
let _ = graph.query().incoming(id);
}
Ok(())
}
fn insert_entity(graph: &SqliteGraph, create: GraphEntityCreate) -> Result<i64, SqliteGraphError> {
let entity = sqlitegraph::GraphEntity {
id: 0, kind: create.kind,
name: create.name,
file_path: create.file_path,
data: create.data,
};
graph.insert_entity(&entity)
}
fn insert_edge(graph: &SqliteGraph, create: GraphEdgeCreate) -> Result<i64, SqliteGraphError> {
let edge = sqlitegraph::GraphEdge {
id: 0, from_id: create.from_id,
to_id: create.to_id,
edge_type: create.edge_type,
data: create.data,
};
graph.insert_edge(&edge)
}
fn create_test_graph() -> Result<SqliteGraph, SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let entity1 = GraphEntityCreate {
kind: "function".to_string(),
name: "main".to_string(),
file_path: Some("src/main.rs".to_string()),
data: serde_json::json!({"line": 10}),
};
let entity2 = GraphEntityCreate {
kind: "function".to_string(),
name: "helper".to_string(),
file_path: Some("src/helper.rs".to_string()),
data: serde_json::json!({"line": 5}),
};
let entity3 = GraphEntityCreate {
kind: "variable".to_string(),
name: "config".to_string(),
file_path: Some("src/config.rs".to_string()),
data: serde_json::json!({"type": "String"}),
};
let id1 = insert_entity(&graph, entity1)?;
let id2 = insert_entity(&graph, entity2)?;
let id3 = insert_entity(&graph, entity3)?;
let edge1 = GraphEdgeCreate {
from_id: id1,
to_id: id2,
edge_type: "calls".to_string(),
data: serde_json::json!({"line": 15}),
};
let edge2 = GraphEdgeCreate {
from_id: id1,
to_id: id3,
edge_type: "reads".to_string(),
data: serde_json::json!({"line": 12}),
};
insert_edge(&graph, edge1)?;
insert_edge(&graph, edge2)?;
Ok(graph)
}
fn add_more_data(graph: &SqliteGraph) -> Result<(i64, i64), SqliteGraphError> {
let entity4 = GraphEntityCreate {
kind: "function".to_string(),
name: "new_func".to_string(),
file_path: Some("src/new.rs".to_string()),
data: serde_json::json!({"line": 20}),
};
let entity5 = GraphEntityCreate {
kind: "class".to_string(),
name: "TestClass".to_string(),
file_path: Some("src/class.rs".to_string()),
data: serde_json::json!({"methods": 3}),
};
let id4 = insert_entity(&graph, entity4)?;
let id5 = insert_entity(&graph, entity5)?;
let edge3 = GraphEdgeCreate {
from_id: id4,
to_id: id5,
edge_type: "instantiates".to_string(),
data: serde_json::json!({"line": 25}),
};
insert_edge(&graph, edge3)?;
Ok((id4, id5))
}
#[test]
fn test_snapshot_isolation_single_threaded() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let initial_nodes = node_count(&graph)?;
let initial_edges = edge_count(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count() as i64, initial_nodes);
assert_eq!(snapshot.edge_count() as i64, initial_edges);
add_more_data(&graph)?;
assert!(node_count(&graph)? > initial_nodes);
assert!(edge_count(&graph)? > initial_edges);
assert_eq!(snapshot.node_count() as i64, initial_nodes);
assert_eq!(snapshot.edge_count() as i64, initial_edges);
Ok(())
}
#[test]
fn test_snapshot_neighbor_isolation() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let entity_ids = graph.list_entity_ids()?;
assert!(!entity_ids.is_empty());
let test_node = entity_ids[0];
warm_cache(&graph)?;
let original_neighbors = get_neighbors(&graph, test_node)?;
let snapshot = graph.acquire_snapshot()?;
let snapshot_neighbors = snapshot.get_outgoing(test_node);
assert_eq!(snapshot_neighbors, Some(&original_neighbors));
if entity_ids.len() >= 2 {
let new_edge = GraphEdgeCreate {
from_id: test_node,
to_id: entity_ids[1],
edge_type: "new_relation".to_string(),
data: serde_json::json!({"test": true}),
};
insert_edge(&graph, new_edge)?;
let updated_neighbors = get_neighbors(&graph, test_node)?;
assert!(updated_neighbors.len() > original_neighbors.len());
let snapshot_neighbors_after = snapshot.get_outgoing(test_node);
assert_eq!(snapshot_neighbors_after, Some(&original_neighbors));
}
Ok(())
}
#[test]
fn test_snapshot_incoming_neighbor_isolation() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let entity_ids = graph.list_entity_ids()?;
assert!(entity_ids.len() >= 2);
let target_node = entity_ids[1];
warm_cache(&graph)?;
let original_incoming = get_incoming(&graph, target_node)?;
let snapshot = graph.acquire_snapshot()?;
let snapshot_incoming = snapshot.get_incoming(target_node);
assert_eq!(snapshot_incoming, Some(&original_incoming));
let new_edge = GraphEdgeCreate {
from_id: entity_ids[0],
to_id: target_node,
edge_type: "new_incoming".to_string(),
data: serde_json::json!({}),
};
insert_edge(&graph, new_edge)?;
let updated_incoming = get_incoming(&graph, target_node)?;
assert!(updated_incoming.len() > original_incoming.len());
let snapshot_incoming_after = snapshot.get_incoming(target_node);
assert_eq!(snapshot_incoming_after, Some(&original_incoming));
Ok(())
}
#[test]
fn test_snapshot_creation_basic() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert!(snapshot.node_count() > 0);
assert!(snapshot.edge_count() > 0);
let created_at = snapshot.created_at();
let now = std::time::SystemTime::now();
assert!(created_at <= now);
Ok(())
}
#[test]
fn test_multiple_snapshots_same_state() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let initial_nodes = node_count(&graph)?;
let initial_edges = edge_count(&graph)?;
let snapshot1 = graph.acquire_snapshot()?;
let snapshot2 = graph.acquire_snapshot()?;
let snapshot3 = graph.acquire_snapshot()?;
assert_eq!(snapshot1.node_count(), initial_nodes as usize);
assert_eq!(snapshot2.node_count(), initial_nodes as usize);
assert_eq!(snapshot3.node_count(), initial_nodes as usize);
assert_eq!(snapshot1.edge_count(), initial_edges as usize);
assert_eq!(snapshot2.edge_count(), initial_edges as usize);
assert_eq!(snapshot3.edge_count(), initial_edges as usize);
Ok(())
}
#[test]
fn test_snapshot_ordering_consistency() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let entity_ids = graph.list_entity_ids()?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
let count1 = snapshot.node_count();
let count2 = snapshot.node_count();
let count3 = snapshot.node_count();
assert_eq!(count1, entity_ids.len());
assert_eq!(count2, entity_ids.len());
assert_eq!(count3, entity_ids.len());
Ok(())
}
#[test]
fn test_snapshot_memory_overhead() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let mut entity_ids = Vec::new();
for i in 0..100 {
let entity = GraphEntityCreate {
kind: "test".to_string(),
name: format!("entity_{}", i),
file_path: Some(format!("file_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
let id = insert_entity(&graph, entity)?;
entity_ids.push(id);
}
for i in 0..200 {
let from = entity_ids[i % entity_ids.len()];
let to = entity_ids[(i + 1) % entity_ids.len()];
let edge = GraphEdgeCreate {
from_id: from,
to_id: to,
edge_type: "connects".to_string(),
data: serde_json::json!({"pair": i}),
};
insert_edge(&graph, edge)?;
}
warm_cache(&graph)?;
let before_nodes = node_count(&graph)?;
let before_edges = edge_count(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count() as i64, before_nodes);
assert_eq!(snapshot.edge_count() as i64, before_edges);
Ok(())
}
#[test]
fn test_large_graph_snapshot() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let mut entity_ids = Vec::new();
for i in 0..1000 {
let entity = GraphEntityCreate {
kind: "test".to_string(),
name: format!("entity_{}", i),
file_path: Some(format!("file_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
let id = insert_entity(&graph, entity)?;
entity_ids.push(id);
}
for i in 0..2000 {
let from = entity_ids[i % entity_ids.len()];
let to = entity_ids[(i + 1) % entity_ids.len()];
let edge = GraphEdgeCreate {
from_id: from,
to_id: to,
edge_type: "connects".to_string(),
data: serde_json::json!({"pair": i}),
};
insert_edge(&graph, edge)?;
}
warm_cache(&graph)?;
let total_nodes = node_count(&graph)?;
let total_edges = edge_count(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count() as i64, total_nodes);
assert_eq!(snapshot.edge_count() as i64, total_edges);
Ok(())
}
#[test]
fn test_multiple_snapshots_memory() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot1 = graph.acquire_snapshot()?;
let snapshot2 = graph.acquire_snapshot()?;
let snapshot3 = graph.acquire_snapshot()?;
assert!(snapshot1.node_count() > 0);
assert!(snapshot2.node_count() > 0);
assert!(snapshot3.node_count() > 0);
Ok(())
}
#[test]
fn test_snapshot_acquisition_latency() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let _ = graph.acquire_snapshot()?;
let start = Instant::now();
let _snapshot = graph.acquire_snapshot()?;
let duration = start.elapsed();
println!("Snapshot acquisition latency: {:?}", duration);
assert!(duration < Duration::from_secs(1));
Ok(())
}
#[test]
fn test_snapshot_clone_performance() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
let start = Instant::now();
for _ in 0..1000 {
let _clone = snapshot.state().clone();
}
let duration = start.elapsed();
println!("1000 snapshot clones: {:?}", duration);
assert!(duration < Duration::from_millis(100));
Ok(())
}
#[test]
fn test_multiple_snapshot_overhead() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let start = Instant::now();
let mut snapshots = Vec::new();
for _ in 0..100 {
snapshots.push(graph.acquire_snapshot()?);
}
let duration = start.elapsed();
println!("100 snapshots created in: {:?}", duration);
for snapshot in snapshots {
assert!(snapshot.node_count() > 0);
}
Ok(())
}
#[test]
fn test_snapshot_with_sqlite_backend() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let entity = GraphEntityCreate {
kind: "test".to_string(),
name: "test_entity".to_string(),
file_path: Some("test.rs".to_string()),
data: serde_json::json!({}),
};
let entity_id = insert_entity(&graph, entity)?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert!(snapshot.contains_node(entity_id));
assert_eq!(snapshot.node_count(), 1);
Ok(())
}
#[test]
fn test_snapshot_with_labels_and_properties() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let entity_ids = graph.list_entity_ids()?;
let test_node = entity_ids[0];
add_label(&graph, test_node, "test_label")?;
add_property(&graph, test_node, "test_key", "test_value")?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert!(snapshot.contains_node(test_node));
Ok(())
}
#[test]
fn test_empty_graph_snapshot() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count(), 0);
assert_eq!(snapshot.edge_count(), 0);
let entity = GraphEntityCreate {
kind: "first".to_string(),
name: "first".to_string(),
file_path: Some("first.rs".to_string()),
data: serde_json::json!({}),
};
insert_entity(&graph, entity)?;
assert_eq!(snapshot.node_count(), 0);
assert_eq!(snapshot.edge_count(), 0);
Ok(())
}
#[test]
fn test_single_node_snapshot() -> Result<(), SqliteGraphError> {
let graph = SqliteGraph::open_in_memory()?;
let entity = GraphEntityCreate {
kind: "single".to_string(),
name: "single".to_string(),
file_path: Some("single.rs".to_string()),
data: serde_json::json!({}),
};
let entity_id = insert_entity(&graph, entity)?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert_eq!(snapshot.node_count(), 1);
assert_eq!(snapshot.edge_count(), 0);
let neighbors = snapshot.get_outgoing(entity_id);
assert_eq!(neighbors, Some(&vec![]));
Ok(())
}
#[test]
fn test_snapshot_with_deleted_entities() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let initial_nodes = node_count(&graph)?;
let snapshot = graph.acquire_snapshot()?;
let entity_ids = graph.list_entity_ids()?;
if !entity_ids.is_empty() {
graph.delete_entity(entity_ids[0])?;
}
assert!(node_count(&graph)? < initial_nodes);
assert_eq!(snapshot.node_count(), initial_nodes as usize);
Ok(())
}
#[test]
fn test_snapshot_consistency_during_modifications() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let entity_ids = graph.list_entity_ids();
let entity_ids = match entity_ids {
Ok(ids) if !ids.is_empty() => ids,
_ => return Ok(()), };
warm_cache(&graph)?;
let initial_neighbors = get_neighbors(&graph, entity_ids[0])?;
let snapshot = graph.acquire_snapshot()?;
for i in 0..10 {
let new_entity = GraphEntityCreate {
kind: "temp".to_string(),
name: format!("temp_{}", i),
file_path: Some(format!("temp_{}.rs", i)),
data: serde_json::json!({"index": i}),
};
let new_id = insert_entity(&graph, new_entity)?;
let new_edge = GraphEdgeCreate {
from_id: entity_ids[0],
to_id: new_id,
edge_type: "temp_relation".to_string(),
data: serde_json::json!({"temp": true}),
};
insert_edge(&graph, new_edge)?;
}
let snapshot_neighbors = snapshot.get_outgoing(entity_ids[0]);
assert_eq!(snapshot_neighbors, Some(&initial_neighbors));
Ok(())
}
#[test]
fn test_cache_independence() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let entity_ids = graph.list_entity_ids()?;
if !entity_ids.is_empty() {
get_neighbors(&graph, entity_ids[0])?;
get_incoming(&graph, entity_ids[0])?;
}
let initial_cache_stats = cache_stats(&graph);
let snapshot = graph.acquire_snapshot()?;
if !entity_ids.is_empty() {
let _ = snapshot.get_outgoing(entity_ids[0]);
}
if !entity_ids.is_empty() {
let new_entity = GraphEntityCreate {
kind: "cache_test".to_string(),
name: "cache_test".to_string(),
file_path: Some("cache_test.rs".to_string()),
data: serde_json::json!({}),
};
let new_id = insert_entity(&graph, new_entity)?;
let new_edge = GraphEdgeCreate {
from_id: entity_ids[0],
to_id: new_id,
edge_type: "cache_test_relation".to_string(),
data: serde_json::json!({}),
};
insert_edge(&graph, new_edge)?;
}
let final_cache_stats = cache_stats(&graph);
println!("Initial cache stats: {:?}", initial_cache_stats);
println!("Final cache stats: {:?}", final_cache_stats);
Ok(())
}
#[test]
fn test_snapshot_read_only_enforcement() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
warm_cache(&graph)?;
let snapshot = graph.acquire_snapshot()?;
assert!(snapshot.node_count() > 0);
Ok(())
}
#[test]
fn test_repeatable_snapshot_results() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let snapshot1 = graph.acquire_snapshot()?;
let snapshot2 = graph.acquire_snapshot()?;
assert_eq!(snapshot1.node_count(), snapshot2.node_count());
assert_eq!(snapshot1.edge_count(), snapshot2.edge_count());
let entity_ids = graph.list_entity_ids()?;
if !entity_ids.is_empty() {
let neighbors1 = snapshot1.get_outgoing(entity_ids[0]);
let neighbors2 = snapshot2.get_outgoing(entity_ids[0]);
assert_eq!(neighbors1, neighbors2);
}
Ok(())
}
#[test]
fn test_deterministic_query_results() -> Result<(), SqliteGraphError> {
let graph = create_test_graph()?;
let snapshot = graph.acquire_snapshot()?;
let count1 = snapshot.node_count();
let count2 = snapshot.node_count();
let count3 = snapshot.node_count();
assert_eq!(count1, count2);
assert_eq!(count2, count3);
Ok(())
}