use super::*;
use crate::parser::{parse_select, CmpOp, DateTimeField, Expr, Literal};
use crate::planner::{plan_select, TableStats};
use alloc::string::ToString;
fn users() -> Table {
let mut t = Table::new(alloc::vec!["id".to_string(), "age".to_string()]);
for i in 0..10 {
t.insert(alloc::vec![Value::Int(i), Value::Int(i * 5)]);
}
t
}
fn run(sql: &str, table: &Table) -> ResultSet {
let stmt = parse_select(sql).unwrap();
let plan = plan_select(
&stmt,
TableStats {
row_count: table.rows.len() as u64,
},
);
execute(&plan, table).unwrap()
}
#[test]
fn select_star_returns_all() {
let t = users();
let r = run("SELECT * FROM users", &t);
assert_eq!(r.rows.len(), 10);
assert_eq!(r.columns, t.columns);
}
#[test]
fn filter_and_project() {
let t = users();
let r = run("SELECT id FROM users WHERE age >= 25", &t);
assert_eq!(r.columns, alloc::vec!["id".to_string()]);
assert_eq!(r.rows.len(), 5);
assert_eq!(r.rows[0], alloc::vec![Value::Int(5)]);
}
#[test]
fn limit_truncates() {
let t = users();
let r = run("SELECT * FROM users LIMIT 3", &t);
assert_eq!(r.rows.len(), 3);
}
#[test]
fn unknown_column_errors() {
let t = users();
let stmt = parse_select("SELECT nope FROM users").unwrap();
let plan = plan_select(&stmt, TableStats { row_count: 10 });
assert_eq!(
execute(&plan, &t),
Err(ExecError::UnknownColumn("nope".to_string()))
);
}
#[test]
fn vector_similarity_topk() {
let embeddings = alloc::vec![
alloc::vec![1.0, 0.0],
alloc::vec![0.0, 1.0],
alloc::vec![0.9, 0.1],
];
let q = alloc::vec![1.0, 0.0];
let top = vector_topk(&embeddings, &q, 2);
assert_eq!(top[0], 0);
assert_eq!(top[1], 2);
}
#[test]
fn evaluates_and_or_expressions() {
let mut t = Table::new(alloc::vec![
"id".to_string(),
"a".to_string(),
"b".to_string(),
]);
for i in 0..10 {
t.insert(alloc::vec![Value::Int(i), Value::Int(i), Value::Int(i * 2),]);
}
let r = run("SELECT * FROM t WHERE a > 3 AND b < 12", &t);
assert_eq!(r.rows.len(), 2);
}
#[test]
fn evaluates_in_list() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
for i in 0..5 {
t.insert(alloc::vec![Value::Int(i), Value::Int(i * 10)]);
}
let r = run("SELECT * FROM t WHERE id IN (1, 3)", &t);
assert_eq!(r.rows.len(), 2);
}
#[test]
fn evaluates_between() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
for i in 0..10 {
t.insert(alloc::vec![Value::Int(i), Value::Int(i)]);
}
let r = run("SELECT * FROM t WHERE x BETWEEN 3 AND 7", &t);
assert_eq!(r.rows.len(), 5);
}
#[test]
fn evaluates_like() {
let mut t = Table::new(alloc::vec!["id".to_string(), "name".to_string()]);
t.insert(alloc::vec![Value::Int(0), Value::Text("alice".to_string()),]);
t.insert(alloc::vec![Value::Int(1), Value::Text("bob".to_string()),]);
t.insert(alloc::vec![
Value::Int(2),
Value::Text("alicia".to_string()),
]);
let r = run("SELECT * FROM t WHERE name LIKE 'al%'", &t);
assert_eq!(r.rows.len(), 2);
}
#[test]
fn sort_ascending() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
t.insert(alloc::vec![Value::Int(2), Value::Int(20)]);
t.insert(alloc::vec![Value::Int(0), Value::Int(0)]);
t.insert(alloc::vec![Value::Int(1), Value::Int(10)]);
let r = run("SELECT * FROM t ORDER BY x ASC", &t);
assert_eq!(r.rows[0][1], Value::Int(0));
assert_eq!(r.rows[1][1], Value::Int(10));
assert_eq!(r.rows[2][1], Value::Int(20));
}
#[test]
fn sort_descending() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
t.insert(alloc::vec![Value::Int(0), Value::Int(10)]);
t.insert(alloc::vec![Value::Int(1), Value::Int(30)]);
t.insert(alloc::vec![Value::Int(2), Value::Int(20)]);
let r = run("SELECT * FROM t ORDER BY x DESC", &t);
assert_eq!(r.rows[0][1], Value::Int(30));
assert_eq!(r.rows[1][1], Value::Int(20));
assert_eq!(r.rows[2][1], Value::Int(10));
}
#[test]
fn order_by_non_select_column_errors() {
let t = users();
let stmt = parse_select("SELECT id FROM users ORDER BY nonexistent ASC").unwrap();
let plan = plan_select(&stmt, TableStats { row_count: 10 });
assert!(matches!(
execute(&plan, &t),
Err(ExecError::UnknownColumn(_))
));
}
#[test]
fn aggregate_count() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
for i in 0..5 {
t.insert(alloc::vec![Value::Int(i), Value::Int(i)]);
}
let r = run("SELECT COUNT(*) FROM t", &t);
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Int(5));
}
#[test]
fn aggregate_sum() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
for i in 1..=4 {
t.insert(alloc::vec![Value::Int(i), Value::Int(i)]);
}
let r = run("SELECT SUM(x) FROM t", &t);
assert_eq!(r.rows[0][0], Value::Int(10));
}
#[test]
fn aggregate_min_max_over_all_null_group_is_null() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
t.insert(alloc::vec![Value::Int(0), Value::Null]);
t.insert(alloc::vec![Value::Int(1), Value::Null]);
let r = run("SELECT MIN(x), MAX(x) FROM t", &t);
assert_eq!(r.rows[0][0], Value::Null);
assert_eq!(r.rows[0][1], Value::Null);
}
#[test]
fn text_comparison_compares_content_not_length() {
let mut t = Table::new(alloc::vec!["id".to_string(), "name".to_string()]);
t.insert(alloc::vec![Value::Int(0), Value::Text("bob".to_string())]);
t.insert(alloc::vec![Value::Int(1), Value::Text("amy".to_string())]);
let r = run("SELECT id FROM t WHERE name = 'bob'", &t);
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Int(0));
}
#[test]
fn is_null_is_false_for_non_null_int() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
t.insert(alloc::vec![Value::Int(0), Value::Int(5)]);
t.insert(alloc::vec![Value::Int(1), Value::Null]);
let r = run("SELECT id FROM t WHERE x IS NULL", &t);
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Int(1));
}
#[test]
fn is_not_null_negates() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string()]);
t.insert(alloc::vec![Value::Int(0), Value::Int(5)]);
t.insert(alloc::vec![Value::Int(1), Value::Null]);
let r = run("SELECT id FROM t WHERE x IS NOT NULL", &t);
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Int(0));
}
#[test]
fn not_negates_expression() {
let mut t = Table::new(alloc::vec!["id".to_string(), "x".to_string(),]);
for i in 0..5 {
t.insert(alloc::vec![Value::Int(i), Value::Int(i)]);
}
let r = run("SELECT id FROM t WHERE NOT x > 2", &t);
assert_eq!(r.rows.len(), 3);
}
#[test]
fn kleene_null_and_true_propagates_null() {
let mut t = Table::new(alloc::vec![
"id".to_string(),
"a".to_string(),
"b".to_string(),
]);
t.insert(alloc::vec![Value::Int(0), Value::Null, Value::Int(1)]);
t.insert(alloc::vec![Value::Int(1), Value::Int(5), Value::Int(1)]);
let r = run("SELECT id FROM t WHERE a = 5 AND b = 1", &t);
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Int(1));
}
#[test]
fn kleene_null_or_false_propagates_null() {
let mut t = Table::new(alloc::vec![
"id".to_string(),
"a".to_string(),
"b".to_string(),
]);
t.insert(alloc::vec![Value::Int(0), Value::Null, Value::Int(0)]);
t.insert(alloc::vec![Value::Int(1), Value::Int(5), Value::Int(0)]);
let r = run("SELECT id FROM t WHERE a = 5 OR b = 1", &t);
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Int(1));
}
#[test]
fn eval_scalar_returns_none_for_null_comparison() {
let row = alloc::vec![Value::Null];
let cols = alloc::vec!["x".to_string()];
let expr = Expr::Cmp {
column: "x".to_string(),
op: CmpOp::Eq,
value: Literal::Null,
};
assert!(eval_scalar(&expr, &cols, &row).unwrap().is_none());
}
#[test]
fn eval_scalar_returns_int_for_true_comparison() {
let row = alloc::vec![Value::Int(5)];
let cols = alloc::vec!["x".to_string()];
let expr = Expr::Cmp {
column: "x".to_string(),
op: CmpOp::Eq,
value: Literal::Int(5),
};
assert_eq!(
eval_scalar(&expr, &cols, &row).unwrap(),
Some(Value::Int(1))
);
}
#[test]
fn extract_cmp_matches_correct_year() {
let row = alloc::vec![Value::Int(19723)]; let cols = alloc::vec!["created".to_string()];
let expr = Expr::ExtractCmp {
field: DateTimeField::Year,
source: "created".to_string(),
op: CmpOp::Eq,
value: Literal::Int(2024),
};
assert!(eval_expr(&expr, &cols, &row).unwrap());
}
#[test]
fn extract_cmp_rejects_wrong_year() {
let row = alloc::vec![Value::Int(19723)];
let cols = alloc::vec!["created".to_string()];
let expr = Expr::ExtractCmp {
field: DateTimeField::Year,
source: "created".to_string(),
op: CmpOp::Eq,
value: Literal::Int(2023),
};
assert!(!eval_expr(&expr, &cols, &row).unwrap());
}
#[test]
fn extract_cmp_works_with_hour_field() {
let row = alloc::vec![Value::Int(1_704_105_000_000_000)];
let cols = alloc::vec!["ts".to_string()];
let expr = Expr::ExtractCmp {
field: DateTimeField::Hour,
source: "ts".to_string(),
op: CmpOp::Eq,
value: Literal::Int(10),
};
assert!(eval_expr(&expr, &cols, &row).unwrap());
}
#[test]
fn extract_cmp_null_source_yields_null() {
let row = alloc::vec![Value::Null];
let cols = alloc::vec!["ts".to_string()];
let expr = Expr::ExtractCmp {
field: DateTimeField::Month,
source: "ts".to_string(),
op: CmpOp::Eq,
value: Literal::Int(1),
};
assert!(!eval_expr(&expr, &cols, &row).unwrap());
}