use core::ops::ControlFlow;
use oxgraph_algo::{
BfsBounds, BfsEpochScratch, breadth_first_search_bounded, breadth_first_search_bounded_both,
reverse_breadth_first_search_bounded,
};
use oxgraph_graph::{CanonicalElementIdentity, ElementIndex, LocalElementIdentity};
use crate::{
DbError, ElementId,
projection::{GraphProjection, ProjectionElementId},
};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum TraversalDirection {
#[default]
Outgoing,
Incoming,
Both,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TraversalOptions {
pub max_depth: usize,
pub direction: TraversalDirection,
pub limit: usize,
pub include_start: bool,
}
impl Default for TraversalOptions {
fn default() -> Self {
Self {
max_depth: 1,
direction: TraversalDirection::Outgoing,
limit: usize::MAX,
include_start: false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TraversalRow {
pub element: ElementId,
pub depth: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TraversalResult {
rows: Vec<TraversalRow>,
}
impl TraversalResult {
#[must_use]
pub(crate) const fn new(rows: Vec<TraversalRow>) -> Self {
Self { rows }
}
#[must_use]
pub fn rows(&self) -> &[TraversalRow] {
&self.rows
}
}
pub(crate) fn traverse_graph_projection(
graph: &GraphProjection,
seeds: &[ElementId],
options: TraversalOptions,
) -> Result<TraversalResult, DbError> {
if seeds.is_empty() || options.limit == 0 {
return Ok(TraversalResult::new(Vec::new()));
}
let local_seeds = seeds
.iter()
.map(|seed| {
graph
.local_element_id(*seed)
.ok_or(DbError::UnknownElement { id: *seed })
})
.collect::<Result<Vec<ProjectionElementId>, DbError>>()?;
let bounds = BfsBounds {
max_depth: Some(u32::try_from(options.max_depth).unwrap_or(u32::MAX)),
result_limit: options.limit,
include_seeds: options.include_start,
};
let bound = graph.element_bound();
let mut marks = vec![0_u32; bound];
let mut queue = vec![ProjectionElementId::new(0); bound];
let mut scratch = BfsEpochScratch::for_graph(graph, &mut marks, &mut queue);
let mut rows = Vec::new();
{
let mut visitor = |element: ProjectionElementId, depth: u32| {
rows.push(TraversalRow {
element: graph.canonical_element_id(element),
depth: usize::try_from(depth).unwrap_or(usize::MAX),
});
ControlFlow::Continue(())
};
match options.direction {
TraversalDirection::Outgoing => breadth_first_search_bounded(
graph,
&local_seeds,
bounds,
&mut scratch,
&mut visitor,
),
TraversalDirection::Incoming => reverse_breadth_first_search_bounded(
graph,
&local_seeds,
bounds,
&mut scratch,
&mut visitor,
),
TraversalDirection::Both => breadth_first_search_bounded_both(
graph,
&local_seeds,
bounds,
&mut scratch,
&mut visitor,
),
}
.map_err(DbError::traversal)?;
}
Ok(TraversalResult::new(rows))
}