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
109/// Scan nodes from the graph.
110#[derive(Debug, Clone)]
111pub struct NodeScanOp {
112    /// Variable name to bind the node to.
113    pub variable: String,
114    /// Optional label filter.
115    pub label: Option<String>,
116    /// Child operator (if any, for chained patterns).
117    pub input: Option<Box<LogicalOperator>>,
118}
119
120/// Scan edges from the graph.
121#[derive(Debug, Clone)]
122pub struct EdgeScanOp {
123    /// Variable name to bind the edge to.
124    pub variable: String,
125    /// Optional edge type filter.
126    pub edge_type: Option<String>,
127    /// Child operator (if any).
128    pub input: Option<Box<LogicalOperator>>,
129}
130
131/// Expand from nodes to their neighbors.
132#[derive(Debug, Clone)]
133pub struct ExpandOp {
134    /// Source node variable.
135    pub from_variable: String,
136    /// Target node variable to bind.
137    pub to_variable: String,
138    /// Edge variable to bind (optional).
139    pub edge_variable: Option<String>,
140    /// Direction of expansion.
141    pub direction: ExpandDirection,
142    /// Optional edge type filter.
143    pub edge_type: Option<String>,
144    /// Minimum hops (for variable-length patterns).
145    pub min_hops: u32,
146    /// Maximum hops (for variable-length patterns).
147    pub max_hops: Option<u32>,
148    /// Input operator.
149    pub input: Box<LogicalOperator>,
150}
151
152/// Direction for edge expansion.
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum ExpandDirection {
155    /// Follow outgoing edges.
156    Outgoing,
157    /// Follow incoming edges.
158    Incoming,
159    /// Follow edges in either direction.
160    Both,
161}
162
163/// Join two inputs.
164#[derive(Debug, Clone)]
165pub struct JoinOp {
166    /// Left input.
167    pub left: Box<LogicalOperator>,
168    /// Right input.
169    pub right: Box<LogicalOperator>,
170    /// Join type.
171    pub join_type: JoinType,
172    /// Join conditions.
173    pub conditions: Vec<JoinCondition>,
174}
175
176/// Join type.
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum JoinType {
179    /// Inner join.
180    Inner,
181    /// Left outer join.
182    Left,
183    /// Right outer join.
184    Right,
185    /// Full outer join.
186    Full,
187    /// Cross join (Cartesian product).
188    Cross,
189    /// Semi join (returns left rows with matching right rows).
190    Semi,
191    /// Anti join (returns left rows without matching right rows).
192    Anti,
193}
194
195/// A join condition.
196#[derive(Debug, Clone)]
197pub struct JoinCondition {
198    /// Left expression.
199    pub left: LogicalExpression,
200    /// Right expression.
201    pub right: LogicalExpression,
202}
203
204/// Aggregate with grouping.
205#[derive(Debug, Clone)]
206pub struct AggregateOp {
207    /// Group by expressions.
208    pub group_by: Vec<LogicalExpression>,
209    /// Aggregate functions.
210    pub aggregates: Vec<AggregateExpr>,
211    /// Input operator.
212    pub input: Box<LogicalOperator>,
213}
214
215/// An aggregate expression.
216#[derive(Debug, Clone)]
217pub struct AggregateExpr {
218    /// Aggregate function.
219    pub function: AggregateFunction,
220    /// Expression to aggregate.
221    pub expression: Option<LogicalExpression>,
222    /// Whether to use DISTINCT.
223    pub distinct: bool,
224    /// Alias for the result.
225    pub alias: Option<String>,
226}
227
228/// Aggregate function.
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum AggregateFunction {
231    /// Count rows.
232    Count,
233    /// Sum values.
234    Sum,
235    /// Average values.
236    Avg,
237    /// Minimum value.
238    Min,
239    /// Maximum value.
240    Max,
241    /// Collect into list.
242    Collect,
243}
244
245/// Filter rows based on a predicate.
246#[derive(Debug, Clone)]
247pub struct FilterOp {
248    /// The filter predicate.
249    pub predicate: LogicalExpression,
250    /// Input operator.
251    pub input: Box<LogicalOperator>,
252}
253
254/// Project specific columns.
255#[derive(Debug, Clone)]
256pub struct ProjectOp {
257    /// Columns to project.
258    pub projections: Vec<Projection>,
259    /// Input operator.
260    pub input: Box<LogicalOperator>,
261}
262
263/// A single projection (column selection or computation).
264#[derive(Debug, Clone)]
265pub struct Projection {
266    /// Expression to compute.
267    pub expression: LogicalExpression,
268    /// Alias for the result.
269    pub alias: Option<String>,
270}
271
272/// Limit the number of results.
273#[derive(Debug, Clone)]
274pub struct LimitOp {
275    /// Maximum number of rows to return.
276    pub count: usize,
277    /// Input operator.
278    pub input: Box<LogicalOperator>,
279}
280
281/// Skip a number of results.
282#[derive(Debug, Clone)]
283pub struct SkipOp {
284    /// Number of rows to skip.
285    pub count: usize,
286    /// Input operator.
287    pub input: Box<LogicalOperator>,
288}
289
290/// Sort results.
291#[derive(Debug, Clone)]
292pub struct SortOp {
293    /// Sort keys.
294    pub keys: Vec<SortKey>,
295    /// Input operator.
296    pub input: Box<LogicalOperator>,
297}
298
299/// A sort key.
300#[derive(Debug, Clone)]
301pub struct SortKey {
302    /// Expression to sort by.
303    pub expression: LogicalExpression,
304    /// Sort order.
305    pub order: SortOrder,
306}
307
308/// Sort order.
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub enum SortOrder {
311    /// Ascending order.
312    Ascending,
313    /// Descending order.
314    Descending,
315}
316
317/// Remove duplicate results.
318#[derive(Debug, Clone)]
319pub struct DistinctOp {
320    /// Input operator.
321    pub input: Box<LogicalOperator>,
322}
323
324/// Create a new node.
325#[derive(Debug, Clone)]
326pub struct CreateNodeOp {
327    /// Variable name to bind the created node to.
328    pub variable: String,
329    /// Labels for the new node.
330    pub labels: Vec<String>,
331    /// Properties for the new node.
332    pub properties: Vec<(String, LogicalExpression)>,
333    /// Input operator (for chained creates).
334    pub input: Option<Box<LogicalOperator>>,
335}
336
337/// Create a new edge.
338#[derive(Debug, Clone)]
339pub struct CreateEdgeOp {
340    /// Variable name to bind the created edge to.
341    pub variable: Option<String>,
342    /// Source node variable.
343    pub from_variable: String,
344    /// Target node variable.
345    pub to_variable: String,
346    /// Edge type.
347    pub edge_type: String,
348    /// Properties for the new edge.
349    pub properties: Vec<(String, LogicalExpression)>,
350    /// Input operator.
351    pub input: Box<LogicalOperator>,
352}
353
354/// Delete a node.
355#[derive(Debug, Clone)]
356pub struct DeleteNodeOp {
357    /// Variable of the node to delete.
358    pub variable: String,
359    /// Input operator.
360    pub input: Box<LogicalOperator>,
361}
362
363/// Delete an edge.
364#[derive(Debug, Clone)]
365pub struct DeleteEdgeOp {
366    /// Variable of the edge to delete.
367    pub variable: String,
368    /// Input operator.
369    pub input: Box<LogicalOperator>,
370}
371
372/// Set properties on a node or edge.
373#[derive(Debug, Clone)]
374pub struct SetPropertyOp {
375    /// Variable of the entity to update.
376    pub variable: String,
377    /// Properties to set (name -> expression).
378    pub properties: Vec<(String, LogicalExpression)>,
379    /// Whether to replace all properties (vs. merge).
380    pub replace: bool,
381    /// Input operator.
382    pub input: Box<LogicalOperator>,
383}
384
385/// Add labels to a node.
386#[derive(Debug, Clone)]
387pub struct AddLabelOp {
388    /// Variable of the node to update.
389    pub variable: String,
390    /// Labels to add.
391    pub labels: Vec<String>,
392    /// Input operator.
393    pub input: Box<LogicalOperator>,
394}
395
396/// Remove labels from a node.
397#[derive(Debug, Clone)]
398pub struct RemoveLabelOp {
399    /// Variable of the node to update.
400    pub variable: String,
401    /// Labels to remove.
402    pub labels: Vec<String>,
403    /// Input operator.
404    pub input: Box<LogicalOperator>,
405}
406
407// ==================== RDF/SPARQL Operators ====================
408
409/// Scan RDF triples matching a pattern.
410#[derive(Debug, Clone)]
411pub struct TripleScanOp {
412    /// Subject pattern (variable name or IRI).
413    pub subject: TripleComponent,
414    /// Predicate pattern (variable name or IRI).
415    pub predicate: TripleComponent,
416    /// Object pattern (variable name, IRI, or literal).
417    pub object: TripleComponent,
418    /// Named graph (optional).
419    pub graph: Option<TripleComponent>,
420    /// Input operator (for chained patterns).
421    pub input: Option<Box<LogicalOperator>>,
422}
423
424/// A component of a triple pattern.
425#[derive(Debug, Clone)]
426pub enum TripleComponent {
427    /// A variable to bind.
428    Variable(String),
429    /// A constant IRI.
430    Iri(String),
431    /// A constant literal value.
432    Literal(Value),
433}
434
435/// Union of multiple result sets.
436#[derive(Debug, Clone)]
437pub struct UnionOp {
438    /// Inputs to union together.
439    pub inputs: Vec<LogicalOperator>,
440}
441
442/// Left outer join for OPTIONAL patterns.
443#[derive(Debug, Clone)]
444pub struct LeftJoinOp {
445    /// Left (required) input.
446    pub left: Box<LogicalOperator>,
447    /// Right (optional) input.
448    pub right: Box<LogicalOperator>,
449    /// Optional filter condition.
450    pub condition: Option<LogicalExpression>,
451}
452
453/// Anti-join for MINUS patterns.
454#[derive(Debug, Clone)]
455pub struct AntiJoinOp {
456    /// Left input (results to keep if no match on right).
457    pub left: Box<LogicalOperator>,
458    /// Right input (patterns to exclude).
459    pub right: Box<LogicalOperator>,
460}
461
462/// Bind a variable to an expression.
463#[derive(Debug, Clone)]
464pub struct BindOp {
465    /// Expression to compute.
466    pub expression: LogicalExpression,
467    /// Variable to bind the result to.
468    pub variable: String,
469    /// Input operator.
470    pub input: Box<LogicalOperator>,
471}
472
473/// Unwind a list into individual rows.
474///
475/// For each input row, evaluates the expression (which should return a list)
476/// and emits one row for each element in the list.
477#[derive(Debug, Clone)]
478pub struct UnwindOp {
479    /// The list expression to unwind.
480    pub expression: LogicalExpression,
481    /// The variable name for each element.
482    pub variable: String,
483    /// Input operator.
484    pub input: Box<LogicalOperator>,
485}
486
487/// Merge a pattern (match or create).
488///
489/// MERGE tries to match a pattern in the graph. If found, returns the existing
490/// elements (optionally applying ON MATCH SET). If not found, creates the pattern
491/// (optionally applying ON CREATE SET).
492#[derive(Debug, Clone)]
493pub struct MergeOp {
494    /// The node to merge.
495    pub variable: String,
496    /// Labels to match/create.
497    pub labels: Vec<String>,
498    /// Properties that must match (used for both matching and creation).
499    pub match_properties: Vec<(String, LogicalExpression)>,
500    /// Properties to set on CREATE.
501    pub on_create: Vec<(String, LogicalExpression)>,
502    /// Properties to set on MATCH.
503    pub on_match: Vec<(String, LogicalExpression)>,
504    /// Input operator.
505    pub input: Box<LogicalOperator>,
506}
507
508/// Return results (terminal operator).
509#[derive(Debug, Clone)]
510pub struct ReturnOp {
511    /// Items to return.
512    pub items: Vec<ReturnItem>,
513    /// Whether to return distinct results.
514    pub distinct: bool,
515    /// Input operator.
516    pub input: Box<LogicalOperator>,
517}
518
519/// A single return item.
520#[derive(Debug, Clone)]
521pub struct ReturnItem {
522    /// Expression to return.
523    pub expression: LogicalExpression,
524    /// Alias for the result column.
525    pub alias: Option<String>,
526}
527
528/// A logical expression.
529#[derive(Debug, Clone)]
530pub enum LogicalExpression {
531    /// A literal value.
532    Literal(Value),
533
534    /// A variable reference.
535    Variable(String),
536
537    /// Property access (e.g., n.name).
538    Property {
539        /// The variable to access.
540        variable: String,
541        /// The property name.
542        property: String,
543    },
544
545    /// Binary operation.
546    Binary {
547        /// Left operand.
548        left: Box<LogicalExpression>,
549        /// Operator.
550        op: BinaryOp,
551        /// Right operand.
552        right: Box<LogicalExpression>,
553    },
554
555    /// Unary operation.
556    Unary {
557        /// Operator.
558        op: UnaryOp,
559        /// Operand.
560        operand: Box<LogicalExpression>,
561    },
562
563    /// Function call.
564    FunctionCall {
565        /// Function name.
566        name: String,
567        /// Arguments.
568        args: Vec<LogicalExpression>,
569    },
570
571    /// List literal.
572    List(Vec<LogicalExpression>),
573
574    /// Map literal (e.g., {name: 'Alice', age: 30}).
575    Map(Vec<(String, LogicalExpression)>),
576
577    /// Index access (e.g., `list[0]`).
578    IndexAccess {
579        /// The base expression (typically a list or string).
580        base: Box<LogicalExpression>,
581        /// The index expression.
582        index: Box<LogicalExpression>,
583    },
584
585    /// Slice access (e.g., list[1..3]).
586    SliceAccess {
587        /// The base expression (typically a list or string).
588        base: Box<LogicalExpression>,
589        /// Start index (None means from beginning).
590        start: Option<Box<LogicalExpression>>,
591        /// End index (None means to end).
592        end: Option<Box<LogicalExpression>>,
593    },
594
595    /// CASE expression.
596    Case {
597        /// Test expression (for simple CASE).
598        operand: Option<Box<LogicalExpression>>,
599        /// WHEN clauses.
600        when_clauses: Vec<(LogicalExpression, LogicalExpression)>,
601        /// ELSE clause.
602        else_clause: Option<Box<LogicalExpression>>,
603    },
604
605    /// Parameter reference.
606    Parameter(String),
607
608    /// Labels of a node.
609    Labels(String),
610
611    /// Type of an edge.
612    Type(String),
613
614    /// ID of a node or edge.
615    Id(String),
616
617    /// List comprehension: [x IN list WHERE predicate | expression]
618    ListComprehension {
619        /// Variable name for each element.
620        variable: String,
621        /// The source list expression.
622        list_expr: Box<LogicalExpression>,
623        /// Optional filter predicate.
624        filter_expr: Option<Box<LogicalExpression>>,
625        /// The mapping expression for each element.
626        map_expr: Box<LogicalExpression>,
627    },
628
629    /// EXISTS subquery.
630    ExistsSubquery(Box<LogicalOperator>),
631
632    /// COUNT subquery.
633    CountSubquery(Box<LogicalOperator>),
634}
635
636/// Binary operator.
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
638pub enum BinaryOp {
639    /// Equality comparison (=).
640    Eq,
641    /// Inequality comparison (<>).
642    Ne,
643    /// Less than (<).
644    Lt,
645    /// Less than or equal (<=).
646    Le,
647    /// Greater than (>).
648    Gt,
649    /// Greater than or equal (>=).
650    Ge,
651
652    /// Logical AND.
653    And,
654    /// Logical OR.
655    Or,
656    /// Logical XOR.
657    Xor,
658
659    /// Addition (+).
660    Add,
661    /// Subtraction (-).
662    Sub,
663    /// Multiplication (*).
664    Mul,
665    /// Division (/).
666    Div,
667    /// Modulo (%).
668    Mod,
669
670    /// String concatenation.
671    Concat,
672    /// String starts with.
673    StartsWith,
674    /// String ends with.
675    EndsWith,
676    /// String contains.
677    Contains,
678
679    /// Collection membership (IN).
680    In,
681    /// Pattern matching (LIKE).
682    Like,
683    /// Regex matching (=~).
684    Regex,
685    /// Power/exponentiation (^).
686    Pow,
687}
688
689/// Unary operator.
690#[derive(Debug, Clone, Copy, PartialEq, Eq)]
691pub enum UnaryOp {
692    /// Logical NOT.
693    Not,
694    /// Numeric negation.
695    Neg,
696    /// IS NULL check.
697    IsNull,
698    /// IS NOT NULL check.
699    IsNotNull,
700}
701
702#[cfg(test)]
703mod tests {
704    use super::*;
705
706    #[test]
707    fn test_simple_node_scan_plan() {
708        let plan = LogicalPlan::new(LogicalOperator::Return(ReturnOp {
709            items: vec![ReturnItem {
710                expression: LogicalExpression::Variable("n".into()),
711                alias: None,
712            }],
713            distinct: false,
714            input: Box::new(LogicalOperator::NodeScan(NodeScanOp {
715                variable: "n".into(),
716                label: Some("Person".into()),
717                input: None,
718            })),
719        }));
720
721        // Verify structure
722        if let LogicalOperator::Return(ret) = &plan.root {
723            assert_eq!(ret.items.len(), 1);
724            assert!(!ret.distinct);
725            if let LogicalOperator::NodeScan(scan) = ret.input.as_ref() {
726                assert_eq!(scan.variable, "n");
727                assert_eq!(scan.label, Some("Person".into()));
728            } else {
729                panic!("Expected NodeScan");
730            }
731        } else {
732            panic!("Expected Return");
733        }
734    }
735
736    #[test]
737    fn test_filter_plan() {
738        let plan = LogicalPlan::new(LogicalOperator::Return(ReturnOp {
739            items: vec![ReturnItem {
740                expression: LogicalExpression::Property {
741                    variable: "n".into(),
742                    property: "name".into(),
743                },
744                alias: Some("name".into()),
745            }],
746            distinct: false,
747            input: Box::new(LogicalOperator::Filter(FilterOp {
748                predicate: LogicalExpression::Binary {
749                    left: Box::new(LogicalExpression::Property {
750                        variable: "n".into(),
751                        property: "age".into(),
752                    }),
753                    op: BinaryOp::Gt,
754                    right: Box::new(LogicalExpression::Literal(Value::Int64(30))),
755                },
756                input: Box::new(LogicalOperator::NodeScan(NodeScanOp {
757                    variable: "n".into(),
758                    label: Some("Person".into()),
759                    input: None,
760                })),
761            })),
762        }));
763
764        if let LogicalOperator::Return(ret) = &plan.root {
765            if let LogicalOperator::Filter(filter) = ret.input.as_ref() {
766                if let LogicalExpression::Binary { op, .. } = &filter.predicate {
767                    assert_eq!(*op, BinaryOp::Gt);
768                } else {
769                    panic!("Expected Binary expression");
770                }
771            } else {
772                panic!("Expected Filter");
773            }
774        } else {
775            panic!("Expected Return");
776        }
777    }
778}