#![cfg(feature = "durability")]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use lsm_db::{Batch, Lsm, LsmConfig};
fn durable(dir: &std::path::Path) -> Lsm {
Lsm::open_with(dir, LsmConfig::new().compaction_trigger(usize::MAX)).unwrap()
}
#[test]
fn unflushed_writes_are_recovered() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
db.put(b"a", b"1").unwrap();
db.put(b"b", b"2").unwrap();
}
let db = durable(dir.path());
assert_eq!(db.get(b"a").unwrap(), Some(b"1".to_vec()));
assert_eq!(db.get(b"b").unwrap(), Some(b"2".to_vec()));
}
#[test]
fn unflushed_overwrite_is_recovered() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
db.put(b"k", b"first").unwrap();
db.flush().unwrap(); db.put(b"k", b"second").unwrap(); }
let db = durable(dir.path());
assert_eq!(db.get(b"k").unwrap(), Some(b"second".to_vec()));
}
#[test]
fn unflushed_delete_is_recovered() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
db.put(b"k", b"v").unwrap();
db.flush().unwrap();
db.delete(b"k").unwrap(); }
let db = durable(dir.path());
assert_eq!(db.get(b"k").unwrap(), None);
}
#[test]
fn unflushed_batch_is_recovered_atomically() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
let mut batch = Batch::new();
for i in 0..200u32 {
batch.put(format!("k{i:03}").into_bytes(), b"v");
}
db.write(batch).unwrap();
}
let db = durable(dir.path());
assert_eq!(db.scan(..).unwrap().count(), 200);
assert_eq!(db.get(b"k000").unwrap(), Some(b"v".to_vec()));
assert_eq!(db.get(b"k199").unwrap(), Some(b"v".to_vec()));
}
#[test]
fn mixed_flushed_and_unflushed_recover() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
db.put(b"flushed", b"1").unwrap();
db.flush().unwrap();
db.put(b"buffered-a", b"2").unwrap();
db.put(b"buffered-b", b"3").unwrap();
}
let db = durable(dir.path());
assert_eq!(db.get(b"flushed").unwrap(), Some(b"1".to_vec()));
assert_eq!(db.get(b"buffered-a").unwrap(), Some(b"2".to_vec()));
assert_eq!(db.get(b"buffered-b").unwrap(), Some(b"3".to_vec()));
assert_eq!(db.scan(..).unwrap().count(), 3);
}
#[test]
fn reopen_twice_does_not_duplicate() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
db.put(b"k", b"v").unwrap();
}
{
let db = durable(dir.path());
assert_eq!(db.get(b"k").unwrap(), Some(b"v".to_vec()));
assert_eq!(db.scan(..).unwrap().count(), 1);
}
let db = durable(dir.path());
assert_eq!(db.get(b"k").unwrap(), Some(b"v".to_vec()));
assert_eq!(db.scan(..).unwrap().count(), 1);
}
#[test]
fn writes_continue_after_recovery() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
db.put(b"before", b"1").unwrap();
}
let db = durable(dir.path());
db.put(b"after", b"2").unwrap();
assert_eq!(db.get(b"before").unwrap(), Some(b"1".to_vec()));
assert_eq!(db.get(b"after").unwrap(), Some(b"2".to_vec()));
drop(db);
let db = durable(dir.path());
assert_eq!(db.get(b"after").unwrap(), Some(b"2".to_vec()));
}
#[test]
fn many_unflushed_writes_recover() {
let dir = tempfile::tempdir().unwrap();
{
let db = durable(dir.path());
for i in 0..1_000u32 {
db.put(
format!("key-{i:04}").into_bytes(),
format!("val-{i}").into_bytes(),
)
.unwrap();
}
for i in 0..200u32 {
db.delete(format!("key-{i:04}").into_bytes()).unwrap();
}
}
let db = durable(dir.path());
assert_eq!(db.scan(..).unwrap().count(), 800);
assert_eq!(db.get(b"key-0000").unwrap(), None);
assert_eq!(db.get(b"key-0199").unwrap(), None);
assert_eq!(db.get(b"key-0200").unwrap(), Some(b"val-200".to_vec()));
assert_eq!(db.get(b"key-0999").unwrap(), Some(b"val-999".to_vec()));
}