use crate::body::BodyReader;
use crate::codec::SnapshotInfo;
use crate::errors::{Result, SnapshotCodecError};
use crate::format::BODY_FORMAT_VERSION;
#[derive(Debug, Clone)]
pub struct SnapshotView<'a> {
info: SnapshotInfo,
body: BodyView<'a>,
}
impl<'a> SnapshotView<'a> {
pub(crate) fn parse(info: SnapshotInfo, bytes: &'a [u8]) -> Result<Self> {
Ok(Self {
info,
body: BodyView::parse(bytes)?,
})
}
pub fn info(&self) -> &SnapshotInfo {
&self.info
}
pub fn next_node_id(&self) -> u64 {
self.body.next_node_id
}
pub fn next_rel_id(&self) -> u64 {
self.body.next_rel_id
}
pub fn node_ids(&self) -> U64ColumnView<'a> {
self.body.node_ids
}
pub fn relationship_ids(&self) -> U64ColumnView<'a> {
self.body.rel_ids
}
pub fn relationship_sources(&self) -> U64ColumnView<'a> {
self.body.rel_src
}
pub fn relationship_targets(&self) -> U64ColumnView<'a> {
self.body.rel_dst
}
pub fn relationship_type_ids(&self) -> U32ColumnView<'a> {
self.body.rel_type_ids
}
pub fn labels_for_node_index(
&self,
index: usize,
) -> Result<impl Iterator<Item = &'a str> + '_> {
let start =
self.body.node_label_offsets.get(index).ok_or_else(|| {
SnapshotCodecError::Decode("node label offset out of bounds".into())
})? as usize;
let end =
self.body.node_label_offsets.get(index + 1).ok_or_else(|| {
SnapshotCodecError::Decode("node label offset out of bounds".into())
})? as usize;
if start > end || end > self.body.node_label_ids.len() {
return Err(SnapshotCodecError::Decode(
"invalid node label offset".into(),
));
}
Ok(self.body.node_label_ids.slice(start, end).map(move |id| {
self.body
.label_dictionary
.get(id as usize)
.unwrap_or("<invalid-label>")
}))
}
pub fn relationship_type(&self, type_id: u32) -> Option<&'a str> {
self.body.rel_type_dictionary.get(type_id as usize)
}
}
#[derive(Debug, Clone)]
struct BodyView<'a> {
next_node_id: u64,
next_rel_id: u64,
node_ids: U64ColumnView<'a>,
label_dictionary: StringTableView<'a>,
node_label_offsets: U32ColumnView<'a>,
node_label_ids: U32ColumnView<'a>,
rel_ids: U64ColumnView<'a>,
rel_src: U64ColumnView<'a>,
rel_dst: U64ColumnView<'a>,
rel_type_dictionary: StringTableView<'a>,
rel_type_ids: U32ColumnView<'a>,
}
impl<'a> BodyView<'a> {
fn parse(bytes: &'a [u8]) -> Result<Self> {
let mut reader = BodyReader::new(bytes);
let version = reader.read_u32()?;
if version != BODY_FORMAT_VERSION {
return Err(SnapshotCodecError::Decode(format!(
"unsupported snapshot body format version {version}"
)));
}
let next_node_id = reader.read_u64()?;
let next_rel_id = reader.read_u64()?;
let node_ids = reader.read_u64_column_view()?;
let label_dictionary = reader.read_string_table_view()?;
let node_label_offsets = reader.read_u32_column_view()?;
let node_label_ids = reader.read_u32_column_view()?;
let rel_ids = reader.read_u64_column_view()?;
let rel_src = reader.read_u64_column_view()?;
let rel_dst = reader.read_u64_column_view()?;
let rel_type_dictionary = reader.read_string_table_view()?;
let rel_type_ids = reader.read_u32_column_view()?;
Ok(Self {
next_node_id,
next_rel_id,
node_ids,
label_dictionary,
node_label_offsets,
node_label_ids,
rel_ids,
rel_src,
rel_dst,
rel_type_dictionary,
rel_type_ids,
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct U64ColumnView<'a> {
bytes: &'a [u8],
len: usize,
}
impl<'a> U64ColumnView<'a> {
pub(crate) fn new(bytes: &'a [u8], len: usize) -> Self {
Self { bytes, len }
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn get(&self, index: usize) -> Option<u64> {
if index >= self.len {
return None;
}
let start = index.checked_mul(8)?;
let end = start.checked_add(8)?;
Some(u64::from_le_bytes(
self.bytes.get(start..end)?.try_into().ok()?,
))
}
pub fn iter(&self) -> impl Iterator<Item = u64> + '_ {
(0..self.len).filter_map(|index| self.get(index))
}
}
#[derive(Debug, Clone, Copy)]
pub struct U32ColumnView<'a> {
bytes: &'a [u8],
len: usize,
}
impl<'a> U32ColumnView<'a> {
pub(crate) fn new(bytes: &'a [u8], len: usize) -> Self {
Self { bytes, len }
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn get(&self, index: usize) -> Option<u32> {
if index >= self.len {
return None;
}
let start = index.checked_mul(4)?;
let end = start.checked_add(4)?;
Some(u32::from_le_bytes(
self.bytes.get(start..end)?.try_into().ok()?,
))
}
pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
(0..self.len).filter_map(|index| self.get(index))
}
pub(crate) fn slice(&self, start: usize, end: usize) -> impl Iterator<Item = u32> + '_ {
(start..end).filter_map(|index| self.get(index))
}
}
#[derive(Debug, Clone)]
pub struct StringTableView<'a> {
entries: Vec<&'a str>,
}
impl<'a> StringTableView<'a> {
pub(crate) fn new(entries: Vec<&'a str>) -> Self {
Self { entries }
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: usize) -> Option<&'a str> {
self.entries.get(index).copied()
}
pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
self.entries.iter().copied()
}
}