1use crate::{errors::*, resolved::*, scope::*, symbols::*};
2use lora_ast::{
3 Create, Delete, Document, Expr, InQueryCall, MapProjectionSelector, Match, Merge, NodePattern,
4 Pattern, PatternElement, PatternPart, ProjectionBody, ProjectionItem, Query, QueryPart,
5 ReadingClause, RelationshipPattern, Remove, RemoveItem, Return, Set, SetItem, SinglePartQuery,
6 SingleQuery, Statement, Unwind, UpdatingClause, With,
7};
8use lora_store::GraphStorage;
9use std::collections::{BTreeMap, BTreeSet};
10
11pub struct Analyzer<'a, S: GraphStorage + ?Sized> {
12 storage: &'a S,
13 scopes: ScopeStack,
14 symbols: SymbolTable,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18enum PatternContext {
19 Read,
20 OptionalRead,
22 Write,
23}
24
25impl<'a, S: GraphStorage + ?Sized> Analyzer<'a, S> {
26 pub fn new(storage: &'a S) -> Self {
27 Self {
28 storage,
29 scopes: ScopeStack::new(),
30 symbols: SymbolTable::default(),
31 }
32 }
33
34 pub fn analyze(&mut self, doc: &Document) -> Result<ResolvedQuery, SemanticError> {
35 match &doc.statement {
36 Statement::Query(q) => self.analyze_query(q),
37 }
38 }
39
40 fn analyze_query(&mut self, query: &Query) -> Result<ResolvedQuery, SemanticError> {
41 let mut clauses = Vec::new();
42 let mut unions = Vec::new();
43
44 match query {
45 Query::Regular(r) => {
46 clauses.extend(self.analyze_single_query(&r.head)?);
47
48 for union_part in &r.unions {
49 self.scopes.clear();
52
53 let branch_clauses = self.analyze_single_query(&union_part.query)?;
54 unions.push(ResolvedUnionPart {
55 all: union_part.all,
56 clauses: branch_clauses,
57 });
58 }
59
60 if !unions.is_empty() {
64 let head_cols = return_column_info(&clauses);
65 for branch in &unions {
66 let branch_cols = return_column_info(&branch.clauses);
67 if let (Some(hc), Some(bc)) = (&head_cols, &branch_cols) {
68 if hc.len() != bc.len() {
69 return Err(SemanticError::UnionColumnCountMismatch(
70 hc.len(),
71 bc.len(),
72 ));
73 }
74 for ((h_name, h_explicit), (b_name, b_explicit)) in
77 hc.iter().zip(bc.iter())
78 {
79 if (*h_explicit || *b_explicit) && h_name != b_name {
80 return Err(SemanticError::UnionColumnNameMismatch(
81 h_name.clone(),
82 b_name.clone(),
83 ));
84 }
85 }
86 }
87 }
88 }
89 }
90 Query::StandaloneCall(_) => {
91 return Err(SemanticError::UnsupportedFeature(
92 "Standalone CALL is not yet supported by the analyzer".into(),
93 ));
94 }
95 }
96
97 Ok(ResolvedQuery { clauses, unions })
98 }
99
100 fn analyze_single_query(
101 &mut self,
102 q: &SingleQuery,
103 ) -> Result<Vec<ResolvedClause>, SemanticError> {
104 match q {
105 SingleQuery::SinglePart(sp) => self.analyze_single_part(sp),
106 SingleQuery::MultiPart(mp) => {
107 let mut clauses = Vec::new();
108
109 for part in &mp.parts {
110 clauses.extend(self.analyze_query_part(part)?);
111 }
112
113 clauses.extend(self.analyze_single_part(&mp.tail)?);
114 Ok(clauses)
115 }
116 }
117 }
118
119 fn analyze_query_part(
120 &mut self,
121 part: &QueryPart,
122 ) -> Result<Vec<ResolvedClause>, SemanticError> {
123 let mut clauses = Vec::new();
124
125 for rc in &part.reading_clauses {
126 clauses.push(self.analyze_reading_clause(rc)?);
127 }
128
129 for uc in &part.updating_clauses {
130 clauses.push(self.analyze_updating_clause(uc)?);
131 }
132
133 clauses.push(ResolvedClause::With(self.analyze_with(&part.with_clause)?));
134 Ok(clauses)
135 }
136
137 fn analyze_single_part(
138 &mut self,
139 q: &SinglePartQuery,
140 ) -> Result<Vec<ResolvedClause>, SemanticError> {
141 let mut clauses = Vec::new();
142
143 for rc in &q.reading_clauses {
144 clauses.push(self.analyze_reading_clause(rc)?);
145 }
146
147 for uc in &q.updating_clauses {
148 clauses.push(self.analyze_updating_clause(uc)?);
149 }
150
151 if let Some(ret) = &q.return_clause {
152 clauses.push(ResolvedClause::Return(self.analyze_return(ret)?));
153 }
154
155 Ok(clauses)
156 }
157
158 fn analyze_reading_clause(
159 &mut self,
160 rc: &ReadingClause,
161 ) -> Result<ResolvedClause, SemanticError> {
162 match rc {
163 ReadingClause::Match(m) => Ok(ResolvedClause::Match(self.analyze_match(m)?)),
164 ReadingClause::Unwind(u) => Ok(ResolvedClause::Unwind(self.analyze_unwind(u)?)),
165 ReadingClause::InQueryCall(c) => self.analyze_in_query_call(c),
166 }
167 }
168
169 fn analyze_updating_clause(
170 &mut self,
171 uc: &UpdatingClause,
172 ) -> Result<ResolvedClause, SemanticError> {
173 match uc {
174 UpdatingClause::Create(c) => Ok(ResolvedClause::Create(self.analyze_create(c)?)),
175 UpdatingClause::Merge(m) => Ok(ResolvedClause::Merge(self.analyze_merge(m)?)),
176 UpdatingClause::Delete(d) => Ok(ResolvedClause::Delete(self.analyze_delete(d)?)),
177 UpdatingClause::Set(s) => Ok(ResolvedClause::Set(self.analyze_set(s)?)),
178 UpdatingClause::Remove(r) => Ok(ResolvedClause::Remove(self.analyze_remove(r)?)),
179 }
180 }
181
182 fn analyze_match(&mut self, m: &Match) -> Result<ResolvedMatch, SemanticError> {
183 let ctx = if m.optional {
184 PatternContext::OptionalRead
185 } else {
186 PatternContext::Read
187 };
188 let pattern = self.analyze_pattern(&m.pattern, ctx)?;
189 let where_ = m
190 .where_
191 .as_ref()
192 .map(|e| self.analyze_expr(e))
193 .transpose()?;
194
195 if let Some(ref w) = where_ {
196 if expr_contains_aggregate(w) {
197 return Err(SemanticError::AggregationInWhere);
198 }
199 }
200
201 Ok(ResolvedMatch {
202 optional: m.optional,
203 pattern,
204 where_,
205 })
206 }
207
208 fn analyze_unwind(&mut self, u: &Unwind) -> Result<ResolvedUnwind, SemanticError> {
209 let expr = self.analyze_expr(&u.expr)?;
210 let alias = self.declare_fresh_variable(&u.alias.name)?;
211
212 Ok(ResolvedUnwind { expr, alias })
213 }
214
215 fn analyze_in_query_call(
216 &mut self,
217 _call: &InQueryCall,
218 ) -> Result<ResolvedClause, SemanticError> {
219 Err(SemanticError::UnsupportedFeature(
220 "CALL ... YIELD is not yet supported by the analyzer".into(),
221 ))
222 }
223
224 fn analyze_create(&mut self, c: &Create) -> Result<ResolvedCreate, SemanticError> {
225 let pattern = self.analyze_pattern(&c.pattern, PatternContext::Write)?;
226 Ok(ResolvedCreate { pattern })
227 }
228
229 fn analyze_merge(&mut self, m: &Merge) -> Result<ResolvedMerge, SemanticError> {
230 let pattern_part = self.analyze_pattern_part(&m.pattern_part, PatternContext::Write)?;
231 let mut actions = Vec::with_capacity(m.actions.len());
232
233 for action in &m.actions {
234 actions.push(ResolvedMergeAction {
235 on_match: action.on_match,
236 set: self.analyze_set(&action.set)?,
237 });
238 }
239
240 Ok(ResolvedMerge {
241 pattern_part,
242 actions,
243 })
244 }
245
246 fn analyze_delete(&mut self, d: &Delete) -> Result<ResolvedDelete, SemanticError> {
247 let expressions = d
248 .expressions
249 .iter()
250 .map(|e| self.analyze_expr(e))
251 .collect::<Result<Vec<_>, _>>()?;
252
253 Ok(ResolvedDelete {
254 detach: d.detach,
255 expressions,
256 })
257 }
258
259 fn analyze_set(&mut self, s: &Set) -> Result<ResolvedSet, SemanticError> {
260 let mut items = Vec::with_capacity(s.items.len());
261
262 for item in &s.items {
263 match item {
264 SetItem::SetProperty { target, value, .. } => {
265 items.push(ResolvedSetItem::SetProperty {
268 target: self.analyze_expr_write_property(target)?,
269 value: self.analyze_expr(value)?,
270 });
271 }
272 SetItem::SetVariable {
273 variable, value, ..
274 } => {
275 let var = self.resolve_required_variable(&variable.name)?;
276 items.push(ResolvedSetItem::SetVariable {
277 variable: var,
278 value: self.analyze_expr(value)?,
279 });
280 }
281 SetItem::MutateVariable {
282 variable, value, ..
283 } => {
284 let var = self.resolve_required_variable(&variable.name)?;
285 items.push(ResolvedSetItem::MutateVariable {
286 variable: var,
287 value: self.analyze_expr(value)?,
288 });
289 }
290 SetItem::SetLabels {
291 variable, labels, ..
292 } => {
293 let var = self.resolve_required_variable(&variable.name)?;
294 for label in labels {
295 self.validate_label_name(label, PatternContext::Write)?;
296 }
297 items.push(ResolvedSetItem::SetLabels {
298 variable: var,
299 labels: labels.clone(),
300 });
301 }
302 }
303 }
304
305 Ok(ResolvedSet { items })
306 }
307
308 fn analyze_remove(&mut self, r: &Remove) -> Result<ResolvedRemove, SemanticError> {
309 let mut items = Vec::with_capacity(r.items.len());
310
311 for item in &r.items {
312 match item {
313 RemoveItem::Labels {
314 variable, labels, ..
315 } => {
316 let var = self.resolve_required_variable(&variable.name)?;
317 items.push(ResolvedRemoveItem::Labels {
318 variable: var,
319 labels: labels.clone(),
320 });
321 }
322 RemoveItem::Property { expr, .. } => {
323 items.push(ResolvedRemoveItem::Property {
324 expr: self.analyze_expr(expr)?,
325 });
326 }
327 }
328 }
329
330 Ok(ResolvedRemove { items })
331 }
332
333 fn analyze_pattern(
334 &mut self,
335 p: &Pattern,
336 context: PatternContext,
337 ) -> Result<ResolvedPattern, SemanticError> {
338 let mut parts = Vec::with_capacity(p.parts.len());
339
340 if matches!(context, PatternContext::Read | PatternContext::OptionalRead) {
343 let mut node_labels: BTreeMap<String, Vec<String>> = BTreeMap::new();
344 for part in &p.parts {
345 self.collect_node_var_labels(&part.element, &mut node_labels);
346 }
347 for (name, labels_list) in &node_labels {
348 if labels_list.len() > 1 {
350 let non_empty: Vec<&String> =
351 labels_list.iter().filter(|l| !l.is_empty()).collect();
352 let unique_labels: BTreeSet<&String> = non_empty.iter().copied().collect();
353 if unique_labels.len() > 1 {
354 return Err(SemanticError::DuplicateVariable(name.clone()));
355 }
356 }
357 }
358 }
359
360 for part in &p.parts {
361 parts.push(self.analyze_pattern_part(part, context)?);
362 }
363
364 Ok(ResolvedPattern { parts })
365 }
366
367 fn collect_node_var_labels(
369 &self,
370 el: &PatternElement,
371 map: &mut BTreeMap<String, Vec<String>>,
372 ) {
373 match el {
374 PatternElement::NodeChain { head, chain, .. } => {
375 if let Some(ref v) = head.variable {
376 let label_str = format_label_groups(&head.labels);
377 map.entry(v.name.clone()).or_default().push(label_str);
378 }
379 for step in chain {
380 if let Some(ref v) = step.node.variable {
381 let label_str = format_label_groups(&step.node.labels);
382 map.entry(v.name.clone()).or_default().push(label_str);
383 }
384 }
385 }
386 PatternElement::Parenthesized(inner, _) => {
387 self.collect_node_var_labels(inner, map);
388 }
389 PatternElement::ShortestPath { element, .. } => {
390 self.collect_node_var_labels(element, map);
391 }
392 }
393 }
394
395 fn analyze_pattern_part(
396 &mut self,
397 part: &PatternPart,
398 context: PatternContext,
399 ) -> Result<ResolvedPatternPart, SemanticError> {
400 let binding = part
401 .binding
402 .as_ref()
403 .map(|v| self.declare_or_reuse_variable(&v.name))
404 .transpose()?;
405
406 let element = self.analyze_pattern_element(&part.element, context)?;
407
408 Ok(ResolvedPatternPart { binding, element })
409 }
410
411 fn analyze_pattern_element(
412 &mut self,
413 el: &PatternElement,
414 context: PatternContext,
415 ) -> Result<ResolvedPatternElement, SemanticError> {
416 match el {
417 PatternElement::NodeChain { head, chain, .. } => {
418 if chain.is_empty() {
419 let node = self.analyze_node(head, context)?;
420 return Ok(ResolvedPatternElement::Node {
421 var: node.var,
422 labels: node.labels,
423 properties: node.properties,
424 });
425 }
426
427 let head = self.analyze_node(head, context)?;
428 let mut resolved_chain = Vec::with_capacity(chain.len());
429
430 for step in chain {
431 let rel = self.analyze_relationship(&step.relationship, context)?;
432 let node = self.analyze_node(&step.node, context)?;
433 resolved_chain.push(ResolvedChain { rel, node });
434 }
435
436 Ok(ResolvedPatternElement::NodeChain {
437 head,
438 chain: resolved_chain,
439 })
440 }
441
442 PatternElement::Parenthesized(inner, _) => self.analyze_pattern_element(inner, context),
443
444 PatternElement::ShortestPath { all, element, .. } => {
445 let resolved = self.analyze_pattern_element(element, context)?;
446 match resolved {
447 ResolvedPatternElement::NodeChain { head, chain } => {
448 Ok(ResolvedPatternElement::ShortestPath {
449 all: *all,
450 head,
451 chain,
452 })
453 }
454 other => Ok(other),
455 }
456 }
457 }
458 }
459
460 fn analyze_node(
461 &mut self,
462 node: &NodePattern,
463 context: PatternContext,
464 ) -> Result<ResolvedNode, SemanticError> {
465 let var = Some(match &node.variable {
466 Some(v) => self.declare_or_reuse_variable(&v.name)?,
468 None => self.symbols.new_var(),
472 });
473
474 let labels: Vec<Vec<String>> = node
475 .labels
476 .iter()
477 .map(|group| {
478 group
479 .iter()
480 .map(|l| {
481 self.validate_label_name(l, context)?;
482 Ok(l.clone())
483 })
484 .collect::<Result<Vec<_>, SemanticError>>()
485 })
486 .collect::<Result<Vec<_>, SemanticError>>()?;
487
488 let properties = node
489 .properties
490 .as_ref()
491 .map(|e| self.analyze_property_map_expr(e))
492 .transpose()?;
493
494 Ok(ResolvedNode {
495 var,
496 labels,
497 properties,
498 })
499 }
500
501 fn analyze_relationship(
502 &mut self,
503 rel: &RelationshipPattern,
504 context: PatternContext,
505 ) -> Result<ResolvedRel, SemanticError> {
506 if let Some(detail) = &rel.detail {
507 let var = Some(match &detail.variable {
508 Some(v) => self.declare_or_reuse_variable(&v.name)?,
509 None => self.symbols.new_var(),
513 });
514
515 let types = detail
516 .types
517 .iter()
518 .map(|t| {
519 self.validate_relationship_type_name(t, context)?;
520 Ok(t.clone())
521 })
522 .collect::<Result<Vec<_>, SemanticError>>()?;
523
524 if let Some(range) = &detail.range {
525 if let (Some(start), Some(end)) = (range.start, range.end) {
526 if start > end {
527 return Err(SemanticError::InvalidRange(
528 start,
529 end,
530 range.span.start,
531 range.span.end,
532 ));
533 }
534 }
535 }
536
537 let properties = detail
538 .properties
539 .as_ref()
540 .map(|e| self.analyze_property_map_expr(e))
541 .transpose()?;
542
543 Ok(ResolvedRel {
544 var,
545 types,
546 direction: rel.direction,
547 range: detail.range.clone(),
548 properties,
549 })
550 } else {
551 Ok(ResolvedRel {
552 var: None,
553 types: Vec::new(),
554 direction: rel.direction,
555 range: None,
556 properties: None,
557 })
558 }
559 }
560
561 fn analyze_expr_with_aliases(
564 &mut self,
565 expr: &Expr,
566 aliases: &BTreeMap<String, VarId>,
567 ) -> Result<ResolvedExpr, SemanticError> {
568 match expr {
569 Expr::Variable(v) => {
570 if self.scopes.resolve(&v.name).is_some() {
573 return self.analyze_expr(expr);
574 }
575 if let Some(&id) = aliases.get(&v.name) {
576 return Ok(ResolvedExpr::Variable(id));
577 }
578 self.analyze_expr(expr)
579 }
580 Expr::Property {
582 expr: inner,
583 key,
584 span,
585 } => {
586 let inner = self.analyze_expr_with_aliases(inner, aliases)?;
587 if self.property_access_allowed(&inner, key) {
588 Ok(ResolvedExpr::Property {
589 expr: Box::new(inner),
590 property: key.clone(),
591 })
592 } else {
593 Err(SemanticError::UnknownPropertyAt(
594 key.clone(),
595 span.start,
596 span.end,
597 ))
598 }
599 }
600 Expr::FunctionCall {
602 name,
603 distinct,
604 args,
605 span,
606 } => {
607 let fn_name = name.join(".");
608 validate_function_name(&fn_name, span.start, span.end)?;
609 validate_function_arity(&fn_name, args.len())?;
610
611 let args = args
612 .iter()
613 .map(|a| self.analyze_expr_with_aliases(a, aliases))
614 .collect::<Result<Vec<_>, _>>()?;
615
616 Ok(ResolvedExpr::Function {
617 name: fn_name,
618 distinct: *distinct,
619 args,
620 })
621 }
622 _ => self.analyze_expr(expr),
623 }
624 }
625
626 fn analyze_expr(&mut self, expr: &Expr) -> Result<ResolvedExpr, SemanticError> {
627 match expr {
628 Expr::Variable(v) => {
629 let id = self.resolve_required_variable(&v.name)?;
630 Ok(ResolvedExpr::Variable(id))
631 }
632
633 Expr::Integer(v, _) => Ok(ResolvedExpr::Literal(LiteralValue::Integer(*v))),
634 Expr::Float(v, _) => Ok(ResolvedExpr::Literal(LiteralValue::Float(*v))),
635 Expr::String(v, _) => Ok(ResolvedExpr::Literal(LiteralValue::String(v.clone()))),
636 Expr::Bool(v, _) => Ok(ResolvedExpr::Literal(LiteralValue::Bool(*v))),
637 Expr::Null(_) => Ok(ResolvedExpr::Literal(LiteralValue::Null)),
638 Expr::Parameter(name, _) => Ok(ResolvedExpr::Parameter(name.clone())),
639
640 Expr::List(items, _) => {
641 let items = items
642 .iter()
643 .map(|e| self.analyze_expr(e))
644 .collect::<Result<Vec<_>, _>>()?;
645 Ok(ResolvedExpr::List(items))
646 }
647
648 Expr::Map(items, _) => {
649 let mut seen = BTreeSet::new();
650 let mut out = Vec::with_capacity(items.len());
651
652 for (k, v) in items {
653 if !seen.insert(k.clone()) {
654 return Err(SemanticError::DuplicateMapKey(k.clone()));
655 }
656 out.push((k.clone(), self.analyze_expr(v)?));
657 }
658
659 Ok(ResolvedExpr::Map(out))
660 }
661
662 Expr::Property { expr, key, span } => {
663 let inner = self.analyze_expr(expr)?;
664
665 if self.property_access_allowed(&inner, key) {
666 Ok(ResolvedExpr::Property {
667 expr: Box::new(inner),
668 property: key.clone(),
669 })
670 } else {
671 Err(SemanticError::UnknownPropertyAt(
672 key.clone(),
673 span.start,
674 span.end,
675 ))
676 }
677 }
678
679 Expr::Binary { lhs, op, rhs, .. } => {
680 let lhs = self.analyze_expr(lhs)?;
681 let rhs = self.analyze_expr(rhs)?;
682
683 Ok(ResolvedExpr::Binary {
684 lhs: Box::new(lhs),
685 op: *op,
686 rhs: Box::new(rhs),
687 })
688 }
689
690 Expr::Unary { op, expr, .. } => {
691 let expr = self.analyze_expr(expr)?;
692 Ok(ResolvedExpr::Unary {
693 op: *op,
694 expr: Box::new(expr),
695 })
696 }
697
698 Expr::FunctionCall {
699 name,
700 distinct,
701 args,
702 span,
703 ..
704 } => {
705 let fn_name = name.join(".");
706 validate_function_name(&fn_name, span.start, span.end)?;
707 validate_function_arity(&fn_name, args.len())?;
708
709 let args = args
710 .iter()
711 .map(|a| self.analyze_expr(a))
712 .collect::<Result<Vec<_>, _>>()?;
713
714 Ok(ResolvedExpr::Function {
715 name: fn_name,
716 distinct: *distinct,
717 args,
718 })
719 }
720
721 Expr::ListPredicate {
722 kind,
723 variable,
724 list,
725 predicate,
726 ..
727 } => {
728 let list = self.analyze_expr(list)?;
729 let var_id = self.symbols.new_var();
730 self.scopes.push();
731 self.scopes.declare(variable.name.clone(), var_id);
732 let predicate = self.analyze_expr(predicate)?;
733 self.scopes.pop();
734
735 Ok(ResolvedExpr::ListPredicate {
736 kind: *kind,
737 variable: var_id,
738 list: Box::new(list),
739 predicate: Box::new(predicate),
740 })
741 }
742
743 Expr::ListComprehension {
744 variable,
745 list,
746 filter,
747 map_expr,
748 ..
749 } => {
750 let list = self.analyze_expr(list)?;
751 let var_id = self.symbols.new_var();
752 self.scopes.push();
753 self.scopes.declare(variable.name.clone(), var_id);
754 let filter = filter.as_ref().map(|e| self.analyze_expr(e)).transpose()?;
755 let map_expr = map_expr
756 .as_ref()
757 .map(|e| self.analyze_expr(e))
758 .transpose()?;
759 self.scopes.pop();
760
761 Ok(ResolvedExpr::ListComprehension {
762 variable: var_id,
763 list: Box::new(list),
764 filter: filter.map(Box::new),
765 map_expr: map_expr.map(Box::new),
766 })
767 }
768
769 Expr::Reduce {
770 accumulator,
771 init,
772 variable,
773 list,
774 expr,
775 ..
776 } => {
777 let init = self.analyze_expr(init)?;
778 let list = self.analyze_expr(list)?;
779 let acc_id = self.symbols.new_var();
780 let var_id = self.symbols.new_var();
781 self.scopes.push();
782 self.scopes.declare(accumulator.name.clone(), acc_id);
783 self.scopes.declare(variable.name.clone(), var_id);
784 let expr = self.analyze_expr(expr)?;
785 self.scopes.pop();
786
787 Ok(ResolvedExpr::Reduce {
788 accumulator: acc_id,
789 init: Box::new(init),
790 variable: var_id,
791 list: Box::new(list),
792 expr: Box::new(expr),
793 })
794 }
795
796 Expr::Index {
797 expr: inner, index, ..
798 } => {
799 let expr = self.analyze_expr(inner)?;
800 let index = self.analyze_expr(index)?;
801 Ok(ResolvedExpr::Index {
802 expr: Box::new(expr),
803 index: Box::new(index),
804 })
805 }
806
807 Expr::Slice {
808 expr: inner,
809 from,
810 to,
811 ..
812 } => {
813 let expr = self.analyze_expr(inner)?;
814 let from = from
815 .as_ref()
816 .map(|e| self.analyze_expr(e))
817 .transpose()?
818 .map(Box::new);
819 let to = to
820 .as_ref()
821 .map(|e| self.analyze_expr(e))
822 .transpose()?
823 .map(Box::new);
824 Ok(ResolvedExpr::Slice {
825 expr: Box::new(expr),
826 from,
827 to,
828 })
829 }
830
831 Expr::MapProjection {
832 base, selectors, ..
833 } => {
834 let base = self.analyze_expr(base)?;
835 let mut resolved_selectors = Vec::new();
836 for sel in selectors {
837 match sel {
838 MapProjectionSelector::Property(name) => {
839 resolved_selectors.push(ResolvedMapSelector::Property(name.clone()));
840 }
841 MapProjectionSelector::AllProperties => {
842 resolved_selectors.push(ResolvedMapSelector::AllProperties);
843 }
844 MapProjectionSelector::Literal(key, expr) => {
845 let resolved = self.analyze_expr(expr)?;
846 resolved_selectors
847 .push(ResolvedMapSelector::Literal(key.clone(), resolved));
848 }
849 }
850 }
851 Ok(ResolvedExpr::MapProjection {
852 base: Box::new(base),
853 selectors: resolved_selectors,
854 })
855 }
856
857 Expr::Case {
858 input,
859 alternatives,
860 else_expr,
861 ..
862 } => {
863 let input = input
864 .as_ref()
865 .map(|e| self.analyze_expr(e))
866 .transpose()?
867 .map(Box::new);
868
869 let alternatives = alternatives
870 .iter()
871 .map(|(when, then)| Ok((self.analyze_expr(when)?, self.analyze_expr(then)?)))
872 .collect::<Result<Vec<_>, SemanticError>>()?;
873
874 let else_expr = else_expr
875 .as_ref()
876 .map(|e| self.analyze_expr(e))
877 .transpose()?
878 .map(Box::new);
879
880 Ok(ResolvedExpr::Case {
881 input,
882 alternatives,
883 else_expr,
884 })
885 }
886
887 Expr::ExistsSubquery {
888 pattern, where_, ..
889 } => {
890 let resolved_pattern =
891 self.analyze_pattern(pattern, PatternContext::OptionalRead)?;
892 let resolved_where = where_.as_ref().map(|e| self.analyze_expr(e)).transpose()?;
893 Ok(ResolvedExpr::ExistsSubquery {
894 pattern: resolved_pattern,
895 where_: resolved_where.map(Box::new),
896 })
897 }
898
899 Expr::PatternComprehension {
900 pattern: pat_element,
901 where_,
902 map_expr,
903 ..
904 } => {
905 let pat = Pattern {
907 parts: vec![PatternPart {
908 binding: None,
909 element: (**pat_element).clone(),
910 span: map_expr.span(),
911 }],
912 span: map_expr.span(),
913 };
914 let resolved_pattern = self.analyze_pattern(&pat, PatternContext::OptionalRead)?;
915 let resolved_where = where_.as_ref().map(|e| self.analyze_expr(e)).transpose()?;
916 let resolved_map = self.analyze_expr(map_expr)?;
917 Ok(ResolvedExpr::PatternComprehension {
918 pattern: resolved_pattern,
919 where_: resolved_where.map(Box::new),
920 map_expr: Box::new(resolved_map),
921 })
922 }
923 }
924 }
925
926 fn analyze_property_map_expr(&mut self, expr: &Expr) -> Result<ResolvedExpr, SemanticError> {
927 match expr {
928 Expr::Map(_, _) | Expr::Parameter(_, _) => self.analyze_expr(expr),
929 _ => Err(SemanticError::ExpectedPropertyMap(
930 expr.span().start,
931 expr.span().end,
932 )),
933 }
934 }
935
936 fn analyze_return(&mut self, r: &Return) -> Result<ResolvedReturn, SemanticError> {
937 let analyzed = self.analyze_projection_body(&r.body)?;
938
939 Ok(ResolvedReturn {
940 distinct: r.body.distinct,
941 items: analyzed.items,
942 include_existing: analyzed.include_existing,
943 order: analyzed.order,
944 skip: analyzed.skip,
945 limit: analyzed.limit,
946 })
947 }
948
949 fn analyze_with(&mut self, w: &With) -> Result<ResolvedWith, SemanticError> {
950 let old_scope = self.visible_bindings();
951 let analyzed = self.analyze_projection_body(&w.body)?;
952
953 let mut new_scope = BTreeMap::<String, VarId>::new();
954
955 if analyzed.include_existing {
956 for (name, id) in old_scope {
957 new_scope.insert(name, id);
958 }
959 }
960
961 for exported in &analyzed.exported_aliases {
962 new_scope.insert(exported.name.clone(), exported.id);
963 }
964
965 self.replace_scope(new_scope);
966
967 let where_ = w
968 .where_
969 .as_ref()
970 .map(|e| self.analyze_expr(e))
971 .transpose()?;
972
973 Ok(ResolvedWith {
974 distinct: w.body.distinct,
975 items: analyzed.items,
976 include_existing: analyzed.include_existing,
977 order: analyzed.order,
978 skip: analyzed.skip,
979 limit: analyzed.limit,
980 where_,
981 })
982 }
983
984 fn analyze_projection_body(
985 &mut self,
986 body: &ProjectionBody,
987 ) -> Result<AnalyzedProjectionBody, SemanticError> {
988 let mut items = Vec::new();
989 let mut include_existing = false;
990 let mut exported_aliases = Vec::new();
991 let mut seen_alias_names = BTreeSet::new();
992
993 for item in &body.items {
994 match item {
995 ProjectionItem::Expr { expr, alias, span } => {
996 let resolved = self.analyze_expr(expr)?;
997
998 let explicit = alias.is_some();
999 let name = if let Some(var) = alias {
1000 if !seen_alias_names.insert(var.name.clone()) {
1001 return Err(SemanticError::DuplicateProjectionAlias(var.name.clone()));
1002 }
1003 var.name.clone()
1004 } else {
1005 projection_name(expr)
1006 };
1007
1008 let output = self.symbols.new_var();
1009
1010 exported_aliases.push(ExportedAlias {
1011 name: name.clone(),
1012 id: output,
1013 });
1014
1015 items.push(ResolvedProjection {
1016 expr: resolved,
1017 output,
1018 name,
1019 explicit_alias: explicit,
1020 span: *span,
1021 });
1022 }
1023
1024 ProjectionItem::Star { .. } => {
1025 include_existing = true;
1026 }
1027 }
1028 }
1029
1030 let alias_map: BTreeMap<String, VarId> = exported_aliases
1033 .iter()
1034 .map(|a| (a.name.clone(), a.id))
1035 .collect();
1036
1037 let order = body
1038 .order
1039 .iter()
1040 .map(|item| {
1041 let expr = self.analyze_expr_with_aliases(&item.expr, &alias_map)?;
1042 Ok(ResolvedSortItem {
1043 expr,
1044 direction: item.direction,
1045 })
1046 })
1047 .collect::<Result<Vec<_>, SemanticError>>()?;
1048
1049 let skip = body
1050 .skip
1051 .as_ref()
1052 .map(|e| self.analyze_expr(e))
1053 .transpose()?;
1054 let limit = body
1055 .limit
1056 .as_ref()
1057 .map(|e| self.analyze_expr(e))
1058 .transpose()?;
1059
1060 Ok(AnalyzedProjectionBody {
1061 items,
1062 include_existing,
1063 exported_aliases,
1064 order,
1065 skip,
1066 limit,
1067 })
1068 }
1069
1070 fn resolve_required_variable(&self, name: &str) -> Result<VarId, SemanticError> {
1071 self.scopes
1072 .resolve(name)
1073 .ok_or_else(|| SemanticError::UnknownVariable(name.to_string()))
1074 }
1075
1076 fn declare_fresh_variable(&mut self, name: &str) -> Result<VarId, SemanticError> {
1077 if self.scopes.resolve(name).is_some() {
1078 return Err(SemanticError::DuplicateVariable(name.to_string()));
1079 }
1080
1081 let id = self.symbols.new_var();
1082 self.scopes.declare(name.to_string(), id);
1083 Ok(id)
1084 }
1085
1086 fn declare_or_reuse_variable(&mut self, name: &str) -> Result<VarId, SemanticError> {
1087 if let Some(id) = self.scopes.resolve(name) {
1088 Ok(id)
1089 } else {
1090 let id = self.symbols.new_var();
1091 self.scopes.declare(name.to_string(), id);
1092 Ok(id)
1093 }
1094 }
1095
1096 fn validate_label_name(
1097 &self,
1098 label: &str,
1099 context: PatternContext,
1100 ) -> Result<(), SemanticError> {
1101 if matches!(
1102 context,
1103 PatternContext::Write | PatternContext::OptionalRead
1104 ) || self.storage.has_label_name(label)
1105 || self.storage.node_count() == 0
1106 {
1107 Ok(())
1108 } else {
1109 Err(SemanticError::UnknownLabel(label.to_string()))
1110 }
1111 }
1112
1113 fn validate_relationship_type_name(
1114 &self,
1115 rel_type: &str,
1116 context: PatternContext,
1117 ) -> Result<(), SemanticError> {
1118 if matches!(
1119 context,
1120 PatternContext::Write | PatternContext::OptionalRead
1121 ) || self.storage.has_relationship_type_name(rel_type)
1122 || self.storage.relationship_count() == 0
1123 {
1124 Ok(())
1125 } else {
1126 Err(SemanticError::UnknownRelationshipType(rel_type.to_string()))
1127 }
1128 }
1129
1130 fn analyze_expr_write_property(&mut self, expr: &Expr) -> Result<ResolvedExpr, SemanticError> {
1133 match expr {
1134 Expr::Property {
1135 expr: inner, key, ..
1136 } => {
1137 let inner_resolved = self.analyze_expr(inner)?;
1138 Ok(ResolvedExpr::Property {
1139 expr: Box::new(inner_resolved),
1140 property: key.clone(),
1141 })
1142 }
1143 other => self.analyze_expr(other),
1145 }
1146 }
1147
1148 fn property_access_allowed(&self, base: &ResolvedExpr, key: &str) -> bool {
1149 match base {
1150 ResolvedExpr::Map(_) => true,
1151 _ => {
1152 self.storage.has_property_key(key)
1153 || (self.storage.node_count() == 0 && self.storage.relationship_count() == 0)
1154 }
1155 }
1156 }
1157
1158 fn visible_bindings(&self) -> BTreeMap<String, VarId> {
1159 self.scopes.visible_bindings()
1160 }
1161
1162 fn replace_scope(&mut self, bindings: BTreeMap<String, VarId>) {
1163 self.scopes.clear();
1164 for (name, id) in bindings {
1165 self.scopes.declare(name, id);
1166 }
1167 }
1168}
1169
1170const KNOWN_FUNCTIONS: &[&str] = &[
1172 "count",
1174 "sum",
1175 "avg",
1176 "min",
1177 "max",
1178 "collect",
1179 "stdev",
1180 "stdevp",
1181 "percentilecont",
1182 "percentiledisc",
1183 "id",
1185 "type",
1186 "labels",
1187 "keys",
1188 "properties",
1189 "nodes",
1191 "relationships",
1192 "tolower",
1194 "toupper",
1195 "trim",
1196 "ltrim",
1197 "rtrim",
1198 "replace",
1199 "split",
1200 "substring",
1201 "reverse",
1202 "left",
1203 "right",
1204 "lpad",
1205 "rpad",
1206 "char_length",
1207 "normalize",
1208 "tostring",
1210 "tointeger",
1211 "toint",
1212 "tofloat",
1213 "toboolean",
1214 "tobooleanornull",
1215 "valuetype",
1216 "abs",
1218 "ceil",
1219 "floor",
1220 "round",
1221 "sqrt",
1222 "sign",
1223 "log",
1225 "ln",
1226 "log10",
1227 "exp",
1228 "sin",
1229 "cos",
1230 "tan",
1231 "asin",
1232 "acos",
1233 "atan",
1234 "atan2",
1235 "degrees",
1236 "radians",
1237 "pi",
1239 "e",
1240 "rand",
1241 "size",
1243 "length",
1244 "head",
1245 "tail",
1246 "last",
1247 "range",
1248 "coalesce",
1250 "timestamp",
1251 "date",
1253 "datetime",
1254 "time",
1255 "localtime",
1256 "localdatetime",
1257 "duration",
1258 "date.truncate",
1259 "datetime.truncate",
1260 "duration.between",
1261 "duration.indays",
1262 "point",
1264 "distance",
1265];
1266
1267const AGGREGATE_FUNCTIONS: &[&str] = &[
1268 "count",
1269 "sum",
1270 "avg",
1271 "min",
1272 "max",
1273 "collect",
1274 "stdev",
1275 "stdevp",
1276 "percentilecont",
1277 "percentiledisc",
1278];
1279
1280fn function_arity(name: &str) -> Option<(usize, Option<usize>)> {
1282 match name {
1283 "count" => Some((0, Some(1))),
1285 "sum" | "avg" | "min" | "max" | "collect" | "stdev" | "stdevp" => Some((1, Some(1))),
1286 "percentilecont" | "percentiledisc" => Some((2, Some(2))),
1287 "id" | "type" | "labels" | "keys" | "properties" | "nodes" | "relationships" => {
1289 Some((1, Some(1)))
1290 }
1291 "tolower" | "toupper" | "trim" | "ltrim" | "rtrim" | "reverse" => Some((1, Some(1))),
1293 "split" | "left" | "right" => Some((2, Some(2))),
1295 "replace" => Some((3, Some(3))),
1297 "substring" => Some((2, Some(3))),
1299 "tostring" | "tointeger" | "toint" | "tofloat" | "toboolean" | "tobooleanornull"
1301 | "valuetype" => Some((1, Some(1))),
1302 "lpad" | "rpad" => Some((3, Some(3))),
1304 "char_length" | "normalize" => Some((1, Some(1))),
1306 "abs" | "ceil" | "floor" | "round" | "sqrt" | "sign" => Some((1, Some(1))),
1308 "log" | "ln" | "log10" | "exp" | "sin" | "cos" | "tan" | "asin" | "acos" | "atan"
1310 | "degrees" | "radians" => Some((1, Some(1))),
1311 "atan2" => Some((2, Some(2))),
1313 "pi" | "e" | "rand" => Some((0, Some(0))),
1315 "size" | "length" | "head" | "tail" | "last" => Some((1, Some(1))),
1317 "range" => Some((2, Some(3))),
1319 "coalesce" => Some((1, None)),
1321 "timestamp" => Some((0, Some(0))),
1323 "date" | "datetime" | "time" | "localtime" | "localdatetime" => Some((0, Some(1))),
1325 "duration" => Some((1, Some(1))),
1327 "date.truncate" | "datetime.truncate" | "duration.between" | "duration.indays" => {
1329 Some((2, Some(2)))
1330 }
1331 "point" => Some((1, Some(1))),
1333 "distance" => Some((2, Some(2))),
1334 _ => None,
1335 }
1336}
1337
1338fn is_aggregate_function(name: &str) -> bool {
1339 AGGREGATE_FUNCTIONS.contains(&name.to_ascii_lowercase().as_str())
1340}
1341
1342fn validate_function_name(name: &str, start: usize, end: usize) -> Result<(), SemanticError> {
1343 let lower = name.to_ascii_lowercase();
1344 if KNOWN_FUNCTIONS.contains(&lower.as_str()) {
1345 Ok(())
1346 } else {
1347 Err(SemanticError::UnknownFunction(name.to_string(), start, end))
1348 }
1349}
1350
1351fn validate_function_arity(name: &str, arg_count: usize) -> Result<(), SemanticError> {
1352 let lower = name.to_ascii_lowercase();
1353 if let Some((min, max)) = function_arity(&lower) {
1354 if arg_count < min {
1355 let expected = if max == Some(min) {
1356 format!("{min}")
1357 } else if let Some(mx) = max {
1358 format!("{min}..{mx}")
1359 } else {
1360 format!("at least {min}")
1361 };
1362 return Err(SemanticError::WrongArity(
1363 name.to_string(),
1364 expected,
1365 arg_count,
1366 ));
1367 }
1368 if let Some(mx) = max {
1369 if arg_count > mx {
1370 let expected = if mx == min {
1371 format!("{min}")
1372 } else {
1373 format!("{min}..{mx}")
1374 };
1375 return Err(SemanticError::WrongArity(
1376 name.to_string(),
1377 expected,
1378 arg_count,
1379 ));
1380 }
1381 }
1382 }
1383 Ok(())
1384}
1385
1386fn format_label_groups(groups: &[impl AsRef<[String]>]) -> String {
1388 groups
1389 .iter()
1390 .map(|g| g.as_ref().join("|"))
1391 .collect::<Vec<_>>()
1392 .join(":")
1393}
1394
1395fn return_column_info(clauses: &[ResolvedClause]) -> Option<Vec<(String, bool)>> {
1397 for clause in clauses.iter().rev() {
1398 if let ResolvedClause::Return(ret) = clause {
1399 return Some(
1400 ret.items
1401 .iter()
1402 .map(|p| (p.name.clone(), p.explicit_alias))
1403 .collect(),
1404 );
1405 }
1406 }
1407 None
1408}
1409
1410fn expr_contains_aggregate(expr: &ResolvedExpr) -> bool {
1412 match expr {
1413 ResolvedExpr::Function { name, args, .. } => {
1414 if is_aggregate_function(name) {
1415 return true;
1416 }
1417 args.iter().any(expr_contains_aggregate)
1418 }
1419 ResolvedExpr::Binary { lhs, rhs, .. } => {
1420 expr_contains_aggregate(lhs) || expr_contains_aggregate(rhs)
1421 }
1422 ResolvedExpr::Unary { expr, .. } => expr_contains_aggregate(expr),
1423 ResolvedExpr::Property { expr, .. } => expr_contains_aggregate(expr),
1424 ResolvedExpr::List(items) => items.iter().any(expr_contains_aggregate),
1425 ResolvedExpr::Map(items) => items.iter().any(|(_, v)| expr_contains_aggregate(v)),
1426 ResolvedExpr::Case {
1427 input,
1428 alternatives,
1429 else_expr,
1430 } => {
1431 input.as_ref().is_some_and(|e| expr_contains_aggregate(e))
1432 || alternatives
1433 .iter()
1434 .any(|(w, t)| expr_contains_aggregate(w) || expr_contains_aggregate(t))
1435 || else_expr
1436 .as_ref()
1437 .is_some_and(|e| expr_contains_aggregate(e))
1438 }
1439 ResolvedExpr::ListPredicate {
1440 list, predicate, ..
1441 } => expr_contains_aggregate(list) || expr_contains_aggregate(predicate),
1442 ResolvedExpr::ListComprehension {
1443 list,
1444 filter,
1445 map_expr,
1446 ..
1447 } => {
1448 expr_contains_aggregate(list)
1449 || filter.as_ref().is_some_and(|e| expr_contains_aggregate(e))
1450 || map_expr
1451 .as_ref()
1452 .is_some_and(|e| expr_contains_aggregate(e))
1453 }
1454 ResolvedExpr::Reduce {
1455 init, list, expr, ..
1456 } => {
1457 expr_contains_aggregate(init)
1458 || expr_contains_aggregate(list)
1459 || expr_contains_aggregate(expr)
1460 }
1461 ResolvedExpr::Index { expr, index } => {
1462 expr_contains_aggregate(expr) || expr_contains_aggregate(index)
1463 }
1464 ResolvedExpr::Slice { expr, from, to } => {
1465 expr_contains_aggregate(expr)
1466 || from.as_ref().is_some_and(|e| expr_contains_aggregate(e))
1467 || to.as_ref().is_some_and(|e| expr_contains_aggregate(e))
1468 }
1469 ResolvedExpr::MapProjection { base, selectors } => expr_contains_aggregate(base)
1470 || selectors.iter().any(
1471 |s| matches!(s, ResolvedMapSelector::Literal(_, e) if expr_contains_aggregate(e)),
1472 ),
1473 ResolvedExpr::ExistsSubquery { .. } | ResolvedExpr::PatternComprehension { .. } => false,
1474 ResolvedExpr::Variable(_) | ResolvedExpr::Literal(_) | ResolvedExpr::Parameter(_) => false,
1475 }
1476}
1477
1478fn projection_name(expr: &Expr) -> String {
1479 match expr {
1480 Expr::Variable(v) => v.name.clone(),
1481 Expr::Property { key, .. } => key.clone(),
1482 Expr::FunctionCall { name, .. } => {
1483 name.last().cloned().unwrap_or_else(|| "expr".to_string())
1484 }
1485 _ => "expr".to_string(),
1486 }
1487}
1488
1489#[cfg(test)]
1490mod tests {
1491 use super::*;
1492 use lora_parser::parse_query;
1493 use lora_store::{GraphStorageMut, InMemoryGraph, Properties};
1494
1495 #[test]
1496 fn create_allows_new_relationship_type_when_graph_is_not_empty() {
1497 let mut graph = InMemoryGraph::new();
1498 let alice = graph.create_node(vec!["User".into()], Properties::new());
1499 let bob = graph.create_node(vec!["User".into()], Properties::new());
1500 let _carol = graph.create_node(vec!["User".into()], Properties::new());
1501
1502 graph
1503 .create_relationship(alice.id, bob.id, "FOLLOWS", Properties::new())
1504 .unwrap();
1505
1506 let doc = parse_query(
1507 "MATCH (a:User {id: 2}), (b:User {id: 3}) CREATE (a)-[:KNOWS]->(b) RETURN a, b",
1508 )
1509 .unwrap();
1510
1511 let mut analyzer = Analyzer::new(&graph);
1512 assert!(analyzer.analyze(&doc).is_ok());
1513
1514 let match_doc = parse_query("MATCH (a)-[:KNOWS]->(b) RETURN a, b").unwrap();
1515 let mut analyzer = Analyzer::new(&graph);
1516 assert!(matches!(
1517 analyzer.analyze(&match_doc),
1518 Err(SemanticError::UnknownRelationshipType(rel_type)) if rel_type == "KNOWS"
1519 ));
1520 }
1521}
1522
1523#[derive(Debug, Clone)]
1524struct ExportedAlias {
1525 name: String,
1526 id: VarId,
1527}
1528
1529#[derive(Debug, Clone)]
1530struct AnalyzedProjectionBody {
1531 items: Vec<ResolvedProjection>,
1532 include_existing: bool,
1533 exported_aliases: Vec<ExportedAlias>,
1534 order: Vec<ResolvedSortItem>,
1535 skip: Option<ResolvedExpr>,
1536 limit: Option<ResolvedExpr>,
1537}