Skip to main content

lora_compiler/
logical.rs

1use lora_analyzer::symbols::VarId;
2use lora_analyzer::{
3    ResolvedClause, ResolvedExpr, ResolvedMergeAction, ResolvedPattern, ResolvedPatternPart,
4    ResolvedProjection, ResolvedRemoveItem, ResolvedSetItem, ResolvedSortItem,
5};
6use lora_ast::{Direction, RangeLiteral};
7
8pub type PlanNodeId = usize;
9
10#[derive(Debug, Clone)]
11pub struct LogicalPlan {
12    pub root: PlanNodeId,
13    pub nodes: Vec<LogicalOp>,
14}
15
16#[derive(Debug, Clone)]
17pub enum LogicalOp {
18    Argument(Argument),
19    NodeScan(NodeScan),
20    NodeByPropertyScan(NodeByPropertyScan),
21    NodeByPropertyRangeScan(NodeByPropertyRangeScan),
22    NodeByTextScan(NodeByTextScan),
23    NodeByPointScan(NodeByPointScan),
24    RelByPropertyRangeScan(RelByPropertyRangeScan),
25    RelByTextScan(RelByTextScan),
26    RelByPointScan(RelByPointScan),
27    Expand(Expand),
28    Filter(Filter),
29    Projection(Projection),
30    Unwind(Unwind),
31    Aggregation(Aggregation),
32    Sort(Sort),
33    Limit(Limit),
34    Merge(Merge),
35    Delete(Delete),
36    Set(Set),
37    Remove(Remove),
38    Create(Create),
39    Foreach(Foreach),
40    OptionalMatch(OptionalMatch),
41    PathBuild(PathBuild),
42    CallSubquery(CallSubquery),
43}
44
45/// `FOREACH (var IN list | body...)` — for each input row, evaluate
46/// the list, then run each body clause once per element with `var`
47/// bound to the element. The body is a flat list of resolved updating
48/// clauses applied for side effects only; the outer row is emitted
49/// unchanged after the loop.
50#[derive(Debug, Clone)]
51pub struct Foreach {
52    pub input: PlanNodeId,
53    pub variable: VarId,
54    pub list: ResolvedExpr,
55    pub body: Vec<ResolvedClause>,
56}
57
58/// `CALL { ... }` subquery: for each upstream row, runs the inner
59/// sub-plan with the upstream row as its initial argument, then
60/// emits the cartesian product of `(upstream row, inner row)` for
61/// each inner row produced. `new_vars` are the VarIds the inner
62/// RETURN exposes to the outer scope.
63#[derive(Debug, Clone)]
64pub struct CallSubquery {
65    pub input: PlanNodeId,
66    pub inner: PlanNodeId,
67    pub new_vars: Vec<VarId>,
68}
69
70/// Assembles a path value from matched node and relationship VarIds.
71#[derive(Debug, Clone)]
72pub struct PathBuild {
73    pub input: PlanNodeId,
74    /// VarId to store the assembled path.
75    pub output: VarId,
76    /// Node VarIds in order: head, chain[0].node, chain[1].node, ...
77    pub node_vars: Vec<VarId>,
78    /// Relationship VarIds in order: chain[0].rel, chain[1].rel, ...
79    pub rel_vars: Vec<VarId>,
80    /// `None` = normal path, `Some(false)` = shortestPath, `Some(true)` = allShortestPaths
81    pub shortest_path_all: Option<bool>,
82}
83
84/// Left-outer-join style node: runs the inner sub-plan for each input row.
85/// If no rows are produced, emits one row with nulls for the new variables.
86#[derive(Debug, Clone)]
87pub struct OptionalMatch {
88    /// Upstream rows that feed the optional match.
89    pub input: PlanNodeId,
90    /// The root of the inner sub-plan that implements the pattern + filter.
91    pub inner: PlanNodeId,
92    /// Variables introduced by the optional match (need null-extension).
93    pub new_vars: Vec<VarId>,
94}
95
96#[derive(Debug, Clone)]
97pub struct Argument;
98
99#[derive(Debug, Clone)]
100pub struct NodeScan {
101    pub input: Option<PlanNodeId>,
102    pub var: VarId,
103    /// Each inner Vec is a disjunctive group (OR). Outer Vec is conjunctive (AND).
104    pub labels: Vec<Vec<String>>,
105}
106
107#[derive(Debug, Clone)]
108pub struct NodeByPropertyScan {
109    pub input: Option<PlanNodeId>,
110    pub var: VarId,
111    /// Each inner Vec is a disjunctive group (OR). Outer Vec is conjunctive (AND).
112    pub labels: Vec<Vec<String>>,
113    pub key: String,
114    pub value: ResolvedExpr,
115    /// `false`: seek `key = value`. `true`: `value` evaluates to a list and
116    /// the scan seeks `key IN value`, one lookup per distinct element.
117    pub in_list: bool,
118}
119
120/// Range-bounded property scan rewritten from `Filter(NodeScan, var.prop CMP value)`
121/// patterns. `lo == None` means `-∞`, `hi == None` means `+∞`. Inclusivity flags
122/// distinguish `>` from `>=` and `<` from `<=`. Both bounds combined cover
123/// `BETWEEN`-style queries (`a < x AND x <= b`).
124#[derive(Debug, Clone)]
125pub struct NodeByPropertyRangeScan {
126    pub input: Option<PlanNodeId>,
127    pub var: VarId,
128    pub labels: Vec<Vec<String>>,
129    pub key: String,
130    pub lo: Option<ResolvedExpr>,
131    pub lo_inclusive: bool,
132    pub hi: Option<ResolvedExpr>,
133    pub hi_inclusive: bool,
134    /// Set when the optimizer replaced an `ORDER BY var.key` above this
135    /// scan: rows must come out in that order (from the index when it can
136    /// supply it, otherwise sorted by the operator itself).
137    pub order: Option<lora_ast::SortDirection>,
138}
139
140/// Trigram-backed property scan rewritten from `Filter(NodeScan, var.prop OP "literal")`
141/// where OP is `STARTS WITH`, `ENDS WITH`, or `CONTAINS`. The executor consults
142/// the trigram registry for candidates and re-verifies the predicate.
143#[derive(Debug, Clone)]
144pub struct NodeByTextScan {
145    pub input: Option<PlanNodeId>,
146    pub var: VarId,
147    pub labels: Vec<Vec<String>>,
148    pub key: String,
149    pub predicate: TextPredicate,
150    pub query: ResolvedExpr,
151}
152
153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum TextPredicate {
155    StartsWith,
156    EndsWith,
157    Contains,
158}
159
160/// Spatial-index scan rewritten from `Filter(NodeScan, predicate)`
161/// where the predicate is `point.withinBBox(n.prop, ll, ur)` or
162/// `point.distance(n.prop, c) OP d`. Index probe is conservative;
163/// the executor refilters with the precise predicate (including the
164/// inclusivity of distance comparisons and the z-axis when the point
165/// is 3D).
166#[derive(Debug, Clone)]
167pub struct NodeByPointScan {
168    pub input: Option<PlanNodeId>,
169    pub var: VarId,
170    pub labels: Vec<Vec<String>>,
171    pub key: String,
172    pub predicate: PointPredicate,
173}
174
175#[derive(Debug, Clone)]
176pub enum PointPredicate {
177    WithinBBox {
178        lower_left: ResolvedExpr,
179        upper_right: ResolvedExpr,
180    },
181    WithinDistance {
182        center: ResolvedExpr,
183        max_distance: ResolvedExpr,
184        inclusive: bool,
185    },
186}
187
188/// Range-bounded relationship scan, the rel-side mirror of
189/// [`NodeByPropertyRangeScan`]. Produces one row per indexed
190/// relationship of `types`, binding `src`, `rel`, `dst` to the stored
191/// endpoints. The optimizer only emits this operator for patterns
192/// with anonymous endpoints (no upstream label/property constraints
193/// on src/dst), since the operator does not refilter endpoints.
194#[derive(Debug, Clone)]
195pub struct RelByPropertyRangeScan {
196    pub input: Option<PlanNodeId>,
197    pub src: VarId,
198    pub rel: VarId,
199    pub dst: VarId,
200    pub types: Vec<String>,
201    pub direction: Direction,
202    pub key: String,
203    pub lo: Option<ResolvedExpr>,
204    pub lo_inclusive: bool,
205    pub hi: Option<ResolvedExpr>,
206    pub hi_inclusive: bool,
207}
208
209/// Trigram-backed relationship scan. Mirror of [`NodeByTextScan`].
210#[derive(Debug, Clone)]
211pub struct RelByTextScan {
212    pub input: Option<PlanNodeId>,
213    pub src: VarId,
214    pub rel: VarId,
215    pub dst: VarId,
216    pub types: Vec<String>,
217    pub direction: Direction,
218    pub key: String,
219    pub predicate: TextPredicate,
220    pub query: ResolvedExpr,
221}
222
223/// Spatial-index relationship scan. Mirror of [`NodeByPointScan`].
224#[derive(Debug, Clone)]
225pub struct RelByPointScan {
226    pub input: Option<PlanNodeId>,
227    pub src: VarId,
228    pub rel: VarId,
229    pub dst: VarId,
230    pub types: Vec<String>,
231    pub direction: Direction,
232    pub key: String,
233    pub predicate: PointPredicate,
234}
235
236#[derive(Debug, Clone)]
237pub struct Expand {
238    pub input: PlanNodeId,
239    pub src: VarId,
240    pub rel: Option<VarId>,
241    pub dst: VarId,
242    pub types: Vec<String>,
243    pub direction: Direction,
244    pub rel_properties: Option<ResolvedExpr>,
245    pub range: Option<RangeLiteral>,
246}
247
248#[derive(Debug, Clone)]
249pub struct Filter {
250    pub input: PlanNodeId,
251    pub predicate: ResolvedExpr,
252}
253
254#[derive(Debug, Clone)]
255pub struct Projection {
256    pub input: PlanNodeId,
257    pub distinct: bool,
258    pub items: Vec<ResolvedProjection>,
259    pub include_existing: bool,
260}
261
262#[derive(Debug, Clone)]
263pub struct Unwind {
264    pub input: PlanNodeId,
265    pub expr: ResolvedExpr,
266    pub alias: VarId,
267}
268
269#[derive(Debug, Clone)]
270pub struct Aggregation {
271    pub input: PlanNodeId,
272    pub group_by: Vec<ResolvedProjection>,
273    pub aggregates: Vec<ResolvedProjection>,
274}
275
276#[derive(Debug, Clone)]
277pub struct Sort {
278    pub input: PlanNodeId,
279    pub items: Vec<ResolvedSortItem>,
280    /// Optional upper bound for rows the sort must retain because a parent
281    /// LIMIT will discard everything after this many sorted rows.
282    pub top_k: Option<usize>,
283}
284
285#[derive(Debug, Clone)]
286pub struct Limit {
287    pub input: PlanNodeId,
288    pub skip: Option<ResolvedExpr>,
289    pub limit: Option<ResolvedExpr>,
290}
291
292#[derive(Debug, Clone)]
293pub struct Create {
294    pub input: PlanNodeId,
295    pub pattern: ResolvedPattern,
296}
297
298#[derive(Debug, Clone)]
299pub struct Merge {
300    pub input: PlanNodeId,
301    pub pattern_part: ResolvedPatternPart,
302    pub actions: Vec<ResolvedMergeAction>,
303}
304
305#[derive(Debug, Clone)]
306pub struct Delete {
307    pub input: PlanNodeId,
308    pub detach: bool,
309    pub expressions: Vec<ResolvedExpr>,
310}
311
312#[derive(Debug, Clone)]
313pub struct Set {
314    pub input: PlanNodeId,
315    pub items: Vec<ResolvedSetItem>,
316}
317
318#[derive(Debug, Clone)]
319pub struct Remove {
320    pub input: PlanNodeId,
321    pub items: Vec<ResolvedRemoveItem>,
322}