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}