use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::bloom::ExistenceFilter;
use crate::btree::BtreeIndex;
use crate::error::Result;
use crate::hash::murmur3_x64_128_h1;
use crate::salt::Salt;
use crate::seg::{Getter, OpenOptions, Seg};
pub struct KvReader {
seg: Seg,
index: Option<BtreeIndex>,
bloom: Option<ExistenceFilter>,
salt: Option<u32>,
name: String,
}
impl KvReader {
pub fn open(kv_path: impl AsRef<Path>) -> Result<KvReader> {
KvReader::open_with(kv_path, OpenOptions::default())
}
pub fn open_with(kv_path: impl AsRef<Path>, opts: OpenOptions) -> Result<KvReader> {
let kv_path = kv_path.as_ref();
let seg = Seg::open_with(kv_path, opts)?;
let bt_path = kv_path.with_extension("bt");
let index = if bt_path.exists() {
Some(BtreeIndex::open(&bt_path)?)
} else {
None
};
let kvei_path = kv_path.with_extension("kvei");
let bloom = if kvei_path.exists() {
Some(ExistenceFilter::open(&kvei_path)?)
} else {
None
};
let name = kv_path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
Ok(KvReader {
seg,
index,
bloom,
salt: None,
name,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn bloom_active(&self) -> bool {
self.salt.is_some()
}
pub fn seg(&self) -> &Seg {
&self.seg
}
pub fn index(&self) -> Option<&BtreeIndex> {
self.index.as_ref()
}
pub fn existence_filter(&self) -> Option<&ExistenceFilter> {
self.bloom.as_ref()
}
pub fn salt(&self) -> Option<u32> {
self.salt
}
pub fn key_count(&self) -> u64 {
match &self.index {
Some(idx) => idx.key_count(),
None => self.seg.words_count() / 2,
}
}
pub fn enable_bloom(&mut self, salt: Salt) -> bool {
let Some(bloom) = &self.bloom else {
return false;
};
if !bloom.is_accelerating() {
return false;
}
let resolved = match salt {
Salt::None => return false,
Salt::Known(s) => s,
Salt::Find(threads) => match self.find_salt(threads) {
Some(s) => s,
None => return false,
},
};
let samples = self.sample_keys(64);
if samples.is_empty() {
return false;
}
let ok = samples
.iter()
.all(|k| bloom.contains_hash(murmur3_x64_128_h1(k, resolved)));
if ok {
self.salt = Some(resolved);
}
ok
}
pub fn find_salt(&self, threads: usize) -> Option<u32> {
let bloom = self.bloom.as_ref()?;
if !bloom.is_accelerating() {
return None;
}
let samples = self.sample_keys(16);
if samples.is_empty() {
return None;
}
let threads = threads.clamp(1, 256) as u32;
let found = AtomicU64::new(u64::MAX);
std::thread::scope(|sc| {
for t in 0..threads {
let (found, bloom, samples) = (&found, bloom, &samples);
sc.spawn(move || {
let mut salt = t;
loop {
if found.load(Ordering::Relaxed) != u64::MAX {
return;
}
if samples
.iter()
.all(|k| bloom.contains_hash(murmur3_x64_128_h1(k, salt)))
{
found.fetch_min(salt as u64, Ordering::Relaxed);
return;
}
match salt.checked_add(threads) {
Some(s) => salt = s,
None => return,
}
}
});
}
});
let f = found.load(Ordering::Relaxed);
(f != u64::MAX).then_some(f as u32)
}
pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
if let Some(salt) = self.salt
&& let Some(bloom) = &self.bloom
&& !bloom.contains_hash(murmur3_x64_128_h1(key, salt))
{
return Ok(None);
}
match &self.index {
Some(idx) => Ok(self.get_indexed(idx, key)),
None => Ok(self.get_scan(key)),
}
}
fn get_indexed(&self, idx: &BtreeIndex, key: &[u8]) -> Option<Vec<u8>> {
let n = idx.key_count();
if n == 0 {
return None;
}
let mut g = self.seg.getter();
let (mut lo, mut hi) = (0u64, n);
while lo < hi {
let mid = lo + (hi - lo) / 2;
let off = idx.key_offset(mid)?;
g.reset(off);
if !g.has_next() {
return None;
}
let probe = g.next();
match probe.as_slice().cmp(key) {
std::cmp::Ordering::Less => lo = mid + 1,
std::cmp::Ordering::Greater => hi = mid,
std::cmp::Ordering::Equal => {
return Some(if g.has_next() { g.next() } else { Vec::new() });
}
}
}
None
}
fn get_scan(&self, key: &[u8]) -> Option<Vec<u8>> {
let mut g = self.seg.getter();
while g.has_next() {
let k = g.next();
let v = if g.has_next() { g.next() } else { Vec::new() };
match k.as_slice().cmp(key) {
std::cmp::Ordering::Less => continue,
std::cmp::Ordering::Greater => return None, std::cmp::Ordering::Equal => return Some(v),
}
}
None
}
fn sample_keys(&self, n: usize) -> Vec<Vec<u8>> {
match &self.index {
Some(idx) => {
let count = idx.key_count();
if count == 0 {
return Vec::new();
}
let n = (n as u64).min(count);
let mut g = self.seg.getter();
(0..n)
.filter_map(|s| {
let di = s * count / n;
idx.key_offset(di).map(|off| {
g.reset(off);
g.next()
})
})
.collect()
}
None => {
let mut g = self.seg.getter();
let mut out = Vec::new();
while out.len() < n && g.has_next() {
out.push(g.next());
if g.has_next() {
g.next(); }
}
out
}
}
}
pub fn iter(&self) -> KvIter<'_> {
KvIter {
getter: self.seg.getter(),
}
}
}
pub struct KvIter<'a> {
getter: Getter<'a>,
}
impl Iterator for KvIter<'_> {
type Item = Result<(Vec<u8>, Vec<u8>)>;
fn next(&mut self) -> Option<Self::Item> {
if !self.getter.has_next() {
return None;
}
let key = self.getter.next();
let value = if self.getter.has_next() {
self.getter.next()
} else {
Vec::new()
};
Some(Ok((key, value)))
}
}