ruvector-temporal-tensor 2.0.6

Temporal tensor compression with tiered quantization for RuVector
Documentation
#![cfg(feature = "persistence")]

use ruvector_temporal_tensor::persistence::{FileBlockIO, FileMetaLog};
use ruvector_temporal_tensor::store::{
    BlockIO, BlockKey, BlockMeta, DType, MetaLog, ReconstructPolicy, Tier,
};
use std::path::PathBuf;

fn test_dir(name: &str) -> PathBuf {
    let dir = std::env::temp_dir().join(format!("ruvector_test_{}", name));
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

fn cleanup(dir: &PathBuf) {
    let _ = std::fs::remove_dir_all(dir);
}

fn make_key(id: u128, idx: u32) -> BlockKey {
    BlockKey {
        tensor_id: id,
        block_index: idx,
    }
}

fn make_meta(key: BlockKey, tier: Tier) -> BlockMeta {
    BlockMeta {
        key,
        dtype: DType::F32,
        tier,
        bits: 8,
        scale: 0.5,
        zero_point: 0,
        created_at: 100,
        last_access_at: 200,
        access_count: 5,
        ema_rate: 0.1,
        window: 0xFF,
        checksum: 0xDEADBEEF,
        reconstruct: ReconstructPolicy::None,
        tier_age: 10,
        lineage_parent: None,
        block_bytes: 64,
    }
}

// -----------------------------------------------------------------------
// FileBlockIO tests
// -----------------------------------------------------------------------

#[test]
fn test_file_block_io_write_read() {
    let dir = test_dir("block_io_write_read");
    let mut bio = FileBlockIO::new(&dir).unwrap();

    let key = make_key(1, 0);
    let data = vec![0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89];
    bio.write_block(Tier::Tier1, key, &data).unwrap();

    let mut dst = vec![0u8; 32];
    let n = bio.read_block(Tier::Tier1, key, &mut dst).unwrap();
    assert_eq!(n, data.len());
    assert_eq!(&dst[..n], &data[..]);

    cleanup(&dir);
}

#[test]
fn test_file_block_io_different_tiers() {
    let dir = test_dir("block_io_tiers");
    let mut bio = FileBlockIO::new(&dir).unwrap();

    let key = make_key(1, 0);
    let data1 = vec![1u8; 16];
    let data2 = vec![2u8; 8];
    let data3 = vec![3u8; 4];

    bio.write_block(Tier::Tier1, key, &data1).unwrap();
    bio.write_block(Tier::Tier2, key, &data2).unwrap();
    bio.write_block(Tier::Tier3, key, &data3).unwrap();

    let mut buf = vec![0u8; 32];

    let n1 = bio.read_block(Tier::Tier1, key, &mut buf).unwrap();
    assert_eq!(&buf[..n1], &data1[..]);

    let n2 = bio.read_block(Tier::Tier2, key, &mut buf).unwrap();
    assert_eq!(&buf[..n2], &data2[..]);

    let n3 = bio.read_block(Tier::Tier3, key, &mut buf).unwrap();
    assert_eq!(&buf[..n3], &data3[..]);

    cleanup(&dir);
}

#[test]
fn test_file_block_io_delete() {
    let dir = test_dir("block_io_delete");
    let mut bio = FileBlockIO::new(&dir).unwrap();

    let key = make_key(1, 0);
    bio.write_block(Tier::Tier1, key, &[1, 2, 3]).unwrap();
    bio.delete_block(Tier::Tier1, key).unwrap();

    let mut buf = vec![0u8; 32];
    let result = bio.read_block(Tier::Tier1, key, &mut buf);
    assert!(result.is_err() || result.unwrap() == 0);

    cleanup(&dir);
}

#[test]
fn test_file_block_io_overwrite() {
    let dir = test_dir("block_io_overwrite");
    let mut bio = FileBlockIO::new(&dir).unwrap();

    let key = make_key(1, 0);
    bio.write_block(Tier::Tier1, key, &[1, 2, 3]).unwrap();
    bio.write_block(Tier::Tier1, key, &[4, 5, 6, 7]).unwrap();

    let mut buf = vec![0u8; 32];
    let n = bio.read_block(Tier::Tier1, key, &mut buf).unwrap();
    assert_eq!(&buf[..n], &[4, 5, 6, 7]);

    cleanup(&dir);
}

#[test]
fn test_file_block_io_missing_key() {
    let dir = test_dir("block_io_missing");
    let bio = FileBlockIO::new(&dir).unwrap();

    let mut buf = vec![0u8; 32];
    let result = bio.read_block(Tier::Tier1, make_key(99, 0), &mut buf);
    assert!(result.is_err() || result.unwrap() == 0);

    cleanup(&dir);
}

// -----------------------------------------------------------------------
// FileMetaLog tests
// -----------------------------------------------------------------------

#[test]
fn test_file_meta_log_append_get() {
    let dir = test_dir("meta_log_append");
    let mut log = FileMetaLog::new(&dir).unwrap();

    let key = make_key(1, 0);
    let meta = make_meta(key, Tier::Tier1);
    log.append(&meta).unwrap();

    let retrieved = log.get(key).unwrap();
    assert_eq!(retrieved.key, key);
    assert_eq!(retrieved.tier, Tier::Tier1);
    assert_eq!(retrieved.bits, 8);
    assert!((retrieved.scale - 0.5).abs() < 1e-6);
    assert_eq!(retrieved.checksum, 0xDEADBEEF);

    cleanup(&dir);
}

#[test]
fn test_file_meta_log_upsert() {
    let dir = test_dir("meta_log_upsert");
    let mut log = FileMetaLog::new(&dir).unwrap();

    let key = make_key(1, 0);
    let meta1 = make_meta(key, Tier::Tier1);
    log.append(&meta1).unwrap();

    let mut meta2 = make_meta(key, Tier::Tier2);
    meta2.bits = 7;
    log.append(&meta2).unwrap();

    let retrieved = log.get(key).unwrap();
    assert_eq!(retrieved.tier, Tier::Tier2);
    assert_eq!(retrieved.bits, 7);

    cleanup(&dir);
}

#[test]
fn test_file_meta_log_iter() {
    let dir = test_dir("meta_log_iter");
    let mut log = FileMetaLog::new(&dir).unwrap();

    for i in 0..5u128 {
        let key = make_key(i, 0);
        log.append(&make_meta(key, Tier::Tier1)).unwrap();
    }

    let count = log.iter().count();
    assert_eq!(count, 5);

    cleanup(&dir);
}

#[test]
fn test_file_meta_log_missing_key() {
    let dir = test_dir("meta_log_missing");
    let log = FileMetaLog::new(&dir).unwrap();
    assert!(log.get(make_key(99, 0)).is_none());

    cleanup(&dir);
}

#[test]
fn test_file_meta_log_multiple_blocks_same_tensor() {
    let dir = test_dir("meta_log_multi_block");
    let mut log = FileMetaLog::new(&dir).unwrap();

    for idx in 0..3u32 {
        let key = make_key(1, idx);
        log.append(&make_meta(key, Tier::Tier1)).unwrap();
    }

    assert!(log.get(make_key(1, 0)).is_some());
    assert!(log.get(make_key(1, 1)).is_some());
    assert!(log.get(make_key(1, 2)).is_some());
    assert!(log.get(make_key(1, 3)).is_none());

    cleanup(&dir);
}