use super::*;
use crate::graph::session::execute::{execute_mut, ExecuteOptions};
fn run(graph: &mut DirGraph, query: &str) {
let params = std::collections::HashMap::new();
let opts = ExecuteOptions::eager(¶ms);
execute_mut(graph, query, &opts).unwrap_or_else(|e| panic!("setup query failed: {query}: {e}"));
}
fn seeded_people() -> DirGraph {
let mut graph = DirGraph::new();
run(
&mut graph,
"CREATE (:Person {id: 1, name: 'a'}), (:Person {id: 2, name: 'b'}), \
(:Person {id: 3, name: 'c'})",
);
graph.build_id_index("Person");
assert!(graph.id_indices.contains_key("Person"));
graph
}
#[test]
fn delete_edits_id_index_in_place() {
let mut graph = seeded_people();
let doomed = graph
.lookup_by_id_readonly("Person", &Value::Int64(2))
.expect("id 2 must be indexed");
let survivor = graph
.lookup_by_id_readonly("Person", &Value::Int64(3))
.expect("id 3 must be indexed");
let mut to_delete = HashSet::new();
to_delete.insert(doomed);
assert_eq!(detach_delete_nodes(&mut graph, &to_delete), (1, 0));
assert_eq!(
graph.id_indices.lookup("Person", &Value::Int64(3)),
Some(survivor),
"the delete must leave the id index usable, not force a rebuild"
);
assert_eq!(graph.id_indices.lookup("Person", &Value::Int64(2)), None);
assert_eq!(graph.id_indices.overlay_len("Person"), Some(2));
assert!(graph
.lookup_by_id_readonly("Person", &Value::Int64(2))
.is_none());
assert_eq!(
graph.lookup_by_id_readonly("Person", &Value::Int64(1)),
graph.id_indices.lookup("Person", &Value::Int64(1))
);
}
#[test]
fn delete_with_duplicate_ids_falls_back_to_rebuild() {
let mut graph = DirGraph::new();
run(&mut graph, "CREATE (:Person {id: 1, name: 'first'})");
run(&mut graph, "CREATE (:Person {id: 1, name: 'second'})");
run(&mut graph, "CREATE (:Person {id: 2, name: 'other'})");
graph.build_id_index("Person");
assert_eq!(graph.type_indices.get("Person").map(|m| m.len()), Some(3));
assert_eq!(graph.id_indices.overlay_len("Person"), Some(2));
let indexed = graph
.lookup_by_id_readonly("Person", &Value::Int64(1))
.expect("id 1 resolves to one of the two");
let mut to_delete = HashSet::new();
to_delete.insert(indexed);
assert_eq!(detach_delete_nodes(&mut graph, &to_delete), (1, 0));
let survivor = graph
.lookup_by_id_readonly("Person", &Value::Int64(1))
.expect("the shadowed duplicate must remain reachable by id");
assert_ne!(survivor, indexed);
assert!(graph
.lookup_by_id_readonly("Person", &Value::Int64(2))
.is_some());
}
#[test]
fn evicting_an_absent_id_index_does_not_materialize_it() {
let mut graph = DirGraph::new();
run(
&mut graph,
"CREATE (:Person {id: 1, name: 'a'}), (:Person {id: 2, name: 'b'})",
);
graph.build_id_index("Person");
let doomed = graph
.lookup_by_id_readonly("Person", &Value::Int64(1))
.unwrap();
graph.id_indices.remove("Person");
assert!(!graph.id_indices.contains_key("Person"));
let evicted = graph
.id_indices
.evict_entries("Person", &[(Value::Int64(1), doomed)]);
assert!(!evicted, "an absent index cannot be edited in place");
assert!(
!graph.id_indices.contains_key("Person"),
"an absent index must not be partially materialized by an evict"
);
assert!(graph
.lookup_by_id_readonly("Person", &Value::Int64(2))
.is_some());
assert_eq!(graph.id_indices.overlay_len("Person"), Some(2));
}
#[test]
fn detach_delete_edits_every_affected_type() {
let mut graph = DirGraph::new();
run(
&mut graph,
"CREATE (i:Issue {id: 1}), (c:Comment {id: 10}), (c2:Comment {id: 11})",
);
run(
&mut graph,
"MATCH (i:Issue {id: 1}), (c:Comment {id: 10}) CREATE (c)-[:ON]->(i)",
);
run(
&mut graph,
"MATCH (i:Issue {id: 1}), (c:Comment {id: 11}) CREATE (c)-[:ON]->(i)",
);
assert_eq!(graph.graph.edge_count(), 2);
graph.build_id_index("Issue");
graph.build_id_index("Comment");
let issue = graph
.lookup_by_id_readonly("Issue", &Value::Int64(1))
.unwrap();
let comment = graph
.lookup_by_id_readonly("Comment", &Value::Int64(10))
.unwrap();
let survivor = graph
.lookup_by_id_readonly("Comment", &Value::Int64(11))
.unwrap();
let mut to_delete = HashSet::new();
to_delete.insert(issue);
to_delete.insert(comment);
let (nodes, edges) = detach_delete_nodes(&mut graph, &to_delete);
assert_eq!((nodes, edges), (2, 2));
assert_eq!(graph.id_indices.lookup("Issue", &Value::Int64(1)), None);
assert_eq!(graph.id_indices.overlay_len("Issue"), Some(0));
assert_eq!(graph.id_indices.lookup("Comment", &Value::Int64(10)), None);
assert_eq!(
graph.id_indices.lookup("Comment", &Value::Int64(11)),
Some(survivor),
"an untouched sibling must stay indexed without a rebuild"
);
assert_eq!(graph.id_indices.overlay_len("Comment"), Some(1));
}