use std::time::Instant;
use tempfile::TempDir;
use sqlitegraph::backend::native::v3::V3Backend;
use sqlitegraph::backend::{EdgeSpec, GraphBackend, NodeSpec};
fn main() {
println!("=== V3 Batch Mode Stress Tests ===\n");
println!("Test 1: Batch 20k nodes");
stress_test_nodes(20_000);
println!("\nTest 2: Batch 50k nodes (may fail due to BTree depth limit)");
stress_test_nodes_or_warn(50_000);
println!("\nTest 3: Batch 10k edges");
stress_test_edges(10_000);
println!("\nTest 4: Page allocation validation (10k nodes)");
test_page_allocation(10_000);
println!("\n=== All stress tests passed ===");
}
fn stress_test_nodes_or_warn(count: usize) {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("stress_nodes.db");
let backend = V3Backend::create(&db_path).unwrap();
let start = Instant::now();
let result = {
let mut batch = backend.begin_batch();
for i in 0..count {
if let Err(e) = batch.insert_node(NodeSpec {
kind: "Test".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
}) {
println!(" Warning: Failed at node {}: {}", i, e);
break;
}
if i > 0 && i % 10_000 == 0 {
println!(" Inserted {} nodes...", i);
}
}
batch.commit()
};
match result {
Ok(_) => {
let elapsed = start.elapsed();
let nodes_per_sec = count as f64 / elapsed.as_secs_f64();
println!(" Inserted {} nodes in {:?}", count, elapsed);
println!(" Throughput: {:.0} nodes/sec", nodes_per_sec);
}
Err(e) => {
println!(" Expected limitation: {}", e);
println!(" (BTree recursive split not yet implemented)");
}
}
}
fn stress_test_nodes(count: usize) {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("stress_nodes.db");
let backend = V3Backend::create(&db_path).unwrap();
let start = Instant::now();
{
let mut batch = backend.begin_batch();
for i in 0..count {
batch
.insert_node(NodeSpec {
kind: "Test".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.unwrap();
if i > 0 && i % 10_000 == 0 {
println!(" Inserted {} nodes...", i);
}
}
batch.commit().unwrap();
}
let elapsed = start.elapsed();
let nodes_per_sec = count as f64 / elapsed.as_secs_f64();
println!(" Inserted {} nodes in {:?}", count, elapsed);
println!(" Throughput: {:.0} nodes/sec", nodes_per_sec);
println!(
" Per-node: {:.4}ms",
elapsed.as_secs_f64() * 1000.0 / count as f64
);
}
fn stress_test_edges(count: usize) {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("stress_edges.db");
let backend = V3Backend::create(&db_path).unwrap();
let mut node_ids = Vec::with_capacity(count + 1);
{
let mut batch = backend.begin_batch();
for i in 0..=count {
let id = batch
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({}),
})
.unwrap();
node_ids.push(id);
}
batch.commit().unwrap();
}
println!(" Created {} nodes", node_ids.len());
let start = Instant::now();
{
let mut batch = backend.begin_batch();
for i in 0..count {
batch
.insert_edge(EdgeSpec {
from: node_ids[i],
to: node_ids[i + 1],
edge_type: "connects".to_string(),
data: serde_json::json!({}),
})
.unwrap();
if i > 0 && i % 1000 == 0 {
println!(" Inserted {} edges...", i);
}
}
batch.commit().unwrap();
}
let elapsed = start.elapsed();
println!(" Inserted {} edges in {:?}", count, elapsed);
println!(
" Per-edge: {:.4}ms",
elapsed.as_secs_f64() * 1000.0 / count as f64
);
}
fn test_page_allocation(count: usize) {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("page_alloc.db");
let backend = V3Backend::create(&db_path).unwrap();
let mut node_ids = Vec::with_capacity(count);
{
let mut batch = backend.begin_batch();
for i in 0..count {
let id = batch
.insert_node(NodeSpec {
kind: "Test".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"seq": i}),
})
.unwrap();
node_ids.push(id);
}
batch.commit().unwrap();
}
let unique_count = node_ids
.iter()
.collect::<std::collections::HashSet<_>>()
.len();
assert_eq!(unique_count, count, "Duplicate node IDs detected!");
println!(" ✓ No duplicate node IDs ({} unique)", unique_count);
let expected_ids: Vec<i64> = (1..=count as i64).collect();
assert_eq!(node_ids, expected_ids, "Node IDs not sequential!");
println!(" ✓ Node IDs are sequential (1..={})", count);
use sqlitegraph::snapshot::SnapshotId;
let snapshot = SnapshotId::current();
let mut read_count = 0;
let mut errors = 0;
for (i, id) in node_ids.iter().enumerate() {
match backend.get_node(snapshot, *id) {
Ok(_) => read_count += 1,
Err(e) => {
errors += 1;
if i < 5 {
println!(" Error reading node {}: {}", id, e);
}
}
}
}
println!(
" Read back {}/{} nodes ({} errors)",
read_count, count, errors
);
assert_eq!(read_count, count, "Could not read back all nodes!");
println!(" ✓ All {} nodes readable after batch commit", read_count);
}