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 msg(result: motedb::StreamingQueryResult) -> String {
match result {
motedb::StreamingQueryResult::Definition { message } => message,
_ => panic!("Expected Definition"),
}
}
#[test]
fn test_show_tables() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE alpha (id INT PRIMARY KEY)")
.unwrap();
db.execute("CREATE TABLE beta (id INT PRIMARY KEY)")
.unwrap();
let result = db.execute("SHOW TABLES");
assert!(result.is_ok(), "SHOW TABLES should not error");
}
#[test]
fn test_show_tables_empty() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
let result = db.execute("SHOW TABLES");
assert!(result.is_ok(), "SHOW TABLES should not error on empty db");
}
#[test]
fn test_describe_table() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE users (id INT PRIMARY KEY, name TEXT, age INT, score FLOAT)")
.unwrap();
let result = db.execute("DESCRIBE users");
assert!(result.is_ok(), "DESCRIBE should not error");
}
#[test]
fn test_desc_alias() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY)").unwrap();
let result = db.execute("DESC t");
assert!(result.is_ok(), "DESC should work as alias for DESCRIBE");
}
#[test]
fn test_describe_nonexistent_table() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
let result = db.execute("DESCRIBE nonexistent");
assert!(result.is_err(), "DESCRIBE nonexistent table should error");
}
#[test]
fn test_alter_table_auto_increment() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE counters (id INT PRIMARY KEY AUTO_INCREMENT, val TEXT)")
.unwrap();
db.execute("INSERT INTO counters (val) VALUES ('first')")
.unwrap();
db.execute("INSERT INTO counters (val) VALUES ('second')")
.unwrap();
let result = db
.execute("ALTER TABLE counters AUTO_INCREMENT = 100")
.unwrap();
let m = msg(result);
assert!(
m.contains("100"),
"Should confirm AUTO_INCREMENT set to 100"
);
db.execute("INSERT INTO counters (val) VALUES ('third')")
.unwrap();
let result = db
.execute("SELECT id FROM counters WHERE val = 'third'")
.unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(100));
}
#[test]
fn test_alter_table_no_auto_increment() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE plain (id INT PRIMARY KEY, val TEXT)")
.unwrap();
let result = db.execute("ALTER TABLE plain AUTO_INCREMENT = 10");
assert!(
result.is_err(),
"ALTER AUTO_INCREMENT on non-AUTO_INCREMENT table should error"
);
}
#[test]
fn test_multi_row_insert() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE items (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO items VALUES (1, 'apple'), (2, 'banana'), (3, 'cherry')")
.unwrap();
let result = db.execute("SELECT COUNT(*) FROM items").unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(3));
}
#[test]
fn test_multi_row_insert_with_columns() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE items (id INT PRIMARY KEY, name TEXT, price FLOAT)")
.unwrap();
db.execute("INSERT INTO items (id, name) VALUES (1, 'a'), (2, 'b')")
.unwrap();
let result = db.execute("SELECT id, name, price FROM items").unwrap();
let r = rows(result);
assert_eq!(r.len(), 2);
for row in &r {
assert!(matches!(&row[2], Value::Null), "price should be NULL");
}
}
#[test]
fn test_where_in_subquery() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE orders (id INT PRIMARY KEY, customer_id INT, total FLOAT)")
.unwrap();
db.execute("INSERT INTO orders VALUES (1, 10, 100), (2, 20, 200), (3, 10, 150)")
.unwrap();
db.execute("CREATE TABLE vip_customers (id INT PRIMARY KEY)")
.unwrap();
db.execute("INSERT INTO vip_customers VALUES (10)").unwrap();
let result =
db.execute("SELECT id FROM orders WHERE customer_id IN (SELECT id FROM vip_customers)");
match result {
Ok(r) => {
match r.materialize() {
Ok(motedb::QueryResult::Select { rows, .. }) => {
assert!(rows.len() <= 3, "IN subquery should find at most 3 matches");
}
_ => {} }
}
Err(_) => {
}
}
}
#[test]
fn test_from_subquery() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE sales (id INT PRIMARY KEY, amount FLOAT)")
.unwrap();
db.execute("INSERT INTO sales VALUES (1, 100), (2, 200), (3, 300)")
.unwrap();
let result = db
.execute("SELECT SUM(sub.amount) FROM (SELECT amount FROM sales WHERE amount > 100) AS sub")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
if let Value::Float(sum) = &r[0][0] {
assert!(
(sum - 500.0).abs() < 0.01,
"SUM of amount > 100 should be 500, got {}",
sum
);
}
}
#[test]
fn test_scalar_subquery() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE products (id INT PRIMARY KEY, price FLOAT)")
.unwrap();
db.execute("INSERT INTO products VALUES (1, 10), (2, 20), (3, 30)")
.unwrap();
let result = db
.execute("SELECT id, price FROM products WHERE price > (SELECT AVG(price) FROM products)")
.unwrap();
let r = rows(result);
assert!(r.len() <= 2);
}
#[test]
fn test_data_type_aliases() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR)")
.unwrap();
db.execute("INSERT INTO t1 VALUES (1, 'hello')").unwrap();
db.execute("CREATE TABLE t2 (id INT PRIMARY KEY, name STRING)")
.unwrap();
db.execute("INSERT INTO t2 VALUES (1, 'world')").unwrap();
db.execute("CREATE TABLE t3 (id INT PRIMARY KEY, val REAL)")
.unwrap();
db.execute("INSERT INTO t3 VALUES (1, 3.14)").unwrap();
db.execute("CREATE TABLE t4 (id INT PRIMARY KEY, val DOUBLE)")
.unwrap();
db.execute("INSERT INTO t4 VALUES (1, 2.718)").unwrap();
db.execute("CREATE TABLE t5 (id INT PRIMARY KEY, big BIGINT)")
.unwrap();
db.execute("INSERT INTO t5 VALUES (1, 9999999999)").unwrap();
for table in &["t1", "t3", "t4", "t5"] {
let result = db.execute(&format!("SELECT COUNT(*) FROM {}", table));
if let Ok(r) = result {
let r = rows(r);
assert!(r.len() >= 1);
}
}
}
#[test]
fn test_boolean_type() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE flags (id INT PRIMARY KEY, active BOOLEAN)")
.unwrap();
db.execute("INSERT INTO flags VALUES (1, TRUE)").unwrap();
db.execute("INSERT INTO flags VALUES (2, FALSE)").unwrap();
db.execute("INSERT INTO flags VALUES (3, NULL)").unwrap();
let result = db
.execute("SELECT id FROM flags WHERE active = TRUE")
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
assert_eq!(&r[0][0], &Value::Integer(1));
}
#[test]
fn test_latest_by() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE readings (id INT PRIMARY KEY, device TEXT, ts INT, value FLOAT)")
.unwrap();
db.execute("INSERT INTO readings VALUES (1, 'sensor_a', 1000, 10.0)")
.unwrap();
db.execute("INSERT INTO readings VALUES (2, 'sensor_a', 2000, 20.0)")
.unwrap();
db.execute("INSERT INTO readings VALUES (3, 'sensor_b', 1000, 30.0)")
.unwrap();
db.execute("INSERT INTO readings VALUES (4, 'sensor_b', 3000, 40.0)")
.unwrap();
let result = db.execute("SELECT device, value FROM readings LATEST BY device ORDER BY device");
match result {
Ok(r) => {
let r = rows(r);
assert!(r.len() >= 1, "LATEST BY should return at least 1 row");
}
Err(_) => {
}
}
}
#[test]
fn test_drop_and_recreate() {
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, 'original')").unwrap();
db.execute("DROP TABLE t").unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 42)").unwrap();
let result = db.execute("SELECT val FROM t").unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(42));
}
#[test]
fn test_select_column_alias() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 42)").unwrap();
let result = db.execute("SELECT val AS value, id AS identifier FROM t");
assert!(result.is_ok(), "SELECT with aliases should not error");
}