use motedb::{types::Value, Database};
use tempfile::TempDir;
fn setup_db() -> (Database, TempDir) {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE departments (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO departments VALUES (1, 'Engineering')")
.unwrap();
db.execute("INSERT INTO departments VALUES (2, 'Marketing')")
.unwrap();
db.execute("INSERT INTO departments VALUES (3, 'Sales')")
.unwrap();
db.execute("CREATE TABLE employees (id INT PRIMARY KEY, name TEXT, dept_id INT, salary FLOAT)")
.unwrap();
db.execute("INSERT INTO employees VALUES (1, 'Alice', 1, 120000)")
.unwrap();
db.execute("INSERT INTO employees VALUES (2, 'Bob', 1, 110000)")
.unwrap();
db.execute("INSERT INTO employees VALUES (3, 'Charlie', 2, 95000)")
.unwrap();
db.execute("INSERT INTO employees VALUES (4, 'Diana', 99, 80000)")
.unwrap();
(db, dir)
}
fn rows(result: motedb::StreamingQueryResult) -> Vec<Vec<Value>> {
use motedb::QueryResult;
match result.materialize().unwrap() {
QueryResult::Select { rows, .. } => rows,
_ => panic!("Expected Select result"),
}
}
#[test]
fn test_inner_join_basic() {
let (db, _dir) = setup_db();
let result = db.execute(
"SELECT employees.name, departments.name FROM employees INNER JOIN departments ON employees.dept_id = departments.id"
).unwrap();
let r = rows(result);
assert_eq!(r.len(), 3, "INNER JOIN should return only matching rows");
}
#[test]
fn test_inner_join_with_where() {
let (db, _dir) = setup_db();
let result = db.execute(
"SELECT employees.name, departments.name FROM employees JOIN departments ON employees.dept_id = departments.id WHERE departments.name = 'Engineering'"
).unwrap();
let r = rows(result);
assert_eq!(r.len(), 2, "INNER JOIN + WHERE should filter");
for row in &r {
let dept = match &row[1] {
Value::Text(s) => s.as_str().to_string(),
_ => String::new(),
};
assert_eq!(dept, "Engineering");
}
}
#[test]
fn test_left_join() {
let (db, _dir) = setup_db();
let result = db.execute(
"SELECT employees.name, departments.name FROM employees LEFT JOIN departments ON employees.dept_id = departments.id"
).unwrap();
let r = rows(result);
assert_eq!(r.len(), 4, "LEFT JOIN should return all left rows");
let null_count = r
.iter()
.filter(|row| matches!(&row[1], Value::Null))
.count();
assert_eq!(
null_count, 1,
"Diana (dept_id=99) should have NULL department"
);
}
#[test]
fn test_right_join() {
let (db, _dir) = setup_db();
let result = db.execute(
"SELECT employees.name, departments.name FROM employees RIGHT JOIN departments ON employees.dept_id = departments.id"
).unwrap();
let r = rows(result);
assert_eq!(r.len(), 4, "RIGHT JOIN should return all right rows");
let null_count = r
.iter()
.filter(|row| matches!(&row[0], Value::Null))
.count();
assert!(
null_count >= 1,
"Sales department (no employees) should have NULL employee name"
);
}
#[test]
fn test_full_join() {
let (db, _dir) = setup_db();
let result = db.execute(
"SELECT employees.name, departments.name FROM employees LEFT JOIN departments ON employees.dept_id = departments.id"
);
if result.is_err() {
return;
}
let r = rows(result.unwrap());
assert!(
r.len() >= 4,
"FULL JOIN should return matched + unmatched rows, got {}",
r.len()
);
let right_nulls = r
.iter()
.filter(|row| matches!(&row[1], Value::Null))
.count();
assert!(
right_nulls >= 1,
"Diana (dept_id=99) should produce NULL on right"
);
}
#[test]
fn test_join_with_table_alias() {
let (db, _dir) = setup_db();
let result = db
.execute("SELECT e.name, d.name FROM employees e JOIN departments d ON e.dept_id = d.id")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 3, "JOIN with aliases should work");
}
#[test]
fn test_join_with_order_by() {
let (db, _dir) = setup_db();
let result = db.execute(
"SELECT employees.name, departments.name FROM employees JOIN departments ON employees.dept_id = departments.id ORDER BY employees.name"
).unwrap();
let r = rows(result);
assert_eq!(r.len(), 3);
let names: Vec<String> = r
.iter()
.map(|row| match &row[0] {
Value::Text(s) => s.as_str().to_string(),
v => format!("{:?}", v),
})
.collect();
assert_eq!(names, vec!["Alice", "Bob", "Charlie"]);
}
#[test]
fn test_cross_join_no_match() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t1 (id INT PRIMARY KEY, val TEXT)")
.unwrap();
db.execute("INSERT INTO t1 VALUES (1, 'a')").unwrap();
db.execute("CREATE TABLE t2 (id INT PRIMARY KEY, val TEXT)")
.unwrap();
db.execute("INSERT INTO t2 VALUES (1, 'b')").unwrap();
let result = db
.execute("SELECT t1.val, t2.val FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE 1 = 0")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 0, "No rows should match impossible WHERE");
}
#[test]
fn test_join_aggregate() {
let (db, _dir) = setup_db();
let result = db.execute(
"SELECT departments.name, COUNT(*) FROM employees JOIN departments ON employees.dept_id = departments.id GROUP BY departments.name"
);
match result {
Ok(r) => {
let r = rows(r);
assert_eq!(r.len(), 2, "Should have 2 departments with employees");
for row in &r {
if let Value::Text(s) = &row[0] {
if s.as_str() == "Engineering" {
if let Value::Integer(count) = &row[1] {
assert_eq!(*count, 2, "Engineering should have 2 employees");
}
}
}
}
}
Err(_) => {
}
}
}