use std::collections::{BTreeSet, VecDeque};
use oxgraph_graph::{
CanonicalElementIdentity, ElementPredecessors, ElementSuccessors, 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 mut traversal = TraversalState::new(graph, options.limit);
for seed in seeds {
let local = graph
.local_element_id(*seed)
.ok_or(DbError::UnknownElement { id: *seed })?;
traversal.push_seed(local, *seed, options.include_start);
if traversal.at_limit() {
return Ok(traversal.finish());
}
}
while let Some((element, depth)) = traversal.pop_frontier() {
if depth >= options.max_depth {
continue;
}
let next_depth = depth + 1;
let reached_limit = match options.direction {
TraversalDirection::Outgoing => traversal.visit_outgoing(element, next_depth),
TraversalDirection::Incoming => traversal.visit_incoming(element, next_depth),
TraversalDirection::Both => {
traversal.visit_outgoing(element, next_depth)
|| traversal.visit_incoming(element, next_depth)
}
};
if reached_limit {
return Ok(traversal.finish());
}
}
Ok(traversal.finish())
}
struct TraversalState<'graph> {
graph: &'graph GraphProjection,
limit: usize,
visited: BTreeSet<ProjectionElementId>,
queue: VecDeque<(ProjectionElementId, usize)>,
rows: Vec<TraversalRow>,
}
impl<'graph> TraversalState<'graph> {
const fn new(graph: &'graph GraphProjection, limit: usize) -> Self {
Self {
graph,
limit,
visited: BTreeSet::new(),
queue: VecDeque::new(),
rows: Vec::new(),
}
}
fn push_seed(&mut self, local: ProjectionElementId, seed: ElementId, include_start: bool) {
if !self.visited.insert(local) {
return;
}
self.queue.push_back((local, 0));
if include_start {
self.rows.push(TraversalRow {
element: seed,
depth: 0,
});
}
}
fn pop_frontier(&mut self) -> Option<(ProjectionElementId, usize)> {
self.queue.pop_front()
}
const fn at_limit(&self) -> bool {
self.rows.len() == self.limit
}
fn visit_outgoing(&mut self, element: ProjectionElementId, depth: usize) -> bool {
for neighbor in self.graph.element_successors(element) {
if self.push_discovered(neighbor, depth) {
return true;
}
}
false
}
fn visit_incoming(&mut self, element: ProjectionElementId, depth: usize) -> bool {
for neighbor in self.graph.element_predecessors(element) {
if self.push_discovered(neighbor, depth) {
return true;
}
}
false
}
fn push_discovered(&mut self, neighbor: ProjectionElementId, depth: usize) -> bool {
if !self.visited.insert(neighbor) {
return false;
}
self.queue.push_back((neighbor, depth));
self.rows.push(TraversalRow {
element: self.graph.canonical_element_id(neighbor),
depth,
});
self.at_limit()
}
fn finish(self) -> TraversalResult {
TraversalResult::new(self.rows)
}
}