use crate::aae::tictac::Tree;
use crate::datastore::noxu::{NoxuDatastore, NoxuDatastoreError};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum NoxuFoldError {
#[error("noxu fold: {0}")]
Datastore(#[from] NoxuDatastoreError),
}
impl Tree {
pub fn build_from_noxu_fold(&mut self, db: &NoxuDatastore) -> Result<(), NoxuFoldError> {
db.fold_primary(|bucket, key, value| {
self.insert(bucket, key, value, 0);
Ok(())
})?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::aae::tictac::TreeShape;
use tempfile::TempDir;
fn shape() -> TreeShape {
TreeShape {
n_time_buckets: 4,
n_segments: 64,
time_window_seconds: 60,
}
}
fn open_ds() -> (TempDir, NoxuDatastore) {
let dir = TempDir::new().expect("tempdir");
let ds = NoxuDatastore::open_in(dir.path()).expect("open");
(dir, ds)
}
#[test]
fn empty_datastore_yields_empty_tree() {
let (_dir, ds) = open_ds();
let mut tree = Tree::new(shape());
tree.build_from_noxu_fold(&ds).expect("fold");
for (_, root) in tree.roots() {
assert_eq!(root, 0, "fold over empty datastore should leave roots zero");
}
}
#[test]
fn fold_inserts_every_primary_record() {
let (_dir, ds) = open_ds();
for i in 0..1000u32 {
let key = format!("k{i:06}");
let val = format!("v{i}");
ds.put_object(b"users", key.as_bytes(), val.as_bytes(), &[])
.expect("put");
}
let mut tree = Tree::new(shape());
tree.build_from_noxu_fold(&ds).expect("fold");
let mut count = 0usize;
for tb in 0..shape().n_time_buckets {
for seg in 0..shape().n_segments {
count += tree
.keys_in_segment(tb, seg)
.expect("keys_in_segment")
.len();
}
}
assert_eq!(count, 1000, "expected 1000 leaves, got {count}");
}
#[test]
fn fold_skips_2i_records() {
let (_dir, ds) = open_ds();
ds.put_object(
b"users",
b"alice",
b"v1",
&[(b"age_int".to_vec(), b"42".to_vec())],
)
.expect("put");
let mut tree = Tree::new(shape());
tree.build_from_noxu_fold(&ds).expect("fold");
let mut count = 0usize;
for tb in 0..shape().n_time_buckets {
for seg in 0..shape().n_segments {
count += tree
.keys_in_segment(tb, seg)
.expect("keys_in_segment")
.len();
}
}
assert_eq!(count, 1, "only the primary record should be folded");
}
#[test]
fn fold_matches_explicit_inserts_bit_for_bit() {
let (_dir, ds) = open_ds();
let pairs: Vec<(Vec<u8>, Vec<u8>)> = (0..256u32)
.map(|i| {
(
format!("k{i:04}").into_bytes(),
format!("v{i}").into_bytes(),
)
})
.collect();
let mut tree_api = Tree::new(shape());
for (k, v) in &pairs {
ds.put_object(b"users", k, v, &[]).expect("put");
tree_api.insert(b"users", k, v, 0);
}
let mut tree_fold = Tree::new(shape());
tree_fold.build_from_noxu_fold(&ds).expect("fold");
assert_eq!(
tree_api.roots(),
tree_fold.roots(),
"api-rebuild and noxu-fold should produce identical roots"
);
for tb in 0..shape().n_time_buckets {
assert_eq!(
tree_api.segments(tb).expect("api segs"),
tree_fold.segments(tb).expect("fold segs"),
"segments differ for tb {tb}"
);
}
}
}