use std::sync::Arc;
use selene_core::DbString;
use crate::adjacency::AdjacencyEdge;
use crate::error::{GraphError, GraphResult};
use crate::graph::SeleneGraph;
use crate::index_provider::{IndexProvider, ProviderError};
use crate::store::{EdgeStore, RowIndex};
pub(crate) fn rebuild_derived_state(graph: &mut SeleneGraph) -> GraphResult<()> {
graph.idx_label.clear();
graph.idx_edge_label.clear();
graph.adjacency_out.clear();
graph.adjacency_in.clear();
let node_count = graph.node_store.labels.len();
for row_index in 0..node_count {
let row = u32::try_from(row_index).map_err(|_| GraphError::Inconsistent {
reason: format!(
"node store row index {row_index} exceeds u32::MAX; selene-graph \
caps rows at u32::MAX",
),
})?;
if !graph.node_store.is_alive(row) {
continue;
}
if let Some(labels) = graph.node_store.labels.get(row_index) {
for label in labels.iter() {
graph
.idx_label
.entry(label.clone())
.or_default()
.insert(row);
}
}
}
let edge_count = graph.edge_store.label.len();
for row_index in 0..edge_count {
let row = u32::try_from(row_index).map_err(|_| GraphError::Inconsistent {
reason: format!(
"edge store row index {row_index} exceeds u32::MAX; selene-graph \
caps rows at u32::MAX",
),
})?;
if !graph.edge_store.is_alive(row) {
continue;
}
if let Some(label) = graph.edge_store.label.get(row_index) {
graph
.idx_edge_label
.entry(label.clone())
.or_default()
.insert(row);
}
}
rebuild_id_maps(graph)?;
rebuild_adjacency(graph)?;
Ok(())
}
fn rebuild_id_maps(graph: &mut SeleneGraph) -> GraphResult<()> {
graph.node_id_to_row.clear();
graph.edge_id_to_row.clear();
let node_len = graph.node_store.len();
let edge_len = graph.edge_store.len();
pad_row_to_id(
&mut graph.node_store.row_to_id,
node_len,
selene_core::NodeId::TOMBSTONE,
);
pad_row_to_id(
&mut graph.edge_store.row_to_id,
edge_len,
selene_core::EdgeId::TOMBSTONE,
);
for row in 0..node_len {
let raw = row as u32;
let mut id = graph
.node_store
.row_to_id
.get(row)
.copied()
.unwrap_or(selene_core::NodeId::TOMBSTONE);
if id == selene_core::NodeId::TOMBSTONE {
if !graph.node_store.is_alive(raw) {
continue;
}
id = selene_core::NodeId::new(u64::from(raw) + 1); graph.node_store.row_to_id.set(row, id);
}
graph.node_id_to_row.insert(id, RowIndex::new(raw));
}
for row in 0..edge_len {
let raw = row as u32;
let mut id = graph
.edge_store
.row_to_id
.get(row)
.copied()
.unwrap_or(selene_core::EdgeId::TOMBSTONE);
if id == selene_core::EdgeId::TOMBSTONE {
if !graph.edge_store.is_alive(raw) {
continue;
}
id = selene_core::EdgeId::new(u64::from(raw) + 1); graph.edge_store.row_to_id.set(row, id);
}
graph.edge_id_to_row.insert(id, RowIndex::new(raw));
}
Ok(())
}
fn pad_row_to_id<T: Clone>(
column: &mut crate::chunked_vec::ChunkedVec<T>,
target_len: usize,
tombstone: T,
) {
while column.len() < target_len {
column.push(tombstone.clone());
}
}
fn rebuild_adjacency(graph: &mut SeleneGraph) -> GraphResult<()> {
let edge_count = graph.edge_store.len();
for row_index in 0..edge_count {
let row = u32::try_from(row_index).map_err(|_| GraphError::Inconsistent {
reason: format!(
"edge store row index {row_index} exceeds u32::MAX; selene-graph \
caps rows at u32::MAX",
),
})?;
if !graph.edge_store.is_alive(row) {
continue;
}
let (label, source, target) = edge_row_parts(&graph.edge_store, row_index)?;
let edge_id =
graph
.edge_id_for_row(RowIndex::new(row))
.ok_or_else(|| GraphError::Inconsistent {
reason: format!(
"alive edge row {row} has no mapped external id during rebuild"
),
})?;
graph
.adjacency_out
.entry(source)
.or_default()
.add(AdjacencyEdge {
label: label.clone(),
neighbor: target,
edge_id,
});
graph
.adjacency_in
.entry(target)
.or_default()
.add(AdjacencyEdge {
label,
neighbor: source,
edge_id,
});
}
Ok(())
}
fn edge_row_parts(
store: &EdgeStore,
row_index: usize,
) -> GraphResult<(DbString, selene_core::NodeId, selene_core::NodeId)> {
let label = store
.label
.get(row_index)
.cloned()
.ok_or_else(|| GraphError::Inconsistent {
reason: format!("edge label column missing row {row_index}"),
})?;
let source = *store
.source
.get(row_index)
.ok_or_else(|| GraphError::Inconsistent {
reason: format!("edge source column missing row {row_index}"),
})?;
let target = *store
.target
.get(row_index)
.ok_or_else(|| GraphError::Inconsistent {
reason: format!("edge target column missing row {row_index}"),
})?;
Ok((label, source, target))
}
pub(crate) fn validate_unique_provider_tags(
providers: &[Arc<dyn IndexProvider>],
) -> GraphResult<()> {
let mut seen = std::collections::BTreeSet::new();
for provider in providers {
let tag = provider.provider_tag();
if !seen.insert(tag) {
return Err(GraphError::Provider(ProviderError::Inconsistent {
reason: format!("duplicate provider tag {tag}"),
}));
}
}
Ok(())
}