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