use std::collections::HashMap;
use serde_json::json;
use velesdb_core::{Database, GraphEdge, Point};
use super::helpers::create_test_db;
fn setup_vector_first_collection(db: &Database) {
let vc = {
db.create_vector_collection("papers", 4, velesdb_core::DistanceMetric::Cosine)
.expect("test: create papers collection");
db.get_vector_collection("papers")
.expect("test: get papers collection")
};
vc.upsert(vec![
Point::new(
1,
vec![1.0, 0.0, 0.0, 0.0],
Some(json!({
"_labels": ["Document"],
"title": "Physics 101",
"category": "science"
})),
),
Point::new(
2,
vec![0.9, 0.1, 0.0, 0.0],
Some(json!({
"_labels": ["Reference"],
"title": "Newton's Laws",
"category": "science"
})),
),
Point::new(
3,
vec![0.05, 0.95, 0.0, 0.0],
Some(json!({
"_labels": ["Document"],
"title": "Rust Handbook",
"category": "tech"
})),
),
Point::new(
4,
vec![0.0, 0.0, 1.0, 0.0],
Some(json!({
"_labels": ["Reference"],
"title": "Ownership Model",
"category": "tech"
})),
),
Point::new(
5,
vec![0.95, 0.05, 0.0, 0.0],
Some(json!({
"_labels": ["Document"],
"title": "Chemistry Basics",
"category": "science"
})),
),
])
.expect("test: upsert papers corpus");
let edge1 = GraphEdge::new(100, 1, 2, "CITES").expect("test: create edge 1->2");
vc.add_edge(edge1).expect("test: add edge 1->2 CITES");
let edge2 = GraphEdge::new(101, 3, 4, "CITES").expect("test: create edge 3->4");
vc.add_edge(edge2).expect("test: add edge 3->4 CITES");
}
fn match_params(
collection: &str,
param_name: &str,
vector: &[f32],
) -> HashMap<String, serde_json::Value> {
let mut params = HashMap::new();
params.insert(
"_collection".to_string(),
serde_json::Value::String(collection.to_string()),
);
params.insert(param_name.to_string(), json!(vector));
params
}
fn collection_param(collection: &str) -> HashMap<String, serde_json::Value> {
let mut params = HashMap::new();
params.insert(
"_collection".to_string(),
serde_json::Value::String(collection.to_string()),
);
params
}
#[test]
fn test_match_vector_first_basic() {
let (_dir, db) = create_test_db();
setup_vector_first_collection(&db);
let sql = "MATCH (doc:Document)-[:CITES]->(ref) \
WHERE similarity(doc.embedding, $v) > 0.7 \
RETURN doc, ref LIMIT 10";
let params = match_params("papers", "v", &[1.0, 0.0, 0.0, 0.0]);
let query =
velesdb_core::velesql::Parser::parse(sql).expect("test: parse MATCH with similarity");
let results = db
.execute_query(&query, ¶ms)
.expect("test: execute VectorFirst MATCH query");
let ids: Vec<u64> = results.iter().map(|r| r.point.id).collect();
assert!(
ids.contains(&1),
"Node 1 (Physics 101) should pass both similarity and graph filter, got: {ids:?}"
);
assert!(
!ids.contains(&5),
"Node 5 (Chemistry Basics) has no CITES edge and must be excluded despite high similarity, got: {ids:?}"
);
}
#[test]
fn test_match_vector_first_filters_by_graph_pattern() {
let (_dir, db) = create_test_db();
setup_vector_first_collection(&db);
let sql = "MATCH (doc:Document)-[:CITES]->(ref) \
WHERE similarity(doc.embedding, $v) > 0.5 \
RETURN doc LIMIT 20";
let params = match_params("papers", "v", &[1.0, 0.0, 0.0, 0.0]);
let query = velesdb_core::velesql::Parser::parse(sql).expect("test: parse MATCH query");
let results = db
.execute_query(&query, ¶ms)
.expect("test: execute query");
let ids: Vec<u64> = results.iter().map(|r| r.point.id).collect();
assert!(
!ids.contains(&5),
"Node 5 (Chemistry Basics) has no CITES edge and must be excluded, got: {ids:?}"
);
assert!(
ids.contains(&1),
"Node 1 should pass both filters, got: {ids:?}"
);
}
#[test]
fn test_match_vector_first_returns_multiple_candidates() {
let (_dir, db) = create_test_db();
setup_vector_first_collection(&db);
let sql = "MATCH (doc:Document)-[:CITES]->(ref) \
WHERE similarity(doc.embedding, $v) > 0.0 \
RETURN doc LIMIT 20";
let params = match_params("papers", "v", &[1.0, 0.0, 0.0, 0.0]);
let query = velesdb_core::velesql::Parser::parse(sql).expect("test: parse MATCH query");
let results = db
.execute_query(&query, ¶ms)
.expect("test: execute query");
let ids: Vec<u64> = results.iter().map(|r| r.point.id).collect();
assert!(
ids.contains(&1),
"Node 1 (high sim + CITES edge) should appear, got: {ids:?}"
);
assert!(
ids.contains(&3),
"Node 3 (low sim + CITES edge) should appear at threshold >0.0, got: {ids:?}"
);
assert!(
!ids.contains(&5),
"Node 5 has no CITES edge and must be excluded, got: {ids:?}"
);
let scores: Vec<f32> = results.iter().map(|r| r.score).collect();
assert!(
scores.windows(2).all(|w| w[0] >= w[1]),
"Results must be ordered by descending score, got: {scores:?}"
);
}
#[test]
fn test_match_vector_first_empty_collection() {
let (_dir, db) = create_test_db();
db.create_vector_collection("empty", 4, velesdb_core::DistanceMetric::Cosine)
.expect("test: create empty collection");
let sql = "MATCH (doc:Document)-[:CITES]->(ref) \
WHERE similarity(doc.embedding, $v) > 0.5 \
RETURN doc LIMIT 10";
let params = match_params("empty", "v", &[1.0, 0.0, 0.0, 0.0]);
let query = velesdb_core::velesql::Parser::parse(sql).expect("test: parse MATCH query");
let results = db
.execute_query(&query, ¶ms)
.expect("test: VectorFirst on empty collection should not error");
assert!(
results.is_empty(),
"Empty collection should return 0 results, got {}",
results.len()
);
}
#[test]
fn test_match_vector_first_high_threshold() {
let (_dir, db) = create_test_db();
setup_vector_first_collection(&db);
let sql = "MATCH (doc:Document)-[:CITES]->(ref) \
WHERE similarity(doc.embedding, $v) > 0.999 \
RETURN doc LIMIT 10";
let params = match_params("papers", "v", &[1.0, 0.0, 0.0, 0.0]);
let query =
velesdb_core::velesql::Parser::parse(sql).expect("test: parse high-threshold MATCH query");
let results = db
.execute_query(&query, ¶ms)
.expect("test: execute high-threshold query");
for r in &results {
assert!(
r.score > 0.999,
"All results should exceed 0.999 threshold, got score {} for id {}",
r.score,
r.point.id
);
}
let ids: Vec<u64> = results.iter().map(|r| r.point.id).collect();
assert!(
!ids.contains(&5),
"Node 5 must not appear (no CITES edge), got: {ids:?}"
);
}
#[test]
fn test_match_vector_first_missing_vector_param() {
let (_dir, db) = create_test_db();
setup_vector_first_collection(&db);
let sql = "MATCH (doc:Document)-[:CITES]->(ref) \
WHERE similarity(doc.embedding, $v) > 0.7 \
RETURN doc LIMIT 10";
let params = collection_param("papers");
let query =
velesdb_core::velesql::Parser::parse(sql).expect("test: parse MATCH query (missing param)");
let err = db
.execute_query(&query, ¶ms)
.expect_err("test: missing $v param should produce an error");
let msg = err.to_string();
assert!(
msg.to_lowercase().contains("param")
|| msg.to_lowercase().contains("vector")
|| msg.to_lowercase().contains("not found")
|| msg.to_lowercase().contains("missing"),
"Error should indicate missing parameter, got: {msg}"
);
}