use crate::datatypes::{DataFrame, Value};
use crate::graph::dir_graph::DirGraph;
use crate::graph::io::file::{load_file, save_graph};
use crate::graph::mutation::maintain::add_nodes;
use crate::graph::session::{execute_read, ExecuteOptions};
use std::sync::Arc;
fn rows(count: usize) -> DataFrame {
let rows: Vec<Vec<Value>> = (0..count)
.map(|i| {
vec![
Value::Int64(i as i64 + 1),
Value::String(format!("row-{i}")),
]
})
.collect();
DataFrame::from_cypher_rows(vec!["id".to_string(), "name".to_string()], rows).unwrap()
}
fn declare(graph: &mut DirGraph, node_type: &str, row_count: usize) {
add_nodes(
graph,
rows(row_count),
node_type.to_string(),
"id".to_string(),
Some("name".to_string()),
None,
)
.unwrap_or_else(|e| panic!("add_nodes({node_type}, {row_count} rows) failed: {e}"));
}
fn count_of_type(graph: &DirGraph, node_type: &str) -> i64 {
let params = std::collections::HashMap::new();
let opts = ExecuteOptions::new(¶ms);
let query = format!("MATCH (n:{node_type}) RETURN count(n) AS c");
let outcome = execute_read(graph, &query, &opts).expect("count query");
match outcome.result.rows.first().and_then(|row| row.first()) {
Some(Value::Int64(n)) => *n,
other => panic!("unexpected count value: {other:?}"),
}
}
fn disk_round_trip(mut graph: DirGraph, dir: &std::path::Path) -> Arc<DirGraph> {
graph.enable_disk_mode().expect("enable disk mode");
let mut handle = Arc::new(graph);
save_graph(&mut handle, dir.to_str().unwrap()).expect("save disk graph");
load_file(dir.to_str().unwrap()).expect("a saved disk directory must load")
}
#[test]
fn declared_type_with_zero_rows_survives_a_disk_round_trip() {
let tmp = tempfile::tempdir().unwrap();
let mut graph = DirGraph::new();
declare(&mut graph, "Full", 2);
declare(&mut graph, "Empty", 0);
let loaded = disk_round_trip(graph, &tmp.path().join("graph"));
assert!(
loaded.get_node_type_metadata("Empty").is_some(),
"the declared-but-unpopulated type must survive the round trip"
);
assert_eq!(count_of_type(&loaded, "Full"), 2);
assert_eq!(count_of_type(&loaded, "Empty"), 0);
}
#[test]
fn many_declared_types_with_zero_rows_survive_a_disk_round_trip() {
let tmp = tempfile::tempdir().unwrap();
let mut graph = DirGraph::new();
declare(&mut graph, "Full", 3);
for name in ["Filing", "Insider", "Holding", "Transaction", "Rating"] {
declare(&mut graph, name, 0);
}
let loaded = disk_round_trip(graph, &tmp.path().join("graph"));
assert_eq!(count_of_type(&loaded, "Full"), 3);
for name in ["Filing", "Insider", "Holding", "Transaction", "Rating"] {
assert!(
loaded.get_node_type_metadata(name).is_some(),
"declared type '{name}' must survive the round trip"
);
assert_eq!(count_of_type(&loaded, name), 0);
}
}
#[test]
fn declared_type_with_zero_rows_survives_a_kgl_round_trip() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("graph.kgl");
let mut graph = DirGraph::new();
declare(&mut graph, "Full", 2);
declare(&mut graph, "Empty", 0);
let mut handle = Arc::new(graph);
save_graph(&mut handle, path.to_str().unwrap()).expect("save .kgl");
let loaded = load_file(path.to_str().unwrap()).expect("load .kgl");
assert!(loaded.get_node_type_metadata("Empty").is_some());
assert_eq!(count_of_type(&loaded, "Full"), 2);
assert_eq!(count_of_type(&loaded, "Empty"), 0);
}
#[test]
fn single_row_type_survives_a_disk_round_trip() {
let tmp = tempfile::tempdir().unwrap();
let mut graph = DirGraph::new();
declare(&mut graph, "Full", 2);
declare(&mut graph, "Sparse", 1);
let loaded = disk_round_trip(graph, &tmp.path().join("graph"));
assert_eq!(count_of_type(&loaded, "Full"), 2);
assert_eq!(count_of_type(&loaded, "Sparse"), 1);
}
#[test]
fn a_label_only_mentioned_by_a_query_does_not_poison_the_next_save() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path().join("graph");
let mut graph = DirGraph::new();
declare(&mut graph, "Full", 2);
graph.enable_disk_mode().expect("enable disk mode");
let params = std::collections::HashMap::new();
let opts = ExecuteOptions::new(¶ms);
execute_read(&graph, "MATCH (n:Ghost {id: 1}) RETURN n", &opts).expect("ghost query");
assert!(
graph.id_indices.contains_key("Ghost"),
"precondition: the read path caches an index for the mentioned label"
);
let mut handle = Arc::new(graph);
save_graph(&mut handle, dir.to_str().unwrap()).expect("save disk graph");
let loaded = load_file(dir.to_str().unwrap()).expect("a saved disk directory must load");
assert_eq!(count_of_type(&loaded, "Full"), 2);
}