#[cfg(test)]
mod tests {
use crate::collection::types::Collection;
use crate::distance::DistanceMetric;
use crate::velesql::Parser;
use crate::Point;
use std::collections::HashMap;
use std::path::PathBuf;
fn create_test_collection_with_data() -> (Collection, tempfile::TempDir) {
let temp_dir = tempfile::tempdir().unwrap();
let path = PathBuf::from(temp_dir.path());
let collection = Collection::create(path, 4, DistanceMetric::Cosine).unwrap();
let points = vec![
Point {
id: 1,
vector: vec![1.0, 0.0, 0.0, 0.0],
payload: Some(serde_json::json!({"name": "point_1"})),
sparse_vectors: None,
},
Point {
id: 2,
vector: vec![0.0, 1.0, 0.0, 0.0],
payload: Some(serde_json::json!({"name": "point_2"})),
sparse_vectors: None,
},
Point {
id: 3,
vector: vec![0.7, 0.7, 0.0, 0.0],
payload: Some(serde_json::json!({"name": "point_3"})),
sparse_vectors: None,
},
Point {
id: 4,
vector: vec![0.5, 0.5, 0.5, 0.5],
payload: Some(serde_json::json!({"name": "point_4"})),
sparse_vectors: None,
},
Point {
id: 5,
vector: vec![0.9, 0.1, 0.0, 0.0],
payload: Some(serde_json::json!({"name": "point_5"})),
sparse_vectors: None,
},
];
collection.upsert(points).unwrap();
(collection, temp_dir)
}
#[test]
fn test_similarity_greater_than_threshold() {
let (collection, _temp) = create_test_collection_with_data();
let query = "SELECT * FROM test_similarity WHERE similarity(vector, $v) > 0.8 LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
assert!(
!results.is_empty(),
"Should return results with high similarity"
);
for r in &results {
assert!(r.score > 0.8, "Score {} should be > 0.8", r.score);
}
}
#[test]
fn test_similarity_greater_than_or_equal() {
let (collection, _temp) = create_test_collection_with_data();
let query = "SELECT * FROM test_similarity WHERE similarity(vector, $v) >= 0.99 LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
for r in &results {
assert!(r.score >= 0.99, "Score {} should be >= 0.99", r.score);
}
}
#[test]
fn test_similarity_less_than_threshold() {
let (collection, _temp) = create_test_collection_with_data();
let query = "SELECT * FROM test_similarity WHERE similarity(vector, $v) < 0.5 LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
for r in &results {
assert!(r.score < 0.5, "Score {} should be < 0.5", r.score);
}
}
#[test]
fn test_similarity_with_literal_vector() {
let (collection, _temp) = create_test_collection_with_data();
let query =
"SELECT * FROM test_similarity WHERE similarity(vector, [1.0, 0.0, 0.0, 0.0]) > 0.9 LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let params = HashMap::new();
let results = collection.execute_query(&parsed, ¶ms).unwrap();
for r in &results {
assert!(r.score > 0.9, "Score {} should be > 0.9", r.score);
}
}
#[test]
fn test_similarity_no_results_when_threshold_too_high() {
let (collection, _temp) = create_test_collection_with_data();
let query = "SELECT * FROM test_similarity WHERE similarity(vector, $v) > 1.5 LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
assert!(
results.is_empty(),
"Should return no results for impossible threshold"
);
}
#[test]
fn test_similarity_missing_parameter_error() {
let (collection, _temp) = create_test_collection_with_data();
let query =
"SELECT * FROM test_similarity WHERE similarity(vector, $missing) > 0.8 LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let params = HashMap::new(); let result = collection.execute_query(&parsed, ¶ms);
assert!(result.is_err(), "Should error on missing parameter");
}
#[test]
fn test_similarity_with_metadata_filter_applied() {
let temp_dir = tempfile::tempdir().unwrap();
let path = PathBuf::from(temp_dir.path());
let collection = Collection::create(path, 4, DistanceMetric::Cosine).unwrap();
let points = vec![
Point {
id: 1,
vector: vec![1.0, 0.0, 0.0, 0.0],
payload: Some(serde_json::json!({"category": "tech", "name": "tech_1"})),
sparse_vectors: None,
},
Point {
id: 2,
vector: vec![0.95, 0.05, 0.0, 0.0], payload: Some(serde_json::json!({"category": "sports", "name": "sports_1"})),
sparse_vectors: None,
},
Point {
id: 3,
vector: vec![0.9, 0.1, 0.0, 0.0], payload: Some(serde_json::json!({"category": "tech", "name": "tech_2"})),
sparse_vectors: None,
},
Point {
id: 4,
vector: vec![0.5, 0.5, 0.5, 0.5], payload: Some(serde_json::json!({"category": "tech", "name": "tech_3"})),
sparse_vectors: None,
},
];
collection.upsert(points).unwrap();
let query =
"SELECT * FROM test WHERE similarity(vector, $v) > 0.8 AND category = 'tech' LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
for result in &results {
let payload = result.point.payload.as_ref().expect("Should have payload");
let category = payload.get("category").and_then(|v| v.as_str());
assert_eq!(
category,
Some("tech"),
"All results should have category='tech', but got {:?} for id={}",
category,
result.point.id
);
}
let ids: Vec<u64> = results.iter().map(|r| r.point.id).collect();
assert!(
!ids.contains(&2),
"id=2 (sports) should be filtered out, but got ids: {:?}",
ids
);
}
#[test]
fn test_order_by_similarity_desc() {
let (collection, _temp_dir) = create_test_collection_with_data();
let query = "SELECT * FROM test ORDER BY similarity(vector, $v) DESC LIMIT 5";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
assert!(!results.is_empty(), "Should return results");
for i in 1..results.len() {
assert!(
results[i - 1].score >= results[i].score,
"Results should be sorted DESC: {} >= {} at position {}",
results[i - 1].score,
results[i].score,
i
);
}
assert_eq!(results[0].point.id, 1, "Most similar point should be first");
}
#[test]
fn test_order_by_similarity_asc() {
let (collection, _temp_dir) = create_test_collection_with_data();
let query = "SELECT * FROM test ORDER BY similarity(vector, $v) ASC LIMIT 5";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
assert!(!results.is_empty(), "Should return results");
for i in 1..results.len() {
assert!(
results[i - 1].score <= results[i].score,
"Results should be sorted ASC: {} <= {} at position {}",
results[i - 1].score,
results[i].score,
i
);
}
}
#[test]
fn test_order_by_similarity_with_filter() {
let temp_dir = tempfile::tempdir().unwrap();
let path = PathBuf::from(temp_dir.path());
let collection = Collection::create(path, 4, DistanceMetric::Cosine).unwrap();
let points = vec![
Point {
id: 1,
vector: vec![1.0, 0.0, 0.0, 0.0],
payload: Some(serde_json::json!({"category": "tech", "name": "tech_1"})),
sparse_vectors: None,
},
Point {
id: 2,
vector: vec![0.9, 0.1, 0.0, 0.0],
payload: Some(serde_json::json!({"category": "tech", "name": "tech_2"})),
sparse_vectors: None,
},
Point {
id: 3,
vector: vec![0.8, 0.2, 0.0, 0.0],
payload: Some(serde_json::json!({"category": "sports", "name": "sports_1"})),
sparse_vectors: None,
},
];
collection.upsert(points).unwrap();
let query =
"SELECT * FROM test WHERE category = 'tech' ORDER BY similarity(vector, $v) DESC LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
for result in &results {
let payload = result.point.payload.as_ref().expect("Should have payload");
let category = payload
.get("category")
.and_then(|v: &serde_json::Value| v.as_str());
assert_eq!(
category,
Some("tech"),
"All results should have category='tech'"
);
}
for i in 1..results.len() {
assert!(
results[i - 1].score >= results[i].score,
"Results should be sorted DESC"
);
}
}
#[test]
fn test_issue_122_union_query_nested_and_or() {
let temp_dir = tempfile::tempdir().unwrap();
let path = PathBuf::from(temp_dir.path());
let collection = Collection::create(path, 4, DistanceMetric::Cosine).unwrap();
let points = vec![
Point {
id: 1,
vector: vec![1.0, 0.0, 0.0, 0.0], payload: Some(serde_json::json!({
"category": "tech",
"status": "active"
})),
sparse_vectors: None,
},
Point {
id: 2,
vector: vec![0.0, 1.0, 0.0, 0.0], payload: Some(serde_json::json!({
"category": "tech",
"status": "active" })),
sparse_vectors: None,
},
Point {
id: 3,
vector: vec![0.0, 0.0, 1.0, 0.0], payload: Some(serde_json::json!({
"category": "tech",
"status": "inactive" })),
sparse_vectors: None,
},
Point {
id: 4,
vector: vec![0.9, 0.1, 0.0, 0.0], payload: Some(serde_json::json!({
"category": "sports",
"status": "inactive" })),
sparse_vectors: None,
},
];
collection.upsert(points).unwrap();
let query = "SELECT * FROM test WHERE (similarity(vector, $v) > 0.8 OR category = 'tech') AND status = 'active' LIMIT 10";
let parsed = Parser::parse(query).unwrap();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([1.0, 0.0, 0.0, 0.0]));
let results = collection.execute_query(&parsed, ¶ms).unwrap();
for result in &results {
let payload = result.point.payload.as_ref().expect("Should have payload");
let status = payload.get("status").and_then(|v| v.as_str());
assert_eq!(
status,
Some("active"),
"Issue #122: All results must satisfy outer AND filter (status='active'), but id={} has status={:?}",
result.point.id,
status
);
}
let ids: Vec<u64> = results.iter().map(|r| r.point.id).collect();
assert!(
!ids.contains(&3),
"Issue #122: id=3 has status='inactive', should be filtered by outer AND, but got ids: {:?}",
ids
);
assert!(
!ids.contains(&4),
"Issue #122: id=4 has status='inactive', should be filtered by outer AND, but got ids: {:?}",
ids
);
assert!(
ids.contains(&1) || ids.contains(&2),
"Should have at least one result matching the criteria, got ids: {:?}",
ids
);
}
}