1use crate::ast::dml::{FromItem, Select, SelectItem};
2use crate::ast::expr::{Expr, ExprKind};
3use crate::ast::{Statement, StatementKind};
4use crate::error::{ParserError, Result};
5use crate::nim_ffi::{self, OwnedBuffer, ParseResultKind};
6
7pub fn parser_contract_version() -> String {
9 nim_ffi::parser_contract_version()
10}
11
12pub fn parse_sql(sql: &str) -> Result<Vec<Statement>> {
13 if sql.as_bytes().contains(&0) {
14 return Err(ParserError::UnexpectedToken {
15 line: 0,
16 column: 0,
17 expected: "valid SQL without interior NUL bytes".to_string(),
18 found: "interior NUL byte".to_string(),
19 });
20 }
21
22 let natural_join_markers = natural_join_markers(sql);
23 let normalized_sql = normalize_quoted_identifiers(sql);
28 let result = nim_ffi::parse_sql(&normalized_sql);
29 match result.kind {
30 ParseResultKind::Ok => {
31 let buffer = OwnedBuffer::new(result.buffer_ptr, result.buffer_len);
32 if buffer.as_slice().is_empty() {
37 return Err(ParserError::UnexpectedToken {
38 line: 0,
39 column: 0,
40 expected: "MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
41 found: "empty payload from Nim parser (leaked exception at FFI boundary; \
42 see issue #40)"
43 .to_string(),
44 });
45 }
46 let mut statements = rmp_serde::from_slice::<Vec<Statement>>(buffer.as_slice())
47 .map_err(|err| ParserError::UnexpectedToken {
48 line: 0,
49 column: 0,
50 expected: "MessagePack AST matching docs/ffi-ast-contract.md".to_string(),
51 found: err.to_string(),
52 })?;
53 annotate_natural_joins(&mut statements, natural_join_markers)?;
54 Ok(statements)
55 }
56 ParseResultKind::Error => {
57 let buffer = OwnedBuffer::new(result.error_ptr.cast(), result.error_len);
58 Err(parser_error_from_nim(
59 String::from_utf8_lossy(buffer.as_slice()).as_ref(),
60 ))
61 }
62 }
63}
64
65fn normalize_quoted_identifiers(sql: &str) -> String {
66 let mut normalized = String::with_capacity(sql.len());
67 let mut chars = sql.chars().peekable();
68 while let Some(ch) = chars.next() {
69 match ch {
70 '\'' => {
71 normalized.push(ch);
72 while let Some(string_ch) = chars.next() {
73 normalized.push(string_ch);
74 if string_ch == '\'' {
75 if chars.peek() == Some(&'\'') {
76 normalized.push(chars.next().expect("peeked quote"));
77 } else {
78 break;
79 }
80 }
81 }
82 }
83 '"' => {
84 normalized.push(' ');
88 while let Some(identifier_ch) = chars.next() {
89 if identifier_ch == '"' {
90 if chars.peek() == Some(&'"') {
91 normalized.push(chars.next().expect("peeked quote"));
94 normalized.push(' ');
95 } else {
96 normalized.push(' ');
97 break;
98 }
99 } else {
100 normalized.push(identifier_ch);
101 }
102 }
103 }
104 '-' if chars.peek() == Some(&'-') => {
105 normalized.push(ch);
106 normalized.push(chars.next().expect("peeked comment dash"));
107 for comment_ch in chars.by_ref() {
108 normalized.push(comment_ch);
109 if comment_ch == '\n' {
110 break;
111 }
112 }
113 }
114 '/' if chars.peek() == Some(&'*') => {
115 normalized.push(ch);
116 normalized.push(chars.next().expect("peeked comment star"));
117 let mut previous = '\0';
118 for comment_ch in chars.by_ref() {
119 normalized.push(comment_ch);
120 if previous == '*' && comment_ch == '/' {
121 break;
122 }
123 previous = comment_ch;
124 }
125 }
126 ch if ch.is_ascii_alphabetic() || ch == '_' => {
127 let mut identifier = String::from(ch);
128 while chars
129 .peek()
130 .is_some_and(|next| next.is_ascii_alphanumeric() || *next == '_')
131 {
132 identifier.push(chars.next().expect("peeked identifier character"));
133 }
134 normalized.push_str(&identifier.to_ascii_lowercase());
138 }
139 _ => normalized.push(ch),
140 }
141 }
142 normalized
143}
144
145fn natural_join_markers(sql: &str) -> Vec<bool> {
146 let mut markers = Vec::new();
147 let mut saw_natural = false;
148 let mut chars = sql.chars().peekable();
149 while let Some(ch) = chars.next() {
150 match ch {
151 '\'' | '"' => skip_quoted(&mut chars, ch),
152 '-' if chars.peek() == Some(&'-') => {
153 chars.next();
154 for comment_ch in chars.by_ref() {
155 if comment_ch == '\n' {
156 break;
157 }
158 }
159 }
160 '/' if chars.peek() == Some(&'*') => {
161 chars.next();
162 let mut previous = '\0';
163 for comment_ch in chars.by_ref() {
164 if previous == '*' && comment_ch == '/' {
165 break;
166 }
167 previous = comment_ch;
168 }
169 }
170 ';' => saw_natural = false,
171 c if c.is_ascii_alphabetic() || c == '_' => {
172 let mut word = String::from(c);
173 while chars
174 .peek()
175 .is_some_and(|next| next.is_ascii_alphanumeric() || *next == '_')
176 {
177 word.push(chars.next().expect("peeked identifier character"));
178 }
179 match word.to_ascii_lowercase().as_str() {
180 "natural" => saw_natural = true,
181 "join" => {
182 markers.push(saw_natural);
183 saw_natural = false;
184 }
185 _ => {}
186 }
187 }
188 _ => {}
189 }
190 }
191 markers
192}
193
194fn skip_quoted(chars: &mut std::iter::Peekable<std::str::Chars<'_>>, quote: char) {
195 while let Some(ch) = chars.next() {
196 if ch == quote {
197 if chars.peek() == Some("e) {
198 chars.next();
199 } else {
200 break;
201 }
202 }
203 }
204}
205
206fn annotate_natural_joins(statements: &mut [Statement], natural_markers: Vec<bool>) -> Result<()> {
213 let supplied = natural_markers.len();
214 let mut natural_markers = natural_markers.into_iter();
215 let mut consumed = 0usize;
216 for statement in statements {
217 if let StatementKind::Select(select) = &mut statement.kind {
218 annotate_select_natural_joins(select, &mut natural_markers, &mut consumed);
219 }
220 }
221
222 if consumed != supplied {
223 return Err(ParserError::UnexpectedToken {
224 line: 0,
225 column: 0,
226 expected: format!("{supplied} NATURAL join markers, one per join"),
227 found: format!("{consumed} joins in the AST"),
228 });
229 }
230 Ok(())
231}
232
233fn annotate_select_natural_joins(
234 select: &mut Select,
235 natural_markers: &mut impl Iterator<Item = bool>,
236 consumed: &mut usize,
237) {
238 for item in &mut select.projection {
239 if let SelectItem::Expr { expr, .. } = item {
240 annotate_expr_natural_joins(expr, natural_markers, consumed);
241 }
242 }
243 for from in &mut select.from {
244 annotate_from_natural_joins(from, natural_markers, consumed);
245 }
246 if let Some(selection) = &mut select.selection {
247 annotate_expr_natural_joins(selection, natural_markers, consumed);
248 }
249 if let Some(group_by) = &mut select.group_by {
250 for expression in group_by {
251 annotate_expr_natural_joins(expression, natural_markers, consumed);
252 }
253 }
254 if let Some(having) = &mut select.having {
255 annotate_expr_natural_joins(having, natural_markers, consumed);
256 }
257 for order_by in &mut select.order_by {
258 annotate_expr_natural_joins(&mut order_by.expr, natural_markers, consumed);
259 }
260 if let Some(limit) = &mut select.limit {
261 annotate_expr_natural_joins(limit, natural_markers, consumed);
262 }
263 if let Some(offset) = &mut select.offset {
264 annotate_expr_natural_joins(offset, natural_markers, consumed);
265 }
266}
267
268fn annotate_from_natural_joins(
269 from: &mut FromItem,
270 natural_markers: &mut impl Iterator<Item = bool>,
271 consumed: &mut usize,
272) {
273 match from {
274 FromItem::Join {
275 left,
276 right,
277 natural,
278 ..
279 } => {
280 annotate_from_natural_joins(left, natural_markers, consumed);
281 if let Some(marker) = natural_markers.next() {
282 *natural |= marker;
283 *consumed += 1;
284 }
285 annotate_from_natural_joins(right, natural_markers, consumed);
286 }
287 FromItem::Derived { subquery, .. } => {
288 if let StatementKind::Select(select) = &mut subquery.kind {
289 annotate_select_natural_joins(select, natural_markers, consumed);
290 }
291 }
292 FromItem::Table { .. } => {}
293 }
294}
295
296fn annotate_expr_natural_joins(
297 expr: &mut Expr,
298 natural_markers: &mut impl Iterator<Item = bool>,
299 consumed: &mut usize,
300) {
301 match &mut expr.kind {
302 ExprKind::ScalarSubquery { subquery } | ExprKind::Exists { subquery, .. } => {
303 if let StatementKind::Select(select) = &mut subquery.kind {
304 annotate_select_natural_joins(select, natural_markers, consumed);
305 }
306 }
307 ExprKind::InSubquery { expr, subquery, .. }
308 | ExprKind::Quantified { expr, subquery, .. } => {
309 annotate_expr_natural_joins(expr, natural_markers, consumed);
310 if let StatementKind::Select(select) = &mut subquery.kind {
311 annotate_select_natural_joins(select, natural_markers, consumed);
312 }
313 }
314 ExprKind::BinaryOp { left, right, .. } => {
315 annotate_expr_natural_joins(left, natural_markers, consumed);
316 annotate_expr_natural_joins(right, natural_markers, consumed);
317 }
318 ExprKind::UnaryOp { operand, .. } | ExprKind::IsNull { expr: operand, .. } => {
319 annotate_expr_natural_joins(operand, natural_markers, consumed);
320 }
321 ExprKind::FunctionCall { args, .. } => {
322 for argument in args {
323 annotate_expr_natural_joins(argument, natural_markers, consumed);
324 }
325 }
326 ExprKind::Between {
327 expr, low, high, ..
328 } => {
329 annotate_expr_natural_joins(expr, natural_markers, consumed);
330 annotate_expr_natural_joins(low, natural_markers, consumed);
331 annotate_expr_natural_joins(high, natural_markers, consumed);
332 }
333 ExprKind::Like {
334 expr,
335 pattern,
336 escape,
337 ..
338 } => {
339 annotate_expr_natural_joins(expr, natural_markers, consumed);
340 annotate_expr_natural_joins(pattern, natural_markers, consumed);
341 if let Some(escape) = escape {
342 annotate_expr_natural_joins(escape, natural_markers, consumed);
343 }
344 }
345 ExprKind::InList { expr, list, .. } => {
346 annotate_expr_natural_joins(expr, natural_markers, consumed);
347 for item in list {
348 annotate_expr_natural_joins(item, natural_markers, consumed);
349 }
350 }
351 ExprKind::Cast { expr, .. } => {
352 annotate_expr_natural_joins(expr, natural_markers, consumed);
353 }
354 ExprKind::Literal { .. } | ExprKind::ColumnRef { .. } | ExprKind::VectorLiteral { .. } => {}
355 }
356}
357
358pub fn parse_expression_sql(sql: &str) -> Result<crate::ast::Expr> {
359 let wrapped = format!("SELECT {sql}");
360 let statements = parse_sql(&wrapped)?;
361 let Some(statement) = statements.into_iter().next() else {
362 return Err(empty_expression_error());
363 };
364 let StatementKind::Select(select) = statement.kind else {
365 return Err(empty_expression_error());
366 };
367 let Some(crate::ast::SelectItem::Expr { expr, .. }) = select.projection.into_iter().next()
368 else {
369 return Err(empty_expression_error());
370 };
371 Ok(expr)
372}
373
374fn empty_expression_error() -> ParserError {
375 ParserError::UnexpectedToken {
376 line: 0,
377 column: 0,
378 expected: "expression".to_string(),
379 found: "empty parser result".to_string(),
380 }
381}
382
383const INTERNAL_DEFECT_PREFIX: &str =
389 "internal parser defect (this is a parser bug, not invalid SQL): ";
390
391fn parser_error_from_nim(message: &str) -> ParserError {
392 if let Some(defect_message) = message.strip_prefix(INTERNAL_DEFECT_PREFIX) {
393 return ParserError::InternalParserDefect {
394 message: defect_message.to_string(),
395 };
396 }
397 let (line, column) = parse_nim_line_col(message).unwrap_or((0, 0));
398 ParserError::UnexpectedToken {
399 line,
400 column,
401 expected: "valid SQL".to_string(),
402 found: message.to_string(),
403 }
404}
405
406fn parse_nim_line_col(message: &str) -> Option<(u64, u64)> {
407 let after_line = message.strip_prefix("Parse error at line ")?;
408 let (line, rest) = after_line.split_once(", col ")?;
409 let (col, _) = rest.split_once(':')?;
410 Some((line.parse().ok()?, col.parse().ok()?))
411}