use mongreldb_core::{
schema::{ColumnDef, ColumnFlags, Schema, TypeId},
RowId, Snapshot, Table, Value,
};
use tempfile::tempdir;
fn schema() -> Schema {
Schema {
schema_id: 1,
columns: vec![
ColumnDef {
id: 1,
name: "id".into(),
ty: TypeId::Int64,
flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
},
ColumnDef {
id: 2,
name: "v".into(),
ty: TypeId::Int64,
flags: ColumnFlags::empty(),
},
],
indexes: vec![],
colocation: vec![],
constraints: Default::default(),
clustered: false,
}
}
fn put(db: &mut Table, id: i64, v: i64) -> RowId {
db.put(vec![(1, Value::Int64(id)), (2, Value::Int64(v))])
.unwrap()
}
#[test]
fn flush_coalesces_into_mutable_run_without_writing_a_run() {
let dir = tempdir().unwrap();
let mut db = Table::create(dir.path(), schema(), 1).unwrap();
let r0 = put(&mut db, 0, 100);
let r1 = put(&mut db, 1, 101);
db.flush().unwrap();
assert_eq!(
db.run_count(),
0,
"no run should be written below the spill watermark"
);
assert!(db.memtable_len() == 0, "memtable drained into the tier");
assert!(
db.mutable_run_len() >= 2,
"rows live in the mutable-run tier"
);
let snap = db.snapshot();
assert!(matches!(
db.get(r0, snap).unwrap().columns.get(&2),
Some(Value::Int64(100))
));
assert!(matches!(
db.get(r1, snap).unwrap().columns.get(&2),
Some(Value::Int64(101))
));
assert_eq!(db.visible_rows(snap).unwrap().len(), 2);
}
#[test]
fn multiple_flushes_coalesce_into_one_run_on_spill() {
let dir = tempdir().unwrap();
let mut db = Table::create(dir.path(), schema(), 1).unwrap();
db.set_mutable_run_spill_bytes(1);
for i in 0..10 {
put(&mut db, i, i * 10);
db.flush().unwrap();
}
assert_eq!(db.run_count(), 10);
let snap = db.snapshot();
assert_eq!(db.visible_rows(snap).unwrap().len(), 10);
db.set_mutable_run_spill_bytes(u64::MAX);
for i in 10..20 {
put(&mut db, i, i * 10);
db.flush().unwrap();
}
assert_eq!(
db.run_count(),
10,
"coalesced flushes add no runs below watermark"
);
assert!(db.mutable_run_len() >= 10);
let snap = db.snapshot();
assert_eq!(
db.visible_rows(snap).unwrap().len(),
20,
"all 20 rows visible"
);
}
#[test]
fn mvcc_merge_across_memtable_tier_and_run() {
let dir = tempdir().unwrap();
let mut db = Table::create(dir.path(), schema(), 1).unwrap();
db.set_mutable_run_spill_bytes(1);
let r = put(&mut db, 1, 10);
db.flush().unwrap(); db.set_mutable_run_spill_bytes(u64::MAX);
db.delete(r).unwrap();
db.flush().unwrap();
let r2 = put(&mut db, 2, 20);
db.flush().unwrap();
let snap = db.snapshot();
assert!(
db.get(r, snap).is_none(),
"deleted row hidden by the tier's tombstone"
);
assert!(db.get(r2, snap).is_some());
assert!(matches!(
db.get(r2, snap).unwrap().columns.get(&2),
Some(Value::Int64(20))
));
assert_eq!(db.visible_rows(snap).unwrap().len(), 1);
}
#[test]
fn reopen_rebuilds_unflushed_tier_from_wal_replay() {
let dir = tempdir().unwrap();
let path = dir.path().to_path_buf();
{
let mut db = Table::create(&path, schema(), 1).unwrap();
put(&mut db, 0, 5);
put(&mut db, 1, 6);
db.flush().unwrap();
assert_eq!(db.run_count(), 0, "data is only in the tier + WAL");
}
let db = Table::open(&path).unwrap();
assert_eq!(db.run_count(), 0);
assert_eq!(db.count(), 2);
let snap = db.snapshot();
assert_eq!(db.visible_rows(snap).unwrap().len(), 2);
assert!(matches!(
db.get(RowId(0), snap).unwrap().columns.get(&2),
Some(Value::Int64(5))
));
let _ = Snapshot::at(db.current_epoch());
}
#[test]
fn rows_for_rids_overlay_shadows_run_and_drops_tombstones() {
let dir = tempdir().unwrap();
let mut db = Table::create(dir.path(), schema(), 1).unwrap();
db.bulk_load(vec![vec![(1, Value::Int64(7)), (2, Value::Int64(70))]])
.unwrap();
assert_eq!(db.run_count(), 1);
let snap = db.snapshot();
let from_run = db.rows_for_rids(&[0], snap).unwrap();
assert_eq!(from_run.len(), 1);
assert!(matches!(
from_run[0].columns.get(&2),
Some(Value::Int64(70))
));
let r_new = put(&mut db, 9, 99);
db.commit().unwrap();
let snap = db.snapshot();
let from_overlay = db.rows_for_rids(&[r_new.0], snap).unwrap();
assert_eq!(from_overlay.len(), 1);
assert!(matches!(
from_overlay[0].columns.get(&2),
Some(Value::Int64(99))
));
db.delete(r_new).unwrap();
db.commit().unwrap();
let snap = db.snapshot();
assert_eq!(db.rows_for_rids(&[r_new.0], snap).unwrap().len(), 0);
}