use motedb::{sql::QueryResult, types::Value, Database};
fn exec(db: &Database, sql: &str) -> QueryResult {
db.execute(sql).unwrap().materialize().unwrap()
}
fn get_first_count(db: &Database, sql: &str) -> i64 {
match exec(db, sql) {
QueryResult::Select { rows, .. } => rows
.first()
.and_then(|r| r.first())
.and_then(|v| match v {
Value::Integer(i) => Some(*i),
_ => None,
})
.unwrap_or(0),
_ => 0,
}
}
fn remove_db(path: &str) {
let _ = std::fs::remove_dir_all(path);
let _ = std::fs::remove_dir_all(format!("{}.mote", path));
}
#[test]
fn test_txn_writes_visible_after_commit_lsm() {
let dir = "/tmp/motedb_mvcc_test_1";
remove_db(dir);
let db = Database::create(dir).unwrap();
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(100)])
.unwrap();
db.commit_transaction(txn).unwrap();
let result = exec(&db, "SELECT * FROM t WHERE id = 1");
let rows = match result {
QueryResult::Select { rows, .. } => rows,
_ => vec![],
};
assert_eq!(
rows.len(),
1,
"Committed transactional row should be visible"
);
assert_eq!(rows[0][1], Value::Integer(100));
db.close().unwrap();
remove_db(dir);
}
#[test]
fn test_txn_inserts_survive_crash() {
let dir = "/tmp/motedb_mvcc_test_2";
remove_db(dir);
{
let db = Database::create(dir).unwrap();
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(111)])
.unwrap();
db.insert_row_with_txn("t", txn, vec![Value::Integer(2), Value::Integer(222)])
.unwrap();
db.commit_transaction(txn).unwrap();
db.execute("INSERT INTO t VALUES (3, 333)").unwrap();
db.flush().unwrap();
db.close().unwrap();
}
{
let db = Database::open(dir).unwrap();
let cnt = get_first_count(&db, "SELECT COUNT(*) FROM t");
assert_eq!(
cnt, 3,
"All 3 rows should survive crash (2 txn + 1 auto-commit)"
);
let r1 = exec(&db, "SELECT val FROM t WHERE id = 1");
let rows1 = match r1 {
QueryResult::Select { rows, .. } => rows,
_ => vec![],
};
assert_eq!(rows1[0][0], Value::Integer(111));
let r2 = exec(&db, "SELECT val FROM t WHERE id = 2");
let rows2 = match r2 {
QueryResult::Select { rows, .. } => rows,
_ => vec![],
};
assert_eq!(rows2[0][0], Value::Integer(222));
db.close().unwrap();
}
remove_db(dir);
}
#[test]
fn test_write_write_conflict_detection() {
let dir = "/tmp/motedb_mvcc_test_3";
remove_db(dir);
let db = Database::create(dir).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
let txn1 = db.begin_transaction().unwrap();
db.insert_row_with_txn("t", txn1, vec![Value::Integer(1), Value::Integer(100)])
.unwrap();
db.commit_transaction(txn1).unwrap();
let result = exec(&db, "SELECT * FROM t WHERE id = 1");
let rows = match &result {
QueryResult::Select { rows, .. } => rows,
_ => &vec![],
};
assert_eq!(rows.len(), 1, "PK lookup should find committed txn row");
assert_eq!(rows[0][1], Value::Integer(100));
db.close().unwrap();
remove_db(dir);
}
#[test]
fn test_txn_rollback_discards_writes() {
let dir = "/tmp/motedb_mvcc_test_4";
remove_db(dir);
{
let db = Database::create(dir).unwrap();
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(100)])
.unwrap();
db.rollback_transaction(txn).unwrap();
let result = exec(&db, "SELECT * FROM t WHERE id = 1");
let rows = match result {
QueryResult::Select { rows, .. } => rows,
_ => vec![],
};
assert!(rows.is_empty(), "Rolled-back row should not be visible");
db.flush().unwrap();
db.close().unwrap();
}
{
let db = Database::open(dir).unwrap();
let cnt = get_first_count(&db, "SELECT COUNT(*) FROM t");
assert_eq!(cnt, 0, "Rolled-back data should not survive crash");
db.close().unwrap();
}
remove_db(dir);
}
#[test]
fn test_mixed_autocommit_and_txn_writes() {
let dir = "/tmp/motedb_mvcc_test_5";
remove_db(dir);
{
let db = Database::create(dir).unwrap();
db.execute("CREATE TABLE t (id INT PRIMARY KEY, val INT)")
.unwrap();
db.execute("INSERT INTO t VALUES (1, 10)").unwrap();
let txn = db.begin_transaction().unwrap();
db.insert_row_with_txn("t", txn, vec![Value::Integer(2), Value::Integer(20)])
.unwrap();
db.commit_transaction(txn).unwrap();
db.execute("INSERT INTO t VALUES (3, 30)").unwrap();
db.flush().unwrap();
db.close().unwrap();
}
{
let db = Database::open(dir).unwrap();
let cnt = get_first_count(&db, "SELECT COUNT(*) FROM t");
assert_eq!(cnt, 3, "All 3 rows should survive crash");
db.close().unwrap();
}
remove_db(dir);
}