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 }
168 }
169
170 fn analyze_updating_clause(
171 &mut self,
172 uc: &UpdatingClause,
173 ) -> Result<ResolvedClause, SemanticError> {
174 match uc {
175 UpdatingClause::Create(c) => Ok(ResolvedClause::Create(self.analyze_create(c)?)),
176 UpdatingClause::Merge(m) => Ok(ResolvedClause::Merge(self.analyze_merge(m)?)),
177 UpdatingClause::Delete(d) => Ok(ResolvedClause::Delete(self.analyze_delete(d)?)),
178 UpdatingClause::Set(s) => Ok(ResolvedClause::Set(self.analyze_set(s)?)),
179 UpdatingClause::Remove(r) => Ok(ResolvedClause::Remove(self.analyze_remove(r)?)),
180 }
181 }
182
183 pub(super) fn analyze_property_map_expr(
184 &mut self,
185 expr: &Expr,
186 ) -> Result<ResolvedExpr, SemanticError> {
187 match expr {
188 Expr::Map(_, _) | Expr::Parameter(_, _) => self.analyze_expr(expr),
189 _ => Err(SemanticError::ExpectedPropertyMap(
190 expr.span().start,
191 expr.span().end,
192 )),
193 }
194 }
195
196 pub(super) fn resolve_required_variable(&self, name: &str) -> Result<VarId, SemanticError> {
197 self.scopes
198 .resolve(name)
199 .ok_or_else(|| SemanticError::UnknownVariable(name.to_string()))
200 }
201
202 pub(super) fn declare_fresh_variable(&mut self, name: &str) -> Result<VarId, SemanticError> {
203 if self.scopes.resolve(name).is_some() {
204 return Err(SemanticError::DuplicateVariable(name.to_string()));
205 }
206
207 let id = self.symbols.new_var();
208 self.scopes.declare(name.to_string(), id);
209 Ok(id)
210 }
211
212 pub(super) fn declare_or_reuse_variable(&mut self, name: &str) -> Result<VarId, SemanticError> {
213 if let Some(id) = self.scopes.resolve(name) {
214 Ok(id)
215 } else {
216 let id = self.symbols.new_var();
217 self.scopes.declare(name.to_string(), id);
218 Ok(id)
219 }
220 }
221
222 pub(super) fn validate_label_name(
223 &self,
224 label: &str,
225 context: PatternContext,
226 ) -> Result<(), SemanticError> {
227 if matches!(
228 context,
229 PatternContext::Write | PatternContext::OptionalRead
230 ) || self.storage.has_label_name(label)
231 || self.storage.node_count() == 0
232 {
233 Ok(())
234 } else {
235 Err(SemanticError::UnknownLabel(label.to_string()))
236 }
237 }
238
239 pub(super) fn validate_relationship_type_name(
240 &self,
241 rel_type: &str,
242 context: PatternContext,
243 ) -> Result<(), SemanticError> {
244 if matches!(
245 context,
246 PatternContext::Write | PatternContext::OptionalRead
247 ) || self.storage.has_relationship_type_name(rel_type)
248 || self.storage.relationship_count() == 0
249 {
250 Ok(())
251 } else {
252 Err(SemanticError::UnknownRelationshipType(rel_type.to_string()))
253 }
254 }
255
256 pub(super) fn analyze_expr_write_property(
259 &mut self,
260 expr: &Expr,
261 ) -> Result<ResolvedExpr, SemanticError> {
262 match expr {
263 Expr::Property {
264 expr: inner, key, ..
265 } => {
266 let inner_resolved = self.analyze_expr(inner)?;
267 Ok(ResolvedExpr::Property {
268 expr: Box::new(inner_resolved),
269 property: key.clone(),
270 })
271 }
272 other => self.analyze_expr(other),
274 }
275 }
276
277 pub(super) fn property_access_allowed(&self, base: &ResolvedExpr, key: &str) -> bool {
278 match base {
279 ResolvedExpr::Map(_) => true,
280 _ => {
281 self.storage.has_property_key(key)
282 || (self.storage.node_count() == 0 && self.storage.relationship_count() == 0)
283 }
284 }
285 }
286
287 pub(super) fn visible_bindings(&self) -> BTreeMap<String, VarId> {
288 self.scopes.visible_bindings()
289 }
290
291 pub(super) fn replace_scope(&mut self, bindings: BTreeMap<String, VarId>) {
292 self.scopes.clear();
293 for (name, id) in bindings {
294 self.scopes.declare(name, id);
295 }
296 }
297}
298
299fn return_column_info(clauses: &[ResolvedClause]) -> Option<Vec<(String, bool)>> {
301 for clause in clauses.iter().rev() {
302 if let ResolvedClause::Return(ret) = clause {
303 return Some(
304 ret.items
305 .iter()
306 .map(|p| (p.name.clone(), p.explicit_alias))
307 .collect(),
308 );
309 }
310 }
311 None
312}