use std::fs;
use std::path::Path;
use sparrowdb::GraphDb;
use sparrowdb_catalog::catalog::Catalog;
use sparrowdb_storage::edge_store::{EdgeStore, RelTableId};
use sparrowdb_storage::node_store::{NodeStore, Value as StoreValue};
fn build_knows_graph(db_root: &Path) {
let mut cat = Catalog::open(db_root).expect("catalog");
let person_id = cat.create_label("Person").expect("Person label") as u32;
cat.create_rel_table(person_id as u16, person_id as u16, "KNOWS")
.expect("KNOWS rel table");
let mut store = NodeStore::open(db_root).expect("node store");
let alice = store
.create_node(person_id, &[(0, StoreValue::Int64(1))])
.expect("Alice");
let bob = store
.create_node(person_id, &[(0, StoreValue::Int64(2))])
.expect("Bob");
let carol = store
.create_node(person_id, &[(0, StoreValue::Int64(3))])
.expect("Carol");
let dave = store
.create_node(person_id, &[(0, StoreValue::Int64(4))])
.expect("Dave");
let eve = store
.create_node(person_id, &[(0, StoreValue::Int64(5))])
.expect("Eve");
let frank = store
.create_node(person_id, &[(0, StoreValue::Int64(6))])
.expect("Frank");
let alice_slot = alice.0 & 0xFFFF_FFFF;
let bob_slot = bob.0 & 0xFFFF_FFFF;
let carol_slot = carol.0 & 0xFFFF_FFFF;
let dave_slot = dave.0 & 0xFFFF_FFFF;
let eve_slot = eve.0 & 0xFFFF_FFFF;
let frank_slot = frank.0 & 0xFFFF_FFFF;
let rel = RelTableId(0);
let mut edge_store = EdgeStore::open(db_root, rel).expect("edge store");
use sparrowdb_common::NodeId;
edge_store
.create_edge(NodeId(alice_slot), rel, NodeId(bob_slot))
.expect("Alice→Bob");
edge_store
.create_edge(NodeId(carol_slot), rel, NodeId(dave_slot))
.expect("Carol→Dave");
edge_store
.create_edge(NodeId(eve_slot), rel, NodeId(frank_slot))
.expect("Eve→Frank");
}
fn assert_delta_empty(db_root: &Path) {
let store = EdgeStore::open(db_root, RelTableId(0)).expect("open edge store");
let records = store.read_delta().expect("read delta");
assert_eq!(
records.len(),
0,
"delta.log must be empty after checkpoint (found {} records)",
records.len()
);
}
fn assert_csr_base_exists(db_root: &Path) {
let fwd_path = db_root.join("edges").join("0").join("base.fwd.csr");
assert!(
fwd_path.exists(),
"base.fwd.csr must exist after checkpoint"
);
let bwd_path = db_root.join("edges").join("0").join("base.bwd.csr");
assert!(
bwd_path.exists(),
"base.bwd.csr must exist after checkpoint"
);
}
fn assert_neighbors_sorted(db_root: &Path) {
let store = EdgeStore::open(db_root, RelTableId(0)).expect("edge store");
let fwd = store.open_fwd().expect("open_fwd");
for node_id in 0..fwd.n_nodes() {
let neighbors = fwd.neighbors(node_id);
let mut sorted = neighbors.to_vec();
sorted.sort_unstable();
assert_eq!(
neighbors,
sorted.as_slice(),
"neighbor list for node {} must be sorted after OPTIMIZE",
node_id
);
}
}
#[test]
fn checkpoint_and_optimize_clean_base() {
let dir = tempfile::tempdir().unwrap();
let db_root = dir.path();
build_knows_graph(db_root);
{
let store = EdgeStore::open(db_root, RelTableId(0)).expect("edge store");
let records = store.read_delta().expect("read delta");
assert_eq!(records.len(), 3, "expect 3 delta records before checkpoint");
}
{
let db = GraphDb::open(db_root).expect("open db");
db.checkpoint().expect("checkpoint must succeed");
}
assert_delta_empty(db_root);
assert_csr_base_exists(db_root);
{
use sparrowdb_storage::wal::codec::{WalRecord, WalRecordKind};
let wal_dir = db_root.join("wal");
let seg_path = sparrowdb_storage::wal::writer::segment_path(&wal_dir, 0);
let data = fs::read(&seg_path).expect("WAL segment must exist");
let mut offset = 1usize;
let mut kinds = Vec::new();
while offset < data.len() {
match WalRecord::decode(&data[offset..]) {
Ok((rec, consumed)) => {
kinds.push(rec.kind);
offset += consumed;
}
Err(_) => break,
}
}
assert!(
kinds.contains(&WalRecordKind::CheckpointBegin),
"WAL must contain CheckpointBegin"
);
assert!(
kinds.contains(&WalRecordKind::CheckpointEnd),
"WAL must contain CheckpointEnd"
);
}
{
let db = GraphDb::open(db_root).expect("open db");
db.optimize().expect("optimize must succeed");
}
assert_neighbors_sorted(db_root);
assert_delta_empty(db_root);
{
let db = GraphDb::open(db_root).expect("reopen db");
let store = EdgeStore::open(db_root, RelTableId(0)).expect("edge store");
let fwd = store.open_fwd().expect("open_fwd after reopen");
assert!(
fwd.neighbors(0).contains(&1),
"Alice→Bob must survive checkpoint+optimize+reopen"
);
assert!(
fwd.neighbors(2).contains(&3),
"Carol→Dave must survive checkpoint+optimize+reopen"
);
assert!(
fwd.neighbors(4).contains(&5),
"Eve→Frank must survive checkpoint+optimize+reopen"
);
let _ = db;
}
{
let db = GraphDb::open(db_root).expect("open db for Cypher query");
db.checkpoint().expect("CHECKPOINT must succeed");
db.optimize().expect("OPTIMIZE must succeed");
let store = EdgeStore::open(db_root, RelTableId(0)).expect("edge store");
let fwd = store.open_fwd().expect("open_fwd after Cypher CHECKPOINT");
assert!(
fwd.neighbors(0).contains(&1),
"Alice→Bob after Cypher CHECKPOINT"
);
assert!(
fwd.neighbors(2).contains(&3),
"Carol→Dave after Cypher CHECKPOINT"
);
assert!(
fwd.neighbors(4).contains(&5),
"Eve→Frank after Cypher CHECKPOINT"
);
}
}
#[test]
fn writetx_checkpoint_routes_to_engine() {
let dir = tempfile::tempdir().unwrap();
let db_root = dir.path();
{
let rel = RelTableId(0);
let mut store = EdgeStore::open(db_root, rel).expect("edge store");
use sparrowdb_common::NodeId;
store
.create_edge(NodeId(0), rel, NodeId(1))
.expect("create edge");
}
let db = GraphDb::open(db_root).expect("open db");
db.checkpoint().expect("CHECKPOINT must succeed");
assert_delta_empty(db_root);
assert_csr_base_exists(db_root);
}
#[test]
fn writetx_optimize_sorts_neighbors() {
let dir = tempfile::tempdir().unwrap();
let db_root = dir.path();
{
let rel = RelTableId(0);
let mut store = EdgeStore::open(db_root, rel).expect("edge store");
use sparrowdb_common::NodeId;
store.create_edge(NodeId(0), rel, NodeId(5)).expect("0→5");
store.create_edge(NodeId(0), rel, NodeId(2)).expect("0→2");
store.create_edge(NodeId(0), rel, NodeId(8)).expect("0→8");
store.create_edge(NodeId(0), rel, NodeId(1)).expect("0→1");
}
let db = GraphDb::open(db_root).expect("open db");
db.optimize().expect("OPTIMIZE must succeed");
assert_neighbors_sorted(db_root);
}