use super::super::match_exec::MatchResult;
use super::merge_match_results;
fn mr(node_id: u64, score: Option<f32>) -> MatchResult {
let mut r = MatchResult::new(node_id, 0, Vec::new());
r.score = score;
r
}
#[cfg(feature = "persistence")]
mod parallel_counters {
use crate::collection::graph::GraphEdge;
use crate::collection::types::Collection;
use crate::distance::DistanceMetric;
use crate::point::Point;
use crate::velesql::match_planner::MatchExecutionStrategy;
use crate::velesql::{MatchClause, Parser};
use std::collections::HashMap;
use std::path::PathBuf;
fn setup_parallel_collection() -> (tempfile::TempDir, Collection) {
let dir = tempfile::tempdir().expect("temp dir");
let col = Collection::create(PathBuf::from(dir.path()), 2, DistanceMetric::Cosine)
.expect("create collection");
col.upsert(vec![
Point::new(
1,
vec![1.0, 0.0],
Some(serde_json::json!({"_labels": ["Doc"]})),
),
Point::new(
2,
vec![0.7, 0.7],
Some(serde_json::json!({"_labels": ["Doc"]})),
),
Point::new(
3,
vec![0.0, 1.0],
Some(serde_json::json!({"_labels": ["Doc"]})),
),
])
.expect("upsert");
col.add_edge(GraphEdge::new(10, 1, 2, "LINK").expect("edge"))
.expect("add edge");
col.add_edge(GraphEdge::new(11, 2, 3, "LINK").expect("edge"))
.expect("add edge");
(dir, col)
}
fn parallel_match_clause() -> MatchClause {
Parser::parse(
"MATCH (a:Doc)-[:LINK]->(b:Doc) WHERE similarity(a, $v) > 0.0 RETURN a, b LIMIT 10",
)
.expect("parse parallel MATCH")
.match_clause
.expect("MATCH clause present")
}
fn vector_first_hint() -> MatchExecutionStrategy {
MatchExecutionStrategy::VectorFirst {
similarity_alias: "a".to_string(),
top_k: 10,
threshold: 0.0,
}
}
#[test]
fn parallel_counters_sum_both_legs() {
let (_dir, col) = setup_parallel_collection();
let mc = parallel_match_clause();
let hint = vector_first_hint();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0]));
let graph_ctx = col.runtime.guard_rails.create_context();
col.execute_match_with_context(&mc, ¶ms, Some(&graph_ctx))
.expect("graph leg");
let graph_nodes = graph_ctx.traversal_nodes_visited();
let graph_edges = graph_ctx.traversal_edges_traversed();
let vec_ctx = col.runtime.guard_rails.create_context();
col.execute_match_vector_first(&mc, ¶ms, &vec_ctx, "a", 10, 0.0)
.expect("vector leg");
let vec_nodes = vec_ctx.traversal_nodes_visited();
let vec_edges = vec_ctx.traversal_edges_traversed();
assert!(graph_edges > 0, "graph leg must follow LINK edges");
assert!(vec_nodes > 0, "vector leg must evaluate candidates");
let par_ctx = col.runtime.guard_rails.create_context();
col.execute_match_parallel(&mc, ¶ms, &par_ctx, &hint)
.expect("parallel run");
assert_eq!(
par_ctx.traversal_nodes_visited(),
graph_nodes + vec_nodes,
"Parallel nodes_visited must equal the sum of both legs"
);
assert_eq!(
par_ctx.traversal_edges_traversed(),
graph_edges + vec_edges,
"Parallel edges_traversed must equal the sum of both legs"
);
}
}
#[test]
fn test_merge_empty_inputs() {
let merged = merge_match_results(Vec::new(), Vec::new(), true);
assert!(merged.is_empty());
}
#[test]
fn test_merge_graph_only() {
let graph = vec![mr(1, None), mr(2, Some(0.5))];
let merged = merge_match_results(graph, Vec::new(), true);
assert_eq!(merged.len(), 2);
assert_eq!(merged[0].node_id, 2);
}
#[test]
fn test_merge_vector_only() {
let vector = vec![mr(3, Some(0.9)), mr(4, Some(0.7))];
let merged = merge_match_results(Vec::new(), vector, true);
assert_eq!(merged.len(), 2);
assert_eq!(merged[0].node_id, 3);
assert_eq!(merged[1].node_id, 4);
}
#[test]
fn test_merge_union_distinct_nodes() {
let graph = vec![mr(1, None), mr(2, None)];
let vector = vec![mr(3, Some(0.8)), mr(4, Some(0.6))];
let merged = merge_match_results(graph, vector, true);
assert_eq!(merged.len(), 4);
}
#[test]
fn test_merge_duplicate_keeps_higher_score() {
let graph = vec![mr(1, Some(0.3))];
let vector = vec![mr(1, Some(0.9))];
let merged = merge_match_results(graph, vector, true);
assert_eq!(merged.len(), 1);
assert_eq!(merged[0].node_id, 1);
assert!((merged[0].score.expect("test: should have score") - 0.9).abs() < f32::EPSILON);
}
#[test]
fn test_merge_duplicate_graph_wins_when_higher() {
let graph = vec![mr(1, Some(0.95))];
let vector = vec![mr(1, Some(0.5))];
let merged = merge_match_results(graph, vector, true);
assert_eq!(merged.len(), 1);
assert!((merged[0].score.expect("test: should have score") - 0.95).abs() < f32::EPSILON);
}
#[test]
fn test_merge_sorted_descending() {
let graph = vec![mr(1, Some(0.3)), mr(2, Some(0.1))];
let vector = vec![mr(3, Some(0.9)), mr(4, Some(0.5))];
let merged = merge_match_results(graph, vector, true);
let scores: Vec<f32> = merged
.iter()
.map(|r| r.score.unwrap_or(f32::NEG_INFINITY))
.collect();
for w in scores.windows(2) {
assert!(w[0] >= w[1], "scores should be descending: {scores:?}");
}
}
#[test]
fn test_merge_none_scores_sorted_last() {
let graph = vec![mr(1, None), mr(2, None)];
let vector = vec![mr(3, Some(0.5))];
let merged = merge_match_results(graph, vector, true);
assert_eq!(merged.len(), 3);
assert_eq!(merged[0].node_id, 3);
}
#[test]
fn test_merge_euclidean_duplicate_keeps_lower_score() {
let graph = vec![mr(1, Some(0.9))];
let vector = vec![mr(1, Some(0.2))];
let merged = merge_match_results(graph, vector, false);
assert_eq!(merged.len(), 1);
assert!(
(merged[0].score.expect("test: should have score") - 0.2).abs() < f32::EPSILON,
"Euclidean: lower distance should win"
);
}
#[test]
fn test_merge_euclidean_graph_wins_when_lower() {
let graph = vec![mr(1, Some(0.1))];
let vector = vec![mr(1, Some(0.8))];
let merged = merge_match_results(graph, vector, false);
assert_eq!(merged.len(), 1);
assert!(
(merged[0].score.expect("test: should have score") - 0.1).abs() < f32::EPSILON,
"Euclidean: graph result with lower distance should win"
);
}
#[test]
fn test_merge_euclidean_sorted_ascending() {
let graph = vec![mr(1, Some(0.9)), mr(2, Some(0.3))];
let vector = vec![mr(3, Some(0.1)), mr(4, Some(0.5))];
let merged = merge_match_results(graph, vector, false);
let scores: Vec<f32> = merged.iter().map(|r| r.score.unwrap_or(f32::MAX)).collect();
for w in scores.windows(2) {
assert!(
w[0] <= w[1],
"Euclidean scores should be ascending (best first): {scores:?}"
);
}
}
#[test]
fn test_merge_euclidean_none_scores_sorted_last() {
let graph = vec![mr(1, None), mr(2, None)];
let vector = vec![mr(3, Some(0.5))];
let merged = merge_match_results(graph, vector, false);
assert_eq!(merged.len(), 3);
assert_eq!(
merged[0].node_id, 3,
"Euclidean: scored result should sort before None"
);
}
#[test]
fn test_merge_empty_inputs_euclidean() {
let merged = merge_match_results(Vec::new(), Vec::new(), false);
assert!(merged.is_empty());
}
fn graph_mr_with_edge_data(node_id: u64) -> MatchResult {
let mut r = MatchResult::new(node_id, 1, vec![100]);
r.bindings.insert("b".to_string(), node_id);
r.edge_bindings.insert("r".to_string(), 100);
r.projected
.insert("r.since".to_string(), serde_json::json!(2020));
r
}
#[test]
fn test_merge_collision_preserves_graph_edge_data() {
let graph = vec![graph_mr_with_edge_data(1)];
let vector = vec![mr(1, Some(0.9))];
let merged = merge_match_results(graph, vector, true);
assert_eq!(merged.len(), 1);
assert!(
(merged[0].score.expect("test: should have score") - 0.9).abs() < f32::EPSILON,
"the better (vector) score must win"
);
assert_eq!(
merged[0].projected.get("r.since"),
Some(&serde_json::json!(2020)),
"GraphFirst projection must survive the collision merge"
);
assert_eq!(
merged[0].edge_bindings.get("r"),
Some(&100),
"GraphFirst edge binding must survive the collision merge"
);
assert_eq!(
merged[0].bindings.get("b"),
Some(&1),
"GraphFirst node binding must survive the collision merge"
);
}
#[test]
fn test_merge_collision_preserves_loser_only_keys() {
let mut graph = graph_mr_with_edge_data(1);
graph.score = Some(0.95);
let mut vector = mr(1, Some(0.5));
vector
.projected
.insert("similarity()".to_string(), serde_json::json!(0.5));
let merged = merge_match_results(vec![graph], vec![vector], true);
assert_eq!(merged.len(), 1);
assert!(
(merged[0].score.expect("test: should have score") - 0.95).abs() < f32::EPSILON,
"the better (graph) score must win"
);
assert!(
merged[0].projected.contains_key("similarity()"),
"loser-only projection keys must survive the collision merge"
);
assert_eq!(
merged[0].projected.get("r.since"),
Some(&serde_json::json!(2020)),
"winner projection must be untouched"
);
}
#[test]
fn test_merge_enriches_all_parallel_edge_rows() {
let mut g1 = graph_mr_with_edge_data(1);
g1.edge_bindings.insert("r".to_string(), 100);
let mut g2 = graph_mr_with_edge_data(1);
g2.edge_bindings.insert("r".to_string(), 101);
let vector = vec![mr(1, Some(0.9))];
let merged = merge_match_results(vec![g1, g2], vector, true);
assert_eq!(merged.len(), 2, "both parallel-edge rows must survive");
for row in &merged {
assert!(
(row.score.expect("test: enriched score") - 0.9).abs() < f32::EPSILON,
"every row of the node group must carry the node-level score"
);
}
let mut edge_ids: Vec<u64> = merged
.iter()
.filter_map(|r| r.edge_bindings.get("r").copied())
.collect();
edge_ids.sort_unstable();
assert_eq!(edge_ids, vec![100, 101], "edge identities must be distinct");
}