use std::time::Duration;
use pagedb::vfs::memory::MemVfs;
use pagedb::{CommitId, Db, OpenOptions, PagedbError, RealmId, RealmQuotas, RetainPolicy};
const PAGE: usize = 4096;
const KEK: [u8; 32] = [7u8; 32];
const REALM: RealmId = RealmId::new([1u8; 16]);
async fn open_with_policy(policy: RetainPolicy) -> Db<MemVfs> {
let opts = OpenOptions::default().with_commit_history_retain(policy);
Db::open(MemVfs::new(), KEK, PAGE, REALM, opts)
.await
.unwrap()
}
async fn write_n(db: &Db<MemVfs>, n: usize) -> Vec<CommitId> {
let mut ids = Vec::with_capacity(n);
for i in 0..n {
let mut w = db.begin_write().await.unwrap();
let val = (i as u64).to_le_bytes().to_vec();
w.put(b"k", &val).await.unwrap();
let cid = w.commit().await.unwrap();
ids.push(cid);
}
ids
}
#[tokio::test(flavor = "current_thread")]
async fn begin_read_at_recent_commit() {
let db = open_with_policy(RetainPolicy::Unbounded).await;
let ids = write_n(&db, 5).await;
for (i, &cid) in ids.iter().enumerate() {
let rtxn = db.begin_read_at(cid).await.unwrap();
let val = rtxn.get(b"k").await.unwrap().expect("key must exist");
let stored = u64::from_le_bytes(val.as_ref().try_into().unwrap());
assert_eq!(stored, i as u64, "commit {cid:?} should see value {i}");
assert_eq!(rtxn.commit_id(), cid);
}
}
#[tokio::test(flavor = "current_thread")]
async fn begin_read_at_pruned_returns_commit_gone() {
let db = open_with_policy(RetainPolicy::Count(3)).await;
let ids = write_n(&db, 10).await;
let result = db.begin_read_at(ids[0]).await;
match result {
Err(PagedbError::CommitGone {
commit,
oldest_available,
}) => {
assert_eq!(commit, ids[0]);
assert!(
oldest_available.value() >= ids[6].value(),
"oldest_available={oldest_available:?} expected >= {:?}",
ids[6]
);
}
Err(other) => panic!("expected CommitGone, got {other:?}"),
Ok(_) => panic!("expected CommitGone for pruned commit, but got Ok"),
}
}
#[tokio::test(flavor = "current_thread")]
async fn retain_policy_count_prunes_to_n() {
let db = open_with_policy(RetainPolicy::Count(3)).await;
let ids = write_n(&db, 10).await;
let mut reachable = Vec::new();
for &cid in &ids {
if db.begin_read_at(cid).await.is_ok() {
reachable.push(cid);
}
}
assert_eq!(
reachable.len(),
3,
"expected exactly 3 reachable commits, got {reachable:?}"
);
assert_eq!(reachable, &ids[7..]);
}
#[tokio::test(flavor = "current_thread")]
async fn retain_policy_unbounded_keeps_all() {
let db = open_with_policy(RetainPolicy::Unbounded).await;
let ids = write_n(&db, 20).await;
for &cid in &ids {
db.begin_read_at(cid)
.await
.unwrap_or_else(|e| panic!("expected commit {cid:?} to be reachable, got {e:?}"));
}
}
#[tokio::test(flavor = "current_thread")]
async fn reader_pin_blocks_pruning() {
let db = open_with_policy(RetainPolicy::Count(2)).await;
let ids = write_n(&db, 1).await;
let first = ids[0];
let _pinned = db.begin_read_at(first).await.unwrap();
write_n(&db, 10).await;
db.begin_read_at(first)
.await
.unwrap_or_else(|e| panic!("pinned commit should still be reachable, got {e:?}"));
}
#[tokio::test(flavor = "current_thread")]
async fn history_persists_across_reopen() {
let vfs = MemVfs::new();
let opts = OpenOptions::default().with_commit_history_retain(RetainPolicy::Unbounded);
let ids = {
let db = Db::open(vfs.clone(), KEK, PAGE, REALM, opts.clone())
.await
.unwrap();
write_n(&db, 5).await
};
let db2 = Db::open(vfs, KEK, PAGE, REALM, opts).await.unwrap();
let cid3 = ids[2]; db2.begin_read_at(cid3)
.await
.unwrap_or_else(|e| panic!("expected commit {cid3:?} to survive reopen, got {e:?}"));
}
#[tokio::test(flavor = "current_thread")]
async fn quota_update_preserves_history_across_reopen() {
let vfs = MemVfs::new();
let opts = OpenOptions::default().with_commit_history_retain(RetainPolicy::Unbounded);
let first_commit = {
let db = Db::open(vfs.clone(), KEK, PAGE, REALM, opts.clone())
.await
.unwrap();
let ids = write_n(&db, 2).await;
db.set_realm_quotas(REALM, RealmQuotas::default())
.await
.unwrap();
db.begin_read_at(ids[0])
.await
.expect("quota publication must preserve live retained history");
ids[0]
};
let reopened = Db::open(vfs, KEK, PAGE, REALM, opts).await.unwrap();
let historical = reopened
.begin_read_at(first_commit)
.await
.expect("quota publication must preserve retained history after reopen");
assert_eq!(
historical.get(b"k").await.unwrap().as_deref(),
Some(0_u64.to_le_bytes().as_slice())
);
}
#[tokio::test(flavor = "current_thread")]
async fn retain_policy_age_prunes_old() {
let db = open_with_policy(RetainPolicy::Age(Duration::from_secs(0))).await;
let ids = write_n(&db, 3).await;
db.begin_read_at(ids[2])
.await
.unwrap_or_else(|e| panic!("latest commit should be reachable, got {e:?}"));
for &cid in &ids[..2] {
let result = db.begin_read_at(cid).await;
let _ = result;
}
let latest_txn = db.begin_read_at(ids[2]).await.unwrap();
assert_eq!(latest_txn.commit_id(), ids[2]);
}