use crate::velesql::{ArithmeticExpr, ArithmeticOp, OrderByExpr, Parser};
#[test]
fn test_orderby_multiple_columns() {
let sql = "SELECT * FROM products ORDER BY category ASC, price DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 2, "Should have 2 ORDER BY items");
assert!(matches!(&order_by[0].expr, OrderByExpr::Field(f) if f == "category"));
assert!(!order_by[0].descending, "category should be ASC");
assert!(matches!(&order_by[1].expr, OrderByExpr::Field(f) if f == "price"));
assert!(order_by[1].descending, "price should be DESC");
}
#[test]
fn test_orderby_with_aggregate() {
let sql = "SELECT category, COUNT(*) FROM products GROUP BY category ORDER BY COUNT(*) DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(order_by[0].descending, "Should be DESC");
assert!(
matches!(&order_by[0].expr, OrderByExpr::Aggregate(_)),
"Should be Aggregate variant"
);
}
#[test]
fn test_orderby_mixed_columns_and_aggregates() {
let sql = "SELECT category, COUNT(*), AVG(price) FROM products GROUP BY category ORDER BY COUNT(*) DESC, category ASC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 2, "Should have 2 ORDER BY items");
assert!(order_by[0].descending);
assert!(!order_by[1].descending);
}
#[test]
fn test_orderby_aggregate_with_column_arg() {
let sql =
"SELECT category, SUM(price) FROM products GROUP BY category ORDER BY SUM(price) DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(order_by[0].descending);
}
#[test]
fn test_orderby_default_direction_is_asc() {
let sql = "SELECT * FROM products ORDER BY price, category";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 2);
assert!(matches!(&order_by[0].expr, OrderByExpr::Field(f) if f == "price"));
assert!(!order_by[0].descending, "Default should be ASC");
assert!(matches!(&order_by[1].expr, OrderByExpr::Field(f) if f == "category"));
assert!(!order_by[1].descending, "Default should be ASC");
}
#[test]
fn test_orderby_similarity_with_column() {
let sql = "SELECT * FROM products ORDER BY similarity(embedding, $query) DESC, price ASC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 2);
assert!(matches!(&order_by[0].expr, OrderByExpr::Similarity(_)));
assert!(order_by[0].descending, "similarity should be DESC");
assert!(matches!(&order_by[1].expr, OrderByExpr::Field(f) if f == "price"));
assert!(!order_by[1].descending, "price should be ASC");
}
#[test]
fn test_parse_order_by_simple_arithmetic() {
let sql = "SELECT * FROM docs ORDER BY 0.7 * vector_score + 0.3 * graph_score DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(order_by[0].descending, "Should be DESC");
match &order_by[0].expr {
OrderByExpr::Arithmetic(ArithmeticExpr::BinaryOp { left, op, right }) => {
assert_eq!(*op, ArithmeticOp::Add);
match left.as_ref() {
ArithmeticExpr::BinaryOp {
left: ll,
op: lo,
right: lr,
} => {
assert_eq!(*lo, ArithmeticOp::Mul);
assert!(
matches!(ll.as_ref(), ArithmeticExpr::Literal(v) if (*v - 0.7).abs() < 1e-9)
);
assert!(
matches!(lr.as_ref(), ArithmeticExpr::Variable(n) if n == "vector_score")
);
}
other => panic!("Expected BinaryOp(Mul), got {other:?}"),
}
match right.as_ref() {
ArithmeticExpr::BinaryOp {
left: rl,
op: ro,
right: rr,
} => {
assert_eq!(*ro, ArithmeticOp::Mul);
assert!(
matches!(rl.as_ref(), ArithmeticExpr::Literal(v) if (*v - 0.3).abs() < 1e-9)
);
assert!(
matches!(rr.as_ref(), ArithmeticExpr::Variable(n) if n == "graph_score")
);
}
other => panic!("Expected BinaryOp(Mul), got {other:?}"),
}
}
other => panic!("Expected Arithmetic, got {other:?}"),
}
}
#[test]
fn test_parse_order_by_parenthesized() {
let sql = "SELECT * FROM docs ORDER BY (vector_score + graph_score) / 2 DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(order_by[0].descending);
match &order_by[0].expr {
OrderByExpr::Arithmetic(ArithmeticExpr::BinaryOp { left, op, right }) => {
assert_eq!(*op, ArithmeticOp::Div);
match left.as_ref() {
ArithmeticExpr::BinaryOp {
left: ll,
op: lo,
right: lr,
} => {
assert_eq!(*lo, ArithmeticOp::Add);
assert!(
matches!(ll.as_ref(), ArithmeticExpr::Variable(n) if n == "vector_score")
);
assert!(
matches!(lr.as_ref(), ArithmeticExpr::Variable(n) if n == "graph_score")
);
}
other => panic!("Expected BinaryOp(Add), got {other:?}"),
}
assert!(
matches!(right.as_ref(), ArithmeticExpr::Literal(v) if (*v - 2.0).abs() < 1e-9)
);
}
other => panic!("Expected Arithmetic, got {other:?}"),
}
}
#[test]
fn test_parse_order_by_single_variable_remains_field() {
let sql = "SELECT * FROM products ORDER BY price ASC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(!order_by[0].descending, "Should be ASC");
assert!(
matches!(&order_by[0].expr, OrderByExpr::Field(f) if f == "price"),
"Single identifier must collapse to Field, got {:?}",
order_by[0].expr
);
}
#[test]
fn test_parse_order_by_similarity_bare_in_arithmetic() {
let sql =
"SELECT * FROM docs WHERE vector NEAR $v ORDER BY 0.5 * similarity() + 0.5 * bm25_score DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(order_by[0].descending);
match &order_by[0].expr {
OrderByExpr::Arithmetic(ArithmeticExpr::BinaryOp { left, op, right }) => {
assert_eq!(*op, ArithmeticOp::Add);
match left.as_ref() {
ArithmeticExpr::BinaryOp {
left: ll,
op: lo,
right: lr,
} => {
assert_eq!(*lo, ArithmeticOp::Mul);
assert!(
matches!(ll.as_ref(), ArithmeticExpr::Literal(v) if (*v - 0.5).abs() < 1e-9)
);
assert!(
matches!(lr.as_ref(), ArithmeticExpr::Similarity(inner) if **inner == OrderByExpr::SimilarityBare),
"Expected Similarity(SimilarityBare), got {:?}",
lr
);
}
other => panic!("Expected BinaryOp(Mul), got {other:?}"),
}
match right.as_ref() {
ArithmeticExpr::BinaryOp {
left: rl,
op: ro,
right: rr,
} => {
assert_eq!(*ro, ArithmeticOp::Mul);
assert!(
matches!(rl.as_ref(), ArithmeticExpr::Literal(v) if (*v - 0.5).abs() < 1e-9)
);
assert!(
matches!(rr.as_ref(), ArithmeticExpr::Variable(n) if n == "bm25_score")
);
}
other => panic!("Expected BinaryOp(Mul), got {other:?}"),
}
}
other => panic!("Expected Arithmetic, got {other:?}"),
}
}
#[test]
fn test_parse_order_by_similarity_bare_alone_remains_bare() {
let sql = "SELECT * FROM docs WHERE vector NEAR $v ORDER BY similarity() DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(order_by[0].descending);
assert_eq!(
order_by[0].expr,
OrderByExpr::SimilarityBare,
"Bare similarity() must collapse to SimilarityBare, got {:?}",
order_by[0].expr,
);
}
#[test]
fn test_parse_order_by_literal_only() {
let sql = "SELECT * FROM docs ORDER BY 42 DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
assert!(
matches!(&order_by[0].expr, OrderByExpr::Arithmetic(ArithmeticExpr::Literal(v)) if (*v - 42.0).abs() < 1e-9),
"Expected Arithmetic(Literal(42)), got {:?}",
order_by[0].expr
);
}
#[test]
fn test_parse_order_by_subtraction() {
let sql = "SELECT * FROM docs ORDER BY vector_score - penalty DESC";
let result = Parser::parse(sql);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let query = result.unwrap();
let order_by = query.select.order_by.expect("ORDER BY should be present");
assert_eq!(order_by.len(), 1);
match &order_by[0].expr {
OrderByExpr::Arithmetic(ArithmeticExpr::BinaryOp { op, .. }) => {
assert_eq!(*op, ArithmeticOp::Sub);
}
other => panic!("Expected Arithmetic(BinaryOp(Sub)), got {other:?}"),
}
}