1use std::collections::HashMap;
23use std::collections::hash_map::Entry;
24
25use crate::csr::CsrIndex;
26use crate::overlay_delta::GraphOverlayDelta;
27use crate::path_params::ShortestPathParams;
28
29impl CsrIndex {
30 pub(crate) fn shortest_path_overlay(
36 &self,
37 params: ShortestPathParams<'_>,
38 overlay: &GraphOverlayDelta,
39 ) -> Option<Vec<String>> {
40 let ShortestPathParams {
41 src,
42 dst,
43 label_filter,
44 max_depth,
45 max_visited,
46 frontier_bitmap,
47 } = params;
48 if src == dst {
49 return Some(vec![src.to_string()]);
50 }
51
52 let mut fwd_parent: HashMap<String, String> = HashMap::new();
55 let mut bwd_parent: HashMap<String, String> = HashMap::new();
56 fwd_parent.insert(src.to_string(), src.to_string());
57 bwd_parent.insert(dst.to_string(), dst.to_string());
58
59 let mut fwd_frontier: Vec<String> = vec![src.to_string()];
60 let mut bwd_frontier: Vec<String> = vec![dst.to_string()];
61
62 let label_id = label_filter.and_then(|l| self.label_id(l));
63
64 for _depth in 0..max_depth {
65 if fwd_parent.len() + bwd_parent.len() >= max_visited {
66 break;
67 }
68
69 let mut next_fwd = Vec::new();
70 for node in std::mem::take(&mut fwd_frontier) {
71 let neighbors =
72 self.forward_neighbors(&node, label_id, label_filter, frontier_bitmap, overlay);
73 for neighbor in neighbors {
74 if let Some(meeting) = relax(
75 &neighbor,
76 &node,
77 &mut fwd_parent,
78 &bwd_parent,
79 &mut next_fwd,
80 ) {
81 return Some(reconstruct(&meeting, &fwd_parent, &bwd_parent));
82 }
83 }
84 }
85 fwd_frontier = next_fwd;
86
87 let mut next_bwd = Vec::new();
88 for node in std::mem::take(&mut bwd_frontier) {
89 let neighbors = self.backward_neighbors(
90 &node,
91 label_id,
92 label_filter,
93 frontier_bitmap,
94 overlay,
95 );
96 for neighbor in neighbors {
97 if let Some(meeting) = relax(
98 &neighbor,
99 &node,
100 &mut bwd_parent,
101 &fwd_parent,
102 &mut next_bwd,
103 ) {
104 return Some(reconstruct(&meeting, &fwd_parent, &bwd_parent));
105 }
106 }
107 }
108 bwd_frontier = next_bwd;
109
110 if fwd_frontier.is_empty() && bwd_frontier.is_empty() {
111 break;
112 }
113 }
114 None
115 }
116
117 fn forward_neighbors(
121 &self,
122 node: &str,
123 label_id: Option<u32>,
124 label_filter: Option<&str>,
125 frontier_bitmap: Option<&nodedb_types::SurrogateBitmap>,
126 overlay: &GraphOverlayDelta,
127 ) -> Vec<String> {
128 let mut out = Vec::new();
129 if let Some(&node_id) = self.node_to_id.get(node) {
130 self.record_access(node_id);
131 for (lid, dst) in self.dense_iter_out(node_id) {
132 if label_id.is_some_and(|f| f != lid) {
133 continue;
134 }
135 let dst_name = &self.id_to_node[dst as usize];
136 if overlay.is_tombstoned(node, self.label_name(lid), dst_name) {
137 continue;
138 }
139 if !frontier_bitmap.is_none_or(|bm| {
140 bm.contains(nodedb_types::Surrogate::new(self.node_surrogate_raw(dst)))
141 }) {
142 continue;
143 }
144 out.push(dst_name.clone());
145 }
146 }
147 for (_, dst) in overlay.out_neighbors(node, label_filter) {
148 out.push(dst.to_string());
149 }
150 out
151 }
152
153 fn backward_neighbors(
157 &self,
158 node: &str,
159 label_id: Option<u32>,
160 label_filter: Option<&str>,
161 frontier_bitmap: Option<&nodedb_types::SurrogateBitmap>,
162 overlay: &GraphOverlayDelta,
163 ) -> Vec<String> {
164 let mut out = Vec::new();
165 if let Some(&node_id) = self.node_to_id.get(node) {
166 self.record_access(node_id);
167 for (lid, src) in self.dense_iter_in(node_id) {
168 if label_id.is_some_and(|f| f != lid) {
169 continue;
170 }
171 let src_name = &self.id_to_node[src as usize];
172 if overlay.is_tombstoned(src_name, self.label_name(lid), node) {
173 continue;
174 }
175 if !frontier_bitmap.is_none_or(|bm| {
176 bm.contains(nodedb_types::Surrogate::new(self.node_surrogate_raw(src)))
177 }) {
178 continue;
179 }
180 out.push(src_name.clone());
181 }
182 }
183 for (_, src) in overlay.in_neighbors(node, label_filter) {
184 out.push(src.to_string());
185 }
186 out
187 }
188}
189
190fn relax(
197 neighbor: &str,
198 parent: &str,
199 this_parent: &mut HashMap<String, String>,
200 other_parent: &HashMap<String, String>,
201 next_frontier: &mut Vec<String>,
202) -> Option<String> {
203 if let Entry::Vacant(e) = this_parent.entry(neighbor.to_string()) {
204 e.insert(parent.to_string());
205 next_frontier.push(neighbor.to_string());
206 }
207 if other_parent.contains_key(neighbor) {
208 return Some(neighbor.to_string());
209 }
210 None
211}
212
213fn reconstruct(
217 meeting: &str,
218 fwd_parent: &HashMap<String, String>,
219 bwd_parent: &HashMap<String, String>,
220) -> Vec<String> {
221 let mut path = walk_to_root(meeting, fwd_parent);
222 path.reverse();
223
224 let suffix_root = &bwd_parent[meeting];
225 if suffix_root != meeting {
226 let mut cur = suffix_root.clone();
227 loop {
228 path.push(cur.clone());
229 let parent = &bwd_parent[&cur];
230 if *parent == cur {
231 break;
232 }
233 cur = parent.clone();
234 }
235 }
236 path
237}
238
239fn walk_to_root(start: &str, parents: &HashMap<String, String>) -> Vec<String> {
242 let mut chain = Vec::new();
243 let mut cur = start.to_string();
244 loop {
245 chain.push(cur.clone());
246 let parent = &parents[&cur];
247 if *parent == cur {
248 break;
249 }
250 cur = parent.clone();
251 }
252 chain
253}
254
255#[cfg(test)]
256mod tests {
257 use crate::csr::CsrIndex;
258 use crate::overlay_delta::GraphOverlayDelta;
259 use crate::path_params::ShortestPathParams;
260 use crate::traversal::DEFAULT_MAX_VISITED;
261
262 fn params<'a>(
263 src: &'a str,
264 dst: &'a str,
265 label_filter: Option<&'a str>,
266 max_depth: usize,
267 ) -> ShortestPathParams<'a> {
268 ShortestPathParams {
269 src,
270 dst,
271 label_filter,
272 max_depth,
273 max_visited: DEFAULT_MAX_VISITED,
274 frontier_bitmap: None,
275 }
276 }
277
278 #[test]
281 fn staged_edge_completes_path() {
282 let mut csr = CsrIndex::new();
283 csr.add_edge("a", "KNOWS", "b").unwrap();
284 let mut ov = GraphOverlayDelta::new();
285 ov.stage_edge("b", "KNOWS", "c");
286
287 let path = csr
288 .shortest_path(params("a", "c", Some("KNOWS"), 5), Some(&ov))
289 .expect("staged edge should complete the path");
290 assert_eq!(path, vec!["a", "b", "c"]);
291
292 assert!(
294 csr.shortest_path(params("a", "c", Some("KNOWS"), 5), None)
295 .is_none()
296 );
297 }
298
299 #[test]
303 fn path_through_staged_only_node() {
304 let csr = CsrIndex::new();
305 let mut ov = GraphOverlayDelta::new();
306 ov.stage_edge("a", "KNOWS", "x");
307 ov.stage_edge("x", "KNOWS", "d");
308
309 let path = csr
310 .shortest_path(params("a", "d", Some("KNOWS"), 5), Some(&ov))
311 .expect("staged-only path should be found");
312 assert_eq!(path, vec!["a", "x", "d"]);
313 }
314
315 #[test]
318 fn tombstone_forces_detour() {
319 let mut csr = CsrIndex::new();
321 csr.add_edge("a", "KNOWS", "d").unwrap();
322 csr.add_edge("a", "KNOWS", "b").unwrap();
323 csr.add_edge("b", "KNOWS", "d").unwrap();
324 let mut ov = GraphOverlayDelta::new();
325 ov.stage_tombstone("a", "KNOWS", "d");
326
327 let path = csr
328 .shortest_path(params("a", "d", Some("KNOWS"), 5), Some(&ov))
329 .expect("detour path should be found");
330 assert_eq!(path, vec!["a", "b", "d"]);
331 }
332
333 #[test]
335 fn tombstone_removes_only_path() {
336 let mut csr = CsrIndex::new();
337 csr.add_edge("a", "KNOWS", "d").unwrap();
338 let mut ov = GraphOverlayDelta::new();
339 ov.stage_tombstone("a", "KNOWS", "d");
340
341 assert!(
342 csr.shortest_path(params("a", "d", Some("KNOWS"), 5), Some(&ov))
343 .is_none()
344 );
345 }
346
347 #[test]
350 fn empty_overlay_matches_dense() {
351 let mut csr = CsrIndex::new();
352 csr.add_edge("a", "KNOWS", "b").unwrap();
353 csr.add_edge("b", "KNOWS", "c").unwrap();
354 csr.add_edge("c", "KNOWS", "d").unwrap();
355 let ov = GraphOverlayDelta::new();
356
357 let dense = csr
360 .shortest_path(params("a", "d", Some("KNOWS"), 10), None)
361 .unwrap();
362 let overlaid = csr.shortest_path_overlay(params("a", "d", Some("KNOWS"), 10), &ov);
363 assert_eq!(overlaid, Some(dense));
364 }
365
366 #[test]
367 fn src_equals_dst() {
368 let mut csr = CsrIndex::new();
369 csr.add_edge("a", "KNOWS", "b").unwrap();
370 let mut ov = GraphOverlayDelta::new();
371 ov.stage_edge("a", "KNOWS", "x");
372 let path = csr
373 .shortest_path(params("a", "a", None, 5), Some(&ov))
374 .unwrap();
375 assert_eq!(path, vec!["a"]);
376 }
377
378 #[test]
379 fn unreachable_is_none() {
380 let mut csr = CsrIndex::new();
381 csr.add_edge("a", "KNOWS", "b").unwrap();
382 let mut ov = GraphOverlayDelta::new();
383 ov.stage_edge("m", "KNOWS", "n");
384 assert!(
385 csr.shortest_path(params("a", "n", Some("KNOWS"), 5), Some(&ov))
386 .is_none()
387 );
388 }
389}