use noxu_db::{
Comparator, DatabaseConfig, DatabaseEntry, EnvironmentConfig, Get,
OperationStatus,
};
use tempfile::TempDir;
fn env(dir: &TempDir) -> noxu_db::Environment {
let cfg = EnvironmentConfig::new(dir.path().to_path_buf())
.with_allow_create(true)
.with_transactional(true);
noxu_db::Environment::open(cfg).unwrap()
}
fn put(db: &noxu_db::Database, k: &[u8], v: &[u8]) {
db.put(DatabaseEntry::from_bytes(k), DatabaseEntry::from_bytes(v)).unwrap();
}
fn cursor_keys(db: &noxu_db::Database) -> Vec<Vec<u8>> {
let mut cur = db.open_cursor(None).unwrap();
let mut key = DatabaseEntry::new();
let mut data = DatabaseEntry::new();
let mut out = Vec::new();
let mut s = cur.get(&mut key, &mut data, Get::First, None).unwrap();
while s == OperationStatus::Success {
out.push(key.data().to_vec());
s = cur.get(&mut key, &mut data, Get::Next, None).unwrap();
}
out
}
#[test]
fn headline1_reverse_btree_comparator_orders_cursor_walk() {
let dir = TempDir::new().unwrap();
let e = env(&dir);
let cmp = Comparator::new("reverse", |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_btree_comparator(cmp);
let db = e.open_database(None, "rev", &cfg).unwrap();
for k in [b"a".as_ref(), b"b", b"c", b"d", b"e"] {
put(&db, k, b"v");
}
let keys = cursor_keys(&db);
assert_eq!(
keys,
vec![
b"e".to_vec(),
b"d".to_vec(),
b"c".to_vec(),
b"b".to_vec(),
b"a".to_vec()
],
"cursor walk must follow the reverse comparator, not byte order"
);
}
#[test]
fn headline1_le_integer_comparator_diverges_from_byte_order() {
let dir = TempDir::new().unwrap();
let e = env(&dir);
let cmp = Comparator::new("le_u32", |a: &[u8], b: &[u8]| {
let pa = u32::from_le_bytes(a.try_into().unwrap());
let pb = u32::from_le_bytes(b.try_into().unwrap());
pa.cmp(&pb)
});
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_btree_comparator(cmp);
let db = e.open_database(None, "le", &cfg).unwrap();
let vals: [u32; 3] = [1, 256, 65536];
for v in vals {
put(&db, &v.to_le_bytes(), b"v");
}
let keys = cursor_keys(&db);
let got: Vec<u32> = keys
.iter()
.map(|k| u32::from_le_bytes(k[..].try_into().unwrap()))
.collect();
assert_eq!(got, vec![1u32, 256, 65536]);
let mut cur = db.open_cursor(None).unwrap();
let mut key = DatabaseEntry::from_bytes(&200u32.to_le_bytes());
let mut data = DatabaseEntry::new();
let s = cur.get(&mut key, &mut data, Get::SearchGte, None).unwrap();
assert_eq!(s, OperationStatus::Success);
assert_eq!(u32::from_le_bytes(key.data().try_into().unwrap()), 256);
}
#[test]
fn headline2_reopen_without_matching_comparator_fails() {
let dir = TempDir::new().unwrap();
{
let e = env(&dir);
let cmp = Comparator::new("reverse", |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_btree_comparator(cmp);
let db = e.open_database(None, "rev", &cfg).unwrap();
put(&db, b"a", b"1");
put(&db, b"b", b"2");
drop(db);
e.close().unwrap();
}
let e = env(&dir);
let cfg =
DatabaseConfig::new().with_allow_create(false).with_transactional(true);
let res = e.open_database(None, "rev", &cfg);
assert!(
res.is_err(),
"reopen without matching comparator must fail, not silently \
reinterpret a comparator-ordered tree as byte-ordered"
);
}
#[test]
fn headline2_reopen_with_matching_identity_succeeds() {
let dir = TempDir::new().unwrap();
{
let e = env(&dir);
let cmp = Comparator::new("reverse", |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_btree_comparator(cmp);
let db = e.open_database(None, "rev", &cfg).unwrap();
put(&db, b"a", b"1");
put(&db, b"b", b"2");
put(&db, b"c", b"3");
drop(db);
e.close().unwrap();
}
let e = env(&dir);
let cmp = Comparator::new("reverse", |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(false)
.with_transactional(true)
.with_btree_comparator(cmp);
let db = e.open_database(None, "rev", &cfg).unwrap();
let keys = cursor_keys(&db);
assert_eq!(
keys,
vec![b"c".to_vec(), b"b".to_vec(), b"a".to_vec()],
"reopened tree must keep its comparator order"
);
}
#[test]
fn headline2_reopen_with_wrong_identity_fails() {
let dir = TempDir::new().unwrap();
{
let e = env(&dir);
let cmp = Comparator::new("reverse", |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_btree_comparator(cmp);
let db = e.open_database(None, "rev", &cfg).unwrap();
put(&db, b"a", b"1");
drop(db);
e.close().unwrap();
}
let e = env(&dir);
let cmp = Comparator::new("forward", |a: &[u8], b: &[u8]| a.cmp(b));
let cfg = DatabaseConfig::new()
.with_allow_create(false)
.with_transactional(true)
.with_btree_comparator(cmp);
let res = e.open_database(None, "rev", &cfg);
assert!(res.is_err(), "mismatched comparator identity must fail open");
}
#[test]
fn headline2_override_allows_replacing_persisted_comparator() {
let dir = TempDir::new().unwrap();
{
let e = env(&dir);
let cmp = Comparator::new("reverse", |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_btree_comparator(cmp);
let db = e.open_database(None, "rev", &cfg).unwrap();
put(&db, b"a", b"1");
drop(db);
e.close().unwrap();
}
let e = env(&dir);
let cmp = Comparator::new("forward", |a: &[u8], b: &[u8]| a.cmp(b));
let mut cfg = DatabaseConfig::new()
.with_allow_create(false)
.with_transactional(true)
.with_btree_comparator(cmp);
cfg.set_override_btree_comparator(true);
let res = e.open_database(None, "rev", &cfg);
assert!(res.is_ok(), "override must permit replacing the comparator");
}
#[test]
fn headline3_duplicate_comparator_orders_dup_data() {
let dir = TempDir::new().unwrap();
let e = env(&dir);
let dup_cmp = Comparator::new("rev_dup", |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_sorted_duplicates(true)
.with_duplicate_comparator(dup_cmp);
let db = e.open_database(None, "dup", &cfg).unwrap();
for d in [b"a".as_ref(), b"c", b"b", b"e", b"d"] {
db.put(DatabaseEntry::from_bytes(b"k"), DatabaseEntry::from_bytes(d))
.unwrap();
}
let mut cur = db.open_cursor(None).unwrap();
let mut key = DatabaseEntry::new();
let mut data = DatabaseEntry::new();
let mut datas = Vec::new();
let mut s = cur.get(&mut key, &mut data, Get::First, None).unwrap();
while s == OperationStatus::Success {
datas.push(data.data().to_vec());
s = cur.get(&mut key, &mut data, Get::Next, None).unwrap();
}
assert_eq!(
datas,
vec![
b"e".to_vec(),
b"d".to_vec(),
b"c".to_vec(),
b"b".to_vec(),
b"a".to_vec()
],
"duplicate data must follow the dup comparator (descending)"
);
}
#[test]
fn default_duplicate_order_is_ascending_byte_order() {
let dir = TempDir::new().unwrap();
let e = env(&dir);
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_sorted_duplicates(true);
let db = e.open_database(None, "dup_def", &cfg).unwrap();
for d in [b"c".as_ref(), b"a", b"b"] {
db.put(DatabaseEntry::from_bytes(b"k"), DatabaseEntry::from_bytes(d))
.unwrap();
}
let mut cur = db.open_cursor(None).unwrap();
let mut key = DatabaseEntry::new();
let mut data = DatabaseEntry::new();
let mut datas = Vec::new();
let mut s = cur.get(&mut key, &mut data, Get::First, None).unwrap();
while s == OperationStatus::Success {
datas.push(data.data().to_vec());
s = cur.get(&mut key, &mut data, Get::Next, None).unwrap();
}
assert_eq!(datas, vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]);
}
#[test]
fn reopen_sorted_dup_with_dup_comparator_preserves_order() {
let dir = TempDir::new().unwrap();
let dup_id = "rev_dup";
{
let e = env(&dir);
let dup_cmp = Comparator::new(dup_id, |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(true)
.with_transactional(true)
.with_sorted_duplicates(true)
.with_duplicate_comparator(dup_cmp);
let db = e.open_database(None, "dup", &cfg).unwrap();
for d in [b"a".as_ref(), b"c", b"b", b"e", b"d"] {
db.put(
DatabaseEntry::from_bytes(b"k"),
DatabaseEntry::from_bytes(d),
)
.unwrap();
}
drop(db);
e.close().unwrap();
}
let e = env(&dir);
let dup_cmp = Comparator::new(dup_id, |a: &[u8], b: &[u8]| b.cmp(a));
let cfg = DatabaseConfig::new()
.with_allow_create(false)
.with_transactional(true)
.with_sorted_duplicates(true)
.with_duplicate_comparator(dup_cmp);
let db = e.open_database(None, "dup", &cfg).unwrap();
let mut cur = db.open_cursor(None).unwrap();
let mut key = DatabaseEntry::new();
let mut data = DatabaseEntry::new();
let mut datas = Vec::new();
let mut s = cur.get(&mut key, &mut data, Get::First, None).unwrap();
while s == OperationStatus::Success {
datas.push(data.data().to_vec());
s = cur.get(&mut key, &mut data, Get::Next, None).unwrap();
}
assert_eq!(
datas,
vec![
b"e".to_vec(),
b"d".to_vec(),
b"c".to_vec(),
b"b".to_vec(),
b"a".to_vec()
],
"reopened sorted-dup DB must keep dup-comparator order"
);
}
#[test]
fn comparator_equality_is_by_identity() {
let a = Comparator::new("x", |p: &[u8], q: &[u8]| p.cmp(q));
let b = Comparator::new("x", |p: &[u8], q: &[u8]| q.cmp(p));
let c = Comparator::new("y", |p: &[u8], q: &[u8]| p.cmp(q));
assert_eq!(a, b); assert_ne!(a, c); }