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 | "stddev"
1206 | "stdevp"
1207 | "stddevp"
1208 | "variance"
1209 | "variancep"
1210 | "percentiledisc"
1211 | "percentilecont"
1212 ) || crate::plugin_aggregates::is_known_plugin_aggregate(name))
1213 }
1214 Expr::CountSubquery(_) | Expr::CollectSubquery(_) => true,
1215 _ => {
1220 let mut found = false;
1221 self.for_each_child(&mut |c| {
1222 if !found && c.is_aggregate() {
1223 found = true;
1224 }
1225 });
1226 found
1227 }
1228 }
1229 }
1230
1231 pub fn to_string_repr(&self) -> String {
1233 match self {
1234 Expr::Literal(v) => v.to_string(),
1235 Expr::Parameter(p) => format!("${p}"),
1236 Expr::Variable(v) => v.clone(),
1237 Expr::Wildcard => "*".to_string(),
1238 Expr::Property(base, prop) => {
1239 format!("{}.{prop}", base.to_string_repr())
1240 }
1241 Expr::List(exprs) => format!("[{}]", join_exprs(exprs, ", ")),
1242 Expr::Map(entries) => {
1243 let items = entries
1244 .iter()
1245 .map(|(k, v)| format!("{k}: {}", v.to_string_repr()))
1246 .collect::<Vec<_>>()
1247 .join(", ");
1248 format!("{{{items}}}")
1249 }
1250 Expr::FunctionCall {
1251 name,
1252 args,
1253 distinct,
1254 window_spec,
1255 } => {
1256 let args_str = join_exprs(args, ", ");
1257 let distinct_str = if *distinct { "DISTINCT " } else { "" };
1258 let base = format!("{name}({distinct_str}{args_str})");
1259 let Some(window) = window_spec else {
1260 return base;
1261 };
1262 let mut parts = Vec::new();
1263 if !window.partition_by.is_empty() {
1264 parts.push(format!(
1265 "PARTITION BY {}",
1266 join_exprs(&window.partition_by, ", ")
1267 ));
1268 }
1269 if !window.order_by.is_empty() {
1270 let items = window
1271 .order_by
1272 .iter()
1273 .map(|s| {
1274 let dir = if s.ascending { "ASC" } else { "DESC" };
1275 format!("{} {dir}", s.expr.to_string_repr())
1276 })
1277 .collect::<Vec<_>>()
1278 .join(", ");
1279 parts.push(format!("ORDER BY {items}"));
1280 }
1281 format!("{base} OVER ({})", parts.join(" "))
1282 }
1283 Expr::BinaryOp { left, op, right } => {
1284 format!(
1285 "{} {} {}",
1286 left.to_string_repr(),
1287 op,
1288 right.to_string_repr()
1289 )
1290 }
1291 Expr::UnaryOp { op, expr } => {
1292 format!("{op}{}", expr.to_string_repr())
1293 }
1294 Expr::Case {
1295 expr,
1296 when_then,
1297 else_expr,
1298 } => {
1299 let mut s = "CASE".to_string();
1300 if let Some(e) = expr {
1301 s.push_str(&format!(" {}", e.to_string_repr()));
1302 }
1303 for (w, t) in when_then {
1304 s.push_str(&format!(
1305 " WHEN {} THEN {}",
1306 w.to_string_repr(),
1307 t.to_string_repr()
1308 ));
1309 }
1310 if let Some(e) = else_expr {
1311 s.push_str(&format!(" ELSE {}", e.to_string_repr()));
1312 }
1313 s.push_str(" END");
1314 s
1315 }
1316 Expr::Exists { .. } => "EXISTS {...}".to_string(),
1317 Expr::CountSubquery(_) => "COUNT {...}".to_string(),
1318 Expr::CollectSubquery(_) => "COLLECT {...}".to_string(),
1319 Expr::IsNull(e) => format!("{} IS NULL", e.to_string_repr()),
1320 Expr::IsNotNull(e) => format!("{} IS NOT NULL", e.to_string_repr()),
1321 Expr::IsUnique(e) => format!("{} IS UNIQUE", e.to_string_repr()),
1322 Expr::In { expr, list } => {
1323 format!("{} IN {}", expr.to_string_repr(), list.to_string_repr())
1324 }
1325 Expr::ArrayIndex { array, index } => {
1326 format!("{}[{}]", array.to_string_repr(), index.to_string_repr())
1327 }
1328 Expr::ArraySlice { array, start, end } => {
1329 let start_str = start
1330 .as_ref()
1331 .map(|e| e.to_string_repr())
1332 .unwrap_or_default();
1333 let end_str = end.as_ref().map(|e| e.to_string_repr()).unwrap_or_default();
1334 format!("{}[{}..{}]", array.to_string_repr(), start_str, end_str)
1335 }
1336 Expr::Quantifier {
1337 quantifier,
1338 variable,
1339 list,
1340 predicate,
1341 } => {
1342 format!(
1343 "{quantifier}({variable} IN {} WHERE {})",
1344 list.to_string_repr(),
1345 predicate.to_string_repr()
1346 )
1347 }
1348 Expr::Reduce {
1349 accumulator,
1350 init,
1351 variable,
1352 list,
1353 expr,
1354 } => {
1355 format!(
1356 "REDUCE({accumulator} = {}, {variable} IN {} | {})",
1357 init.to_string_repr(),
1358 list.to_string_repr(),
1359 expr.to_string_repr()
1360 )
1361 }
1362
1363 Expr::ListComprehension {
1364 variable,
1365 list,
1366 where_clause,
1367 map_expr,
1368 } => {
1369 let where_str = where_clause
1370 .as_ref()
1371 .map_or(String::new(), |e| format!(" WHERE {}", e.to_string_repr()));
1372 format!(
1373 "[{variable} IN {}{where_str} | {}]",
1374 list.to_string_repr(),
1375 map_expr.to_string_repr()
1376 )
1377 }
1378
1379 Expr::PatternComprehension {
1380 path_variable,
1381 pattern,
1382 where_clause,
1383 map_expr,
1384 } => {
1385 let var_part = path_variable
1386 .as_ref()
1387 .map(|v| format!("{v} = "))
1388 .unwrap_or_default();
1389 let where_str = where_clause
1390 .as_ref()
1391 .map_or(String::new(), |e| format!(" WHERE {}", e.to_string_repr()));
1392 format!(
1393 "[{var_part}{pattern:?}{where_str} | {}]",
1394 map_expr.to_string_repr()
1395 )
1396 }
1397
1398 Expr::ValidAt {
1399 entity,
1400 timestamp,
1401 start_prop,
1402 end_prop,
1403 } => match (start_prop, end_prop) {
1404 (Some(start), Some(end)) => format!(
1405 "{} VALID_AT({}, '{start}', '{end}')",
1406 entity.to_string_repr(),
1407 timestamp.to_string_repr(),
1408 ),
1409 _ => format!(
1410 "{} VALID_AT {}",
1411 entity.to_string_repr(),
1412 timestamp.to_string_repr(),
1413 ),
1414 },
1415
1416 Expr::MapProjection { base, items } => {
1417 let items_str = items
1418 .iter()
1419 .map(MapProjectionItem::to_string_repr)
1420 .collect::<Vec<_>>()
1421 .join(", ");
1422 format!("{}{{{items_str}}}", base.to_string_repr())
1423 }
1424
1425 Expr::LabelCheck { expr, labels } => {
1426 let labels_str: String = labels.iter().map(|l| format!(":{l}")).collect();
1427 format!("{}{labels_str}", expr.to_string_repr())
1428 }
1429 }
1430 }
1431
1432 pub fn for_each_child(&self, f: &mut dyn FnMut(&Expr)) {
1437 match self {
1438 Expr::Literal(_) | Expr::Parameter(_) | Expr::Variable(_) | Expr::Wildcard => {}
1439 Expr::Property(base, _) => f(base),
1440 Expr::List(items) => {
1441 for item in items {
1442 f(item);
1443 }
1444 }
1445 Expr::Map(entries) => {
1446 for (_, expr) in entries {
1447 f(expr);
1448 }
1449 }
1450 Expr::FunctionCall { args, .. } => {
1451 for arg in args {
1452 f(arg);
1453 }
1454 }
1455 Expr::BinaryOp { left, right, .. } => {
1456 f(left);
1457 f(right);
1458 }
1459 Expr::UnaryOp { expr, .. } => f(expr),
1460 Expr::Case {
1461 expr,
1462 when_then,
1463 else_expr,
1464 } => {
1465 if let Some(e) = expr {
1466 f(e);
1467 }
1468 for (w, t) in when_then {
1469 f(w);
1470 f(t);
1471 }
1472 if let Some(e) = else_expr {
1473 f(e);
1474 }
1475 }
1476 Expr::Exists { .. } | Expr::CountSubquery(_) | Expr::CollectSubquery(_) => {}
1477 Expr::IsNull(e) | Expr::IsNotNull(e) | Expr::IsUnique(e) => f(e),
1478 Expr::In { expr, list } => {
1479 f(expr);
1480 f(list);
1481 }
1482 Expr::ArrayIndex { array, index } => {
1483 f(array);
1484 f(index);
1485 }
1486 Expr::ArraySlice { array, start, end } => {
1487 f(array);
1488 if let Some(s) = start {
1489 f(s);
1490 }
1491 if let Some(e) = end {
1492 f(e);
1493 }
1494 }
1495 Expr::Quantifier {
1496 list, predicate, ..
1497 } => {
1498 f(list);
1499 f(predicate);
1500 }
1501 Expr::Reduce {
1502 init, list, expr, ..
1503 } => {
1504 f(init);
1505 f(list);
1506 f(expr);
1507 }
1508 Expr::ListComprehension {
1509 list,
1510 where_clause,
1511 map_expr,
1512 ..
1513 } => {
1514 f(list);
1515 if let Some(w) = where_clause {
1516 f(w);
1517 }
1518 f(map_expr);
1519 }
1520 Expr::PatternComprehension {
1521 where_clause,
1522 map_expr,
1523 ..
1524 } => {
1525 if let Some(w) = where_clause {
1526 f(w);
1527 }
1528 f(map_expr);
1529 }
1530 Expr::ValidAt {
1531 entity, timestamp, ..
1532 } => {
1533 f(entity);
1534 f(timestamp);
1535 }
1536 Expr::MapProjection { base, items } => {
1537 f(base);
1538 for item in items {
1539 if let MapProjectionItem::LiteralEntry(_, expr) = item {
1540 f(expr);
1541 }
1542 }
1543 }
1544 Expr::LabelCheck { expr, .. } => f(expr),
1545 }
1546 }
1547
1548 pub fn map_children(self, f: &mut dyn FnMut(Expr) -> Expr) -> Expr {
1552 match self {
1553 Expr::Literal(_) | Expr::Parameter(_) | Expr::Variable(_) | Expr::Wildcard => self,
1554 Expr::Property(base, prop) => Expr::Property(Box::new(f(*base)), prop),
1555 Expr::List(items) => Expr::List(items.into_iter().map(&mut *f).collect()),
1556 Expr::Map(entries) => Expr::Map(entries.into_iter().map(|(k, v)| (k, f(v))).collect()),
1557 Expr::FunctionCall {
1558 name,
1559 args,
1560 distinct,
1561 window_spec,
1562 } => Expr::FunctionCall {
1563 name,
1564 args: args.into_iter().map(&mut *f).collect(),
1565 distinct,
1566 window_spec,
1567 },
1568 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
1569 left: Box::new(f(*left)),
1570 op,
1571 right: Box::new(f(*right)),
1572 },
1573 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
1574 op,
1575 expr: Box::new(f(*expr)),
1576 },
1577 Expr::Case {
1578 expr,
1579 when_then,
1580 else_expr,
1581 } => Expr::Case {
1582 expr: expr.map(|e| Box::new(f(*e))),
1583 when_then: when_then.into_iter().map(|(w, t)| (f(w), f(t))).collect(),
1584 else_expr: else_expr.map(|e| Box::new(f(*e))),
1585 },
1586 Expr::Exists { .. } | Expr::CountSubquery(_) | Expr::CollectSubquery(_) => self,
1587 Expr::IsNull(e) => Expr::IsNull(Box::new(f(*e))),
1588 Expr::IsNotNull(e) => Expr::IsNotNull(Box::new(f(*e))),
1589 Expr::IsUnique(e) => Expr::IsUnique(Box::new(f(*e))),
1590 Expr::In { expr, list } => Expr::In {
1591 expr: Box::new(f(*expr)),
1592 list: Box::new(f(*list)),
1593 },
1594 Expr::ArrayIndex { array, index } => Expr::ArrayIndex {
1595 array: Box::new(f(*array)),
1596 index: Box::new(f(*index)),
1597 },
1598 Expr::ArraySlice { array, start, end } => Expr::ArraySlice {
1599 array: Box::new(f(*array)),
1600 start: start.map(|s| Box::new(f(*s))),
1601 end: end.map(|e| Box::new(f(*e))),
1602 },
1603 Expr::Quantifier {
1604 quantifier,
1605 variable,
1606 list,
1607 predicate,
1608 } => Expr::Quantifier {
1609 quantifier,
1610 variable,
1611 list: Box::new(f(*list)),
1612 predicate: Box::new(f(*predicate)),
1613 },
1614 Expr::Reduce {
1615 accumulator,
1616 init,
1617 variable,
1618 list,
1619 expr,
1620 } => Expr::Reduce {
1621 accumulator,
1622 init: Box::new(f(*init)),
1623 variable,
1624 list: Box::new(f(*list)),
1625 expr: Box::new(f(*expr)),
1626 },
1627 Expr::ListComprehension {
1628 variable,
1629 list,
1630 where_clause,
1631 map_expr,
1632 } => Expr::ListComprehension {
1633 variable,
1634 list: Box::new(f(*list)),
1635 where_clause: where_clause.map(|w| Box::new(f(*w))),
1636 map_expr: Box::new(f(*map_expr)),
1637 },
1638 Expr::PatternComprehension {
1639 path_variable,
1640 pattern,
1641 where_clause,
1642 map_expr,
1643 } => Expr::PatternComprehension {
1644 path_variable,
1645 pattern,
1646 where_clause: where_clause.map(|w| Box::new(f(*w))),
1647 map_expr: Box::new(f(*map_expr)),
1648 },
1649 Expr::ValidAt {
1650 entity,
1651 timestamp,
1652 start_prop,
1653 end_prop,
1654 } => Expr::ValidAt {
1655 entity: Box::new(f(*entity)),
1656 timestamp: Box::new(f(*timestamp)),
1657 start_prop,
1658 end_prop,
1659 },
1660 Expr::MapProjection { base, items } => Expr::MapProjection {
1661 base: Box::new(f(*base)),
1662 items: items
1663 .into_iter()
1664 .map(|item| match item {
1665 MapProjectionItem::LiteralEntry(key, expr) => {
1666 MapProjectionItem::LiteralEntry(key, Box::new(f(*expr)))
1667 }
1668 other => other,
1669 })
1670 .collect(),
1671 },
1672 Expr::LabelCheck { expr, labels } => Expr::LabelCheck {
1673 expr: Box::new(f(*expr)),
1674 labels,
1675 },
1676 }
1677 }
1678}