use std::collections::HashMap;
use crate::datatypes::Value;
use crate::graph::dir_graph::DirGraph;
use crate::graph::schema::NodeData;
use crate::graph::session::{execute_mut, execute_read, ExecuteOptions};
use crate::graph::storage::{GraphRead, GraphWrite};
fn run(graph: &mut DirGraph, query: &str) {
let params = HashMap::new();
let opts = ExecuteOptions::eager(¶ms);
execute_mut(graph, query, &opts).unwrap_or_else(|e| panic!("query failed: {query}: {e}"));
}
fn ids(graph: &DirGraph, query: &str) -> Vec<Value> {
let params = HashMap::new();
let opts = ExecuteOptions::eager(¶ms);
execute_read(graph, query, &opts)
.unwrap_or_else(|e| panic!("{query}: {e}"))
.result
.rows
.into_iter()
.filter_map(|r| r.into_iter().next())
.collect()
}
fn mapped_row_storage_people(n: i64) -> DirGraph {
let mut g = crate::graph::storage::mode::new_dir_graph_in_mode(
crate::graph::storage::mode::StorageMode::Mapped,
None,
)
.expect("mapped backend must be constructible");
for i in 1..=n {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::String(format!("name-{i}")));
let nd = NodeData::new(
Value::Int64(i),
Value::String(format!("t{i}")),
"Person".to_string(),
props,
&mut g.interner,
);
let idx = GraphWrite::add_node(&mut g.graph, nd);
g.type_indices
.entry_or_default("Person".to_string())
.push(idx);
g.id_indices
.entry_or_default("Person".to_string())
.insert(Value::Int64(i), idx);
}
assert!(
g.graph
.lookup_by_property_eq("Person", "name", "name-1")
.is_some_and(|hits| !hits.is_empty()),
"fixture must produce a live property index, or every assertion below \
passes through a full scan and tests nothing"
);
g
}
#[test]
fn a_set_invalidates_the_mapped_property_index() {
let mut g = mapped_row_storage_people(3);
assert_eq!(
ids(&g, "MATCH (p:Person {name:'name-1'}) RETURN p.id"),
vec![Value::Int64(1)],
"precondition: the index answers before the write"
);
run(&mut g, "MATCH (p:Person) WHERE p.id = 1 SET p.name = 'Bob'");
assert!(
ids(&g, "MATCH (p:Person {name:'name-1'}) RETURN p.id").is_empty(),
"the overwritten value must stop matching"
);
assert_eq!(
ids(&g, "MATCH (p:Person {name:'Bob'}) RETURN p.id"),
vec![Value::Int64(1)],
"the written value must match"
);
}
#[test]
fn a_remove_invalidates_the_mapped_property_index() {
let mut g = mapped_row_storage_people(3);
assert_eq!(
ids(&g, "MATCH (p:Person {name:'name-2'}) RETURN p.id"),
vec![Value::Int64(2)]
);
run(&mut g, "MATCH (p:Person) WHERE p.id = 2 REMOVE p.name");
assert!(
ids(&g, "MATCH (p:Person {name:'name-2'}) RETURN p.id").is_empty(),
"a removed property must stop matching"
);
}
#[test]
fn a_set_invalidates_the_mapped_global_property_index() {
let mut g = mapped_row_storage_people(3);
assert_eq!(
g.graph
.lookup_by_property_eq_any_type("name", "name-3")
.expect("global index must be live for the fixture"),
vec![petgraph::graph::NodeIndex::new(2)],
"precondition: the global index answers before the write"
);
run(&mut g, "MATCH (p:Person) WHERE p.id = 3 SET p.name = 'Zoe'");
assert_eq!(
g.graph.lookup_by_property_eq_any_type("name", "name-3"),
Some(Vec::new()),
"the overwritten value must stop resolving through the global index"
);
assert_eq!(
g.graph.lookup_by_property_eq_any_type("name", "Zoe"),
Some(vec![petgraph::graph::NodeIndex::new(2)]),
"the written value must resolve through the global index"
);
}
#[test]
fn a_columnar_row_keeps_its_types_index_off() {
let mut g = mapped_row_storage_people(2);
run(&mut g, "CREATE (:Person {id: 9, name: 'name-9'})");
assert!(
g.graph
.lookup_by_property_eq("Person", "name", "name-1")
.is_none(),
"a partially-covered index must report 'no index' so the matcher scans"
);
assert_eq!(
ids(&g, "MATCH (p:Person {name:'name-9'}) RETURN p.name"),
vec![Value::String("name-9".into())],
"the columnar row must still be found"
);
assert_eq!(
ids(&g, "MATCH (p:Person {name:'name-1'}) RETURN p.id"),
vec![Value::Int64(1)],
"and the row-storage nodes must still be found"
);
}