#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::float_cmp
)]
use crate::collection::types::Collection;
use crate::distance::DistanceMetric;
use crate::point::Point;
use std::collections::HashMap;
use tempfile::TempDir;
fn setup_let_collection() -> (TempDir, Collection) {
let dir = TempDir::new().unwrap();
let path = dir.path().join("let_col");
let col = Collection::create(path, 4, DistanceMetric::Cosine).expect("create collection");
let mut points = Vec::new();
for i in 0u64..20 {
#[allow(clippy::cast_precision_loss)]
let fi = i as f32;
let v = vec![fi / 20.0, 1.0 - fi / 20.0, 0.5, 0.3];
points.push(Point {
id: i,
vector: v,
payload: Some(serde_json::json!({ "idx": i, "priority": 20 - i })),
sparse_vectors: None,
});
}
col.upsert(points).expect("upsert");
(dir, col)
}
#[test]
fn test_let_binding_in_order_by() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let baseline = col
.execute_query_str(
"SELECT * FROM docs WHERE vector NEAR $v ORDER BY similarity() DESC LIMIT 5",
¶ms,
)
.expect("baseline query");
let let_results = col
.execute_query_str(
"LET s = similarity() SELECT * FROM docs WHERE vector NEAR $v ORDER BY s DESC LIMIT 5",
¶ms,
)
.expect("LET query");
assert_eq!(baseline.len(), let_results.len());
let baseline_ids: Vec<u64> = baseline.iter().map(|r| r.point.id).collect();
let let_ids: Vec<u64> = let_results.iter().map(|r| r.point.id).collect();
assert_eq!(
baseline_ids, let_ids,
"LET s = similarity() ORDER BY s should match ORDER BY similarity()"
);
}
#[test]
fn test_let_weighted_hybrid_ordering() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let baseline = col
.execute_query_str(
"SELECT * FROM docs WHERE vector NEAR $v ORDER BY similarity() DESC LIMIT 10",
¶ms,
)
.expect("baseline");
let hybrid = col
.execute_query_str(
"LET hybrid = 0.01 * similarity() + 0.99 * priority \
SELECT * FROM docs WHERE vector NEAR $v ORDER BY hybrid DESC LIMIT 10",
¶ms,
)
.expect("hybrid query");
assert_eq!(hybrid.len(), 10);
let baseline_ids: Vec<u64> = baseline.iter().map(|r| r.point.id).collect();
let hybrid_ids: Vec<u64> = hybrid.iter().map(|r| r.point.id).collect();
assert_ne!(
baseline_ids, hybrid_ids,
"Priority-dominated ordering should differ from similarity ordering"
);
let min_id_in_candidates = *hybrid_ids.iter().min().expect("non-empty results");
assert_eq!(
hybrid_ids[0], min_id_in_candidates,
"First result should be the lowest-id (highest priority) candidate"
);
}
#[test]
fn test_let_chained_bindings() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let baseline = col
.execute_query_str(
"SELECT * FROM docs WHERE vector NEAR $v ORDER BY similarity() DESC LIMIT 5",
¶ms,
)
.expect("baseline");
let chained = col
.execute_query_str(
"LET a = similarity() LET b = a * 2.0 \
SELECT * FROM docs WHERE vector NEAR $v ORDER BY b DESC LIMIT 5",
¶ms,
)
.expect("chained query");
let baseline_ids: Vec<u64> = baseline.iter().map(|r| r.point.id).collect();
let chained_ids: Vec<u64> = chained.iter().map(|r| r.point.id).collect();
assert_eq!(
baseline_ids, chained_ids,
"Monotonic transform should preserve ordering"
);
}
#[test]
fn test_let_literal_binding() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET threshold = 0.8 \
SELECT * FROM docs WHERE vector NEAR $v ORDER BY threshold DESC LIMIT 5",
¶ms,
)
.expect("literal query");
assert_eq!(results.len(), 5);
}
#[test]
fn test_let_unused_binding_no_error() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET unused = 0.5 \
SELECT * FROM docs WHERE vector NEAR $v ORDER BY similarity() DESC LIMIT 5",
¶ms,
)
.expect("unused binding should not error");
assert_eq!(results.len(), 5);
}
#[test]
fn test_let_backward_compat_no_let() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"SELECT * FROM docs WHERE vector NEAR $v ORDER BY similarity() DESC LIMIT 5",
¶ms,
)
.expect("no-LET query");
assert_eq!(results.len(), 5);
}
#[test]
fn test_let_with_offset_and_limit() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET s = similarity() \
SELECT * FROM docs WHERE vector NEAR $v ORDER BY s DESC LIMIT 3 OFFSET 2",
¶ms,
)
.expect("LET + OFFSET");
assert_eq!(results.len(), 3);
}
#[test]
fn test_let_with_not_similarity_returns_error() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let result = col.execute_query_str(
"LET s = 1.0 SELECT * FROM docs WHERE NOT similarity(vector, $v) > 0.8 LIMIT 5",
¶ms,
);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("NOT similarity()"),
"expected NOT similarity() error, got: {msg}"
);
}
#[test]
fn test_let_with_union_query_returns_error() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let result = col.execute_query_str(
"LET s = 1.0 SELECT * FROM docs \
WHERE similarity(vector, $v) > 0.5 OR category = 'tech' LIMIT 5",
¶ms,
);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
assert!(
msg.contains("OR/union"),
"expected OR/union error, got: {msg}"
);
}
#[test]
fn test_let_with_with_clause() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET s = similarity() \
SELECT * FROM docs WHERE vector NEAR $v ORDER BY s DESC LIMIT 5 WITH (mode='fast')",
¶ms,
)
.expect("LET + WITH");
assert_eq!(results.len(), 5);
}
#[test]
fn test_evaluate_let_bindings_single_literal() {
use crate::collection::search::query::ordering::evaluate_let_bindings;
use crate::velesql::{ArithmeticExpr, LetBinding};
let bindings = vec![LetBinding {
name: "x".to_string(),
expr: ArithmeticExpr::Literal(0.42),
}];
let result = evaluate_let_bindings(&bindings, 0.5, None, None);
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "x");
assert!((result[0].1 - 0.42).abs() < 1e-5);
}
#[test]
fn test_evaluate_let_bindings_chained() {
use crate::collection::search::query::ordering::evaluate_let_bindings;
use crate::velesql::{ArithmeticExpr, ArithmeticOp, LetBinding};
let bindings = vec![
LetBinding {
name: "a".to_string(),
expr: ArithmeticExpr::Literal(2.0),
},
LetBinding {
name: "b".to_string(),
expr: ArithmeticExpr::BinaryOp {
left: Box::new(ArithmeticExpr::Variable("a".to_string())),
op: ArithmeticOp::Mul,
right: Box::new(ArithmeticExpr::Literal(3.0)),
},
},
];
let result = evaluate_let_bindings(&bindings, 0.0, None, None);
assert_eq!(result.len(), 2);
assert!((result[0].1 - 2.0).abs() < 1e-5, "a = 2.0");
assert!(
(result[1].1 - 6.0).abs() < 1e-5,
"b = a * 3.0 = 6.0, got {}",
result[1].1
);
}
#[test]
fn test_evaluate_let_bindings_similarity() {
use crate::collection::search::query::ordering::evaluate_let_bindings;
use crate::velesql::{ArithmeticExpr, LetBinding, OrderByExpr};
let bindings = vec![LetBinding {
name: "s".to_string(),
expr: ArithmeticExpr::Similarity(Box::new(OrderByExpr::SimilarityBare)),
}];
let result = evaluate_let_bindings(&bindings, 0.77, None, None);
assert!((result[0].1 - 0.77).abs() < 1e-5);
}
#[test]
fn test_let_with_match_returns_clear_error() {
use crate::collection::graph::GraphEdge;
let (_dir, col) = setup_let_collection();
let edge1 = GraphEdge::new(1, 0, 1, "RELATED").expect("edge");
let edge2 = GraphEdge::new(2, 1, 2, "RELATED").expect("edge");
col.add_edge(edge1).expect("add edge");
col.add_edge(edge2).expect("add edge");
let params = HashMap::new();
let result = col.execute_query_str(
"LET x = 0.5 MATCH (a)-[r:RELATED]->(b) RETURN a LIMIT 5",
¶ms,
);
assert!(result.is_err(), "LET + MATCH should return an error");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("LET") && err_msg.contains("MATCH"),
"Error should mention LET and MATCH, got: {err_msg}"
);
}
#[test]
fn test_match_without_let_still_works() {
use crate::collection::graph::GraphEdge;
let (_dir, col) = setup_let_collection();
let edge = GraphEdge::new(1, 0, 1, "RELATED").expect("edge");
col.add_edge(edge).expect("add edge");
let params = HashMap::new();
let result = col.execute_query_str("MATCH (a)-[r:RELATED]->(b) RETURN a LIMIT 5", ¶ms);
assert!(result.is_ok(), "MATCH without LET should succeed");
}
#[test]
fn test_evaluate_let_bindings_empty() {
use crate::collection::search::query::ordering::evaluate_let_bindings;
let result = evaluate_let_bindings(&[], 0.5, None, None);
assert!(result.is_empty());
}
#[test]
fn test_let_binding_appears_in_select_projection() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET hybrid = 0.5 \
SELECT * FROM docs WHERE vector NEAR $v LIMIT 5",
¶ms,
)
.expect("LET projection query");
assert_eq!(results.len(), 5);
for r in &results {
let payload = r.point.payload.as_ref().expect("payload should exist");
let hybrid_val = payload.get("hybrid").expect("hybrid field should exist");
let v = hybrid_val.as_f64().expect("hybrid should be a number");
assert!((v - 0.5).abs() < 1e-5, "hybrid should be 0.5, got {v}");
}
}
#[test]
fn test_let_binding_overrides_payload_field() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET idx = 99.0 \
SELECT * FROM docs WHERE vector NEAR $v LIMIT 5",
¶ms,
)
.expect("LET override query");
assert_eq!(results.len(), 5);
for r in &results {
let payload = r.point.payload.as_ref().expect("payload should exist");
let idx_val = payload.get("idx").expect("idx field should exist");
let v = idx_val.as_f64().expect("idx should be a number");
assert!(
(v - 99.0).abs() < 1e-5,
"idx should be 99.0 (LET override), got {v}"
);
}
}
#[test]
fn test_multiple_let_bindings_in_projection() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET a = 1.0 LET b = 2.0 \
SELECT * FROM docs WHERE vector NEAR $v LIMIT 5",
¶ms,
)
.expect("multiple LET query");
assert_eq!(results.len(), 5);
for r in &results {
let payload = r.point.payload.as_ref().expect("payload should exist");
let a_val = payload
.get("a")
.expect("a field should exist")
.as_f64()
.expect("a should be number");
let b_val = payload
.get("b")
.expect("b field should exist")
.as_f64()
.expect("b should be number");
assert!((a_val - 1.0).abs() < 1e-5, "a should be 1.0, got {a_val}");
assert!((b_val - 2.0).abs() < 1e-5, "b should be 2.0, got {b_val}");
}
}
#[test]
fn test_let_similarity_binding_in_projection() {
let (_dir, col) = setup_let_collection();
let mut params = HashMap::new();
params.insert("v".to_string(), serde_json::json!([0.5, 0.5, 0.5, 0.3]));
let results = col
.execute_query_str(
"LET s = similarity() \
SELECT * FROM docs WHERE vector NEAR $v ORDER BY s DESC LIMIT 5",
¶ms,
)
.expect("LET similarity projection query");
assert_eq!(results.len(), 5);
for r in &results {
let payload = r.point.payload.as_ref().expect("payload should exist");
let s_val = payload
.get("s")
.expect("s field should exist")
.as_f64()
.expect("s should be number");
assert!(s_val > 0.0, "similarity should be positive, got {s_val}");
assert!(
s_val <= 1.0 + 1e-5,
"similarity should be ≤ 1.0, got {s_val}"
);
}
}