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