1use crate::ast::Statement;
9use crate::parser::{self, ParseError};
10
11#[derive(Debug, Clone)]
12pub struct ParsedSql {
13 pub statement: Statement,
14 pub canonical_powql: String,
15}
16
17pub fn parse_sql(input: &str) -> Result<Statement, ParseError> {
18 parse_sql_with_canonical(input).map(|p| p.statement)
19}
20
21pub fn parse_sql_with_canonical(input: &str) -> Result<ParsedSql, ParseError> {
22 let toks = lex_sql(input)?;
23 let mut p = SqlParser {
24 toks,
25 pos: 0,
26 depth: 0,
27 };
28 let canonical_powql = p.statement()?;
29 if !p.at_end() {
30 return Err(ParseError::Syntax {
31 message: format!(
32 "unexpected trailing SQL token: {}",
33 p.peek()
34 .map(|t| t.display())
35 .unwrap_or_else(|| "<eof>".into())
36 ),
37 });
38 }
39 let statement = parser::parse(&canonical_powql)?;
40 Ok(ParsedSql {
41 statement,
42 canonical_powql,
43 })
44}
45
46#[derive(Debug, Clone, PartialEq)]
47enum SqlTok {
48 Word(String),
49 Number(String),
50 String(String),
51 Symbol(char),
52 Op(String),
53 Param(String),
54}
55
56impl SqlTok {
57 fn display(&self) -> String {
58 match self {
59 SqlTok::Word(s) => s.clone(),
60 SqlTok::Number(s) => s.clone(),
61 SqlTok::String(s) => format!("'{s}'"),
62 SqlTok::Symbol(c) => c.to_string(),
63 SqlTok::Op(s) => s.clone(),
64 SqlTok::Param(s) => format!("${s}"),
65 }
66 }
67}
68
69fn lex_sql(input: &str) -> Result<Vec<SqlTok>, ParseError> {
70 let mut out = Vec::new();
71 let chars: Vec<char> = input.chars().collect();
72 let mut i = 0usize;
73 while i < chars.len() {
74 let c = chars[i];
75 if c.is_whitespace() {
76 i += 1;
77 continue;
78 }
79 if c == '-' && chars.get(i + 1) == Some(&'-') {
80 i += 2;
81 while i < chars.len() && chars[i] != '\n' {
82 i += 1;
83 }
84 continue;
85 }
86 if c == '/' && chars.get(i + 1) == Some(&'*') {
87 i += 2;
88 while i + 1 < chars.len() && !(chars[i] == '*' && chars[i + 1] == '/') {
89 i += 1;
90 }
91 if i + 1 >= chars.len() {
92 return Err(ParseError::Lex {
93 message: "unterminated block comment".into(),
94 position: i,
95 });
96 }
97 i += 2;
98 continue;
99 }
100 if c == '\'' || c == '"' {
101 let quote = c;
102 i += 1;
103 let mut s = String::new();
104 while i < chars.len() {
105 if chars[i] == quote {
106 if quote == '\'' && chars.get(i + 1) == Some(&'\'') {
107 s.push('\'');
108 i += 2;
109 continue;
110 }
111 i += 1;
112 break;
113 }
114 if chars[i] == '\\' && i + 1 < chars.len() {
115 let next = chars[i + 1];
116 match next {
117 'n' => s.push('\n'),
118 't' => s.push('\t'),
119 other => s.push(other),
120 }
121 i += 2;
122 } else {
123 s.push(chars[i]);
124 i += 1;
125 }
126 }
127 if i > chars.len() || chars.get(i.saturating_sub(1)) != Some("e) {
128 return Err(ParseError::Lex {
129 message: "unterminated string".into(),
130 position: i,
131 });
132 }
133 out.push(SqlTok::String(s));
134 continue;
135 }
136 if c == '$' {
137 i += 1;
138 let start = i;
139 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
140 i += 1;
141 }
142 out.push(SqlTok::Param(chars[start..i].iter().collect()));
143 continue;
144 }
145 if c.is_ascii_digit() || (c == '-' && chars.get(i + 1).is_some_and(|n| n.is_ascii_digit()))
146 {
147 let start = i;
148 i += 1;
149 while i < chars.len() && chars[i].is_ascii_digit() {
150 i += 1;
151 }
152 if i < chars.len()
153 && chars[i] == '.'
154 && chars.get(i + 1).is_some_and(|n| n.is_ascii_digit())
155 {
156 i += 1;
157 while i < chars.len() && chars[i].is_ascii_digit() {
158 i += 1;
159 }
160 }
161 out.push(SqlTok::Number(chars[start..i].iter().collect()));
162 continue;
163 }
164 if c.is_alphabetic() || c == '_' {
165 let start = i;
166 i += 1;
167 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
168 i += 1;
169 }
170 out.push(SqlTok::Word(chars[start..i].iter().collect()));
171 continue;
172 }
173 if matches!(c, '(' | ')' | ',' | '*' | '.') {
174 out.push(SqlTok::Symbol(c));
175 i += 1;
176 continue;
177 }
178 if matches!(c, '=' | '<' | '>' | '!') {
179 let mut op = String::new();
180 op.push(c);
181 if matches!(chars.get(i + 1), Some('=') | Some('>')) {
182 op.push(chars[i + 1]);
183 i += 2;
184 } else {
185 i += 1;
186 }
187 if op == "<>" {
188 op = "!=".into();
189 }
190 out.push(SqlTok::Op(op));
191 continue;
192 }
193 if matches!(c, '+' | '-' | '/') {
194 out.push(SqlTok::Op(c.to_string()));
195 i += 1;
196 continue;
197 }
198 return Err(ParseError::Lex {
199 message: format!("unexpected SQL character `{c}`"),
200 position: i,
201 });
202 }
203 Ok(out)
204}
205
206const MAX_SQL_NESTING_DEPTH: usize = 64;
213
214struct SqlParser {
215 toks: Vec<SqlTok>,
216 pos: usize,
217 depth: usize,
218}
219
220impl SqlParser {
221 fn at_end(&self) -> bool {
222 self.pos >= self.toks.len()
223 }
224 fn peek(&self) -> Option<&SqlTok> {
225 self.toks.get(self.pos)
226 }
227 fn bump(&mut self) -> Option<SqlTok> {
228 let t = self.toks.get(self.pos).cloned();
229 if t.is_some() {
230 self.pos += 1;
231 }
232 t
233 }
234 fn is_kw(&self, kw: &str) -> bool {
235 matches!(self.peek(), Some(SqlTok::Word(w)) if w.eq_ignore_ascii_case(kw))
236 }
237 fn eat_kw(&mut self, kw: &str) -> bool {
238 if self.is_kw(kw) {
239 self.pos += 1;
240 true
241 } else {
242 false
243 }
244 }
245 fn expect_kw(&mut self, kw: &str) -> Result<(), ParseError> {
246 if self.eat_kw(kw) {
247 Ok(())
248 } else {
249 Err(ParseError::UnexpectedToken {
250 expected: kw.into(),
251 got: self
252 .peek()
253 .map(|t| t.display())
254 .unwrap_or_else(|| "<eof>".into()),
255 })
256 }
257 }
258 fn eat_sym(&mut self, c: char) -> bool {
259 if matches!(self.peek(), Some(SqlTok::Symbol(got)) if *got == c) {
260 self.pos += 1;
261 true
262 } else {
263 false
264 }
265 }
266 fn expect_sym(&mut self, c: char) -> Result<(), ParseError> {
267 if self.eat_sym(c) {
268 Ok(())
269 } else {
270 Err(ParseError::UnexpectedToken {
271 expected: c.to_string(),
272 got: self
273 .peek()
274 .map(|t| t.display())
275 .unwrap_or_else(|| "<eof>".into()),
276 })
277 }
278 }
279 fn expect_ident(&mut self, what: &str) -> Result<String, ParseError> {
280 match self.bump() {
281 Some(SqlTok::Word(w)) if !is_reserved_identifier(&w) => Ok(w),
282 Some(SqlTok::Word(w)) => Err(ParseError::Syntax {
283 message: format!("expected {what}, got reserved word `{w}`"),
284 }),
285 Some(t) => Err(ParseError::UnexpectedToken {
286 expected: what.into(),
287 got: t.display(),
288 }),
289 None => Err(ParseError::UnexpectedToken {
290 expected: what.into(),
291 got: "<eof>".into(),
292 }),
293 }
294 }
295
296 fn statement(&mut self) -> Result<String, ParseError> {
297 if self.is_kw("select") {
298 self.select()
299 } else if self.is_kw("insert") {
300 self.insert()
301 } else if self.is_kw("update") {
302 self.update()
303 } else if self.is_kw("delete") {
304 self.delete()
305 } else if self.is_kw("create") {
306 self.create()
307 } else if self.is_kw("drop") {
308 self.drop_stmt()
309 } else if self.is_kw("alter") {
310 self.alter()
311 } else if self.eat_kw("begin") {
312 let _ = self.eat_kw("transaction");
313 Ok("begin".into())
314 } else if self.eat_kw("commit") {
315 Ok("commit".into())
316 } else if self.eat_kw("rollback") {
317 Ok("rollback".into())
318 } else {
319 Err(ParseError::UnexpectedToken {
320 expected: "SQL statement".into(),
321 got: self
322 .peek()
323 .map(|t| t.display())
324 .unwrap_or_else(|| "<eof>".into()),
325 })
326 }
327 }
328
329 fn select(&mut self) -> Result<String, ParseError> {
330 self.expect_kw("select")?;
331 let distinct = self.eat_kw("distinct");
332 let projection = self.projection_list()?;
333 self.expect_kw("from")?;
334 let source = self.table_ref()?;
335 let mut joins = Vec::new();
336 while self.starts_join() {
337 joins.push(self.join_clause()?);
338 }
339 let filter = if self.eat_kw("where") {
340 Some(self.expr_until(&["group", "having", "order", "limit", "offset"])?)
341 } else {
342 None
343 };
344 let group = if self.eat_kw("group") {
345 self.expect_kw("by")?;
346 Some(self.field_list_until(&["having", "order", "limit", "offset"])?)
347 } else {
348 None
349 };
350 let having = if self.eat_kw("having") {
351 Some(self.expr_until(&["order", "limit", "offset"])?)
352 } else {
353 None
354 };
355 let order = if self.eat_kw("order") {
356 self.expect_kw("by")?;
357 Some(self.order_list_until(&["limit", "offset"])?)
358 } else {
359 None
360 };
361 let limit = if self.eat_kw("limit") {
362 Some(self.expr_until(&["offset"])?)
363 } else {
364 None
365 };
366 let offset = if self.eat_kw("offset") {
367 Some(self.expr_until(&[])?)
368 } else {
369 None
370 };
371
372 let mut out = source;
373 for j in joins {
374 out.push(' ');
375 out.push_str(&j);
376 }
377 if distinct {
378 out.push_str(" distinct");
379 }
380 if let Some(f) = filter {
381 out.push_str(" filter ");
382 out.push_str(&f);
383 }
384 if let Some(keys) = group {
385 out.push_str(" group ");
386 out.push_str(&keys.join(", "));
387 if let Some(h) = having {
388 out.push_str(" having ");
389 out.push_str(&h);
390 }
391 } else if having.is_some() {
392 return Err(ParseError::Syntax {
393 message: "HAVING requires GROUP BY".into(),
394 });
395 }
396 if let Some(o) = order {
397 out.push_str(" order ");
398 out.push_str(&o);
399 }
400 if let Some(l) = limit {
401 out.push_str(" limit ");
402 out.push_str(&l);
403 }
404 if let Some(o) = offset {
405 out.push_str(" offset ");
406 out.push_str(&o);
407 }
408 if let Some(p) = projection {
409 out.push_str(" { ");
410 out.push_str(&p.join(", "));
411 out.push_str(" }");
412 }
413 Ok(out)
414 }
415
416 fn projection_list(&mut self) -> Result<Option<Vec<String>>, ParseError> {
417 if self.eat_sym('*') {
418 return Ok(None);
419 }
420 let mut fields = Vec::new();
421 loop {
422 let expr = self.expr_until(&["from", "as"])?;
423 let field = if self.eat_kw("as") {
424 let alias = self.expect_ident("projection alias")?;
425 format!("{alias}: {expr}")
426 } else {
427 expr
428 };
429 fields.push(field);
430 if !self.eat_sym(',') {
431 break;
432 }
433 }
434 Ok(Some(fields))
435 }
436
437 fn table_ref(&mut self) -> Result<String, ParseError> {
438 let table = self.expect_ident("table name")?;
439 let has_alias = self.eat_kw("as")
440 || matches!(self.peek(), Some(SqlTok::Word(w)) if !is_clause_kw(w) && !is_join_modifier(w));
441 if has_alias {
442 let alias = self.expect_ident("table alias")?;
443 Ok(format!("{table} as {alias}"))
444 } else {
445 Ok(table)
446 }
447 }
448
449 fn starts_join(&self) -> bool {
450 self.is_kw("join")
451 || self.is_kw("inner")
452 || self.is_kw("left")
453 || self.is_kw("right")
454 || self.is_kw("cross")
455 }
456
457 fn join_clause(&mut self) -> Result<String, ParseError> {
458 let kind = if self.eat_kw("inner") {
459 self.expect_kw("join")?;
460 "inner join"
461 } else if self.eat_kw("left") {
462 let _ = self.eat_kw("outer");
463 self.expect_kw("join")?;
464 "left join"
465 } else if self.eat_kw("right") {
466 let _ = self.eat_kw("outer");
467 self.expect_kw("join")?;
468 "right join"
469 } else if self.eat_kw("cross") {
470 self.expect_kw("join")?;
471 "cross join"
472 } else {
473 self.expect_kw("join")?;
474 "inner join"
475 };
476 let table = self.table_ref()?;
477 if kind == "cross join" {
478 return Ok(format!("{kind} {table}"));
479 }
480 self.expect_kw("on")?;
481 let on = self.expr_until(&[
482 "join", "inner", "left", "right", "cross", "where", "group", "having", "order",
483 "limit", "offset",
484 ])?;
485 Ok(format!("{kind} {table} on {on}"))
486 }
487
488 fn insert(&mut self) -> Result<String, ParseError> {
489 self.expect_kw("insert")?;
490 self.expect_kw("into")?;
491 let table = self.expect_ident("table name")?;
492 self.expect_sym('(')?;
493 let mut cols = Vec::new();
494 loop {
495 cols.push(self.expect_ident("column name")?);
496 if !self.eat_sym(',') {
497 break;
498 }
499 }
500 self.expect_sym(')')?;
501 self.expect_kw("values")?;
502 let mut rows = Vec::new();
503 loop {
504 self.expect_sym('(')?;
505 let mut vals = Vec::new();
506 loop {
507 vals.push(self.expr_until(&[])?);
508 if !self.eat_sym(',') {
509 break;
510 }
511 }
512 self.expect_sym(')')?;
513 if vals.len() != cols.len() {
514 return Err(ParseError::Syntax {
515 message: format!(
516 "INSERT has {} column(s) but {} value(s)",
517 cols.len(),
518 vals.len()
519 ),
520 });
521 }
522 let assigns = cols
523 .iter()
524 .zip(vals)
525 .map(|(c, v)| format!("{c} := {v}"))
526 .collect::<Vec<_>>();
527 rows.push(format!("{{ {} }}", assigns.join(", ")));
528 if !self.eat_sym(',') {
529 break;
530 }
531 }
532 let mut out = format!("insert {table} {}", rows.join(", "));
533 if self.returning_clause()? {
534 out.push_str(" returning");
535 }
536 Ok(out)
537 }
538
539 fn update(&mut self) -> Result<String, ParseError> {
540 self.expect_kw("update")?;
541 let table = self.expect_ident("table name")?;
542 self.expect_kw("set")?;
543 let assigns = self.assignment_list_until(&["where", "returning"])?;
544 let filter = if self.eat_kw("where") {
545 Some(self.expr_until(&["returning"])?)
546 } else {
547 None
548 };
549 let mut out = table;
550 if let Some(f) = filter {
551 out.push_str(" filter ");
552 out.push_str(&f);
553 }
554 out.push_str(" update { ");
555 out.push_str(&assigns.join(", "));
556 out.push_str(" }");
557 if self.returning_clause()? {
558 out.push_str(" returning");
559 }
560 Ok(out)
561 }
562
563 fn delete(&mut self) -> Result<String, ParseError> {
564 self.expect_kw("delete")?;
565 self.expect_kw("from")?;
566 let table = self.expect_ident("table name")?;
567 let filter = if self.eat_kw("where") {
568 Some(self.expr_until(&["returning"])?)
569 } else {
570 None
571 };
572 let mut out = table;
573 if let Some(f) = filter {
574 out.push_str(" filter ");
575 out.push_str(&f);
576 }
577 out.push_str(" delete");
578 if self.returning_clause()? {
579 out.push_str(" returning");
580 }
581 Ok(out)
582 }
583
584 fn default_literal(&mut self) -> Result<String, ParseError> {
588 match self.bump() {
589 Some(SqlTok::Number(n)) => Ok(n),
590 Some(SqlTok::String(s)) => Ok(quote_powql_string(&s)),
591 Some(SqlTok::Word(w))
592 if w.eq_ignore_ascii_case("true") || w.eq_ignore_ascii_case("false") =>
593 {
594 Ok(w.to_ascii_lowercase())
595 }
596 other => Err(ParseError::Syntax {
597 message: format!(
598 "DEFAULT requires a literal value, got {}",
599 other.map(|t| t.display()).unwrap_or_else(|| "<eof>".into())
600 ),
601 }),
602 }
603 }
604
605 fn returning_clause(&mut self) -> Result<bool, ParseError> {
610 if !self.eat_kw("returning") {
611 return Ok(false);
612 }
613 if !self.eat_sym('*') {
614 return Err(ParseError::Syntax {
615 message: "RETURNING currently supports only `RETURNING *` \
616 (column projection is not yet supported)"
617 .into(),
618 });
619 }
620 Ok(true)
621 }
622
623 fn create(&mut self) -> Result<String, ParseError> {
624 self.expect_kw("create")?;
625 if self.eat_kw("table") {
626 let table = self.expect_ident("table name")?;
627 self.expect_sym('(')?;
628 let mut fields = Vec::new();
629 while !self.eat_sym(')') {
630 if self.is_kw("primary") || self.is_kw("foreign") || self.is_kw("constraint") {
631 return Err(ParseError::Unsupported { feature: "SQL table constraints are not supported; declare UNIQUE columns or add indexes explicitly".into() });
632 }
633 let name = self.expect_ident("column name")?;
634 let ty = self.sql_type()?;
635 let mut required = false;
636 let mut unique = false;
637 let mut auto = false;
638 let mut default: Option<String> = None;
639 loop {
640 if self.eat_kw("not") {
641 self.expect_kw("null")?;
642 required = true;
643 } else if self.eat_kw("unique") {
644 unique = true;
645 } else if self.eat_kw("autoincrement") || self.eat_kw("auto_increment") {
646 auto = true;
647 } else if self.eat_kw("default") {
648 default = Some(self.default_literal()?);
649 } else if self.eat_kw("null") {
650 } else {
651 break;
652 }
653 }
654 let mut mods = Vec::new();
655 if required {
656 mods.push("required");
657 }
658 if unique {
659 mods.push("unique");
660 }
661 if auto {
662 mods.push("auto");
663 }
664 let prefix = if mods.is_empty() {
665 String::new()
666 } else {
667 format!("{} ", mods.join(" "))
668 };
669 let suffix = match default {
670 Some(lit) => format!(" default {lit}"),
671 None => String::new(),
672 };
673 fields.push(format!("{prefix}{name}: {ty}{suffix}"));
674 let _ = self.eat_sym(',');
675 }
676 return Ok(format!("type {table} {{ {} }}", fields.join(", ")));
677 }
678 let unique = self.eat_kw("unique");
679 self.expect_kw("index")?;
680 let _idx = self.expect_ident("index name")?;
681 self.expect_kw("on")?;
682 let table = self.expect_ident("table name")?;
683 self.expect_sym('(')?;
684 let col = self.expect_ident("column name")?;
685 self.expect_sym(')')?;
686 Ok(if unique {
687 format!("alter {table} add unique .{col}")
688 } else {
689 format!("alter {table} add index .{col}")
690 })
691 }
692
693 fn drop_stmt(&mut self) -> Result<String, ParseError> {
694 self.expect_kw("drop")?;
695 if self.eat_kw("table") {
696 let table = self.expect_ident("table name")?;
697 Ok(format!("drop {table}"))
698 } else if self.eat_kw("view") {
699 let view = self.expect_ident("view name")?;
700 Ok(format!("drop view {view}"))
701 } else {
702 Err(ParseError::UnexpectedToken {
703 expected: "TABLE or VIEW".into(),
704 got: self
705 .peek()
706 .map(|t| t.display())
707 .unwrap_or_else(|| "<eof>".into()),
708 })
709 }
710 }
711
712 fn alter(&mut self) -> Result<String, ParseError> {
713 self.expect_kw("alter")?;
714 self.expect_kw("table")?;
715 let table = self.expect_ident("table name")?;
716 if self.eat_kw("add") {
717 let _ = self.eat_kw("column");
718 let name = self.expect_ident("column name")?;
719 let ty = self.sql_type()?;
720 let mut required = false;
721 if self.eat_kw("not") {
722 self.expect_kw("null")?;
723 required = true;
724 }
725 let prefix = if required { "required " } else { "" };
726 Ok(format!("alter {table} add column {prefix}{name}: {ty}"))
727 } else if self.eat_kw("drop") {
728 let _ = self.eat_kw("column");
729 let name = self.expect_ident("column name")?;
730 Ok(format!("alter {table} drop column {name}"))
731 } else {
732 Err(ParseError::UnexpectedToken {
733 expected: "ADD or DROP".into(),
734 got: self
735 .peek()
736 .map(|t| t.display())
737 .unwrap_or_else(|| "<eof>".into()),
738 })
739 }
740 }
741
742 fn sql_type(&mut self) -> Result<String, ParseError> {
743 let raw = self.expect_ident("type name")?;
744 if self.eat_sym('(') {
746 while !self.eat_sym(')') {
747 if self.at_end() {
748 return Err(ParseError::Syntax {
749 message: "unterminated SQL type length".into(),
750 });
751 }
752 self.bump();
753 }
754 }
755 let ty = match raw.to_ascii_lowercase().as_str() {
756 "text" | "varchar" | "char" | "string" | "str" => "str",
757 "int" | "integer" | "bigint" | "smallint" => "int",
758 "real" | "double" | "float" | "decimal" | "numeric" => "float",
759 "bool" | "boolean" => "bool",
760 "datetime" | "timestamp" => "datetime",
761 "uuid" => "uuid",
762 "blob" | "bytes" => "bytes",
763 other => {
764 return Err(ParseError::Unsupported {
765 feature: format!("unsupported SQL type `{other}`"),
766 })
767 }
768 };
769 Ok(ty.into())
770 }
771
772 fn assignment_list_until(&mut self, stop: &[&str]) -> Result<Vec<String>, ParseError> {
773 let mut out = Vec::new();
774 loop {
775 let name = self.expect_ident("column name")?;
776 match self.bump() {
777 Some(SqlTok::Op(op)) if op == "=" => {}
778 Some(t) => {
779 return Err(ParseError::UnexpectedToken {
780 expected: "=".into(),
781 got: t.display(),
782 })
783 }
784 None => {
785 return Err(ParseError::UnexpectedToken {
786 expected: "=".into(),
787 got: "<eof>".into(),
788 })
789 }
790 }
791 let v = self.expr_until(stop)?;
792 out.push(format!("{name} := {v}"));
793 if !self.eat_sym(',') {
794 break;
795 }
796 }
797 Ok(out)
798 }
799
800 fn field_list_until(&mut self, stop: &[&str]) -> Result<Vec<String>, ParseError> {
801 let mut fields = Vec::new();
802 loop {
803 fields.push(self.field_ref()?);
804 if !self.eat_sym(',') || self.next_is_stop(stop) {
805 break;
806 }
807 }
808 Ok(fields)
809 }
810
811 fn order_list_until(&mut self, stop: &[&str]) -> Result<String, ParseError> {
812 let mut parts = Vec::new();
813 loop {
814 let mut p = self.field_ref()?;
815 if self.eat_kw("desc") {
816 p.push_str(" desc");
817 } else if self.eat_kw("asc") {
818 p.push_str(" asc");
819 }
820 parts.push(p);
821 if !self.eat_sym(',') || self.next_is_stop(stop) {
822 break;
823 }
824 }
825 Ok(parts.join(", "))
826 }
827
828 fn field_ref(&mut self) -> Result<String, ParseError> {
829 let first = self.expect_ident("column name")?;
830 if self.eat_sym('.') {
831 let second = self.expect_ident("qualified column name")?;
832 Ok(format!("{first}.{second}"))
833 } else {
834 Ok(format!(".{first}"))
835 }
836 }
837
838 fn expr_until(&mut self, stop: &[&str]) -> Result<String, ParseError> {
839 self.expr_bp(0, stop)
840 }
841
842 fn expr_bp(&mut self, min_bp: u8, stop: &[&str]) -> Result<String, ParseError> {
843 self.depth += 1;
847 if self.depth > MAX_SQL_NESTING_DEPTH {
848 return Err(ParseError::NestingDepthExceeded {
849 max: MAX_SQL_NESTING_DEPTH,
850 });
851 }
852 let mut lhs = if self.eat_kw("not") {
853 format!("not ({})", self.expr_bp(5, stop)?)
859 } else if self.eat_kw("exists") {
860 if self.eat_sym('(') {
861 if self.is_kw("select") {
862 return Err(ParseError::Unsupported {
863 feature:
864 "SQL EXISTS subqueries are not supported yet; use PowQL EXISTS for now"
865 .into(),
866 });
867 }
868 return Err(ParseError::Syntax {
869 message: "expected subquery after EXISTS".into(),
870 });
871 }
872 return Err(ParseError::Syntax {
873 message: "expected EXISTS (...)".into(),
874 });
875 } else if self.eat_sym('(') {
876 if self.is_kw("select") {
877 return Err(ParseError::Unsupported {
878 feature:
879 "SQL scalar subqueries are not supported yet; use PowQL subqueries for now"
880 .into(),
881 });
882 }
883 let inner = self.expr_bp(0, stop)?;
884 self.expect_sym(')')?;
885 format!("({inner})")
886 } else {
887 self.primary_expr()?
888 };
889
890 loop {
891 if self.next_is_stop(stop)
892 || self.at_end()
893 || matches!(self.peek(), Some(SqlTok::Symbol(')' | ',')))
894 {
895 break;
896 }
897 if self.eat_kw("is") {
898 let not = self.eat_kw("not");
899 self.expect_kw("null")?;
900 lhs = if not {
901 format!("{lhs} != null")
902 } else {
903 format!("{lhs} = null")
904 };
905 continue;
906 }
907 if self.eat_kw("not") {
908 if self.eat_kw("in") {
909 return Err(ParseError::Unsupported {
910 feature:
911 "SQL IN lists/subqueries are not supported yet in the SQL frontend"
912 .into(),
913 });
914 }
915 if self.eat_kw("like") {
916 let rhs = self.expr_bp(6, stop)?;
917 lhs = format!("{lhs} not like {rhs}");
918 continue;
919 }
920 if self.eat_kw("between") {
921 return Err(ParseError::Unsupported {
922 feature: "SQL BETWEEN is not supported yet in the SQL frontend".into(),
923 });
924 }
925 return Err(ParseError::UnexpectedToken {
926 expected: "IN, LIKE, or BETWEEN after NOT".into(),
927 got: self
928 .peek()
929 .map(|t| t.display())
930 .unwrap_or_else(|| "<eof>".into()),
931 });
932 }
933 if self.eat_kw("in") {
934 return Err(ParseError::Unsupported {
935 feature: "SQL IN lists/subqueries are not supported yet in the SQL frontend"
936 .into(),
937 });
938 }
939 if self.eat_kw("between") {
940 return Err(ParseError::Unsupported {
941 feature: "SQL BETWEEN is not supported yet in the SQL frontend".into(),
942 });
943 }
944 if self.eat_kw("like") {
945 let (l_bp, r_bp) = (5, 6);
946 if l_bp < min_bp {
947 self.pos -= 1;
948 break;
949 }
950 let rhs = self.expr_bp(r_bp, stop)?;
951 lhs = format!("{lhs} like {rhs}");
952 continue;
953 }
954
955 let op = if self.eat_kw("or") {
956 "or".to_string()
957 } else if self.eat_kw("and") {
958 "and".to_string()
959 } else if let Some(SqlTok::Op(op)) = self.peek().cloned() {
960 self.pos += 1;
961 op
962 } else if self.eat_sym('*') {
963 "*".into()
964 } else {
965 break;
966 };
967 let (l_bp, r_bp) = infix_bp(&op).ok_or_else(|| ParseError::Syntax {
968 message: format!("unsupported SQL operator `{op}`"),
969 })?;
970 if l_bp < min_bp {
971 self.pos -= 1;
972 break;
973 }
974 let rhs = self.expr_bp(r_bp, stop)?;
975 lhs = format!("{lhs} {op} {rhs}");
976 }
977 self.depth -= 1;
978 Ok(lhs)
979 }
980
981 fn primary_expr(&mut self) -> Result<String, ParseError> {
982 match self.bump() {
983 Some(SqlTok::Word(w)) if w.eq_ignore_ascii_case("null") => Ok("null".into()),
984 Some(SqlTok::Word(w))
985 if w.eq_ignore_ascii_case("true") || w.eq_ignore_ascii_case("false") =>
986 {
987 Ok(w.to_ascii_lowercase())
988 }
989 Some(SqlTok::Word(w)) => {
990 if self.eat_sym('(') {
991 let func = w.to_ascii_lowercase();
992 if func == "count" && self.eat_sym('*') {
993 self.expect_sym(')')?;
994 return Ok("count(*)".into());
995 }
996 let mut args = Vec::new();
997 while !self.eat_sym(')') {
998 args.push(self.expr_bp(0, &[])?);
999 let _ = self.eat_sym(',');
1000 }
1001 return Ok(format!("{}({})", func, args.join(", ")));
1002 }
1003 if self.eat_sym('.') {
1004 let f = self.expect_ident("qualified column name")?;
1005 Ok(format!("{w}.{f}"))
1006 } else {
1007 Ok(format!(".{w}"))
1008 }
1009 }
1010 Some(SqlTok::Number(n)) => Ok(n),
1011 Some(SqlTok::String(s)) => Ok(quote_powql_string(&s)),
1012 Some(SqlTok::Param(p)) => Ok(format!("${p}")),
1013 Some(SqlTok::Symbol('*')) => Ok("*".into()),
1014 Some(t) => Err(ParseError::Syntax {
1015 message: format!("unexpected SQL token in expression: {}", t.display()),
1016 }),
1017 None => Err(ParseError::UnexpectedToken {
1018 expected: "expression".into(),
1019 got: "<eof>".into(),
1020 }),
1021 }
1022 }
1023
1024 fn next_is_stop(&self, stop: &[&str]) -> bool {
1025 matches!(self.peek(), Some(SqlTok::Word(w)) if stop.iter().any(|kw| w.eq_ignore_ascii_case(kw)))
1026 }
1027}
1028
1029fn infix_bp(op: &str) -> Option<(u8, u8)> {
1030 Some(match op.to_ascii_lowercase().as_str() {
1031 "or" => (1, 2),
1032 "and" => (3, 4),
1033 "=" | "!=" | "<" | ">" | "<=" | ">=" => (5, 6),
1034 "+" | "-" => (7, 8),
1035 "*" | "/" => (9, 10),
1036 _ => return None,
1037 })
1038}
1039
1040fn quote_powql_string(s: &str) -> String {
1041 format!(
1042 "\"{}\"",
1043 s.replace('\\', "\\\\")
1044 .replace('"', "\\\"")
1045 .replace('\n', "\\n")
1046 .replace('\t', "\\t")
1047 )
1048}
1049
1050fn is_clause_kw(w: &str) -> bool {
1051 matches!(
1052 w.to_ascii_lowercase().as_str(),
1053 "where"
1054 | "group"
1055 | "having"
1056 | "order"
1057 | "limit"
1058 | "offset"
1059 | "join"
1060 | "inner"
1061 | "left"
1062 | "right"
1063 | "cross"
1064 | "on"
1065 | "values"
1066 | "set"
1067 )
1068}
1069fn is_join_modifier(w: &str) -> bool {
1070 matches!(
1071 w.to_ascii_lowercase().as_str(),
1072 "join" | "inner" | "left" | "right" | "cross" | "outer"
1073 )
1074}
1075fn is_reserved_identifier(w: &str) -> bool {
1076 matches!(
1077 w.to_ascii_lowercase().as_str(),
1078 "select"
1079 | "from"
1080 | "where"
1081 | "insert"
1082 | "into"
1083 | "values"
1084 | "update"
1085 | "set"
1086 | "delete"
1087 | "create"
1088 | "table"
1089 | "drop"
1090 | "alter"
1091 )
1092}
1093
1094#[cfg(test)]
1095mod tests {
1096 use super::*;
1097
1098 #[test]
1099 fn select_lowers_to_powql_ast() {
1100 let sql = parse_sql_with_canonical(
1101 "SELECT name, age FROM User WHERE age > 25 ORDER BY age DESC LIMIT 10",
1102 )
1103 .unwrap();
1104 assert_eq!(
1105 sql.canonical_powql,
1106 "User filter .age > 25 order .age desc limit 10 { .name, .age }"
1107 );
1108 assert_eq!(
1109 sql.statement,
1110 parser::parse("User filter .age > 25 order .age desc limit 10 { .name, .age }")
1111 .unwrap()
1112 );
1113 }
1114
1115 #[test]
1116 fn insert_update_delete_and_ddl_lower_to_existing_ast() {
1117 assert!(matches!(
1118 parse_sql("CREATE TABLE User (id INTEGER NOT NULL UNIQUE, name TEXT)").unwrap(),
1119 Statement::CreateType(_)
1120 ));
1121 assert!(matches!(
1122 parse_sql("INSERT INTO User (id, name) VALUES (1, 'Ada')").unwrap(),
1123 Statement::Insert(_)
1124 ));
1125 assert!(matches!(
1126 parse_sql("UPDATE User SET name = 'Grace' WHERE id = 1").unwrap(),
1127 Statement::UpdateQuery(_)
1128 ));
1129 assert!(matches!(
1130 parse_sql("DELETE FROM User WHERE id = 1").unwrap(),
1131 Statement::DeleteQuery(_)
1132 ));
1133 }
1134
1135 #[test]
1136 fn unsupported_sql_gets_explicit_error() {
1137 let err = parse_sql("SELECT name FROM User WHERE id IN (SELECT user_id FROM Orders)")
1138 .unwrap_err();
1139 assert!(err.to_string().contains("SQL IN"));
1140 }
1141}