use motedb::{types::Value, Database};
use tempfile::TempDir;
fn rows(result: motedb::StreamingQueryResult) -> Vec<Vec<Value>> {
use motedb::QueryResult;
match result.materialize().unwrap() {
QueryResult::Select { rows, .. } => rows,
_ => panic!("Expected Select result"),
}
}
fn setup_sales_db() -> (Database, TempDir) {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE sales (id INT PRIMARY KEY, product TEXT, category TEXT, amount FLOAT, quantity INT)").unwrap();
db.execute("INSERT INTO sales VALUES (1, 'Widget', 'Hardware', 29.99, 10)")
.unwrap();
db.execute("INSERT INTO sales VALUES (2, 'Gadget', 'Hardware', 49.99, 5)")
.unwrap();
db.execute("INSERT INTO sales VALUES (3, 'Book', 'Media', 19.99, 20)")
.unwrap();
db.execute("INSERT INTO sales VALUES (4, 'CD', 'Media', 14.99, 15)")
.unwrap();
db.execute("INSERT INTO sales VALUES (5, 'Cable', 'Hardware', 9.99, 50)")
.unwrap();
(db, dir)
}
#[test]
fn test_group_by_basic() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, COUNT(*) FROM sales GROUP BY category")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 2, "Should group into 2 categories");
}
#[test]
fn test_group_by_with_sum() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, SUM(amount) FROM sales GROUP BY category")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 2);
for row in &r {
if let Value::Text(s) = &row[0] {
if s.as_str() == "Hardware" {
if let Value::Float(sum) = &row[1] {
assert!(
(sum - 89.97).abs() < 0.01,
"Hardware SUM should be ~89.97, got {}",
sum
);
}
}
}
}
}
#[test]
fn test_group_by_with_avg() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, AVG(quantity) FROM sales GROUP BY category")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 2);
for row in &r {
if let Value::Text(s) = &row[0] {
if s.as_str() == "Hardware" {
if let Value::Float(avg) = &row[1] {
assert!(
(avg - 21.666).abs() < 0.1,
"Hardware AVG should be ~21.67, got {}",
avg
);
}
}
}
}
}
#[test]
fn test_group_by_with_min_max() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, MIN(amount), MAX(amount) FROM sales GROUP BY category")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 2);
for row in &r {
if let Value::Text(s) = &row[0] {
if s.as_str() == "Hardware" {
if let (Value::Float(min), Value::Float(max)) = (&row[1], &row[2]) {
assert!((min - 9.99).abs() < 0.01);
assert!((max - 49.99).abs() < 0.01);
}
}
}
}
}
#[test]
fn test_group_by_multiple_columns() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, product, COUNT(*) FROM sales GROUP BY category, product")
.unwrap();
let r = rows(result);
assert_eq!(
r.len(),
5,
"GROUP BY multiple columns should produce 5 groups"
);
}
#[test]
fn test_having_basic() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, COUNT(*) FROM sales GROUP BY category HAVING COUNT(*) > 2")
.unwrap();
let r = rows(result);
assert!(r.len() <= 2);
}
#[test]
fn test_having_with_sum() {
let (db, _dir) = setup_sales_db();
let result = db
.execute(
"SELECT category, SUM(amount) FROM sales GROUP BY category HAVING SUM(amount) > 50",
)
.unwrap();
let r = rows(result);
assert!(r.len() <= 2);
}
#[test]
fn test_having_filters_all() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, COUNT(*) FROM sales GROUP BY category HAVING COUNT(*) > 100")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 0, "No group should pass HAVING COUNT(*) > 100");
}
#[test]
fn test_aggregate_without_group_by() {
let (db, _dir) = setup_sales_db();
let result = db.execute("SELECT COUNT(*) FROM sales").unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
assert_eq!(&r[0][0], &Value::Integer(5));
let result = db.execute("SELECT SUM(quantity) FROM sales").unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
if let Value::Integer(sum) = &r[0][0] {
assert_eq!(*sum, 100); }
let result = db.execute("SELECT AVG(amount) FROM sales").unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
if let Value::Float(avg) = &r[0][0] {
let expected = (29.99 + 49.99 + 19.99 + 14.99 + 9.99) / 5.0;
assert!((avg - expected).abs() < 0.01);
}
}
#[test]
fn test_count_distinct() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT COUNT(DISTINCT category) FROM sales")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
assert_eq!(&r[0][0], &Value::Integer(2)); }
#[test]
fn test_count_column_nulls() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
db.execute("INSERT INTO t VALUES (2, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (3, 'b')").unwrap();
let result = db.execute("SELECT COUNT(*), COUNT(val) FROM t").unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(3)); assert_eq!(&r[0][1], &Value::Integer(2)); }
#[test]
fn test_aggregate_empty_table() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE empty (id INT PRIMARY KEY, val INT)")
.unwrap();
let result = db
.execute("SELECT COUNT(*), SUM(val), AVG(val), MIN(val), MAX(val) FROM empty")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 1, "Aggregate on empty table should return 1 row");
assert_eq!(&r[0][0], &Value::Integer(0)); assert!(
matches!(&r[0][1], Value::Null),
"SUM on empty should be NULL"
);
assert!(
matches!(&r[0][2], Value::Null),
"AVG on empty should be NULL"
);
}
#[test]
fn test_group_by_order_by() {
let (db, _dir) = setup_sales_db();
let result = db
.execute("SELECT category, SUM(amount) FROM sales GROUP BY category ORDER BY category")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 2);
}