1use core::ops::ControlFlow;
4
5use oxgraph_algo::{
6 BfsBounds, BfsEpochScratch, breadth_first_search_bounded, breadth_first_search_bounded_both,
7 reverse_breadth_first_search_bounded,
8};
9use oxgraph_graph::{CanonicalElementIdentity, ElementIndex, LocalElementIdentity};
10
11use crate::{
12 DbError, ElementId,
13 projection::{GraphProjection, ProjectionElementId},
14};
15
16#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub enum TraversalDirection {
23 #[default]
25 Outgoing,
26 Incoming,
28 Both,
30}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub struct TraversalOptions {
39 pub max_depth: usize,
41 pub direction: TraversalDirection,
43 pub limit: usize,
45 pub include_start: bool,
47}
48
49impl Default for TraversalOptions {
50 fn default() -> Self {
56 Self {
57 max_depth: 1,
58 direction: TraversalDirection::Outgoing,
59 limit: usize::MAX,
60 include_start: false,
61 }
62 }
63}
64
65#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub struct TraversalRow {
72 pub element: ElementId,
74 pub depth: usize,
76}
77
78#[derive(Clone, Debug, Eq, PartialEq)]
86pub struct TraversalResult {
87 rows: Vec<TraversalRow>,
89}
90
91impl TraversalResult {
92 #[must_use]
98 pub(crate) const fn new(rows: Vec<TraversalRow>) -> Self {
99 Self { rows }
100 }
101
102 #[must_use]
108 pub fn rows(&self) -> &[TraversalRow] {
109 &self.rows
110 }
111}
112
113pub(crate) fn traverse_graph_projection(
124 graph: &GraphProjection,
125 seeds: &[ElementId],
126 options: TraversalOptions,
127) -> Result<TraversalResult, DbError> {
128 if seeds.is_empty() || options.limit == 0 {
129 return Ok(TraversalResult::new(Vec::new()));
130 }
131
132 let local_seeds = seeds
133 .iter()
134 .map(|seed| {
135 graph
136 .local_element_id(*seed)
137 .ok_or(DbError::UnknownElement { id: *seed })
138 })
139 .collect::<Result<Vec<ProjectionElementId>, DbError>>()?;
140
141 let bounds = BfsBounds {
142 max_depth: Some(u32::try_from(options.max_depth).unwrap_or(u32::MAX)),
143 result_limit: options.limit,
144 include_seeds: options.include_start,
145 };
146
147 let bound = graph.element_bound();
148 let mut marks = vec![0_u32; bound];
149 let mut queue = vec![ProjectionElementId::new(0); bound];
150 let mut scratch = BfsEpochScratch::for_graph(graph, &mut marks, &mut queue);
151
152 let mut rows = Vec::new();
153 {
154 let mut visitor = |element: ProjectionElementId, depth: u32| {
155 rows.push(TraversalRow {
156 element: graph.canonical_element_id(element),
157 depth: usize::try_from(depth).unwrap_or(usize::MAX),
158 });
159 ControlFlow::Continue(())
160 };
161
162 match options.direction {
163 TraversalDirection::Outgoing => breadth_first_search_bounded(
164 graph,
165 &local_seeds,
166 bounds,
167 &mut scratch,
168 &mut visitor,
169 ),
170 TraversalDirection::Incoming => reverse_breadth_first_search_bounded(
171 graph,
172 &local_seeds,
173 bounds,
174 &mut scratch,
175 &mut visitor,
176 ),
177 TraversalDirection::Both => breadth_first_search_bounded_both(
178 graph,
179 &local_seeds,
180 bounds,
181 &mut scratch,
182 &mut visitor,
183 ),
184 }
185 .map_err(DbError::traversal)?;
186 }
187
188 Ok(TraversalResult::new(rows))
189}