use motedb::{Database, types::Value, sql::QueryResult, config::DBConfig};
use tempfile::TempDir;
use std::time::Instant;
fn setup_db(dir: &std::path::Path) -> Database {
Database::create(dir.join("test.mote")).unwrap()
}
fn setup_db_fast(dir: &std::path::Path) -> Database {
Database::create_with_config(dir.join("test.mote"), DBConfig::for_testing()).unwrap()
}
fn exec(db: &Database, sql: &str) -> QueryResult {
db.execute(sql).unwrap().materialize().unwrap()
}
fn query_rows(db: &Database, sql: &str) -> Vec<Vec<Value>> {
match exec(db, sql) {
QueryResult::Select { rows, .. } => rows,
_ => vec![],
}
}
fn qr(db: &Database, sql: &str) -> Option<Vec<Value>> {
query_rows(db, sql).into_iter().next()
}
fn qv(db: &Database, sql: &str) -> Value {
let row = qr(db, sql).unwrap();
row.into_iter().next().unwrap()
}
#[test]
fn test_ddl_create_drop_create() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("DROP TABLE t").unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 'alice')").unwrap();
assert_eq!(qv(&db, "SELECT name FROM t WHERE id = 1"), Value::Text("alice".into()));
}
#[test]
fn test_ddl_multiple_tables() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
for i in 0..5 {
db.execute(&format!("CREATE TABLE t{} (id INT PRIMARY KEY, v INT)", i)).unwrap();
db.execute(&format!("INSERT INTO t{} VALUES (1, {})", i, i * 10)).unwrap();
}
for i in 0..5 {
let v = qv(&db, &format!("SELECT v FROM t{} WHERE id = 1", i));
assert_eq!(v, Value::Integer(i as i64 * 10));
}
db.execute("DROP TABLE t2").unwrap();
assert!(db.execute("SELECT * FROM t2").is_err());
assert_eq!(qv(&db, "SELECT v FROM t3 WHERE id = 1"), Value::Integer(30));
}
#[test]
fn test_ddl_table_with_all_types() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, i INT, f FLOAT, s TEXT, b INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, -42, 3.14, 'hello', 1)").unwrap();
db.execute("INSERT INTO t VALUES (2, 0, 0.0, '', 0)").unwrap();
db.execute("INSERT INTO t VALUES (3, NULL, NULL, NULL, NULL)").unwrap();
let rows = query_rows(&db, "SELECT * FROM t ORDER BY id");
assert_eq!(rows.len(), 3);
assert_eq!(rows[0][1], Value::Integer(-42));
assert_eq!(rows[0][3], Value::Text("hello".into()));
assert_eq!(rows[2][1], Value::Null);
}
#[test]
fn test_insert_auto_increment() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT, v TEXT)").unwrap();
for c in ['a', 'b', 'c', 'd', 'e'] {
db.execute(&format!("INSERT INTO t (v) VALUES ('{}')", c)).unwrap();
}
let rows = query_rows(&db, "SELECT * FROM t ORDER BY id");
assert_eq!(rows.len(), 5);
for (i, row) in rows.iter().enumerate() {
assert_eq!(row[0], Value::Integer(i as i64 + 1));
}
}
#[test]
fn test_insert_null_columns() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b TEXT, c FLOAT)").unwrap();
db.execute("INSERT INTO t VALUES (1, NULL, NULL, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (2, 10, NULL, 1.5)").unwrap();
db.execute("INSERT INTO t VALUES (3, NULL, 'text', NULL)").unwrap();
let rows = query_rows(&db, "SELECT * FROM t ORDER BY id");
assert_eq!(rows[0], vec![Value::Integer(1), Value::Null, Value::Null, Value::Null]);
assert_eq!(rows[1][1], Value::Integer(10));
assert_eq!(rows[1][2], Value::Null);
assert_eq!(rows[2][2], Value::Text("text".into()));
}
#[test]
fn test_insert_duplicate_pk_rejected() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
assert!(db.execute("INSERT INTO t VALUES (1, 20)").is_err());
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(1));
}
#[test]
fn test_insert_wrong_column_count() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
assert!(db.execute("INSERT INTO t VALUES (1)").is_err());
assert!(db.execute("INSERT INTO t VALUES (1, 2, 3)").is_err());
}
#[test]
fn test_insert_type_mismatch() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
assert!(db.execute("INSERT INTO t VALUES (1, 'text')").is_err());
assert!(db.execute("INSERT INTO t VALUES ('abc', 1)").is_err());
}
#[test]
fn test_insert_named_columns() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT)").unwrap();
db.execute("INSERT INTO t (id, b, a) VALUES (1, 20, 10)").unwrap();
let row = qr(&db, "SELECT * FROM t WHERE id = 1").unwrap();
assert_eq!(row[0], Value::Integer(1));
assert_eq!(row[1], Value::Integer(10));
assert_eq!(row[2], Value::Integer(20));
}
#[test]
fn test_select_star() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b TEXT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 42, 'hello')").unwrap();
let row = qr(&db, "SELECT * FROM t").unwrap();
assert_eq!(row, vec![Value::Integer(1), Value::Integer(42), Value::Text("hello".into())]);
}
#[test]
fn test_select_specific_columns() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT, c INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10, 20, 30)").unwrap();
let row = qr(&db, "SELECT b, a FROM t WHERE id = 1").unwrap();
assert_eq!(row, vec![Value::Integer(20), Value::Integer(10)]);
}
#[test]
fn test_select_arithmetic() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10, 3)").unwrap();
assert_eq!(qv(&db, "SELECT a + b FROM t WHERE a > 0"), Value::Integer(13));
assert_eq!(qv(&db, "SELECT a - b FROM t WHERE a > 0"), Value::Integer(7));
assert_eq!(qv(&db, "SELECT a * b FROM t WHERE a > 0"), Value::Integer(30));
assert_eq!(qv(&db, "SELECT a / b FROM t WHERE a > 0"), Value::Integer(3));
}
#[test]
fn test_select_functions() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, s TEXT, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 'Hello World', -5)").unwrap();
assert_eq!(qv(&db, "SELECT UPPER(s) FROM t"), Value::Text("HELLO WORLD".into()));
assert_eq!(qv(&db, "SELECT LOWER(s) FROM t"), Value::Text("hello world".into()));
assert_eq!(qv(&db, "SELECT LENGTH(s) FROM t"), Value::Integer(11));
assert_eq!(qv(&db, "SELECT ABS(v) FROM t"), Value::Integer(5));
}
#[test]
fn test_select_concat() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a TEXT, b TEXT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 'hello', 'world')").unwrap();
assert_eq!(qv(&db, "SELECT CONCAT(a, ' ', b) FROM t"), Value::Text("hello world".into()));
}
#[test]
fn test_select_null_literal() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY)").unwrap();
db.execute("INSERT INTO t VALUES (1)").unwrap();
let row = qr(&db, "SELECT id, NULL FROM t").unwrap();
assert_eq!(row[0], Value::Integer(1));
assert_eq!(row[1], Value::Null);
}
#[test]
fn test_aggregates_basic() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=5 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i * 10)).unwrap(); }
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(5));
assert_eq!(qv(&db, "SELECT SUM(v) FROM t"), Value::Integer(150));
assert_eq!(qv(&db, "SELECT MIN(v) FROM t"), Value::Integer(10));
assert_eq!(qv(&db, "SELECT MAX(v) FROM t"), Value::Integer(50));
let avg = qv(&db, "SELECT AVG(v) FROM t");
match avg { Value::Float(f) => assert!((f - 30.0).abs() < 0.01), _ => panic!("AVG should be float") }
}
#[test]
fn test_aggregates_with_nulls() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("INSERT INTO t VALUES (2, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (3, 30)").unwrap();
db.execute("INSERT INTO t VALUES (4, NULL)").unwrap();
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(4));
assert_eq!(qv(&db, "SELECT COUNT(v) FROM t"), Value::Integer(2));
assert_eq!(qv(&db, "SELECT SUM(v) FROM t"), Value::Integer(40));
}
#[test]
fn test_aggregates_all_null() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (2, NULL)").unwrap();
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(2));
assert_eq!(qv(&db, "SELECT COUNT(v) FROM t"), Value::Integer(0));
assert_eq!(qv(&db, "SELECT SUM(v) FROM t"), Value::Null);
assert_eq!(qv(&db, "SELECT AVG(v) FROM t"), Value::Null);
}
#[test]
fn test_count_distinct() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("INSERT INTO t VALUES (2, 20)").unwrap();
db.execute("INSERT INTO t VALUES (3, 10)").unwrap();
db.execute("INSERT INTO t VALUES (4, 30)").unwrap();
db.execute("INSERT INTO t VALUES (5, 20)").unwrap();
assert_eq!(qv(&db, "SELECT COUNT(DISTINCT v) FROM t"), Value::Integer(3));
}
#[test]
fn test_aggregates_empty_table() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(0));
assert_eq!(qv(&db, "SELECT COUNT(v) FROM t"), Value::Integer(0));
}
#[test]
fn test_where_comparison_ops() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=10 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap(); }
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v = 5").len(), 1);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v != 5").len(), 9);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v < 5").len(), 4);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v <= 5").len(), 5);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v > 5").len(), 5);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v >= 5").len(), 6);
}
#[test]
fn test_where_and_or() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 1, 1)").unwrap();
db.execute("INSERT INTO t VALUES (2, 1, 0)").unwrap();
db.execute("INSERT INTO t VALUES (3, 0, 1)").unwrap();
db.execute("INSERT INTO t VALUES (4, 0, 0)").unwrap();
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE a = 1 AND b = 1").len(), 1);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE a = 1 OR b = 1").len(), 3);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE a = 0 AND b = 0").len(), 1);
}
#[test]
fn test_where_is_null_is_not_null() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("INSERT INTO t VALUES (2, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (3, 30)").unwrap();
db.execute("INSERT INTO t VALUES (4, NULL)").unwrap();
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v IS NULL").len(), 2);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v IS NOT NULL").len(), 2);
}
#[test]
fn test_where_in_not_in() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=5 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i * 10)).unwrap(); }
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v IN (10, 30, 50)").len(), 3);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v NOT IN (10, 30, 50)").len(), 2);
}
#[test]
fn test_where_between() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=10 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i * 10)).unwrap(); }
let rows = query_rows(&db, "SELECT * FROM t WHERE v BETWEEN 30 AND 70");
assert_eq!(rows.len(), 5);
}
#[test]
fn test_where_like() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, s TEXT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 'hello world')").unwrap();
db.execute("INSERT INTO t VALUES (2, 'hello')").unwrap();
db.execute("INSERT INTO t VALUES (3, 'world hello')").unwrap();
db.execute("INSERT INTO t VALUES (4, 'HELLO')").unwrap();
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE s LIKE 'hello%'").len(), 2, "LIKE 'hello%'");
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE s LIKE '%world%'").len(), 2, "LIKE '%%world%%'");
let underscore_rows = query_rows(&db, "SELECT * FROM t WHERE s LIKE 'h_l%'");
assert!(underscore_rows.len() >= 1, "LIKE 'h_l%%' should match 'hello'");
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE s NOT LIKE 'hello%'").len(), 2, "NOT LIKE 'hello%'");
}
#[test]
fn test_where_null_semantics() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (2, 5)").unwrap();
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v = NULL").len(), 0);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v != NULL").len(), 0);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v > NULL").len(), 0);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v < NULL").len(), 0);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE NOT v").len(), 0);
}
#[test]
fn test_where_arithmetic() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, price INT, qty INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10, 5)").unwrap();
db.execute("INSERT INTO t VALUES (2, 3, 2)").unwrap();
db.execute("INSERT INTO t VALUES (3, 1, 100)").unwrap();
let rows = query_rows(&db, "SELECT * FROM t WHERE price * qty > 20 ORDER BY id");
assert_eq!(rows.len(), 2);
}
#[test]
fn test_where_parenthesized() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT)").unwrap();
for i in 0..4 {
db.execute(&format!("INSERT INTO t VALUES ({}, {}, {})", i + 1, i % 2, i / 2)).unwrap();
}
let rows = query_rows(&db, "SELECT * FROM t WHERE (a = 0 OR a = 1) AND b = 0 ORDER BY id");
assert_eq!(rows.len(), 2);
}
#[test]
fn test_where_int_float_cross_type() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v FLOAT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 5.0)").unwrap();
db.execute("INSERT INTO t VALUES (2, 3.5)").unwrap();
db.execute("INSERT INTO t VALUES (3, 10.0)").unwrap();
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v = 5").len(), 1);
assert_eq!(query_rows(&db, "SELECT * FROM t WHERE v > 4").len(), 2);
}
#[test]
fn test_order_by_asc_desc() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 30)").unwrap();
db.execute("INSERT INTO t VALUES (2, 10)").unwrap();
db.execute("INSERT INTO t VALUES (3, 20)").unwrap();
let rows = query_rows(&db, "SELECT v FROM t ORDER BY v ASC");
assert_eq!(rows[0][0], Value::Integer(10));
assert_eq!(rows[2][0], Value::Integer(30));
let rows = query_rows(&db, "SELECT v FROM t ORDER BY v DESC");
assert_eq!(rows[0][0], Value::Integer(30));
assert_eq!(rows[2][0], Value::Integer(10));
}
#[test]
fn test_limit_offset() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY)").unwrap();
for i in 1..=10 { db.execute(&format!("INSERT INTO t VALUES ({})", i)).unwrap(); }
assert_eq!(query_rows(&db, "SELECT * FROM t ORDER BY id LIMIT 3").len(), 3);
let rows = query_rows(&db, "SELECT * FROM t ORDER BY id LIMIT 3 OFFSET 5");
assert_eq!(rows.len(), 3);
assert_eq!(rows[0][0], Value::Integer(6));
}
#[test]
fn test_order_by_with_nulls() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 30)").unwrap();
db.execute("INSERT INTO t VALUES (2, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (3, 10)").unwrap();
let rows = query_rows(&db, "SELECT * FROM t ORDER BY v");
assert_eq!(rows.len(), 3, "ORDER BY with NULLs should not lose rows");
}
#[test]
fn test_update_basic() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("UPDATE t SET v = 99 WHERE id = 1").unwrap();
assert_eq!(qv(&db, "SELECT v FROM t WHERE id = 1"), Value::Integer(99));
}
#[test]
fn test_update_swap() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10, 20)").unwrap();
db.execute("UPDATE t SET a = b, b = a WHERE id = 1").unwrap();
assert_eq!(qv(&db, "SELECT a FROM t WHERE id = 1"), Value::Integer(20));
assert_eq!(qv(&db, "SELECT b FROM t WHERE id = 1"), Value::Integer(10));
}
#[test]
fn test_update_expression() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("UPDATE t SET v = v * 2 + 1 WHERE id = 1").unwrap();
assert_eq!(qv(&db, "SELECT v FROM t WHERE id = 1"), Value::Integer(21));
}
#[test]
fn test_update_multi_column() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT, c INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 1, 2, 3)").unwrap();
db.execute("UPDATE t SET a = c, b = a, c = b WHERE id = 1").unwrap();
let row = qr(&db, "SELECT a, b, c FROM t WHERE id = 1").unwrap();
assert_eq!(row, vec![Value::Integer(3), Value::Integer(1), Value::Integer(2)]);
}
#[test]
fn test_update_set_null() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 100)").unwrap();
db.execute("UPDATE t SET v = NULL WHERE id = 1").unwrap();
assert_eq!(qv(&db, "SELECT v FROM t WHERE id = 1"), Value::Null);
}
#[test]
fn test_update_no_match() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
let result = exec(&db, "UPDATE t SET v = 99 WHERE id = 999");
if let QueryResult::Modification { affected_rows } = result {
assert_eq!(affected_rows, 0);
}
assert_eq!(qv(&db, "SELECT v FROM t WHERE id = 1"), Value::Integer(10));
}
#[test]
fn test_update_where_is_null() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (2, 10)").unwrap();
db.execute("UPDATE t SET v = 0 WHERE v IS NULL").unwrap();
assert_eq!(qv(&db, "SELECT v FROM t WHERE id = 1"), Value::Integer(0));
assert_eq!(qv(&db, "SELECT v FROM t WHERE id = 2"), Value::Integer(10));
}
#[test]
fn test_delete_basic() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=5 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap(); }
db.execute("DELETE FROM t WHERE id = 3").unwrap();
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(4));
}
#[test]
fn test_delete_with_where() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=10 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i * 10)).unwrap(); }
db.execute("DELETE FROM t WHERE v >= 50 AND v <= 80").unwrap();
let rows = query_rows(&db, "SELECT * FROM t ORDER BY id");
assert_eq!(rows.len(), 6);
}
#[test]
fn test_delete_all() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=5 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap(); }
db.execute("DELETE FROM t WHERE id >= 1").unwrap();
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(0));
}
#[test]
fn test_delete_reinsert() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v TEXT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 'old')").unwrap();
db.execute("DELETE FROM t WHERE id = 1").unwrap();
db.execute("INSERT INTO t VALUES (1, 'new')").unwrap();
assert_eq!(qv(&db, "SELECT v FROM t WHERE id = 1"), Value::Text("new".into()));
}
#[test]
fn test_delete_is_null() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (2, 10)").unwrap();
db.execute("DELETE FROM t WHERE v IS NULL").unwrap();
assert_eq!(query_rows(&db, "SELECT * FROM t").len(), 1);
}
#[test]
fn test_prepared_insert_select() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT, age INT)").unwrap();
for i in 1..=5 {
db.execute_prepared(
"INSERT INTO t VALUES (?, ?, ?)",
vec![Value::Integer(i), Value::Text(format!("user{}", i)), Value::Integer(20 + i)],
).unwrap();
}
for i in 1..=5 {
let r = db.execute_prepared(
"SELECT name FROM t WHERE id = ?",
vec![Value::Integer(i)],
).unwrap().materialize().unwrap();
if let QueryResult::Select { rows, .. } = r {
assert_eq!(rows[0][0], Value::Text(format!("user{}", i)));
}
}
}
#[test]
fn test_reopen_full_crud() {
let path_ctx = TempDir::new().unwrap();
let path = path_ctx.path().join("test.mote");
{
let db = Database::create(&path).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v TEXT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
db.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
db.execute("INSERT INTO t VALUES (3, 'c')").unwrap();
db.execute("UPDATE t SET v = 'updated' WHERE id = 2").unwrap();
db.execute("DELETE FROM t WHERE id = 3").unwrap();
drop(db);
}
let db = Database::open(&path).unwrap();
let rows = query_rows(&db, "SELECT * FROM t ORDER BY id");
assert_eq!(rows.len(), 2);
assert_eq!(rows[0], vec![Value::Integer(1), Value::Text("a".into())]);
assert_eq!(rows[1], vec![Value::Integer(2), Value::Text("updated".into())]);
}
#[test]
fn test_reopen_null_data() {
let path_ctx = TempDir::new().unwrap();
let path = path_ctx.path().join("test.mote");
{
let db = Database::create(&path).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (2, 42)").unwrap();
drop(db);
}
let db = Database::open(&path).unwrap();
let rows = query_rows(&db, "SELECT * FROM t ORDER BY id");
assert_eq!(rows[0][1], Value::Null);
assert_eq!(rows[1][1], Value::Integer(42));
}
#[test]
fn test_stress_500_rows_crud() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=500 {
db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i * i)).unwrap();
}
assert_eq!(qv(&db, "SELECT COUNT(*) FROM t"), Value::Integer(500));
for i in (10..=500).step_by(10) {
db.execute(&format!("UPDATE t SET v = v + 1000 WHERE id = {}", i)).unwrap();
}
for i in (7..=500).step_by(7) {
db.execute(&format!("DELETE FROM t WHERE id = {}", i)).unwrap();
}
let count = match qv(&db, "SELECT COUNT(*) FROM t") {
Value::Integer(c) => c,
other => panic!("Expected integer, got {:?}", other),
};
assert_eq!(count, 429, "Expected 429 rows after deletes");
let v10 = qv(&db, "SELECT v FROM t WHERE id = 10");
assert_eq!(v10, Value::Integer(100 + 1000));
}
#[test]
fn test_perf_insert_throughput() {
eprintln!("\n=== Performance (Default Config — GroupCommit fsync) ===");
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
let n = 1000;
let start = Instant::now();
for i in 1..=n {
db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap();
}
let elapsed = start.elapsed();
let us_per = elapsed.as_micros() as f64 / n as f64;
eprintln!("INSERT (GroupCommit): {} rows in {:.1}ms ({:.0}µs/op, {:.0} ops/sec)",
n, elapsed.as_micros() as f64 / 1000.0, us_per, n as f64 / elapsed.as_secs_f64());
eprintln!("\n=== Performance (NoSync Config — in-memory WAL) ===");
let dir2 = TempDir::new().unwrap();
let db2 = setup_db_fast(dir2.path());
db2.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
let start2 = Instant::now();
for i in 1..=n {
db2.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap();
}
let elapsed2 = start2.elapsed();
let us_per2 = elapsed2.as_micros() as f64 / n as f64;
eprintln!("INSERT (NoSync): {} rows in {:.1}ms ({:.0}µs/op, {:.0} ops/sec)",
n, elapsed2.as_micros() as f64 / 1000.0, us_per2, n as f64 / elapsed2.as_secs_f64());
eprintln!("Speedup from NoSync: {:.1}x", us_per / us_per2);
assert!(us_per < 10000.0, "INSERT too slow: {:.0}µs/op", us_per);
}
#[test]
fn test_perf_select_throughput() {
let dir = TempDir::new().unwrap();
let db = setup_db_fast(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=500 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap(); }
let n = 500;
let start = Instant::now();
for i in 1..=n {
let _ = query_rows(&db, &format!("SELECT * FROM t WHERE id = {}", i));
}
let elapsed = start.elapsed();
let us_per = elapsed.as_micros() as f64 / n as f64;
eprintln!("SELECT by PK: {} queries in {:.1}ms ({:.0}µs/op, {:.0} ops/sec)",
n, elapsed.as_micros() as f64 / 1000.0, us_per, n as f64 / elapsed.as_secs_f64());
assert!(us_per < 2000.0, "SELECT too slow: {:.0}µs/op", us_per);
}
#[test]
fn test_perf_prepared_select() {
let dir = TempDir::new().unwrap();
let db = setup_db_fast(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=500 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap(); }
let n = 500;
let sql = "SELECT * FROM t WHERE id = ?";
let start = Instant::now();
for i in 1..=n {
let _ = db.execute_prepared(sql, vec![Value::Integer(i)])
.unwrap().materialize().unwrap();
}
let elapsed = start.elapsed();
let us_per = elapsed.as_micros() as f64 / n as f64;
eprintln!("Prepared SELECT: {} queries in {:.1}ms ({:.0}µs/op, {:.0} ops/sec)",
n, elapsed.as_micros() as f64 / 1000.0, us_per, n as f64 / elapsed.as_secs_f64());
}
#[test]
fn test_perf_update_throughput() {
let dir = TempDir::new().unwrap();
let db = setup_db_fast(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=500 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap(); }
let n = 500;
let start = Instant::now();
for i in 1..=n {
db.execute(&format!("UPDATE t SET v = v + 1 WHERE id = {}", i)).unwrap();
}
let elapsed = start.elapsed();
let us_per = elapsed.as_micros() as f64 / n as f64;
eprintln!("UPDATE by PK: {} updates in {:.1}ms ({:.0}µs/op, {:.0} ops/sec)",
n, elapsed.as_micros() as f64 / 1000.0, us_per, n as f64 / elapsed.as_secs_f64());
}
#[test]
fn test_perf_full_scan() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE t (id INT PRIMARY KEY, v INT)").unwrap();
for i in 1..=1000 { db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i)).unwrap(); }
let start = Instant::now();
let count = qv(&db, "SELECT COUNT(*) FROM t");
let elapsed = start.elapsed();
eprintln!("Full scan COUNT(*) 1000 rows: {:.1}ms", elapsed.as_micros() as f64 / 1000.0);
assert_eq!(count, Value::Integer(1000));
let start = Instant::now();
let rows = query_rows(&db, "SELECT * FROM t WHERE v > 900 ORDER BY id");
let elapsed = start.elapsed();
eprintln!("Filtered scan (v>900, ~100 rows): {:.1}ms, returned {} rows",
elapsed.as_micros() as f64 / 1000.0, rows.len());
assert!(rows.len() > 90);
}