use motedb::{Database, types::Value, config::DBConfig};
use std::time::Instant;
use std::path::Path;
use tempfile::TempDir;
fn setup_db(dir: &Path) -> Database {
let db = Database::create(dir.join("audit.mote")).unwrap();
db.execute("CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT, name TEXT, age INT, score FLOAT)").unwrap();
db
}
fn setup_db_fast(dir: &Path) -> Database {
let config = DBConfig::for_testing();
let db = Database::create_with_config(dir.join("audit.mote"), config).unwrap();
db.execute("CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT, name TEXT, age INT, score FLOAT)").unwrap();
db
}
fn insert_n_users(db: &Database, n: i64) {
for i in 0..n {
db.execute(&format!(
"INSERT INTO users VALUES (null, 'user{}', {}, {})",
i, 20 + (i % 50), 50.0 + i as f64
)).unwrap();
}
}
fn query_rows(db: &Database, sql: &str) -> Vec<Vec<Value>> {
let result = db.execute(sql).unwrap().materialize().unwrap();
match result {
motedb::QueryResult::Select { rows, .. } => rows,
_ => vec![],
}
}
fn query_rows_prepared(db: &Database, sql: &str, params: Vec<Value>) -> Vec<Vec<Value>> {
let result = db.execute_prepared(sql, params).unwrap().materialize().unwrap();
match result {
motedb::QueryResult::Select { rows, .. } => rows,
_ => vec![],
}
}
#[test]
fn test_parameterized_select_basic() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 100);
let all = query_rows(&db, "SELECT * FROM users WHERE name = 'user42'");
assert_eq!(all.len(), 1);
let target_id = match all[0][0] { Value::Integer(id) => id, _ => panic!("no id") };
let rows = query_rows_prepared(&db, "SELECT * FROM users WHERE id = ?", vec![Value::Integer(target_id)]);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][1], Value::Text("user42".to_string()));
}
#[test]
fn test_parameterized_select_multi_params() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 50);
let rows = query_rows_prepared(&db,
"SELECT * FROM users WHERE age > ? AND age < ?",
vec![Value::Integer(30), Value::Integer(40)]
);
assert!(rows.len() > 0, "Should find rows with age between 30 and 40");
for row in &rows {
let age = match &row[2] { Value::Integer(a) => *a, _ => -1 };
assert!(age > 30 && age < 40, "Age should be in (30, 40), got {}", age);
}
}
#[test]
fn test_parameterized_insert() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute_prepared(
"INSERT INTO users VALUES (null, ?, ?, ?)",
vec![Value::Text("Alice".to_string()), Value::Integer(30), Value::Float(95.5)]
).unwrap();
let rows = query_rows(&db, "SELECT * FROM users WHERE name = 'Alice'");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][2], Value::Integer(30));
assert_eq!(rows[0][3], Value::Float(95.5));
}
#[test]
fn test_parameterized_numbered_params() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 10);
let rows = query_rows_prepared(&db,
"SELECT * FROM users WHERE age > ?1 AND score < ?2",
vec![Value::Integer(15), Value::Float(999.0)]
);
assert!(rows.len() > 0, "Should find rows with age>15 and score<999");
}
#[test]
fn test_parameterized_unbound_error() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 5);
let result = db.execute_prepared("SELECT * FROM users WHERE id = ?", vec![]);
assert!(result.is_err(), "execute_prepared with unbound ? should error");
let result = db.execute("SELECT * FROM users WHERE id = ?");
match result {
Ok(r) => {
let rows = match r.materialize() {
Ok(motedb::QueryResult::Select { rows, .. }) => rows,
_ => vec![],
};
assert_eq!(rows.len(), 0, "Unbound ? via execute() should return 0 rows");
}
Err(_) => {} }
}
#[test]
fn test_parameterized_cache_hit() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 10);
let sql = "SELECT * FROM users WHERE name = ?";
let rows1 = query_rows_prepared(&db, sql, vec![Value::Text("user1".to_string())]);
assert_eq!(rows1.len(), 1);
let rows2 = query_rows_prepared(&db, sql, vec![Value::Text("user5".to_string())]);
assert_eq!(rows2.len(), 1);
assert_eq!(rows2[0][1], Value::Text("user5".to_string()));
}
#[test]
fn test_execute_with_unbound_parameter() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 5);
let result = db.execute("SELECT * FROM users WHERE id = ?");
match result {
Ok(r) => {
let rows = match r.materialize() {
Ok(motedb::QueryResult::Select { rows, .. }) => rows,
_ => vec![],
};
assert_eq!(rows.len(), 0, "Unbound parameter should return 0 rows");
}
Err(_) => {} }
}
#[test]
fn test_table_qualified_column_where() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 30);
let rows = query_rows(&db, "SELECT * FROM users WHERE users.age > 30");
assert!(rows.len() > 0);
for row in &rows {
let age = match &row[2] { Value::Integer(a) => *a, _ => 0 };
assert!(age > 30, "Age should be > 30, got {}", age);
}
}
#[test]
fn test_table_qualified_column_select() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 1);
let row0 = query_rows(&db, "SELECT * FROM users").into_iter().next().unwrap();
let id = match row0[0] { Value::Integer(i) => i, _ => panic!() };
let rows = query_rows(&db, &format!("SELECT users.name, users.age FROM users WHERE id = {}", id));
assert_eq!(rows.len(), 1);
assert_eq!(rows[0][0], Value::Text("user0".to_string()));
}
#[test]
fn test_full_scan_where_filter() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 200);
let rows = query_rows(&db, "SELECT * FROM users WHERE age = 35");
assert!(rows.len() > 0);
for row in &rows {
let age = match &row[2] { Value::Integer(a) => *a, _ => -1 };
assert_eq!(age, 35);
}
}
#[test]
fn test_full_scan_where_and() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 100);
let rows = query_rows(&db, "SELECT * FROM users WHERE age > 25 AND age < 30");
for row in &rows {
let age = match &row[2] { Value::Integer(a) => *a, _ => -1 };
assert!(age > 25 && age < 30);
}
}
#[test]
fn test_column_index_scan_correctness() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("CREATE TABLE items (id INT PRIMARY KEY AUTO_INCREMENT, category TEXT, price FLOAT)").unwrap();
db.execute("CREATE INDEX items_category ON items(category)").unwrap();
for i in 0..50 {
let cat = if i % 3 == 0 { "A" } else if i % 3 == 1 { "B" } else { "C" };
db.execute(&format!("INSERT INTO items VALUES (null, '{}', {})", cat, 10.0 + i as f64)).unwrap();
}
let rows = query_rows(&db, "SELECT * FROM items WHERE category = 'A'");
assert_eq!(rows.len(), 17);
}
#[test]
fn test_edge_null_where() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 5);
let rows = query_rows(&db, "SELECT * FROM users WHERE age = NULL");
assert_eq!(rows.len(), 0);
}
#[test]
fn test_edge_empty_table() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
let rows = query_rows(&db, "SELECT * FROM users WHERE age > 10");
assert_eq!(rows.len(), 0);
}
#[test]
fn test_edge_update_reselect() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 1);
let row0 = query_rows(&db, "SELECT * FROM users").into_iter().next().unwrap();
let id = match row0[0] { Value::Integer(i) => i, _ => panic!() };
db.execute(&format!("UPDATE users SET age = 30, score = 95.0 WHERE id = {}", id)).unwrap();
let rows = query_rows(&db, &format!("SELECT * FROM users WHERE id = {}", id));
assert_eq!(rows[0][2], Value::Integer(30));
assert_eq!(rows[0][3], Value::Float(95.0));
}
#[test]
fn test_edge_delete_verify() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
insert_n_users(&db, 10);
let row5 = query_rows(&db, "SELECT * FROM users WHERE name = 'user5'");
let id5 = match row5[0][0] { Value::Integer(i) => i, _ => panic!() };
db.execute(&format!("DELETE FROM users WHERE id = {}", id5)).unwrap();
let rows = query_rows(&db, "SELECT * FROM users");
assert_eq!(rows.len(), 9);
let rows2 = query_rows(&db, &format!("SELECT * FROM users WHERE id = {}", id5));
assert_eq!(rows2.len(), 0);
}
#[test]
fn test_edge_string_with_keywords() {
let dir = TempDir::new().unwrap();
let db = setup_db(dir.path());
db.execute("INSERT INTO users VALUES (null, 'from where select', 25, 90.0)").unwrap();
let rows = query_rows(&db, "SELECT * FROM users WHERE name = 'from where select'");
assert_eq!(rows.len(), 1);
}
#[test]
fn test_edge_checkpoint_recovery() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("audit.mote");
{
let db = Database::create(&path).unwrap();
db.execute("CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT, name TEXT, age INT)").unwrap();
insert_n_users_simple(&db, 100);
db.checkpoint().unwrap();
}
let db = Database::open(&path).unwrap();
let rows = query_rows(&db, "SELECT * FROM users");
assert_eq!(rows.len(), 100);
let rows2 = query_rows_prepared(&db, "SELECT * FROM users WHERE name = ?", vec![Value::Text("user50".to_string())]);
assert_eq!(rows2.len(), 1);
}
fn insert_n_users_simple(db: &Database, n: i64) {
for i in 0..n {
db.execute(&format!("INSERT INTO users VALUES (null, 'user{}', {})", i, 20 + i)).unwrap();
}
}
fn get_rss_kb() -> u64 {
#[cfg(target_os = "macos")]
{
let pid = std::process::id();
let output = std::process::Command::new("ps")
.args(["-o", "rss=", "-p", &pid.to_string()])
.output().ok();
output.and_then(|o| String::from_utf8(o.stdout).ok())
.and_then(|s| s.trim().parse::<u64>().ok())
.unwrap_or(0)
}
#[cfg(not(target_os = "macos"))]
{ 0 }
}
#[test]
fn test_comprehensive_summary() {
let dir = TempDir::new().unwrap();
let db = setup_db_fast(dir.path());
println!("\n====== MoteDB Comprehensive Audit ======");
let n = 2000;
let start = Instant::now();
insert_n_users(&db, n);
let insert_time = start.elapsed();
let insert_us = insert_time.as_micros() as f64 / n as f64;
println!(" INSERT: {:.0} µs/op, {:.0} ops/s", insert_us, 1_000_000.0 / insert_us);
let pk_iters = 1000;
let start = Instant::now();
for i in 0..pk_iters {
let _ = query_rows(&db, &format!("SELECT * FROM users WHERE id = {}", i % n + 1));
}
let pk_us = start.elapsed().as_micros() as f64 / pk_iters as f64;
println!(" PK SELECT (raw): {:.0} µs/op", pk_us);
let start = Instant::now();
for i in 0..pk_iters {
let _ = query_rows_prepared(&db,
"SELECT * FROM users WHERE id = ?",
vec![Value::Integer((i % n + 1) as i64)]
);
}
let pk_prep_us = start.elapsed().as_micros() as f64 / pk_iters as f64;
println!(" PK SELECT (prep): {:.0} µs/op ({:.2}x vs raw)", pk_prep_us, pk_us / pk_prep_us);
let start = Instant::now();
let _rows = query_rows(&db, "SELECT * FROM users WHERE age > 40");
let scan_time = start.elapsed();
println!(" Full scan {} rows (WHERE age>40): {:.1} ms ({:.1} µs/row)",
n, scan_time.as_secs_f64() * 1000.0, scan_time.as_micros() as f64 / n as f64);
let start = Instant::now();
for i in 1..=500i64 {
db.execute(&format!("UPDATE users SET score = {} WHERE id = {}", 100.0 + i as f64, i)).unwrap();
}
println!(" UPDATE: {:.0} µs/op", start.elapsed().as_micros() as f64 / 500.0);
let start = Instant::now();
for i in 1..=200i64 {
db.execute(&format!("DELETE FROM users WHERE id = {}", i)).unwrap();
}
println!(" DELETE: {:.0} µs/op", start.elapsed().as_micros() as f64 / 200.0);
let start = Instant::now();
db.checkpoint().unwrap();
println!(" Checkpoint: {:.0} ms", start.elapsed().as_secs_f64() * 1000.0);
println!(" RSS: {:.1} MB", get_rss_kb() as f64 / 1024.0);
let rows = query_rows(&db, "SELECT * FROM users WHERE age > 45");
assert!(rows.len() > 0);
for row in &rows {
let age = match &row[2] { Value::Integer(a) => *a, _ => 0 };
assert!(age > 45);
}
let rows_p = query_rows_prepared(&db,
"SELECT * FROM users WHERE age > ?",
vec![Value::Integer(45)]
);
assert_eq!(rows_p.len(), rows.len());
println!("==========================================\n");
}