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 use std::fmt::Write as _;
1300 let mut s = "CASE".to_string();
1301 if let Some(e) = expr {
1302 let _ = write!(s, " {}", e.to_string_repr());
1303 }
1304 for (w, t) in when_then {
1305 let _ = write!(
1306 s,
1307 " WHEN {} THEN {}",
1308 w.to_string_repr(),
1309 t.to_string_repr()
1310 );
1311 }
1312 if let Some(e) = else_expr {
1313 let _ = write!(s, " ELSE {}", e.to_string_repr());
1314 }
1315 s.push_str(" END");
1316 s
1317 }
1318 Expr::Exists { .. } => "EXISTS {...}".to_string(),
1319 Expr::CountSubquery(_) => "COUNT {...}".to_string(),
1320 Expr::CollectSubquery(_) => "COLLECT {...}".to_string(),
1321 Expr::IsNull(e) => format!("{} IS NULL", e.to_string_repr()),
1322 Expr::IsNotNull(e) => format!("{} IS NOT NULL", e.to_string_repr()),
1323 Expr::IsUnique(e) => format!("{} IS UNIQUE", e.to_string_repr()),
1324 Expr::In { expr, list } => {
1325 format!("{} IN {}", expr.to_string_repr(), list.to_string_repr())
1326 }
1327 Expr::ArrayIndex { array, index } => {
1328 format!("{}[{}]", array.to_string_repr(), index.to_string_repr())
1329 }
1330 Expr::ArraySlice { array, start, end } => {
1331 let start_str = start
1332 .as_ref()
1333 .map(|e| e.to_string_repr())
1334 .unwrap_or_default();
1335 let end_str = end.as_ref().map(|e| e.to_string_repr()).unwrap_or_default();
1336 format!("{}[{}..{}]", array.to_string_repr(), start_str, end_str)
1337 }
1338 Expr::Quantifier {
1339 quantifier,
1340 variable,
1341 list,
1342 predicate,
1343 } => {
1344 format!(
1345 "{quantifier}({variable} IN {} WHERE {})",
1346 list.to_string_repr(),
1347 predicate.to_string_repr()
1348 )
1349 }
1350 Expr::Reduce {
1351 accumulator,
1352 init,
1353 variable,
1354 list,
1355 expr,
1356 } => {
1357 format!(
1358 "REDUCE({accumulator} = {}, {variable} IN {} | {})",
1359 init.to_string_repr(),
1360 list.to_string_repr(),
1361 expr.to_string_repr()
1362 )
1363 }
1364
1365 Expr::ListComprehension {
1366 variable,
1367 list,
1368 where_clause,
1369 map_expr,
1370 } => {
1371 let where_str = where_clause
1372 .as_ref()
1373 .map_or(String::new(), |e| format!(" WHERE {}", e.to_string_repr()));
1374 format!(
1375 "[{variable} IN {}{where_str} | {}]",
1376 list.to_string_repr(),
1377 map_expr.to_string_repr()
1378 )
1379 }
1380
1381 Expr::PatternComprehension {
1382 path_variable,
1383 pattern,
1384 where_clause,
1385 map_expr,
1386 } => {
1387 let var_part = path_variable
1388 .as_ref()
1389 .map(|v| format!("{v} = "))
1390 .unwrap_or_default();
1391 let where_str = where_clause
1392 .as_ref()
1393 .map_or(String::new(), |e| format!(" WHERE {}", e.to_string_repr()));
1394 format!(
1395 "[{var_part}{pattern:?}{where_str} | {}]",
1396 map_expr.to_string_repr()
1397 )
1398 }
1399
1400 Expr::ValidAt {
1401 entity,
1402 timestamp,
1403 start_prop,
1404 end_prop,
1405 } => match (start_prop, end_prop) {
1406 (Some(start), Some(end)) => format!(
1407 "{} VALID_AT({}, '{start}', '{end}')",
1408 entity.to_string_repr(),
1409 timestamp.to_string_repr(),
1410 ),
1411 _ => format!(
1412 "{} VALID_AT {}",
1413 entity.to_string_repr(),
1414 timestamp.to_string_repr(),
1415 ),
1416 },
1417
1418 Expr::MapProjection { base, items } => {
1419 let items_str = items
1420 .iter()
1421 .map(MapProjectionItem::to_string_repr)
1422 .collect::<Vec<_>>()
1423 .join(", ");
1424 format!("{}{{{items_str}}}", base.to_string_repr())
1425 }
1426
1427 Expr::LabelCheck { expr, labels } => {
1428 let labels_str: String = labels.iter().map(|l| format!(":{l}")).collect();
1429 format!("{}{labels_str}", expr.to_string_repr())
1430 }
1431 }
1432 }
1433
1434 pub fn for_each_child(&self, f: &mut dyn FnMut(&Expr)) {
1439 match self {
1440 Expr::Literal(_) | Expr::Parameter(_) | Expr::Variable(_) | Expr::Wildcard => {}
1441 Expr::Property(base, _) => f(base),
1442 Expr::List(items) => {
1443 for item in items {
1444 f(item);
1445 }
1446 }
1447 Expr::Map(entries) => {
1448 for (_, expr) in entries {
1449 f(expr);
1450 }
1451 }
1452 Expr::FunctionCall { args, .. } => {
1453 for arg in args {
1454 f(arg);
1455 }
1456 }
1457 Expr::BinaryOp { left, right, .. } => {
1458 f(left);
1459 f(right);
1460 }
1461 Expr::UnaryOp { expr, .. } => f(expr),
1462 Expr::Case {
1463 expr,
1464 when_then,
1465 else_expr,
1466 } => {
1467 if let Some(e) = expr {
1468 f(e);
1469 }
1470 for (w, t) in when_then {
1471 f(w);
1472 f(t);
1473 }
1474 if let Some(e) = else_expr {
1475 f(e);
1476 }
1477 }
1478 Expr::Exists { .. } | Expr::CountSubquery(_) | Expr::CollectSubquery(_) => {}
1479 Expr::IsNull(e) | Expr::IsNotNull(e) | Expr::IsUnique(e) => f(e),
1480 Expr::In { expr, list } => {
1481 f(expr);
1482 f(list);
1483 }
1484 Expr::ArrayIndex { array, index } => {
1485 f(array);
1486 f(index);
1487 }
1488 Expr::ArraySlice { array, start, end } => {
1489 f(array);
1490 if let Some(s) = start {
1491 f(s);
1492 }
1493 if let Some(e) = end {
1494 f(e);
1495 }
1496 }
1497 Expr::Quantifier {
1498 list, predicate, ..
1499 } => {
1500 f(list);
1501 f(predicate);
1502 }
1503 Expr::Reduce {
1504 init, list, expr, ..
1505 } => {
1506 f(init);
1507 f(list);
1508 f(expr);
1509 }
1510 Expr::ListComprehension {
1511 list,
1512 where_clause,
1513 map_expr,
1514 ..
1515 } => {
1516 f(list);
1517 if let Some(w) = where_clause {
1518 f(w);
1519 }
1520 f(map_expr);
1521 }
1522 Expr::PatternComprehension {
1523 where_clause,
1524 map_expr,
1525 ..
1526 } => {
1527 if let Some(w) = where_clause {
1528 f(w);
1529 }
1530 f(map_expr);
1531 }
1532 Expr::ValidAt {
1533 entity, timestamp, ..
1534 } => {
1535 f(entity);
1536 f(timestamp);
1537 }
1538 Expr::MapProjection { base, items } => {
1539 f(base);
1540 for item in items {
1541 if let MapProjectionItem::LiteralEntry(_, expr) = item {
1542 f(expr);
1543 }
1544 }
1545 }
1546 Expr::LabelCheck { expr, .. } => f(expr),
1547 }
1548 }
1549
1550 pub fn map_children(self, f: &mut dyn FnMut(Expr) -> Expr) -> Expr {
1554 match self {
1555 Expr::Literal(_) | Expr::Parameter(_) | Expr::Variable(_) | Expr::Wildcard => self,
1556 Expr::Property(base, prop) => Expr::Property(Box::new(f(*base)), prop),
1557 Expr::List(items) => Expr::List(items.into_iter().map(&mut *f).collect()),
1558 Expr::Map(entries) => Expr::Map(entries.into_iter().map(|(k, v)| (k, f(v))).collect()),
1559 Expr::FunctionCall {
1560 name,
1561 args,
1562 distinct,
1563 window_spec,
1564 } => Expr::FunctionCall {
1565 name,
1566 args: args.into_iter().map(&mut *f).collect(),
1567 distinct,
1568 window_spec,
1569 },
1570 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
1571 left: Box::new(f(*left)),
1572 op,
1573 right: Box::new(f(*right)),
1574 },
1575 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
1576 op,
1577 expr: Box::new(f(*expr)),
1578 },
1579 Expr::Case {
1580 expr,
1581 when_then,
1582 else_expr,
1583 } => Expr::Case {
1584 expr: expr.map(|e| Box::new(f(*e))),
1585 when_then: when_then.into_iter().map(|(w, t)| (f(w), f(t))).collect(),
1586 else_expr: else_expr.map(|e| Box::new(f(*e))),
1587 },
1588 Expr::Exists { .. } | Expr::CountSubquery(_) | Expr::CollectSubquery(_) => self,
1589 Expr::IsNull(e) => Expr::IsNull(Box::new(f(*e))),
1590 Expr::IsNotNull(e) => Expr::IsNotNull(Box::new(f(*e))),
1591 Expr::IsUnique(e) => Expr::IsUnique(Box::new(f(*e))),
1592 Expr::In { expr, list } => Expr::In {
1593 expr: Box::new(f(*expr)),
1594 list: Box::new(f(*list)),
1595 },
1596 Expr::ArrayIndex { array, index } => Expr::ArrayIndex {
1597 array: Box::new(f(*array)),
1598 index: Box::new(f(*index)),
1599 },
1600 Expr::ArraySlice { array, start, end } => Expr::ArraySlice {
1601 array: Box::new(f(*array)),
1602 start: start.map(|s| Box::new(f(*s))),
1603 end: end.map(|e| Box::new(f(*e))),
1604 },
1605 Expr::Quantifier {
1606 quantifier,
1607 variable,
1608 list,
1609 predicate,
1610 } => Expr::Quantifier {
1611 quantifier,
1612 variable,
1613 list: Box::new(f(*list)),
1614 predicate: Box::new(f(*predicate)),
1615 },
1616 Expr::Reduce {
1617 accumulator,
1618 init,
1619 variable,
1620 list,
1621 expr,
1622 } => Expr::Reduce {
1623 accumulator,
1624 init: Box::new(f(*init)),
1625 variable,
1626 list: Box::new(f(*list)),
1627 expr: Box::new(f(*expr)),
1628 },
1629 Expr::ListComprehension {
1630 variable,
1631 list,
1632 where_clause,
1633 map_expr,
1634 } => Expr::ListComprehension {
1635 variable,
1636 list: Box::new(f(*list)),
1637 where_clause: where_clause.map(|w| Box::new(f(*w))),
1638 map_expr: Box::new(f(*map_expr)),
1639 },
1640 Expr::PatternComprehension {
1641 path_variable,
1642 pattern,
1643 where_clause,
1644 map_expr,
1645 } => Expr::PatternComprehension {
1646 path_variable,
1647 pattern,
1648 where_clause: where_clause.map(|w| Box::new(f(*w))),
1649 map_expr: Box::new(f(*map_expr)),
1650 },
1651 Expr::ValidAt {
1652 entity,
1653 timestamp,
1654 start_prop,
1655 end_prop,
1656 } => Expr::ValidAt {
1657 entity: Box::new(f(*entity)),
1658 timestamp: Box::new(f(*timestamp)),
1659 start_prop,
1660 end_prop,
1661 },
1662 Expr::MapProjection { base, items } => Expr::MapProjection {
1663 base: Box::new(f(*base)),
1664 items: items
1665 .into_iter()
1666 .map(|item| match item {
1667 MapProjectionItem::LiteralEntry(key, expr) => {
1668 MapProjectionItem::LiteralEntry(key, Box::new(f(*expr)))
1669 }
1670 other => other,
1671 })
1672 .collect(),
1673 },
1674 Expr::LabelCheck { expr, labels } => Expr::LabelCheck {
1675 expr: Box::new(f(*expr)),
1676 labels,
1677 },
1678 }
1679 }
1680}