use std::marker::PhantomData;
use std::ops::Bound;
use crate::Key;
use crate::batch::{Applied, BatchWrite};
use crate::compaction::CompactionIndex;
use crate::config::Config;
use crate::const_tree::{ConstIter, ConstShard, ConstTree};
use crate::disk_loc::DiskLoc;
use crate::durability::{Bitcask, Durability, Fixed};
use crate::error::{DbError, DbResult};
use crate::fixed::config::FixedConfig;
use crate::hook::{NoHook, TypedWriteHook, WriteHook, ZeroHookAdapter};
use crate::key::Location;
pub struct ZeroTree<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable = [u8; V],
H: TypedWriteHook<K, T> = NoHook,
D: Durability = Bitcask,
> {
inner: ConstTree<K, V, ZeroHookAdapter<K, T, H>, D>,
_marker: PhantomData<T>,
}
impl<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
> ZeroTree<K, V, T, NoHook, Bitcask>
{
pub fn open(path: impl AsRef<std::path::Path>, config: Config) -> DbResult<Self> {
const { assert!(size_of::<T>() == V) }
let adapter = ZeroHookAdapter {
inner: NoHook,
_marker: PhantomData,
};
Ok(Self {
inner: ConstTree::open_hooked(path, config, adapter)?,
_marker: PhantomData,
})
}
}
impl<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
H: TypedWriteHook<K, T>,
> ZeroTree<K, V, T, H, Bitcask>
{
pub fn open_hooked(
path: impl AsRef<std::path::Path>,
config: Config,
hook: H,
) -> DbResult<Self> {
const { assert!(size_of::<T>() == V) }
let adapter = ZeroHookAdapter {
inner: hook,
_marker: PhantomData,
};
Ok(Self {
inner: ConstTree::open_hooked(path, config, adapter)?,
_marker: PhantomData,
})
}
pub fn close(self) -> DbResult<()> {
self.inner.close()
}
pub fn flush_buffers(&self) -> DbResult<()> {
self.inner.flush_buffers()
}
pub fn config(&self) -> &Config {
self.inner.config()
}
pub fn compact(&self) -> DbResult<usize> {
self.inner.compact()
}
pub fn sync_hints(&self) -> DbResult<()> {
self.inner.sync_hints()
}
pub fn as_inner(&self) -> &ConstTree<K, V, ZeroHookAdapter<K, T, H>, Bitcask> {
&self.inner
}
}
impl<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
> ZeroTree<K, V, T, NoHook, Fixed>
{
pub fn open(path: impl AsRef<std::path::Path>, config: FixedConfig) -> DbResult<Self> {
const { assert!(size_of::<T>() == V) }
let adapter = ZeroHookAdapter {
inner: NoHook,
_marker: PhantomData,
};
Ok(Self {
inner: ConstTree::open_with_hook(path, config, adapter)?,
_marker: PhantomData,
})
}
}
impl<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
H: TypedWriteHook<K, T>,
> ZeroTree<K, V, T, H, Fixed>
{
pub fn open_with_hook(
path: impl AsRef<std::path::Path>,
config: FixedConfig,
hook: H,
) -> DbResult<Self> {
const { assert!(size_of::<T>() == V) }
let adapter = ZeroHookAdapter {
inner: hook,
_marker: PhantomData,
};
Ok(Self {
inner: ConstTree::open_with_hook(path, config, adapter)?,
_marker: PhantomData,
})
}
pub fn close(self) -> DbResult<()> {
self.inner.close()
}
}
impl<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
H: TypedWriteHook<K, T>,
D: Durability,
> ZeroTree<K, V, T, H, D>
{
pub fn get(&self, key: &K) -> Option<T> {
let bytes = self.inner.get(key)?;
Some(from_value_bytes::<V, T>(&bytes))
}
pub fn get_or_err(&self, key: &K) -> DbResult<T> {
self.get(key).ok_or(DbError::KeyNotFound)
}
pub fn contains(&self, key: &K) -> bool {
self.inner.contains(key)
}
pub fn first(&self) -> Option<(K, T)> {
self.inner
.first()
.map(|(k, v)| (k, from_value_bytes::<V, T>(&v)))
}
pub fn last(&self) -> Option<(K, T)> {
self.inner
.last()
.map(|(k, v)| (k, from_value_bytes::<V, T>(&v)))
}
pub fn put(&self, key: &K, value: &T) -> DbResult<Option<T>> {
let bytes = to_bytes::<V, T>(value);
self.inner
.put(key, &bytes)
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn insert(&self, key: &K, value: &T) -> DbResult<()> {
let bytes = to_bytes::<V, T>(value);
self.inner.insert(key, &bytes)
}
pub fn delete(&self, key: &K) -> DbResult<Option<T>> {
self.inner
.delete(key)
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn cas(&self, key: &K, expected: &T, new_value: &T) -> DbResult<()> {
let exp_bytes = to_bytes::<V, T>(expected);
let new_bytes = to_bytes::<V, T>(new_value);
self.inner.cas(key, &exp_bytes, &new_bytes)
}
pub fn compare_delete(&self, key: &K, expected: &T) -> DbResult<()> {
let exp_bytes = to_bytes::<V, T>(expected);
self.inner.compare_delete(key, &exp_bytes)
}
pub fn update(&self, key: &K, f: impl FnOnce(&T) -> T) -> DbResult<Option<T>> {
self.inner
.update(key, |bytes| {
let val = from_value_bytes::<V, T>(bytes);
let new_val = f(&val);
to_bytes::<V, T>(&new_val)
})
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn fetch_update(&self, key: &K, f: impl FnOnce(&T) -> T) -> DbResult<Option<T>> {
self.inner
.fetch_update(key, |bytes| {
let val = from_value_bytes::<V, T>(bytes);
let new_val = f(&val);
to_bytes::<V, T>(&new_val)
})
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn atomic<R>(
&self,
shard_key: &K,
f: impl FnOnce(&mut ZeroShard<'_, K, V, T, D, ZeroHookAdapter<K, T, H>>) -> DbResult<R>,
) -> DbResult<R> {
self.inner.atomic(shard_key, |const_shard| {
let shard = unsafe {
&mut *(const_shard as *mut ConstShard<'_, K, V, ZeroHookAdapter<K, T, H>, D>
as *mut ZeroShard<'_, K, V, T, D, ZeroHookAdapter<K, T, H>>)
};
f(shard)
})
}
pub fn prefix_iter(&self, prefix: &[u8]) -> ZeroIter<'_, K, V, T, D::Loc> {
ZeroIter {
inner: self.inner.prefix_iter(prefix),
_marker: PhantomData,
}
}
pub fn iter(&self) -> ZeroIter<'_, K, V, T, D::Loc> {
ZeroIter {
inner: self.inner.iter(),
_marker: PhantomData,
}
}
pub fn range(&self, start: &K, end: &K) -> ZeroIter<'_, K, V, T, D::Loc> {
self.range_bounds(Bound::Included(start), Bound::Excluded(end))
}
pub fn range_bounds(&self, start: Bound<&K>, end: Bound<&K>) -> ZeroIter<'_, K, V, T, D::Loc> {
ZeroIter {
inner: self.inner.range_bounds(start, end),
_marker: PhantomData,
}
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn shard_for(&self, key: &K) -> usize {
self.inner.shard_for(key)
}
pub fn flush(&self) -> DbResult<()> {
self.inner.flush()
}
pub fn get_many(&self, keys: &[K]) -> Vec<Option<T>> {
self.inner
.get_many(keys)
.into_iter()
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
.collect()
}
pub fn update_many<P>(
&self,
items: Vec<(K, P)>,
f: impl Fn(&K, Option<&T>, &P) -> BatchWrite<T>,
) -> DbResult<Vec<(K, Applied<T>)>> {
let inner_out = self.inner.update_many(items, |k, old_bytes, p| {
let old_t: Option<T> = old_bytes.map(|b| from_value_bytes::<V, T>(b));
match f(k, old_t.as_ref(), p) {
BatchWrite::Set(v) => BatchWrite::Set(to_bytes::<V, T>(&v)),
BatchWrite::Keep => BatchWrite::Keep,
BatchWrite::Delete => BatchWrite::Delete,
}
})?;
Ok(inner_out
.into_iter()
.map(|(k, applied)| {
let applied_t = match applied {
Applied::Written { old, new } => Applied::Written {
old: old.map(|b| from_value_bytes::<V, T>(&b)),
new: from_value_bytes::<V, T>(&new),
},
Applied::Kept => Applied::Kept,
Applied::Deleted(b) => Applied::Deleted(from_value_bytes::<V, T>(&b)),
};
(k, applied_t)
})
.collect())
}
pub fn migrate(&self, f: impl Fn(&K, &T) -> crate::MigrateAction<T>) -> DbResult<usize> {
self.inner.migrate(|key, bytes| {
let val: T = from_value_bytes(bytes);
match f(key, &val) {
crate::MigrateAction::Keep => crate::MigrateAction::Keep,
crate::MigrateAction::Update(new) => {
crate::MigrateAction::Update(to_bytes::<V, T>(&new))
}
crate::MigrateAction::Delete => crate::MigrateAction::Delete,
}
})
}
pub(crate) fn replay_init(&self) {
self.inner.replay_init();
}
}
impl<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable + Send + Sync,
H: TypedWriteHook<K, T>,
> CompactionIndex<K> for ZeroTree<K, V, T, H, Bitcask>
{
fn update_if_match(
&self,
key: &K,
old_loc: crate::disk_loc::DiskLoc,
new_loc: crate::disk_loc::DiskLoc,
) -> bool {
self.inner.update_if_match(key, old_loc, new_loc)
}
fn contains_key(&self, key: &K) -> bool {
self.contains(key)
}
fn is_live(&self, shard_id: u8, key: &K, loc: crate::disk_loc::DiskLoc) -> bool {
self.inner.is_live(shard_id, key, loc)
}
}
pub struct ZeroIter<'a, K: Key, const V: usize, T = [u8; V], L: Location = DiskLoc> {
inner: ConstIter<'a, K, V, L>,
_marker: PhantomData<T>,
}
impl<
'a,
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
L: Location,
> Iterator for ZeroIter<'a, K, V, T, L>
{
type Item = (K, T);
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|(k, v)| (k, from_value_bytes::<V, T>(&v)))
}
}
impl<
'a,
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
L: Location,
> DoubleEndedIterator for ZeroIter<'a, K, V, T, L>
{
fn next_back(&mut self) -> Option<Self::Item> {
self.inner
.next_back()
.map(|(k, v)| (k, from_value_bytes::<V, T>(&v)))
}
}
#[repr(transparent)]
pub struct ZeroShard<
'a,
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable = [u8; V],
D: Durability = Bitcask,
WH: WriteHook<K> = NoHook,
> {
inner: ConstShard<'a, K, V, WH, D>,
_marker: PhantomData<T>,
}
impl<
K: Key,
const V: usize,
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
D: Durability,
WH: WriteHook<K>,
> ZeroShard<'_, K, V, T, D, WH>
{
pub fn put(&mut self, key: &K, value: &T) -> DbResult<Option<T>> {
let bytes = to_bytes::<V, T>(value);
self.inner
.put(key, &bytes)
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn insert(&mut self, key: &K, value: &T) -> DbResult<()> {
let bytes = to_bytes::<V, T>(value);
self.inner.insert(key, &bytes)
}
pub fn delete(&mut self, key: &K) -> DbResult<Option<T>> {
self.inner
.delete(key)
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn get(&self, key: &K) -> Option<T> {
let bytes = self.inner.get(key)?;
Some(from_value_bytes::<V, T>(&bytes))
}
pub fn get_or_err(&self, key: &K) -> DbResult<T> {
self.get(key).ok_or(DbError::KeyNotFound)
}
pub fn contains(&self, key: &K) -> bool {
self.inner.contains(key)
}
pub fn update(&mut self, key: &K, f: impl FnOnce(&T) -> T) -> DbResult<Option<T>> {
self.inner
.update(key, |bytes| {
let val = from_value_bytes::<V, T>(bytes);
to_bytes::<V, T>(&f(&val))
})
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn fetch_update(&mut self, key: &K, f: impl FnOnce(&T) -> T) -> DbResult<Option<T>> {
self.inner
.fetch_update(key, |bytes| {
let val = from_value_bytes::<V, T>(bytes);
to_bytes::<V, T>(&f(&val))
})
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
}
#[inline(always)]
pub(crate) fn to_bytes<const V: usize, T: Copy + zerocopy::IntoBytes + zerocopy::Immutable>(
value: &T,
) -> [u8; V] {
debug_assert_eq!(size_of::<T>(), V);
let mut buf = [0u8; V];
buf.copy_from_slice(zerocopy::IntoBytes::as_bytes(value));
buf
}
#[inline(always)]
pub(crate) fn from_value_bytes<const V: usize, T: Copy + zerocopy::FromBytes>(
bytes: &[u8; V],
) -> T {
debug_assert_eq!(V, size_of::<T>());
zerocopy::FromBytes::read_from_bytes(bytes.as_slice())
.expect("size validated by const V == size_of::<T>()")
}
#[cfg(feature = "armour")]
impl<T, const V: usize, H> crate::armour::collection::Collection
for ZeroTree<T::SelfId, V, T, H, Bitcask>
where
T: crate::CollectionMeta
+ Copy
+ zerocopy::IntoBytes
+ zerocopy::FromBytes
+ zerocopy::Immutable
+ Send
+ Sync,
H: crate::hook::TypedWriteHook<T::SelfId, T>,
T::SelfId: crate::Key + Ord,
{
fn name(&self) -> &str {
T::NAME
}
fn len(&self) -> usize {
self.len()
}
fn compact(&self) -> crate::DbResult<usize> {
self.compact()
}
fn flush(&self) -> crate::DbResult<()> {
self.flush_buffers()?;
self.sync_hints()?;
Ok(())
}
fn periodic_flush(&self) -> crate::DbResult<()> {
self.flush_buffers()
}
}
#[cfg(feature = "armour")]
impl<T, const V: usize, H> crate::armour::collection::Collection
for ZeroTree<T::SelfId, V, T, H, Fixed>
where
T: crate::CollectionMeta
+ Copy
+ zerocopy::IntoBytes
+ zerocopy::FromBytes
+ zerocopy::Immutable
+ Send
+ Sync,
H: crate::hook::TypedWriteHook<T::SelfId, T>,
T::SelfId: crate::Key + Ord,
{
fn name(&self) -> &str {
T::NAME
}
fn len(&self) -> usize {
self.len()
}
fn compact(&self) -> crate::DbResult<usize> {
Ok(0)
}
fn flush(&self) -> crate::DbResult<()> {
self.flush()
}
fn periodic_flush(&self) -> crate::DbResult<()> {
self.flush()
}
}
#[cfg(feature = "armour")]
pub struct ZeroTx<'a, K: Key, const V: usize, T, H, D: Durability>
where
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
H: TypedWriteHook<K, T>,
{
inner: crate::const_tree::ConstTx<'a, K, V, ZeroHookAdapter<K, T, H>, D>,
}
#[cfg(feature = "armour")]
impl<K: Key, const V: usize, T, H, D: Durability> ZeroTx<'_, K, V, T, H, D>
where
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
H: TypedWriteHook<K, T>,
{
pub fn try_get(&self, key: &K) -> DbResult<Option<T>> {
Ok(self
.inner
.try_get(key)?
.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn try_contains(&self, key: &K) -> DbResult<bool> {
self.inner.try_contains(key)
}
pub fn get_or_err(&self, key: &K) -> DbResult<T> {
self.try_get(key)?.ok_or(DbError::KeyNotFound)
}
pub fn put(&mut self, key: &K, value: &T) -> DbResult<Option<T>> {
let bytes = to_bytes::<V, T>(value);
self.inner
.put(key, &bytes)
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
pub fn insert(&mut self, key: &K, value: &T) -> DbResult<()> {
let bytes = to_bytes::<V, T>(value);
self.inner.insert(key, &bytes)
}
pub fn delete(&mut self, key: &K) -> DbResult<Option<T>> {
self.inner
.delete(key)
.map(|opt| opt.map(|b| from_value_bytes::<V, T>(&b)))
}
}
#[cfg(feature = "armour")]
impl<K: Key, const V: usize, T, H, D: Durability> crate::armour::multi_tx::MultiTx
for ZeroTree<K, V, T, H, D>
where
T: Copy + zerocopy::IntoBytes + zerocopy::FromBytes + zerocopy::Immutable,
H: TypedWriteHook<K, T>,
{
type Key = K;
type Tx<'a>
= ZeroTx<'a, K, V, T, H, D>
where
Self: 'a;
fn shard_for_key(&self, key: &K) -> usize {
self.inner.shard_for(key)
}
fn begin_tx(&self) -> ZeroTx<'_, K, V, T, H, D> {
ZeroTx {
inner: crate::armour::multi_tx::MultiTx::begin_tx(&self.inner),
}
}
fn lock_shard_into<'a>(&'a self, shard_id: usize, tx: &mut ZeroTx<'a, K, V, T, H, D>) {
crate::armour::multi_tx::MultiTx::lock_shard_into(&self.inner, shard_id, &mut tx.inner)
}
fn release_locks(
&self,
tx: &mut ZeroTx<'_, K, V, T, H, D>,
) -> crate::armour::multi_tx::SyncNeeds {
crate::armour::multi_tx::MultiTx::release_locks(&self.inner, &mut tx.inner)
}
fn run_sync(&self, needs: crate::armour::multi_tx::SyncNeeds) -> DbResult<()> {
crate::armour::multi_tx::MultiTx::run_sync(&self.inner, needs)
}
fn replay_hooks(&self, tx: ZeroTx<'_, K, V, T, H, D>) {
crate::armour::multi_tx::MultiTx::replay_hooks(&self.inner, tx.inner)
}
}
#[cfg(test)]
mod tests {
use super::ZeroTree;
use crate::Config;
use crate::hook::TypedWriteHook;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering as AO};
struct ZHook {
writes: Arc<AtomicUsize>,
last_new: Arc<crate::sync::Mutex<Option<u32>>>,
}
impl ZHook {
fn new() -> Self {
Self {
writes: Arc::new(AtomicUsize::new(0)),
last_new: Arc::new(crate::sync::Mutex::new(None)),
}
}
}
impl TypedWriteHook<[u8; 8], u32> for ZHook {
fn on_write(&self, _k: &[u8; 8], _old: Option<&u32>, new: Option<&u32>) {
self.writes.fetch_add(1, AO::Relaxed);
*crate::sync::lock(&self.last_new) = new.copied();
}
}
#[test]
fn zero_tree_atomic_fires_typed_hook() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = Config::test();
cfg.shard_count = 1;
let hook = ZHook::new();
let writes = Arc::clone(&hook.writes);
let last_new = Arc::clone(&hook.last_new);
let tree = ZeroTree::<[u8; 8], 4, u32, _>::open_hooked(dir.path(), cfg, hook).unwrap();
let k = 1u64.to_be_bytes();
tree.atomic(&k, |s| {
s.put(&k, &42u32)?;
Ok(())
})
.unwrap();
assert_eq!(writes.load(AO::Relaxed), 1);
assert_eq!(*crate::sync::lock(&last_new), Some(42));
}
#[test]
fn zero_get_many_and_update_many_typed() {
use crate::{Applied, BatchWrite};
let dir = tempfile::tempdir().unwrap();
let cfg = Config::test();
let tree = ZeroTree::<[u8; 8], 8, u64>::open(dir.path(), cfg).unwrap();
tree.put(&1u64.to_be_bytes(), &100).unwrap();
tree.put(&2u64.to_be_bytes(), &200).unwrap();
let keys: Vec<[u8; 8]> = vec![2u64.to_be_bytes(), 9u64.to_be_bytes(), 1u64.to_be_bytes()];
assert_eq!(tree.get_many(&keys), vec![Some(200u64), None, Some(100u64)]);
let items: Vec<([u8; 8], u64)> = vec![(1u64.to_be_bytes(), 5), (3u64.to_be_bytes(), 42)];
let out = tree
.update_many(items, |_k, cur, d| {
BatchWrite::Set(cur.copied().unwrap_or(0) + *d)
})
.unwrap();
assert_eq!(
out[0].1,
Applied::Written {
old: Some(100u64),
new: 105u64
}
);
assert_eq!(
out[1].1,
Applied::Written {
old: None,
new: 42u64
}
);
assert_eq!(tree.get(&1u64.to_be_bytes()), Some(105));
assert_eq!(tree.get(&3u64.to_be_bytes()), Some(42));
}
#[test]
fn zero_tree_shard_update_and_fetch_update() {
let dir = tempfile::tempdir().unwrap();
let mut cfg = Config::test();
cfg.shard_count = 1;
let tree = ZeroTree::<[u8; 8], 4, u32>::open(dir.path(), cfg).unwrap();
let key = 1u64.to_be_bytes();
tree.put(&key, &100u32).unwrap();
let (upd, fetched, missing) = tree
.atomic(&key, |s| {
let upd = s.update(&key, |old| old + 1)?; let fetched = s.fetch_update(&key, |old| old + 1)?; let missing = s.update(&2u64.to_be_bytes(), |old| *old)?;
Ok((upd, fetched, missing))
})
.expect("atomic");
assert_eq!(upd, Some(101));
assert_eq!(fetched, Some(101));
assert_eq!(missing, None);
assert_eq!(tree.get(&key), Some(102));
}
#[test]
fn zero_tree_fixed_direction() {
use crate::FixedConfig;
use crate::durability::Fixed;
use crate::hook::NoHook;
let dir = tempfile::tempdir().unwrap();
let tree =
ZeroTree::<[u8; 8], 4, u32, NoHook, Fixed>::open(dir.path(), FixedConfig::test())
.unwrap();
for i in 1u32..=3 {
tree.put(&(i as u64).to_be_bytes(), &i).unwrap();
}
let desc: Vec<u64> = tree.iter().map(|(k, _)| u64::from_be_bytes(k)).collect();
assert_eq!(desc, [3, 2, 1], "ZeroTree<Fixed> default DESC");
let dir2 = tempfile::tempdir().unwrap();
let cfg = FixedConfig {
reversed: false,
..FixedConfig::test()
};
let tree2 = ZeroTree::<[u8; 8], 4, u32, NoHook, Fixed>::open(dir2.path(), cfg).unwrap();
for i in 1u32..=3 {
tree2.put(&(i as u64).to_be_bytes(), &i).unwrap();
}
let asc: Vec<u64> = tree2.iter().map(|(k, _)| u64::from_be_bytes(k)).collect();
assert_eq!(asc, [1, 2, 3], "ZeroTree<Fixed> reversed=false ASC");
}
}