Skip to main content

lora_compiler/
logical.rs

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