Skip to main content

grafeo_engine/query/
plan.rs

1//! Logical query plan representation.
2//!
3//! The logical plan is the intermediate representation between parsed queries
4//! and physical execution. Both GQL and Cypher queries are translated to this
5//! common representation.
6
7use grafeo_common::types::Value;
8
9/// A logical query plan.
10#[derive(Debug, Clone)]
11pub struct LogicalPlan {
12    /// The root operator of the plan.
13    pub root: LogicalOperator,
14}
15
16impl LogicalPlan {
17    /// Creates a new logical plan with the given root operator.
18    pub fn new(root: LogicalOperator) -> Self {
19        Self { root }
20    }
21}
22
23/// A logical operator in the query plan.
24#[derive(Debug, Clone)]
25pub enum LogicalOperator {
26    /// Scan all nodes, optionally filtered by label.
27    NodeScan(NodeScanOp),
28
29    /// Scan all edges, optionally filtered by type.
30    EdgeScan(EdgeScanOp),
31
32    /// Expand from nodes to neighbors via edges.
33    Expand(ExpandOp),
34
35    /// Filter rows based on a predicate.
36    Filter(FilterOp),
37
38    /// Project specific columns.
39    Project(ProjectOp),
40
41    /// Join two inputs.
42    Join(JoinOp),
43
44    /// Aggregate with grouping.
45    Aggregate(AggregateOp),
46
47    /// Limit the number of results.
48    Limit(LimitOp),
49
50    /// Skip a number of results.
51    Skip(SkipOp),
52
53    /// Sort results.
54    Sort(SortOp),
55
56    /// Remove duplicate results.
57    Distinct(DistinctOp),
58
59    /// Create a new node.
60    CreateNode(CreateNodeOp),
61
62    /// Create a new edge.
63    CreateEdge(CreateEdgeOp),
64
65    /// Delete a node.
66    DeleteNode(DeleteNodeOp),
67
68    /// Delete an edge.
69    DeleteEdge(DeleteEdgeOp),
70
71    /// Set properties on a node or edge.
72    SetProperty(SetPropertyOp),
73
74    /// Add labels to a node.
75    AddLabel(AddLabelOp),
76
77    /// Remove labels from a node.
78    RemoveLabel(RemoveLabelOp),
79
80    /// Return results (terminal operator).
81    Return(ReturnOp),
82
83    /// Empty result set.
84    Empty,
85
86    // ==================== RDF/SPARQL Operators ====================
87    /// Scan RDF triples matching a pattern.
88    TripleScan(TripleScanOp),
89
90    /// Union of multiple result sets.
91    Union(UnionOp),
92
93    /// Left outer join for OPTIONAL patterns.
94    LeftJoin(LeftJoinOp),
95
96    /// Anti-join for MINUS patterns.
97    AntiJoin(AntiJoinOp),
98
99    /// Bind a variable to an expression.
100    Bind(BindOp),
101
102    /// Unwind a list into individual rows.
103    Unwind(UnwindOp),
104
105    /// Merge a pattern (match or create).
106    Merge(MergeOp),
107
108    /// Find shortest path between nodes.
109    ShortestPath(ShortestPathOp),
110
111    // ==================== SPARQL Update Operators ====================
112    /// Insert RDF triples.
113    InsertTriple(InsertTripleOp),
114
115    /// Delete RDF triples.
116    DeleteTriple(DeleteTripleOp),
117
118    /// SPARQL MODIFY operation (DELETE/INSERT WHERE).
119    /// Evaluates WHERE once, applies DELETE templates, then INSERT templates.
120    Modify(ModifyOp),
121
122    /// Clear a graph (remove all triples).
123    ClearGraph(ClearGraphOp),
124
125    /// Create a new named graph.
126    CreateGraph(CreateGraphOp),
127
128    /// Drop (remove) a named graph.
129    DropGraph(DropGraphOp),
130
131    /// Load data from a URL into a graph.
132    LoadGraph(LoadGraphOp),
133
134    /// Copy triples from one graph to another.
135    CopyGraph(CopyGraphOp),
136
137    /// Move triples from one graph to another.
138    MoveGraph(MoveGraphOp),
139
140    /// Add (merge) triples from one graph to another.
141    AddGraph(AddGraphOp),
142}
143
144/// Scan nodes from the graph.
145#[derive(Debug, Clone)]
146pub struct NodeScanOp {
147    /// Variable name to bind the node to.
148    pub variable: String,
149    /// Optional label filter.
150    pub label: Option<String>,
151    /// Child operator (if any, for chained patterns).
152    pub input: Option<Box<LogicalOperator>>,
153}
154
155/// Scan edges from the graph.
156#[derive(Debug, Clone)]
157pub struct EdgeScanOp {
158    /// Variable name to bind the edge to.
159    pub variable: String,
160    /// Optional edge type filter.
161    pub edge_type: Option<String>,
162    /// Child operator (if any).
163    pub input: Option<Box<LogicalOperator>>,
164}
165
166/// Expand from nodes to their neighbors.
167#[derive(Debug, Clone)]
168pub struct ExpandOp {
169    /// Source node variable.
170    pub from_variable: String,
171    /// Target node variable to bind.
172    pub to_variable: String,
173    /// Edge variable to bind (optional).
174    pub edge_variable: Option<String>,
175    /// Direction of expansion.
176    pub direction: ExpandDirection,
177    /// Optional edge type filter.
178    pub edge_type: Option<String>,
179    /// Minimum hops (for variable-length patterns).
180    pub min_hops: u32,
181    /// Maximum hops (for variable-length patterns).
182    pub max_hops: Option<u32>,
183    /// Input operator.
184    pub input: Box<LogicalOperator>,
185    /// Path alias for variable-length patterns (e.g., `p` in `p = (a)-[*1..3]->(b)`).
186    /// When set, a path length column will be output under this name.
187    pub path_alias: Option<String>,
188}
189
190/// Direction for edge expansion.
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum ExpandDirection {
193    /// Follow outgoing edges.
194    Outgoing,
195    /// Follow incoming edges.
196    Incoming,
197    /// Follow edges in either direction.
198    Both,
199}
200
201/// Join two inputs.
202#[derive(Debug, Clone)]
203pub struct JoinOp {
204    /// Left input.
205    pub left: Box<LogicalOperator>,
206    /// Right input.
207    pub right: Box<LogicalOperator>,
208    /// Join type.
209    pub join_type: JoinType,
210    /// Join conditions.
211    pub conditions: Vec<JoinCondition>,
212}
213
214/// Join type.
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub enum JoinType {
217    /// Inner join.
218    Inner,
219    /// Left outer join.
220    Left,
221    /// Right outer join.
222    Right,
223    /// Full outer join.
224    Full,
225    /// Cross join (Cartesian product).
226    Cross,
227    /// Semi join (returns left rows with matching right rows).
228    Semi,
229    /// Anti join (returns left rows without matching right rows).
230    Anti,
231}
232
233/// A join condition.
234#[derive(Debug, Clone)]
235pub struct JoinCondition {
236    /// Left expression.
237    pub left: LogicalExpression,
238    /// Right expression.
239    pub right: LogicalExpression,
240}
241
242/// Aggregate with grouping.
243#[derive(Debug, Clone)]
244pub struct AggregateOp {
245    /// Group by expressions.
246    pub group_by: Vec<LogicalExpression>,
247    /// Aggregate functions.
248    pub aggregates: Vec<AggregateExpr>,
249    /// Input operator.
250    pub input: Box<LogicalOperator>,
251    /// HAVING clause filter (applied after aggregation).
252    pub having: Option<LogicalExpression>,
253}
254
255/// An aggregate expression.
256#[derive(Debug, Clone)]
257pub struct AggregateExpr {
258    /// Aggregate function.
259    pub function: AggregateFunction,
260    /// Expression to aggregate.
261    pub expression: Option<LogicalExpression>,
262    /// Whether to use DISTINCT.
263    pub distinct: bool,
264    /// Alias for the result.
265    pub alias: Option<String>,
266    /// Percentile parameter for PERCENTILE_DISC/PERCENTILE_CONT (0.0 to 1.0).
267    pub percentile: Option<f64>,
268}
269
270/// Aggregate function.
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum AggregateFunction {
273    /// Count all rows (COUNT(*)).
274    Count,
275    /// Count non-null values (COUNT(expr)).
276    CountNonNull,
277    /// Sum values.
278    Sum,
279    /// Average values.
280    Avg,
281    /// Minimum value.
282    Min,
283    /// Maximum value.
284    Max,
285    /// Collect into list.
286    Collect,
287    /// Sample standard deviation (STDEV).
288    StdDev,
289    /// Population standard deviation (STDEVP).
290    StdDevPop,
291    /// Discrete percentile (PERCENTILE_DISC).
292    PercentileDisc,
293    /// Continuous percentile (PERCENTILE_CONT).
294    PercentileCont,
295}
296
297/// Filter rows based on a predicate.
298#[derive(Debug, Clone)]
299pub struct FilterOp {
300    /// The filter predicate.
301    pub predicate: LogicalExpression,
302    /// Input operator.
303    pub input: Box<LogicalOperator>,
304}
305
306/// Project specific columns.
307#[derive(Debug, Clone)]
308pub struct ProjectOp {
309    /// Columns to project.
310    pub projections: Vec<Projection>,
311    /// Input operator.
312    pub input: Box<LogicalOperator>,
313}
314
315/// A single projection (column selection or computation).
316#[derive(Debug, Clone)]
317pub struct Projection {
318    /// Expression to compute.
319    pub expression: LogicalExpression,
320    /// Alias for the result.
321    pub alias: Option<String>,
322}
323
324/// Limit the number of results.
325#[derive(Debug, Clone)]
326pub struct LimitOp {
327    /// Maximum number of rows to return.
328    pub count: usize,
329    /// Input operator.
330    pub input: Box<LogicalOperator>,
331}
332
333/// Skip a number of results.
334#[derive(Debug, Clone)]
335pub struct SkipOp {
336    /// Number of rows to skip.
337    pub count: usize,
338    /// Input operator.
339    pub input: Box<LogicalOperator>,
340}
341
342/// Sort results.
343#[derive(Debug, Clone)]
344pub struct SortOp {
345    /// Sort keys.
346    pub keys: Vec<SortKey>,
347    /// Input operator.
348    pub input: Box<LogicalOperator>,
349}
350
351/// A sort key.
352#[derive(Debug, Clone)]
353pub struct SortKey {
354    /// Expression to sort by.
355    pub expression: LogicalExpression,
356    /// Sort order.
357    pub order: SortOrder,
358}
359
360/// Sort order.
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub enum SortOrder {
363    /// Ascending order.
364    Ascending,
365    /// Descending order.
366    Descending,
367}
368
369/// Remove duplicate results.
370#[derive(Debug, Clone)]
371pub struct DistinctOp {
372    /// Input operator.
373    pub input: Box<LogicalOperator>,
374    /// Optional columns to use for deduplication.
375    /// If None, all columns are used.
376    pub columns: Option<Vec<String>>,
377}
378
379/// Create a new node.
380#[derive(Debug, Clone)]
381pub struct CreateNodeOp {
382    /// Variable name to bind the created node to.
383    pub variable: String,
384    /// Labels for the new node.
385    pub labels: Vec<String>,
386    /// Properties for the new node.
387    pub properties: Vec<(String, LogicalExpression)>,
388    /// Input operator (for chained creates).
389    pub input: Option<Box<LogicalOperator>>,
390}
391
392/// Create a new edge.
393#[derive(Debug, Clone)]
394pub struct CreateEdgeOp {
395    /// Variable name to bind the created edge to.
396    pub variable: Option<String>,
397    /// Source node variable.
398    pub from_variable: String,
399    /// Target node variable.
400    pub to_variable: String,
401    /// Edge type.
402    pub edge_type: String,
403    /// Properties for the new edge.
404    pub properties: Vec<(String, LogicalExpression)>,
405    /// Input operator.
406    pub input: Box<LogicalOperator>,
407}
408
409/// Delete a node.
410#[derive(Debug, Clone)]
411pub struct DeleteNodeOp {
412    /// Variable of the node to delete.
413    pub variable: String,
414    /// Whether to detach (delete connected edges) before deleting.
415    pub detach: bool,
416    /// Input operator.
417    pub input: Box<LogicalOperator>,
418}
419
420/// Delete an edge.
421#[derive(Debug, Clone)]
422pub struct DeleteEdgeOp {
423    /// Variable of the edge to delete.
424    pub variable: String,
425    /// Input operator.
426    pub input: Box<LogicalOperator>,
427}
428
429/// Set properties on a node or edge.
430#[derive(Debug, Clone)]
431pub struct SetPropertyOp {
432    /// Variable of the entity to update.
433    pub variable: String,
434    /// Properties to set (name -> expression).
435    pub properties: Vec<(String, LogicalExpression)>,
436    /// Whether to replace all properties (vs. merge).
437    pub replace: bool,
438    /// Input operator.
439    pub input: Box<LogicalOperator>,
440}
441
442/// Add labels to a node.
443#[derive(Debug, Clone)]
444pub struct AddLabelOp {
445    /// Variable of the node to update.
446    pub variable: String,
447    /// Labels to add.
448    pub labels: Vec<String>,
449    /// Input operator.
450    pub input: Box<LogicalOperator>,
451}
452
453/// Remove labels from a node.
454#[derive(Debug, Clone)]
455pub struct RemoveLabelOp {
456    /// Variable of the node to update.
457    pub variable: String,
458    /// Labels to remove.
459    pub labels: Vec<String>,
460    /// Input operator.
461    pub input: Box<LogicalOperator>,
462}
463
464// ==================== RDF/SPARQL Operators ====================
465
466/// Scan RDF triples matching a pattern.
467#[derive(Debug, Clone)]
468pub struct TripleScanOp {
469    /// Subject pattern (variable name or IRI).
470    pub subject: TripleComponent,
471    /// Predicate pattern (variable name or IRI).
472    pub predicate: TripleComponent,
473    /// Object pattern (variable name, IRI, or literal).
474    pub object: TripleComponent,
475    /// Named graph (optional).
476    pub graph: Option<TripleComponent>,
477    /// Input operator (for chained patterns).
478    pub input: Option<Box<LogicalOperator>>,
479}
480
481/// A component of a triple pattern.
482#[derive(Debug, Clone)]
483pub enum TripleComponent {
484    /// A variable to bind.
485    Variable(String),
486    /// A constant IRI.
487    Iri(String),
488    /// A constant literal value.
489    Literal(Value),
490}
491
492/// Union of multiple result sets.
493#[derive(Debug, Clone)]
494pub struct UnionOp {
495    /// Inputs to union together.
496    pub inputs: Vec<LogicalOperator>,
497}
498
499/// Left outer join for OPTIONAL patterns.
500#[derive(Debug, Clone)]
501pub struct LeftJoinOp {
502    /// Left (required) input.
503    pub left: Box<LogicalOperator>,
504    /// Right (optional) input.
505    pub right: Box<LogicalOperator>,
506    /// Optional filter condition.
507    pub condition: Option<LogicalExpression>,
508}
509
510/// Anti-join for MINUS patterns.
511#[derive(Debug, Clone)]
512pub struct AntiJoinOp {
513    /// Left input (results to keep if no match on right).
514    pub left: Box<LogicalOperator>,
515    /// Right input (patterns to exclude).
516    pub right: Box<LogicalOperator>,
517}
518
519/// Bind a variable to an expression.
520#[derive(Debug, Clone)]
521pub struct BindOp {
522    /// Expression to compute.
523    pub expression: LogicalExpression,
524    /// Variable to bind the result to.
525    pub variable: String,
526    /// Input operator.
527    pub input: Box<LogicalOperator>,
528}
529
530/// Unwind a list into individual rows.
531///
532/// For each input row, evaluates the expression (which should return a list)
533/// and emits one row for each element in the list.
534#[derive(Debug, Clone)]
535pub struct UnwindOp {
536    /// The list expression to unwind.
537    pub expression: LogicalExpression,
538    /// The variable name for each element.
539    pub variable: String,
540    /// Input operator.
541    pub input: Box<LogicalOperator>,
542}
543
544/// Merge a pattern (match or create).
545///
546/// MERGE tries to match a pattern in the graph. If found, returns the existing
547/// elements (optionally applying ON MATCH SET). If not found, creates the pattern
548/// (optionally applying ON CREATE SET).
549#[derive(Debug, Clone)]
550pub struct MergeOp {
551    /// The node to merge.
552    pub variable: String,
553    /// Labels to match/create.
554    pub labels: Vec<String>,
555    /// Properties that must match (used for both matching and creation).
556    pub match_properties: Vec<(String, LogicalExpression)>,
557    /// Properties to set on CREATE.
558    pub on_create: Vec<(String, LogicalExpression)>,
559    /// Properties to set on MATCH.
560    pub on_match: Vec<(String, LogicalExpression)>,
561    /// Input operator.
562    pub input: Box<LogicalOperator>,
563}
564
565/// Find shortest path between two nodes.
566///
567/// This operator uses Dijkstra's algorithm to find the shortest path(s)
568/// between a source node and a target node, optionally filtered by edge type.
569#[derive(Debug, Clone)]
570pub struct ShortestPathOp {
571    /// Input operator providing source/target nodes.
572    pub input: Box<LogicalOperator>,
573    /// Variable name for the source node.
574    pub source_var: String,
575    /// Variable name for the target node.
576    pub target_var: String,
577    /// Optional edge type filter.
578    pub edge_type: Option<String>,
579    /// Direction of edge traversal.
580    pub direction: ExpandDirection,
581    /// Variable name to bind the path result.
582    pub path_alias: String,
583    /// Whether to find all shortest paths (vs. just one).
584    pub all_paths: bool,
585}
586
587// ==================== SPARQL Update Operators ====================
588
589/// Insert RDF triples.
590#[derive(Debug, Clone)]
591pub struct InsertTripleOp {
592    /// Subject of the triple.
593    pub subject: TripleComponent,
594    /// Predicate of the triple.
595    pub predicate: TripleComponent,
596    /// Object of the triple.
597    pub object: TripleComponent,
598    /// Named graph (optional).
599    pub graph: Option<String>,
600    /// Input operator (provides variable bindings).
601    pub input: Option<Box<LogicalOperator>>,
602}
603
604/// Delete RDF triples.
605#[derive(Debug, Clone)]
606pub struct DeleteTripleOp {
607    /// Subject pattern.
608    pub subject: TripleComponent,
609    /// Predicate pattern.
610    pub predicate: TripleComponent,
611    /// Object pattern.
612    pub object: TripleComponent,
613    /// Named graph (optional).
614    pub graph: Option<String>,
615    /// Input operator (provides variable bindings).
616    pub input: Option<Box<LogicalOperator>>,
617}
618
619/// SPARQL MODIFY operation (DELETE/INSERT WHERE).
620///
621/// Per SPARQL 1.1 Update spec, this operator:
622/// 1. Evaluates the WHERE clause once to get bindings
623/// 2. Applies DELETE templates using those bindings
624/// 3. Applies INSERT templates using the SAME bindings
625///
626/// This ensures DELETE and INSERT see consistent data.
627#[derive(Debug, Clone)]
628pub struct ModifyOp {
629    /// DELETE triple templates (patterns with variables).
630    pub delete_templates: Vec<TripleTemplate>,
631    /// INSERT triple templates (patterns with variables).
632    pub insert_templates: Vec<TripleTemplate>,
633    /// WHERE clause that provides variable bindings.
634    pub where_clause: Box<LogicalOperator>,
635    /// Named graph context (for WITH clause).
636    pub graph: Option<String>,
637}
638
639/// A triple template for DELETE/INSERT operations.
640#[derive(Debug, Clone)]
641pub struct TripleTemplate {
642    /// Subject (may be a variable).
643    pub subject: TripleComponent,
644    /// Predicate (may be a variable).
645    pub predicate: TripleComponent,
646    /// Object (may be a variable or literal).
647    pub object: TripleComponent,
648    /// Named graph (optional).
649    pub graph: Option<String>,
650}
651
652/// Clear all triples from a graph.
653#[derive(Debug, Clone)]
654pub struct ClearGraphOp {
655    /// Target graph (None = default graph, Some("") = all named, Some(iri) = specific graph).
656    pub graph: Option<String>,
657    /// Whether to silently ignore errors.
658    pub silent: bool,
659}
660
661/// Create a new named graph.
662#[derive(Debug, Clone)]
663pub struct CreateGraphOp {
664    /// IRI of the graph to create.
665    pub graph: String,
666    /// Whether to silently ignore if graph already exists.
667    pub silent: bool,
668}
669
670/// Drop (remove) a named graph.
671#[derive(Debug, Clone)]
672pub struct DropGraphOp {
673    /// Target graph (None = default graph).
674    pub graph: Option<String>,
675    /// Whether to silently ignore errors.
676    pub silent: bool,
677}
678
679/// Load data from a URL into a graph.
680#[derive(Debug, Clone)]
681pub struct LoadGraphOp {
682    /// Source URL to load data from.
683    pub source: String,
684    /// Destination graph (None = default graph).
685    pub destination: Option<String>,
686    /// Whether to silently ignore errors.
687    pub silent: bool,
688}
689
690/// Copy triples from one graph to another.
691#[derive(Debug, Clone)]
692pub struct CopyGraphOp {
693    /// Source graph.
694    pub source: Option<String>,
695    /// Destination graph.
696    pub destination: Option<String>,
697    /// Whether to silently ignore errors.
698    pub silent: bool,
699}
700
701/// Move triples from one graph to another.
702#[derive(Debug, Clone)]
703pub struct MoveGraphOp {
704    /// Source graph.
705    pub source: Option<String>,
706    /// Destination graph.
707    pub destination: Option<String>,
708    /// Whether to silently ignore errors.
709    pub silent: bool,
710}
711
712/// Add (merge) triples from one graph to another.
713#[derive(Debug, Clone)]
714pub struct AddGraphOp {
715    /// Source graph.
716    pub source: Option<String>,
717    /// Destination graph.
718    pub destination: Option<String>,
719    /// Whether to silently ignore errors.
720    pub silent: bool,
721}
722
723/// Return results (terminal operator).
724#[derive(Debug, Clone)]
725pub struct ReturnOp {
726    /// Items to return.
727    pub items: Vec<ReturnItem>,
728    /// Whether to return distinct results.
729    pub distinct: bool,
730    /// Input operator.
731    pub input: Box<LogicalOperator>,
732}
733
734/// A single return item.
735#[derive(Debug, Clone)]
736pub struct ReturnItem {
737    /// Expression to return.
738    pub expression: LogicalExpression,
739    /// Alias for the result column.
740    pub alias: Option<String>,
741}
742
743/// A logical expression.
744#[derive(Debug, Clone)]
745pub enum LogicalExpression {
746    /// A literal value.
747    Literal(Value),
748
749    /// A variable reference.
750    Variable(String),
751
752    /// Property access (e.g., n.name).
753    Property {
754        /// The variable to access.
755        variable: String,
756        /// The property name.
757        property: String,
758    },
759
760    /// Binary operation.
761    Binary {
762        /// Left operand.
763        left: Box<LogicalExpression>,
764        /// Operator.
765        op: BinaryOp,
766        /// Right operand.
767        right: Box<LogicalExpression>,
768    },
769
770    /// Unary operation.
771    Unary {
772        /// Operator.
773        op: UnaryOp,
774        /// Operand.
775        operand: Box<LogicalExpression>,
776    },
777
778    /// Function call.
779    FunctionCall {
780        /// Function name.
781        name: String,
782        /// Arguments.
783        args: Vec<LogicalExpression>,
784        /// Whether DISTINCT is applied (e.g., COUNT(DISTINCT x)).
785        distinct: bool,
786    },
787
788    /// List literal.
789    List(Vec<LogicalExpression>),
790
791    /// Map literal (e.g., {name: 'Alice', age: 30}).
792    Map(Vec<(String, LogicalExpression)>),
793
794    /// Index access (e.g., `list[0]`).
795    IndexAccess {
796        /// The base expression (typically a list or string).
797        base: Box<LogicalExpression>,
798        /// The index expression.
799        index: Box<LogicalExpression>,
800    },
801
802    /// Slice access (e.g., list[1..3]).
803    SliceAccess {
804        /// The base expression (typically a list or string).
805        base: Box<LogicalExpression>,
806        /// Start index (None means from beginning).
807        start: Option<Box<LogicalExpression>>,
808        /// End index (None means to end).
809        end: Option<Box<LogicalExpression>>,
810    },
811
812    /// CASE expression.
813    Case {
814        /// Test expression (for simple CASE).
815        operand: Option<Box<LogicalExpression>>,
816        /// WHEN clauses.
817        when_clauses: Vec<(LogicalExpression, LogicalExpression)>,
818        /// ELSE clause.
819        else_clause: Option<Box<LogicalExpression>>,
820    },
821
822    /// Parameter reference.
823    Parameter(String),
824
825    /// Labels of a node.
826    Labels(String),
827
828    /// Type of an edge.
829    Type(String),
830
831    /// ID of a node or edge.
832    Id(String),
833
834    /// List comprehension: [x IN list WHERE predicate | expression]
835    ListComprehension {
836        /// Variable name for each element.
837        variable: String,
838        /// The source list expression.
839        list_expr: Box<LogicalExpression>,
840        /// Optional filter predicate.
841        filter_expr: Option<Box<LogicalExpression>>,
842        /// The mapping expression for each element.
843        map_expr: Box<LogicalExpression>,
844    },
845
846    /// EXISTS subquery.
847    ExistsSubquery(Box<LogicalOperator>),
848
849    /// COUNT subquery.
850    CountSubquery(Box<LogicalOperator>),
851}
852
853/// Binary operator.
854#[derive(Debug, Clone, Copy, PartialEq, Eq)]
855pub enum BinaryOp {
856    /// Equality comparison (=).
857    Eq,
858    /// Inequality comparison (<>).
859    Ne,
860    /// Less than (<).
861    Lt,
862    /// Less than or equal (<=).
863    Le,
864    /// Greater than (>).
865    Gt,
866    /// Greater than or equal (>=).
867    Ge,
868
869    /// Logical AND.
870    And,
871    /// Logical OR.
872    Or,
873    /// Logical XOR.
874    Xor,
875
876    /// Addition (+).
877    Add,
878    /// Subtraction (-).
879    Sub,
880    /// Multiplication (*).
881    Mul,
882    /// Division (/).
883    Div,
884    /// Modulo (%).
885    Mod,
886
887    /// String concatenation.
888    Concat,
889    /// String starts with.
890    StartsWith,
891    /// String ends with.
892    EndsWith,
893    /// String contains.
894    Contains,
895
896    /// Collection membership (IN).
897    In,
898    /// Pattern matching (LIKE).
899    Like,
900    /// Regex matching (=~).
901    Regex,
902    /// Power/exponentiation (^).
903    Pow,
904}
905
906/// Unary operator.
907#[derive(Debug, Clone, Copy, PartialEq, Eq)]
908pub enum UnaryOp {
909    /// Logical NOT.
910    Not,
911    /// Numeric negation.
912    Neg,
913    /// IS NULL check.
914    IsNull,
915    /// IS NOT NULL check.
916    IsNotNull,
917}
918
919#[cfg(test)]
920mod tests {
921    use super::*;
922
923    #[test]
924    fn test_simple_node_scan_plan() {
925        let plan = LogicalPlan::new(LogicalOperator::Return(ReturnOp {
926            items: vec![ReturnItem {
927                expression: LogicalExpression::Variable("n".into()),
928                alias: None,
929            }],
930            distinct: false,
931            input: Box::new(LogicalOperator::NodeScan(NodeScanOp {
932                variable: "n".into(),
933                label: Some("Person".into()),
934                input: None,
935            })),
936        }));
937
938        // Verify structure
939        if let LogicalOperator::Return(ret) = &plan.root {
940            assert_eq!(ret.items.len(), 1);
941            assert!(!ret.distinct);
942            if let LogicalOperator::NodeScan(scan) = ret.input.as_ref() {
943                assert_eq!(scan.variable, "n");
944                assert_eq!(scan.label, Some("Person".into()));
945            } else {
946                panic!("Expected NodeScan");
947            }
948        } else {
949            panic!("Expected Return");
950        }
951    }
952
953    #[test]
954    fn test_filter_plan() {
955        let plan = LogicalPlan::new(LogicalOperator::Return(ReturnOp {
956            items: vec![ReturnItem {
957                expression: LogicalExpression::Property {
958                    variable: "n".into(),
959                    property: "name".into(),
960                },
961                alias: Some("name".into()),
962            }],
963            distinct: false,
964            input: Box::new(LogicalOperator::Filter(FilterOp {
965                predicate: LogicalExpression::Binary {
966                    left: Box::new(LogicalExpression::Property {
967                        variable: "n".into(),
968                        property: "age".into(),
969                    }),
970                    op: BinaryOp::Gt,
971                    right: Box::new(LogicalExpression::Literal(Value::Int64(30))),
972                },
973                input: Box::new(LogicalOperator::NodeScan(NodeScanOp {
974                    variable: "n".into(),
975                    label: Some("Person".into()),
976                    input: None,
977                })),
978            })),
979        }));
980
981        if let LogicalOperator::Return(ret) = &plan.root {
982            if let LogicalOperator::Filter(filter) = ret.input.as_ref() {
983                if let LogicalExpression::Binary { op, .. } = &filter.predicate {
984                    assert_eq!(*op, BinaryOp::Gt);
985                } else {
986                    panic!("Expected Binary expression");
987                }
988            } else {
989                panic!("Expected Filter");
990            }
991        } else {
992            panic!("Expected Return");
993        }
994    }
995}