use std::path::Path;
use std::sync::Arc;
use memmap2::Mmap;
use nodedb_array::segment::{HilbertPackedRTree, SegmentReader};
use nodedb_wal::crypto::WalEncryptionKey;
#[derive(Debug, thiserror::Error)]
pub enum SegmentHandleError {
#[error("mmap segment failed: {detail}")]
Mmap { detail: String },
#[error("segment open: {detail}")]
Open { detail: String },
#[error("segment schema_hash mismatch: array={array:x} segment={seg:x}")]
SchemaHashMismatch { array: u64, seg: u64 },
}
enum Backing {
Mmap(Arc<Mmap>),
Decrypted(Arc<Vec<u8>>),
}
impl Backing {
fn bytes(&self) -> &[u8] {
match self {
Backing::Mmap(m) => m,
Backing::Decrypted(v) => v,
}
}
}
#[derive(Clone)]
pub struct SegmentHandle {
backing: Arc<Backing>,
rtree: Arc<HilbertPackedRTree>,
schema_hash: u64,
tile_count: usize,
id: String,
}
impl SegmentHandle {
pub fn open(
path: &Path,
id: String,
expected_schema_hash: u64,
kek: Option<&WalEncryptionKey>,
) -> Result<Self, SegmentHandleError> {
let backing: Arc<Backing> = if let Some(key) = kek {
let raw = std::fs::read(path).map_err(|e| SegmentHandleError::Open {
detail: format!("{path:?}: {e}"),
})?;
let owned =
nodedb_array::segment::reader::OwnedSegmentReader::open_with_kek(&raw, Some(key))
.map_err(|e| SegmentHandleError::Open {
detail: format!("{path:?}: {e}"),
})?;
Arc::new(Backing::Decrypted(Arc::new(owned.into_plaintext())))
} else {
let file = std::fs::File::open(path).map_err(|e| SegmentHandleError::Open {
detail: format!("{path:?}: {e}"),
})?;
let mmap = unsafe { Mmap::map(&file) }.map_err(|e| SegmentHandleError::Mmap {
detail: format!("{path:?}: {e}"),
})?;
Arc::new(Backing::Mmap(Arc::new(mmap)))
};
let (rtree, schema_hash, tile_count) = {
let reader =
SegmentReader::open(backing.bytes()).map_err(|e| SegmentHandleError::Open {
detail: format!("{path:?}: {e}"),
})?;
if reader.schema_hash() != expected_schema_hash {
return Err(SegmentHandleError::SchemaHashMismatch {
array: expected_schema_hash,
seg: reader.schema_hash(),
});
}
let rtree = HilbertPackedRTree::build(reader.tiles());
(rtree, reader.schema_hash(), reader.tile_count())
};
Ok(Self {
backing,
rtree: Arc::new(rtree),
schema_hash,
tile_count,
id,
})
}
pub fn id(&self) -> &str {
&self.id
}
pub fn schema_hash(&self) -> u64 {
self.schema_hash
}
pub fn tile_count(&self) -> usize {
self.tile_count
}
pub fn rtree(&self) -> &HilbertPackedRTree {
&self.rtree
}
pub fn reader(&self) -> SegmentReader<'_> {
SegmentReader::open(self.backing.bytes()).expect("segment validated at open")
}
}
impl std::fmt::Debug for SegmentHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SegmentHandle")
.field("id", &self.id)
.field("schema_hash", &format_args!("{:x}", self.schema_hash))
.field("tile_count", &self.tile_count)
.finish()
}
}