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 row(result: motedb::StreamingQueryResult) -> Vec<Value> {
let r = rows(result);
assert_eq!(r.len(), 1);
r.into_iter().next().unwrap()
}
fn setup_orders() -> (Database, TempDir) {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE orders (id INT PRIMARY KEY, customer TEXT, product TEXT, amount FLOAT, qty INT)").unwrap();
db.execute("INSERT INTO orders VALUES (1, 'Alice', 'Widget', 10.0, 5)")
.unwrap();
db.execute("INSERT INTO orders VALUES (2, 'Bob', 'Gadget', 25.0, 3)")
.unwrap();
db.execute("INSERT INTO orders VALUES (3, 'Alice', 'Gadget', 30.0, 2)")
.unwrap();
db.execute("INSERT INTO orders VALUES (4, 'Charlie', 'Widget', 10.0, 10)")
.unwrap();
db.execute("INSERT INTO orders VALUES (5, 'Bob', 'Widget', 10.0, 7)")
.unwrap();
(db, dir)
}
#[test]
fn test_cast_int_to_text() {
let db = Database::create(TempDir::new().unwrap()).unwrap();
let result = db.execute("SELECT CAST(42 AS TEXT)");
match result {
Ok(r) => {
let r = row(r);
if let Value::Text(s) = &r[0] {
assert_eq!(s.as_str(), "42");
}
}
Err(_) => {}
}
}
#[test]
fn test_cast_in_where() {
let (db, _dir) = setup_orders();
let result = db.execute("SELECT id FROM orders WHERE CAST(amount AS INT) = 10 ORDER BY id");
match result {
Ok(r) => {
let r = rows(r);
assert!(r.len() >= 2, "Should find Widget orders (amount=10)");
}
Err(_) => {}
}
}
#[test]
fn test_prepared_insert_select() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT, val INT)")
.unwrap();
db.execute_prepared(
"INSERT INTO t VALUES (?, ?, ?)",
vec![
Value::Integer(1),
Value::text("first".to_string()),
Value::Integer(100),
],
)
.unwrap();
db.execute_prepared(
"INSERT INTO t VALUES (?, ?, ?)",
vec![
Value::Integer(2),
Value::text("second".to_string()),
Value::Integer(200),
],
)
.unwrap();
let result = db
.execute_prepared(
"SELECT name, val FROM t WHERE id = ?",
vec![Value::Integer(1)],
)
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
assert_eq!(&r[0][0], &Value::text("first".to_string()));
}
#[test]
fn test_prepared_update() {
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, 10)").unwrap();
db.execute_prepared(
"UPDATE t SET val = ? WHERE id = ?",
vec![Value::Integer(99), Value::Integer(1)],
)
.unwrap();
let r = row(db.execute("SELECT val FROM t WHERE id = 1").unwrap());
assert_eq!(&r[0], &Value::Integer(99));
}
#[test]
fn test_prepared_delete() {
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, 10)").unwrap();
db.execute("INSERT INTO t VALUES (2, 20)").unwrap();
db.execute_prepared("DELETE FROM t WHERE id = ?", vec![Value::Integer(1)])
.unwrap();
let result = db.execute("SELECT COUNT(*) FROM t").unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(1));
}
#[test]
fn test_prepared_multiple_params() {
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, 10)").unwrap();
db.execute("INSERT INTO t VALUES (2, 20)").unwrap();
db.execute("INSERT INTO t VALUES (3, 30)").unwrap();
let result = db
.execute_prepared(
"SELECT id FROM t WHERE val >= ? AND val <= ? ORDER BY id",
vec![Value::Integer(15), Value::Integer(25)],
)
.unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
assert_eq!(&r[0][0], &Value::Integer(2));
}
#[test]
fn test_nested_arithmetic() {
let (db, _dir) = setup_orders();
let r = row(db
.execute("SELECT amount * qty + 100 FROM orders WHERE id = 1")
.unwrap());
match &r[0] {
Value::Float(f) => assert!((f - 150.0).abs() < 0.01, "Expected 150, got {}", f),
Value::Integer(i) => assert_eq!(*i, 150),
other => panic!("Expected numeric, got {:?}", other),
}
}
#[test]
fn test_expression_with_parens() {
let (db, _dir) = setup_orders();
let r = row(db
.execute("SELECT (amount + qty) * 2 FROM orders WHERE id = 1")
.unwrap());
match &r[0] {
Value::Float(f) => assert!((f - 30.0).abs() < 0.1, "Expected 30, got {}", f),
Value::Integer(i) => assert_eq!(*i, 30),
other => panic!("Expected numeric, got {:?}", other),
}
}
#[test]
fn test_where_with_or_and_parens() {
let (db, _dir) = setup_orders();
let r = rows(db.execute(
"SELECT id FROM orders WHERE (customer = 'Alice' AND product = 'Widget') OR (customer = 'Bob' AND product = 'Gadget') ORDER BY id"
).unwrap());
assert_eq!(
r.len(),
2,
"Should match order 1 (Alice+Widget) and order 2 (Bob+Gadget)"
);
}
#[test]
fn test_where_not() {
let (db, _dir) = setup_orders();
let result = db.execute("SELECT id FROM orders WHERE NOT customer = 'Alice' ORDER BY id");
match result {
Ok(r) => {
let r = rows(r);
assert!(r.len() <= 5, "NOT should filter rows");
}
Err(_) => {
}
}
}
#[test]
fn test_where_comparison_chain() {
let (db, _dir) = setup_orders();
let r = rows(
db.execute("SELECT id FROM orders WHERE amount > 10 AND qty < 10 ORDER BY id")
.unwrap(),
);
assert_eq!(r.len(), 2);
}
#[test]
fn test_update_expression() {
let (db, _dir) = setup_orders();
db.execute("UPDATE orders SET amount = amount * 1.1 WHERE customer = 'Alice'")
.unwrap();
let r = rows(
db.execute("SELECT amount FROM orders WHERE customer = 'Alice' ORDER BY id")
.unwrap(),
);
for row in &r {
match &row[0] {
Value::Float(f) => assert!(*f > 10.0, "Amount should be increased"),
other => panic!("Expected Float, got {:?}", other),
}
}
}
#[test]
fn test_update_multiple_columns() {
let (db, _dir) = setup_orders();
db.execute("UPDATE orders SET qty = qty + 1, amount = amount + 5 WHERE id = 1")
.unwrap();
let r = row(db
.execute("SELECT qty, amount FROM orders WHERE id = 1")
.unwrap());
assert_eq!(&r[0], &Value::Integer(6)); match &r[1] {
Value::Float(f) => assert!((f - 15.0).abs() < 0.01),
other => panic!("Expected Float, got {:?}", other),
}
}
#[test]
fn test_delete_with_in() {
let (db, _dir) = setup_orders();
db.execute("DELETE FROM orders WHERE customer IN ('Alice', 'Charlie')")
.unwrap();
let r = rows(
db.execute("SELECT DISTINCT customer FROM orders ORDER BY customer")
.unwrap(),
);
assert_eq!(r.len(), 1);
assert_eq!(&r[0][0], &Value::text("Bob".to_string()));
}
#[test]
fn test_distinct_multiple_columns() {
let (db, _dir) = setup_orders();
let r = rows(
db.execute("SELECT DISTINCT customer, product FROM orders ORDER BY customer, product")
.unwrap(),
);
assert_eq!(r.len(), 5);
}
#[test]
fn test_group_by_where_having() {
let (db, _dir) = setup_orders();
let result = db
.execute("SELECT customer, SUM(qty) FROM orders WHERE qty > 2 GROUP BY customer")
.unwrap();
let r = rows(result);
assert!(r.len() >= 2);
}
#[test]
fn test_case_sensitivity() {
let (db, _dir) = setup_orders();
let r = rows(
db.execute("select id from orders order by id limit 2")
.unwrap(),
);
assert_eq!(r.len(), 2);
}
#[test]
fn test_mixed_case_identifiers() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE MixedCase (Id INT PRIMARY KEY, Name TEXT)")
.unwrap();
db.execute("INSERT INTO MixedCase VALUES (1, 'test')")
.unwrap();
let result = db.execute("SELECT Name FROM MixedCase WHERE Id = 1");
assert!(result.is_ok(), "Mixed case identifiers should work");
}
#[test]
fn test_order_by_expression() {
let (db, _dir) = setup_orders();
let result = db.execute("SELECT id, amount * qty FROM orders ORDER BY amount * qty DESC");
match result {
Ok(r) => {
let r = rows(r);
assert_eq!(r.len(), 5);
match &r[0][1] {
Value::Float(f) => assert!(*f > 0.0, "Total should be positive"),
Value::Integer(i) => assert!(*i > 0),
other => panic!("Expected numeric, got {:?}", other),
}
}
Err(_) => {
}
}
}
#[test]
fn test_reopen_preserves_indexes() {
let dir = TempDir::new().unwrap();
let path = dir.path().to_path_buf();
{
let db = Database::create(&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, 'b')").unwrap();
db.execute("CREATE INDEX idx ON t(val) USING COLUMN")
.unwrap();
db.checkpoint().unwrap();
db.close().unwrap();
}
{
let db = Database::open(&path).unwrap();
let r = rows(db.execute("SELECT val FROM t ORDER BY id").unwrap());
assert_eq!(r.len(), 2);
assert_eq!(&r[0][0], &Value::text("a".to_string()));
}
}
#[test]
fn test_select_with_aliases_and_expressions() {
let (db, _dir) = setup_orders();
let result =
db.execute("SELECT id AS order_id, amount * qty AS total FROM orders WHERE id = 1");
assert!(
result.is_ok(),
"SELECT with aliases and expressions should not error"
);
}
#[test]
fn test_select_impossible_condition() {
let (db, _dir) = setup_orders();
let r = rows(db.execute("SELECT * FROM orders WHERE 1 = 0").unwrap());
assert_eq!(r.len(), 0);
}
#[test]
fn test_select_always_true() {
let (db, _dir) = setup_orders();
let r = rows(db.execute("SELECT * FROM orders WHERE 1 = 1").unwrap());
assert_eq!(r.len(), 5);
}