Skip to main content

uni_cypher/
ast.rs

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 AS OF 'snapshot_id'
9    Version(String),
10    /// TIMESTAMP AS OF '2025-02-01T12:00:00Z'
11    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    /// Query with time-travel: wraps any query with a VERSION/TIMESTAMP AS OF clause.
26    /// Resolved at the API layer before planning.
27    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, // String representation of type
187    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    /// RETURN * - return all variables
295    All,
296    /// RETURN expr [AS alias]
297    Expr {
298        expr: Expr,
299        alias: Option<String>,
300        /// Original source text of the expression (for column naming).
301        #[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, // Expected to be a property access
327        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,    // shortestPath(...)
394    AllShortest, // allShortestPaths(...)
395}
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>,   // Map literal
412    pub where_clause: Option<Expr>, // Inline WHERE clause
413}
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>,   // Map literal
422    pub where_clause: Option<Expr>, // Inline WHERE clause
423}
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/// Window specification for window functions (OVER clause)
445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub struct WindowSpec {
447    pub partition_by: Vec<Expr>,
448    pub order_by: Vec<SortItem>,
449}
450
451/// A typed Cypher literal value, replacing `serde_json::Value` in the AST.
452///
453/// This makes impossible states unrepresentable: no arrays/objects (those are
454/// `Expr::List`/`Expr::Map`), and integer vs. float is always known.
455#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
456pub enum CypherLiteral {
457    Null,
458    Bool(bool),
459    Integer(i64),
460    Float(f64),
461    String(String),
462    /// Pre-encoded CypherValue bytes (LargeBinary).
463    /// Used when a runtime Value (e.g. list or map) must round-trip through the
464    /// AST while preserving its CypherValue-encoded storage format.
465    Bytes(Vec<u8>),
466}
467
468impl CypherLiteral {
469    /// Convert to `uni_common::Value` for the executor.
470    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        /// True when created from a bare pattern predicate `(n)-->()` in expression context.
559        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    // Array/list indexing and slicing
571    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 expressions: ALL, ANY, SINGLE, NONE
581    Quantifier {
582        quantifier: Quantifier,
583        variable: String,
584        list: Box<Expr>,
585        predicate: Box<Expr>,
586    },
587    // REDUCE expression: REDUCE(acc = init, var IN list | expr)
588    Reduce {
589        accumulator: String,
590        init: Box<Expr>,
591        variable: String,
592        list: Box<Expr>,
593        expr: Box<Expr>,
594    },
595    // List comprehension: [x IN list WHERE pred | expr]
596    ListComprehension {
597        variable: String,
598        list: Box<Expr>,
599        where_clause: Option<Box<Expr>>,
600        map_expr: Box<Expr>,
601    },
602    // Pattern comprehension: [p = (n)-->(m) WHERE pred | expr]
603    PatternComprehension {
604        path_variable: Option<String>,
605        pattern: Pattern,
606        where_clause: Option<Box<Expr>>,
607        map_expr: Box<Expr>,
608    },
609    // VALID_AT macro: e VALID_AT timestamp or e VALID_AT(timestamp, 'start', 'end')
610    ValidAt {
611        entity: Box<Expr>,
612        timestamp: Box<Expr>,
613        start_prop: Option<String>,
614        end_prop: Option<String>,
615    },
616    // Map projection: node{.name, .age, city: node.address.city}
617    MapProjection {
618        base: Box<Expr>,
619        items: Vec<MapProjectionItem>,
620    },
621    /// Label check expression: `a:B` or conjunctive `a:A:B`
622    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),                // .name
631    AllProperties,                   // .*
632    LiteralEntry(String, Box<Expr>), // key: expr
633    Variable(String),                // variable
634}
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// ============================================================================
736// Parser Helper Types (Internal - Used for resolving [Identifier ambiguity)
737// ============================================================================
738
739/// Intermediate type for resolving [Identifier ambiguity in list expressions.
740/// After parsing "[" Identifier, we branch based on the next token to determine
741/// whether this is a list comprehension or a list literal.
742#[derive(Debug, Clone)]
743pub enum ListAfterIdentifier {
744    /// [x IN list WHERE pred | expr] - List comprehension
745    Comprehension {
746        list: Expr,
747        filter: Option<Expr>,
748        projection: Box<Expr>,
749    },
750
751    /// List literal: \[id, ...\], \[id.prop, ...\], or \[id + 1, ...\]
752    /// Empty suffixes means the identifier stands alone as the first element.
753    ExpressionTail {
754        suffix: Vec<ExprSuffix>,
755        more: Vec<Expr>,
756    },
757}
758
759impl ListAfterIdentifier {
760    /// Resolve this intermediate representation into a final Expr, given the identifier.
761    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/// Expression suffix for building complex expressions after an identifier.
783/// Used to parse things like: id.prop, id\[0\], id(), id+1, etc.
784#[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/// Postfix operations for building expressions from primary expressions.
800///
801/// Used by the parser's `PostfixExpression` rule to collect operations like
802/// property access (`.prop`), function calls (`(args)`), and indexing (`[i]`).
803/// This approach avoids LR(1) conflicts that would occur with left-recursive
804/// grammar rules for dotted function names like `uni.vector.query()`.
805///
806/// Note: This is separate from `ExprSuffix` which serves the list comprehension
807/// factoring logic (resolving `[Identifier ...` ambiguity).
808#[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
824/// Extracts a dotted name from a variable or property chain.
825///
826/// Used to convert property access chains into dotted function names.
827///
828/// # Examples
829///
830/// - `Variable("func")` => `Some("func")`
831/// - `Property(Variable("uni"), "validAt")` => `Some("uni.validAt")`
832/// - `Property(Property(Variable("db"), "idx"), "query")` => `Some("db.idx.query")`
833///
834/// Returns `None` for expressions that are not simple identifier chains.
835pub 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
846/// Applies a postfix suffix to an expression, building a new expression.
847///
848/// This function handles the key transformation for dotted function names:
849/// when a `Call` suffix follows a property chain like `db.idx.vector`,
850/// it extracts the dotted name and creates a `FunctionCall` expression.
851pub 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
891/// Apply a sequence of expression suffixes to build a complete expression.
892/// Example: id.prop[0] + 1 => ((id.prop)[0]) + 1
893fn 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
938/// Join a slice of expressions with a separator using `to_string_repr`.
939fn 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    /// Sentinel expression representing a literal `true`.
949    ///
950    /// Useful in the planner for predicate reduction: when all conjuncts have
951    /// been pushed down, the remaining predicate is replaced with this constant.
952    pub const TRUE: Expr = Expr::Literal(CypherLiteral::Bool(true));
953
954    /// Returns `true` if this expression is the literal boolean `true`.
955    pub fn is_true_literal(&self) -> bool {
956        matches!(self, Expr::Literal(CypherLiteral::Bool(true)))
957    }
958
959    /// Extract a simple variable name if this expression is just a variable reference
960    pub fn extract_variable(&self) -> Option<String> {
961        match self {
962            Expr::Variable(v) => Some(v.clone()),
963            _ => None,
964        }
965    }
966
967    /// Substitute all occurrences of a variable with a new variable name
968    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            // Don't substitute inside subqueries
1022            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    /// Check if this expression contains an aggregate function
1161    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    /// Generate a string representation of this expression for debugging/display
1232    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    /// Call `f` on each direct child expression (non-recursive).
1433    ///
1434    /// Does NOT descend into subqueries (Exists, CountSubquery, CollectSubquery)
1435    /// since those have separate variable scope.
1436    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    /// Map each direct child expression through `f`, producing a new Expr.
1549    ///
1550    /// Same scoping rules as `for_each_child`: does not descend into subqueries.
1551    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}