use std::sync::Arc;
use grit_core::{GraphOp, Grit, ManualClock, Options};
use serde_json::json;
fn plan_of(sql: &str, params_count: usize) -> String {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("plan.db");
{
let clock = Arc::new(ManualClock::new(1_000));
let g = Grit::open(&path, Options::new("dev").clock(clock)).unwrap();
let a = g.new_id();
let b = g.new_id();
for (id, name) in [(a, "left"), (b, "right")] {
g.apply(GraphOp::AddNode {
id,
kind: "k".into(),
name: name.into(),
summary: String::new(),
attrs: json!({}),
group_id: String::new(),
})
.unwrap();
}
g.apply(GraphOp::AddEdge {
id: g.new_id(),
src: a,
dst: b,
rel: "R".into(),
fact: "left relates to right".into(),
attrs: json!({}),
group_id: String::new(),
valid_at: None,
invalid_at: None,
})
.unwrap();
}
let conn = rusqlite::Connection::open(&path).unwrap();
let mut stmt = conn.prepare(&format!("EXPLAIN QUERY PLAN {sql}")).unwrap();
let dummy: Vec<Box<dyn rusqlite::types::ToSql>> = (0..params_count)
.map(|_| Box::new(1i64) as Box<dyn rusqlite::types::ToSql>)
.collect();
let refs: Vec<&dyn rusqlite::types::ToSql> = dummy.iter().map(|b| b.as_ref()).collect();
let rows = stmt
.query_map(refs.as_slice(), |r| r.get::<_, String>(3))
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
rows.join("\n")
}
#[test]
fn traversal_expansion_uses_edge_indexes() {
for (name, sql) in [
("current", grit_core::EXPAND_SQL_CURRENT),
("as_at", grit_core::EXPAND_SQL_AS_AT),
] {
let plan = plan_of(sql, 4);
assert!(
plan.contains("idx_edges_src") && plan.contains("idx_edges_dst"),
"BFS expansion ({name}) must probe both edge indexes (OR-optimized), got:\n{plan}"
);
assert!(
!plan.lines().any(|l| l.trim().starts_with("SCAN e")),
"BFS expansion ({name}) must not full-scan edges, got:\n{plan}"
);
}
}
#[test]
fn incident_edge_lookup_uses_indexes() {
let plan = plan_of("SELECT id FROM edges WHERE src = ?1 OR dst = ?1", 1);
assert!(
plan.contains("idx_edges_src") && plan.contains("idx_edges_dst"),
"incident-edge lookup must be index-driven, got:\n{plan}"
);
}
#[test]
fn node_point_lookup_uses_primary_key() {
let plan = plan_of("SELECT name FROM nodes WHERE id = ?1", 1);
assert!(
plan.contains("USING INDEX sqlite_autoindex_nodes_1")
|| plan.contains("USING INTEGER PRIMARY KEY"),
"node lookup must use the primary key, got:\n{plan}"
);
}