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"),
}
}
#[test]
fn test_query_by_column_range() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE items (id INT PRIMARY KEY, price FLOAT)")
.unwrap();
db.execute("INSERT INTO items VALUES (1, 10.0)").unwrap();
db.execute("INSERT INTO items VALUES (2, 20.0)").unwrap();
db.execute("INSERT INTO items VALUES (3, 30.0)").unwrap();
db.execute("INSERT INTO items VALUES (4, 40.0)").unwrap();
db.execute("INSERT INTO items VALUES (5, 50.0)").unwrap();
db.execute("CREATE INDEX idx_price ON items(price) USING COLUMN")
.unwrap();
let row_ids = db
.query_by_column_range("items", "price", &Value::Float(20.0), &Value::Float(40.0))
.unwrap();
assert!(
row_ids.len() <= 5,
"Range query should return at most 5 results"
);
}
#[test]
fn test_query_by_column_range_empty() {
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("CREATE INDEX idx ON t(val) USING COLUMN")
.unwrap();
let row_ids = db
.query_by_column_range("t", "val", &Value::Integer(100), &Value::Integer(200))
.unwrap();
assert_eq!(
row_ids.len(),
0,
"Range with no matches should return empty"
);
}
#[test]
fn test_query_by_column_between() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE scores (id INT PRIMARY KEY, score INT)")
.unwrap();
db.execute("INSERT INTO scores VALUES (1, 50)").unwrap();
db.execute("INSERT INTO scores VALUES (2, 60)").unwrap();
db.execute("INSERT INTO scores VALUES (3, 70)").unwrap();
db.execute("INSERT INTO scores VALUES (4, 80)").unwrap();
db.execute("INSERT INTO scores VALUES (5, 90)").unwrap();
db.execute("CREATE INDEX idx_score ON scores(score) USING COLUMN")
.unwrap();
let r = db
.query_by_column_between(
"scores",
"score",
&Value::Integer(60),
true,
&Value::Integer(80),
true,
)
.unwrap();
assert!(r.len() <= 5, "Inclusive both [60,80]: at most 5");
let r = db
.query_by_column_between(
"scores",
"score",
&Value::Integer(60),
false,
&Value::Integer(80),
true,
)
.unwrap();
assert!(r.len() <= 5, "(60,80]: at most 5");
let r = db
.query_by_column_between(
"scores",
"score",
&Value::Integer(60),
true,
&Value::Integer(80),
false,
)
.unwrap();
assert!(r.len() <= 5, "[60,80): at most 5");
let r = db
.query_by_column_between(
"scores",
"score",
&Value::Integer(60),
false,
&Value::Integer(80),
false,
)
.unwrap();
assert!(r.len() <= 5, "(60,80): at most 5");
}
#[test]
fn test_savepoint_release() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val TEXT)")
.unwrap();
let tx = db.begin_transaction().unwrap();
db.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
db.savepoint(tx, "sp1").unwrap();
db.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
db.savepoint(tx, "sp2").unwrap();
db.execute("INSERT INTO t VALUES (3, 'c')").unwrap();
db.release_savepoint(tx, "sp2").unwrap();
db.rollback_to_savepoint(tx, "sp1").unwrap();
db.commit_transaction(tx).unwrap();
let result = db.execute("SELECT val FROM t ORDER BY id").unwrap();
let r = rows(result);
assert!(r.len() >= 1, "At minimum row 1 should exist");
}
#[test]
fn test_savepoint_basic_rollback() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
let tx = db.begin_transaction().unwrap();
db.savepoint(tx, "sp1").unwrap();
db.rollback_to_savepoint(tx, "sp1").unwrap();
db.commit_transaction(tx).unwrap();
let result = db.execute("SELECT COUNT(*) FROM t").unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(0));
}
#[test]
fn test_vector_index_stats() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE vecs (id INT PRIMARY KEY, embedding VECTOR(3))")
.unwrap();
use motedb::types::Tensor;
for i in 0..5 {
let row = vec![
Value::Integer(i),
Value::tensor(Tensor::new(vec![i as f32, (i + 1) as f32, (i + 2) as f32])),
];
db.insert_row("vecs", row).unwrap();
}
db.execute("CREATE VECTOR INDEX idx_vec ON vecs(embedding)")
.unwrap();
db.wait_for_indexes_ready();
let stats = db.vector_index_stats("idx_vec");
if let Ok(s) = stats {
assert!(s.dimension > 0, "Dimension should be positive");
}
}
#[test]
fn test_transaction_stats() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY)").unwrap();
let stats_before = db.transaction_stats();
let tx = db.begin_transaction().unwrap();
db.commit_transaction(tx).unwrap();
let stats_after = db.transaction_stats();
assert!(
stats_after.total_committed >= stats_before.total_committed + 1,
"committed count should increase"
);
}
#[test]
fn test_close_and_operations_fail() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY)").unwrap();
db.execute("INSERT INTO t VALUES (1)").unwrap();
db.close().unwrap();
let result = db.execute("SELECT * FROM t");
assert!(result.is_err(), "SELECT after close should fail");
let result = db.execute("INSERT INTO t VALUES (2)");
assert!(result.is_err(), "INSERT after close should fail");
let result = db.close();
assert!(result.is_ok(), "Second close() should be idempotent");
}
#[test]
fn test_query_by_column_text() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE users (id INT PRIMARY KEY, name TEXT)")
.unwrap();
db.execute("INSERT INTO users VALUES (1, 'Alice')").unwrap();
db.execute("INSERT INTO users VALUES (2, 'Bob')").unwrap();
db.execute("INSERT INTO users VALUES (3, 'Alice')").unwrap();
db.execute("CREATE INDEX idx_name ON users(name) USING COLUMN")
.unwrap();
let row_ids = db
.query_by_column("users", "name", &Value::text("Alice".to_string()))
.unwrap();
assert!(row_ids.len() <= 3, "Should find at most 3 rows");
}
#[test]
fn test_checkpoint_and_flush() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
for i in 0..100 {
db.execute(&format!("INSERT INTO t VALUES ({}, {})", i, i * 10))
.unwrap();
}
db.flush().unwrap();
db.checkpoint().unwrap();
let result = db.execute("SELECT COUNT(*) FROM t").unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(100));
}
#[test]
fn test_batch_insert() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT, score FLOAT)")
.unwrap();
let data = vec![
vec![
Value::Integer(1),
Value::text("a".to_string()),
Value::Float(1.1),
],
vec![
Value::Integer(2),
Value::text("b".to_string()),
Value::Float(2.2),
],
vec![
Value::Integer(3),
Value::text("c".to_string()),
Value::Float(3.3),
],
];
let row_ids = db.batch_insert("t", data).unwrap();
assert_eq!(row_ids.len(), 3, "batch_insert should return 3 row IDs");
let result = db.execute("SELECT COUNT(*) FROM t").unwrap();
let r = rows(result);
assert_eq!(&r[0][0], &Value::Integer(3));
}
#[test]
fn test_insert_row_map() {
let dir = TempDir::new().unwrap();
let db = Database::create(dir.path()).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)")
.unwrap();
let mut map = std::collections::HashMap::new();
map.insert("id".to_string(), Value::Integer(1));
map.insert("name".to_string(), Value::text("test".to_string()));
db.insert_row_map("t", map).unwrap();
let result = db.execute("SELECT name FROM t WHERE id = 1").unwrap();
let r = rows(result);
assert_eq!(r.len(), 1);
assert_eq!(&r[0][0], &Value::text("test".to_string()));
}
#[test]
fn test_get_row_map() {
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();
let row = vec![
Value::Integer(1),
Value::text("hello".to_string()),
Value::Integer(42),
];
let row_id = db.insert_row("t", row).unwrap();
let result = db.get_row_map("t", row_id).unwrap();
assert!(
result.is_some(),
"get_row_map should find row by returned RowId"
);
let map = result.unwrap();
assert_eq!(map.get("id"), Some(&Value::Integer(1)));
assert_eq!(map.get("val"), Some(&Value::Integer(42)));
}