linkrs_query/executor/expression/functions/builtin/
path.rs1use crate::executor::expression::ExpressionError;
6use linkrs_core::value::list::List;
7use linkrs_core::value::NullType;
8use linkrs_core::Value;
9
10#[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 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 pub fn arity(&self) -> usize {
36 1
37 }
38
39 pub fn is_variadic(&self) -> bool {
41 false
42 }
43
44 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 for (k, v) in path.src.properties().iter() {
112 all_props.insert(k.clone(), v.clone());
113 }
114 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}