1use crate::{errors::*, resolved::*, scope::*, symbols::*};
2use lora_ast::{
3 Document, Expr, Query, QueryPart, ReadingClause, SinglePartQuery, SingleQuery, Statement,
4 UpdatingClause,
5};
6use lora_store::GraphCatalog;
7use std::collections::{BTreeMap, BTreeSet};
8
9pub struct Analyzer<'a, S: GraphCatalog + ?Sized> {
10 pub(super) _catalog: std::marker::PhantomData<&'a S>,
15 pub(super) scopes: ScopeStack,
16 pub(super) symbols: SymbolTable,
17 pub(super) dynamic_property_vars: BTreeSet<VarId>,
23 pub(super) parameters: BTreeSet<String>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub(super) enum PatternContext {
29 Read,
30 OptionalRead,
32 Write,
33}
34
35impl<'a, S: GraphCatalog + ?Sized> Analyzer<'a, S> {
36 pub fn new(_storage: &'a S) -> Self {
37 Self {
38 _catalog: std::marker::PhantomData,
39 scopes: ScopeStack::new(),
40 symbols: SymbolTable::default(),
41 dynamic_property_vars: BTreeSet::new(),
42 parameters: BTreeSet::new(),
43 }
44 }
45
46 pub fn analyze(&mut self, doc: &Document) -> Result<ResolvedQuery, SemanticError> {
47 match &doc.statement {
48 Statement::Query(q) => self.analyze_query(q),
49 Statement::Schema(_) => Err(SemanticError::UnsupportedFeature(
50 "schema commands are dispatched outside the analyzer".to_string(),
51 )),
52 }
53 }
54
55 fn analyze_query(&mut self, query: &Query) -> Result<ResolvedQuery, SemanticError> {
56 let mut clauses = Vec::new();
57 let mut unions = Vec::new();
58
59 match query {
60 Query::Regular(r) => {
61 clauses.extend(self.analyze_single_query(&r.head)?);
62
63 for union_part in &r.unions {
64 self.scopes.clear();
67
68 let branch_clauses = self.analyze_single_query(&union_part.query)?;
69 unions.push(ResolvedUnionPart {
70 all: union_part.all,
71 clauses: branch_clauses,
72 });
73 }
74
75 if !unions.is_empty() {
79 let head_cols = return_column_info(&clauses);
80 for branch in &unions {
81 let branch_cols = return_column_info(&branch.clauses);
82 if let (Some(hc), Some(bc)) = (&head_cols, &branch_cols) {
83 if hc.len() != bc.len() {
84 return Err(SemanticError::UnionColumnCountMismatch(
85 hc.len(),
86 bc.len(),
87 ));
88 }
89 for ((h_name, h_explicit), (b_name, b_explicit)) in
92 hc.iter().zip(bc.iter())
93 {
94 if (*h_explicit || *b_explicit) && h_name != b_name {
95 return Err(SemanticError::UnionColumnNameMismatch(
96 h_name.clone(),
97 b_name.clone(),
98 ));
99 }
100 }
101 }
102 }
103 }
104 }
105 Query::StandaloneCall(_) => {
106 return Err(SemanticError::UnsupportedFeature(
107 "Standalone CALL is not yet supported by the analyzer".into(),
108 ));
109 }
110 }
111
112 Ok(ResolvedQuery {
113 clauses,
114 unions,
115 parameters: std::mem::take(&mut self.parameters),
116 })
117 }
118
119 fn analyze_single_query(
120 &mut self,
121 q: &SingleQuery,
122 ) -> Result<Vec<ResolvedClause>, SemanticError> {
123 match q {
124 SingleQuery::SinglePart(sp) => self.analyze_single_part(sp),
125 SingleQuery::MultiPart(mp) => {
126 let mut clauses = Vec::new();
127
128 for part in &mp.parts {
129 clauses.extend(self.analyze_query_part(part)?);
130 }
131
132 clauses.extend(self.analyze_single_part(&mp.tail)?);
133 Ok(clauses)
134 }
135 }
136 }
137
138 fn analyze_query_part(
139 &mut self,
140 part: &QueryPart,
141 ) -> Result<Vec<ResolvedClause>, SemanticError> {
142 let mut clauses = Vec::new();
143
144 for rc in &part.reading_clauses {
145 clauses.extend(self.analyze_reading_clause(rc)?);
146 }
147
148 for uc in &part.updating_clauses {
149 clauses.push(self.analyze_updating_clause(uc)?);
150 }
151
152 clauses.push(ResolvedClause::With(self.analyze_with(&part.with_clause)?));
153 Ok(clauses)
154 }
155
156 fn analyze_single_part(
157 &mut self,
158 q: &SinglePartQuery,
159 ) -> Result<Vec<ResolvedClause>, SemanticError> {
160 let mut clauses = Vec::new();
161
162 for rc in &q.reading_clauses {
163 clauses.extend(self.analyze_reading_clause(rc)?);
164 }
165
166 for uc in &q.updating_clauses {
167 clauses.push(self.analyze_updating_clause(uc)?);
168 }
169
170 if let Some(ret) = &q.return_clause {
171 clauses.push(ResolvedClause::Return(self.analyze_return(ret)?));
172 } else if q.updating_clauses.is_empty() {
173 match clauses.last() {
176 Some(ResolvedClause::CallSubquery(call)) if call.return_vars.is_empty() => {}
177 _ => {
178 return Err(SemanticError::UnsupportedFeature(
179 "a query can only end in CALL { ... } when the subquery ends in an \
180 updating clause; add a RETURN"
181 .into(),
182 ))
183 }
184 }
185 }
186
187 Ok(clauses)
188 }
189
190 fn analyze_reading_clause(
191 &mut self,
192 rc: &ReadingClause,
193 ) -> Result<Vec<ResolvedClause>, SemanticError> {
194 Ok(match rc {
195 ReadingClause::Match(m) => vec![ResolvedClause::Match(self.analyze_match(m)?)],
196 ReadingClause::Unwind(u) => vec![ResolvedClause::Unwind(self.analyze_unwind(u)?)],
197 ReadingClause::InQueryCall(c) => self.analyze_in_query_call(c)?,
198 ReadingClause::CallSubquery(c) => {
199 vec![ResolvedClause::CallSubquery(self.analyze_call_subquery(c)?)]
200 }
201 })
202 }
203
204 fn analyze_call_subquery(
210 &mut self,
211 call: &lora_ast::CallSubquery,
212 ) -> Result<ResolvedCallSubquery, SemanticError> {
213 let outer = self.visible_bindings();
214
215 if !call.body.unions.is_empty() {
216 return Err(SemanticError::UnsupportedFeature(
217 "UNION inside CALL { ... } is not yet supported".into(),
218 ));
219 }
220
221 let inner_clauses = self.analyze_single_query(&call.body.head)?;
222
223 let return_items: Vec<(String, VarId)> = match inner_clauses.last() {
224 Some(ResolvedClause::Return(ret)) => ret
225 .items
226 .iter()
227 .map(|p| (p.name.to_string(), p.output))
228 .collect(),
229 Some(
233 ResolvedClause::Create(_)
234 | ResolvedClause::Merge(_)
235 | ResolvedClause::Delete(_)
236 | ResolvedClause::Set(_)
237 | ResolvedClause::Remove(_)
238 | ResolvedClause::Foreach(_),
239 ) => Vec::new(),
240 Some(ResolvedClause::CallSubquery(inner)) if inner.return_vars.is_empty() => Vec::new(),
241 _ => {
242 return Err(SemanticError::UnsupportedFeature(
243 "CALL { ... } subquery must end with RETURN or an updating clause".into(),
244 ));
245 }
246 };
247
248 let mut new_scope = outer;
249 for (name, id) in &return_items {
250 new_scope.insert(name.clone(), *id);
251 }
252 self.replace_scope(new_scope);
253
254 Ok(ResolvedCallSubquery {
255 clauses: inner_clauses,
256 return_vars: return_items.into_iter().map(|(_, id)| id).collect(),
257 })
258 }
259
260 fn analyze_updating_clause(
261 &mut self,
262 uc: &UpdatingClause,
263 ) -> Result<ResolvedClause, SemanticError> {
264 match uc {
265 UpdatingClause::Create(c) => Ok(ResolvedClause::Create(self.analyze_create(c)?)),
266 UpdatingClause::Merge(m) => Ok(ResolvedClause::Merge(self.analyze_merge(m)?)),
267 UpdatingClause::Delete(d) => Ok(ResolvedClause::Delete(self.analyze_delete(d)?)),
268 UpdatingClause::Set(s) => Ok(ResolvedClause::Set(self.analyze_set(s)?)),
269 UpdatingClause::Remove(r) => Ok(ResolvedClause::Remove(self.analyze_remove(r)?)),
270 UpdatingClause::Foreach(f) => Ok(ResolvedClause::Foreach(self.analyze_foreach(f)?)),
271 }
272 }
273
274 pub(super) fn analyze_property_map_expr(
275 &mut self,
276 expr: &Expr,
277 ) -> Result<ResolvedExpr, SemanticError> {
278 match expr {
279 Expr::Map(_, _) | Expr::Parameter(_, _) => self.analyze_expr(expr),
280 _ => Err(SemanticError::ExpectedPropertyMap(
281 expr.span().start,
282 expr.span().end,
283 )),
284 }
285 }
286
287 pub(super) fn resolve_required_variable(&self, name: &str) -> Result<VarId, SemanticError> {
288 self.scopes
289 .resolve(name)
290 .ok_or_else(|| SemanticError::UnknownVariable(name.to_string()))
291 }
292
293 pub(super) fn declare_fresh_variable(&mut self, name: &str) -> Result<VarId, SemanticError> {
294 if self.scopes.resolve(name).is_some() {
295 return Err(SemanticError::DuplicateVariable(name.to_string()));
296 }
297
298 let id = self.symbols.new_var();
299 self.scopes.declare(name.to_string(), id);
300 Ok(id)
301 }
302
303 pub(super) fn declare_or_reuse_variable(&mut self, name: &str) -> Result<VarId, SemanticError> {
304 if let Some(id) = self.scopes.resolve(name) {
305 Ok(id)
306 } else {
307 let id = self.symbols.new_var();
308 self.scopes.declare(name.to_string(), id);
309 Ok(id)
310 }
311 }
312
313 pub(super) fn validate_label_name(
320 &self,
321 _label: &str,
322 _context: PatternContext,
323 ) -> Result<(), SemanticError> {
324 Ok(())
325 }
326
327 pub(super) fn validate_relationship_type_name(
330 &self,
331 _rel_type: &str,
332 _context: PatternContext,
333 ) -> Result<(), SemanticError> {
334 Ok(())
335 }
336
337 pub(super) fn analyze_expr_write_property(
340 &mut self,
341 expr: &Expr,
342 ) -> Result<ResolvedExpr, SemanticError> {
343 match expr {
344 Expr::Property {
345 expr: inner, key, ..
346 } => {
347 let inner_resolved = self.analyze_expr(inner)?;
348 Ok(ResolvedExpr::Property {
349 expr: Box::new(inner_resolved),
350 property: key.clone(),
351 })
352 }
353 other => self.analyze_expr(other),
355 }
356 }
357
358 pub(super) fn property_access_allowed(&self, _base: &ResolvedExpr, _key: &str) -> bool {
363 true
364 }
365
366 pub(super) fn visible_bindings(&self) -> BTreeMap<String, VarId> {
367 self.scopes.visible_bindings()
368 }
369
370 pub(super) fn replace_scope(&mut self, bindings: BTreeMap<String, VarId>) {
371 self.scopes.clear();
372 for (name, id) in bindings {
373 self.scopes.declare(name, id);
374 }
375 }
376}
377
378fn return_column_info(clauses: &[ResolvedClause]) -> Option<Vec<(String, bool)>> {
380 for clause in clauses.iter().rev() {
381 if let ResolvedClause::Return(ret) = clause {
382 return Some(
383 ret.items
384 .iter()
385 .map(|p| (p.name.to_string(), p.explicit_alias))
386 .collect(),
387 );
388 }
389 }
390 None
391}