use crate::velesql::{ArithmeticExpr, ArithmeticOp, OrderByExpr, Parser};
#[test]
fn test_parse_single_let_literal() {
let sql = "LET x = 0.5 SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse LET literal");
assert_eq!(query.let_bindings.len(), 1);
assert_eq!(query.let_bindings[0].name, "x");
assert!(
matches!(&query.let_bindings[0].expr, ArithmeticExpr::Literal(v) if (*v - 0.5).abs() < 1e-9),
"expected Literal(0.5), got {:?}",
query.let_bindings[0].expr
);
assert_eq!(query.select.from, "docs");
}
#[test]
fn test_parse_single_let_variable() {
let sql = "LET x = vector_score SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse LET variable");
assert_eq!(query.let_bindings.len(), 1);
assert_eq!(query.let_bindings[0].name, "x");
assert_eq!(
query.let_bindings[0].expr,
ArithmeticExpr::Variable("vector_score".to_string())
);
}
#[test]
fn test_parse_single_let_arithmetic() {
let sql = "LET hybrid = 0.7 * vector_score + 0.3 * bm25_score SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse LET arithmetic");
assert_eq!(query.let_bindings.len(), 1);
assert_eq!(query.let_bindings[0].name, "hybrid");
assert!(
matches!(
&query.let_bindings[0].expr,
ArithmeticExpr::BinaryOp {
op: ArithmeticOp::Add,
..
}
),
"expected top-level Add, got {:?}",
query.let_bindings[0].expr
);
}
#[test]
fn test_parse_single_let_similarity() {
let sql = "LET x = similarity() SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse LET similarity");
assert_eq!(query.let_bindings.len(), 1);
assert_eq!(query.let_bindings[0].name, "x");
assert!(
matches!(
&query.let_bindings[0].expr,
ArithmeticExpr::Similarity(inner) if matches!(inner.as_ref(), OrderByExpr::SimilarityBare)
),
"expected Similarity(SimilarityBare), got {:?}",
query.let_bindings[0].expr
);
}
#[test]
fn test_parse_multiple_let() {
let sql = "LET a = 1.0 LET b = a * 2.0 SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse multiple LET");
assert_eq!(query.let_bindings.len(), 2);
assert_eq!(query.let_bindings[0].name, "a");
assert!(matches!(
&query.let_bindings[0].expr,
ArithmeticExpr::Literal(v) if (*v - 1.0).abs() < 1e-9
));
assert_eq!(query.let_bindings[1].name, "b");
assert!(matches!(
&query.let_bindings[1].expr,
ArithmeticExpr::BinaryOp {
op: ArithmeticOp::Mul,
..
}
));
}
#[test]
fn test_parse_let_with_match() {
let sql = "LET x = 0.5 MATCH (a)-[r]->(b) RETURN a LIMIT 5";
let query = Parser::parse(sql).expect("should parse LET with MATCH");
assert_eq!(query.let_bindings.len(), 1);
assert_eq!(query.let_bindings[0].name, "x");
assert!(query.is_match_query());
}
#[test]
fn test_parse_no_let() {
let sql = "SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse without LET");
assert!(query.let_bindings.is_empty());
}
#[test]
fn test_let_name_is_case_sensitive() {
let sql = "LET MyScore = 0.5 SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse case-sensitive name");
assert_eq!(query.let_bindings[0].name, "MyScore");
}
#[test]
fn test_parse_let_nested_parentheses() {
let sql = "LET x = (0.7 * (vector_score + 0.1)) SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse nested parens");
assert_eq!(query.let_bindings.len(), 1);
assert_eq!(query.let_bindings[0].name, "x");
}
#[test]
fn test_let_keyword_case_insensitive() {
let sql = "let x = 0.5 SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse lowercase let");
assert_eq!(query.let_bindings.len(), 1);
assert_eq!(query.let_bindings[0].name, "x");
}
#[test]
fn test_let_with_all_clauses() {
let sql = "LET x = similarity() \
SELECT * FROM docs \
WHERE vector NEAR $v \
ORDER BY x DESC \
LIMIT 10 OFFSET 5 \
WITH (mode = 'fast')";
let query = Parser::parse(sql).expect("should parse LET with all clauses");
assert_eq!(query.let_bindings.len(), 1);
assert!(query.select.order_by.is_some());
assert!(query.select.offset.is_some());
assert!(query.select.with_clause.is_some());
}
#[test]
fn test_let_binding_serde_roundtrip() {
let sql = "LET hybrid = 0.7 * vector_score + 0.3 * bm25_score SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse");
let json = serde_json::to_string(&query).expect("should serialize");
let roundtrip: crate::velesql::Query = serde_json::from_str(&json).expect("should deserialize");
assert_eq!(query, roundtrip);
}
#[test]
fn test_let_binding_serde_backward_compat() {
let json = r#"{"select":{"distinct":"None","columns":"All","from":"docs","from_alias":[],"joins":[],"where_clause":null,"order_by":null,"limit":5,"offset":null,"with_clause":null,"group_by":null,"having":null,"fusion_clause":null},"compound":null,"match_clause":null,"dml":null}"#;
let query: crate::velesql::Query = serde_json::from_str(json).expect("should deserialize");
assert!(query.let_bindings.is_empty());
}
#[test]
fn test_parse_let_integer_literal() {
let sql = "LET threshold = 1 SELECT * FROM docs LIMIT 5";
let query = Parser::parse(sql).expect("should parse LET integer");
assert_eq!(query.let_bindings.len(), 1);
assert!(matches!(
&query.let_bindings[0].expr,
ArithmeticExpr::Literal(v) if (*v - 1.0).abs() < 1e-9
));
}