1use std::collections::HashMap;
26use std::collections::hash_map::Entry;
27
28use crate::csr::CsrIndex;
29use crate::csr::index::LabelFilter;
30use crate::overlay_delta::GraphOverlayDelta;
31use crate::path_params::ShortestPathParams;
32use crate::traversal_overlay::node_present;
33
34pub(crate) fn shortest_path_overlay(
44 partition: Option<&CsrIndex>,
45 params: ShortestPathParams<'_>,
46 overlay: &GraphOverlayDelta,
47) -> Option<Vec<String>> {
48 let ShortestPathParams {
49 src,
50 dst,
51 label_filter,
52 max_depth,
53 max_visited,
54 frontier_bitmap,
55 } = params;
56 if src == dst {
57 return node_present(partition, overlay, src).then(|| vec![src.to_string()]);
58 }
59
60 let mut fwd_parent: HashMap<String, String> = HashMap::new();
63 let mut bwd_parent: HashMap<String, String> = HashMap::new();
64 fwd_parent.insert(src.to_string(), src.to_string());
65 bwd_parent.insert(dst.to_string(), dst.to_string());
66
67 let mut fwd_frontier: Vec<String> = vec![src.to_string()];
68 let mut bwd_frontier: Vec<String> = vec![dst.to_string()];
69
70 let gate = PathGate::new(partition, label_filter, frontier_bitmap, [src, dst]);
71 let within_depth =
72 |path: Vec<String>| (path.len().saturating_sub(1) <= max_depth).then_some(path);
73
74 for _round in 0..max_depth.div_ceil(2) {
77 if fwd_parent.len() + bwd_parent.len() >= max_visited {
78 break;
79 }
80
81 let mut candidates: Vec<(String, String)> = Vec::new();
84 for node in std::mem::take(&mut fwd_frontier) {
85 for neighbor in forward_neighbors(&node, &gate, overlay) {
86 candidates.push((neighbor, node.clone()));
87 }
88 }
89 candidates.sort();
90 let mut next_fwd = Vec::new();
91 for (neighbor, node) in candidates {
92 if let Some(meeting) = relax(
93 &neighbor,
94 &node,
95 &mut fwd_parent,
96 &bwd_parent,
97 &mut next_fwd,
98 ) {
99 return within_depth(reconstruct(&meeting, &fwd_parent, &bwd_parent));
100 }
101 }
102 fwd_frontier = next_fwd;
103
104 let mut candidates: Vec<(String, String)> = Vec::new();
105 for node in std::mem::take(&mut bwd_frontier) {
106 for neighbor in backward_neighbors(&node, &gate, overlay) {
107 candidates.push((neighbor, node.clone()));
108 }
109 }
110 candidates.sort();
111 let mut next_bwd = Vec::new();
112 for (neighbor, node) in candidates {
113 if let Some(meeting) = relax(
114 &neighbor,
115 &node,
116 &mut bwd_parent,
117 &fwd_parent,
118 &mut next_bwd,
119 ) {
120 return within_depth(reconstruct(&meeting, &fwd_parent, &bwd_parent));
121 }
122 }
123 bwd_frontier = next_bwd;
124
125 if fwd_frontier.is_empty() && bwd_frontier.is_empty() {
126 break;
127 }
128 }
129 None
130}
131
132fn forward_neighbors(node: &str, gate: &PathGate<'_>, overlay: &GraphOverlayDelta) -> Vec<String> {
135 let mut out = Vec::new();
136 if let Some((csr, durable_labels)) = gate.durable.as_ref()
137 && let Some(&node_id) = csr.node_to_id.get(node)
138 {
139 csr.record_access(node_id);
140 for (lid, dst) in csr.dense_iter_out(node_id) {
141 if !durable_labels.keeps(lid) {
142 continue;
143 }
144 let dst_name = &csr.id_to_node[dst as usize];
145 if overlay.is_tombstoned(node, csr.label_name(lid), dst_name) {
146 continue;
147 }
148 if !gate.admits_durable(csr, dst, dst_name) {
149 continue;
150 }
151 out.push(dst_name.clone());
152 }
153 }
154 out.extend(
155 overlay
156 .out_neighbors(node, gate.labels)
157 .map(|(_, dst)| dst.to_string()),
158 );
159 out
160}
161
162fn backward_neighbors(node: &str, gate: &PathGate<'_>, overlay: &GraphOverlayDelta) -> Vec<String> {
165 let mut out = Vec::new();
166 if let Some((csr, durable_labels)) = gate.durable.as_ref()
167 && let Some(&node_id) = csr.node_to_id.get(node)
168 {
169 csr.record_access(node_id);
170 for (lid, src) in csr.dense_iter_in(node_id) {
171 if !durable_labels.keeps(lid) {
172 continue;
173 }
174 let src_name = &csr.id_to_node[src as usize];
175 if overlay.is_tombstoned(src_name, csr.label_name(lid), node) {
176 continue;
177 }
178 if !gate.admits_durable(csr, src, src_name) {
179 continue;
180 }
181 out.push(src_name.clone());
182 }
183 }
184 out.extend(
185 overlay
186 .in_neighbors(node, gate.labels)
187 .map(|(_, src)| src.to_string()),
188 );
189 out
190}
191
192struct PathGate<'a> {
194 labels: &'a [&'a str],
196 durable: Option<(&'a CsrIndex, LabelFilter)>,
200 frontier_bitmap: Option<&'a nodedb_types::SurrogateBitmap>,
201 endpoints: [&'a str; 2],
203}
204
205impl<'a> PathGate<'a> {
206 fn new(
207 partition: Option<&'a CsrIndex>,
208 label_filter: &'a [&'a str],
209 frontier_bitmap: Option<&'a nodedb_types::SurrogateBitmap>,
210 endpoints: [&'a str; 2],
211 ) -> Self {
212 Self {
213 labels: label_filter,
214 durable: partition.map(|csr| (csr, csr.label_filter(label_filter))),
215 frontier_bitmap,
216 endpoints,
217 }
218 }
219
220 fn admits_durable(&self, csr: &CsrIndex, id: u32, name: &str) -> bool {
223 self.endpoints.contains(&name)
224 || self.frontier_bitmap.is_none_or(|bm| {
225 bm.contains(nodedb_types::Surrogate::new(csr.node_surrogate_raw(id)))
226 })
227 }
228}
229
230fn relax(
237 neighbor: &str,
238 parent: &str,
239 this_parent: &mut HashMap<String, String>,
240 other_parent: &HashMap<String, String>,
241 next_frontier: &mut Vec<String>,
242) -> Option<String> {
243 if let Entry::Vacant(e) = this_parent.entry(neighbor.to_string()) {
244 e.insert(parent.to_string());
245 next_frontier.push(neighbor.to_string());
246 }
247 if other_parent.contains_key(neighbor) {
248 return Some(neighbor.to_string());
249 }
250 None
251}
252
253fn reconstruct(
257 meeting: &str,
258 fwd_parent: &HashMap<String, String>,
259 bwd_parent: &HashMap<String, String>,
260) -> Vec<String> {
261 let mut path = walk_to_root(meeting, fwd_parent);
262 path.reverse();
263
264 let suffix_root = &bwd_parent[meeting];
265 if suffix_root != meeting {
266 let mut cur = suffix_root.clone();
267 loop {
268 path.push(cur.clone());
269 let parent = &bwd_parent[&cur];
270 if *parent == cur {
271 break;
272 }
273 cur = parent.clone();
274 }
275 }
276 path
277}
278
279fn walk_to_root(start: &str, parents: &HashMap<String, String>) -> Vec<String> {
282 let mut chain = Vec::new();
283 let mut cur = start.to_string();
284 loop {
285 chain.push(cur.clone());
286 let parent = &parents[&cur];
287 if *parent == cur {
288 break;
289 }
290 cur = parent.clone();
291 }
292 chain
293}
294
295#[cfg(test)]
296mod tests {
297 use crate::csr::CsrIndex;
298 use crate::overlay_delta::GraphOverlayDelta;
299 use crate::path_params::ShortestPathParams;
300 use crate::test_support::test_memory;
301 use crate::traversal::DEFAULT_MAX_VISITED;
302
303 fn params<'a>(
304 src: &'a str,
305 dst: &'a str,
306 label_filter: &'a [&'a str],
307 max_depth: usize,
308 ) -> ShortestPathParams<'a> {
309 ShortestPathParams {
310 src,
311 dst,
312 label_filter,
313 max_depth,
314 max_visited: DEFAULT_MAX_VISITED,
315 frontier_bitmap: None,
316 }
317 }
318
319 #[test]
322 fn the_frontier_bitmap_never_gates_the_endpoints() {
323 use nodedb_types::{Surrogate, SurrogateBitmap};
324
325 let mut csr = CsrIndex::new(test_memory());
326 csr.add_edge("a", "KNOWS", "c").unwrap();
327 csr.set_node_surrogate("a", Surrogate::new(1));
328 csr.set_node_surrogate("c", Surrogate::new(3));
329 let mut bm = SurrogateBitmap::new();
330 bm.insert(Surrogate::new(99));
331 let mut ov = GraphOverlayDelta::new();
332 ov.stage_edge("x", "KNOWS", "y");
333
334 let mut p = params("a", "c", &["KNOWS"], 1);
335 p.frontier_bitmap = Some(&bm);
336 assert_eq!(
337 csr.shortest_path(p, Some(&ov)),
338 Some(vec!["a".to_string(), "c".to_string()])
339 );
340 }
341
342 #[test]
345 fn staged_edge_completes_path() {
346 let mut csr = CsrIndex::new(test_memory());
347 csr.add_edge("a", "KNOWS", "b").unwrap();
348 let mut ov = GraphOverlayDelta::new();
349 ov.stage_edge("b", "KNOWS", "c");
350
351 let path = csr
352 .shortest_path(params("a", "c", &["KNOWS"], 5), Some(&ov))
353 .expect("staged edge should complete the path");
354 assert_eq!(path, vec!["a", "b", "c"]);
355
356 assert!(
358 csr.shortest_path(params("a", "c", &["KNOWS"], 5), None)
359 .is_none()
360 );
361 }
362
363 #[test]
367 fn path_through_staged_only_node() {
368 let csr = CsrIndex::new(test_memory());
369 let mut ov = GraphOverlayDelta::new();
370 ov.stage_edge("a", "KNOWS", "x");
371 ov.stage_edge("x", "KNOWS", "d");
372
373 let path = csr
374 .shortest_path(params("a", "d", &["KNOWS"], 5), Some(&ov))
375 .expect("staged-only path should be found");
376 assert_eq!(path, vec!["a", "x", "d"]);
377 }
378
379 #[test]
382 fn tombstone_forces_detour() {
383 let mut csr = CsrIndex::new(test_memory());
385 csr.add_edge("a", "KNOWS", "d").unwrap();
386 csr.add_edge("a", "KNOWS", "b").unwrap();
387 csr.add_edge("b", "KNOWS", "d").unwrap();
388 let mut ov = GraphOverlayDelta::new();
389 ov.stage_tombstone("a", "KNOWS", "d");
390
391 let path = csr
392 .shortest_path(params("a", "d", &["KNOWS"], 5), Some(&ov))
393 .expect("detour path should be found");
394 assert_eq!(path, vec!["a", "b", "d"]);
395 }
396
397 #[test]
399 fn tombstone_removes_only_path() {
400 let mut csr = CsrIndex::new(test_memory());
401 csr.add_edge("a", "KNOWS", "d").unwrap();
402 let mut ov = GraphOverlayDelta::new();
403 ov.stage_tombstone("a", "KNOWS", "d");
404
405 assert!(
406 csr.shortest_path(params("a", "d", &["KNOWS"], 5), Some(&ov))
407 .is_none()
408 );
409 }
410
411 #[test]
414 fn empty_overlay_matches_dense() {
415 let mut csr = CsrIndex::new(test_memory());
416 csr.add_edge("a", "KNOWS", "b").unwrap();
417 csr.add_edge("b", "KNOWS", "c").unwrap();
418 csr.add_edge("c", "KNOWS", "d").unwrap();
419 let ov = GraphOverlayDelta::new();
420
421 let dense = csr
424 .shortest_path(params("a", "d", &["KNOWS"], 10), None)
425 .unwrap();
426 let overlaid =
427 super::shortest_path_overlay(Some(&csr), params("a", "d", &["KNOWS"], 10), &ov);
428 assert_eq!(overlaid, Some(dense));
429 }
430
431 #[test]
434 fn a_staged_edge_under_the_second_label_of_a_set_is_followed() {
435 let mut csr = CsrIndex::new(test_memory());
436 csr.add_edge("a", "FIRST", "b").unwrap();
437 let mut ov = GraphOverlayDelta::new();
438 ov.stage_edge("b", "SECOND", "d");
439 ov.stage_edge("b", "OTHER", "x");
440
441 assert_eq!(
442 csr.shortest_path(params("a", "d", &["FIRST", "SECOND"], 5), Some(&ov)),
443 Some(vec!["a".to_string(), "b".to_string(), "d".to_string()])
444 );
445 assert!(
446 csr.shortest_path(params("a", "x", &["FIRST", "SECOND"], 5), Some(&ov))
447 .is_none()
448 );
449 }
450
451 #[test]
452 fn src_equals_dst() {
453 let mut csr = CsrIndex::new(test_memory());
454 csr.add_edge("a", "KNOWS", "b").unwrap();
455 let mut ov = GraphOverlayDelta::new();
456 ov.stage_edge("a", "KNOWS", "x");
457 let path = csr
458 .shortest_path(params("a", "a", &[], 5), Some(&ov))
459 .unwrap();
460 assert_eq!(path, vec!["a"]);
461 }
462
463 #[test]
467 fn src_equals_dst_on_an_absent_node_is_none() {
468 let mut csr = CsrIndex::new(test_memory());
469 csr.add_edge("a", "KNOWS", "b").unwrap();
470 let mut ov = GraphOverlayDelta::new();
471 ov.stage_edge("x", "KNOWS", "y");
472 assert!(
473 csr.shortest_path(params("ghost", "ghost", &[], 5), Some(&ov))
474 .is_none()
475 );
476 assert!(
477 csr.shortest_path(params("ghost", "ghost", &[], 5), None)
478 .is_none()
479 );
480 assert_eq!(
481 csr.shortest_path(params("y", "y", &[], 5), Some(&ov)),
482 Some(vec!["y".to_string()])
483 );
484 }
485
486 #[test]
488 fn a_missing_partition_walks_staged_edges() {
489 let mut ov = GraphOverlayDelta::new();
490 ov.stage_edge("a", "KNOWS", "x");
491 ov.stage_edge("x", "KNOWS", "d");
492 assert_eq!(
493 CsrIndex::shortest_path_on(None, params("a", "d", &["KNOWS"], 5), Some(&ov)),
494 Some(vec!["a".to_string(), "x".to_string(), "d".to_string()])
495 );
496 assert!(CsrIndex::shortest_path_on(None, params("a", "d", &[], 5), None).is_none());
497 assert!(
498 CsrIndex::shortest_path_on(None, params("ghost", "ghost", &[], 5), Some(&ov)).is_none()
499 );
500 }
501
502 #[test]
503 fn unreachable_is_none() {
504 let mut csr = CsrIndex::new(test_memory());
505 csr.add_edge("a", "KNOWS", "b").unwrap();
506 let mut ov = GraphOverlayDelta::new();
507 ov.stage_edge("m", "KNOWS", "n");
508 assert!(
509 csr.shortest_path(params("a", "n", &["KNOWS"], 5), Some(&ov))
510 .is_none()
511 );
512 }
513}