1use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(untagged)]
16pub enum RowCount {
17 Literal(u64),
18 Parameter(String),
19}
20
21impl RowCount {
22 pub fn value(&self) -> crate::error::Result<u64> {
24 match self {
25 Self::Literal(value) => Ok(*value),
26 Self::Parameter(name) => Err(crate::error::GraphError::PlanError {
27 message: format!("Unresolved pagination parameter: ${name}"),
28 location: snafu::Location::new(file!(), line!(), column!()),
29 }),
30 }
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36pub struct CypherQuery {
37 pub reading_clauses: Vec<ReadingClause>,
39 pub where_clause: Option<WhereClause>,
41 pub with_clause: Option<WithClause>,
43 pub post_with_reading_clauses: Vec<ReadingClause>,
45 pub post_with_where_clause: Option<WhereClause>,
47 #[serde(default)]
50 pub further_with_stages: Vec<WithStage>,
51 pub return_clause: ReturnClause,
53 pub limit: Option<RowCount>,
55 pub order_by: Option<OrderByClause>,
57 pub skip: Option<RowCount>,
59 #[serde(default, skip_serializing_if = "Vec::is_empty")]
61 pub union_branches: Vec<UnionBranch>,
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct UnionBranch {
67 pub all: bool,
68 pub query: CypherQuery,
69}
70
71impl CypherQuery {
72 pub(crate) fn all_reading_clauses(&self) -> impl Iterator<Item = &ReadingClause> {
73 self.reading_clauses
74 .iter()
75 .chain(&self.post_with_reading_clauses)
76 .chain(
77 self.further_with_stages
78 .iter()
79 .flat_map(|stage| &stage.reading_clauses),
80 )
81 }
82
83 pub(crate) fn for_each_query(&self, visit: &mut dyn FnMut(&Self)) {
85 visit(self);
86 for clause in self.all_reading_clauses() {
87 if let ReadingClause::Call(query) = clause {
88 query.for_each_query(visit);
89 }
90 }
91 for branch in &self.union_branches {
92 branch.query.for_each_query(visit);
93 }
94 }
95
96 pub fn get_node_labels(&self) -> Vec<String> {
98 let mut labels = Vec::new();
99 for clause in self.all_reading_clauses() {
101 if let ReadingClause::Call(inner) = clause {
102 for label in inner.get_node_labels() {
103 if !labels.contains(&label) {
104 labels.push(label);
105 }
106 }
107 }
108 if let ReadingClause::Match(match_clause) = clause {
109 for pattern in &match_clause.patterns {
110 match pattern {
111 GraphPattern::Node(node) => {
112 for label in &node.labels {
113 if !labels.contains(label) {
114 labels.push(label.clone());
115 }
116 }
117 }
118 GraphPattern::Path(path) => {
119 for label in &path.start_node.labels {
120 if !labels.contains(label) {
121 labels.push(label.clone());
122 }
123 }
124 for segment in &path.segments {
125 for label in &segment.end_node.labels {
126 if !labels.contains(label) {
127 labels.push(label.clone());
128 }
129 }
130 }
131 }
132 }
133 }
134 }
135 }
136 for branch in &self.union_branches {
137 for label in branch.query.get_node_labels() {
138 if !labels.contains(&label) {
139 labels.push(label);
140 }
141 }
142 }
143 labels
144 }
145
146 pub fn get_relationship_types(&self) -> Vec<String> {
148 let mut types = Vec::new();
149 for clause in self.all_reading_clauses() {
150 if let ReadingClause::Call(inner) = clause {
151 for kind in inner.get_relationship_types() {
152 if !types.contains(&kind) {
153 types.push(kind);
154 }
155 }
156 }
157 if let ReadingClause::Match(match_clause) = clause {
158 for pattern in &match_clause.patterns {
159 self.collect_relationship_types_from_pattern(pattern, &mut types);
160 }
161 }
162 }
163 for branch in &self.union_branches {
164 for rel_type in branch.query.get_relationship_types() {
165 if !types.contains(&rel_type) {
166 types.push(rel_type);
167 }
168 }
169 }
170 types
171 }
172
173 fn collect_relationship_types_from_pattern(
174 &self,
175 pattern: &GraphPattern,
176 types: &mut Vec<String>,
177 ) {
178 if let GraphPattern::Path(path) = pattern {
179 for segment in &path.segments {
180 for rel_type in &segment.relationship.types {
181 if !types.contains(rel_type) {
182 types.push(rel_type.clone());
183 }
184 }
185 }
186 }
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192pub enum ReadingClause {
193 Match(MatchClause),
194 Unwind(UnwindClause),
195 Call(Box<CypherQuery>),
197}
198
199impl CypherQuery {
200 pub(crate) fn call_union_imports(&self) -> crate::error::Result<Vec<String>> {
202 let mut names = self.call_imports()?;
203 for branch in &self.union_branches {
204 names.extend(branch.query.call_union_imports()?);
205 }
206 names.sort();
207 names.dedup();
208 Ok(names)
209 }
210
211 pub(crate) fn call_imports(&self) -> crate::error::Result<Vec<String>> {
213 let Some(clause) = self
214 .with_clause
215 .as_ref()
216 .filter(|_| self.reading_clauses.is_empty())
217 else {
218 return Ok(vec![]);
219 };
220 if clause.distinct
221 || clause.order_by.is_some()
222 || clause.limit.is_some()
223 || clause.where_clause.is_some()
224 {
225 return Err(crate::error::GraphError::PlanError {
226 message: "CALL importing WITH must contain only unaliased variable names; use a second WITH for expressions or filters".into(),
227 location: snafu::Location::new(file!(), line!(), column!()),
228 });
229 }
230 clause.items.iter().map(|item| match (&item.expression, &item.alias) {
231 (ValueExpression::Variable(name), None) => Ok(name.to_lowercase()),
232 _ => Err(crate::error::GraphError::PlanError {
233 message: "CALL importing WITH must contain only unaliased variable names; use a second WITH for expressions".into(),
234 location: snafu::Location::new(file!(), line!(), column!()),
235 }),
236 }).collect()
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242pub struct MatchClause {
243 pub patterns: Vec<GraphPattern>,
245 #[serde(default)]
247 pub optional: bool,
248 #[serde(default)]
252 pub where_clause: Option<WhereClause>,
253}
254
255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
257pub struct UnwindClause {
258 pub expression: ValueExpression,
260 pub alias: String,
262}
263
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266pub enum GraphPattern {
267 Node(NodePattern),
269 Path(PathPattern),
271}
272
273#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275pub struct NodePattern {
276 pub variable: Option<String>,
278 pub labels: Vec<String>,
280 pub properties: HashMap<String, PropertyValue>,
282}
283
284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286pub struct PathPattern {
287 #[serde(default)]
289 pub variable: Option<String>,
290 pub start_node: NodePattern,
292 pub segments: Vec<PathSegment>,
294 #[serde(default)]
297 pub shortest: Option<ShortestPathSpec>,
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302pub struct ShortestPathSpec {
303 pub all: bool,
305 pub variable: Option<String>,
307}
308
309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311pub struct PathSegment {
312 pub relationship: RelationshipPattern,
314 pub end_node: NodePattern,
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320pub struct RelationshipPattern {
321 pub variable: Option<String>,
323 pub types: Vec<String>,
325 pub direction: RelationshipDirection,
327 pub properties: HashMap<String, PropertyValue>,
329 pub length: Option<LengthRange>,
331}
332
333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
335pub enum RelationshipDirection {
336 Outgoing,
338 Incoming,
340 Undirected,
342}
343
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub struct LengthRange {
347 pub min: Option<u32>,
349 pub max: Option<u32>,
351}
352
353#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
355pub enum PropertyValue {
356 String(String),
358 Integer(i64),
360 Float(f64),
362 Boolean(bool),
364 Null,
366 Parameter(String),
368 Property(PropertyRef),
370 Variable(String),
373}
374
375#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
377pub struct PropertyRef {
378 pub variable: String,
380 pub property: String,
382}
383
384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub struct WhereClause {
387 pub expression: BooleanExpression,
389}
390
391#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393pub enum BooleanExpression {
394 Comparison {
396 left: ValueExpression,
397 operator: ComparisonOperator,
398 right: ValueExpression,
399 },
400 And(Box<BooleanExpression>, Box<BooleanExpression>),
402 Or(Box<BooleanExpression>, Box<BooleanExpression>),
404 Not(Box<BooleanExpression>),
406 Exists(PropertyRef),
408 PatternExists {
411 patterns: Vec<GraphPattern>,
412 where_clause: Option<Box<BooleanExpression>>,
413 },
414 In {
416 expression: ValueExpression,
417 list: Vec<ValueExpression>,
418 },
419 Member {
422 expression: ValueExpression,
423 list: ValueExpression,
424 },
425 Like {
427 expression: ValueExpression,
428 pattern: String,
429 },
430 ILike {
432 expression: ValueExpression,
433 pattern: String,
434 },
435 Contains {
437 expression: ValueExpression,
438 substring: String,
439 },
440 StartsWith {
442 expression: ValueExpression,
443 prefix: String,
444 },
445 EndsWith {
447 expression: ValueExpression,
448 suffix: String,
449 },
450 IsNull(ValueExpression),
452 IsNotNull(ValueExpression),
454}
455
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
458pub enum DistanceMetric {
459 L2,
461 #[default]
463 Cosine,
464 Dot,
466}
467
468#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
470pub enum ComparisonOperator {
471 Equal,
472 NotEqual,
473 LessThan,
474 LessThanOrEqual,
475 GreaterThan,
476 GreaterThanOrEqual,
477}
478
479#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
481pub enum ValueExpression {
482 Variable(String),
484 Property(PropertyRef),
486 Literal(PropertyValue),
488 Case {
491 whens: Vec<(BooleanExpression, ValueExpression)>,
492 otherwise: Option<Box<ValueExpression>>,
493 },
494 ScalarFunction {
497 name: String,
498 args: Vec<ValueExpression>,
499 },
500 AggregateFunction {
503 name: String,
504 args: Vec<ValueExpression>,
505 distinct: bool,
507 },
508 Arithmetic {
510 left: Box<ValueExpression>,
511 operator: ArithmeticOperator,
512 right: Box<ValueExpression>,
513 },
514 VectorDistance {
517 left: Box<ValueExpression>,
518 right: Box<ValueExpression>,
519 metric: DistanceMetric,
520 },
521 VectorSimilarity {
524 left: Box<ValueExpression>,
525 right: Box<ValueExpression>,
526 metric: DistanceMetric,
527 },
528 Parameter(String),
530 Boolean(Box<BooleanExpression>),
532 Map(Vec<(String, ValueExpression)>),
534 Field {
537 base: Box<ValueExpression>,
538 field: String,
539 },
540 PatternComprehension {
543 pattern: Box<PathPattern>,
544 filter: Option<Box<BooleanExpression>>,
545 map: Box<ValueExpression>,
546 },
547 ListComprehension {
549 variable: String,
550 list: Box<ValueExpression>,
551 filter: Option<Box<BooleanExpression>>,
552 map: Option<Box<ValueExpression>>,
553 },
554 LocalVariable(String),
556 Reduce {
558 accumulator: String,
559 initial: Box<ValueExpression>,
560 variable: String,
561 list: Box<ValueExpression>,
562 expression: Box<ValueExpression>,
563 },
564 ListPredicate {
566 kind: String,
567 variable: String,
568 list: Box<ValueExpression>,
569 predicate: Box<ValueExpression>,
570 },
571 VectorLiteral(Vec<f32>),
574}
575
576#[derive(Debug, Clone, PartialEq)]
578pub enum FunctionType {
579 Aggregate,
581 Scalar,
583 Unknown,
585}
586
587pub fn classify_function(name: &str) -> FunctionType {
589 match name.to_lowercase().as_str() {
590 "count" | "sum" | "avg" | "min" | "max" | "collect" => FunctionType::Aggregate,
591 "tolower" | "lower" | "toupper" | "upper" | "coalesce" | "date_part" | "to_epoch_ms"
592 | "length" | "nodes" | "size" | "range" | "__index" | "__list" | "tostring" | "head"
593 | "tointeger" | "tofloat" | "date" | "datetime" | "duration.inseconds" => {
594 FunctionType::Scalar
595 }
596 _ => FunctionType::Unknown,
598 }
599}
600
601#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
603pub enum ArithmeticOperator {
604 Add,
605 Subtract,
606 Multiply,
607 Divide,
608 Modulo,
609}
610
611#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616pub struct WithClause {
617 #[serde(default)]
619 pub distinct: bool,
620 pub items: Vec<ReturnItem>,
622 pub order_by: Option<OrderByClause>,
624 pub limit: Option<RowCount>,
626 #[serde(default)]
628 pub where_clause: Option<WhereClause>,
629}
630
631#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
634pub struct WithStage {
635 pub with_clause: WithClause,
636 pub reading_clauses: Vec<ReadingClause>,
637 pub where_clause: Option<WhereClause>,
638}
639
640#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
642pub struct ReturnClause {
643 pub distinct: bool,
645 pub items: Vec<ReturnItem>,
647}
648
649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
651pub struct ReturnItem {
652 pub expression: ValueExpression,
654 pub alias: Option<String>,
656}
657
658#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
660pub struct OrderByClause {
661 pub items: Vec<OrderByItem>,
663}
664
665#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
667pub struct OrderByItem {
668 pub expression: ValueExpression,
670 pub direction: SortDirection,
672}
673
674#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
676pub enum SortDirection {
677 Ascending,
678 Descending,
679}
680
681impl NodePattern {
682 pub fn new(variable: Option<String>) -> Self {
684 Self {
685 variable,
686 labels: Vec::new(),
687 properties: HashMap::new(),
688 }
689 }
690
691 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
693 self.labels.push(label.into());
694 self
695 }
696
697 pub fn with_property<S: Into<String>>(mut self, key: S, value: PropertyValue) -> Self {
699 self.properties.insert(key.into(), value);
700 self
701 }
702}
703
704impl RelationshipPattern {
705 pub fn new(direction: RelationshipDirection) -> Self {
707 Self {
708 variable: None,
709 types: Vec::new(),
710 direction,
711 properties: HashMap::new(),
712 length: None,
713 }
714 }
715
716 pub fn with_variable<S: Into<String>>(mut self, variable: S) -> Self {
718 self.variable = Some(variable.into());
719 self
720 }
721
722 pub fn with_type<S: Into<String>>(mut self, rel_type: S) -> Self {
724 self.types.push(rel_type.into());
725 self
726 }
727
728 pub fn with_property<S: Into<String>>(mut self, key: S, value: PropertyValue) -> Self {
730 self.properties.insert(key.into(), value);
731 self
732 }
733}
734
735impl PropertyRef {
736 pub fn new<S: Into<String>>(variable: S, property: S) -> Self {
738 Self {
739 variable: variable.into(),
740 property: property.into(),
741 }
742 }
743}
744
745pub fn transform_value(expr: &mut ValueExpression, f: &mut dyn FnMut(&mut ValueExpression)) {
748 match expr {
749 ValueExpression::ScalarFunction { args, .. }
750 | ValueExpression::AggregateFunction { args, .. } => {
751 args.iter_mut().for_each(|a| transform_value(a, f))
752 }
753 ValueExpression::Arithmetic { left, right, .. }
754 | ValueExpression::VectorDistance { left, right, .. }
755 | ValueExpression::VectorSimilarity { left, right, .. } => {
756 transform_value(left, f);
757 transform_value(right, f);
758 }
759 ValueExpression::Case { whens, otherwise } => {
760 for (condition, then) in whens {
761 transform_boolean_values(condition, f);
762 transform_value(then, f);
763 }
764 if let Some(e) = otherwise {
765 transform_value(e, f);
766 }
767 }
768 ValueExpression::Boolean(b) => transform_boolean_values(b, f),
769 ValueExpression::Map(entries) => {
770 entries.iter_mut().for_each(|(_, v)| transform_value(v, f))
771 }
772 ValueExpression::Field { base, .. } => transform_value(base, f),
773 ValueExpression::ListComprehension {
774 list, filter, map, ..
775 } => {
776 transform_value(list, f);
777 if let Some(b) = filter {
778 transform_boolean_values(b, f);
779 }
780 if let Some(m) = map {
781 transform_value(m, f);
782 }
783 }
784 ValueExpression::PatternComprehension { filter, map, .. } => {
785 if let Some(b) = filter {
786 transform_boolean_values(b, f);
787 }
788 transform_value(map, f);
789 }
790 ValueExpression::Reduce {
791 initial,
792 list,
793 expression,
794 ..
795 } => {
796 transform_value(initial, f);
797 transform_value(list, f);
798 transform_value(expression, f);
799 }
800 ValueExpression::ListPredicate {
801 list, predicate, ..
802 } => {
803 transform_value(list, f);
804 transform_value(predicate, f);
805 }
806 ValueExpression::Variable(_)
807 | ValueExpression::LocalVariable(_)
808 | ValueExpression::Property(_)
809 | ValueExpression::Literal(_)
810 | ValueExpression::Parameter(_)
811 | ValueExpression::VectorLiteral(_) => {}
812 }
813 f(expr);
814}
815
816pub(crate) fn mark_locals(expr: &mut ValueExpression, names: &[&str]) {
818 transform_value(expr, &mut |value| match value {
819 ValueExpression::Variable(name) if names.iter().any(|n| n.eq_ignore_ascii_case(name)) => {
820 *value = ValueExpression::LocalVariable(name.to_lowercase());
821 }
822 ValueExpression::Property(p)
823 if names.iter().any(|n| n.eq_ignore_ascii_case(&p.variable)) =>
824 {
825 *value = ValueExpression::Field {
826 base: Box::new(ValueExpression::LocalVariable(p.variable.to_lowercase())),
827 field: p.property.clone(),
828 };
829 }
830 _ => {}
831 });
832}
833
834pub(crate) fn needs_relational_evaluation(expr: &ValueExpression) -> bool {
836 fn boolean_has_pattern(expr: &BooleanExpression) -> bool {
837 match expr {
838 BooleanExpression::PatternExists { .. } => true,
839 BooleanExpression::And(a, b) | BooleanExpression::Or(a, b) => {
840 boolean_has_pattern(a) || boolean_has_pattern(b)
841 }
842 BooleanExpression::Not(b) => boolean_has_pattern(b),
843 _ => false,
844 }
845 }
846 let mut found = false;
847 transform_value(&mut expr.clone(), &mut |value| {
848 found |= match value {
849 ValueExpression::PatternComprehension { .. }
850 | ValueExpression::ListComprehension { .. } => true,
851 ValueExpression::Boolean(b) => boolean_has_pattern(b),
852 ValueExpression::Case { whens, .. } => {
853 whens.iter().any(|(b, _)| boolean_has_pattern(b))
854 }
855 _ => false,
856 };
857 });
858 found
859}
860
861pub fn transform_boolean_values(
863 expr: &mut BooleanExpression,
864 f: &mut dyn FnMut(&mut ValueExpression),
865) {
866 match expr {
867 BooleanExpression::Comparison { left, right, .. } => {
868 transform_value(left, f);
869 transform_value(right, f);
870 }
871 BooleanExpression::And(a, b) | BooleanExpression::Or(a, b) => {
872 transform_boolean_values(a, f);
873 transform_boolean_values(b, f);
874 }
875 BooleanExpression::Not(inner) => transform_boolean_values(inner, f),
876 BooleanExpression::In { expression, list } => {
877 transform_value(expression, f);
878 list.iter_mut().for_each(|v| transform_value(v, f));
879 }
880 BooleanExpression::Member { expression, list } => {
881 transform_value(expression, f);
882 transform_value(list, f);
883 }
884 BooleanExpression::Like { expression, .. }
885 | BooleanExpression::ILike { expression, .. }
886 | BooleanExpression::Contains { expression, .. }
887 | BooleanExpression::StartsWith { expression, .. }
888 | BooleanExpression::EndsWith { expression, .. }
889 | BooleanExpression::IsNull(expression)
890 | BooleanExpression::IsNotNull(expression) => transform_value(expression, f),
891 BooleanExpression::Exists(_) | BooleanExpression::PatternExists { .. } => {}
892 }
893}
894
895#[cfg(test)]
896mod tests {
897 use super::*;
898
899 #[test]
900 fn test_node_pattern_creation() {
901 let node = NodePattern::new(Some("n".to_string()))
902 .with_label("Person")
903 .with_property("name", PropertyValue::String("John".to_string()));
904
905 assert_eq!(node.variable, Some("n".to_string()));
906 assert_eq!(node.labels, vec!["Person"]);
907 assert_eq!(node.properties.len(), 1);
908 }
909
910 #[test]
911 fn test_relationship_pattern_creation() {
912 let rel = RelationshipPattern::new(RelationshipDirection::Outgoing)
913 .with_variable("r")
914 .with_type("KNOWS");
915
916 assert_eq!(rel.variable, Some("r".to_string()));
917 assert_eq!(rel.types, vec!["KNOWS"]);
918 assert_eq!(rel.direction, RelationshipDirection::Outgoing);
919 }
920
921 #[test]
922 fn test_property_ref() {
923 let prop_ref = PropertyRef::new("n", "name");
924 assert_eq!(prop_ref.variable, "n");
925 assert_eq!(prop_ref.property, "name");
926 }
927}