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