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