1use super::types::*;
6use linkrs_core::types::expr::analysis_utils::collect_variables_from_contextual;
7use linkrs_core::types::expr::contextual::ContextualExpression;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum Pattern {
12 Node(NodePattern),
13 Edge(EdgePattern),
14 Path(PathPattern),
15 Variable(VariablePattern),
16}
17
18impl Pattern {
19 pub fn span(&self) -> Span {
21 match self {
22 Pattern::Node(p) => p.span,
23 Pattern::Edge(p) => p.span,
24 Pattern::Path(p) => p.span,
25 Pattern::Variable(p) => p.span,
26 }
27 }
28
29 pub fn to_pattern_string(&self) -> Option<String> {
37 match self {
38 Pattern::Node(node) => render_node(node),
39 Pattern::Path(path) => {
40 let mut out = String::new();
41 for element in &path.elements {
42 match element {
43 PathElement::Node(node) => out.push_str(&render_node(node)?),
44 PathElement::Edge(edge) => out.push_str(&render_edge(edge)?),
45 PathElement::CreateEdge(edge) => out.push_str(&render_create_edge(edge)?),
46 _ => return None,
47 }
48 }
49 Some(out)
50 }
51 _ => None,
52 }
53 }
54}
55
56#[derive(Debug, Clone, PartialEq)]
58pub struct NodePattern {
59 pub span: Span,
60 pub variable: Option<String>,
61 pub labels: Vec<String>,
62 pub properties: Option<ContextualExpression>,
63 pub predicates: Vec<ContextualExpression>,
64}
65
66impl NodePattern {
67 pub fn new(
68 variable: Option<String>,
69 labels: Vec<String>,
70 properties: Option<ContextualExpression>,
71 predicates: Vec<ContextualExpression>,
72 span: Span,
73 ) -> Self {
74 Self {
75 span,
76 variable,
77 labels,
78 properties,
79 predicates,
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq)]
86pub struct EdgePattern {
87 pub span: Span,
88 pub variable: Option<String>,
89 pub edge_types: Vec<String>,
90 pub properties: Option<ContextualExpression>,
91 pub predicates: Vec<ContextualExpression>,
92 pub direction: EdgeDirection,
93 pub range: Option<EdgeRange>,
94 pub path_semantic: Option<PathSemantic>,
95 pub recursive_comprehension: Option<RecursiveComprehension>,
97}
98
99#[derive(Debug, Clone, PartialEq)]
101pub enum PathSemantic {
102 Walk, Trail, Acyclic, Shortest, AllShortest, WeightedShortest(String), }
109
110impl EdgePattern {
111 pub fn new(
112 variable: Option<String>,
113 edge_types: Vec<String>,
114 properties: Option<ContextualExpression>,
115 predicates: Vec<ContextualExpression>,
116 direction: EdgeDirection,
117 range: Option<EdgeRange>,
118 span: Span,
119 ) -> Self {
120 Self {
121 span,
122 variable,
123 edge_types,
124 properties,
125 predicates,
126 direction,
127 range,
128 path_semantic: None,
129 recursive_comprehension: None,
130 }
131 }
132}
133
134#[derive(Debug, Clone, PartialEq)]
141pub struct CreateEdgePattern {
142 pub span: Span,
143 pub variable: Option<String>,
144 pub edge_types: Vec<String>,
145 pub properties: Option<ContextualExpression>,
146 pub direction: EdgeDirection,
147}
148
149#[derive(Debug, Clone, PartialEq)]
151pub struct EdgeRange {
152 pub min: Option<usize>,
153 pub max: Option<usize>,
154}
155
156impl EdgeRange {
157 pub fn new(min: Option<usize>, max: Option<usize>) -> Self {
158 Self { min, max }
159 }
160
161 pub fn fixed(steps: usize) -> Self {
162 Self {
163 min: Some(steps),
164 max: Some(steps),
165 }
166 }
167
168 pub fn range(min: usize, max: usize) -> Self {
169 Self {
170 min: Some(min),
171 max: Some(max),
172 }
173 }
174
175 pub fn at_least(min: usize) -> Self {
176 Self {
177 min: Some(min),
178 max: None,
179 }
180 }
181
182 pub fn at_most(max: usize) -> Self {
183 Self {
184 min: None,
185 max: Some(max),
186 }
187 }
188
189 pub fn any() -> Self {
190 Self {
191 min: None,
192 max: None,
193 }
194 }
195}
196
197#[derive(Debug, Clone, PartialEq)]
199pub struct PathPattern {
200 pub span: Span,
201 pub elements: Vec<PathElement>,
202 pub name: Option<String>,
206}
207
208impl PathPattern {
209 pub fn new(elements: Vec<PathElement>, span: Span) -> Self {
210 Self {
211 span,
212 elements,
213 name: None,
214 }
215 }
216
217 pub fn with_name(elements: Vec<PathElement>, span: Span, name: String) -> Self {
218 Self {
219 span,
220 elements,
221 name: Some(name),
222 }
223 }
224}
225
226#[derive(Debug, Clone, PartialEq)]
228pub enum PathElement {
229 Node(NodePattern),
230 Edge(EdgePattern),
231 CreateEdge(CreateEdgePattern),
233 Alternative(Vec<Pattern>),
234 Optional(Box<PathElement>),
235 Repeated(Box<PathElement>, RepetitionType),
236 Recursive(RecursiveComprehension),
237}
238
239#[derive(Debug, Clone, PartialEq)]
241pub struct RecursiveComprehension {
242 pub span: Span,
243 pub variable: String,
245 pub edge_variable: Option<String>,
247 pub filter_predicate: Option<ContextualExpression>,
249 pub node_projection: Option<ContextualExpression>,
251 pub edge_projection: Option<ContextualExpression>,
253}
254
255#[derive(Debug, Clone, Copy, PartialEq)]
257pub enum RepetitionType {
258 ZeroOrMore, OneOrMore, ZeroOrOne, Exactly(usize), Range(usize, usize), }
264
265#[derive(Debug, Clone, PartialEq)]
267pub struct VariablePattern {
268 pub span: Span,
269 pub name: String,
270}
271
272impl VariablePattern {
273 pub fn new(name: String, span: Span) -> Self {
274 Self { span, name }
275 }
276}
277
278fn render_node(node: &NodePattern) -> Option<String> {
281 let mut out = String::from("(");
282 if let Some(ref var) = node.variable {
283 out.push_str(var);
284 }
285 for label in &node.labels {
286 out.push(':');
287 out.push_str(label);
288 }
289 if let Some(ref props) = node.properties {
290 out.push_str(&render_properties(props)?);
291 }
292 out.push(')');
293 Some(out)
294}
295
296fn render_edge(edge: &EdgePattern) -> Option<String> {
301 if edge.recursive_comprehension.is_some() {
302 return None;
303 }
304 let mut out = String::new();
305 match edge.direction {
306 EdgeDirection::In => out.push_str("<-"),
307 EdgeDirection::Out | EdgeDirection::Both => out.push('-'),
308 }
309 out.push('[');
310 if let Some(ref var) = edge.variable {
311 out.push_str(var);
312 }
313 for (i, edge_type) in edge.edge_types.iter().enumerate() {
314 if i == 0 {
315 out.push(':');
316 } else {
317 out.push_str("|:");
318 }
319 out.push_str(edge_type);
320 }
321 if let Some(ref props) = edge.properties {
322 out.push_str(&render_properties(props)?);
323 }
324 if let Some(ref semantic) = edge.path_semantic {
325 match semantic {
326 PathSemantic::Walk => {
327 if edge.range.is_some() {
328 out.push_str(&render_range(edge.range.as_ref())?);
329 }
330 }
331 PathSemantic::Trail => out.push_str("*TRAIL"),
332 PathSemantic::Acyclic => out.push_str("*ACYCLIC"),
333 PathSemantic::Shortest => out.push_str("*SHORTEST"),
334 PathSemantic::AllShortest => out.push_str("*ALL SHORTEST"),
335 PathSemantic::WeightedShortest(weight) => {
336 out.push_str("*WEIGHTED(");
337 out.push_str(weight);
338 out.push(')');
339 }
340 }
341 } else if let Some(ref range) = edge.range {
342 out.push_str(&render_range(Some(range))?);
343 }
344 out.push(']');
345 match edge.direction {
346 EdgeDirection::Out => out.push_str("->"),
347 EdgeDirection::In | EdgeDirection::Both => out.push('-'),
348 }
349 Some(out)
350}
351
352fn render_create_edge(edge: &CreateEdgePattern) -> Option<String> {
355 let mut out = String::new();
356 match edge.direction {
357 EdgeDirection::In => out.push_str("<-"),
358 EdgeDirection::Out | EdgeDirection::Both => out.push('-'),
359 }
360 out.push('[');
361 if let Some(ref var) = edge.variable {
362 out.push_str(var);
363 }
364 for (i, edge_type) in edge.edge_types.iter().enumerate() {
365 if i == 0 {
366 out.push(':');
367 } else {
368 out.push_str("|:");
369 }
370 out.push_str(edge_type);
371 }
372 if let Some(ref props) = edge.properties {
373 out.push_str(&render_properties(props)?);
374 }
375 out.push(']');
376 match edge.direction {
377 EdgeDirection::Out => out.push_str("->"),
378 EdgeDirection::In | EdgeDirection::Both => out.push('-'),
379 }
380 Some(out)
381}
382
383fn render_range(range: Option<&EdgeRange>) -> Option<String> {
386 let range = range?;
387 let mut out = String::from("*");
388 match (range.min, range.max) {
389 (None, None) => {}
390 (Some(min), Some(max)) if min == max => out.push_str(&min.to_string()),
391 (Some(min), Some(max)) => {
392 out.push_str(&min.to_string());
393 out.push_str("..");
394 out.push_str(&max.to_string());
395 }
396 (Some(min), None) => {
397 out.push_str(&min.to_string());
398 out.push_str("..");
399 }
400 (None, Some(max)) => {
401 out.push_str("0..");
402 out.push_str(&max.to_string());
403 }
404 }
405 Some(out)
406}
407
408fn render_properties(props: &ContextualExpression) -> Option<String> {
410 let expr = props.get_expression()?;
411 match expr {
412 linkrs_core::types::expr::Expression::Map(entries) => {
413 let mut out = String::from(" {");
414 for (i, (key, value)) in entries.iter().enumerate() {
415 if i > 0 {
416 out.push_str(", ");
417 }
418 out.push_str(key);
419 out.push_str(": ");
420 out.push_str(&value.to_expression_string());
421 }
422 out.push('}');
423 Some(out)
424 }
425 _ => None,
426 }
427}
428
429pub struct PatternUtils;
431impl PatternUtils {
432 pub fn find_variables(pattern: &Pattern) -> Vec<String> {
434 let mut variables = Vec::new();
435 Self::find_variables_recursive(pattern, &mut variables);
436 variables
437 }
438
439 fn find_variables_recursive(pattern: &Pattern, variables: &mut Vec<String>) {
440 match pattern {
441 Pattern::Node(p) => {
442 if let Some(ref var) = p.variable {
443 variables.push(var.clone());
444 }
445 if let Some(ref props) = p.properties {
446 variables.extend(collect_variables_from_contextual(props));
447 }
448 for predicate in &p.predicates {
449 variables.extend(collect_variables_from_contextual(predicate));
450 }
451 }
452 Pattern::Edge(p) => {
453 if let Some(ref var) = p.variable {
454 variables.push(var.clone());
455 }
456 if let Some(ref props) = p.properties {
457 variables.extend(collect_variables_from_contextual(props));
458 }
459 for predicate in &p.predicates {
460 variables.extend(collect_variables_from_contextual(predicate));
461 }
462 }
463 Pattern::Path(p) => {
464 if let Some(ref path_name) = p.name {
465 variables.push(path_name.clone());
466 }
467 for element in &p.elements {
468 Self::find_variables_in_element(element, variables);
469 }
470 }
471 Pattern::Variable(p) => {
472 variables.push(p.name.clone());
473 }
474 }
475 }
476
477 fn find_variables_in_element(element: &PathElement, variables: &mut Vec<String>) {
478 match element {
479 PathElement::Node(p) => {
480 if let Some(ref var) = p.variable {
481 variables.push(var.clone());
482 }
483 if let Some(ref props) = p.properties {
484 variables.extend(collect_variables_from_contextual(props));
485 }
486 for predicate in &p.predicates {
487 variables.extend(collect_variables_from_contextual(predicate));
488 }
489 }
490 PathElement::Edge(p) => {
491 if let Some(ref var) = p.variable {
492 variables.push(var.clone());
493 }
494 if let Some(ref props) = p.properties {
495 variables.extend(collect_variables_from_contextual(props));
496 }
497 for predicate in &p.predicates {
498 variables.extend(collect_variables_from_contextual(predicate));
499 }
500 }
501 PathElement::CreateEdge(p) => {
502 if let Some(ref var) = p.variable {
503 variables.push(var.clone());
504 }
505 if let Some(ref props) = p.properties {
506 variables.extend(collect_variables_from_contextual(props));
507 }
508 }
509 PathElement::Alternative(patterns) => {
510 for pattern in patterns {
511 Self::find_variables_recursive(pattern, variables);
512 }
513 }
514 PathElement::Optional(elem) => {
515 Self::find_variables_in_element(elem, variables);
516 }
517 PathElement::Repeated(elem, _) => {
518 Self::find_variables_in_element(elem, variables);
519 }
520 PathElement::Recursive(rc) => {
521 variables.push(rc.variable.clone());
522 if let Some(ref edge_var) = rc.edge_variable {
523 variables.push(edge_var.clone());
524 }
525 if let Some(ref filter) = rc.filter_predicate {
526 variables.extend(collect_variables_from_contextual(filter));
527 }
528 if let Some(ref node_proj) = rc.node_projection {
529 variables.extend(collect_variables_from_contextual(node_proj));
530 }
531 if let Some(ref edge_proj) = rc.edge_projection {
532 variables.extend(collect_variables_from_contextual(edge_proj));
533 }
534 }
535 }
536 }
537
538 pub fn has_variables(pattern: &Pattern) -> bool {
540 !Self::find_variables(pattern).is_empty()
541 }
542
543 pub fn get_labels(pattern: &Pattern) -> Vec<String> {
545 let mut labels = Vec::new();
546 Self::get_labels_recursive(pattern, &mut labels);
547 labels
548 }
549
550 fn get_labels_recursive(pattern: &Pattern, labels: &mut Vec<String>) {
551 match pattern {
552 Pattern::Node(p) => {
553 labels.extend(p.labels.clone());
554 }
555 Pattern::Path(p) => {
556 for element in &p.elements {
557 Self::get_labels_in_element(element, labels);
558 }
559 }
560 _ => {}
561 }
562 }
563
564 fn get_labels_in_element(element: &PathElement, labels: &mut Vec<String>) {
565 match element {
566 PathElement::Node(p) => {
567 labels.extend(p.labels.clone());
568 }
569 PathElement::CreateEdge(_) => {
570 }
572 PathElement::Alternative(patterns) => {
573 for pattern in patterns {
574 Self::get_labels_recursive(pattern, labels);
575 }
576 }
577 PathElement::Optional(elem) => {
578 Self::get_labels_in_element(elem, labels);
579 }
580 PathElement::Repeated(elem, _) => {
581 Self::get_labels_in_element(elem, labels);
582 }
583 PathElement::Recursive(_rc) => {
584 }
587 _ => {}
588 }
589 }
590}
591
592#[cfg(test)]
593mod tests {
594 use super::*;
595
596 #[test]
597 fn test_node_pattern() {
598 let pattern = Pattern::Node(NodePattern::new(
599 Some("n".to_string()),
600 vec!["Person".to_string()],
601 None,
602 vec![],
603 Span::default(),
604 ));
605
606 assert!(matches!(pattern, Pattern::Node(_)));
607 let vars = PatternUtils::find_variables(&pattern);
608 assert_eq!(vars, vec!["n"]);
609 }
610
611 #[test]
612 fn test_edge_pattern() {
613 let pattern = Pattern::Edge(EdgePattern::new(
614 Some("e".to_string()),
615 vec!["KNOWS".to_string()],
616 None,
617 vec![],
618 EdgeDirection::Out,
619 None,
620 Span::default(),
621 ));
622
623 assert!(matches!(pattern, Pattern::Edge(_)));
624 let vars = PatternUtils::find_variables(&pattern);
625 assert_eq!(vars, vec!["e"]);
626 }
627
628 #[test]
629 fn test_path_pattern() {
630 let elements = vec![
631 PathElement::Node(NodePattern::new(
632 Some("a".to_string()),
633 vec![],
634 None,
635 vec![],
636 Span::default(),
637 )),
638 PathElement::Edge(EdgePattern::new(
639 Some("e".to_string()),
640 vec![],
641 None,
642 vec![],
643 EdgeDirection::Out,
644 None,
645 Span::default(),
646 )),
647 PathElement::Node(NodePattern::new(
648 Some("b".to_string()),
649 vec![],
650 None,
651 vec![],
652 Span::default(),
653 )),
654 ];
655
656 let pattern = Pattern::Path(PathPattern::new(elements, Span::default()));
657 let vars = PatternUtils::find_variables(&pattern);
658 assert_eq!(vars, vec!["a", "e", "b"]);
659 }
660
661 #[test]
662 fn test_edge_range() {
663 let range1 = EdgeRange::fixed(2);
664 assert_eq!(range1.min, Some(2));
665 assert_eq!(range1.max, Some(2));
666
667 let range2 = EdgeRange::range(1, 3);
668 assert_eq!(range2.min, Some(1));
669 assert_eq!(range2.max, Some(3));
670
671 let range3 = EdgeRange::at_least(1);
672 assert_eq!(range3.min, Some(1));
673 assert_eq!(range3.max, None);
674
675 let range4 = EdgeRange::any();
676 assert_eq!(range4.min, None);
677 assert_eq!(range4.max, None);
678 }
679}