Skip to main content

lora_analyzer/
resolved.rs

1use std::collections::BTreeSet;
2
3use crate::{analyzer::FunctionId, symbols::*};
4use lora_ast::{
5    BinaryOp, Direction, ListPredicateKind, RangeLiteral, SortDirection, Span, UnaryOp,
6};
7
8#[derive(Debug, Clone)]
9pub struct ResolvedQuery {
10    pub clauses: Vec<ResolvedClause>,
11    /// Additional UNION branches. Each branch is a separate resolved query
12    /// that produces rows to be combined with the head query's results.
13    pub unions: Vec<ResolvedUnionPart>,
14}
15
16#[derive(Debug, Clone)]
17pub struct ResolvedUnionPart {
18    /// If true, this is UNION ALL (no deduplication). If false, plain UNION (deduplicate).
19    pub all: bool,
20    /// The resolved clauses for this branch.
21    pub clauses: Vec<ResolvedClause>,
22}
23
24#[derive(Debug, Clone)]
25pub enum ResolvedClause {
26    Match(ResolvedMatch),
27    Unwind(ResolvedUnwind),
28    Create(ResolvedCreate),
29    Merge(ResolvedMerge),
30    Delete(ResolvedDelete),
31    Set(ResolvedSet),
32    Remove(ResolvedRemove),
33    Foreach(ResolvedForeach),
34    Return(ResolvedReturn),
35    With(ResolvedWith),
36    CallSubquery(ResolvedCallSubquery),
37}
38
39/// `CALL { ... }` subquery body. The inner clause list reads from
40/// the outer scope (variables visible at the call site are still
41/// visible inside the subquery), runs once per outer row, and
42/// projects the variables named in its final RETURN back into the
43/// outer scope.
44#[derive(Debug, Clone)]
45pub struct ResolvedCallSubquery {
46    pub clauses: Vec<ResolvedClause>,
47    /// VarIds produced by the inner final RETURN that become
48    /// available in the outer scope after the CALL.
49    pub return_vars: Vec<VarId>,
50}
51
52#[derive(Debug, Clone)]
53pub struct ResolvedMatch {
54    pub optional: bool,
55    pub pattern: ResolvedPattern,
56    pub where_: Option<ResolvedExpr>,
57}
58
59#[derive(Debug, Clone)]
60pub struct ResolvedUnwind {
61    pub expr: ResolvedExpr,
62    pub alias: VarId,
63}
64
65#[derive(Debug, Clone)]
66pub struct ResolvedCreate {
67    pub pattern: ResolvedPattern,
68}
69
70#[derive(Debug, Clone)]
71pub struct ResolvedMerge {
72    pub pattern_part: ResolvedPatternPart,
73    pub actions: Vec<ResolvedMergeAction>,
74}
75
76#[derive(Debug, Clone)]
77pub struct ResolvedMergeAction {
78    pub on_match: bool,
79    pub set: ResolvedSet,
80}
81
82#[derive(Debug, Clone)]
83pub struct ResolvedDelete {
84    pub detach: bool,
85    pub expressions: Vec<ResolvedExpr>,
86}
87
88#[derive(Debug, Clone)]
89pub struct ResolvedSet {
90    pub items: Vec<ResolvedSetItem>,
91}
92
93#[derive(Debug, Clone)]
94pub enum ResolvedSetItem {
95    SetProperty {
96        target: ResolvedExpr,
97        value: ResolvedExpr,
98    },
99    SetVariable {
100        variable: VarId,
101        value: ResolvedExpr,
102    },
103    MutateVariable {
104        variable: VarId,
105        value: ResolvedExpr,
106    },
107    SetLabels {
108        variable: VarId,
109        labels: Vec<String>,
110    },
111}
112
113#[derive(Debug, Clone)]
114pub struct ResolvedRemove {
115    pub items: Vec<ResolvedRemoveItem>,
116}
117
118/// `FOREACH (var IN list | body...)`. The body is restricted to
119/// updating clauses (Create / Merge / Delete / Set / Remove / nested
120/// Foreach). The analyzer enforces that restriction; the planner /
121/// executor treat each body item as a side-effect-only operation
122/// applied row-by-row inside the iteration.
123#[derive(Debug, Clone)]
124pub struct ResolvedForeach {
125    pub variable: VarId,
126    pub list: ResolvedExpr,
127    pub body: Vec<ResolvedClause>,
128}
129
130#[derive(Debug, Clone)]
131pub enum ResolvedRemoveItem {
132    Labels {
133        variable: VarId,
134        labels: Vec<String>,
135    },
136    Property {
137        expr: ResolvedExpr,
138    },
139}
140
141#[derive(Debug, Clone)]
142pub struct ResolvedReturn {
143    pub distinct: bool,
144    pub items: Vec<ResolvedProjection>,
145    pub include_existing: bool,
146    pub order: Vec<ResolvedSortItem>,
147    pub skip: Option<ResolvedExpr>,
148    pub limit: Option<ResolvedExpr>,
149}
150
151#[derive(Debug, Clone)]
152pub struct ResolvedWith {
153    pub distinct: bool,
154    pub items: Vec<ResolvedProjection>,
155    pub include_existing: bool,
156    pub order: Vec<ResolvedSortItem>,
157    pub skip: Option<ResolvedExpr>,
158    pub limit: Option<ResolvedExpr>,
159    pub where_: Option<ResolvedExpr>,
160}
161
162#[derive(Debug, Clone)]
163pub struct ResolvedProjection {
164    pub expr: ResolvedExpr,
165    pub output: VarId,
166    /// Output column name. `Arc<str>` because executors stamp it onto
167    /// every produced row; cloning it per row is a refcount bump rather
168    /// than a heap allocation per cell.
169    pub name: std::sync::Arc<str>,
170    /// True when the name came from an explicit `AS` alias.
171    pub explicit_alias: bool,
172    pub span: Span,
173}
174
175#[derive(Debug, Clone)]
176pub struct ResolvedSortItem {
177    pub expr: ResolvedExpr,
178    pub direction: SortDirection,
179}
180
181#[derive(Debug, Clone)]
182pub struct ResolvedPattern {
183    pub parts: Vec<ResolvedPatternPart>,
184}
185
186#[derive(Debug, Clone)]
187pub struct ResolvedPatternPart {
188    pub binding: Option<VarId>,
189    pub element: ResolvedPatternElement,
190}
191
192#[derive(Debug, Clone)]
193pub enum ResolvedPatternElement {
194    Node {
195        var: Option<VarId>,
196        /// Each inner Vec is a disjunctive group (OR). Outer Vec is conjunctive (AND).
197        labels: Vec<Vec<String>>,
198        properties: Option<ResolvedExpr>,
199    },
200    NodeChain {
201        head: ResolvedNode,
202        chain: Vec<ResolvedChain>,
203    },
204    ShortestPath {
205        all: bool,
206        head: ResolvedNode,
207        chain: Vec<ResolvedChain>,
208    },
209}
210
211#[derive(Debug, Clone)]
212pub struct ResolvedNode {
213    pub var: Option<VarId>,
214    /// Each inner Vec is a disjunctive group (OR). Outer Vec is conjunctive (AND).
215    pub labels: Vec<Vec<String>>,
216    pub properties: Option<ResolvedExpr>,
217}
218
219#[derive(Debug, Clone)]
220pub struct ResolvedChain {
221    pub rel: ResolvedRel,
222    pub node: ResolvedNode,
223}
224
225#[derive(Debug, Clone)]
226pub struct ResolvedRel {
227    pub var: Option<VarId>,
228    pub types: Vec<String>,
229    pub direction: Direction,
230    pub range: Option<RangeLiteral>,
231    pub properties: Option<ResolvedExpr>,
232}
233
234#[derive(Debug, Clone)]
235pub enum ResolvedExpr {
236    Variable(VarId),
237    Literal(LiteralValue),
238    Property {
239        expr: Box<ResolvedExpr>,
240        property: String,
241    },
242    Binary {
243        lhs: Box<ResolvedExpr>,
244        op: BinaryOp,
245        rhs: Box<ResolvedExpr>,
246    },
247    Unary {
248        op: UnaryOp,
249        expr: Box<ResolvedExpr>,
250    },
251    Function {
252        function: FunctionId,
253        distinct: bool,
254        args: Vec<ResolvedExpr>,
255    },
256    List(Vec<ResolvedExpr>),
257    Map(Vec<(String, ResolvedExpr)>),
258    Case {
259        input: Option<Box<ResolvedExpr>>,
260        alternatives: Vec<(ResolvedExpr, ResolvedExpr)>,
261        else_expr: Option<Box<ResolvedExpr>>,
262    },
263    Parameter(String),
264    ListPredicate {
265        kind: ListPredicateKind,
266        variable: VarId,
267        list: Box<ResolvedExpr>,
268        predicate: Box<ResolvedExpr>,
269    },
270    ListComprehension {
271        variable: VarId,
272        list: Box<ResolvedExpr>,
273        filter: Option<Box<ResolvedExpr>>,
274        map_expr: Option<Box<ResolvedExpr>>,
275    },
276    Reduce {
277        accumulator: VarId,
278        init: Box<ResolvedExpr>,
279        variable: VarId,
280        list: Box<ResolvedExpr>,
281        expr: Box<ResolvedExpr>,
282    },
283    MapProjection {
284        base: Box<ResolvedExpr>,
285        selectors: Vec<ResolvedMapSelector>,
286    },
287    Index {
288        expr: Box<ResolvedExpr>,
289        index: Box<ResolvedExpr>,
290    },
291    Slice {
292        expr: Box<ResolvedExpr>,
293        from: Option<Box<ResolvedExpr>>,
294        to: Option<Box<ResolvedExpr>>,
295    },
296    ExistsSubquery {
297        pattern: ResolvedPattern,
298        where_: Option<Box<ResolvedExpr>>,
299        /// Every variable the pattern and WHERE use (see
300        /// [`ResolvedExpr::collect_vars`]), sorted: the outer bindings the
301        /// subquery can read, computed once so evaluation need not walk it.
302        reads: Vec<VarId>,
303    },
304    PatternComprehension {
305        pattern: ResolvedPattern,
306        where_: Option<Box<ResolvedExpr>>,
307        map_expr: Box<ResolvedExpr>,
308        /// Every variable the pattern, WHERE and projection use, as for
309        /// [`ResolvedExpr::ExistsSubquery`].
310        reads: Vec<VarId>,
311    },
312}
313
314#[derive(Debug, Clone)]
315pub enum ResolvedMapSelector {
316    Property(String),
317    AllProperties,
318    Literal(String, ResolvedExpr),
319}
320
321#[derive(Debug, Clone, PartialEq)]
322pub enum LiteralValue {
323    Integer(i64),
324    Float(f64),
325    String(String),
326    TypeName(String),
327    Bool(bool),
328    Null,
329}
330
331impl ResolvedExpr {
332    /// Every variable the expression reads. Patterns inside it (`EXISTS`,
333    /// pattern comprehensions) contribute the outer variables they name and
334    /// their own fresh ones: an over-approximation, which is safe for every
335    /// caller (it only ever keeps a predicate in place, or a binding in a
336    /// row).
337    pub fn collect_vars(&self, out: &mut BTreeSet<VarId>) {
338        let expr = self;
339        match expr {
340            ResolvedExpr::Variable(v) => {
341                out.insert(*v);
342            }
343            ResolvedExpr::Property { expr, .. } => ResolvedExpr::collect_vars(expr, out),
344            ResolvedExpr::Binary { lhs, rhs, .. } => {
345                ResolvedExpr::collect_vars(lhs, out);
346                ResolvedExpr::collect_vars(rhs, out);
347            }
348            ResolvedExpr::Unary { expr, .. } => ResolvedExpr::collect_vars(expr, out),
349            ResolvedExpr::Function { args, .. } => {
350                for arg in args {
351                    ResolvedExpr::collect_vars(arg, out);
352                }
353            }
354            ResolvedExpr::List(items) => {
355                for item in items {
356                    ResolvedExpr::collect_vars(item, out);
357                }
358            }
359            ResolvedExpr::Map(items) => {
360                for (_, v) in items {
361                    ResolvedExpr::collect_vars(v, out);
362                }
363            }
364            ResolvedExpr::Case {
365                input,
366                alternatives,
367                else_expr,
368            } => {
369                if let Some(e) = input {
370                    ResolvedExpr::collect_vars(e, out);
371                }
372                for (w, t) in alternatives {
373                    ResolvedExpr::collect_vars(w, out);
374                    ResolvedExpr::collect_vars(t, out);
375                }
376                if let Some(e) = else_expr {
377                    ResolvedExpr::collect_vars(e, out);
378                }
379            }
380            ResolvedExpr::ListPredicate {
381                variable,
382                list,
383                predicate,
384                ..
385            } => {
386                out.insert(*variable);
387                ResolvedExpr::collect_vars(list, out);
388                ResolvedExpr::collect_vars(predicate, out);
389            }
390            ResolvedExpr::ListComprehension {
391                variable,
392                list,
393                filter,
394                map_expr,
395                ..
396            } => {
397                out.insert(*variable);
398                ResolvedExpr::collect_vars(list, out);
399                if let Some(f) = filter {
400                    ResolvedExpr::collect_vars(f, out);
401                }
402                if let Some(m) = map_expr {
403                    ResolvedExpr::collect_vars(m, out);
404                }
405            }
406            ResolvedExpr::Reduce {
407                accumulator,
408                init,
409                variable,
410                list,
411                expr,
412                ..
413            } => {
414                out.insert(*accumulator);
415                out.insert(*variable);
416                ResolvedExpr::collect_vars(init, out);
417                ResolvedExpr::collect_vars(list, out);
418                ResolvedExpr::collect_vars(expr, out);
419            }
420            ResolvedExpr::Index { expr, index } => {
421                ResolvedExpr::collect_vars(expr, out);
422                ResolvedExpr::collect_vars(index, out);
423            }
424            ResolvedExpr::Slice { expr, from, to } => {
425                ResolvedExpr::collect_vars(expr, out);
426                if let Some(f) = from {
427                    ResolvedExpr::collect_vars(f, out);
428                }
429                if let Some(t) = to {
430                    ResolvedExpr::collect_vars(t, out);
431                }
432            }
433            ResolvedExpr::MapProjection { base, selectors } => {
434                ResolvedExpr::collect_vars(base, out);
435                for sel in selectors {
436                    if let ResolvedMapSelector::Literal(_, e) = sel {
437                        ResolvedExpr::collect_vars(e, out);
438                    }
439                }
440            }
441            // A pattern reads the outer variables it names (`(a)<-[:T]-(x)`
442            // reads `a`). Its own fresh variables are collected too: an
443            // over-approximation, which only ever keeps a predicate in place.
444            ResolvedExpr::ExistsSubquery {
445                pattern, where_, ..
446            } => {
447                pattern.collect_vars(out);
448                if let Some(w) = where_ {
449                    ResolvedExpr::collect_vars(w, out);
450                }
451            }
452            ResolvedExpr::PatternComprehension {
453                pattern,
454                where_,
455                map_expr,
456                ..
457            } => {
458                pattern.collect_vars(out);
459                if let Some(w) = where_ {
460                    ResolvedExpr::collect_vars(w, out);
461                }
462                ResolvedExpr::collect_vars(map_expr, out);
463            }
464            ResolvedExpr::Literal(_) | ResolvedExpr::Parameter(_) => {}
465        }
466    }
467}
468
469impl ResolvedPattern {
470    /// Every variable the pattern binds or names, and those its property
471    /// maps read.
472    pub fn collect_vars(&self, out: &mut BTreeSet<VarId>) {
473        let pattern = self;
474        let node = |n: &ResolvedNode, out: &mut BTreeSet<VarId>| {
475            out.extend(n.var);
476            if let Some(p) = &n.properties {
477                ResolvedExpr::collect_vars(p, out);
478            }
479        };
480        for part in &pattern.parts {
481            out.extend(part.binding);
482            match &part.element {
483                ResolvedPatternElement::Node {
484                    var, properties, ..
485                } => {
486                    out.extend(*var);
487                    if let Some(p) = properties {
488                        ResolvedExpr::collect_vars(p, out);
489                    }
490                }
491                ResolvedPatternElement::NodeChain { head, chain }
492                | ResolvedPatternElement::ShortestPath { head, chain, .. } => {
493                    node(head, out);
494                    for link in chain {
495                        out.extend(link.rel.var);
496                        if let Some(p) = &link.rel.properties {
497                            ResolvedExpr::collect_vars(p, out);
498                        }
499                        node(&link.node, out);
500                    }
501                }
502            }
503        }
504    }
505}