use motedb::{sql::QueryResult, types::Value, Database};
use tempfile::TempDir;
fn create_db() -> (Database, TempDir) {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path().join("test.mote")).unwrap();
(db, dir)
}
fn rows(db: &Database, sql: &str) -> Vec<Vec<Value>> {
match db.execute(sql).unwrap().materialize().unwrap() {
QueryResult::Select { rows, .. } => rows,
_ => vec![],
}
}
#[test]
fn test_group_by_limit() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (cat TEXT, val INT)").unwrap();
for i in 1..=10i64 {
db.execute(&format!("INSERT INTO t VALUES ('a', {})", i))
.unwrap();
}
for i in 1..=10i64 {
db.execute(&format!("INSERT INTO t VALUES ('b', {})", i))
.unwrap();
}
let r = rows(
&db,
"SELECT cat, SUM(val) FROM t GROUP BY cat ORDER BY cat LIMIT 1",
);
assert_eq!(r.len(), 1, "LIMIT 1 should return 1 row, got {}", r.len());
}
#[test]
fn test_group_by_limit_offset() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (cat TEXT, val INT)").unwrap();
for i in 1..=10i64 {
db.execute(&format!("INSERT INTO t VALUES ('a', {})", i))
.unwrap();
}
for i in 1..=10i64 {
db.execute(&format!("INSERT INTO t VALUES ('b', {})", i))
.unwrap();
}
let r = rows(
&db,
"SELECT cat, SUM(val) FROM t GROUP BY cat ORDER BY cat LIMIT 1 OFFSET 1",
);
assert_eq!(
r.len(),
1,
"LIMIT 1 OFFSET 1 should return 1 row, got {}",
r.len()
);
}
#[test]
fn test_group_by_no_limit_returns_all() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (cat TEXT, val INT)").unwrap();
for i in 1..=10i64 {
db.execute(&format!("INSERT INTO t VALUES ('a', {})", i))
.unwrap();
}
for i in 1..=10i64 {
db.execute(&format!("INSERT INTO t VALUES ('b', {})", i))
.unwrap();
}
let r = rows(&db, "SELECT cat, SUM(val) FROM t GROUP BY cat ORDER BY cat");
assert_eq!(r.len(), 2, "Should return 2 groups, got {}", r.len());
}
#[test]
fn test_non_group_by_column_rejected() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (a INT, b INT)").unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("INSERT INTO t VALUES (1, 20)").unwrap();
let result = db.execute("SELECT a FROM t GROUP BY b");
match result {
Ok(_) => {
let r = rows(&db, "SELECT a FROM t GROUP BY b");
eprintln!("Got result for non-group-by column: {:?}", r);
}
Err(_) => {
}
}
}
#[test]
fn test_order_by_integer_float_mix() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val FLOAT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 5.0)").unwrap();
db.execute("INSERT INTO t VALUES (2, 3.0)").unwrap();
db.execute("INSERT INTO t VALUES (3, 10.0)").unwrap();
let r = rows(&db, "SELECT id, val FROM t ORDER BY val ASC");
assert_eq!(r[0][0], Value::Integer(2)); assert_eq!(r[1][0], Value::Integer(1)); assert_eq!(r[2][0], Value::Integer(3)); }
#[test]
fn test_order_by_desc() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val FLOAT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 5.0)").unwrap();
db.execute("INSERT INTO t VALUES (2, 3.0)").unwrap();
db.execute("INSERT INTO t VALUES (3, 10.0)").unwrap();
let r = rows(&db, "SELECT id, val FROM t ORDER BY val DESC");
assert_eq!(r[0][0], Value::Integer(3)); assert_eq!(r[1][0], Value::Integer(1)); assert_eq!(r[2][0], Value::Integer(2)); }
#[test]
fn test_count_with_null_comparison() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (val INT)").unwrap();
db.execute("INSERT INTO t VALUES (1)").unwrap();
db.execute("INSERT INTO t VALUES (NULL)").unwrap();
db.execute("INSERT INTO t VALUES (3)").unwrap();
let r = rows(&db, "SELECT COUNT(*) FROM t WHERE val > 0");
assert_eq!(
r[0][0],
Value::Integer(2),
"WHERE val > 0 should return 2 rows (1 and 3), got {:?}",
r
);
}
#[test]
fn test_sstable_open_on_empty_db() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'hello')").unwrap();
db.flush().unwrap();
let r = rows(&db, "SELECT * FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(r[0][1], Value::text("hello".to_string()));
}
#[test]
fn test_ioctree_large_coordinate_precision() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE points (id INT PRIMARY KEY, x FLOAT, y FLOAT, z FLOAT)")
.unwrap();
db.execute("INSERT INTO points VALUES (1, 10000000.5, 10000000.5, 10000000.5)")
.unwrap();
db.execute("INSERT INTO points VALUES (2, 10000000.0, 10000000.0, 10000000.0)")
.unwrap();
db.flush().unwrap();
let r = rows(&db, "SELECT * FROM points ORDER BY id");
assert_eq!(r.len(), 2, "Should have 2 points");
assert_eq!(r[0][0], Value::Integer(1));
assert_eq!(r[1][0], Value::Integer(2));
}
#[test]
fn test_sstable_metadata_survives_restart() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("test.mote");
{
let db = Database::create(&path).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
for i in 1..=100i64 {
db.execute(&format!("INSERT INTO t VALUES ({}, 'v')", i))
.unwrap();
}
db.flush().unwrap();
db.close().unwrap();
}
{
let db = Database::open(&path).unwrap();
let r = rows(&db, "SELECT COUNT(*) FROM t WHERE id >= 50 AND id <= 60");
assert_eq!(
r[0][0],
Value::Integer(11),
"Range query after restart should return 11 rows (ids 50-60)"
);
}
}
#[test]
fn test_ioctree_flush_data_survival() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE points (id INT PRIMARY KEY, x FLOAT, y FLOAT, z FLOAT)")
.unwrap();
for i in 1..=10i64 {
db.execute(&format!(
"INSERT INTO points VALUES ({}, {}, {}, {})",
i,
i as f64 * 1.5,
i as f64 * 2.0,
i as f64 * 0.5
))
.unwrap();
}
db.flush().unwrap();
let r = rows(&db, "SELECT id FROM points ORDER BY id");
assert_eq!(r.len(), 10, "All points should survive flush");
assert_eq!(r[0][0], Value::Integer(1));
assert_eq!(r[9][0], Value::Integer(10));
}
#[test]
fn test_order_by_with_nulls() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (2, 10)").unwrap();
db.execute("INSERT INTO t VALUES (3, NULL)").unwrap();
db.execute("INSERT INTO t VALUES (4, 5)").unwrap();
let r = rows(&db, "SELECT id FROM t ORDER BY val ASC");
assert_eq!(r.len(), 4);
assert_eq!(r[2][0], Value::Integer(4)); assert_eq!(r[3][0], Value::Integer(2)); }
#[test]
fn test_text_index_delete_and_reinsert() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE docs (id INT PRIMARY KEY, content TEXT)")
.unwrap();
db.execute("INSERT INTO docs VALUES (1, 'hello world')")
.unwrap();
db.execute("INSERT INTO docs VALUES (2, 'hello rust')")
.unwrap();
db.flush().unwrap();
db.execute("DELETE FROM docs WHERE id = 1").unwrap();
db.flush().unwrap();
let r = rows(&db, "SELECT * FROM docs WHERE id = 1");
assert_eq!(r.len(), 0, "Deleted row should not exist");
let r = rows(&db, "SELECT * FROM docs WHERE id = 2");
assert_eq!(r.len(), 1, "Survivor row should exist");
}
#[test]
fn test_empty_string_not_null() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, '')").unwrap();
db.flush().unwrap();
let r = rows(&db, "SELECT * FROM t WHERE id = 1");
assert_eq!(r.len(), 1, "Row with empty string should exist");
match &r[0][1] {
Value::Text(s) => assert_eq!(&**s, "", "Should be empty string, got '{}'", s),
Value::Null => panic!("Empty string incorrectly stored as NULL"),
other => panic!("Unexpected type: {:?}", other),
}
}
#[test]
fn test_null_vs_empty_string_distinct() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, '')").unwrap();
db.execute("INSERT INTO t VALUES (2, NULL)").unwrap();
db.flush().unwrap();
let r = rows(&db, "SELECT id FROM t WHERE val = ''");
assert_eq!(r.len(), 1, "Should find 1 row with empty string");
assert_eq!(r[0][0], Value::Integer(1));
let r = rows(&db, "SELECT id FROM t WHERE val IS NULL");
assert_eq!(r.len(), 1, "Should find 1 NULL row");
assert_eq!(r[0][0], Value::Integer(2));
}
#[test]
fn test_membuffer_stable_dedup_update_survives() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
db.execute("UPDATE t SET val = 20 WHERE id = 1").unwrap();
db.flush().unwrap();
let r = rows(&db, "SELECT val FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(
r[0][0],
Value::Integer(20),
"UPDATE value should survive flush"
);
}
#[test]
fn test_sequential_row_ids() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
for i in 1..=100i64 {
db.execute(&format!("INSERT INTO t VALUES ({}, 'v')", i))
.unwrap();
}
db.flush().unwrap();
let r = rows(&db, "SELECT COUNT(*) FROM t");
assert_eq!(r[0][0], Value::Integer(100), "Should have 100 rows");
}
#[test]
fn test_batch_insert_row_ids() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT, val TEXT)")
.unwrap();
for i in 1..=50i64 {
db.execute(&format!("INSERT INTO t (val) VALUES ('row_{}')", i))
.unwrap();
}
db.flush().unwrap();
let r = rows(&db, "SELECT COUNT(*) FROM t");
assert_eq!(r[0][0], Value::Integer(50));
}
#[test]
fn test_hash_join_integer_float_cross_type() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE a (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("CREATE TABLE b (id INT PRIMARY KEY, val FLOAT)")
.unwrap();
db.execute("INSERT INTO a VALUES (1, 5)").unwrap();
db.execute("INSERT INTO b VALUES (1, 5.0)").unwrap();
db.execute("INSERT INTO b VALUES (2, 3.0)").unwrap();
let r = rows(
&db,
"SELECT a.id, b.id FROM a INNER JOIN b ON a.val = b.val",
);
assert_eq!(r.len(), 1, "Should find 1 match: 5 == 5.0");
assert_eq!(r[0][0], Value::Integer(1));
assert_eq!(r[0][1], Value::Integer(1));
}
#[test]
fn test_hash_join_multiple_matches() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE a (id INT PRIMARY KEY, grp INT)")
.unwrap();
db.execute("CREATE TABLE b (id INT PRIMARY KEY, grp INT)")
.unwrap();
db.execute("INSERT INTO a VALUES (1, 10)").unwrap();
db.execute("INSERT INTO a VALUES (2, 10)").unwrap();
db.execute("INSERT INTO b VALUES (1, 10)").unwrap();
db.execute("INSERT INTO b VALUES (2, 10)").unwrap();
let r = rows(
&db,
"SELECT a.id, b.id FROM a INNER JOIN b ON a.grp = b.grp",
);
assert_eq!(r.len(), 4, "2*2 = 4 matches for grp=10");
}
#[test]
fn test_left_join_no_match() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE a (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("CREATE TABLE b (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO a VALUES (1, 10)").unwrap();
db.execute("INSERT INTO b VALUES (1, 99)").unwrap();
let r = rows(&db, "SELECT a.id, b.id FROM a LEFT JOIN b ON a.val = b.val");
assert_eq!(r.len(), 1, "LEFT JOIN returns left row even with no match");
assert_eq!(r[0][0], Value::Integer(1));
assert!(matches!(r[0][1], Value::Null), "b.id should be NULL");
}
#[test]
fn test_deeply_nested_parens_rejected() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
let mut sql = String::from("SELECT * FROM t WHERE ");
for _ in 0..300 {
sql.push('(');
}
sql.push_str("val = 10");
for _ in 0..300 {
sql.push(')');
}
let result = db.execute(&sql);
match result {
Err(e) => {
let msg = format!("{:?}", e);
assert!(
msg.contains("nesting") || msg.contains("Expected") || msg.contains("parse"),
"Should get parse error, got: {}",
msg
);
}
Ok(_) => {} }
}
#[test]
fn test_duplicate_pk_rejected_in_transaction() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'first')").unwrap();
let result = db.execute("INSERT INTO t VALUES (1, 'second')");
assert!(result.is_err(), "Duplicate PK should be rejected");
}
#[test]
fn test_index_rebuild_skips_deleted_rows() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
for i in 1..=10i64 {
db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i * 10))
.unwrap();
}
db.flush().unwrap();
for i in 1..=5i64 {
db.execute(&format!("DELETE FROM t WHERE id = {}", i))
.unwrap();
}
db.flush().unwrap();
db.execute("CREATE INDEX idx_val ON t (val) USING COLUMN")
.unwrap();
db.flush().unwrap();
let r = rows(&db, "SELECT id FROM t ORDER BY id");
assert_eq!(r.len(), 5, "Only 5 live rows should exist, got {}", r.len());
for i in 0..5 {
let id = 6 + i as i64;
assert_eq!(r[i][0], Value::Integer(id), "Row {} should exist", id);
}
}
#[test]
fn test_sci_notation_overflow_rejected() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val FLOAT)")
.unwrap();
let result = db.execute("INSERT INTO t VALUES (1, 1e500)");
assert!(result.is_err(), "1e500 should be rejected as out of range");
}
#[test]
fn test_sci_notation_underflow_rejected() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val FLOAT)")
.unwrap();
let result = db.execute("INSERT INTO t VALUES (1, 1e-500)");
if let Err(ref e) = result {
eprintln!("1e-500 was rejected: {:?}", e);
}
}
#[test]
fn test_row_count_matches_after_flush() {
let (db, _dir) = create_db();
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();
db.flush().unwrap();
let r = rows(&db, "SELECT COUNT(*) FROM t");
assert_eq!(
r[0][0],
Value::Integer(3),
"COUNT(*) should match number of inserted rows, got {:?}",
r
);
}
#[test]
fn test_full_join_syntax_accepted() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE a (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("CREATE TABLE b (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO a VALUES (1, 10)").unwrap();
db.execute("INSERT INTO b VALUES (2, 20)").unwrap();
let r = rows(&db, "SELECT a.id, b.id FROM a FULL JOIN b ON a.val = b.val");
assert_eq!(
r.len(),
2,
"FULL JOIN should return 2 rows (1 match + 1 NULL)"
);
}
#[test]
fn test_timestamp_integer_equality_consistent() {
use motedb::types::Timestamp;
let ts = Value::Timestamp(Timestamp::from_micros(100));
let int = Value::Integer(100);
assert_eq!(
ts.partial_cmp(&int),
Some(std::cmp::Ordering::Equal),
"partial_cmp(Timestamp(100), Integer(100)) must be Equal"
);
assert!(ts == int, "Timestamp(100) == Integer(100) must be true");
assert!(int == ts, "Integer(100) == Timestamp(100) must be true");
}
#[test]
fn test_timestamp_float_equality_consistent() {
use motedb::types::Timestamp;
let ts = Value::Timestamp(Timestamp::from_micros(42));
let flt = Value::Float(42.0);
assert_eq!(
ts.partial_cmp(&flt),
Some(std::cmp::Ordering::Equal),
"partial_cmp(Timestamp(42), Float(42.0)) must be Equal"
);
assert!(ts == flt, "Timestamp(42) == Float(42.0) must be true");
}
#[test]
fn test_mod_i64_min_minus_one() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val BIGINT)")
.unwrap();
db.execute(&format!("INSERT INTO t VALUES (1, {})", i64::MIN))
.unwrap();
let r = rows(&db, &format!("SELECT val % -1 FROM t WHERE id = 1"));
assert_eq!(r.len(), 1, "Should return 1 row");
assert_eq!(r[0][0], Value::Integer(0), "i64::MIN % -1 should be 0");
}
#[test]
fn test_abs_i64_min_no_panic() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val BIGINT)")
.unwrap();
db.execute(&format!("INSERT INTO t VALUES (1, {})", i64::MIN))
.unwrap();
let r = rows(&db, "SELECT ABS(val) FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
match &r[0][0] {
Value::Float(f) => assert!(*f > 0.0, "abs(i64::MIN) should be positive float"),
Value::Integer(i) => assert!(*i > 0, "abs(i64::MIN) should be positive"),
other => panic!("Expected numeric, got {:?}", other),
}
}
#[test]
fn test_fast_update_add_overflow() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val BIGINT)")
.unwrap();
db.execute(&format!("INSERT INTO t VALUES (1, {})", i64::MAX - 10))
.unwrap();
let result = db.execute("UPDATE t SET val = val + 100 WHERE id = 1");
match result {
Ok(_) => {
let r = rows(&db, "SELECT val FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
match &r[0][0] {
Value::Integer(_) | Value::Float(_) => {}
_ => panic!("Expected numeric"),
}
}
Err(e) => {
let msg = format!("{}", e);
assert!(
msg.contains("validation") || msg.contains("mismatch") || msg.contains("Integer"),
"expected type-validation error, got: {}",
msg
);
}
}
}
#[test]
fn test_fast_update_mul_overflow() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val BIGINT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 1000000000)").unwrap();
let result = db.execute("UPDATE t SET val = val * val WHERE id = 1");
assert!(result.is_ok(), "UPDATE with mul overflow should not panic");
}
#[test]
fn test_left_join_empty_right_table() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE a (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("CREATE TABLE b (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO a VALUES (1, 'alice')").unwrap();
let r = rows(
&db,
"SELECT a.name, b.val FROM a LEFT JOIN b ON a.id = b.id",
);
assert_eq!(
r.len(),
1,
"LEFT JOIN should return 1 row when right table is empty"
);
assert_eq!(
r[0][0],
Value::text("alice".into()),
"left column should have value"
);
assert_eq!(r[0][1], Value::Null, "right column should be NULL");
}
#[test]
fn test_right_join_empty_left_table() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE a (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("CREATE TABLE b (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO b VALUES (1, 42)").unwrap();
let r = rows(
&db,
"SELECT a.name, b.val FROM a RIGHT JOIN b ON a.id = b.id",
);
assert_eq!(
r.len(),
1,
"RIGHT JOIN should return 1 row when left table is empty"
);
assert_eq!(r[0][0], Value::Null, "left column should be NULL");
assert_eq!(
r[0][1],
Value::Integer(42),
"right column should have value"
);
}
#[test]
fn test_substr_negative_start() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'hello')").unwrap();
let r = rows(&db, "SELECT SUBSTR(name, -1) FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(
r[0][0],
Value::text("o".to_string()),
"SUBSTR('hello', -1) should be 'o'"
);
}
#[test]
fn test_substr_zero_start() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'hello')").unwrap();
let r = rows(&db, "SELECT SUBSTR(name, 0) FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(
r[0][0],
Value::text("hello".to_string()),
"SUBSTR('hello', 0) should be 'hello'"
);
}
#[test]
fn test_floor_ceil_large_float() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val FLOAT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 1e18)").unwrap();
let r = rows(&db, "SELECT FLOOR(val), CEIL(val) FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
match &r[0][0] {
Value::Integer(i) => assert_eq!(*i, 1_000_000_000_000_000_000i64),
Value::Float(f) => assert!((*f - 1e18).abs() < 1.0),
other => panic!("Expected numeric, got {:?}", other),
}
match &r[0][1] {
Value::Integer(i) => assert_eq!(*i, 1_000_000_000_000_000_000i64),
Value::Float(f) => assert!((*f - 1e18).abs() < 1.0),
other => panic!("Expected numeric, got {:?}", other),
}
}
#[test]
fn test_create_table_duplicate_column_names() {
let (db, _dir) = create_db();
let result = db.execute("CREATE TABLE t (id INT, id TEXT)");
assert!(
result.is_err(),
"CREATE TABLE with duplicate column names should fail"
);
}
#[test]
fn test_alter_auto_increment_rejects_negative() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT)")
.unwrap();
let result = db.execute("ALTER TABLE t AUTO_INCREMENT = -5");
assert!(
result.is_err(),
"Negative AUTO_INCREMENT should be rejected"
);
}
#[test]
fn test_alter_auto_increment_rejects_non_integer() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT)")
.unwrap();
let result = db.execute("ALTER TABLE t AUTO_INCREMENT = 1.5");
assert!(
result.is_err(),
"Fractional AUTO_INCREMENT should be rejected"
);
}
#[test]
fn test_fast_div_preserves_integer_type() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
let result = db.execute("UPDATE t SET val = val / 3 WHERE id = 1");
assert!(result.is_ok(), "Integer division should succeed");
let r = rows(&db, "SELECT val FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(r[0][0], Value::Integer(3), "10/3 should be Integer(3)");
}
#[test]
fn test_version_chain_end_ts_on_prepend() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
let txn = db.begin_transaction().unwrap();
db.insert_row_with_txn("t", txn, vec![Value::Integer(1), Value::Integer(10)])
.unwrap();
db.commit_transaction(txn).unwrap();
let r = rows(&db, "SELECT val FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(r[0][0], Value::Integer(10));
}
#[test]
fn test_hash_consistency_timestamp_integer() {
use motedb::types::Timestamp;
use std::collections::HashSet;
let ts = Value::Timestamp(Timestamp::from_micros(42));
let int = Value::Integer(42);
assert!(ts == int, "must be equal for hash contract");
let mut set = HashSet::new();
set.insert(ts.clone());
assert!(
set.contains(&int),
"HashSet lookup with equal value must succeed"
);
}
#[test]
fn test_order_by_column_position() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT, score INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'alice', 90)").unwrap();
db.execute("INSERT INTO t VALUES (2, 'bob', 85)").unwrap();
db.execute("INSERT INTO t VALUES (3, 'charlie', 95)")
.unwrap();
let r = rows(&db, "SELECT id, name, score FROM t ORDER BY 3");
assert_eq!(r.len(), 3);
assert_eq!(
r[0][2],
Value::Integer(85),
"First row should have lowest score"
);
assert_eq!(
r[1][2],
Value::Integer(90),
"Second row should have middle score"
);
assert_eq!(
r[2][2],
Value::Integer(95),
"Third row should have highest score"
);
}
#[test]
fn test_order_by_column_position_desc() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val 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 r = rows(&db, "SELECT id, val FROM t ORDER BY 2 DESC");
assert_eq!(r.len(), 3);
assert_eq!(r[0][1], Value::Integer(30));
assert_eq!(r[1][1], Value::Integer(20));
assert_eq!(r[2][1], Value::Integer(10));
}
#[test]
fn test_like_no_exponential_backtracking() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'aaaaaaaaaa')")
.unwrap();
let start = std::time::Instant::now();
let r = rows(
&db,
"SELECT * FROM t WHERE name LIKE '%a%a%a%a%a%a%a%a%a%a'",
);
let elapsed = start.elapsed();
assert!(
elapsed < std::time::Duration::from_secs(2),
"LIKE with many %a patterns took {:?}",
elapsed
);
assert_eq!(r.len(), 1, "Should match the string of a's");
}
#[test]
fn test_like_complex_pattern() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'hello world')")
.unwrap();
db.execute("INSERT INTO t VALUES (2, 'hellX world')")
.unwrap();
db.execute("INSERT INTO t VALUES (3, 'helloworld')")
.unwrap();
let r = rows(&db, "SELECT * FROM t WHERE name LIKE 'h%l_ world'");
assert_eq!(r.len(), 2, "Should match 'hello world' and 'hellX world'");
}
#[test]
fn test_like_prefix_optimization() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'apple')").unwrap();
db.execute("INSERT INTO t VALUES (2, 'application')")
.unwrap();
db.execute("INSERT INTO t VALUES (3, 'banana')").unwrap();
let r = rows(&db, "SELECT * FROM t WHERE name LIKE 'app%'");
assert_eq!(r.len(), 2);
}
#[test]
fn test_ioctree_flush_reopen() {
let dir = TempDir::new().unwrap();
let db_path = dir.path().join("test.mote");
{
let db = Database::create(&db_path).unwrap();
db.execute("CREATE TABLE pts (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO pts VALUES (1, 42)").unwrap();
db.flush().unwrap();
}
let db2 = Database::open(&db_path).unwrap();
let r = rows(&db2, "SELECT val FROM pts WHERE id = 1");
assert_eq!(r.len(), 1, "Should find row after reopen");
assert_eq!(r[0][0], Value::Integer(42));
}
#[test]
fn test_substr_valid_position() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'abcdef')").unwrap();
let r = rows(&db, "SELECT SUBSTR(name, 2, 3) FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(
r[0][0],
Value::text("bcd".into()),
"SUBSTR('abcdef', 2, 3) should be 'bcd'"
);
}
#[test]
fn test_substr_start_past_end() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'hi')").unwrap();
let r = rows(&db, "SELECT SUBSTR(name, 10) FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(
r[0][0],
Value::text(String::new()),
"SUBSTR past end should be empty"
);
}
#[test]
fn test_multiple_updates_version_chain() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
for i in 2..=5 {
db.execute(&format!("UPDATE t SET val = {} WHERE id = 1", i * 10))
.unwrap();
}
let r = rows(&db, "SELECT val FROM t WHERE id = 1");
assert_eq!(r.len(), 1);
assert_eq!(
r[0][0],
Value::Integer(50),
"Should have latest value after 5 updates"
);
}
#[test]
fn test_order_by_qualified_column() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val 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 r = rows(&db, "SELECT id, val FROM t ORDER BY val");
assert_eq!(r.len(), 3);
assert_eq!(r[0][1], Value::Integer(10));
assert_eq!(r[1][1], Value::Integer(20));
assert_eq!(r[2][1], Value::Integer(30));
}
#[test]
fn test_like_underscore_wildcard() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, code TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 'abc')").unwrap();
db.execute("INSERT INTO t VALUES (2, 'abcd')").unwrap();
db.execute("INSERT INTO t VALUES (3, 'ab')").unwrap();
let r = rows(&db, "SELECT * FROM t WHERE code LIKE 'a_c'");
assert_eq!(r.len(), 1, "Only 'abc' should match 'a_c'");
assert_eq!(r[0][0], Value::Integer(1));
}
#[test]
fn test_like_empty_pattern() {
let (db, _dir) = create_db();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, '')").unwrap();
db.execute("INSERT INTO t VALUES (2, 'hello')").unwrap();
let r = rows(&db, "SELECT * FROM t WHERE name LIKE ''");
assert_eq!(r.len(), 1, "Empty pattern should match empty string");
assert_eq!(r[0][0], Value::Integer(1));
}