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 Transaction(TransactionCommand),
24 Explain(Box<Query>),
25 TimeTravel {
28 query: Box<Query>,
29 spec: TimeTravelSpec,
30 },
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub enum TransactionCommand {
35 Begin,
36 Commit,
37 Rollback,
38}
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub enum SchemaCommand {
42 CreateVectorIndex(CreateVectorIndex),
43 CreateFullTextIndex(CreateFullTextIndex),
44 CreateScalarIndex(CreateScalarIndex),
45 CreateJsonFtsIndex(CreateJsonFtsIndex),
46 DropIndex(DropIndex),
47 CreateConstraint(CreateConstraint),
48 DropConstraint(DropConstraint),
49 CreateLabel(CreateLabel),
50 CreateEdgeType(CreateEdgeType),
51 AlterLabel(AlterLabel),
52 AlterEdgeType(AlterEdgeType),
53 DropLabel(DropLabel),
54 DropEdgeType(DropEdgeType),
55 ShowConstraints(ShowConstraints),
56 ShowIndexes(ShowIndexes),
57 ShowDatabase,
58 ShowConfig,
59 ShowStatistics,
60 Vacuum,
61 Checkpoint,
62 Backup { path: String },
63 CopyTo(CopyToCommand),
64 CopyFrom(CopyFromCommand),
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub struct CreateVectorIndex {
69 pub name: String,
70 pub label: String,
71 pub property: String,
72 pub options: HashMap<String, Value>,
73 pub if_not_exists: bool,
74}
75
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct CreateFullTextIndex {
78 pub name: String,
79 pub label: String,
80 pub properties: Vec<String>,
81 pub options: HashMap<String, Value>,
82 pub if_not_exists: bool,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct CreateScalarIndex {
87 pub name: String,
88 pub label: String,
89 pub expressions: Vec<Expr>,
90 pub where_clause: Option<Expr>,
91 pub options: HashMap<String, Value>,
92 pub if_not_exists: bool,
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96pub struct CreateJsonFtsIndex {
97 pub name: String,
98 pub label: String,
99 pub column: String,
100 pub options: HashMap<String, Value>,
101 pub if_not_exists: bool,
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct CreateLabel {
106 pub name: String,
107 pub properties: Vec<PropertyDefinition>,
108 pub if_not_exists: bool,
109}
110
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub struct CreateEdgeType {
113 pub name: String,
114 pub src_labels: Vec<String>,
115 pub dst_labels: Vec<String>,
116 pub properties: Vec<PropertyDefinition>,
117 pub if_not_exists: bool,
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
121pub struct AlterLabel {
122 pub name: String,
123 pub action: AlterAction,
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127pub struct AlterEdgeType {
128 pub name: String,
129 pub action: AlterAction,
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133pub enum AlterAction {
134 AddProperty(PropertyDefinition),
135 DropProperty(String),
136 RenameProperty { old_name: String, new_name: String },
137}
138
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140pub struct DropLabel {
141 pub name: String,
142 pub if_exists: bool,
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub struct DropEdgeType {
147 pub name: String,
148 pub if_exists: bool,
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152pub struct ShowConstraints {
153 pub target: Option<ConstraintTarget>,
154}
155
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157pub enum ConstraintTarget {
158 Label(String),
159 EdgeType(String),
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163pub struct ShowIndexes {
164 pub filter: Option<String>,
165}
166
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168pub struct CopyToCommand {
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 CopyFromCommand {
177 pub label: String,
178 pub path: String,
179 pub format: String,
180 pub options: HashMap<String, Value>,
181}
182
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct PropertyDefinition {
185 pub name: String,
186 pub data_type: String, pub nullable: bool,
188 pub unique: bool,
189 pub default: Option<Expr>,
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct DropIndex {
194 pub name: String,
195}
196
197#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
198pub struct CreateConstraint {
199 pub name: Option<String>,
200 pub constraint_type: ConstraintType,
201 pub label: String,
202 pub properties: Vec<String>,
203 pub expression: Option<Expr>,
204}
205
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct DropConstraint {
208 pub name: String,
209}
210
211#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
212pub enum ConstraintType {
213 Unique,
214 NodeKey,
215 Exists,
216 Check,
217}
218
219#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
220pub struct Statement {
221 pub clauses: Vec<Clause>,
222}
223
224#[derive(Debug, Clone, PartialEq)]
225pub enum ConstraintDef {
226 Unique(String),
227 NodeKey(Vec<String>),
228 Exists(String),
229 Check(Expr),
230}
231
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233pub enum Clause {
234 Match(MatchClause),
235 Create(CreateClause),
236 Merge(MergeClause),
237 With(WithClause),
238 WithRecursive(WithRecursiveClause),
239 Unwind(UnwindClause),
240 Return(ReturnClause),
241 Delete(DeleteClause),
242 Set(SetClause),
243 Remove(RemoveClause),
244 Call(CallClause),
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248pub struct MatchClause {
249 pub optional: bool,
250 pub pattern: Pattern,
251 pub where_clause: Option<Expr>,
252}
253
254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
255pub struct CreateClause {
256 pub pattern: Pattern,
257}
258
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260pub struct MergeClause {
261 pub pattern: Pattern,
262 pub on_match: Vec<SetItem>,
263 pub on_create: Vec<SetItem>,
264}
265
266#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
267pub struct WithClause {
268 pub distinct: bool,
269 pub items: Vec<ReturnItem>,
270 pub order_by: Option<Vec<SortItem>>,
271 pub skip: Option<Expr>,
272 pub limit: Option<Expr>,
273 pub where_clause: Option<Expr>,
274}
275
276#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
277pub struct WithRecursiveClause {
278 pub name: String,
279 pub query: Box<Query>,
280 pub items: Vec<ReturnItem>,
281}
282
283#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
284pub struct ReturnClause {
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}
291
292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub enum ReturnItem {
294 All,
296 Expr {
298 expr: Expr,
299 alias: Option<String>,
300 #[serde(skip_serializing_if = "Option::is_none", default)]
302 source_text: Option<String>,
303 },
304}
305
306#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307pub struct UnwindClause {
308 pub expr: Expr,
309 pub variable: String,
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
313pub struct DeleteClause {
314 pub detach: bool,
315 pub items: Vec<Expr>,
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
319pub struct SetClause {
320 pub items: Vec<SetItem>,
321}
322
323#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
324pub enum SetItem {
325 Property {
326 expr: Expr, value: Expr,
328 },
329 Labels {
330 variable: String,
331 labels: Vec<String>,
332 },
333 Variable {
334 variable: String,
335 value: Expr,
336 },
337 VariablePlus {
338 variable: String,
339 value: Expr,
340 },
341}
342
343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
344pub struct RemoveClause {
345 pub items: Vec<RemoveItem>,
346}
347
348#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
349pub enum RemoveItem {
350 Property(Expr),
351 Labels {
352 variable: String,
353 labels: Vec<String>,
354 },
355}
356
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub struct CallClause {
359 pub kind: CallKind,
360 pub yield_items: Vec<YieldItem>,
361 pub where_clause: Option<Expr>,
362}
363
364#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
365pub enum CallKind {
366 Procedure {
367 procedure: String,
368 arguments: Vec<Expr>,
369 },
370 Subquery(Box<Query>),
371}
372
373#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
374pub struct YieldItem {
375 pub name: String,
376 pub alias: Option<String>,
377}
378
379#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
380pub struct Pattern {
381 pub paths: Vec<PathPattern>,
382}
383
384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
385pub struct PathPattern {
386 pub variable: Option<String>,
387 pub elements: Vec<PatternElement>,
388 pub shortest_path_mode: Option<ShortestPathMode>,
389}
390
391#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
392pub enum ShortestPathMode {
393 Shortest, AllShortest, }
396
397#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
398pub enum PatternElement {
399 Node(NodePattern),
400 Relationship(RelationshipPattern),
401 Parenthesized {
402 pattern: Box<PathPattern>,
403 range: Option<Range>,
404 },
405}
406
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408pub struct NodePattern {
409 pub variable: Option<String>,
410 pub labels: Vec<String>,
411 pub properties: Option<Expr>, pub where_clause: Option<Expr>, }
414
415#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
416pub struct RelationshipPattern {
417 pub variable: Option<String>,
418 pub types: Vec<String>,
419 pub direction: Direction,
420 pub range: Option<Range>,
421 pub properties: Option<Expr>, pub where_clause: Option<Expr>, }
424
425#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
426pub enum Direction {
427 Outgoing,
428 Incoming,
429 Both,
430}
431
432#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
433pub struct Range {
434 pub min: Option<u32>,
435 pub max: Option<u32>,
436}
437
438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
439pub struct SortItem {
440 pub expr: Expr,
441 pub ascending: bool,
442}
443
444#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub struct WindowSpec {
447 pub partition_by: Vec<Expr>,
448 pub order_by: Vec<SortItem>,
449}
450
451#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
456pub enum CypherLiteral {
457 Null,
458 Bool(bool),
459 Integer(i64),
460 Float(f64),
461 String(String),
462 Bytes(Vec<u8>),
466}
467
468impl CypherLiteral {
469 pub fn to_value(&self) -> Value {
471 match self {
472 CypherLiteral::Null => Value::Null,
473 CypherLiteral::Bool(b) => Value::Bool(*b),
474 CypherLiteral::Integer(i) => Value::Int(*i),
475 CypherLiteral::Float(f) => Value::Float(*f),
476 CypherLiteral::String(s) => Value::String(s.clone()),
477 CypherLiteral::Bytes(b) => {
478 uni_common::cypher_value_codec::decode(b).unwrap_or(Value::Null)
479 }
480 }
481 }
482}
483
484impl std::fmt::Display for CypherLiteral {
485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
486 match self {
487 CypherLiteral::Null => f.write_str("null"),
488 CypherLiteral::Bool(b) => write!(f, "{b}"),
489 CypherLiteral::Integer(i) => write!(f, "{i}"),
490 CypherLiteral::Float(v) => write!(f, "{v}"),
491 CypherLiteral::String(s) => write!(f, "\"{s}\""),
492 CypherLiteral::Bytes(b) => write!(f, "<bytes:{}>", b.len()),
493 }
494 }
495}
496
497impl From<i64> for CypherLiteral {
498 fn from(v: i64) -> Self {
499 Self::Integer(v)
500 }
501}
502
503impl From<f64> for CypherLiteral {
504 fn from(v: f64) -> Self {
505 Self::Float(v)
506 }
507}
508
509impl From<bool> for CypherLiteral {
510 fn from(v: bool) -> Self {
511 Self::Bool(v)
512 }
513}
514
515impl From<String> for CypherLiteral {
516 fn from(v: String) -> Self {
517 Self::String(v)
518 }
519}
520
521impl From<&str> for CypherLiteral {
522 fn from(v: &str) -> Self {
523 Self::String(v.to_string())
524 }
525}
526
527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
528pub enum Expr {
529 Literal(CypherLiteral),
530 Parameter(String),
531 Variable(String),
532 Wildcard,
533 Property(Box<Expr>, String),
534 List(Vec<Expr>),
535 Map(Vec<(String, Expr)>),
536 FunctionCall {
537 name: String,
538 args: Vec<Expr>,
539 distinct: bool,
540 window_spec: Option<WindowSpec>,
541 },
542 BinaryOp {
543 left: Box<Expr>,
544 op: BinaryOp,
545 right: Box<Expr>,
546 },
547 UnaryOp {
548 op: UnaryOp,
549 expr: Box<Expr>,
550 },
551 Case {
552 expr: Option<Box<Expr>>,
553 when_then: Vec<(Expr, Expr)>,
554 else_expr: Option<Box<Expr>>,
555 },
556 Exists {
557 query: Box<Query>,
558 from_pattern_predicate: bool,
560 },
561 CountSubquery(Box<Query>),
562 CollectSubquery(Box<Query>),
563 IsNull(Box<Expr>),
564 IsNotNull(Box<Expr>),
565 IsUnique(Box<Expr>),
566 In {
567 expr: Box<Expr>,
568 list: Box<Expr>,
569 },
570 ArrayIndex {
572 array: Box<Expr>,
573 index: Box<Expr>,
574 },
575 ArraySlice {
576 array: Box<Expr>,
577 start: Option<Box<Expr>>,
578 end: Option<Box<Expr>>,
579 },
580 Quantifier {
582 quantifier: Quantifier,
583 variable: String,
584 list: Box<Expr>,
585 predicate: Box<Expr>,
586 },
587 Reduce {
589 accumulator: String,
590 init: Box<Expr>,
591 variable: String,
592 list: Box<Expr>,
593 expr: Box<Expr>,
594 },
595 ListComprehension {
597 variable: String,
598 list: Box<Expr>,
599 where_clause: Option<Box<Expr>>,
600 map_expr: Box<Expr>,
601 },
602 PatternComprehension {
604 path_variable: Option<String>,
605 pattern: Pattern,
606 where_clause: Option<Box<Expr>>,
607 map_expr: Box<Expr>,
608 },
609 ValidAt {
611 entity: Box<Expr>,
612 timestamp: Box<Expr>,
613 start_prop: Option<String>,
614 end_prop: Option<String>,
615 },
616 MapProjection {
618 base: Box<Expr>,
619 items: Vec<MapProjectionItem>,
620 },
621 LabelCheck {
623 expr: Box<Expr>,
624 labels: Vec<String>,
625 },
626}
627
628#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
629pub enum MapProjectionItem {
630 Property(String), AllProperties, LiteralEntry(String, Box<Expr>), Variable(String), }
635
636impl MapProjectionItem {
637 fn to_string_repr(&self) -> String {
638 match self {
639 MapProjectionItem::Property(prop) => format!(".{prop}"),
640 MapProjectionItem::AllProperties => ".*".to_string(),
641 MapProjectionItem::LiteralEntry(key, expr) => {
642 format!("{key}: {}", expr.to_string_repr())
643 }
644 MapProjectionItem::Variable(v) => v.clone(),
645 }
646 }
647}
648
649#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
650pub enum Quantifier {
651 All,
652 Any,
653 Single,
654 None,
655}
656
657impl std::fmt::Display for Quantifier {
658 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
659 match self {
660 Quantifier::All => f.write_str("ALL"),
661 Quantifier::Any => f.write_str("ANY"),
662 Quantifier::Single => f.write_str("SINGLE"),
663 Quantifier::None => f.write_str("NONE"),
664 }
665 }
666}
667
668#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
669pub enum BinaryOp {
670 Add,
671 Sub,
672 Mul,
673 Div,
674 Mod,
675 Pow,
676 Eq,
677 NotEq,
678 Lt,
679 LtEq,
680 Gt,
681 GtEq,
682 And,
683 Or,
684 Xor,
685 Regex,
686 Contains,
687 StartsWith,
688 EndsWith,
689 ApproxEq,
690}
691
692impl std::fmt::Display for BinaryOp {
693 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
694 let s = match self {
695 BinaryOp::Add => "+",
696 BinaryOp::Sub => "-",
697 BinaryOp::Mul => "*",
698 BinaryOp::Div => "/",
699 BinaryOp::Mod => "%",
700 BinaryOp::Pow => "^",
701 BinaryOp::Eq => "=",
702 BinaryOp::NotEq => "<>",
703 BinaryOp::Lt => "<",
704 BinaryOp::LtEq => "<=",
705 BinaryOp::Gt => ">",
706 BinaryOp::GtEq => ">=",
707 BinaryOp::And => "AND",
708 BinaryOp::Or => "OR",
709 BinaryOp::Xor => "XOR",
710 BinaryOp::Regex => "=~",
711 BinaryOp::Contains => "CONTAINS",
712 BinaryOp::StartsWith => "STARTS WITH",
713 BinaryOp::EndsWith => "ENDS WITH",
714 BinaryOp::ApproxEq => "~=",
715 };
716 f.write_str(s)
717 }
718}
719
720#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
721pub enum UnaryOp {
722 Not,
723 Neg,
724}
725
726impl std::fmt::Display for UnaryOp {
727 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
728 match self {
729 UnaryOp::Not => f.write_str("NOT "),
730 UnaryOp::Neg => f.write_str("-"),
731 }
732 }
733}
734
735#[derive(Debug, Clone)]
743pub enum ListAfterIdentifier {
744 Comprehension {
746 list: Expr,
747 filter: Option<Expr>,
748 projection: Box<Expr>,
749 },
750
751 ExpressionTail {
754 suffix: Vec<ExprSuffix>,
755 more: Vec<Expr>,
756 },
757}
758
759impl ListAfterIdentifier {
760 pub fn resolve(self, id: String) -> Expr {
762 match self {
763 ListAfterIdentifier::Comprehension {
764 list,
765 filter,
766 projection,
767 } => Expr::ListComprehension {
768 variable: id,
769 list: Box::new(list),
770 where_clause: filter.map(Box::new),
771 map_expr: projection,
772 },
773 ListAfterIdentifier::ExpressionTail { suffix, more } => {
774 let first = apply_suffixes(Expr::Variable(id), suffix);
775 let items = std::iter::once(first).chain(more).collect();
776 Expr::List(items)
777 }
778 }
779 }
780}
781
782#[derive(Debug, Clone)]
785pub enum ExprSuffix {
786 Property(String),
787 Index(Expr),
788 Slice {
789 start: Option<Expr>,
790 end: Option<Expr>,
791 },
792 FunctionCall(Vec<Expr>),
793 IsNull,
794 IsNotNull,
795 Binary(BinaryOp, Expr),
796 In(Expr),
797}
798
799#[derive(Debug, Clone, PartialEq)]
809pub enum PostfixSuffix {
810 Property(String),
811 Call {
812 args: Vec<Expr>,
813 distinct: bool,
814 window_spec: Option<WindowSpec>,
815 },
816 Index(Expr),
817 Slice {
818 start: Option<Expr>,
819 end: Option<Expr>,
820 },
821 MapProjection(Vec<MapProjectionItem>),
822}
823
824pub fn extract_dotted_name(expr: &Expr) -> Option<String> {
836 match expr {
837 Expr::Variable(name) => Some(name.clone()),
838 Expr::Property(base, prop) => {
839 let base_name = extract_dotted_name(base)?;
840 Some(format!("{base_name}.{prop}"))
841 }
842 _ => None,
843 }
844}
845
846pub fn apply_suffix(expr: Expr, suffix: PostfixSuffix) -> Expr {
852 match suffix {
853 PostfixSuffix::Property(prop) => Expr::Property(Box::new(expr), prop),
854
855 PostfixSuffix::Call {
856 args,
857 distinct,
858 window_spec,
859 } => {
860 let name = extract_dotted_name(&expr).unwrap_or_else(|| {
861 panic!(
862 "apply_suffix: function call requires variable or property chain, got: {expr:?}"
863 )
864 });
865 Expr::FunctionCall {
866 name,
867 args,
868 distinct,
869 window_spec,
870 }
871 }
872
873 PostfixSuffix::Index(index) => Expr::ArrayIndex {
874 array: Box::new(expr),
875 index: Box::new(index),
876 },
877
878 PostfixSuffix::Slice { start, end } => Expr::ArraySlice {
879 array: Box::new(expr),
880 start: start.map(Box::new),
881 end: end.map(Box::new),
882 },
883
884 PostfixSuffix::MapProjection(items) => Expr::MapProjection {
885 base: Box::new(expr),
886 items,
887 },
888 }
889}
890
891fn apply_suffixes(mut expr: Expr, suffixes: Vec<ExprSuffix>) -> Expr {
894 for suffix in suffixes {
895 expr = match suffix {
896 ExprSuffix::Property(name) => Expr::Property(Box::new(expr), name),
897
898 ExprSuffix::Index(idx) => Expr::ArrayIndex {
899 array: Box::new(expr),
900 index: Box::new(idx),
901 },
902
903 ExprSuffix::Slice { start, end } => Expr::ArraySlice {
904 array: Box::new(expr),
905 start: start.map(Box::new),
906 end: end.map(Box::new),
907 },
908
909 ExprSuffix::FunctionCall(args) => {
910 let name = extract_dotted_name(&expr)
911 .unwrap_or_else(|| panic!("Function call suffix requires variable or property chain expression, got: {expr:?}"));
912 Expr::FunctionCall {
913 name,
914 args,
915 distinct: false,
916 window_spec: None,
917 }
918 }
919
920 ExprSuffix::IsNull => Expr::IsNull(Box::new(expr)),
921 ExprSuffix::IsNotNull => Expr::IsNotNull(Box::new(expr)),
922
923 ExprSuffix::Binary(op, rhs) => Expr::BinaryOp {
924 left: Box::new(expr),
925 op,
926 right: Box::new(rhs),
927 },
928
929 ExprSuffix::In(right) => Expr::In {
930 expr: Box::new(expr),
931 list: Box::new(right),
932 },
933 };
934 }
935 expr
936}
937
938fn join_exprs(exprs: &[Expr], sep: &str) -> String {
940 exprs
941 .iter()
942 .map(|e| e.to_string_repr())
943 .collect::<Vec<_>>()
944 .join(sep)
945}
946
947impl Expr {
948 pub const TRUE: Expr = Expr::Literal(CypherLiteral::Bool(true));
953
954 pub fn is_true_literal(&self) -> bool {
956 matches!(self, Expr::Literal(CypherLiteral::Bool(true)))
957 }
958
959 pub fn extract_variable(&self) -> Option<String> {
961 match self {
962 Expr::Variable(v) => Some(v.clone()),
963 _ => None,
964 }
965 }
966
967 pub fn substitute_variable(&self, old_var: &str, new_var: &str) -> Expr {
969 let sub = |e: &Expr| e.substitute_variable(old_var, new_var);
970 let sub_box = |e: &Expr| Box::new(sub(e));
971 let sub_opt = |o: &Option<Box<Expr>>| o.as_ref().map(|e| sub_box(e));
972 let sub_vec = |v: &[Expr]| v.iter().map(sub).collect();
973
974 match self {
975 Expr::Variable(v) if v == old_var => Expr::Variable(new_var.to_string()),
976 Expr::Variable(_) | Expr::Literal(_) | Expr::Parameter(_) | Expr::Wildcard => {
977 self.clone()
978 }
979
980 Expr::Property(base, prop) => Expr::Property(sub_box(base), prop.clone()),
981
982 Expr::List(exprs) => Expr::List(sub_vec(exprs)),
983
984 Expr::Map(entries) => {
985 Expr::Map(entries.iter().map(|(k, v)| (k.clone(), sub(v))).collect())
986 }
987
988 Expr::FunctionCall {
989 name,
990 args,
991 distinct,
992 window_spec,
993 } => Expr::FunctionCall {
994 name: name.clone(),
995 args: sub_vec(args),
996 distinct: *distinct,
997 window_spec: window_spec.clone(),
998 },
999
1000 Expr::BinaryOp { left, op, right } => Expr::BinaryOp {
1001 left: sub_box(left),
1002 op: *op,
1003 right: sub_box(right),
1004 },
1005
1006 Expr::UnaryOp { op, expr } => Expr::UnaryOp {
1007 op: *op,
1008 expr: sub_box(expr),
1009 },
1010
1011 Expr::Case {
1012 expr,
1013 when_then,
1014 else_expr,
1015 } => Expr::Case {
1016 expr: sub_opt(expr),
1017 when_then: when_then.iter().map(|(w, t)| (sub(w), sub(t))).collect(),
1018 else_expr: sub_opt(else_expr),
1019 },
1020
1021 Expr::Exists { .. } | Expr::CountSubquery(_) | Expr::CollectSubquery(_) => self.clone(),
1023
1024 Expr::IsNull(e) => Expr::IsNull(sub_box(e)),
1025 Expr::IsNotNull(e) => Expr::IsNotNull(sub_box(e)),
1026 Expr::IsUnique(e) => Expr::IsUnique(sub_box(e)),
1027
1028 Expr::In { expr, list } => Expr::In {
1029 expr: sub_box(expr),
1030 list: sub_box(list),
1031 },
1032
1033 Expr::ArrayIndex { array, index } => Expr::ArrayIndex {
1034 array: sub_box(array),
1035 index: sub_box(index),
1036 },
1037
1038 Expr::ArraySlice { array, start, end } => Expr::ArraySlice {
1039 array: sub_box(array),
1040 start: sub_opt(start),
1041 end: sub_opt(end),
1042 },
1043
1044 Expr::Quantifier {
1045 quantifier,
1046 variable,
1047 list,
1048 predicate,
1049 } => {
1050 let shadowed = variable == old_var;
1051 Expr::Quantifier {
1052 quantifier: *quantifier,
1053 variable: variable.clone(),
1054 list: sub_box(list),
1055 predicate: if shadowed {
1056 predicate.clone()
1057 } else {
1058 sub_box(predicate)
1059 },
1060 }
1061 }
1062
1063 Expr::Reduce {
1064 accumulator,
1065 init,
1066 variable,
1067 list,
1068 expr,
1069 } => {
1070 let shadowed = variable == old_var || accumulator == old_var;
1071 Expr::Reduce {
1072 accumulator: accumulator.clone(),
1073 init: sub_box(init),
1074 variable: variable.clone(),
1075 list: sub_box(list),
1076 expr: if shadowed {
1077 expr.clone()
1078 } else {
1079 sub_box(expr)
1080 },
1081 }
1082 }
1083
1084 Expr::ListComprehension {
1085 variable,
1086 list,
1087 where_clause,
1088 map_expr,
1089 } => {
1090 let shadowed = variable == old_var;
1091 Expr::ListComprehension {
1092 variable: variable.clone(),
1093 list: sub_box(list),
1094 where_clause: if shadowed {
1095 where_clause.clone()
1096 } else {
1097 sub_opt(where_clause)
1098 },
1099 map_expr: if shadowed {
1100 map_expr.clone()
1101 } else {
1102 sub_box(map_expr)
1103 },
1104 }
1105 }
1106
1107 Expr::PatternComprehension {
1108 path_variable,
1109 pattern,
1110 where_clause,
1111 map_expr,
1112 } => {
1113 if path_variable.as_deref() == Some(old_var) {
1114 self.clone()
1115 } else {
1116 Expr::PatternComprehension {
1117 path_variable: path_variable.clone(),
1118 pattern: pattern.clone(),
1119 where_clause: sub_opt(where_clause),
1120 map_expr: sub_box(map_expr),
1121 }
1122 }
1123 }
1124
1125 Expr::ValidAt {
1126 entity,
1127 timestamp,
1128 start_prop,
1129 end_prop,
1130 } => Expr::ValidAt {
1131 entity: sub_box(entity),
1132 timestamp: sub_box(timestamp),
1133 start_prop: start_prop.clone(),
1134 end_prop: end_prop.clone(),
1135 },
1136
1137 Expr::MapProjection { base, items } => Expr::MapProjection {
1138 base: sub_box(base),
1139 items: items
1140 .iter()
1141 .map(|item| match item {
1142 MapProjectionItem::LiteralEntry(key, expr) => {
1143 MapProjectionItem::LiteralEntry(key.clone(), sub_box(expr))
1144 }
1145 MapProjectionItem::Variable(v) if v == old_var => {
1146 MapProjectionItem::Variable(new_var.to_string())
1147 }
1148 other => other.clone(),
1149 })
1150 .collect(),
1151 },
1152
1153 Expr::LabelCheck { expr, labels } => Expr::LabelCheck {
1154 expr: sub_box(expr),
1155 labels: labels.clone(),
1156 },
1157 }
1158 }
1159
1160 pub fn is_aggregate(&self) -> bool {
1162 match self {
1163 Expr::FunctionCall {
1164 name, window_spec, ..
1165 } => {
1166 window_spec.is_none()
1167 && matches!(
1168 name.to_lowercase().as_str(),
1169 "count"
1170 | "sum"
1171 | "avg"
1172 | "min"
1173 | "max"
1174 | "collect"
1175 | "stdev"
1176 | "stdevp"
1177 | "percentiledisc"
1178 | "percentilecont"
1179 )
1180 }
1181 Expr::CountSubquery(_) | Expr::CollectSubquery(_) => true,
1182 Expr::Property(base, _) => base.is_aggregate(),
1183 Expr::List(exprs) => exprs.iter().any(|e| e.is_aggregate()),
1184 Expr::Map(entries) => entries.iter().any(|(_, v)| v.is_aggregate()),
1185 Expr::BinaryOp { left, right, .. } => left.is_aggregate() || right.is_aggregate(),
1186 Expr::UnaryOp { expr, .. } => expr.is_aggregate(),
1187 Expr::Case {
1188 expr,
1189 when_then,
1190 else_expr,
1191 } => {
1192 expr.as_ref().is_some_and(|e| e.is_aggregate())
1193 || when_then
1194 .iter()
1195 .any(|(w, t)| w.is_aggregate() || t.is_aggregate())
1196 || else_expr.as_ref().is_some_and(|e| e.is_aggregate())
1197 }
1198 Expr::In { expr, list } => expr.is_aggregate() || list.is_aggregate(),
1199 Expr::IsNull(e) | Expr::IsNotNull(e) | Expr::IsUnique(e) => e.is_aggregate(),
1200 Expr::ArrayIndex { array, index } => array.is_aggregate() || index.is_aggregate(),
1201 Expr::ArraySlice { array, start, end } => {
1202 array.is_aggregate()
1203 || start.as_ref().is_some_and(|e| e.is_aggregate())
1204 || end.as_ref().is_some_and(|e| e.is_aggregate())
1205 }
1206 Expr::Quantifier {
1207 list, predicate, ..
1208 } => list.is_aggregate() || predicate.is_aggregate(),
1209 Expr::Reduce {
1210 init, list, expr, ..
1211 } => init.is_aggregate() || list.is_aggregate() || expr.is_aggregate(),
1212 Expr::ListComprehension {
1213 list,
1214 where_clause,
1215 map_expr,
1216 ..
1217 } => {
1218 list.is_aggregate()
1219 || where_clause.as_ref().is_some_and(|e| e.is_aggregate())
1220 || map_expr.is_aggregate()
1221 }
1222 Expr::PatternComprehension {
1223 where_clause,
1224 map_expr,
1225 ..
1226 } => where_clause.as_ref().is_some_and(|e| e.is_aggregate()) || map_expr.is_aggregate(),
1227 _ => false,
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}