use crate::{Db, DbOptions, KeyRange};
fn memory_db() -> Db {
Db::open_sync(DbOptions::memory()).expect("open in-memory db")
}
#[test]
fn branch_reads_parent_then_shadows_with_local_writes() {
let db = memory_db();
let bucket = db.bucket_sync("data").expect("bucket");
bucket.put_sync(b"k1".to_vec(), b"v1".to_vec()).expect("p1");
bucket.put_sync(b"k2".to_vec(), b"v2".to_vec()).expect("p2");
let mut branch = db.branch_from_latest().expect("branch");
assert_eq!(
branch.get("data", b"k1").expect("get"),
Some(b"v1".to_vec())
);
branch
.put("data", b"k1", b"v1-branch".to_vec())
.expect("put");
branch.delete("data", b"k2").expect("delete");
assert_eq!(
branch.get("data", b"k1").expect("get"),
Some(b"v1-branch".to_vec())
);
assert_eq!(branch.get("data", b"k2").expect("get"), None);
assert_eq!(bucket.get_sync(b"k1").expect("get"), Some(b"v1".to_vec()));
assert_eq!(bucket.get_sync(b"k2").expect("get"), Some(b"v2".to_vec()));
}
#[test]
fn branch_pins_its_fork_while_the_parent_diverges() {
let db = memory_db();
let bucket = db.bucket_sync("data").expect("bucket");
bucket.put_sync(b"k".to_vec(), b"v1".to_vec()).expect("p1");
let branch = db.branch_from_latest().expect("branch");
bucket.put_sync(b"k".to_vec(), b"v2".to_vec()).expect("p2");
assert_eq!(
branch.get("data", b"k").expect("get"),
Some(b"v1".to_vec()),
"the branch stays frozen at its fork while the parent diverges"
);
assert_eq!(bucket.get_sync(b"k").expect("get"), Some(b"v2".to_vec()));
}
#[test]
fn branch_at_a_retained_past_version_time_travels() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(8))
.expect("open with retention");
let bucket = db.bucket_sync("data").expect("bucket");
bucket.put_sync(b"k".to_vec(), b"v1".to_vec()).expect("p1");
let v1 = db.latest_read_version();
bucket.put_sync(b"k".to_vec(), b"v2".to_vec()).expect("p2");
let old = db.branch_at(v1).expect("branch at v1");
assert_eq!(old.get("data", b"k").expect("get"), Some(b"v1".to_vec()));
let now = db.branch_from_latest().expect("branch now");
assert_eq!(now.get("data", b"k").expect("get"), Some(b"v2".to_vec()));
}
#[test]
fn ephemeral_branch_range_merges_overlay_over_parent() {
let db = memory_db();
let bucket = db.bucket_sync("data").expect("bucket");
for (k, v) in [(b"a", b"1"), (b"b", b"2"), (b"c", b"3")] {
bucket.put_sync(k.to_vec(), v.to_vec()).expect("seed");
}
let mut branch = db.branch_from_latest().expect("branch");
branch
.put("data", b"b", b"2-branch".to_vec())
.expect("override b");
branch.delete("data", b"c").expect("delete c");
branch.put("data", b"d", b"4".to_vec()).expect("add d");
let rows = branch.range("data", &KeyRange::all()).expect("range");
let got: Vec<(Vec<u8>, Vec<u8>)> = rows
.map(|kv| {
let kv = kv.expect("row");
(kv.key, kv.value)
})
.collect();
assert_eq!(
got,
vec![
(b"a".to_vec(), b"1".to_vec()),
(b"b".to_vec(), b"2-branch".to_vec()),
(b"d".to_vec(), b"4".to_vec()),
]
);
}
#[test]
fn durable_branch_persists_writes_and_shadows_parent() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(64)).expect("open");
let bucket = db.bucket_sync("data").expect("bucket");
bucket
.put_sync(b"k1".to_vec(), b"parent".to_vec())
.expect("p1");
bucket
.put_sync(b"k2".to_vec(), b"parent".to_vec())
.expect("p2");
db.create_branch("dev", db.latest_read_version())
.expect("create");
{
let mut dev = db.open_branch("dev").expect("open");
dev.put("data", b"k1", b"dev".to_vec()).expect("put");
dev.delete("data", b"k2").expect("delete");
}
let dev = db.open_branch("dev").expect("reopen");
assert_eq!(dev.get("data", b"k1").expect("get"), Some(b"dev".to_vec()));
assert_eq!(
dev.get("data", b"k2").expect("get"),
None,
"branch tombstone hides parent"
);
assert_eq!(dev.get("data", b"k3").expect("get"), None);
assert_eq!(
bucket.get_sync(b"k1").expect("get"),
Some(b"parent".to_vec())
);
assert_eq!(
bucket.get_sync(b"k2").expect("get"),
Some(b"parent".to_vec())
);
assert_eq!(db.list_branches().expect("list"), vec!["dev".to_string()]);
assert!(dev.is_durable());
}
#[test]
fn durable_branch_survives_reopen_with_default_retention() {
let dir = std::env::temp_dir().join(format!("trine-branch-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
{
let db = Db::open_sync(&dir).expect("open");
let bucket = db.bucket_sync("data").expect("bucket");
bucket
.put_sync(b"k".to_vec(), b"parent".to_vec())
.expect("seed");
db.create_branch("dev", db.latest_read_version())
.expect("create");
let mut dev = db.open_branch("dev").expect("open");
dev.put("data", b"k", b"dev".to_vec()).expect("put");
db.flush_sync().expect("flush");
}
let db = Db::open_sync(&dir).expect("reopen");
assert_eq!(db.list_branches().expect("list"), vec!["dev".to_string()]);
let dev = db.open_branch("dev").expect("open after reopen");
assert_eq!(dev.get("data", b"k").expect("get"), Some(b"dev".to_vec()));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn durable_branch_fork_is_pinned_against_aggressive_gc() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(1)).expect("open");
let bucket = db.bucket_sync("data").expect("bucket");
bucket
.put_sync(b"k".to_vec(), b"forked".to_vec())
.expect("seed");
db.create_branch("dev", db.latest_read_version())
.expect("create");
for i in 0..50 {
bucket
.put_sync(b"k".to_vec(), format!("v{i}").into_bytes())
.expect("churn");
}
let dev = db.open_branch("dev").expect("fork still openable");
assert_eq!(
dev.get("data", b"k").expect("get"),
Some(b"forked".to_vec()),
"the branch still reads its fork value despite aggressive parent GC"
);
}
#[test]
fn delete_branch_releases_the_fork_pin() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(1)).expect("open");
let bucket = db.bucket_sync("data").expect("bucket");
bucket
.put_sync(b"k".to_vec(), b"forked".to_vec())
.expect("seed");
let fork = db.latest_read_version();
db.create_branch("dev", fork).expect("create");
bucket
.put_sync(b"k".to_vec(), b"after".to_vec())
.expect("write");
assert!(
db.branch_at(fork).is_ok(),
"fork pinned while branch exists"
);
db.delete_branch("dev").expect("delete");
assert!(
db.open_branch("dev").is_err(),
"deleted branch cannot be opened"
);
bucket
.put_sync(b"k".to_vec(), b"later".to_vec())
.expect("write");
assert!(
db.branch_at(fork).is_err(),
"the fork is no longer pinned after the branch is deleted"
);
}
#[test]
fn durable_branch_range_merges_over_parent() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(64)).expect("open");
let bucket = db.bucket_sync("data").expect("bucket");
for (k, v) in [(b"a", b"1"), (b"b", b"2"), (b"c", b"3")] {
bucket.put_sync(k.to_vec(), v.to_vec()).expect("seed");
}
db.create_branch("dev", db.latest_read_version())
.expect("create");
let mut dev = db.open_branch("dev").expect("open");
dev.put("data", b"b", b"2-dev".to_vec()).expect("override");
dev.delete("data", b"c").expect("delete");
dev.put("data", b"d", b"4".to_vec()).expect("add");
let rows = dev.range("data", &KeyRange::all()).expect("range");
let got: Vec<(Vec<u8>, Vec<u8>)> = rows
.map(|kv| {
let kv = kv.expect("row");
(kv.key, kv.value)
})
.collect();
assert_eq!(
got,
vec![
(b"a".to_vec(), b"1".to_vec()),
(b"b".to_vec(), b"2-dev".to_vec()),
(b"d".to_vec(), b"4".to_vec()),
]
);
}
#[test]
fn branch_of_branch_reads_through_the_whole_chain() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(64)).expect("open");
let bucket = db.bucket_sync("data").expect("bucket");
bucket
.put_sync(b"base".to_vec(), b"root".to_vec())
.expect("seed");
bucket
.put_sync(b"shared".to_vec(), b"root".to_vec())
.expect("seed");
db.create_branch("a", db.latest_read_version())
.expect("create a");
{
let mut a = db.open_branch("a").expect("open a");
a.put("data", b"shared", b"a".to_vec()).expect("a override");
a.put("data", b"a-only", b"a".to_vec()).expect("a add");
}
db.create_branch_from("b", "a").expect("create b from a");
let mut b = db.open_branch("b").expect("open b");
b.put("data", b"shared", b"b".to_vec()).expect("b override");
b.put("data", b"b-only", b"b".to_vec()).expect("b add");
b.delete("data", b"a-only").expect("b delete a-only");
assert_eq!(b.get("data", b"shared").expect("get"), Some(b"b".to_vec()));
assert_eq!(b.get("data", b"b-only").expect("get"), Some(b"b".to_vec()));
assert_eq!(
b.get("data", b"a-only").expect("get"),
None,
"b deleted a's key"
);
assert_eq!(
b.get("data", b"base").expect("get"),
Some(b"root".to_vec()),
"falls through a (untouched) to the root"
);
let rows = b.range("data", &KeyRange::all()).expect("range");
let got: Vec<(Vec<u8>, Vec<u8>)> = rows
.map(|kv| {
let kv = kv.expect("row");
(kv.key, kv.value)
})
.collect();
assert_eq!(
got,
vec![
(b"b-only".to_vec(), b"b".to_vec()),
(b"base".to_vec(), b"root".to_vec()),
(b"shared".to_vec(), b"b".to_vec()),
]
);
let a = db.open_branch("a").expect("reopen a");
assert_eq!(a.get("data", b"shared").expect("get"), Some(b"a".to_vec()));
assert_eq!(a.get("data", b"a-only").expect("get"), Some(b"a".to_vec()));
assert_eq!(a.get("data", b"b-only").expect("get"), None);
}
#[test]
fn branch_of_branch_is_frozen_when_its_parent_advances() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(64)).expect("open");
db.bucket_sync("data").expect("bucket");
db.create_branch("a", db.latest_read_version())
.expect("create a");
{
let mut a = db.open_branch("a").expect("open a");
a.put("data", b"k", b"a1".to_vec()).expect("a write");
}
db.create_branch_from("b", "a").expect("create b");
{
let mut a = db.open_branch("a").expect("reopen a");
a.put("data", b"k", b"a2".to_vec()).expect("a write later");
}
let b = db.open_branch("b").expect("open b");
assert_eq!(b.get("data", b"k").expect("get"), Some(b"a1".to_vec()));
}
#[test]
fn cannot_delete_branch_with_children() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(64)).expect("open");
db.bucket_sync("data").expect("bucket");
db.create_branch("a", db.latest_read_version())
.expect("create a");
db.create_branch_from("b", "a").expect("create b");
assert!(
db.delete_branch("a").is_err(),
"a still has child b, so it cannot be deleted"
);
db.delete_branch("b").expect("delete child");
db.delete_branch("a")
.expect("delete parent after child gone");
assert!(db.list_branches().expect("list").is_empty());
}
#[test]
fn recreated_branch_does_not_inherit_deleted_branch_data() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(64)).expect("open");
let bucket = db.bucket_sync("data").expect("bucket");
bucket
.put_sync(b"k".to_vec(), b"parent".to_vec())
.expect("seed");
db.create_branch("dev", db.latest_read_version())
.expect("create");
{
let mut dev = db.open_branch("dev").expect("open");
dev.put("data", b"k", b"old".to_vec()).expect("write");
dev.put("data", b"only-old", b"x".to_vec()).expect("write2");
}
db.delete_branch("dev")
.expect("delete (clears the data bucket)");
db.create_branch("dev", db.latest_read_version())
.expect("recreate");
let mut dev = db.open_branch("dev").expect("reopen");
dev.put("data", b"k", b"new".to_vec()).expect("write");
assert_eq!(dev.get("data", b"k").expect("get"), Some(b"new".to_vec()));
assert_eq!(
dev.get("data", b"only-old").expect("get"),
None,
"the deleted branch's data was cleared, not inherited"
);
}
#[test]
fn branch_info_exposes_fork_and_parent_without_opening_data() {
let db = Db::open_sync(DbOptions::memory().with_keep_last_read_versions(64)).expect("open");
db.bucket_sync("data").expect("bucket");
assert!(
db.branch_info("missing").expect("info").is_none(),
"an unknown branch has no lineage"
);
let fork = db.latest_read_version();
db.create_branch("a", fork).expect("create a");
let a = db.branch_info("a").expect("info").expect("a present");
assert_eq!(a.fork(), fork, "exposes the fork version for fall-through");
assert_eq!(a.parent(), None, "a forked the root lineage");
db.create_branch_from("b", "a").expect("create b");
let b = db.branch_info("b").expect("info").expect("b present");
assert_eq!(b.parent(), Some("a"), "exposes the parent for nesting");
}
#[test]
fn drop_bucket_removes_it_in_memory() {
let db = memory_db();
let bucket = db.bucket_sync("scratch").expect("bucket");
bucket.put_sync(b"k".to_vec(), b"v".to_vec()).expect("put");
db.drop_bucket_sync("scratch").expect("drop");
assert!(
db.drop_bucket_sync("scratch").is_err(),
"dropping a gone bucket errors"
);
assert!(
db.drop_bucket_sync("default").is_err(),
"the default bucket cannot be dropped"
);
let fresh = db.bucket_sync("scratch").expect("recreate");
assert_eq!(fresh.get_sync(b"k").expect("get"), None);
}
#[test]
fn drop_bucket_persists_across_reopen() {
let dir = std::env::temp_dir().join(format!("trine-drop-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
{
let db = Db::open_sync(&dir).expect("open");
db.bucket_sync("scratch")
.expect("scratch")
.put_sync(b"k".to_vec(), b"v".to_vec())
.expect("put");
db.bucket_sync("keep")
.expect("keep")
.put_sync(b"k".to_vec(), b"keep".to_vec())
.expect("put");
db.drop_bucket_sync("scratch").expect("drop");
}
let db = Db::open_sync(&dir).expect("reopen");
assert_eq!(
db.bucket_sync("scratch")
.expect("scratch")
.get_sync(b"k")
.expect("get"),
None,
"dropped bucket did not come back with its data"
);
assert_eq!(
db.bucket_sync("keep")
.expect("keep")
.get_sync(b"k")
.expect("get"),
Some(b"keep".to_vec()),
"an untouched bucket survives the drop"
);
let _ = std::fs::remove_dir_all(&dir);
}