use sqlitegraph::{EdgeSpec, GraphConfig, NodeSpec, open_graph};
use std::fs::File;
use std::io::Read;
const NODE_SLOT_SIZE: u64 = 4096;
const DEFAULT_NODE_DATA_START: u64 = 1024;
fn read_node_slot_version(
path: &std::path::Path,
node_id: i64,
) -> Result<u8, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let slot_offset = DEFAULT_NODE_DATA_START + ((node_id - 1) as u64 * NODE_SLOT_SIZE);
use std::io::Seek;
use std::io::SeekFrom;
file.seek(SeekFrom::Start(slot_offset))?;
let mut buffer = [0u8; 1];
file.read_exact(&mut buffer)?;
Ok(buffer[0])
}
fn print_node_slot_forensics(path: &std::path::Path, node_id: i64, label: &str) {
match read_node_slot_version(path, node_id) {
Ok(version) => {
let slot_offset = DEFAULT_NODE_DATA_START + ((node_id - 1) as u64 * NODE_SLOT_SIZE);
println!(
"{}: node_id={}, slot_offset=0x{:x}, version={}",
label, node_id, slot_offset, version
);
}
Err(e) => {
println!("{}: node_id={}, ERROR reading slot: {}", label, node_id, e);
}
}
}
#[test]
fn test_v2_bfs_style_node_uninitialized_regression() {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("test.db");
println!("Creating V2 native graph at: {:?}", db_path);
let graph =
open_graph(&db_path, &GraphConfig::native()).expect("Failed to create V2 native graph");
println!("STEP 1: Creating 300 nodes (crossing 256 boundary)...");
let mut node_ids = Vec::new();
for i in 1..=300 {
let node_id = graph
.insert_node(NodeSpec {
kind: "Node".to_string(),
name: format!("node_{}", i),
file_path: None,
data: serde_json::json!({"id": i}),
})
.expect(&format!("Failed to insert node {}", i));
node_ids.push(node_id);
if i >= 250 && i <= 260 {
println!("Created node {} -> node_id {}", i, node_id);
}
}
println!("STEP 2: Verifying node slots have version=2 on disk...");
let critical_nodes = [257, 300];
for &node_id in &critical_nodes {
print_node_slot_forensics(&db_path, node_id, "AFTER_NODE_CREATION");
let version = read_node_slot_version(&db_path, node_id)
.expect(&format!("Should be able to read node {} slot", node_id));
assert_eq!(
version, 2,
"Node {} slot should have version=2 immediately after creation, got version={}",
node_id, version
);
}
println!("STEP 3: Beginning edge insertion phase (like BFS benchmark)...");
for i in 0..299 {
let from_id = node_ids[i];
let to_id = node_ids[i + 1];
if (i >= 255 && i <= 257) || (i >= 298 && i <= 299) {
print_node_slot_forensics(&db_path, from_id, &format!("BEFORE_EDGE_{}_FROM", i));
print_node_slot_forensics(&db_path, to_id, &format!("BEFORE_EDGE_{}_TO", i));
}
match graph.insert_edge(EdgeSpec {
from: from_id,
to: to_id,
edge_type: "chain".to_string(),
data: serde_json::json!({"edge_index": i}),
}) {
Ok(_) => {
if i <= 5 || (i >= 255 && i <= 260) {
println!("Edge {}: {} -> {} SUCCESS", i, from_id, to_id);
}
}
Err(e) => {
println!("Edge {}: {} -> {} FAILED: {}", i, from_id, to_id, e);
if e.to_string().contains("uninitialized slot")
|| e.to_string().contains("version=0")
{
println!(
"CORRUPTION DETECTED at edge {} ({} -> {})",
i, from_id, to_id
);
print_node_slot_forensics(&db_path, from_id, "CORRUPTION_FROM_NODE");
print_node_slot_forensics(&db_path, to_id, "CORRUPTION_TO_NODE");
panic!(
"REPRODUCED: V2 uninitialized slot corruption detected at edge {}",
i
);
} else {
panic!("Unexpected error (not uninitialized slot): {}", e);
}
}
}
}
println!("STEP 4: Final verification of critical node slots...");
for &node_id in &critical_nodes {
print_node_slot_forensics(&db_path, node_id, "FINAL_VERIFICATION");
let version = read_node_slot_version(&db_path, node_id)
.expect(&format!("Should be able to read node {} slot", node_id));
assert_eq!(
version, 2,
"Node {} slot should still have version=2 after all operations, got version={}",
node_id, version
);
}
println!("SUCCESS: No corruption detected - all node slots maintain version=2");
}