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