pub mod builder;
pub mod column;
pub mod csr;
mod graph_store_impl;
pub mod id;
#[cfg(feature = "lpg")]
pub mod layered;
pub mod node_table;
pub mod rel_table;
pub mod schema;
pub mod section;
#[cfg(test)]
mod tests;
pub mod zone_map;
pub use builder::{CompactStoreBuilder, from_graph_store, from_graph_store_preserving_ids};
use std::sync::Arc;
use arcstr::ArcStr;
use grafeo_common::types::{EdgeId, NodeId};
use grafeo_common::utils::hash::FxHashMap;
use self::node_table::NodeTable;
use self::rel_table::RelTable;
use crate::graph::Direction;
use crate::statistics::Statistics;
pub struct CompactStore {
node_tables_by_id: Vec<NodeTable>,
label_to_table_id: FxHashMap<ArcStr, u16>,
rel_tables_by_id: Vec<RelTable>,
edge_type_to_rel_id: FxHashMap<ArcStr, Vec<u16>>,
table_id_to_label: Vec<ArcStr>,
rel_table_id_to_type: Vec<ArcStr>,
src_rel_table_ids: Vec<Vec<u16>>,
dst_rel_table_ids: Vec<Vec<u16>>,
statistics: Arc<Statistics>,
node_id_map: Option<FxHashMap<NodeId, (u16, u64)>>,
edge_id_map: Option<FxHashMap<EdgeId, (u16, u64)>>,
node_offset_to_id: Option<Vec<Vec<NodeId>>>,
edge_offset_to_id: Option<Vec<Vec<EdgeId>>>,
}
impl std::fmt::Debug for CompactStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CompactStore")
.field("node_tables_by_id", &self.node_tables_by_id)
.field("rel_tables_by_id", &self.rel_tables_by_id)
.field("table_id_to_label", &self.table_id_to_label)
.field("rel_table_id_to_type", &self.rel_table_id_to_type)
.finish_non_exhaustive()
}
}
impl CompactStore {
#[must_use]
pub(crate) fn new(
node_tables_by_id: Vec<NodeTable>,
label_to_table_id: FxHashMap<ArcStr, u16>,
rel_tables_by_id: Vec<RelTable>,
edge_type_to_rel_id: FxHashMap<ArcStr, Vec<u16>>,
table_id_to_label: Vec<ArcStr>,
rel_table_id_to_type: Vec<ArcStr>,
statistics: Statistics,
) -> Self {
let node_table_count = node_tables_by_id.len();
let mut src_rel_table_ids = vec![Vec::new(); node_table_count];
let mut dst_rel_table_ids = vec![Vec::new(); node_table_count];
debug_assert!(
rel_tables_by_id.len() <= usize::from(id::MAX_TABLE_ID) + 1,
"rel table count {} exceeds 15-bit limit; caller must validate",
rel_tables_by_id.len()
);
for (rel_idx, rt) in rel_tables_by_id.iter().enumerate() {
let rel_id = u16::try_from(rel_idx).expect("caller validated table count");
let src_tid = rt.src_table_id() as usize;
let dst_tid = rt.dst_table_id() as usize;
if src_tid < node_table_count {
src_rel_table_ids[src_tid].push(rel_id);
}
if dst_tid < node_table_count {
dst_rel_table_ids[dst_tid].push(rel_id);
}
}
Self {
node_tables_by_id,
label_to_table_id,
rel_tables_by_id,
edge_type_to_rel_id,
table_id_to_label,
rel_table_id_to_type,
src_rel_table_ids,
dst_rel_table_ids,
statistics: Arc::new(statistics),
node_id_map: None,
edge_id_map: None,
node_offset_to_id: None,
edge_offset_to_id: None,
}
}
#[inline]
fn resolve_node_table(&self, table_id: u16) -> Option<&NodeTable> {
self.node_tables_by_id.get(table_id as usize)
}
#[inline]
fn resolve_rel_table(&self, rel_table_id: u16) -> Option<&RelTable> {
self.rel_tables_by_id.get(rel_table_id as usize)
}
#[must_use]
pub fn node_table(&self, label: &str) -> Option<&NodeTable> {
let &tid = self.label_to_table_id.get(label)?;
self.node_tables_by_id.get(tid as usize)
}
#[must_use]
pub fn rel_table(&self, edge_type: &str) -> Option<&RelTable> {
let rids = self.edge_type_to_rel_id.get(edge_type)?;
let &rid = rids.first()?;
self.rel_tables_by_id.get(rid as usize)
}
#[must_use]
pub fn rel_tables_for_type(&self, edge_type: &str) -> Vec<&RelTable> {
self.edge_type_to_rel_id
.get(edge_type)
.map(|rids| {
rids.iter()
.filter_map(|&rid| self.rel_tables_by_id.get(rid as usize))
.collect()
})
.unwrap_or_default()
}
#[must_use]
pub fn label_for_table_id(&self, table_id: u16) -> Option<&ArcStr> {
self.table_id_to_label.get(table_id as usize)
}
#[must_use]
pub fn edge_type_for_rel_table_id(&self, rel_table_id: u16) -> Option<&ArcStr> {
self.rel_table_id_to_type.get(rel_table_id as usize)
}
fn collect_edges(
&self,
node_table_id: u16,
node_offset: u32,
direction: Direction,
) -> Vec<(NodeId, EdgeId)> {
let tid = node_table_id as usize;
let mut results = Vec::new();
if matches!(direction, Direction::Outgoing | Direction::Both)
&& let Some(rel_ids) = self.src_rel_table_ids.get(tid)
{
for &rel_id in rel_ids {
let rt = &self.rel_tables_by_id[rel_id as usize];
results.extend(rt.edges_from_source(node_offset));
}
}
if matches!(direction, Direction::Incoming | Direction::Both)
&& let Some(rel_ids) = self.dst_rel_table_ids.get(tid)
{
for &rel_id in rel_ids {
let rt = &self.rel_tables_by_id[rel_id as usize];
if let Some(edges) = rt.edges_to_target(node_offset) {
results.extend(edges);
}
}
}
if self.preserves_ids() {
for (target_id, edge_id) in &mut results {
*target_id = self.to_original_node_id(*target_id);
*edge_id = self.to_original_edge_id(*edge_id);
}
}
results
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
let node_bytes: usize = self
.node_tables_by_id
.iter()
.map(|nt| nt.memory_bytes())
.sum();
let rel_bytes: usize = self
.rel_tables_by_id
.iter()
.map(|rt| rt.memory_bytes())
.sum();
let id_map_bytes = self.id_map_memory_bytes();
node_bytes + rel_bytes + id_map_bytes
}
#[must_use]
pub fn preserves_ids(&self) -> bool {
self.node_id_map.is_some()
}
pub(crate) fn set_id_maps(
&mut self,
node_id_map: FxHashMap<NodeId, (u16, u64)>,
edge_id_map: FxHashMap<EdgeId, (u16, u64)>,
node_offset_to_id: Vec<Vec<NodeId>>,
edge_offset_to_id: Vec<Vec<EdgeId>>,
) {
self.node_id_map = Some(node_id_map);
self.edge_id_map = Some(edge_id_map);
self.node_offset_to_id = Some(node_offset_to_id);
self.edge_offset_to_id = Some(edge_offset_to_id);
}
#[inline]
pub(crate) fn resolve_node(&self, id: NodeId) -> Option<(u16, u64)> {
if let Some(ref map) = self.node_id_map {
map.get(&id).copied()
} else {
Some(id::decode_node_id(id))
}
}
#[inline]
pub(crate) fn resolve_edge(&self, id: EdgeId) -> Option<(u16, u64)> {
if let Some(ref map) = self.edge_id_map {
map.get(&id).copied()
} else {
Some(id::decode_edge_id(id))
}
}
#[inline]
pub(crate) fn to_original_node_id(&self, compact_id: NodeId) -> NodeId {
if let Some(ref offsets) = self.node_offset_to_id {
let (table_id, offset) = id::decode_node_id(compact_id);
offsets
.get(table_id as usize)
.and_then(|v| v.get(usize::try_from(offset).ok()?))
.copied()
.unwrap_or(compact_id)
} else {
compact_id
}
}
#[inline]
pub(crate) fn to_original_edge_id(&self, compact_id: EdgeId) -> EdgeId {
if let Some(ref offsets) = self.edge_offset_to_id {
let (rel_table_id, csr_pos) = id::decode_edge_id(compact_id);
offsets
.get(rel_table_id as usize)
.and_then(|v| v.get(usize::try_from(csr_pos).ok()?))
.copied()
.unwrap_or(compact_id)
} else {
compact_id
}
}
fn id_map_memory_bytes(&self) -> usize {
let node_map = self.node_id_map.as_ref().map_or(0, |m| m.len() * 24);
let edge_map = self.edge_id_map.as_ref().map_or(0, |m| m.len() * 24);
let node_rev = self
.node_offset_to_id
.as_ref()
.map_or(0, |v| v.iter().map(|inner| inner.len() * 8).sum());
let edge_rev = self
.edge_offset_to_id
.as_ref()
.map_or(0, |v| v.iter().map(|inner| inner.len() * 8).sum());
node_map + edge_map + node_rev + edge_rev
}
}