1use crate::executor::expression::evaluation_context::graph_storage::GraphStorageRef;
7use crate::executor::expression::ExpressionError;
8use linkrs_core::types::VertexId;
9use linkrs_core::value::list::List;
10use linkrs_core::value::NullType;
11use linkrs_core::Value;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum GraphFunction {
16 Id,
17 Tags,
18 Labels,
19 Properties,
20 EdgeType,
21 Src,
22 Dst,
23 Rank,
24 StartNode,
25 EndNode,
26 Neighbors,
27 Degree,
28 OutEdges,
29 InEdges,
30 ShortestPath,
31 Bfs,
32 ConnectedComponents,
33 VariableLengthPath,
34 PageRank,
35}
36
37impl GraphFunction {
38 pub fn name(&self) -> &str {
40 match self {
41 Self::Id => "id",
42 Self::Tags => "tags",
43 Self::Labels => "labels",
44 Self::Properties => "properties",
45 Self::EdgeType => "type",
46 Self::Src => "src",
47 Self::Dst => "dst",
48 Self::Rank => "rank",
49 Self::StartNode => "startnode",
50 Self::EndNode => "endnode",
51 Self::Neighbors => "neighbors",
52 Self::Degree => "degree",
53 Self::OutEdges => "out_edges",
54 Self::InEdges => "in_edges",
55 Self::ShortestPath => "shortest_path",
56 Self::Bfs => "bfs",
57 Self::ConnectedComponents => "connected_components",
58 Self::VariableLengthPath => "variable_length_path",
59 Self::PageRank => "pagerank",
60 }
61 }
62
63 pub fn arity(&self) -> usize {
65 match self {
66 Self::Id => 1,
67 Self::Tags => 1,
68 Self::Labels => 1,
69 Self::Properties => 1,
70 Self::EdgeType => 1,
71 Self::Src => 1,
72 Self::Dst => 1,
73 Self::Rank => 1,
74 Self::StartNode => 1,
75 Self::EndNode => 1,
76 Self::Neighbors => 1,
77 Self::Degree => 1,
78 Self::OutEdges => 1,
79 Self::InEdges => 1,
80 Self::ShortestPath => 2,
81 Self::Bfs => 2,
82 Self::ConnectedComponents => 0,
83 Self::VariableLengthPath => 3,
84 Self::PageRank => 0,
85 }
86 }
87
88 pub fn is_variadic(&self) -> bool {
90 matches!(self, Self::VariableLengthPath)
91 }
92
93 pub fn description(&self) -> &str {
95 match self {
96 Self::Id => "Get the ID of the vertex",
97 Self::Tags => "Get all labels of a vertex",
98 Self::Labels => "Get all labels (aliases) of a vertex",
99 Self::Properties => "Get all attributes of a vertex or edge",
100 Self::EdgeType => "Get the type of the edge",
101 Self::Src => "Get the starting vertex ID of the edge",
102 Self::Dst => "Get the target vertex ID of the edge",
103 Self::Rank => "Get the edge's rank value",
104 Self::StartNode => "Get the starting vertex of the edge",
105 Self::EndNode => "Get the target vertex of the edge",
106 Self::Neighbors => "Get all neighbor vertex IDs of a vertex",
107 Self::Degree => "Get the degree (number of edges) of a vertex",
108 Self::OutEdges => "Get all outgoing edges of a vertex",
109 Self::InEdges => "Get all incoming edges of a vertex",
110 Self::ShortestPath => "Find the shortest path between two vertices (returns path length)",
111 Self::Bfs => "Breadth-first search traversal (start_vid, max_depth)",
112 Self::ConnectedComponents => "Find all connected components in the graph",
113 Self::VariableLengthPath => "Find all paths between two vertices within a depth range (start_vid, end_vid, max_depth) or (start_vid, end_vid, min_depth, max_depth)",
114 Self::PageRank => "Calculate PageRank scores for all vertices in the graph",
115 }
116 }
117
118 pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
119 if !self.is_variadic() && args.len() != self.arity() {
120 return Err(ExpressionError::invalid_arity(
121 self.name(),
122 self.arity(),
123 args.len(),
124 ));
125 }
126 match self {
127 Self::Id => execute_id(args),
128 Self::Tags => execute_tags(args),
129 Self::Labels => execute_labels(args),
130 Self::Properties => execute_properties(args),
131 Self::EdgeType => execute_edge_type(args),
132 Self::Src => execute_src(args),
133 Self::Dst => execute_dst(args),
134 Self::Rank => execute_rank(args),
135 Self::StartNode => execute_startnode(args),
136 Self::EndNode => execute_endnode(args),
137 Self::Neighbors => execute_neighbors(args),
138 Self::Degree => execute_degree(args),
139 Self::OutEdges => execute_out_edges(args),
140 Self::InEdges => execute_in_edges(args),
141 Self::ShortestPath => execute_shortest_path(args),
142 Self::Bfs => execute_bfs(args),
143 Self::ConnectedComponents => execute_connected_components(args),
144 Self::VariableLengthPath => execute_variable_length_path(args),
145 Self::PageRank => execute_pagerank(args),
146 }
147 }
148
149 pub fn execute_with_storage(
152 &self,
153 args: &[Value],
154 storage: &GraphStorageRef,
155 ) -> Result<Value, ExpressionError> {
156 if !self.is_variadic() && args.len() != self.arity() {
157 return Err(ExpressionError::invalid_arity(
158 self.name(),
159 self.arity(),
160 args.len(),
161 ));
162 }
163 match self {
164 Self::StartNode => execute_startnode_with_storage(args, storage),
165 Self::EndNode => execute_endnode_with_storage(args, storage),
166 Self::Neighbors => execute_neighbors_with_storage(args, storage),
167 Self::Degree => execute_degree_with_storage(args, storage),
168 Self::OutEdges => execute_out_edges_with_storage(args, storage),
169 Self::InEdges => execute_in_edges_with_storage(args, storage),
170 Self::ShortestPath => execute_shortest_path_with_storage(args, storage),
171 Self::Bfs => execute_bfs_with_storage(args, storage),
172 Self::ConnectedComponents => execute_connected_components_with_storage(args, storage),
173 Self::VariableLengthPath => execute_variable_length_path_with_storage(args, storage),
174 Self::PageRank => execute_pagerank_with_storage(args, storage),
175 _ => self.execute(args),
176 }
177 }
178}
179
180fn execute_id(args: &[Value]) -> Result<Value, ExpressionError> {
181 match &args[0] {
182 Value::Vertex(v) => Ok(Value::from(v.vid)),
183 Value::Null(_) => Ok(Value::Null(NullType::Null)),
184 _ => Err(ExpressionError::type_error(
185 "The id function requires a vertex type",
186 )),
187 }
188}
189
190fn execute_tags(args: &[Value]) -> Result<Value, ExpressionError> {
191 match &args[0] {
192 Value::Vertex(v) => {
193 let tags: Vec<Value> = vec![Value::string(v.tag.name.clone())];
194 Ok(Value::list(List { values: tags }))
195 }
196 Value::Null(_) => Ok(Value::Null(NullType::Null)),
197 _ => Err(ExpressionError::type_error("tags requires vertex type")),
198 }
199}
200
201fn execute_labels(args: &[Value]) -> Result<Value, ExpressionError> {
202 execute_tags(args)
203}
204
205fn execute_properties(args: &[Value]) -> Result<Value, ExpressionError> {
206 match &args[0] {
207 Value::Vertex(v) => {
208 let mut props = std::collections::HashMap::new();
209 props.extend(
210 v.properties()
211 .iter()
212 .map(|(k, v)| (Value::string(k.clone()), v.clone())),
213 );
214 Ok(Value::map(props))
215 }
216 Value::Edge(e) => Ok(Value::string_map(e.props.clone())),
217 Value::EdgeHeader(_) => Err(ExpressionError::type_error(
218 "properties requires a materialized edge: the edge identity reference carries no properties",
219 )),
220 Value::Map(m) => Ok(Value::map((**m).clone())),
221 Value::Null(_) => Ok(Value::Null(NullType::Null)),
222 _ => Err(ExpressionError::type_error(
223 "properties requires vertex, edge or map type",
224 )),
225 }
226}
227
228fn execute_edge_type(args: &[Value]) -> Result<Value, ExpressionError> {
229 match &args[0] {
230 Value::Edge(e) => Ok(Value::string(e.edge_type.clone())),
231 Value::EdgeHeader(h) => Ok(Value::string(h.edge_type.clone())),
232 Value::Null(_) => Ok(Value::Null(NullType::Null)),
233 _ => Err(ExpressionError::type_error(
234 "The type function requires an edge type",
235 )),
236 }
237}
238
239fn execute_src(args: &[Value]) -> Result<Value, ExpressionError> {
240 match &args[0] {
241 Value::Edge(e) => Ok(Value::from(e.src)),
242 Value::EdgeHeader(h) => Ok(Value::from(h.src)),
243 Value::Null(_) => Ok(Value::Null(NullType::Null)),
244 _ => Err(ExpressionError::type_error(
245 "The src function requires the edge type",
246 )),
247 }
248}
249
250fn execute_dst(args: &[Value]) -> Result<Value, ExpressionError> {
251 match &args[0] {
252 Value::Edge(e) => Ok(Value::from(e.dst)),
253 Value::EdgeHeader(h) => Ok(Value::from(h.dst)),
254 Value::Null(_) => Ok(Value::Null(NullType::Null)),
255 _ => Err(ExpressionError::type_error(
256 "The dst function requires an edge type",
257 )),
258 }
259}
260
261fn execute_rank(args: &[Value]) -> Result<Value, ExpressionError> {
262 match &args[0] {
263 Value::Edge(e) => Ok(Value::BigInt(e.ranking)),
264 Value::EdgeHeader(h) => Ok(Value::BigInt(h.ranking)),
265 Value::Null(_) => Ok(Value::Null(NullType::Null)),
266 _ => Err(ExpressionError::type_error(
267 "The rank function requires an edge type",
268 )),
269 }
270}
271
272fn execute_startnode(args: &[Value]) -> Result<Value, ExpressionError> {
273 match &args[0] {
274 Value::Edge(_) | Value::EdgeHeader(_) => Err(ExpressionError::function_error(
275 "startnode() requires graph storage access to resolve the endpoint label; use within a query context",
276 )),
277 Value::Null(_) => Ok(Value::Null(NullType::Null)),
278 _ => Err(ExpressionError::type_error(
279 "The startnode function requires the edge type",
280 )),
281 }
282}
283
284fn execute_endnode(args: &[Value]) -> Result<Value, ExpressionError> {
285 match &args[0] {
286 Value::Edge(_) | Value::EdgeHeader(_) => Err(ExpressionError::function_error(
287 "endnode() requires graph storage access to resolve the endpoint label; use within a query context",
288 )),
289 Value::Null(_) => Ok(Value::Null(NullType::Null)),
290 _ => Err(ExpressionError::type_error(
291 "The endnode function requires an edge type",
292 )),
293 }
294}
295
296fn endpoint_vertex_with_storage(
300 edge_type: &str,
301 vid: VertexId,
302 endpoint_src: bool,
303 storage: &GraphStorageRef,
304) -> Result<Value, ExpressionError> {
305 let reader = storage.storage.read();
306 let info = reader
307 .get_edge_type(&storage.space, edge_type)
308 .map_err(|e| ExpressionError::function_error(format!("Storage error: {}", e)))?
309 .ok_or_else(|| {
310 ExpressionError::function_error(format!("Edge type '{edge_type}' not found"))
311 })?;
312 let tag = if endpoint_src {
313 &info.src_tag_name
314 } else {
315 &info.dst_tag_name
316 };
317 if tag.is_empty() {
318 return Err(ExpressionError::function_error(format!(
319 "Edge type '{edge_type}' declares no endpoint label"
320 )));
321 }
322 let vertex = reader
323 .get_vertex(&storage.space, tag, &vid)
324 .map_err(|e| ExpressionError::function_error(format!("Storage error: {}", e)))?;
325 match vertex {
326 Some(vertex) => Ok(Value::Vertex(Box::new(vertex))),
327 None => Ok(Value::Null(NullType::Null)),
328 }
329}
330
331fn execute_startnode_with_storage(
332 args: &[Value],
333 storage: &GraphStorageRef,
334) -> Result<Value, ExpressionError> {
335 match &args[0] {
336 Value::Edge(e) => endpoint_vertex_with_storage(&e.edge_type, e.src, true, storage),
337 Value::EdgeHeader(h) => endpoint_vertex_with_storage(&h.edge_type, h.src, true, storage),
338 Value::Null(_) => Ok(Value::Null(NullType::Null)),
339 _ => Err(ExpressionError::type_error(
340 "The startnode function requires the edge type",
341 )),
342 }
343}
344
345fn execute_endnode_with_storage(
346 args: &[Value],
347 storage: &GraphStorageRef,
348) -> Result<Value, ExpressionError> {
349 match &args[0] {
350 Value::Edge(e) => endpoint_vertex_with_storage(&e.edge_type, e.dst, false, storage),
351 Value::EdgeHeader(h) => endpoint_vertex_with_storage(&h.edge_type, h.dst, false, storage),
352 Value::Null(_) => Ok(Value::Null(NullType::Null)),
353 _ => Err(ExpressionError::type_error(
354 "The endnode function requires an edge type",
355 )),
356 }
357}
358
359fn extract_vertex_id(value: &Value) -> Result<VertexId, ExpressionError> {
360 match value {
361 Value::Vertex(v) => Ok(v.vid),
362 Value::BigInt(id) => {
363 VertexId::try_from_int64(*id).map_err(|e| ExpressionError::type_error(e.to_string()))
364 }
365 Value::Int(id) => VertexId::try_from_int64(*id as i64)
366 .map_err(|e| ExpressionError::type_error(e.to_string())),
367 Value::Null(_) => Err(ExpressionError::type_error(
368 "Expected a vertex or vertex ID, got null",
369 )),
370 _ => Err(ExpressionError::type_error(
371 "Expected a vertex or vertex ID",
372 )),
373 }
374}
375
376fn execute_neighbors(args: &[Value]) -> Result<Value, ExpressionError> {
377 match &args[0] {
378 Value::Vertex(v) => Ok(Value::from(v.vid)),
379 Value::Null(_) => Ok(Value::Null(NullType::Null)),
380 _ => Err(ExpressionError::type_error(
381 "The neighbors function requires a vertex type",
382 )),
383 }
384}
385
386fn execute_degree(args: &[Value]) -> Result<Value, ExpressionError> {
387 match &args[0] {
388 Value::Vertex(v) => {
389 let degree = v
390 .properties()
391 .keys()
392 .filter(|k| k.starts_with("neighbor_"))
393 .count();
394 Ok(Value::BigInt(degree as i64))
395 }
396 Value::Null(_) => Ok(Value::Null(NullType::Null)),
397 _ => Err(ExpressionError::type_error(
398 "The degree function requires a vertex type",
399 )),
400 }
401}
402
403fn execute_out_edges(args: &[Value]) -> Result<Value, ExpressionError> {
404 match &args[0] {
405 Value::Vertex(v) => {
406 let edges: Vec<Value> = v
407 .properties()
408 .iter()
409 .filter(|(k, _)| k.starts_with("out_"))
410 .map(|(_, v)| v.clone())
411 .collect();
412 Ok(Value::list(List { values: edges }))
413 }
414 Value::Null(_) => Ok(Value::Null(NullType::Null)),
415 _ => Err(ExpressionError::type_error(
416 "The out_edges function requires a vertex type",
417 )),
418 }
419}
420
421fn execute_in_edges(args: &[Value]) -> Result<Value, ExpressionError> {
422 match &args[0] {
423 Value::Vertex(v) => {
424 let edges: Vec<Value> = v
425 .properties()
426 .iter()
427 .filter(|(k, _)| k.starts_with("in_"))
428 .map(|(_, v)| v.clone())
429 .collect();
430 Ok(Value::list(List { values: edges }))
431 }
432 Value::Null(_) => Ok(Value::Null(NullType::Null)),
433 _ => Err(ExpressionError::type_error(
434 "The in_edges function requires a vertex type",
435 )),
436 }
437}
438
439fn execute_shortest_path(args: &[Value]) -> Result<Value, ExpressionError> {
440 let start_vid = match &args[0] {
441 Value::BigInt(id) => *id,
442 Value::Int(id) => *id as i64,
443 _ => {
444 return Err(ExpressionError::type_error(
445 "shortest_path requires integer vertex IDs",
446 ))
447 }
448 };
449 let end_vid = match &args[1] {
450 Value::BigInt(id) => *id,
451 Value::Int(id) => *id as i64,
452 _ => {
453 return Err(ExpressionError::type_error(
454 "shortest_path requires integer vertex IDs",
455 ))
456 }
457 };
458 if start_vid < 0 || end_vid < 0 {
459 return Err(ExpressionError::type_error(
460 "shortest_path requires non-negative integer vertex IDs",
461 ));
462 }
463 if start_vid == end_vid {
464 return Ok(Value::BigInt(0));
465 }
466 Ok(Value::BigInt(-1))
467}
468
469fn execute_bfs(_args: &[Value]) -> Result<Value, ExpressionError> {
470 Err(ExpressionError::function_error(
471 "bfs() requires graph storage access; use within a query context".to_string(),
472 ))
473}
474
475fn execute_connected_components(_args: &[Value]) -> Result<Value, ExpressionError> {
476 Err(ExpressionError::function_error(
477 "connected_components() requires graph storage access; use within a query context"
478 .to_string(),
479 ))
480}
481
482fn execute_neighbors_with_storage(
485 args: &[Value],
486 storage: &GraphStorageRef,
487) -> Result<Value, ExpressionError> {
488 let vid = extract_vertex_id(&args[0])?;
489 let neighbors = storage
490 .get_neighbors(&vid)
491 .map_err(ExpressionError::function_error)?;
492 let neighbor_ids: Vec<Value> = neighbors
493 .into_iter()
494 .map(|(nid, _)| Value::from(nid))
495 .collect();
496 Ok(Value::list(List {
497 values: neighbor_ids,
498 }))
499}
500
501fn execute_degree_with_storage(
502 args: &[Value],
503 storage: &GraphStorageRef,
504) -> Result<Value, ExpressionError> {
505 let vid = extract_vertex_id(&args[0])?;
506 let neighbors = storage
507 .get_neighbors(&vid)
508 .map_err(ExpressionError::function_error)?;
509 Ok(Value::BigInt(neighbors.len() as i64))
510}
511
512fn execute_out_edges_with_storage(
513 args: &[Value],
514 storage: &GraphStorageRef,
515) -> Result<Value, ExpressionError> {
516 use linkrs_core::types::EdgeDirection;
517 let vid = extract_vertex_id(&args[0])?;
518 let reader = storage.storage.read();
519 let edges = reader
520 .get_node_edges(&storage.space, &vid, EdgeDirection::Out, &[])
521 .map_err(|e| ExpressionError::function_error(format!("Storage error: {}", e)))?;
522 drop(reader);
523 let edge_values: Vec<Value> = edges
524 .into_iter()
525 .map(|e| Value::Edge(Box::new(e)))
526 .collect();
527 Ok(Value::list(List {
528 values: edge_values,
529 }))
530}
531
532fn execute_in_edges_with_storage(
533 args: &[Value],
534 storage: &GraphStorageRef,
535) -> Result<Value, ExpressionError> {
536 use linkrs_core::types::EdgeDirection;
537 let vid = extract_vertex_id(&args[0])?;
538 let reader = storage.storage.read();
539 let edges = reader
540 .get_node_edges(&storage.space, &vid, EdgeDirection::In, &[])
541 .map_err(|e| ExpressionError::function_error(format!("Storage error: {}", e)))?;
542 drop(reader);
543 let edge_values: Vec<Value> = edges
544 .into_iter()
545 .map(|e| Value::Edge(Box::new(e)))
546 .collect();
547 Ok(Value::list(List {
548 values: edge_values,
549 }))
550}
551
552fn execute_shortest_path_with_storage(
553 args: &[Value],
554 storage: &GraphStorageRef,
555) -> Result<Value, ExpressionError> {
556 let start_vid = extract_vertex_id(&args[0])?;
557 let end_vid = extract_vertex_id(&args[1])?;
558
559 if start_vid == end_vid {
560 return Ok(Value::BigInt(0));
561 }
562
563 use std::collections::{HashMap, VecDeque};
565 let mut visited: HashMap<VertexId, i64> = HashMap::new();
566 let mut queue: VecDeque<VertexId> = VecDeque::new();
567
568 visited.insert(start_vid, 0);
569 queue.push_back(start_vid);
570
571 while let Some(current) = queue.pop_front() {
572 let distance = visited[¤t];
573
574 let neighbors = storage
575 .get_neighbors(¤t)
576 .map_err(ExpressionError::function_error)?;
577
578 for (neighbor_id, _) in neighbors {
579 if neighbor_id == end_vid {
580 return Ok(Value::BigInt(distance + 1));
581 }
582 if let std::collections::hash_map::Entry::Vacant(e) = visited.entry(neighbor_id) {
583 e.insert(distance + 1);
584 queue.push_back(neighbor_id);
585 }
586 }
587 }
588
589 Ok(Value::BigInt(-1))
590}
591
592fn execute_bfs_with_storage(
593 args: &[Value],
594 storage: &GraphStorageRef,
595) -> Result<Value, ExpressionError> {
596 let start_vid = extract_vertex_id(&args[0])?;
597 let max_depth = match &args[1] {
598 Value::BigInt(d) => *d,
599 Value::Int(d) => *d as i64,
600 _ => {
601 return Err(ExpressionError::type_error(
602 "bfs max_depth must be an integer",
603 ))
604 }
605 };
606
607 use std::collections::{HashSet, VecDeque};
608 let mut visited: HashSet<VertexId> = HashSet::new();
609 let mut queue: VecDeque<(VertexId, i64)> = VecDeque::new();
610 let mut result: Vec<Value> = Vec::new();
611
612 visited.insert(start_vid);
613 queue.push_back((start_vid, 0));
614 result.push(Value::from(start_vid));
615
616 while let Some((current, depth)) = queue.pop_front() {
617 if depth >= max_depth {
618 continue;
619 }
620
621 let neighbors = storage
622 .get_neighbors(¤t)
623 .map_err(ExpressionError::function_error)?;
624
625 for (neighbor_id, _) in neighbors {
626 if visited.insert(neighbor_id) {
627 queue.push_back((neighbor_id, depth + 1));
628 result.push(Value::from(neighbor_id));
629 }
630 }
631 }
632
633 Ok(Value::list(List { values: result }))
634}
635
636fn execute_connected_components_with_storage(
637 _args: &[Value],
638 storage: &GraphStorageRef,
639) -> Result<Value, ExpressionError> {
640 let reader = storage.storage.read();
642 let all_vertices = reader
643 .scan_vertices(&storage.space)
644 .map_err(|e| ExpressionError::function_error(format!("Storage error: {}", e)))?;
645 drop(reader);
646
647 use std::collections::{HashSet, VecDeque};
648
649 let all_vids: HashSet<VertexId> = all_vertices.iter().map(|v| v.vid).collect();
650 let mut visited: HashSet<VertexId> = HashSet::new();
651 let mut components: Vec<Vec<Value>> = Vec::new();
652
653 for start_vid in &all_vids {
654 if visited.contains(start_vid) {
655 continue;
656 }
657
658 let mut component: Vec<Value> = Vec::new();
659 let mut queue: VecDeque<VertexId> = VecDeque::new();
660 visited.insert(*start_vid);
661 queue.push_back(*start_vid);
662 component.push(Value::from(*start_vid));
663
664 while let Some(current) = queue.pop_front() {
665 let neighbors = match storage.get_neighbors(¤t) {
666 Ok(n) => n,
667 Err(_) => continue,
668 };
669 for (neighbor_id, _) in neighbors {
670 if visited.insert(neighbor_id) {
671 queue.push_back(neighbor_id);
672 component.push(Value::from(neighbor_id));
673 }
674 }
675 }
676
677 components.push(component);
678 }
679
680 let component_lists: Vec<Value> = components
681 .into_iter()
682 .map(|comp| Value::list(List { values: comp }))
683 .collect();
684
685 Ok(Value::list(List {
686 values: component_lists,
687 }))
688}
689
690fn execute_variable_length_path(_args: &[Value]) -> Result<Value, ExpressionError> {
691 Err(ExpressionError::type_error(
692 "variable_length_path() requires graph storage access; use within a query context",
693 ))
694}
695
696fn execute_pagerank(_args: &[Value]) -> Result<Value, ExpressionError> {
697 Err(ExpressionError::function_error(
698 "pagerank() requires graph storage access; use within a query context".to_string(),
699 ))
700}
701
702fn execute_variable_length_path_with_storage(
703 args: &[Value],
704 storage: &GraphStorageRef,
705) -> Result<Value, ExpressionError> {
706 let (start_vid, end_vid, min_depth, max_depth) = match args.len() {
707 3 => {
708 let sv = extract_vertex_id(&args[0])?;
709 let ev = extract_vertex_id(&args[1])?;
710 let md = match &args[2] {
711 Value::BigInt(d) => *d,
712 Value::Int(d) => *d as i64,
713 _ => {
714 return Err(ExpressionError::type_error(
715 "variable_length_path max_depth must be an integer",
716 ))
717 }
718 };
719 (sv, ev, 1i64, md)
720 }
721 4 => {
722 let sv = extract_vertex_id(&args[0])?;
723 let ev = extract_vertex_id(&args[1])?;
724 let mind = match &args[2] {
725 Value::BigInt(d) => *d,
726 Value::Int(d) => *d as i64,
727 _ => {
728 return Err(ExpressionError::type_error(
729 "variable_length_path min_depth must be an integer",
730 ))
731 }
732 };
733 let maxd = match &args[3] {
734 Value::BigInt(d) => *d,
735 Value::Int(d) => *d as i64,
736 _ => {
737 return Err(ExpressionError::type_error(
738 "variable_length_path max_depth must be an integer",
739 ))
740 }
741 };
742 (sv, ev, mind, maxd)
743 }
744 _ => {
745 return Err(ExpressionError::type_error(
746 "variable_length_path takes 3 or 4 arguments (start_vid, end_vid, max_depth) or (start_vid, end_vid, min_depth, max_depth)",
747 ))
748 }
749 };
750
751 if min_depth < 1 || max_depth < min_depth {
752 return Err(ExpressionError::type_error(
753 "variable_length_path: min_depth must be >= 1 and max_depth must be >= min_depth",
754 ));
755 }
756
757 if start_vid == end_vid && min_depth <= 0 {
758 return Ok(Value::list(List {
759 values: vec![Value::list(List {
760 values: vec![Value::from(start_vid)],
761 })],
762 }));
763 }
764
765 use std::collections::VecDeque;
768 let mut results: Vec<Vec<Value>> = Vec::new();
771 let mut queue: VecDeque<(VertexId, Vec<Value>, i64)> = VecDeque::new();
772
773 queue.push_back((start_vid, vec![Value::from(start_vid)], 0));
774
775 while let Some((current, path, depth)) = queue.pop_front() {
776 if depth >= max_depth {
777 continue;
778 }
779
780 let neighbors = match storage.get_neighbors(¤t) {
781 Ok(n) => n,
782 Err(_) => continue,
783 };
784
785 for (neighbor_id, _) in neighbors {
786 let neighbor_val = Value::from(neighbor_id);
788 if path.contains(&neighbor_val) {
789 continue;
790 }
791
792 let new_depth = depth + 1;
793 let mut new_path = path.clone();
794 new_path.push(neighbor_val.clone());
795
796 if neighbor_id == end_vid && new_depth >= min_depth {
797 results.push(new_path.clone());
798 }
799
800 if results.len() >= 1000 {
802 break;
803 }
804
805 queue.push_back((neighbor_id, new_path, new_depth));
806 }
807
808 if results.len() >= 1000 {
809 break;
810 }
811 }
812
813 let path_values: Vec<Value> = results
814 .into_iter()
815 .map(|path| Value::list(List { values: path }))
816 .collect();
817
818 Ok(Value::list(List {
819 values: path_values,
820 }))
821}
822
823fn execute_pagerank_with_storage(
824 _args: &[Value],
825 storage: &GraphStorageRef,
826) -> Result<Value, ExpressionError> {
827 let reader = storage.storage.read();
828 let all_vertices = reader
829 .scan_vertices(&storage.space)
830 .map_err(|e| ExpressionError::function_error(format!("Storage error: {}", e)))?;
831 drop(reader);
832
833 let vertex_count = all_vertices.len();
834 if vertex_count == 0 {
835 return Ok(Value::map(std::collections::HashMap::new()));
836 }
837
838 use std::collections::HashMap;
839
840 let vertex_ids: Vec<VertexId> = all_vertices.iter().map(|v| v.vid).collect();
841 let mut vid_to_index: HashMap<VertexId, usize> = HashMap::new();
842 for (i, vid) in vertex_ids.iter().enumerate() {
843 vid_to_index.insert(*vid, i);
844 }
845
846 let damping_factor: f64 = 0.85;
847 let max_iterations: usize = 100;
848 let tolerance: f64 = 1e-6;
849
850 let mut scores: Vec<f64> = vec![1.0 / vertex_count as f64; vertex_count];
851 let mut out_degrees: Vec<usize> = vec![0; vertex_count];
852
853 let mut adjacency: Vec<Vec<usize>> = vec![Vec::new(); vertex_count];
854
855 for vid in &vertex_ids {
856 let reader = storage.storage.read();
857 let edges = reader
858 .get_node_edges(
859 &storage.space,
860 vid,
861 linkrs_core::types::EdgeDirection::Out,
862 &[],
863 )
864 .map_err(|e| ExpressionError::function_error(format!("Storage error: {}", e)))?;
865 drop(reader);
866
867 let src_index = vid_to_index[vid];
868 out_degrees[src_index] = edges.len();
869
870 for edge in edges {
871 if let Some(&dst_index) = vid_to_index.get(&edge.dst) {
872 adjacency[src_index].push(dst_index);
873 }
874 }
875 }
876
877 for _ in 0..max_iterations {
878 let mut new_scores: Vec<f64> = vec![0.0; vertex_count];
879 let mut dangling_sum: f64 = 0.0;
880
881 for i in 0..vertex_count {
882 if out_degrees[i] == 0 {
883 dangling_sum += scores[i];
884 }
885 }
886
887 let base = (1.0 - damping_factor) / vertex_count as f64
888 + damping_factor * dangling_sum / vertex_count as f64;
889 for score in new_scores.iter_mut() {
890 *score = base;
891 }
892
893 for i in 0..vertex_count {
894 if out_degrees[i] > 0 {
895 let share = scores[i] / out_degrees[i] as f64;
896 for &j in &adjacency[i] {
897 new_scores[j] += damping_factor * share;
898 }
899 }
900 }
901
902 let mut diff: f64 = 0.0;
903 for i in 0..vertex_count {
904 diff += (new_scores[i] - scores[i]).abs();
905 }
906
907 scores = new_scores;
908
909 if diff < tolerance {
910 break;
911 }
912 }
913
914 let mut result: HashMap<String, Value> = HashMap::new();
915 for (i, vid) in vertex_ids.iter().enumerate() {
916 let vid_str = vid.to_string();
917 result.insert(vid_str, Value::Double(scores[i]));
918 }
919
920 Ok(Value::string_map(result))
921}
922
923#[cfg(test)]
924mod tests {
925 use super::*;
926 use linkrs_core::types::VertexId;
927 use linkrs_core::vertex_edge_path::{Edge, Tag, Vertex};
928 use std::collections::HashMap;
929
930 fn create_test_vertex() -> Vertex {
931 let mut props = HashMap::from([
932 ("name".into(), Value::string("Alice")),
933 ("age".into(), Value::Int(30)),
934 ]);
935 props.insert("dept".into(), Value::string("Engineering"));
936 let tag = Tag::new("person".into(), props);
937 Vertex::new(VertexId::try_from_int64(1).expect("valid vertex id"), tag)
938 }
939
940 fn create_test_edge() -> Edge {
941 Edge::new(
942 VertexId::try_from_int64(1).expect("valid vertex id"),
943 VertexId::try_from_int64(2).expect("valid vertex id"),
944 "knows".to_string(),
945 0,
946 HashMap::from([("since".into(), Value::Int(2020))]),
947 )
948 }
949
950 #[test]
951 fn test_id_function() {
952 let vertex = create_test_vertex();
953 let result = GraphFunction::Id
954 .execute(&[Value::Vertex(Box::new(vertex))])
955 .expect("The id function execution should succeed");
956 assert_eq!(result, Value::BigInt(1));
957 }
958
959 #[test]
960 fn test_tags_function() {
961 let vertex = create_test_vertex();
962 let result = GraphFunction::Tags
963 .execute(&[Value::Vertex(Box::new(vertex))])
964 .expect("The tags function should be executed successfully");
965 if let Value::List(tags) = result {
966 assert_eq!(tags.values.len(), 1);
967 } else {
968 panic!("The `tags` function should return a list.");
969 }
970 }
971
972 #[test]
973 fn test_properties_vertex() {
974 let vertex = create_test_vertex();
975 let result = GraphFunction::Properties
976 .execute(&[Value::Vertex(Box::new(vertex))])
977 .expect("The properties function should execute successfully");
978 if let Value::Map(props) = result {
979 assert!(props.contains_key(&Value::string("name")));
980 assert!(props.contains_key(&Value::string("age")));
981 assert!(props.contains_key(&Value::string("dept")));
982 } else {
983 panic!("The `properties` function should return a map.");
984 }
985 }
986
987 #[test]
988 fn test_type_function() {
989 let edge = create_test_edge();
990 let result = GraphFunction::EdgeType
991 .execute(&[Value::Edge(Box::new(edge))])
992 .expect("The type function execution should succeed");
993 assert_eq!(result, Value::string("knows"));
994 }
995
996 #[test]
997 fn test_src_function() {
998 let edge = create_test_edge();
999 let result = GraphFunction::Src
1000 .execute(&[Value::Edge(Box::new(edge))])
1001 .expect("The src function execution should succeed");
1002 assert_eq!(result, Value::BigInt(1));
1003 }
1004
1005 #[test]
1006 fn test_dst_function() {
1007 let edge = create_test_edge();
1008 let result = GraphFunction::Dst
1009 .execute(&[Value::Edge(Box::new(edge))])
1010 .expect("The dst function execution should succeed");
1011 assert_eq!(result, Value::BigInt(2));
1012 }
1013
1014 #[test]
1015 fn test_rank_function() {
1016 let edge = create_test_edge();
1017 let result = GraphFunction::Rank
1018 .execute(&[Value::Edge(Box::new(edge))])
1019 .expect("The execution of the rank function should succeed");
1020 assert_eq!(result, Value::BigInt(0));
1021 }
1022
1023 #[test]
1024 fn test_null_handling() {
1025 let null_value = Value::Null(NullType::Null);
1026
1027 assert_eq!(
1028 GraphFunction::Id
1029 .execute(std::slice::from_ref(&null_value))
1030 .expect("The id function should handle NULL"),
1031 Value::Null(NullType::Null)
1032 );
1033 assert_eq!(
1034 GraphFunction::Tags
1035 .execute(std::slice::from_ref(&null_value))
1036 .expect("The tags function should handle NULL"),
1037 Value::Null(NullType::Null)
1038 );
1039 assert_eq!(
1040 GraphFunction::Properties
1041 .execute(std::slice::from_ref(&null_value))
1042 .expect("The properties function should handle NULL."),
1043 Value::Null(NullType::Null)
1044 );
1045 }
1046}