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