1use std::cmp::Ordering;
2use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
3use std::sync::Arc;
4
5use crate::executor::expression::evaluator::traits::ExpressionContext;
6use crate::executor::expression::evaluator::ExpressionEvaluator;
7use crate::executor::streaming::context::ValueRowContext;
8use crate::executor::streaming::query_registry::CancelToken;
9use crate::executor::streaming::slot::SlotLayout;
10use crate::executor::traversal::graph_reader::{edge_key, EdgeKey};
11use crate::planning::plan::core::nodes::traversal::path_algorithms::PathUniqueness;
12use crate::storage::QueryStorage;
13use linkrs_core::error::QueryError;
14use linkrs_core::types::expr::Expression;
15use linkrs_core::types::storage_ids::VertexId;
16use linkrs_core::{EdgeDirection, NPath, Path, Value, Vertex};
17
18pub(crate) fn materialize_path_endpoint(
26 storage: &dyn QueryStorage,
27 space_name: &str,
28 value: &Value,
29 override_tag: &str,
30 role_tag: Option<&str>,
31) -> Result<Option<Vertex>, QueryError> {
32 if let Value::Vertex(vertex) = value {
33 return Ok(Some((**vertex).clone()));
34 }
35 let Ok(vid) = VertexId::try_from(value) else {
36 return Ok(None);
37 };
38 let tag = if !override_tag.is_empty() {
39 override_tag.to_string()
40 } else if let Some(tag) = role_tag {
41 tag.to_string()
42 } else {
43 return Err(QueryError::execution(
44 "Path endpoint is a bare id: OVER must list edge types whose schema resolves one label per path endpoint"
45 .to_string(),
46 ));
47 };
48 storage
49 .get_vertex(space_name, &tag, &vid)
50 .map_err(|error| QueryError::execution(format!("Failed to read path endpoint: {error}")))
51}
52
53pub(crate) fn path_endpoint_tags(
64 storage: &dyn QueryStorage,
65 space_name: &str,
66 edge_types: &[String],
67 direction: EdgeDirection,
68 override_tag: &str,
69) -> Option<(String, String)> {
70 if !override_tag.is_empty() {
71 return Some((override_tag.to_string(), override_tag.to_string()));
72 }
73 if edge_types.is_empty() {
74 return None;
75 }
76 let mut start_tags: Vec<String> = Vec::new();
77 let mut end_tags: Vec<String> = Vec::new();
78 for edge_type in edge_types {
79 let (src_tag, dst_tag) =
80 crate::executor::traversal::graph_reader::resolve_edge_endpoint_tags(
81 storage, space_name, edge_type,
82 )?;
83 let (start, end) = match direction {
84 EdgeDirection::Out => (src_tag, dst_tag),
85 EdgeDirection::In => (dst_tag, src_tag),
86 EdgeDirection::Both => {
87 if src_tag != dst_tag {
88 return None;
89 }
90 (src_tag.clone(), dst_tag)
91 }
92 };
93 push_unique_label(&mut start_tags, start);
94 push_unique_label(&mut end_tags, end);
95 }
96 match (start_tags.as_slice(), end_tags.as_slice()) {
97 ([start], [end]) => Some((start.clone(), end.clone())),
98 _ => None,
99 }
100}
101
102fn push_unique_label(labels: &mut Vec<String>, label: String) {
103 if !label.is_empty() && !labels.iter().any(|kept| kept == &label) {
104 labels.push(label);
105 }
106}
107
108pub(crate) struct BidirBfsConfig<'a> {
109 pub(crate) space_name: &'a str,
110 pub(crate) vertex_tag: &'a str,
111 pub(crate) edge_type_filter: Option<&'a [String]>,
112 pub(crate) max_depth: usize,
113 pub(crate) single_shortest: bool,
114 pub(crate) limit: usize,
115 pub(crate) direction: EdgeDirection,
116}
117
118pub(crate) fn path_endpoint_pairs(
119 row: &[Value],
120 layout: Arc<SlotLayout>,
121 start_vertices: &[Value],
122 target_vertices: &[Value],
123 target_expression: Option<&Expression>,
124) -> Result<Vec<(Value, Value)>, QueryError> {
125 let context = ValueRowContext::new(row.to_vec(), layout.clone());
126 let row_start = context.get_variable("vid").or_else(|| row.first().cloned());
127 let row_target = if let Some(expression) = target_expression {
128 let mut expression_context = ValueRowContext::new(row.to_vec(), layout);
129 Some(
130 ExpressionEvaluator::evaluate(expression, &mut expression_context).map_err(
131 |error| QueryError::execution(format!("Path target evaluation failed: {error}")),
132 )?,
133 )
134 } else {
135 context
136 .get_variable("dst_vid")
137 .or_else(|| row.get(1).cloned())
138 };
139 let starts: Vec<Value> = if start_vertices.is_empty() {
140 row_start.into_iter().collect()
141 } else {
142 start_vertices.to_vec()
143 };
144 let targets: Vec<Value> = if target_vertices.is_empty() {
145 row_target.into_iter().collect()
146 } else {
147 target_vertices.to_vec()
148 };
149 Ok(starts
150 .into_iter()
151 .flat_map(|start| {
152 targets
153 .iter()
154 .cloned()
155 .map(move |target| (start.clone(), target))
156 })
157 .collect())
158}
159
160pub(crate) fn bidir_bfs_shortest_path(
161 storage: &dyn QueryStorage,
162 start_vertex: &Vertex,
163 end_vertex: &Vertex,
164 cfg: BidirBfsConfig,
165 cancel_token: Option<&CancelToken>,
166) -> Result<Vec<Path>, QueryError> {
167 let mut result_paths = Vec::new();
168 let mut best_len: Option<usize> = None;
169 let mut seen_paths: HashSet<String> = HashSet::new();
170 let mut left_depth: usize = 0;
171 let mut right_depth: usize = 0;
172
173 let mut left_visited: HashMap<VertexId, Arc<NPath>> = HashMap::new();
174 let mut right_visited: HashMap<VertexId, Arc<NPath>> = HashMap::new();
175 let mut left_queue: VecDeque<(VertexId, Arc<NPath>)> = VecDeque::new();
176 let mut right_queue: VecDeque<(VertexId, Arc<NPath>)> = VecDeque::new();
177
178 let start_id = start_vertex.vid;
179 let end_id = end_vertex.vid;
180 let np = Arc::new(NPath::new(Arc::new(start_vertex.clone())));
181 left_queue.push_back((start_id, np.clone()));
182 left_visited.insert(start_id, np);
183 let np = Arc::new(NPath::new(Arc::new(end_vertex.clone())));
184 right_queue.push_back((end_id, np.clone()));
185 right_visited.insert(end_id, np);
186
187 let dir_out = EdgeDirection::Out;
188 let dir_in = EdgeDirection::In;
189 let forward_dir = match cfg.direction {
190 EdgeDirection::Out => dir_out,
191 EdgeDirection::In => dir_in,
192 EdgeDirection::Both => dir_out,
193 };
194 let backward_dir = match cfg.direction {
195 EdgeDirection::Out => dir_in,
196 EdgeDirection::In => dir_out,
197 EdgeDirection::Both => dir_in,
198 };
199
200 while !left_queue.is_empty() && !right_queue.is_empty() {
201 if let Some(token) = cancel_token {
202 if token.is_cancelled() {
203 return Err(QueryError::execution("Query cancelled".to_string()));
204 }
205 }
206 if cfg.single_shortest && !result_paths.is_empty() {
207 break;
208 }
209 if result_paths.len() >= cfg.limit {
210 break;
211 }
212 if !cfg.single_shortest {
216 if let Some(best) = best_len {
217 if best <= left_depth && best <= right_depth {
218 break;
219 }
220 }
221 }
222
223 let left_level = left_queue.len();
224 let mut left_next: Vec<(VertexId, Arc<NPath>)> = Vec::new();
225 for _ in 0..left_level {
226 if let Some((current_id, current_npath)) = left_queue.pop_front() {
227 if current_npath.len() >= cfg.max_depth {
228 continue;
229 }
230 let edges = storage.get_node_edges(
231 cfg.space_name,
232 ¤t_id,
233 forward_dir,
234 cfg.edge_type_filter.unwrap_or(&[]),
235 )?;
236 for edge in &edges {
237 let neighbor_id = edge.dst();
238 if left_visited.contains_key(neighbor_id) {
239 continue;
240 }
241 let Some(neighbor_tag) =
242 crate::executor::traversal::graph_reader::resolve_neighbor_tag(
243 storage,
244 cfg.space_name,
245 edge,
246 neighbor_id,
247 cfg.vertex_tag,
248 )
249 else {
250 continue;
251 };
252 if let Some(neighbor_vertex) =
256 storage.get_vertex(cfg.space_name, &neighbor_tag, neighbor_id)?
257 {
258 let new_npath = Arc::new(NPath::extend(
259 current_npath.clone(),
260 Arc::new((*edge).clone()),
261 Arc::new(neighbor_vertex),
262 ));
263 left_next.push((*neighbor_id, new_npath.clone()));
264 left_visited.insert(*neighbor_id, new_npath);
265 }
266 }
267 }
268 }
269 for (id, np) in left_next.drain(..) {
270 left_queue.push_back((id, np));
271 }
272 left_depth += 1;
273
274 if cfg.single_shortest && !result_paths.is_empty() {
275 break;
276 }
277 if result_paths.len() >= cfg.limit {
278 break;
279 }
280 if !cfg.single_shortest {
281 if let Some(best) = best_len {
282 if best <= left_depth && best <= right_depth {
283 break;
284 }
285 }
286 }
287
288 let right_level = right_queue.len();
289 let mut right_next: Vec<(VertexId, Arc<NPath>)> = Vec::new();
290 for _ in 0..right_level {
291 if let Some((current_id, current_npath)) = right_queue.pop_front() {
292 if current_npath.len() >= cfg.max_depth {
293 continue;
294 }
295
296 if let Some(left_npath) = left_visited.get(¤t_id) {
297 let total_len = left_npath.len() + current_npath.len();
298 if total_len <= cfg.max_depth {
299 let mut left_path = left_npath.to_path();
300 let mut right_path = current_npath.to_path();
301 right_path.reverse();
302 left_path.steps.extend(right_path.steps);
303 if push_shortest_path(
304 left_path,
305 &mut best_len,
306 &mut seen_paths,
307 &mut result_paths,
308 cfg.limit,
309 ) {
310 break;
311 }
312 }
313 continue;
314 }
315
316 let edges = storage.get_node_edges(
317 cfg.space_name,
318 ¤t_id,
319 backward_dir,
320 cfg.edge_type_filter.unwrap_or(&[]),
321 )?;
322 for edge in &edges {
323 let neighbor_id = if edge.dst() == ¤t_id {
324 edge.src()
325 } else {
326 edge.dst()
327 };
328 if right_visited.contains_key(neighbor_id) {
329 continue;
330 }
331 let Some(neighbor_tag) =
332 crate::executor::traversal::graph_reader::resolve_neighbor_tag(
333 storage,
334 cfg.space_name,
335 edge,
336 neighbor_id,
337 cfg.vertex_tag,
338 )
339 else {
340 continue;
341 };
342 if let Some(neighbor_vertex) =
343 storage.get_vertex(cfg.space_name, &neighbor_tag, neighbor_id)?
344 {
345 let mut backward_edge = (*edge).clone();
349 std::mem::swap(&mut backward_edge.src, &mut backward_edge.dst);
350 let new_npath = Arc::new(NPath::extend(
351 current_npath.clone(),
352 Arc::new(backward_edge),
353 Arc::new(neighbor_vertex),
354 ));
355 right_next.push((*neighbor_id, new_npath.clone()));
356 right_visited.insert(*neighbor_id, new_npath);
357 }
358 }
359 }
360 }
361 for (id, np) in right_next.drain(..) {
362 if let Some(left_npath) = left_visited.get(&id) {
363 let total_len = left_npath.len() + np.len();
364 if total_len <= cfg.max_depth {
365 let mut left_path = left_npath.to_path();
366 let mut right_path = np.to_path();
367 right_path.reverse();
368 left_path.steps.extend(right_path.steps);
369 if push_shortest_path(
370 left_path,
371 &mut best_len,
372 &mut seen_paths,
373 &mut result_paths,
374 cfg.limit,
375 ) {
376 break;
377 }
378 }
379 } else {
380 right_queue.push_back((id, np));
381 }
382 }
383 right_depth += 1;
384 }
385
386 if cfg.single_shortest && !result_paths.is_empty() {
387 result_paths.sort_by_key(|a| a.steps.len());
388 result_paths.truncate(1);
389 }
390 result_paths.truncate(cfg.limit);
391 Ok(result_paths)
392}
393
394fn push_shortest_path(
398 path: Path,
399 best_len: &mut Option<usize>,
400 seen: &mut HashSet<String>,
401 result: &mut Vec<Path>,
402 limit: usize,
403) -> bool {
404 let total = path.steps.len();
405 match *best_len {
406 Some(best) if total > best => return false,
407 Some(best) if total < best => {
408 *best_len = Some(total);
409 result.clear();
410 }
411 None => *best_len = Some(total),
412 Some(_) => {}
413 }
414 if seen.insert(format!("{:?}", path)) {
415 result.push(path);
416 }
417 result.len() >= limit
418}
419
420pub(crate) struct AllPathsConfig<'a> {
421 pub(crate) space_name: &'a str,
422 pub(crate) vertex_tag: &'a str,
423 pub(crate) edge_types: &'a [String],
424 pub(crate) direction: EdgeDirection,
425 pub(crate) min_depth: usize,
426 pub(crate) max_depth: usize,
427 pub(crate) path_uniqueness: PathUniqueness,
428 pub(crate) result_cap: usize,
429}
430
431struct WeightedPathItem {
435 cost: f64,
436 depth: usize,
437 vertex_id: VertexId,
438 npath: Arc<NPath>,
439}
440
441impl PartialEq for WeightedPathItem {
442 fn eq(&self, other: &Self) -> bool {
443 self.cost == other.cost && self.depth == other.depth
444 }
445}
446impl Eq for WeightedPathItem {}
447impl PartialOrd for WeightedPathItem {
448 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
449 Some(self.cmp(other))
450 }
451}
452impl Ord for WeightedPathItem {
453 fn cmp(&self, other: &Self) -> Ordering {
454 other
455 .cost
456 .partial_cmp(&self.cost)
457 .unwrap_or(Ordering::Equal)
458 .then_with(|| other.depth.cmp(&self.depth))
459 }
460}
461
462pub(crate) fn weighted_shortest_path(
470 storage: &dyn QueryStorage,
471 start_vertex: &Vertex,
472 end_vertex: &Vertex,
473 space_name: &str,
474 vertex_tag: &str,
475 edge_type_filter: Option<&[String]>,
476 direction: EdgeDirection,
477 max_depth: usize,
478 weight_prop: &str,
479 cancel_token: Option<&CancelToken>,
480) -> Result<Option<Path>, QueryError> {
481 if let Some(token) = cancel_token {
482 if token.is_cancelled() {
483 return Err(QueryError::execution("Query cancelled".to_string()));
484 }
485 }
486
487 let start_id = start_vertex.vid;
488 let end_id = end_vertex.vid;
489
490 let mut finalized: HashSet<VertexId> = HashSet::new();
491 let mut dist: HashMap<VertexId, f64> = HashMap::new();
492 let mut heap: BinaryHeap<WeightedPathItem> = BinaryHeap::new();
493 let np = Arc::new(NPath::new(Arc::new(start_vertex.clone())));
494 dist.insert(start_id, 0.0);
495 heap.push(WeightedPathItem {
496 cost: 0.0,
497 depth: 0,
498 vertex_id: start_id,
499 npath: np,
500 });
501
502 while let Some(top) = heap.pop() {
503 if let Some(token) = cancel_token {
504 if token.is_cancelled() {
505 return Err(QueryError::execution("Query cancelled".to_string()));
506 }
507 }
508 if finalized.contains(&top.vertex_id) {
510 continue;
511 }
512 finalized.insert(top.vertex_id);
513
514 if top.vertex_id == end_id {
515 return Ok(Some(top.npath.to_path()));
516 }
517 if top.depth >= max_depth {
518 continue;
519 }
520
521 let edges = storage.get_node_edges(
522 space_name,
523 &top.vertex_id,
524 direction,
525 edge_type_filter.unwrap_or(&[]),
526 )?;
527 for edge in &edges {
528 let neighbor_id = if direction == EdgeDirection::In {
529 edge.src()
530 } else if direction == EdgeDirection::Out {
531 edge.dst()
532 } else if edge.dst() == &top.vertex_id {
533 edge.src()
534 } else {
535 edge.dst()
536 };
537 if finalized.contains(neighbor_id) {
538 continue;
539 }
540 let next_cost = top.cost
541 + crate::executor::traversal::weighted::read_edge_weight(edge, weight_prop);
542 let better = match dist.get(neighbor_id) {
543 Some(old) => next_cost < *old,
544 None => true,
545 };
546 if !better {
547 continue;
548 }
549 let Some(neighbor_tag) = crate::executor::traversal::graph_reader::resolve_neighbor_tag(
550 storage,
551 space_name,
552 edge,
553 neighbor_id,
554 vertex_tag,
555 ) else {
556 continue;
557 };
558 if let Some(neighbor_vertex) =
559 storage.get_vertex(space_name, &neighbor_tag, neighbor_id)?
560 {
561 dist.insert(*neighbor_id, next_cost);
562 heap.push(WeightedPathItem {
563 cost: next_cost,
564 depth: top.depth + 1,
565 vertex_id: *neighbor_id,
566 npath: Arc::new(NPath::extend(
567 top.npath.clone(),
568 Arc::new((*edge).clone()),
569 Arc::new(neighbor_vertex),
570 )),
571 });
572 }
573 }
574 }
575 Ok(None)
576}
577
578pub(crate) fn enumerate_all_paths(
579 storage: &dyn QueryStorage,
580 start_vertex: &Vertex,
581 end_vertex: &Vertex,
582 cfg: AllPathsConfig<'_>,
583 cancel_token: Option<&CancelToken>,
584) -> Result<Vec<Path>, QueryError> {
585 let start_id = start_vertex.vid;
586 let end_id = end_vertex.vid;
587 let mut stack = vec![WalkState {
588 current_id: start_id,
589 path: Arc::new(NPath::new(Arc::new(start_vertex.clone()))),
590 visited_vertices: HashSet::from([start_id]),
591 visited_edges: HashSet::new(),
592 }];
593 let mut paths = Vec::new();
594
595 while let Some(state) = stack.pop() {
596 if let Some(token) = cancel_token {
597 if token.is_cancelled() {
598 return Err(QueryError::execution("Query cancelled".to_string()));
599 }
600 }
601 let depth = state.path.len();
602 if state.current_id == end_id && depth >= cfg.min_depth {
603 paths.push(state.path.to_path());
604 if paths.len() >= cfg.result_cap {
605 break;
606 }
607 }
608 if depth >= cfg.max_depth {
609 continue;
610 }
611 let edges = storage
612 .get_node_edges(
613 cfg.space_name,
614 &state.current_id,
615 cfg.direction,
616 cfg.edge_types,
617 )
618 .map_err(|error| {
619 QueryError::execution(format!("Failed to read path edges: {error}"))
620 })?;
621 for edge in edges {
622 let next_id = match cfg.direction {
623 EdgeDirection::Out => *edge.dst(),
624 EdgeDirection::In => *edge.src(),
625 EdgeDirection::Both => {
626 if edge.src() == &state.current_id {
627 *edge.dst()
628 } else {
629 *edge.src()
630 }
631 }
632 };
633 let is_self_loop = edge.src() == edge.dst();
637 let repeats_vertex = state.visited_vertices.contains(&next_id);
638 let key = edge_key(&edge);
639 match cfg.path_uniqueness {
640 PathUniqueness::VertexUnique => {
641 if repeats_vertex {
642 continue;
643 }
644 }
645 PathUniqueness::AllowSelfLoop => {
646 if repeats_vertex && !is_self_loop {
647 continue;
648 }
649 }
650 PathUniqueness::EdgeUnique => {
651 if state.visited_edges.contains(&key) {
652 continue;
653 }
654 }
655 PathUniqueness::Free => {}
656 }
657 let terminal_extension = is_self_loop
664 && matches!(
665 cfg.path_uniqueness,
666 PathUniqueness::AllowSelfLoop | PathUniqueness::Free
667 );
668 if state.current_id == end_id && !terminal_extension {
673 continue;
674 }
675 let Some(neighbor_tag) = crate::executor::traversal::graph_reader::resolve_neighbor_tag(
676 storage,
677 cfg.space_name,
678 &edge,
679 &next_id,
680 cfg.vertex_tag,
681 ) else {
682 continue;
683 };
684 let Some(vertex) = storage
685 .get_vertex(cfg.space_name, &neighbor_tag, &next_id)
686 .map_err(|error| {
687 QueryError::execution(format!("Failed to read path vertex: {error}"))
688 })?
689 else {
690 continue;
691 };
692 let next_path = Arc::new(NPath::extend(
693 state.path.clone(),
694 Arc::new(edge),
695 Arc::new(vertex),
696 ));
697 if terminal_extension {
698 if next_id == end_id && depth + 1 >= cfg.min_depth {
699 paths.push(next_path.to_path());
700 if paths.len() >= cfg.result_cap {
701 break;
702 }
703 }
704 continue;
705 }
706 let mut next_vertices = state.visited_vertices.clone();
707 next_vertices.insert(next_id);
708 let mut next_edges = state.visited_edges.clone();
709 next_edges.insert(key);
710 stack.push(WalkState {
711 current_id: next_id,
712 path: next_path,
713 visited_vertices: next_vertices,
714 visited_edges: next_edges,
715 });
716 }
717 }
718 Ok(paths)
719}
720
721struct WalkState {
725 current_id: VertexId,
726 path: Arc<NPath>,
727 visited_vertices: HashSet<VertexId>,
728 visited_edges: HashSet<EdgeKey>,
729}