Skip to main content

linkrs_query/executor/expression/functions/builtin/
path.rs

1//! Implementation of path-related functions
2//!
3//! Provide functions for path operations, including nodes and relationships.
4
5use crate::executor::expression::ExpressionError;
6use linkrs_core::value::list::List;
7use linkrs_core::value::NullType;
8use linkrs_core::Value;
9
10/// Path function enumeration
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum PathFunction {
13    Nodes,
14    Relationships,
15    Properties,
16    IsTrail,
17    IsAcyclic,
18    PathLength,
19}
20
21impl PathFunction {
22    /// Obtain the function name
23    pub fn name(&self) -> &str {
24        match self {
25            Self::Nodes => "nodes",
26            Self::Relationships => "relationships",
27            Self::Properties => "path_properties",
28            Self::IsTrail => "is_trail",
29            Self::IsAcyclic => "is_acyclic",
30            Self::PathLength => "length",
31        }
32    }
33
34    /// Determine the number of parameters
35    pub fn arity(&self) -> usize {
36        1
37    }
38
39    /// Is it a function with variable parameters?
40    pub fn is_variadic(&self) -> bool {
41        false
42    }
43
44    /// Obtain the function description
45    pub fn description(&self) -> &str {
46        match self {
47            Self::Nodes => "Get all vertices in the path",
48            Self::Relationships => "Get all edges in the path",
49            Self::Properties => "Get all properties from vertices and edges in the path",
50            Self::IsTrail => "Check if path is a trail (no repeated edges)",
51            Self::IsAcyclic => "Check if path is acyclic (no repeated vertices)",
52            Self::PathLength => "Get the length of a string, path, or list",
53        }
54    }
55
56    pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
57        if !self.is_variadic() && args.len() != self.arity() {
58            return Err(ExpressionError::invalid_arity(
59                self.name(),
60                self.arity(),
61                args.len(),
62            ));
63        }
64        match self {
65            Self::Nodes => execute_nodes(args),
66            Self::Relationships => execute_relationships(args),
67            Self::Properties => execute_properties(args),
68            Self::IsTrail => execute_is_trail(args),
69            Self::IsAcyclic => execute_is_acyclic(args),
70            Self::PathLength => execute_length(args),
71        }
72    }
73}
74
75fn execute_nodes(args: &[Value]) -> Result<Value, ExpressionError> {
76    match &args[0] {
77        Value::Path(path) => {
78            let mut result = vec![Value::Vertex(Box::new((*path.src).clone()))];
79            for step in &path.steps {
80                result.push(Value::Vertex(Box::new((*step.dst).clone())));
81            }
82            Ok(Value::list(List { values: result }))
83        }
84        Value::Null(_) => Ok(Value::Null(NullType::Null)),
85        _ => Err(ExpressionError::type_error("nodes requires a path type")),
86    }
87}
88
89fn execute_relationships(args: &[Value]) -> Result<Value, ExpressionError> {
90    match &args[0] {
91        Value::Path(path) => {
92            let result: Vec<Value> = path
93                .steps
94                .iter()
95                .map(|step| Value::edge((*step.edge).clone()))
96                .collect();
97            Ok(Value::list(List { values: result }))
98        }
99        Value::Null(_) => Ok(Value::Null(NullType::Null)),
100        _ => Err(ExpressionError::type_error(
101            "relationships requires a path type",
102        )),
103    }
104}
105
106fn execute_properties(args: &[Value]) -> Result<Value, ExpressionError> {
107    match &args[0] {
108        Value::Path(path) => {
109            let mut all_props = std::collections::HashMap::new();
110            // Get properties from source vertex
111            for (k, v) in path.src.properties().iter() {
112                all_props.insert(k.clone(), v.clone());
113            }
114            // Get properties from edges and destination vertices
115            for step in &path.steps {
116                for (k, v) in &step.edge.props {
117                    all_props.insert(k.clone(), v.clone());
118                }
119                for (k, v) in step.dst.properties().iter() {
120                    all_props.insert(k.clone(), v.clone());
121                }
122            }
123            let props: Vec<Value> = all_props
124                .into_iter()
125                .map(|(k, v)| {
126                    Value::list(List {
127                        values: vec![Value::string(k), v],
128                    })
129                })
130                .collect();
131            Ok(Value::list(List { values: props }))
132        }
133        Value::Null(_) => Ok(Value::Null(NullType::Null)),
134        _ => Err(ExpressionError::type_error(
135            "properties requires a path type",
136        )),
137    }
138}
139
140fn execute_is_trail(args: &[Value]) -> Result<Value, ExpressionError> {
141    match &args[0] {
142        Value::Path(path) => {
143            let mut seen_edges = std::collections::HashSet::new();
144            for step in &path.steps {
145                let edge_id = (
146                    step.edge.src.as_int64(),
147                    step.edge.dst.as_int64(),
148                    &step.edge.edge_type,
149                );
150                if !seen_edges.insert(edge_id) {
151                    return Ok(Value::Bool(false));
152                }
153            }
154            Ok(Value::Bool(true))
155        }
156        Value::Null(_) => Ok(Value::Null(NullType::Null)),
157        _ => Err(ExpressionError::type_error("is_trail requires a path type")),
158    }
159}
160
161fn execute_is_acyclic(args: &[Value]) -> Result<Value, ExpressionError> {
162    match &args[0] {
163        Value::Path(path) => {
164            let mut seen_vertices = std::collections::HashSet::new();
165            seen_vertices.insert(path.src.vid.as_int64());
166            for step in &path.steps {
167                let vid = step.dst.vid.as_int64();
168                if !seen_vertices.insert(vid) {
169                    return Ok(Value::Bool(false));
170                }
171            }
172            Ok(Value::Bool(true))
173        }
174        Value::Null(_) => Ok(Value::Null(NullType::Null)),
175        _ => Err(ExpressionError::type_error(
176            "is_acyclic requires a path type",
177        )),
178    }
179}
180
181fn execute_length(args: &[Value]) -> Result<Value, ExpressionError> {
182    match &args[0] {
183        Value::Path(path) => Ok(Value::BigInt(path.steps.len() as i64)),
184        Value::String(s) => Ok(Value::BigInt(s.chars().count() as i64)),
185        Value::List(list) => Ok(Value::BigInt(list.values.len() as i64)),
186        Value::Null(_) => Ok(Value::Null(NullType::Null)),
187        _ => Err(ExpressionError::type_error(
188            "length requires a string, path, or list type",
189        )),
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196    use linkrs_core::types::VertexId;
197    use linkrs_core::vertex_edge_path::{Edge, Path, Step, Tag, Vertex};
198    use std::collections::HashMap;
199
200    fn create_test_vertex_with_id(id: i64) -> Vertex {
201        Vertex::new(
202            VertexId::try_from_int64(id).expect("valid vertex id"),
203            Tag::new("person".into(), HashMap::new()),
204        )
205    }
206
207    fn create_test_path() -> Path {
208        let v1 = create_test_vertex_with_id(1);
209        let v2 = create_test_vertex_with_id(2);
210        let v3 = create_test_vertex_with_id(3);
211
212        let e1 = Edge::new(
213            VertexId::try_from_int64(1).expect("valid vertex id"),
214            VertexId::try_from_int64(2).expect("valid vertex id"),
215            "knows".to_string(),
216            0,
217            HashMap::new(),
218        );
219        let e2 = Edge::new(
220            VertexId::try_from_int64(2).expect("valid vertex id"),
221            VertexId::try_from_int64(3).expect("valid vertex id"),
222            "follows".to_string(),
223            0,
224            HashMap::new(),
225        );
226
227        let mut path = Path::new(v1);
228        path.add_step(Step {
229            dst: Box::new(v2),
230            edge: Box::new(e1),
231        });
232        path.add_step(Step {
233            dst: Box::new(v3),
234            edge: Box::new(e2),
235        });
236        path
237    }
238
239    #[test]
240    fn test_nodes_function() {
241        let path = create_test_path();
242        let result = PathFunction::Nodes
243            .execute(&[Value::Path(Box::new(path))])
244            .expect("The execution of the nodes function should succeed");
245
246        if let Value::List(nodes) = result {
247            assert_eq!(nodes.values.len(), 3);
248            if let Value::Vertex(v) = &nodes.values[0] {
249                assert_eq!(v.vid.as_int64(), Some(1));
250            } else {
251                panic!("The first node should be the vertex.");
252            }
253            if let Value::Vertex(v) = &nodes.values[1] {
254                assert_eq!(v.vid.as_int64(), Some(2));
255            } else {
256                panic!("The second node should be the vertex.");
257            }
258            if let Value::Vertex(v) = &nodes.values[2] {
259                assert_eq!(v.vid.as_int64(), Some(3));
260            } else {
261                panic!("The third node should be the vertex.");
262            }
263        } else {
264            panic!("The `nodes` function should return a list.");
265        }
266    }
267
268    #[test]
269    fn test_relationships_function() {
270        let path = create_test_path();
271        let result = PathFunction::Relationships
272            .execute(&[Value::Path(Box::new(path))])
273            .expect("The relationships function should execute successfully");
274
275        if let Value::List(edges) = result {
276            assert_eq!(edges.values.len(), 2);
277            if let Value::Edge(e) = &edges.values[0] {
278                assert_eq!(e.edge_type, "knows");
279            } else {
280                panic!("The first element should be the edge.");
281            }
282            if let Value::Edge(e) = &edges.values[1] {
283                assert_eq!(e.edge_type, "follows");
284            } else {
285                panic!("The second element should be the edge.");
286            }
287        } else {
288            panic!("The `relationships` function should return a list.");
289        }
290    }
291
292    #[test]
293    fn test_nodes_empty_path() {
294        let v1 = create_test_vertex_with_id(1);
295        let path = Path::new(v1);
296        let result = PathFunction::Nodes
297            .execute(&[Value::Path(Box::new(path))])
298            .expect("The execution of the nodes function should succeed");
299
300        if let Value::List(nodes) = result {
301            assert_eq!(nodes.values.len(), 1);
302        } else {
303            panic!("The `nodes` function should return a list.");
304        }
305    }
306
307    #[test]
308    fn test_relationships_empty_path() {
309        let v1 = create_test_vertex_with_id(1);
310        let path = Path::new(v1);
311        let result = PathFunction::Relationships
312            .execute(&[Value::Path(Box::new(path))])
313            .expect("The relationships function should execute successfully");
314
315        if let Value::List(edges) = result {
316            assert_eq!(edges.values.len(), 0);
317        } else {
318            panic!("The `relationships` function should return a list.");
319        }
320    }
321
322    #[test]
323    fn test_null_handling() {
324        let null_value = Value::Null(NullType::Null);
325
326        assert_eq!(
327            PathFunction::Nodes
328                .execute(std::slice::from_ref(&null_value))
329                .expect("The nodes function should handle NULL"),
330            Value::Null(NullType::Null)
331        );
332        assert_eq!(
333            PathFunction::Relationships
334                .execute(std::slice::from_ref(&null_value))
335                .expect("The relationshipships function should handle NULL."),
336            Value::Null(NullType::Null)
337        );
338    }
339
340    #[test]
341    fn test_is_trail() {
342        let path = create_test_path();
343        let result = PathFunction::IsTrail
344            .execute(&[Value::Path(Box::new(path))])
345            .expect("is_trail should succeed");
346        assert_eq!(result, Value::Bool(true));
347    }
348
349    #[test]
350    fn test_is_acyclic() {
351        let path = create_test_path();
352        let result = PathFunction::IsAcyclic
353            .execute(&[Value::Path(Box::new(path))])
354            .expect("is_acyclic should succeed");
355        assert_eq!(result, Value::Bool(true));
356    }
357
358    #[test]
359    fn test_path_length() {
360        let path = create_test_path();
361        let result = PathFunction::PathLength
362            .execute(&[Value::Path(Box::new(path))])
363            .expect("length should succeed");
364        assert_eq!(result, Value::BigInt(2));
365    }
366
367    #[test]
368    fn test_path_length_string() {
369        let result = PathFunction::PathLength
370            .execute(&[Value::string("hello")])
371            .expect("length should succeed");
372        assert_eq!(result, Value::BigInt(5));
373    }
374
375    #[test]
376    fn test_path_length_list() {
377        let list = Value::list(List {
378            values: vec![Value::Int(1), Value::Int(2), Value::Int(3)],
379        });
380        let result = PathFunction::PathLength
381            .execute(&[list])
382            .expect("length should succeed");
383        assert_eq!(result, Value::BigInt(3));
384    }
385
386    #[test]
387    fn test_path_length_non_ascii_counts_chars() {
388        let result = PathFunction::PathLength
389            .execute(&[Value::string("你好")])
390            .expect("length should succeed");
391        assert_eq!(result, Value::BigInt(2));
392    }
393
394    #[test]
395    fn test_path_length_arity() {
396        use crate::executor::expression::ExpressionErrorType;
397        let err = PathFunction::PathLength.execute(&[]).unwrap_err();
398        assert_eq!(err.error_type, ExpressionErrorType::InvalidArgumentCount);
399        assert!(err.message.contains("length"));
400        assert!(PathFunction::PathLength
401            .execute(&[Value::string("a"), Value::string("b")])
402            .is_err());
403    }
404}