use crate::storage_engine::constants::*;
use crate::storage_engine::digest::{Xxh3BuildHasher, compute_hash};
use memmap2::Mmap;
use simd_r_drive_entry_handle::EntryMetadata;
use std::collections::hash_map::Entry;
use std::collections::hash_map::Values;
use std::collections::{HashMap, HashSet};
const TAG_BITS: u64 = 16;
const OFFSET_MASK: u64 = (1u64 << (64 - TAG_BITS)) - 1;
pub struct KeyIndexer {
index: HashMap<u64, u64, Xxh3BuildHasher>,
}
impl KeyIndexer {
#[inline]
pub fn tag_from_hash(key_hash: u64) -> u16 {
(key_hash >> (64 - TAG_BITS)) as u16
}
#[inline]
pub fn tag_from_key(key: &[u8]) -> u16 {
Self::tag_from_hash(compute_hash(key))
}
#[inline]
pub fn pack(tag: u16, offset: u64) -> u64 {
debug_assert!(
offset <= OFFSET_MASK,
"offset exceeds 48-bit range (tag would be corrupted)"
);
((tag as u64) << (64 - TAG_BITS)) | offset
}
#[inline]
pub fn unpack(packed: u64) -> (u16, u64) {
let tag = (packed >> (64 - TAG_BITS)) as u16;
let offset = packed & OFFSET_MASK;
(tag, offset)
}
pub fn build(mmap: &Mmap, tail_offset: u64) -> Self {
let mut index = HashMap::with_hasher(Xxh3BuildHasher);
let mut seen = HashSet::with_hasher(Xxh3BuildHasher);
let mut cursor = tail_offset;
while cursor >= METADATA_SIZE as u64 {
let meta_off = cursor as usize - METADATA_SIZE;
let meta_bytes = &mmap[meta_off..meta_off + METADATA_SIZE];
let meta = EntryMetadata::deserialize(meta_bytes);
if seen.contains(&meta.key_hash) {
cursor = meta.prev_offset;
continue;
}
seen.insert(meta.key_hash);
let tag = Self::tag_from_hash(meta.key_hash);
index.insert(meta.key_hash, Self::pack(tag, meta_off as u64));
if meta.prev_offset == 0 {
break;
}
cursor = meta.prev_offset;
}
Self { index }
}
pub fn insert(&mut self, key_hash: u64, new_offset: u64) -> Result<Option<u64>, &'static str> {
let new_tag = Self::tag_from_hash(key_hash);
let new_packed = Self::pack(new_tag, new_offset);
match self.index.entry(key_hash) {
Entry::Occupied(mut entry) => {
let (stored_tag, _) = Self::unpack(*entry.get());
if new_tag != stored_tag {
return Err("Hash collision detected: tag mismatch on write");
}
let previous_packed = entry.insert(new_packed);
Ok(Some(Self::unpack(previous_packed).1))
}
Entry::Vacant(entry) => {
entry.insert(new_packed);
Ok(None)
}
}
}
#[inline]
pub fn get_packed(&self, key_hash: &u64) -> Option<&u64> {
self.index.get(key_hash)
}
#[inline]
pub fn get_offset(&self, key_hash: &u64) -> Option<u64> {
self.index.get(key_hash).map(|&v| Self::unpack(v).1)
}
#[inline]
pub fn remove(&mut self, key_hash: &u64) -> Option<u64> {
self.index.remove(key_hash).map(|v| Self::unpack(v).1)
}
#[inline]
pub fn len(&self) -> usize {
self.index.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.index.is_empty()
}
#[inline]
pub fn values(&self) -> Values<'_, u64, u64> {
self.index.values()
}
}