1use std::collections::{BTreeSet, VecDeque};
4
5use oxgraph_graph::{
6 CanonicalElementIdentity, ElementPredecessors, ElementSuccessors, LocalElementIdentity,
7};
8
9use crate::{
10 DbError, ElementId,
11 projection::{GraphProjection, ProjectionElementId},
12};
13
14#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
20pub enum TraversalDirection {
21 #[default]
23 Outgoing,
24 Incoming,
26 Both,
28}
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub struct TraversalOptions {
37 pub max_depth: usize,
39 pub direction: TraversalDirection,
41 pub limit: usize,
43 pub include_start: bool,
45}
46
47impl Default for TraversalOptions {
48 fn default() -> Self {
54 Self {
55 max_depth: 1,
56 direction: TraversalDirection::Outgoing,
57 limit: usize::MAX,
58 include_start: false,
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
69pub struct TraversalRow {
70 pub element: ElementId,
72 pub depth: usize,
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
84pub struct TraversalResult {
85 rows: Vec<TraversalRow>,
87}
88
89impl TraversalResult {
90 #[must_use]
96 pub(crate) const fn new(rows: Vec<TraversalRow>) -> Self {
97 Self { rows }
98 }
99
100 #[must_use]
106 pub fn rows(&self) -> &[TraversalRow] {
107 &self.rows
108 }
109}
110
111pub(crate) fn traverse_graph_projection(
113 graph: &GraphProjection,
114 seeds: &[ElementId],
115 options: TraversalOptions,
116) -> Result<TraversalResult, DbError> {
117 if seeds.is_empty() || options.limit == 0 {
118 return Ok(TraversalResult::new(Vec::new()));
119 }
120
121 let mut traversal = TraversalState::new(graph, options.limit);
122 for seed in seeds {
123 let local = graph
124 .local_element_id(*seed)
125 .ok_or(DbError::UnknownElement { id: *seed })?;
126 traversal.push_seed(local, *seed, options.include_start);
127 if traversal.at_limit() {
128 return Ok(traversal.finish());
129 }
130 }
131
132 while let Some((element, depth)) = traversal.pop_frontier() {
133 if depth >= options.max_depth {
134 continue;
135 }
136 let next_depth = depth + 1;
137 let reached_limit = match options.direction {
138 TraversalDirection::Outgoing => traversal.visit_outgoing(element, next_depth),
139 TraversalDirection::Incoming => traversal.visit_incoming(element, next_depth),
140 TraversalDirection::Both => {
141 traversal.visit_outgoing(element, next_depth)
142 || traversal.visit_incoming(element, next_depth)
143 }
144 };
145 if reached_limit {
146 return Ok(traversal.finish());
147 }
148 }
149 Ok(traversal.finish())
150}
151
152struct TraversalState<'graph> {
154 graph: &'graph GraphProjection,
156 limit: usize,
158 visited: BTreeSet<ProjectionElementId>,
160 queue: VecDeque<(ProjectionElementId, usize)>,
162 rows: Vec<TraversalRow>,
164}
165
166impl<'graph> TraversalState<'graph> {
167 const fn new(graph: &'graph GraphProjection, limit: usize) -> Self {
169 Self {
170 graph,
171 limit,
172 visited: BTreeSet::new(),
173 queue: VecDeque::new(),
174 rows: Vec::new(),
175 }
176 }
177
178 fn push_seed(&mut self, local: ProjectionElementId, seed: ElementId, include_start: bool) {
180 if !self.visited.insert(local) {
181 return;
182 }
183 self.queue.push_back((local, 0));
184 if include_start {
185 self.rows.push(TraversalRow {
186 element: seed,
187 depth: 0,
188 });
189 }
190 }
191
192 fn pop_frontier(&mut self) -> Option<(ProjectionElementId, usize)> {
194 self.queue.pop_front()
195 }
196
197 const fn at_limit(&self) -> bool {
199 self.rows.len() == self.limit
200 }
201
202 fn visit_outgoing(&mut self, element: ProjectionElementId, depth: usize) -> bool {
204 for neighbor in self.graph.element_successors(element) {
205 if self.push_discovered(neighbor, depth) {
206 return true;
207 }
208 }
209 false
210 }
211
212 fn visit_incoming(&mut self, element: ProjectionElementId, depth: usize) -> bool {
214 for neighbor in self.graph.element_predecessors(element) {
215 if self.push_discovered(neighbor, depth) {
216 return true;
217 }
218 }
219 false
220 }
221
222 fn push_discovered(&mut self, neighbor: ProjectionElementId, depth: usize) -> bool {
224 if !self.visited.insert(neighbor) {
225 return false;
226 }
227 self.queue.push_back((neighbor, depth));
228 self.rows.push(TraversalRow {
229 element: self.graph.canonical_element_id(neighbor),
230 depth,
231 });
232 self.at_limit()
233 }
234
235 fn finish(self) -> TraversalResult {
237 TraversalResult::new(self.rows)
238 }
239}