1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use uni_common::Value;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub enum TimeTravelSpec {
8 Version(String),
10 Timestamp(String),
12}
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub enum Query {
16 Single(Statement),
17 Union {
18 left: Box<Query>,
19 right: Box<Query>,
20 all: bool,
21 },
22 Schema(Box<SchemaCommand>),
23 Explain(Box<Query>),
24 TimeTravel {
27 query: Box<Query>,
28 spec: TimeTravelSpec,
29 },
30}
31
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub enum SchemaCommand {
34 CreateVectorIndex(CreateVectorIndex),
35 CreateFullTextIndex(CreateFullTextIndex),
36 CreateScalarIndex(CreateScalarIndex),
37 CreateJsonFtsIndex(CreateJsonFtsIndex),
38 DropIndex(DropIndex),
39 CreateConstraint(CreateConstraint),
40 DropConstraint(DropConstraint),
41 CreateLabel(CreateLabel),
42 CreateEdgeType(CreateEdgeType),
43 AlterLabel(AlterLabel),
44 AlterEdgeType(AlterEdgeType),
45 DropLabel(DropLabel),
46 DropEdgeType(DropEdgeType),
47 ShowConstraints(ShowConstraints),
48 ShowIndexes(ShowIndexes),
49 ShowDatabase,
50 ShowConfig,
51 ShowStatistics,
52 Vacuum,
53 Checkpoint,
54 Backup { path: String },
55 CopyTo(CopyToCommand),
56 CopyFrom(CopyFromCommand),
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
60pub struct CreateVectorIndex {
61 pub name: String,
62 pub label: String,
63 pub property: String,
64 pub options: HashMap<String, Value>,
65 pub if_not_exists: bool,
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct CreateFullTextIndex {
70 pub name: String,
71 pub label: String,
72 pub properties: Vec<String>,
73 pub options: HashMap<String, Value>,
74 pub if_not_exists: bool,
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub struct CreateScalarIndex {
79 pub name: String,
80 pub label: String,
81 pub expressions: Vec<Expr>,
82 pub where_clause: Option<Expr>,
83 pub options: HashMap<String, Value>,
84 pub if_not_exists: bool,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct CreateJsonFtsIndex {
89 pub name: String,
90 pub label: String,
91 pub column: String,
92 pub options: HashMap<String, Value>,
93 pub if_not_exists: bool,
94}
95
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct CreateLabel {
98 pub name: String,
99 pub properties: Vec<PropertyDefinition>,
100 pub if_not_exists: bool,
101 pub description: Option<String>,
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct CreateEdgeType {
106 pub name: String,
107 pub src_labels: Vec<String>,
108 pub dst_labels: Vec<String>,
109 pub properties: Vec<PropertyDefinition>,
110 pub if_not_exists: bool,
111 pub description: Option<String>,
112}
113
114#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
115pub struct AlterLabel {
116 pub name: String,
117 pub action: AlterAction,
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub struct AlterEdgeType {
122 pub name: String,
123 pub action: AlterAction,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub enum AlterAction {
128 AddProperty(PropertyDefinition),
129 DropProperty(String),
130 RenameProperty {
131 old_name: String,
132 new_name: String,
133 },
134 SetDescription(Option<String>),
135 SetPropertyDescription {
136 property: String,
137 description: Option<String>,
138 },
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct DropLabel {
143 pub name: String,
144 pub if_exists: bool,
145}
146
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148pub struct DropEdgeType {
149 pub name: String,
150 pub if_exists: bool,
151}
152
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154pub struct ShowConstraints {
155 pub target: Option<ConstraintTarget>,
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub enum ConstraintTarget {
160 Label(String),
161 EdgeType(String),
162}
163
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub struct ShowIndexes {
166 pub filter: Option<String>,
167}
168
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
170pub struct CopyToCommand {
171 pub label: String,
172 pub path: String,
173 pub format: String,
174 pub options: HashMap<String, Value>,
175}
176
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
178pub struct CopyFromCommand {
179 pub label: String,
180 pub path: String,
181 pub format: String,
182 pub options: HashMap<String, Value>,
183}
184
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub struct PropertyDefinition {
187 pub name: String,
188 pub data_type: String, pub nullable: bool,
190 pub unique: bool,
191 pub default: Option<Expr>,
192 pub description: Option<String>,
193}
194
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub struct DropIndex {
197 pub name: String,
198 #[serde(default)]
202 pub if_exists: bool,
203}
204
205#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
206pub struct CreateConstraint {
207 pub name: Option<String>,
208 pub constraint_type: ConstraintType,
209 pub label: String,
210 pub properties: Vec<String>,
211 pub expression: Option<Expr>,
212}
213
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215pub struct DropConstraint {
216 pub name: String,
217 #[serde(default)]
219 pub if_exists: bool,
220}
221
222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223pub enum ConstraintType {
224 Unique,
225 NodeKey,
226 Exists,
227 Check,
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231pub struct Statement {
232 pub clauses: Vec<Clause>,
233}
234
235#[derive(Debug, Clone, PartialEq)]
236pub enum ConstraintDef {
237 Unique(String),
238 NodeKey(Vec<String>),
239 Exists(String),
240 Check(Expr),
241}
242
243#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244pub enum Clause {
245 Match(MatchClause),
246 Create(CreateClause),
247 Merge(MergeClause),
248 With(WithClause),
249 WithRecursive(WithRecursiveClause),
250 Unwind(UnwindClause),
251 Return(ReturnClause),
252 Delete(DeleteClause),
253 Set(SetClause),
254 Remove(RemoveClause),
255 Call(CallClause),
256}
257
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259pub struct MatchClause {
260 pub optional: bool,
261 pub pattern: Pattern,
262 pub where_clause: Option<Expr>,
263 #[serde(default)]
268 pub for_update: bool,
269}
270
271#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
272pub struct CreateClause {
273 pub pattern: Pattern,
274}
275
276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
277pub struct MergeClause {
278 pub pattern: Pattern,
279 pub on_match: Vec<SetItem>,
280 pub on_create: Vec<SetItem>,
281}
282
283#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
284pub struct WithClause {
285 pub distinct: bool,
286 pub items: Vec<ReturnItem>,
287 pub order_by: Option<Vec<SortItem>>,
288 pub skip: Option<Expr>,
289 pub limit: Option<Expr>,
290 pub where_clause: Option<Expr>,
291}
292
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
294pub struct WithRecursiveClause {
295 pub name: String,
296 pub query: Box<Query>,
297 pub items: Vec<ReturnItem>,
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
301pub struct ReturnClause {
302 pub distinct: bool,
303 pub items: Vec<ReturnItem>,
304 pub order_by: Option<Vec<SortItem>>,
305 pub skip: Option<Expr>,
306 pub limit: Option<Expr>,
307}
308
309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
310pub enum ReturnItem {
311 All,
313 Expr {
315 expr: Expr,
316 alias: Option<String>,
317 #[serde(skip_serializing_if = "Option::is_none", default)]
319 source_text: Option<String>,
320 },
321}
322
323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
324pub struct UnwindClause {
325 pub expr: Expr,
326 pub variable: String,
327}
328
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct DeleteClause {
331 pub detach: bool,
332 pub items: Vec<Expr>,
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336pub struct SetClause {
337 pub items: Vec<SetItem>,
338}
339
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341pub enum SetItem {
342 Property {
343 expr: Expr, value: Expr,
345 },
346 Labels {
347 variable: String,
348 labels: Vec<String>,
349 },
350 Variable {
351 variable: String,
352 value: Expr,
353 },
354 VariablePlus {
355 variable: String,
356 value: Expr,
357 },
358}
359
360#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
361pub struct RemoveClause {
362 pub items: Vec<RemoveItem>,
363}
364
365#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
366pub enum RemoveItem {
367 Property(Expr),
368 Labels {
369 variable: String,
370 labels: Vec<String>,
371 },
372}
373
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
375pub struct CallClause {
376 pub kind: CallKind,
377 pub yield_items: Vec<YieldItem>,
378 pub where_clause: Option<Expr>,
379}
380
381#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
382pub enum CallKind {
383 Procedure {
384 procedure: String,
385 arguments: Vec<Expr>,
386 },
387 Subquery(Box<Query>),
388}
389
390#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
391pub struct YieldItem {
392 pub name: String,
393 pub alias: Option<String>,
394}
395
396#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
397pub struct Pattern {
398 pub paths: Vec<PathPattern>,
399}
400
401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
402pub struct PathPattern {
403 pub variable: Option<String>,
404 pub elements: Vec<PatternElement>,
405 pub shortest_path_mode: Option<ShortestPathMode>,
406}
407
408#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409pub enum ShortestPathMode {
410 Shortest, AllShortest, }
413
414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415pub enum PatternElement {
416 Node(NodePattern),
417 Relationship(RelationshipPattern),
418 Parenthesized {
419 pattern: Box<PathPattern>,
420 range: Option<Range>,
421 },
422}
423
424#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
432pub enum LabelExpr {
433 #[default]
435 Empty,
436 Conjunction(Vec<String>),
438 Disjunction(Vec<String>),
441}
442
443impl LabelExpr {
444 pub fn names(&self) -> &[String] {
446 match self {
447 Self::Empty => &[],
448 Self::Conjunction(v) | Self::Disjunction(v) => v,
449 }
450 }
451
452 pub fn is_disjunction(&self) -> bool {
453 matches!(self, Self::Disjunction(_))
454 }
455
456 pub fn is_conjunction(&self) -> bool {
457 matches!(self, Self::Conjunction(_))
458 }
459
460 pub fn is_proper_disjunction(&self) -> bool {
465 matches!(self, Self::Disjunction(v) if v.len() >= 2)
466 }
467
468 pub fn from_conjunction<I, S>(iter: I) -> Self
473 where
474 I: IntoIterator<Item = S>,
475 S: Into<String>,
476 {
477 let v: Vec<String> = iter.into_iter().map(Into::into).collect();
478 if v.is_empty() {
479 Self::Empty
480 } else {
481 Self::Conjunction(v)
482 }
483 }
484
485 pub fn from_disjunction<I, S>(iter: I) -> Self
486 where
487 I: IntoIterator<Item = S>,
488 S: Into<String>,
489 {
490 let v: Vec<String> = iter.into_iter().map(Into::into).collect();
491 if v.is_empty() {
492 Self::Empty
493 } else {
494 Self::Disjunction(v)
495 }
496 }
497}
498
499impl std::ops::Deref for LabelExpr {
504 type Target = [String];
505 fn deref(&self) -> &[String] {
506 self.names()
507 }
508}
509
510impl<'a> IntoIterator for &'a LabelExpr {
511 type Item = &'a String;
512 type IntoIter = std::slice::Iter<'a, String>;
513 fn into_iter(self) -> Self::IntoIter {
514 self.names().iter()
515 }
516}
517
518#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
519pub struct NodePattern {
520 pub variable: Option<String>,
521 pub labels: LabelExpr,
522 pub properties: Option<Expr>, pub where_clause: Option<Expr>, }
525
526#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
527pub struct RelationshipPattern {
528 pub variable: Option<String>,
529 pub types: LabelExpr,
530 pub direction: Direction,
531 pub range: Option<Range>,
532 pub properties: Option<Expr>, pub where_clause: Option<Expr>, }
535
536#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
537pub enum Direction {
538 Outgoing,
539 Incoming,
540 Both,
541}
542
543#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
544pub struct Range {
545 pub min: Option<u32>,
546 pub max: Option<u32>,
547}
548
549#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
550pub struct SortItem {
551 pub expr: Expr,
552 pub ascending: bool,
553}
554
555#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
557pub struct WindowSpec {
558 pub partition_by: Vec<Expr>,
559 pub order_by: Vec<SortItem>,
560}
561
562#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
567pub enum CypherLiteral {
568 Null,
569 Bool(bool),
570 Integer(i64),
571 Float(f64),
572 String(String),
573 Bytes(Vec<u8>),
577}
578
579impl CypherLiteral {
580 pub fn to_value(&self) -> Value {
582 match self {
583 CypherLiteral::Null => Value::Null,
584 CypherLiteral::Bool(b) => Value::Bool(*b),
585 CypherLiteral::Integer(i) => Value::Int(*i),
586 CypherLiteral::Float(f) => Value::Float(*f),
587 CypherLiteral::String(s) => Value::String(s.clone()),
588 CypherLiteral::Bytes(b) => {
589 uni_common::cypher_value_codec::decode(b).unwrap_or(Value::Null)
590 }
591 }
592 }
593}
594
595impl std::fmt::Display for CypherLiteral {
596 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
597 match self {
598 CypherLiteral::Null => f.write_str("null"),
599 CypherLiteral::Bool(b) => write!(f, "{b}"),
600 CypherLiteral::Integer(i) => write!(f, "{i}"),
601 CypherLiteral::Float(v) => write!(f, "{v}"),
602 CypherLiteral::String(s) => write!(f, "\"{s}\""),
603 CypherLiteral::Bytes(b) => write!(f, "<bytes:{}>", b.len()),
604 }
605 }
606}
607
608impl From<i64> for CypherLiteral {
609 fn from(v: i64) -> Self {
610 Self::Integer(v)
611 }
612}
613
614impl From<f64> for CypherLiteral {
615 fn from(v: f64) -> Self {
616 Self::Float(v)
617 }
618}
619
620impl From<bool> for CypherLiteral {
621 fn from(v: bool) -> Self {
622 Self::Bool(v)
623 }
624}
625
626impl From<String> for CypherLiteral {
627 fn from(v: String) -> Self {
628 Self::String(v)
629 }
630}
631
632impl From<&str> for CypherLiteral {
633 fn from(v: &str) -> Self {
634 Self::String(v.to_string())
635 }
636}
637
638#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
639pub enum Expr {
640 Literal(CypherLiteral),
641 Parameter(String),
642 Variable(String),
643 Wildcard,
644 Property(Box<Expr>, String),
645 List(Vec<Expr>),
646 Map(Vec<(String, Expr)>),
647 FunctionCall {
648 name: String,
649 args: Vec<Expr>,
650 distinct: bool,
651 window_spec: Option<WindowSpec>,
652 },
653 BinaryOp {
654 left: Box<Expr>,
655 op: BinaryOp,
656 right: Box<Expr>,
657 },
658 UnaryOp {
659 op: UnaryOp,
660 expr: Box<Expr>,
661 },
662 Case {
663 expr: Option<Box<Expr>>,
664 when_then: Vec<(Expr, Expr)>,
665 else_expr: Option<Box<Expr>>,
666 },
667 Exists {
668 query: Box<Query>,
669 from_pattern_predicate: bool,
671 },
672 CountSubquery(Box<Query>),
673 CollectSubquery(Box<Query>),
674 IsNull(Box<Expr>),
675 IsNotNull(Box<Expr>),
676 IsUnique(Box<Expr>),
677 In {
678 expr: Box<Expr>,
679 list: Box<Expr>,
680 },
681 ArrayIndex {
683 array: Box<Expr>,
684 index: Box<Expr>,
685 },
686 ArraySlice {
687 array: Box<Expr>,
688 start: Option<Box<Expr>>,
689 end: Option<Box<Expr>>,
690 },
691 Quantifier {
693 quantifier: Quantifier,
694 variable: String,
695 list: Box<Expr>,
696 predicate: Box<Expr>,
697 },
698 Reduce {
700 accumulator: String,
701 init: Box<Expr>,
702 variable: String,
703 list: Box<Expr>,
704 expr: Box<Expr>,
705 },
706 ListComprehension {
708 variable: String,
709 list: Box<Expr>,
710 where_clause: Option<Box<Expr>>,
711 map_expr: Box<Expr>,
712 },
713 PatternComprehension {
715 path_variable: Option<String>,
716 pattern: Pattern,
717 where_clause: Option<Box<Expr>>,
718 map_expr: Box<Expr>,
719 },
720 ValidAt {
722 entity: Box<Expr>,
723 timestamp: Box<Expr>,
724 start_prop: Option<String>,
725 end_prop: Option<String>,
726 },
727 MapProjection {
729 base: Box<Expr>,
730 items: Vec<MapProjectionItem>,
731 },
732 LabelCheck {
734 expr: Box<Expr>,
735 labels: Vec<String>,
736 },
737}
738
739#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
740pub enum MapProjectionItem {
741 Property(String), AllProperties, LiteralEntry(String, Box<Expr>), Variable(String), }
746
747impl MapProjectionItem {
748 fn to_string_repr(&self) -> String {
749 match self {
750 MapProjectionItem::Property(prop) => format!(".{prop}"),
751 MapProjectionItem::AllProperties => ".*".to_string(),
752 MapProjectionItem::LiteralEntry(key, expr) => {
753 format!("{key}: {}", expr.to_string_repr())
754 }
755 MapProjectionItem::Variable(v) => v.clone(),
756 }
757 }
758}
759
760#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
761pub enum Quantifier {
762 All,
763 Any,
764 Single,
765 None,
766}
767
768impl std::fmt::Display for Quantifier {
769 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
770 match self {
771 Quantifier::All => f.write_str("ALL"),
772 Quantifier::Any => f.write_str("ANY"),
773 Quantifier::Single => f.write_str("SINGLE"),
774 Quantifier::None => f.write_str("NONE"),
775 }
776 }
777}
778
779#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
780pub enum BinaryOp {
781 Add,
782 Sub,
783 Mul,
784 Div,
785 Mod,
786 Pow,
787 Eq,
788 NotEq,
789 Lt,
790 LtEq,
791 Gt,
792 GtEq,
793 And,
794 Or,
795 Xor,
796 Regex,
797 Contains,
798 StartsWith,
799 EndsWith,
800 ApproxEq,
801}
802
803impl std::fmt::Display for BinaryOp {
804 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805 let s = match self {
806 BinaryOp::Add => "+",
807 BinaryOp::Sub => "-",
808 BinaryOp::Mul => "*",
809 BinaryOp::Div => "/",
810 BinaryOp::Mod => "%",
811 BinaryOp::Pow => "^",
812 BinaryOp::Eq => "=",
813 BinaryOp::NotEq => "<>",
814 BinaryOp::Lt => "<",
815 BinaryOp::LtEq => "<=",
816 BinaryOp::Gt => ">",
817 BinaryOp::GtEq => ">=",
818 BinaryOp::And => "AND",
819 BinaryOp::Or => "OR",
820 BinaryOp::Xor => "XOR",
821 BinaryOp::Regex => "=~",
822 BinaryOp::Contains => "CONTAINS",
823 BinaryOp::StartsWith => "STARTS WITH",
824 BinaryOp::EndsWith => "ENDS WITH",
825 BinaryOp::ApproxEq => "~=",
826 };
827 f.write_str(s)
828 }
829}
830
831#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
832pub enum UnaryOp {
833 Not,
834 Neg,
835}
836
837impl std::fmt::Display for UnaryOp {
838 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
839 match self {
840 UnaryOp::Not => f.write_str("NOT "),
841 UnaryOp::Neg => f.write_str("-"),
842 }
843 }
844}
845
846#[derive(Debug, Clone)]
854pub enum ListAfterIdentifier {
855 Comprehension {
857 list: Expr,
858 filter: Option<Expr>,
859 projection: Box<Expr>,
860 },
861
862 ExpressionTail {
865 suffix: Vec<ExprSuffix>,
866 more: Vec<Expr>,
867 },
868}
869
870impl ListAfterIdentifier {
871 pub fn resolve(self, id: String) -> Expr {
873 match self {
874 ListAfterIdentifier::Comprehension {
875 list,
876 filter,
877 projection,
878 } => Expr::ListComprehension {
879 variable: id,
880 list: Box::new(list),
881 where_clause: filter.map(Box::new),
882 map_expr: projection,
883 },
884 ListAfterIdentifier::ExpressionTail { suffix, more } => {
885 let first = apply_suffixes(Expr::Variable(id), suffix);
886 let items = std::iter::once(first).chain(more).collect();
887 Expr::List(items)
888 }
889 }
890 }
891}
892
893#[derive(Debug, Clone)]
896pub enum ExprSuffix {
897 Property(String),
898 Index(Expr),
899 Slice {
900 start: Option<Expr>,
901 end: Option<Expr>,
902 },
903 FunctionCall(Vec<Expr>),
904 IsNull,
905 IsNotNull,
906 Binary(BinaryOp, Expr),
907 In(Expr),
908}
909
910#[derive(Debug, Clone, PartialEq)]
920pub enum PostfixSuffix {
921 Property(String),
922 Call {
923 args: Vec<Expr>,
924 distinct: bool,
925 window_spec: Option<WindowSpec>,
926 },
927 Index(Expr),
928 Slice {
929 start: Option<Expr>,
930 end: Option<Expr>,
931 },
932 MapProjection(Vec<MapProjectionItem>),
933}
934
935pub fn extract_dotted_name(expr: &Expr) -> Option<String> {
947 match expr {
948 Expr::Variable(name) => Some(name.clone()),
949 Expr::Property(base, prop) => {
950 let base_name = extract_dotted_name(base)?;
951 Some(format!("{base_name}.{prop}"))
952 }
953 _ => None,
954 }
955}
956
957pub fn apply_suffix(expr: Expr, suffix: PostfixSuffix) -> Expr {
963 match suffix {
964 PostfixSuffix::Property(prop) => Expr::Property(Box::new(expr), prop),
965
966 PostfixSuffix::Call {
967 args,
968 distinct,
969 window_spec,
970 } => {
971 let name = extract_dotted_name(&expr).unwrap_or_else(|| {
972 panic!(
973 "apply_suffix: function call requires variable or property chain, got: {expr:?}"
974 )
975 });
976 Expr::FunctionCall {
977 name,
978 args,
979 distinct,
980 window_spec,
981 }
982 }
983
984 PostfixSuffix::Index(index) => Expr::ArrayIndex {
985 array: Box::new(expr),
986 index: Box::new(index),
987 },
988
989 PostfixSuffix::Slice { start, end } => Expr::ArraySlice {
990 array: Box::new(expr),
991 start: start.map(Box::new),
992 end: end.map(Box::new),
993 },
994
995 PostfixSuffix::MapProjection(items) => Expr::MapProjection {
996 base: Box::new(expr),
997 items,
998 },
999 }
1000}
1001
1002fn apply_suffixes(mut expr: Expr, suffixes: Vec<ExprSuffix>) -> Expr {
1005 for suffix in suffixes {
1006 expr = match suffix {
1007 ExprSuffix::Property(name) => Expr::Property(Box::new(expr), name),
1008
1009 ExprSuffix::Index(idx) => Expr::ArrayIndex {
1010 array: Box::new(expr),
1011 index: Box::new(idx),
1012 },
1013
1014 ExprSuffix::Slice { start, end } => Expr::ArraySlice {
1015 array: Box::new(expr),
1016 start: start.map(Box::new),
1017 end: end.map(Box::new),
1018 },
1019
1020 ExprSuffix::FunctionCall(args) => {
1021 let name = extract_dotted_name(&expr)
1022 .unwrap_or_else(|| panic!("Function call suffix requires variable or property chain expression, got: {expr:?}"));
1023 Expr::FunctionCall {
1024 name,
1025 args,
1026 distinct: false,
1027 window_spec: None,
1028 }
1029 }
1030
1031 ExprSuffix::IsNull => Expr::IsNull(Box::new(expr)),
1032 ExprSuffix::IsNotNull => Expr::IsNotNull(Box::new(expr)),
1033
1034 ExprSuffix::Binary(op, rhs) => Expr::BinaryOp {
1035 left: Box::new(expr),
1036 op,
1037 right: Box::new(rhs),
1038 },
1039
1040 ExprSuffix::In(right) => Expr::In {
1041 expr: Box::new(expr),
1042 list: Box::new(right),
1043 },
1044 };
1045 }
1046 expr
1047}
1048
1049fn join_exprs(exprs: &[Expr], sep: &str) -> String {
1051 exprs
1052 .iter()
1053 .map(|e| e.to_string_repr())
1054 .collect::<Vec<_>>()
1055 .join(sep)
1056}
1057
1058impl Expr {
1059 pub const TRUE: Expr = Expr::Literal(CypherLiteral::Bool(true));
1064
1065 pub fn is_true_literal(&self) -> bool {
1067 matches!(self, Expr::Literal(CypherLiteral::Bool(true)))
1068 }
1069
1070 pub fn extract_variable(&self) -> Option<String> {
1072 match self {
1073 Expr::Variable(v) => Some(v.clone()),
1074 _ => None,
1075 }
1076 }
1077
1078 pub fn substitute_variable(&self, old_var: &str, new_var: &str) -> Expr {
1088 let sub = |e: &Expr| e.substitute_variable(old_var, new_var);
1089 let sub_box = |e: &Expr| Box::new(sub(e));
1090 let sub_opt = |o: &Option<Box<Expr>>| o.as_ref().map(|e| sub_box(e));
1091
1092 match self {
1093 Expr::Variable(v) if v == old_var => Expr::Variable(new_var.to_string()),
1094
1095 Expr::Quantifier {
1096 quantifier,
1097 variable,
1098 list,
1099 predicate,
1100 } => {
1101 let shadowed = variable == old_var;
1102 Expr::Quantifier {
1103 quantifier: *quantifier,
1104 variable: variable.clone(),
1105 list: sub_box(list),
1106 predicate: if shadowed {
1107 predicate.clone()
1108 } else {
1109 sub_box(predicate)
1110 },
1111 }
1112 }
1113
1114 Expr::Reduce {
1115 accumulator,
1116 init,
1117 variable,
1118 list,
1119 expr,
1120 } => {
1121 let shadowed = variable == old_var || accumulator == old_var;
1122 Expr::Reduce {
1123 accumulator: accumulator.clone(),
1124 init: sub_box(init),
1125 variable: variable.clone(),
1126 list: sub_box(list),
1127 expr: if shadowed {
1128 expr.clone()
1129 } else {
1130 sub_box(expr)
1131 },
1132 }
1133 }
1134
1135 Expr::ListComprehension {
1136 variable,
1137 list,
1138 where_clause,
1139 map_expr,
1140 } => {
1141 let shadowed = variable == old_var;
1142 Expr::ListComprehension {
1143 variable: variable.clone(),
1144 list: sub_box(list),
1145 where_clause: if shadowed {
1146 where_clause.clone()
1147 } else {
1148 sub_opt(where_clause)
1149 },
1150 map_expr: if shadowed {
1151 map_expr.clone()
1152 } else {
1153 sub_box(map_expr)
1154 },
1155 }
1156 }
1157
1158 Expr::PatternComprehension { path_variable, .. }
1159 if path_variable.as_deref() == Some(old_var) =>
1160 {
1161 self.clone()
1162 }
1163
1164 Expr::MapProjection { base, items } => Expr::MapProjection {
1165 base: sub_box(base),
1166 items: items
1167 .iter()
1168 .map(|item| match item {
1169 MapProjectionItem::LiteralEntry(key, expr) => {
1170 MapProjectionItem::LiteralEntry(key.clone(), sub_box(expr))
1171 }
1172 MapProjectionItem::Variable(v) if v == old_var => {
1173 MapProjectionItem::Variable(new_var.to_string())
1174 }
1175 other => other.clone(),
1176 })
1177 .collect(),
1178 },
1179
1180 _ => self
1184 .clone()
1185 .map_children(&mut |e| e.substitute_variable(old_var, new_var)),
1186 }
1187 }
1188
1189 pub fn is_aggregate(&self) -> bool {
1191 match self {
1192 Expr::FunctionCall {
1193 name, window_spec, ..
1194 } => {
1195 window_spec.is_none()
1196 && (matches!(
1197 name.to_lowercase().as_str(),
1198 "count"
1199 | "sum"
1200 | "avg"
1201 | "min"
1202 | "max"
1203 | "collect"
1204 | "stdev"
1205 | "stdevp"
1206 | "percentiledisc"
1207 | "percentilecont"
1208 ) || crate::plugin_aggregates::is_known_plugin_aggregate(name))
1209 }
1210 Expr::CountSubquery(_) | Expr::CollectSubquery(_) => true,
1211 _ => {
1216 let mut found = false;
1217 self.for_each_child(&mut |c| {
1218 if !found && c.is_aggregate() {
1219 found = true;
1220 }
1221 });
1222 found
1223 }
1224 }
1225 }
1226
1227 pub fn to_string_repr(&self) -> String {
1229 match self {
1230 Expr::Literal(v) => v.to_string(),
1231 Expr::Parameter(p) => format!("${p}"),
1232 Expr::Variable(v) => v.clone(),
1233 Expr::Wildcard => "*".to_string(),
1234 Expr::Property(base, prop) => {
1235 format!("{}.{prop}", base.to_string_repr())
1236 }
1237 Expr::List(exprs) => format!("[{}]", join_exprs(exprs, ", ")),
1238 Expr::Map(entries) => {
1239 let items = entries
1240 .iter()
1241 .map(|(k, v)| format!("{k}: {}", v.to_string_repr()))
1242 .collect::<Vec<_>>()
1243 .join(", ");
1244 format!("{{{items}}}")
1245 }
1246 Expr::FunctionCall {
1247 name,
1248 args,
1249 distinct,
1250 window_spec,
1251 } => {
1252 let args_str = join_exprs(args, ", ");
1253 let distinct_str = if *distinct { "DISTINCT " } else { "" };
1254 let base = format!("{name}({distinct_str}{args_str})");
1255 let Some(window) = window_spec else {
1256 return base;
1257 };
1258 let mut parts = Vec::new();
1259 if !window.partition_by.is_empty() {
1260 parts.push(format!(
1261 "PARTITION BY {}",
1262 join_exprs(&window.partition_by, ", ")
1263 ));
1264 }
1265 if !window.order_by.is_empty() {
1266 let items = window
1267 .order_by
1268 .iter()
1269 .map(|s| {
1270 let dir = if s.ascending { "ASC" } else { "DESC" };
1271 format!("{} {dir}", s.expr.to_string_repr())
1272 })
1273 .collect::<Vec<_>>()
1274 .join(", ");
1275 parts.push(format!("ORDER BY {items}"));
1276 }
1277 format!("{base} OVER ({})", parts.join(" "))
1278 }
1279 Expr::BinaryOp { left, op, right } => {
1280 format!(
1281 "{} {} {}",
1282 left.to_string_repr(),
1283 op,
1284 right.to_string_repr()
1285 )
1286 }
1287 Expr::UnaryOp { op, expr } => {
1288 format!("{op}{}", expr.to_string_repr())
1289 }
1290 Expr::Case {
1291 expr,
1292 when_then,
1293 else_expr,
1294 } => {
1295 let mut s = "CASE".to_string();
1296 if let Some(e) = expr {
1297 s.push_str(&format!(" {}", e.to_string_repr()));
1298 }
1299 for (w, t) in when_then {
1300 s.push_str(&format!(
1301 " WHEN {} THEN {}",
1302 w.to_string_repr(),
1303 t.to_string_repr()
1304 ));
1305 }
1306 if let Some(e) = else_expr {
1307 s.push_str(&format!(" ELSE {}", e.to_string_repr()));
1308 }
1309 s.push_str(" END");
1310 s
1311 }
1312 Expr::Exists { .. } => "EXISTS {...}".to_string(),
1313 Expr::CountSubquery(_) => "COUNT {...}".to_string(),
1314 Expr::CollectSubquery(_) => "COLLECT {...}".to_string(),
1315 Expr::IsNull(e) => format!("{} IS NULL", e.to_string_repr()),
1316 Expr::IsNotNull(e) => format!("{} IS NOT NULL", e.to_string_repr()),
1317 Expr::IsUnique(e) => format!("{} IS UNIQUE", e.to_string_repr()),
1318 Expr::In { expr, list } => {
1319 format!("{} IN {}", expr.to_string_repr(), list.to_string_repr())
1320 }
1321 Expr::ArrayIndex { array, index } => {
1322 format!("{}[{}]", array.to_string_repr(), index.to_string_repr())
1323 }
1324 Expr::ArraySlice { array, start, end } => {
1325 let start_str = start
1326 .as_ref()
1327 .map(|e| e.to_string_repr())
1328 .unwrap_or_default();
1329 let end_str = end.as_ref().map(|e| e.to_string_repr()).unwrap_or_default();
1330 format!("{}[{}..{}]", array.to_string_repr(), start_str, end_str)
1331 }
1332 Expr::Quantifier {
1333 quantifier,
1334 variable,
1335 list,
1336 predicate,
1337 } => {
1338 format!(
1339 "{quantifier}({variable} IN {} WHERE {})",
1340 list.to_string_repr(),
1341 predicate.to_string_repr()
1342 )
1343 }
1344 Expr::Reduce {
1345 accumulator,
1346 init,
1347 variable,
1348 list,
1349 expr,
1350 } => {
1351 format!(
1352 "REDUCE({accumulator} = {}, {variable} IN {} | {})",
1353 init.to_string_repr(),
1354 list.to_string_repr(),
1355 expr.to_string_repr()
1356 )
1357 }
1358
1359 Expr::ListComprehension {
1360 variable,
1361 list,
1362 where_clause,
1363 map_expr,
1364 } => {
1365 let where_str = where_clause
1366 .as_ref()
1367 .map_or(String::new(), |e| format!(" WHERE {}", e.to_string_repr()));
1368 format!(
1369 "[{variable} IN {}{where_str} | {}]",
1370 list.to_string_repr(),
1371 map_expr.to_string_repr()
1372 )
1373 }
1374
1375 Expr::PatternComprehension {
1376 path_variable,
1377 pattern,
1378 where_clause,
1379 map_expr,
1380 } => {
1381 let var_part = path_variable
1382 .as_ref()
1383 .map(|v| format!("{v} = "))
1384 .unwrap_or_default();
1385 let where_str = where_clause
1386 .as_ref()
1387 .map_or(String::new(), |e| format!(" WHERE {}", e.to_string_repr()));
1388 format!(
1389 "[{var_part}{pattern:?}{where_str} | {}]",
1390 map_expr.to_string_repr()
1391 )
1392 }
1393
1394 Expr::ValidAt {
1395 entity,
1396 timestamp,
1397 start_prop,
1398 end_prop,
1399 } => match (start_prop, end_prop) {
1400 (Some(start), Some(end)) => format!(
1401 "{} VALID_AT({}, '{start}', '{end}')",
1402 entity.to_string_repr(),
1403 timestamp.to_string_repr(),
1404 ),
1405 _ => format!(
1406 "{} VALID_AT {}",
1407 entity.to_string_repr(),
1408 timestamp.to_string_repr(),
1409 ),
1410 },
1411
1412 Expr::MapProjection { base, items } => {
1413 let items_str = items
1414 .iter()
1415 .map(MapProjectionItem::to_string_repr)
1416 .collect::<Vec<_>>()
1417 .join(", ");
1418 format!("{}{{{items_str}}}", base.to_string_repr())
1419 }
1420
1421 Expr::LabelCheck { expr, labels } => {
1422 let labels_str: String = labels.iter().map(|l| format!(":{l}")).collect();
1423 format!("{}{labels_str}", expr.to_string_repr())
1424 }
1425 }
1426 }
1427
1428 pub fn for_each_child(&self, f: &mut dyn FnMut(&Expr)) {
1433 match self {
1434 Expr::Literal(_) | Expr::Parameter(_) | Expr::Variable(_) | Expr::Wildcard => {}
1435 Expr::Property(base, _) => f(base),
1436 Expr::List(items) => {
1437 for item in items {
1438 f(item);
1439 }
1440 }
1441 Expr::Map(entries) => {
1442 for (_, expr) in entries {
1443 f(expr);
1444 }
1445 }
1446 Expr::FunctionCall { args, .. } => {
1447 for arg in args {
1448 f(arg);
1449 }
1450 }
1451 Expr::BinaryOp { left, right, .. } => {
1452 f(left);
1453 f(right);
1454 }
1455 Expr::UnaryOp { expr, .. } => f(expr),
1456 Expr::Case {
1457 expr,
1458 when_then,
1459 else_expr,
1460 } => {
1461 if let Some(e) = expr {
1462 f(e);
1463 }
1464 for (w, t) in when_then {
1465 f(w);
1466 f(t);
1467 }
1468 if let Some(e) = else_expr {
1469 f(e);
1470 }
1471 }
1472 Expr::Exists { .. } | Expr::CountSubquery(_) | Expr::CollectSubquery(_) => {}
1473 Expr::IsNull(e) | Expr::IsNotNull(e) | Expr::IsUnique(e) => f(e),
1474 Expr::In { expr, list } => {
1475 f(expr);
1476 f(list);
1477 }
1478 Expr::ArrayIndex { array, index } => {
1479 f(array);
1480 f(index);
1481 }
1482 Expr::ArraySlice { array, start, end } => {
1483 f(array);
1484 if let Some(s) = start {
1485 f(s);
1486 }
1487 if let Some(e) = end {
1488 f(e);
1489 }
1490 }
1491 Expr::Quantifier {
1492 list, predicate, ..
1493 } => {
1494 f(list);
1495 f(predicate);
1496 }
1497 Expr::Reduce {
1498 init, list, expr, ..
1499 } => {
1500 f(init);
1501 f(list);
1502 f(expr);
1503 }
1504 Expr::ListComprehension {
1505 list,
1506 where_clause,
1507 map_expr,
1508 ..
1509 } => {
1510 f(list);
1511 if let Some(w) = where_clause {
1512 f(w);
1513 }
1514 f(map_expr);
1515 }
1516 Expr::PatternComprehension {
1517 where_clause,
1518 map_expr,
1519 ..
1520 } => {
1521 if let Some(w) = where_clause {
1522 f(w);
1523 }
1524 f(map_expr);
1525 }
1526 Expr::ValidAt {
1527 entity, timestamp, ..
1528 } => {
1529 f(entity);
1530 f(timestamp);
1531 }
1532 Expr::MapProjection { base, items } => {
1533 f(base);
1534 for item in items {
1535 if let MapProjectionItem::LiteralEntry(_, expr) = item {
1536 f(expr);
1537 }
1538 }
1539 }
1540 Expr::LabelCheck { expr, .. } => f(expr),
1541 }
1542 }
1543
1544 pub fn map_children(self, f: &mut dyn FnMut(Expr) -> Expr) -> Expr {
1548 match self {
1549 Expr::Literal(_) | Expr::Parameter(_) | Expr::Variable(_) | Expr::Wildcard => self,
1550 Expr::Property(base, prop) => Expr::Property(Box::new(f(*base)), prop),
1551 Expr::List(items) => Expr::List(items.into_iter().map(&mut *f).collect()),
1552 Expr::Map(entries) => Expr::Map(entries.into_iter().map(|(k, v)| (k, f(v))).collect()),
1553 Expr::FunctionCall {
1554 name,
1555 args,
1556 distinct,
1557 window_spec,
1558 } => Expr::FunctionCall {
1559 name,
1560 args: args.into_iter().map(&mut *f).collect(),
1561 distinct,
1562 window_spec,
1563 },
1564 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
1565 left: Box::new(f(*left)),
1566 op,
1567 right: Box::new(f(*right)),
1568 },
1569 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
1570 op,
1571 expr: Box::new(f(*expr)),
1572 },
1573 Expr::Case {
1574 expr,
1575 when_then,
1576 else_expr,
1577 } => Expr::Case {
1578 expr: expr.map(|e| Box::new(f(*e))),
1579 when_then: when_then.into_iter().map(|(w, t)| (f(w), f(t))).collect(),
1580 else_expr: else_expr.map(|e| Box::new(f(*e))),
1581 },
1582 Expr::Exists { .. } | Expr::CountSubquery(_) | Expr::CollectSubquery(_) => self,
1583 Expr::IsNull(e) => Expr::IsNull(Box::new(f(*e))),
1584 Expr::IsNotNull(e) => Expr::IsNotNull(Box::new(f(*e))),
1585 Expr::IsUnique(e) => Expr::IsUnique(Box::new(f(*e))),
1586 Expr::In { expr, list } => Expr::In {
1587 expr: Box::new(f(*expr)),
1588 list: Box::new(f(*list)),
1589 },
1590 Expr::ArrayIndex { array, index } => Expr::ArrayIndex {
1591 array: Box::new(f(*array)),
1592 index: Box::new(f(*index)),
1593 },
1594 Expr::ArraySlice { array, start, end } => Expr::ArraySlice {
1595 array: Box::new(f(*array)),
1596 start: start.map(|s| Box::new(f(*s))),
1597 end: end.map(|e| Box::new(f(*e))),
1598 },
1599 Expr::Quantifier {
1600 quantifier,
1601 variable,
1602 list,
1603 predicate,
1604 } => Expr::Quantifier {
1605 quantifier,
1606 variable,
1607 list: Box::new(f(*list)),
1608 predicate: Box::new(f(*predicate)),
1609 },
1610 Expr::Reduce {
1611 accumulator,
1612 init,
1613 variable,
1614 list,
1615 expr,
1616 } => Expr::Reduce {
1617 accumulator,
1618 init: Box::new(f(*init)),
1619 variable,
1620 list: Box::new(f(*list)),
1621 expr: Box::new(f(*expr)),
1622 },
1623 Expr::ListComprehension {
1624 variable,
1625 list,
1626 where_clause,
1627 map_expr,
1628 } => Expr::ListComprehension {
1629 variable,
1630 list: Box::new(f(*list)),
1631 where_clause: where_clause.map(|w| Box::new(f(*w))),
1632 map_expr: Box::new(f(*map_expr)),
1633 },
1634 Expr::PatternComprehension {
1635 path_variable,
1636 pattern,
1637 where_clause,
1638 map_expr,
1639 } => Expr::PatternComprehension {
1640 path_variable,
1641 pattern,
1642 where_clause: where_clause.map(|w| Box::new(f(*w))),
1643 map_expr: Box::new(f(*map_expr)),
1644 },
1645 Expr::ValidAt {
1646 entity,
1647 timestamp,
1648 start_prop,
1649 end_prop,
1650 } => Expr::ValidAt {
1651 entity: Box::new(f(*entity)),
1652 timestamp: Box::new(f(*timestamp)),
1653 start_prop,
1654 end_prop,
1655 },
1656 Expr::MapProjection { base, items } => Expr::MapProjection {
1657 base: Box::new(f(*base)),
1658 items: items
1659 .into_iter()
1660 .map(|item| match item {
1661 MapProjectionItem::LiteralEntry(key, expr) => {
1662 MapProjectionItem::LiteralEntry(key, Box::new(f(*expr)))
1663 }
1664 other => other,
1665 })
1666 .collect(),
1667 },
1668 Expr::LabelCheck { expr, labels } => Expr::LabelCheck {
1669 expr: Box::new(f(*expr)),
1670 labels,
1671 },
1672 }
1673 }
1674}