use std::{
cmp::min,
fmt::Debug,
hash::RandomState,
io::Read,
ptr,
sync::Arc,
};
use crate::bloom::{
Bloom2,
BloomFilterBuilder,
BytesBitmap,
CompressedBitmap,
FilterSize::KeyBytes3,
};
use bytes::{
BufMut,
Bytes,
BytesMut,
};
use gxhash::{
GxBuildHasher,
GxHasher,
gxhash64,
};
use tracing::{
instrument,
trace,
};
use crate::{
hash::SeedableHasher,
utils::{
Deserializer,
Serializer,
},
};
const INDEX_HEADER_FIELDS: usize = 6;
const INDEX_HEADER_SIZE: usize = INDEX_HEADER_FIELDS * size_of::<u64>();
const OFFSET_ENTRY_SIZE: usize = 2 * size_of::<u64>();
pub(crate) const MIN_INDEX_SIZE: usize = 56;
const BLOOM_OVERRIDE: usize = 10300768;
pub struct Index {
id: u64,
bloom_filter_seed: i64,
num_blocks: u64,
ns_offset_entries: Vec<(u64, u64)>,
bloom_filter: Bloom2<SeedableHasher, BytesBitmap, u64>,
block_offset_entries: Vec<(u64, u64)>, }
impl Index {
#[instrument(level = "trace")]
pub fn new(id: u64, seed: i64) -> Self {
let hasher = SeedableHasher::new(seed);
Self {
id,
bloom_filter_seed: seed,
num_blocks: 0,
ns_offset_entries: Vec::new(),
bloom_filter: BloomFilterBuilder::hasher(hasher)
.with_bitmap()
.size(KeyBytes3)
.build(),
block_offset_entries: Vec::new(),
}
}
pub fn num_blocks(&self) -> u64 {
self.num_blocks
}
pub(crate) fn set_num_blocks(&mut self, count: u64) {
self.num_blocks = count;
}
#[instrument(level = "trace")]
pub fn insert_item(&mut self, key: &[u8]) {
let hash = gxhash64(key, self.bloom_filter_seed);
self.bloom_filter.insert(&hash);
match self
.block_offset_entries
.binary_search_by_key(&hash, |(h, _)| *h)
{
| Ok(_) => {},
| Err(idx) => self
.block_offset_entries
.insert(idx, (hash, self.num_blocks)),
}
}
pub fn rebuild_bloom_from_keys<'a, I>(&mut self, key_block_pairs: I)
where
I: Iterator<Item = (&'a [u8], u64)>, {
let hasher = SeedableHasher::new(self.bloom_filter_seed);
let mut new_bloom = BloomFilterBuilder::hasher(hasher)
.with_bitmap()
.size(KeyBytes3)
.build();
let mut hashes = Vec::with_capacity(1000);
for (key, block_idx) in key_block_pairs {
let hash = gxhash64(key, self.bloom_filter_seed);
new_bloom.insert(&hash);
hashes.push((hash, block_idx));
}
hashes.sort_unstable_by_key(|(h, _)| *h);
hashes.dedup_by_key(|(h, _)| *h);
self.block_offset_entries = hashes;
self.bloom_filter = new_bloom;
}
pub fn inc_block_count(&mut self, i: u64) {
self.num_blocks += i;
}
#[instrument(level = "trace")]
pub fn insert_ns_offset(&mut self, ns: u64) {
match self
.ns_offset_entries
.binary_search_by_key(&ns, |(h, _)| *h)
{
| Ok(_) => {},
| Err(idx) => self.ns_offset_entries.insert(idx, (ns, self.num_blocks)),
}
}
pub fn may_contain(&self, key: &[u8]) -> bool {
let hash = gxhash64(key, self.bloom_filter_seed);
self.bloom_filter.contains(&hash)
}
#[instrument(level = "trace")]
pub fn get_namespace_block(&self, ns: u64) -> Option<u64> {
self.ns_offset_entries
.binary_search_by_key(&ns, |(n, b)| *n)
.ok()
.map(|idx| self.ns_offset_entries[idx].1)
}
#[instrument(level = "trace")]
pub fn get_block(&self, key: &[u8]) -> Option<u64> {
let hash = gxhash64(key, self.bloom_filter_seed);
self.block_offset_entries
.binary_search_by_key(&hash, |(h, b)| *h)
.ok()
.map(|idx| self.block_offset_entries[idx].1)
}
pub fn id(&self) -> u64 {
self.id
}
pub fn block_count(&self) -> u64 {
self.num_blocks
}
pub fn ns_offset_count(&self) -> u64 {
self.ns_offset_entries.len() as u64
}
#[instrument(level = "trace")]
pub fn size(&self) -> usize {
let header_size = INDEX_HEADER_SIZE;
let bloom_size = self.bloom_filter.bitmap().clone().freeze().len();
let block_offset_size = self.block_offset_entries.len() * OFFSET_ENTRY_SIZE;
let ns_offset_size = self.ns_offset_entries.len() * OFFSET_ENTRY_SIZE;
header_size + bloom_size + block_offset_size + ns_offset_size
}
fn block_entries_len(&self) -> usize {
self.block_offset_entries.len()
}
#[instrument(level = "trace", skip(dst))]
pub(crate) unsafe fn finalize(&self, dst: *mut u8) {
debug_assert!(!dst.is_null(), "Destination pointer must not be null");
debug_assert!(
dst as usize % std::mem::align_of::<u64>() == 0,
"Destination pointer must be 8-byte aligned for u64 writes"
);
let mut offset = 0;
let mut block_offset_entries =
BytesMut::with_capacity(self.block_offset_entries.len() * 16);
self.block_offset_entries.iter().for_each(|(h, b)| {
block_offset_entries.put_u64_le(*h);
block_offset_entries.put_u64_le(*b);
});
let block_offset_entries = block_offset_entries.freeze();
let mut ns_offset_entries = BytesMut::with_capacity(self.ns_offset_entries.len() * 16);
self.ns_offset_entries.iter().for_each(|(n, b)| {
ns_offset_entries.put_u64_le(*n);
ns_offset_entries.put_u64_le(*b);
});
let ns_offset_entries = ns_offset_entries.freeze();
let bloom_data = self.bloom_filter.bitmap().clone().freeze();
let bloom_filter_size = bloom_data.len() as u64;
unsafe {
ptr::copy_nonoverlapping(
self.id.to_le_bytes().as_ptr(),
dst.add(offset),
size_of::<u64>(),
);
offset += size_of::<u64>();
ptr::copy_nonoverlapping(
self.bloom_filter_seed.to_le_bytes().as_ptr(),
dst.add(offset),
size_of::<i64>(),
);
offset += size_of::<i64>();
ptr::copy_nonoverlapping(
bloom_filter_size.to_le_bytes().as_ptr(),
dst.add(offset),
size_of::<u64>(),
);
offset += size_of::<u64>();
ptr::copy_nonoverlapping(
ns_offset_entries.len().to_le_bytes().as_ptr(),
dst.add(offset),
size_of::<u64>(),
);
offset += size_of::<u64>();
ptr::copy_nonoverlapping(
block_offset_entries.len().to_le_bytes().as_ptr(),
dst.add(offset),
size_of::<u64>(),
);
offset += size_of::<u64>();
ptr::copy_nonoverlapping(
self.num_blocks.to_le_bytes().as_ptr(),
dst.add(offset),
size_of::<u64>(),
);
offset += size_of::<u64>();
ptr::copy_nonoverlapping(
block_offset_entries.as_ptr(),
dst.add(offset),
block_offset_entries.len(),
);
offset += block_offset_entries.len();
ptr::copy_nonoverlapping(
ns_offset_entries.as_ptr(),
dst.add(offset),
ns_offset_entries.len(),
);
offset += ns_offset_entries.len();
ptr::copy_nonoverlapping(bloom_data.as_ptr(), dst.add(offset), bloom_data.len());
}
}
}
impl From<Index> for Bytes {
#[instrument(level = "trace", skip(value))]
fn from(value: Index) -> Bytes {
let size = value.size();
let mut buffer = BytesMut::with_capacity(size);
buffer.resize(size, 0);
unsafe {
value.finalize(buffer.as_mut_ptr());
}
buffer.freeze()
}
}
impl From<&Index> for Bytes {
#[instrument(level = "trace", skip(value))]
fn from(value: &Index) -> Bytes {
let size = value.size();
let mut buffer = BytesMut::with_capacity(size);
buffer.resize(size, 0);
unsafe {
value.finalize(buffer.as_mut_ptr());
}
buffer.freeze()
}
}
impl From<Bytes> for Index {
#[instrument(level = "trace", skip(value))]
fn from(value: Bytes) -> Self {
debug_assert!(value.len() > 48, "index metadata too small");
let mut offset = 0;
let id = u64::from_le_bytes(value[offset..size_of::<u64>()].try_into().unwrap());
offset += size_of::<u64>();
let bloom_filter_seed =
i64::from_le_bytes(value[offset..offset + size_of::<i64>()].try_into().unwrap());
offset += size_of::<i64>();
let bloom_filter_size =
u64::from_le_bytes(value[offset..offset + size_of::<u64>()].try_into().unwrap());
offset += size_of::<u64>();
let ns_offset_size =
u64::from_le_bytes(value[offset..offset + size_of::<u64>()].try_into().unwrap());
offset += size_of::<u64>();
let block_offset_size =
u64::from_le_bytes(value[offset..offset + size_of::<u64>()].try_into().unwrap());
offset += size_of::<u64>();
let num_blocks =
u64::from_le_bytes(value[offset..offset + size_of::<u64>()].try_into().unwrap());
offset += size_of::<u64>();
let block_offsets = BytesMut::from(&value[offset..offset + block_offset_size as usize]);
offset += block_offsets.len();
let mut block_offset_entries: Vec<(u64, u64)> =
Vec::with_capacity(block_offsets.len() / 16);
block_offsets.chunks_exact(16).for_each(|chunk| {
let hash = u64::from_le_bytes(chunk[0..8].try_into().unwrap());
let block_offset = u64::from_le_bytes(chunk[8..16].try_into().unwrap());
match block_offset_entries.binary_search_by_key(&hash, |(h, _)| *h) {
| Ok(_) => {},
| Err(idx) => block_offset_entries.insert(idx, (hash, block_offset)),
}
});
let ns_offset_entries_bin =
BytesMut::from(&value[offset..offset + ns_offset_size as usize]);
offset += ns_offset_entries_bin.len();
let mut ns_offset_entries: Vec<(u64, u64)> =
Vec::with_capacity(ns_offset_entries_bin.len() / 16);
ns_offset_entries_bin.chunks_exact(16).for_each(|chunk| {
let mut ns_buf = [0u8; 8];
ns_buf.copy_from_slice(chunk[0..8].as_ref());
let ns = u64::from_le_bytes(ns_buf);
let mut block_buf = [0u8; 8];
block_buf.copy_from_slice(chunk[8..16].as_ref());
let block_offset = u64::from_le_bytes(block_buf);
match ns_offset_entries.binary_search_by_key(&ns, |(n, b)| *n) {
| Ok(_) => {},
| Err(idx) => ns_offset_entries.insert(idx, (ns, block_offset)),
}
});
let bloom_filter_data = BytesMut::from(&value[offset..offset + bloom_filter_size as usize]);
let hasher = SeedableHasher::new(bloom_filter_seed);
let bitmap = BytesBitmap::from_bytes(bloom_filter_data.clone());
let bloom_filter = BloomFilterBuilder::hasher(hasher)
.with_bitmap()
.with_bitmap_data(bitmap, KeyBytes3)
.build();
Self {
id,
bloom_filter_seed,
block_offset_entries,
ns_offset_entries,
bloom_filter,
num_blocks,
}
}
}
impl Debug for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Index")
.field("id", &self.id)
.field("bloom_filter_seed", &self.bloom_filter_seed)
.field("num_blocks", &self.num_blocks)
.field("ns_offset_entries", &self.ns_offset_entries)
.field("block_offset_entries", &self.block_offset_entries)
.finish()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use bytes::Bytes;
use super::*;
use crate::keypair::{
DEFAULT_NS,
KeyBytes,
};
fn create_test_key(id: u32) -> Bytes {
KeyBytes::new(
DEFAULT_NS,
Bytes::copy_from_slice(format!("test_key_{id}").as_bytes()),
u128::MAX,
)
.as_bytes()
}
fn create_test_key_ns(id: u32, ns: u64) -> Bytes {
KeyBytes::new(
ns,
Bytes::copy_from_slice(format!("test_key_{id}").as_bytes()),
u128::MAX,
)
.as_bytes()
}
#[test]
fn test_debug() {
let index = Index::new(42, 123);
let debug_str = format!("{:?}", index);
assert!(debug_str.contains("Index"));
assert!(debug_str.contains("id: 42"));
assert!(debug_str.contains("bloom_filter_seed: 123"));
}
#[test]
fn test_new_index() {
let index = Index::new(42, 123);
assert_eq!(index.id(), 42);
assert_eq!(index.block_count(), 0);
assert_eq!(index.ns_offset_count(), 0);
assert_eq!(index.block_entries_len(), 0);
}
#[test]
fn test_add_item() {
let mut index = Index::new(1, 100);
let item1 = create_test_key(1);
let item2 = create_test_key(2);
index.insert_item(&item1);
index.insert_item(&item2);
assert!(index.may_contain(&item1));
assert!(index.may_contain(&item2));
let non_existent = create_test_key(999);
assert!(!index.may_contain(&non_existent));
}
#[test]
fn test_add_block() {
let seed = 100;
let mut index = Index::new(1, seed);
index.inc_block_count(1);
index.inc_block_count(1);
index.inc_block_count(1);
assert_eq!(3, index.block_count());
}
#[test]
fn test_add_ns_offset() {
let seed = 100;
let test_ns = 150;
let mut index = Index::new(1, seed);
index.insert_ns_offset(test_ns);
assert_eq!((test_ns, 0), index.ns_offset_entries[0]);
}
#[test]
fn test_serialization() {
let seed = 123;
let mut index = Index::new(42, seed);
let mut keys = Vec::new();
let mut ns_offsets = 0;
let mut blocks = 0;
for i in 0..1000 {
let key = create_test_key(i);
index.insert_item(&key);
if i % 20 == 0 {
index.insert_ns_offset(i as u64);
ns_offsets += 1;
}
if i % 100 == 0 {
index.inc_block_count(1);
blocks += 1;
}
keys.push(key);
}
let serialized = Bytes::from(index);
assert!(!serialized.is_empty());
assert_eq!(u64::from_le_bytes(serialized[0..8].try_into().unwrap()), 42); assert_eq!(
i64::from_le_bytes(serialized[8..16].try_into().unwrap()),
seed
); assert_ne!(
u64::from_le_bytes(serialized[16..24].try_into().unwrap()),
0
); assert_eq!(
u64::from_le_bytes(serialized[24..32].try_into().unwrap()),
800
); assert_eq!(
u64::from_le_bytes(serialized[32..40].try_into().unwrap()),
16000
); assert_eq!(
u64::from_le_bytes(serialized[40..48].try_into().unwrap()),
10
); }
#[test]
fn test_full_serialization_roundtrip() {
let seed = 123;
let id = 42;
let mut index = Index::new(id, seed);
let mut keys = Vec::new();
let mut ns_offsets = 0;
let mut blocks = 0;
for i in 0..1000 {
let key = create_test_key(i);
index.insert_item(&key);
if i % 20 == 0 {
index.insert_ns_offset(i as u64);
ns_offsets += 1;
}
if i % 100 == 0 {
index.inc_block_count(1);
blocks += 1;
}
keys.push(key);
}
let serialized = Bytes::from(index);
let deserialized = Index::from(serialized);
assert_eq!(id, deserialized.id);
assert_eq!(seed, deserialized.bloom_filter_seed);
assert_eq!(ns_offsets, deserialized.ns_offset_entries.len());
assert_eq!(blocks, deserialized.block_count());
for key in &keys {
assert!(
deserialized.may_contain(key),
"Failed to find key in deserialized bloom filter"
);
}
}
#[test]
fn test_empty_index_serialization() {
let empty_index = Index::new(1, 100);
let serialized = Bytes::from(empty_index);
let deserialized = Index::from(serialized);
assert_eq!(deserialized.id(), 1);
assert_eq!(deserialized.block_count(), 0);
assert_eq!(deserialized.ns_offset_count(), 0);
assert_eq!(deserialized.block_entries_len(), 0);
}
#[test]
fn test_serialization_byte_order() {
let index = Index::new(0x0102030405060708, 0x0102030405060708);
let bytes = Bytes::from(index);
assert_eq!(bytes[0], 0x08);
assert_eq!(bytes[1], 0x07);
assert_eq!(bytes[2], 0x06);
assert_eq!(bytes[3], 0x05);
assert_eq!(bytes[4], 0x04);
assert_eq!(bytes[5], 0x03);
assert_eq!(bytes[6], 0x02);
assert_eq!(bytes[7], 0x01);
assert_eq!(bytes[8], 0x08);
assert_eq!(bytes[9], 0x07);
assert_eq!(bytes[10], 0x06);
assert_eq!(bytes[11], 0x05);
assert_eq!(bytes[12], 0x04);
assert_eq!(bytes[13], 0x03);
assert_eq!(bytes[14], 0x02);
assert_eq!(bytes[15], 0x01);
}
#[test]
fn test_false_positives() {
let mut index = Index::new(1, 100);
for i in 0..1000 {
index.insert_item(&create_test_key(i));
}
let mut false_positives = 0;
for i in 2000..3000 {
if index.may_contain(&create_test_key(i)) {
false_positives += 1;
}
}
assert!(
false_positives < 50,
"Too many false positives: {}",
false_positives
);
}
#[test]
fn test_find_block() {
let seed = 42;
let mut index = Index::new(1, seed);
let key1 = create_test_key(101);
let key2 = create_test_key(202);
let key3 = create_test_key(303);
index.insert_item(&key1);
index.inc_block_count(1);
index.insert_item(&key2);
index.inc_block_count(1);
index.insert_item(&key3);
assert_eq!(index.get_block(&key1), Some(0), "Key1 should be in block 0");
assert_eq!(index.get_block(&key2), Some(1), "Key2 should be in block 1");
assert_eq!(index.get_block(&key3), Some(2), "Key3 should be in block 2");
let nonexistent_key = create_test_key(999);
assert_eq!(
index.get_block(&nonexistent_key),
None,
"Non-existent key should return None"
);
let serialized = Bytes::from(&index);
let deserialized = Index::from(serialized);
assert_eq!(
deserialized.get_block(&key1),
Some(0),
"After serialization, key1 should be in block 0"
);
assert_eq!(
deserialized.get_block(&key2),
Some(1),
"After serialization, key2 should be in block 1"
);
assert_eq!(
deserialized.get_block(&key3),
Some(2),
"After serialization, key3 should be in block 2"
);
assert_eq!(
deserialized.get_block(&nonexistent_key),
None,
"After serialization, non-existent key should return None"
);
}
#[test]
fn test_get_namespace_block_basic() {
let seed = 100;
let mut index = Index::new(1, seed);
let ns1 = 100u64;
let ns2 = 200u64;
let ns3 = 300u64;
index.insert_ns_offset(ns1);
index.inc_block_count(1);
index.insert_ns_offset(ns2);
index.inc_block_count(1);
index.insert_ns_offset(ns3);
index.inc_block_count(1);
assert_eq!(
index.get_namespace_block(ns1),
Some(0),
"Namespace 100 should start at block 0"
);
assert_eq!(
index.get_namespace_block(ns2),
Some(1),
"Namespace 200 should start at block 1"
);
assert_eq!(
index.get_namespace_block(ns3),
Some(2),
"Namespace 300 should start at block 2"
);
}
#[test]
fn test_get_namespace_block_not_found() {
let seed = 100;
let mut index = Index::new(1, seed);
let ns = 100u64;
index.insert_ns_offset(ns);
assert_eq!(
index.get_namespace_block(999),
None,
"Non-existent namespace should return None"
);
}
#[test]
fn test_get_namespace_block_empty_index() {
let seed = 100;
let index = Index::new(1, seed);
assert_eq!(
index.get_namespace_block(100),
None,
"Empty index should return None for any namespace"
);
}
#[test]
fn test_get_namespace_block_searches_by_namespace_not_block() {
let seed = 100;
let mut index = Index::new(1, seed);
index.insert_ns_offset(50);
index.inc_block_count(1);
index.insert_ns_offset(150);
index.inc_block_count(1);
index.insert_ns_offset(300);
assert_eq!(
index.get_namespace_block(50),
Some(0),
"Should find namespace 50 at block 0"
);
assert_eq!(
index.get_namespace_block(150),
Some(1),
"Should find namespace 150 at block 1"
);
assert_eq!(
index.get_namespace_block(300),
Some(2),
"Should find namespace 300 at block 2"
);
assert_eq!(
index.get_namespace_block(1),
None,
"Block offset 0 should not be found as a namespace"
);
assert_eq!(
index.get_namespace_block(2),
None,
"Block offset 1 should not be found as a namespace"
);
assert_eq!(
index.get_namespace_block(3),
None,
"Block offset 2 should not be found as a namespace"
);
assert_eq!(
index.get_namespace_block(49),
None,
"Namespace 49 should not exist"
);
assert_eq!(
index.get_namespace_block(51),
None,
"Namespace 51 should not exist"
);
}
#[test]
fn test_get_namespace_block_returns_from_correct_array() {
let seed = 100;
let mut index = Index::new(1, seed);
let ns1 = 1000u64;
let ns2 = 2000u64;
index.insert_ns_offset(ns1);
index.inc_block_count(1);
index.insert_ns_offset(ns2);
index.inc_block_count(1);
for i in 0..50 {
let key = create_test_key(i);
index.insert_item(&key);
}
let result1 = index.get_namespace_block(ns1);
let result2 = index.get_namespace_block(ns2);
assert_eq!(
result1,
Some(0),
"Namespace 1000 should map to block 0 from ns_offset_entries"
);
assert_eq!(
result2,
Some(1),
"Namespace 2000 should map to block 1 from ns_offset_entries"
);
assert_eq!(
index.ns_offset_entries.len(),
2,
"Should have exactly 2 namespace entries"
);
assert!(
index.block_offset_entries.len() >= 50,
"Should have at least 50 block offset entries, demonstrating they're distinct arrays"
);
}
#[test]
fn test_get_namespace_block_multiple_namespaces() {
let seed = 100;
let mut index = Index::new(1, seed);
let namespaces = vec![10u64, 20, 30, 40, 50, 60, 70, 80, 90, 100];
for (i, ns) in namespaces.iter().enumerate() {
index.insert_ns_offset(*ns);
index.inc_block_count(1);
}
for (i, ns) in namespaces.iter().enumerate() {
let expected_block = i as u64;
assert_eq!(
index.get_namespace_block(*ns),
Some(expected_block),
"Namespace {} should map to block {}",
ns,
expected_block
);
}
assert_eq!(index.get_namespace_block(5), None);
assert_eq!(index.get_namespace_block(15), None);
assert_eq!(index.get_namespace_block(105), None);
}
#[test]
fn test_get_namespace_block_boundary_values() {
let seed = 100;
let mut index = Index::new(1, seed);
let ns_min = 0u64;
let ns_max = u64::MAX;
let ns_mid = u64::MAX / 2;
index.insert_ns_offset(ns_min);
index.inc_block_count(1);
index.insert_ns_offset(ns_mid);
index.inc_block_count(1);
index.insert_ns_offset(ns_max);
index.inc_block_count(1);
assert_eq!(
index.get_namespace_block(ns_min),
Some(0),
"Should handle minimum u64 value"
);
assert_eq!(
index.get_namespace_block(ns_mid),
Some(1),
"Should handle mid-range u64 value"
);
assert_eq!(
index.get_namespace_block(ns_max),
Some(2),
"Should handle maximum u64 value"
);
}
#[test]
fn test_get_namespace_block_after_serialization() {
let seed = 100;
let mut index = Index::new(1, seed);
let ns1 = 1234u64;
let ns2 = 5678u64;
let ns3 = 9999u64;
index.insert_ns_offset(ns1);
index.inc_block_count(1);
index.insert_ns_offset(ns2);
index.inc_block_count(1);
index.insert_ns_offset(ns3);
index.inc_block_count(1);
let serialized = Bytes::from(index);
let deserialized = Index::from(serialized);
assert_eq!(
deserialized.get_namespace_block(ns1),
Some(0),
"After deserialization, namespace 1234 should map to block 0"
);
assert_eq!(
deserialized.get_namespace_block(ns2),
Some(1),
"After deserialization, namespace 5678 should map to block 1"
);
assert_eq!(
deserialized.get_namespace_block(ns3),
Some(2),
"After deserialization, namespace 9999 should map to block 2"
);
assert_eq!(
deserialized.get_namespace_block(4321),
None,
"After deserialization, non-existent namespace should return None"
);
}
#[test]
fn test_get_namespace_block_with_mixed_operations() {
let seed = 100;
let mut index = Index::new(1, seed);
let ns1 = 100u64;
let ns2 = 200u64;
index.insert_ns_offset(ns1);
index.inc_block_count(1);
for i in 0..10 {
let key = create_test_key_ns(i, ns1);
index.insert_item(&key);
}
index.insert_ns_offset(ns2);
index.inc_block_count(1);
for i in 10..20 {
let key = create_test_key_ns(i, ns2);
index.insert_item(&key);
}
assert_eq!(
index.get_namespace_block(ns1),
Some(0),
"Namespace 100 should be at block 0"
);
assert_eq!(
index.get_namespace_block(ns2),
Some(1),
"Namespace 200 should be at block 1"
);
assert!(
index.ns_offset_entries.len() >= 2,
"Should have at least 2 namespace entries"
);
assert!(
index.block_offset_entries.len() >= 20,
"Should have at least 20 block offset entries"
);
}
}