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
220struct Projection {
222 text: String,
225 agg: Option<AggCall>,
229}
230
231struct AggCall {
233 func: String,
235 arg: AggArg,
236}
237
238enum AggArg {
239 Star,
241 Field(String),
243}
244
245impl AggCall {
246 fn canonical(&self) -> String {
248 match &self.arg {
249 AggArg::Star => format!("{}(*)", self.func),
250 AggArg::Field(f) => format!("{}({f})", self.func),
251 }
252 }
253}
254
255fn build_ungrouped_aggregate(agg: &AggCall, inner: &str) -> Result<String, ParseError> {
261 match agg.func.as_str() {
262 "count" => Ok(format!("count({inner})")),
263 "sum" | "avg" | "min" | "max" => match &agg.arg {
264 AggArg::Field(f) => Ok(format!("{}({inner} {{ {f} }})", agg.func)),
265 AggArg::Star => Err(ParseError::Unsupported {
266 feature: format!("{0}(*) is not valid; {0}() needs a column", agg.func),
267 }),
268 },
269 other => Err(ParseError::Syntax {
271 message: format!("unknown aggregate function `{other}`"),
272 }),
273 }
274}
275
276impl SqlParser {
277 fn at_end(&self) -> bool {
278 self.pos >= self.toks.len()
279 }
280 fn peek(&self) -> Option<&SqlTok> {
281 self.toks.get(self.pos)
282 }
283 fn bump(&mut self) -> Option<SqlTok> {
284 let t = self.toks.get(self.pos).cloned();
285 if t.is_some() {
286 self.pos += 1;
287 }
288 t
289 }
290 fn is_kw(&self, kw: &str) -> bool {
291 matches!(self.peek(), Some(SqlTok::Word(w)) if w.eq_ignore_ascii_case(kw))
292 }
293 fn eat_kw(&mut self, kw: &str) -> bool {
294 if self.is_kw(kw) {
295 self.pos += 1;
296 true
297 } else {
298 false
299 }
300 }
301 fn expect_kw(&mut self, kw: &str) -> Result<(), ParseError> {
302 if self.eat_kw(kw) {
303 Ok(())
304 } else {
305 Err(ParseError::UnexpectedToken {
306 expected: kw.into(),
307 got: self
308 .peek()
309 .map(|t| t.display())
310 .unwrap_or_else(|| "<eof>".into()),
311 })
312 }
313 }
314 fn eat_sym(&mut self, c: char) -> bool {
315 if matches!(self.peek(), Some(SqlTok::Symbol(got)) if *got == c) {
316 self.pos += 1;
317 true
318 } else {
319 false
320 }
321 }
322 fn expect_sym(&mut self, c: char) -> Result<(), ParseError> {
323 if self.eat_sym(c) {
324 Ok(())
325 } else {
326 Err(ParseError::UnexpectedToken {
327 expected: c.to_string(),
328 got: self
329 .peek()
330 .map(|t| t.display())
331 .unwrap_or_else(|| "<eof>".into()),
332 })
333 }
334 }
335 fn expect_ident(&mut self, what: &str) -> Result<String, ParseError> {
336 match self.bump() {
337 Some(SqlTok::Word(w)) if !is_reserved_identifier(&w) => Ok(w),
338 Some(SqlTok::Word(w)) => Err(ParseError::Syntax {
339 message: format!("expected {what}, got reserved word `{w}`"),
340 }),
341 Some(t) => Err(ParseError::UnexpectedToken {
342 expected: what.into(),
343 got: t.display(),
344 }),
345 None => Err(ParseError::UnexpectedToken {
346 expected: what.into(),
347 got: "<eof>".into(),
348 }),
349 }
350 }
351
352 fn statement(&mut self) -> Result<String, ParseError> {
353 if self.is_kw("select") {
354 self.select()
355 } else if self.is_kw("insert") {
356 self.insert()
357 } else if self.is_kw("update") {
358 self.update()
359 } else if self.is_kw("delete") {
360 self.delete()
361 } else if self.is_kw("create") {
362 self.create()
363 } else if self.is_kw("drop") {
364 self.drop_stmt()
365 } else if self.is_kw("alter") {
366 self.alter()
367 } else if self.eat_kw("begin") {
368 let _ = self.eat_kw("transaction");
369 Ok("begin".into())
370 } else if self.eat_kw("commit") {
371 Ok("commit".into())
372 } else if self.eat_kw("rollback") {
373 Ok("rollback".into())
374 } else {
375 Err(ParseError::UnexpectedToken {
376 expected: "SQL statement".into(),
377 got: self
378 .peek()
379 .map(|t| t.display())
380 .unwrap_or_else(|| "<eof>".into()),
381 })
382 }
383 }
384
385 fn select(&mut self) -> Result<String, ParseError> {
386 self.expect_kw("select")?;
387 let distinct = self.eat_kw("distinct");
388 let projection = self.projection_list()?;
389 self.expect_kw("from")?;
390 let source = self.table_ref()?;
391 let mut joins = Vec::new();
392 while self.starts_join() {
393 joins.push(self.join_clause()?);
394 }
395 let filter = if self.eat_kw("where") {
396 Some(self.expr_until(&["group", "having", "order", "limit", "offset"])?)
397 } else {
398 None
399 };
400 let group = if self.eat_kw("group") {
401 self.expect_kw("by")?;
402 Some(self.field_list_until(&["having", "order", "limit", "offset"])?)
403 } else {
404 None
405 };
406 let having = if self.eat_kw("having") {
407 Some(self.expr_until(&["order", "limit", "offset"])?)
408 } else {
409 None
410 };
411 let order = if self.eat_kw("order") {
412 self.expect_kw("by")?;
413 Some(self.order_list_until(&["limit", "offset"])?)
414 } else {
415 None
416 };
417 let limit = if self.eat_kw("limit") {
418 Some(self.expr_until(&["offset"])?)
419 } else {
420 None
421 };
422 let offset = if self.eat_kw("offset") {
423 Some(self.expr_until(&[])?)
424 } else {
425 None
426 };
427
428 let has_joins = !joins.is_empty();
429 let has_group = group.is_some();
430
431 let mut out = source;
432 for j in joins {
433 out.push(' ');
434 out.push_str(&j);
435 }
436 if distinct {
437 out.push_str(" distinct");
438 }
439 if let Some(f) = filter {
440 out.push_str(" filter ");
441 out.push_str(&f);
442 }
443 if let Some(keys) = group {
444 out.push_str(" group ");
445 out.push_str(&keys.join(", "));
446 if let Some(h) = having {
447 out.push_str(" having ");
448 out.push_str(&h);
449 }
450 } else if having.is_some() {
451 return Err(ParseError::Syntax {
452 message: "HAVING requires GROUP BY".into(),
453 });
454 }
455 if let Some(o) = order {
456 out.push_str(" order ");
457 out.push_str(&o);
458 }
459 if let Some(l) = limit {
460 out.push_str(" limit ");
461 out.push_str(&l);
462 }
463 if let Some(o) = offset {
464 out.push_str(" offset ");
465 out.push_str(&o);
466 }
467 if let Some(items) = projection {
468 if !has_group && items.iter().any(|p| p.agg.is_some()) {
473 if has_joins || distinct {
474 return Err(ParseError::Unsupported {
475 feature: "aggregates over a join or with DISTINCT and no GROUP BY are not supported by the SQL frontend".into(),
476 });
477 }
478 if items.len() != 1 {
479 return Err(ParseError::Unsupported {
480 feature: "multiple aggregates, or an aggregate mixed with plain columns, without GROUP BY are not supported; aggregate a single expression or add GROUP BY".into(),
481 });
482 }
483 let agg = items.into_iter().next().unwrap().agg.unwrap();
485 return build_ungrouped_aggregate(&agg, &out);
486 }
487 out.push_str(" { ");
488 out.push_str(
489 &items
490 .iter()
491 .map(|p| p.text.as_str())
492 .collect::<Vec<_>>()
493 .join(", "),
494 );
495 out.push_str(" }");
496 }
497 Ok(out)
498 }
499
500 fn projection_list(&mut self) -> Result<Option<Vec<Projection>>, ParseError> {
501 if self.eat_sym('*') {
502 return Ok(None);
503 }
504 let mut fields = Vec::new();
505 loop {
506 let (expr, agg) = match self.try_aggregate()? {
511 Some(a) => (a.canonical(), Some(a)),
512 None => (self.expr_until(&["from", "as"])?, None),
513 };
514 let text = if self.eat_kw("as") {
515 let alias = self.expect_ident("projection alias")?;
516 format!("{alias}: {expr}")
517 } else {
518 expr
519 };
520 fields.push(Projection { text, agg });
521 if !self.eat_sym(',') {
522 break;
523 }
524 }
525 Ok(Some(fields))
526 }
527
528 fn try_aggregate(&mut self) -> Result<Option<AggCall>, ParseError> {
533 let Some(SqlTok::Word(w)) = self.peek().cloned() else {
534 return Ok(None);
535 };
536 let func = w.to_ascii_lowercase();
537 if !matches!(func.as_str(), "count" | "sum" | "avg" | "min" | "max") {
538 return Ok(None);
539 }
540 let save = self.pos;
541 self.pos += 1; if !self.eat_sym('(') {
543 self.pos = save;
544 return Ok(None);
545 }
546 let arg = if func == "count" && self.eat_sym('*') {
547 AggArg::Star
548 } else if func == "count" && self.is_kw("distinct") {
549 self.pos = save;
552 return Ok(None);
553 } else {
554 AggArg::Field(self.expr_bp(0, &[])?)
555 };
556 if self.eat_sym(')')
559 && (matches!(self.peek(), Some(SqlTok::Symbol(',')))
560 || self.is_kw("as")
561 || self.is_kw("from"))
562 {
563 Ok(Some(AggCall { func, arg }))
564 } else {
565 self.pos = save;
566 Ok(None)
567 }
568 }
569
570 fn table_ref(&mut self) -> Result<String, ParseError> {
571 let table = self.expect_ident("table name")?;
572 let has_alias = self.eat_kw("as")
573 || matches!(self.peek(), Some(SqlTok::Word(w)) if !is_clause_kw(w) && !is_join_modifier(w));
574 if has_alias {
575 let alias = self.expect_ident("table alias")?;
576 Ok(format!("{table} as {alias}"))
577 } else {
578 Ok(table)
579 }
580 }
581
582 fn starts_join(&self) -> bool {
583 self.is_kw("join")
584 || self.is_kw("inner")
585 || self.is_kw("left")
586 || self.is_kw("right")
587 || self.is_kw("cross")
588 }
589
590 fn join_clause(&mut self) -> Result<String, ParseError> {
591 let kind = if self.eat_kw("inner") {
592 self.expect_kw("join")?;
593 "inner join"
594 } else if self.eat_kw("left") {
595 let _ = self.eat_kw("outer");
596 self.expect_kw("join")?;
597 "left join"
598 } else if self.eat_kw("right") {
599 let _ = self.eat_kw("outer");
600 self.expect_kw("join")?;
601 "right join"
602 } else if self.eat_kw("cross") {
603 self.expect_kw("join")?;
604 "cross join"
605 } else {
606 self.expect_kw("join")?;
607 "inner join"
608 };
609 let table = self.table_ref()?;
610 if kind == "cross join" {
611 return Ok(format!("{kind} {table}"));
612 }
613 self.expect_kw("on")?;
614 let on = self.expr_until(&[
615 "join", "inner", "left", "right", "cross", "where", "group", "having", "order",
616 "limit", "offset",
617 ])?;
618 Ok(format!("{kind} {table} on {on}"))
619 }
620
621 fn insert(&mut self) -> Result<String, ParseError> {
622 self.expect_kw("insert")?;
623 self.expect_kw("into")?;
624 let table = self.expect_ident("table name")?;
625 self.expect_sym('(')?;
626 let mut cols = Vec::new();
627 loop {
628 cols.push(self.expect_ident("column name")?);
629 if !self.eat_sym(',') {
630 break;
631 }
632 }
633 self.expect_sym(')')?;
634 self.expect_kw("values")?;
635 let mut rows = Vec::new();
636 loop {
637 self.expect_sym('(')?;
638 let mut vals = Vec::new();
639 loop {
640 vals.push(self.expr_until(&[])?);
641 if !self.eat_sym(',') {
642 break;
643 }
644 }
645 self.expect_sym(')')?;
646 if vals.len() != cols.len() {
647 return Err(ParseError::Syntax {
648 message: format!(
649 "INSERT has {} column(s) but {} value(s)",
650 cols.len(),
651 vals.len()
652 ),
653 });
654 }
655 let assigns = cols
656 .iter()
657 .zip(vals)
658 .map(|(c, v)| format!("{c} := {v}"))
659 .collect::<Vec<_>>();
660 rows.push(format!("{{ {} }}", assigns.join(", ")));
661 if !self.eat_sym(',') {
662 break;
663 }
664 }
665 let mut out = format!("insert {table} {}", rows.join(", "));
666 if self.returning_clause()? {
667 out.push_str(" returning");
668 }
669 Ok(out)
670 }
671
672 fn update(&mut self) -> Result<String, ParseError> {
673 self.expect_kw("update")?;
674 let table = self.expect_ident("table name")?;
675 self.expect_kw("set")?;
676 let assigns = self.assignment_list_until(&["where", "returning"])?;
677 let filter = if self.eat_kw("where") {
678 Some(self.expr_until(&["returning"])?)
679 } else {
680 None
681 };
682 let mut out = table;
683 if let Some(f) = filter {
684 out.push_str(" filter ");
685 out.push_str(&f);
686 }
687 out.push_str(" update { ");
688 out.push_str(&assigns.join(", "));
689 out.push_str(" }");
690 if self.returning_clause()? {
691 out.push_str(" returning");
692 }
693 Ok(out)
694 }
695
696 fn delete(&mut self) -> Result<String, ParseError> {
697 self.expect_kw("delete")?;
698 self.expect_kw("from")?;
699 let table = self.expect_ident("table name")?;
700 let filter = if self.eat_kw("where") {
701 Some(self.expr_until(&["returning"])?)
702 } else {
703 None
704 };
705 let mut out = table;
706 if let Some(f) = filter {
707 out.push_str(" filter ");
708 out.push_str(&f);
709 }
710 out.push_str(" delete");
711 if self.returning_clause()? {
712 out.push_str(" returning");
713 }
714 Ok(out)
715 }
716
717 fn default_literal(&mut self) -> Result<String, ParseError> {
721 match self.bump() {
722 Some(SqlTok::Number(n)) => Ok(n),
723 Some(SqlTok::String(s)) => Ok(quote_powql_string(&s)),
724 Some(SqlTok::Word(w))
725 if w.eq_ignore_ascii_case("true") || w.eq_ignore_ascii_case("false") =>
726 {
727 Ok(w.to_ascii_lowercase())
728 }
729 other => Err(ParseError::Syntax {
730 message: format!(
731 "DEFAULT requires a literal value, got {}",
732 other.map(|t| t.display()).unwrap_or_else(|| "<eof>".into())
733 ),
734 }),
735 }
736 }
737
738 fn returning_clause(&mut self) -> Result<bool, ParseError> {
743 if !self.eat_kw("returning") {
744 return Ok(false);
745 }
746 if !self.eat_sym('*') {
747 return Err(ParseError::Syntax {
748 message: "RETURNING currently supports only `RETURNING *` \
749 (column projection is not yet supported)"
750 .into(),
751 });
752 }
753 Ok(true)
754 }
755
756 fn create(&mut self) -> Result<String, ParseError> {
757 self.expect_kw("create")?;
758 if self.eat_kw("table") {
759 let table = self.expect_ident("table name")?;
760 self.expect_sym('(')?;
761 let mut fields = Vec::new();
762 while !self.eat_sym(')') {
763 if self.is_kw("primary") || self.is_kw("foreign") || self.is_kw("constraint") {
764 return Err(ParseError::Unsupported { feature: "SQL table constraints are not supported; declare UNIQUE columns or add indexes explicitly".into() });
765 }
766 let name = self.expect_ident("column name")?;
767 let ty = self.sql_type()?;
768 let mut required = false;
769 let mut unique = false;
770 let mut auto = false;
771 let mut default: Option<String> = None;
772 loop {
773 if self.eat_kw("not") {
774 self.expect_kw("null")?;
775 required = true;
776 } else if self.eat_kw("unique") {
777 unique = true;
778 } else if self.eat_kw("autoincrement") || self.eat_kw("auto_increment") {
779 auto = true;
780 } else if self.eat_kw("default") {
781 default = Some(self.default_literal()?);
782 } else if self.eat_kw("null") {
783 } else {
784 break;
785 }
786 }
787 let mut mods = Vec::new();
788 if required {
789 mods.push("required");
790 }
791 if unique {
792 mods.push("unique");
793 }
794 if auto {
795 mods.push("auto");
796 }
797 let prefix = if mods.is_empty() {
798 String::new()
799 } else {
800 format!("{} ", mods.join(" "))
801 };
802 let suffix = match default {
803 Some(lit) => format!(" default {lit}"),
804 None => String::new(),
805 };
806 fields.push(format!("{prefix}{name}: {ty}{suffix}"));
807 let _ = self.eat_sym(',');
808 }
809 return Ok(format!("type {table} {{ {} }}", fields.join(", ")));
810 }
811 let unique = self.eat_kw("unique");
812 self.expect_kw("index")?;
813 let _idx = self.expect_ident("index name")?;
814 self.expect_kw("on")?;
815 let table = self.expect_ident("table name")?;
816 self.expect_sym('(')?;
817 let col = self.expect_ident("column name")?;
818 self.expect_sym(')')?;
819 Ok(if unique {
820 format!("alter {table} add unique .{col}")
821 } else {
822 format!("alter {table} add index .{col}")
823 })
824 }
825
826 fn drop_stmt(&mut self) -> Result<String, ParseError> {
827 self.expect_kw("drop")?;
828 if self.eat_kw("table") {
829 let table = self.expect_ident("table name")?;
830 Ok(format!("drop {table}"))
831 } else if self.eat_kw("view") {
832 let view = self.expect_ident("view name")?;
833 Ok(format!("drop view {view}"))
834 } else {
835 Err(ParseError::UnexpectedToken {
836 expected: "TABLE or VIEW".into(),
837 got: self
838 .peek()
839 .map(|t| t.display())
840 .unwrap_or_else(|| "<eof>".into()),
841 })
842 }
843 }
844
845 fn alter(&mut self) -> Result<String, ParseError> {
846 self.expect_kw("alter")?;
847 self.expect_kw("table")?;
848 let table = self.expect_ident("table name")?;
849 if self.eat_kw("add") {
850 let _ = self.eat_kw("column");
851 let name = self.expect_ident("column name")?;
852 let ty = self.sql_type()?;
853 let mut required = false;
854 if self.eat_kw("not") {
855 self.expect_kw("null")?;
856 required = true;
857 }
858 let prefix = if required { "required " } else { "" };
859 Ok(format!("alter {table} add column {prefix}{name}: {ty}"))
860 } else if self.eat_kw("drop") {
861 let _ = self.eat_kw("column");
862 let name = self.expect_ident("column name")?;
863 Ok(format!("alter {table} drop column {name}"))
864 } else {
865 Err(ParseError::UnexpectedToken {
866 expected: "ADD or DROP".into(),
867 got: self
868 .peek()
869 .map(|t| t.display())
870 .unwrap_or_else(|| "<eof>".into()),
871 })
872 }
873 }
874
875 fn sql_type(&mut self) -> Result<String, ParseError> {
876 let raw = self.expect_ident("type name")?;
877 if self.eat_sym('(') {
879 while !self.eat_sym(')') {
880 if self.at_end() {
881 return Err(ParseError::Syntax {
882 message: "unterminated SQL type length".into(),
883 });
884 }
885 self.bump();
886 }
887 }
888 let ty = match raw.to_ascii_lowercase().as_str() {
889 "text" | "varchar" | "char" | "string" | "str" => "str",
890 "int" | "integer" | "bigint" | "smallint" => "int",
891 "real" | "double" | "float" | "decimal" | "numeric" => "float",
892 "bool" | "boolean" => "bool",
893 "datetime" | "timestamp" => "datetime",
894 "uuid" => "uuid",
895 "blob" | "bytes" | "bytea" => "bytes",
896 other => {
897 return Err(ParseError::Unsupported {
898 feature: format!("unsupported SQL type `{other}`"),
899 })
900 }
901 };
902 Ok(ty.into())
903 }
904
905 fn assignment_list_until(&mut self, stop: &[&str]) -> Result<Vec<String>, ParseError> {
906 let mut out = Vec::new();
907 loop {
908 let name = self.expect_ident("column name")?;
909 match self.bump() {
910 Some(SqlTok::Op(op)) if op == "=" => {}
911 Some(t) => {
912 return Err(ParseError::UnexpectedToken {
913 expected: "=".into(),
914 got: t.display(),
915 })
916 }
917 None => {
918 return Err(ParseError::UnexpectedToken {
919 expected: "=".into(),
920 got: "<eof>".into(),
921 })
922 }
923 }
924 let v = self.expr_until(stop)?;
925 out.push(format!("{name} := {v}"));
926 if !self.eat_sym(',') {
927 break;
928 }
929 }
930 Ok(out)
931 }
932
933 fn field_list_until(&mut self, stop: &[&str]) -> Result<Vec<String>, ParseError> {
934 let mut fields = Vec::new();
935 loop {
936 fields.push(self.field_ref()?);
937 if !self.eat_sym(',') || self.next_is_stop(stop) {
938 break;
939 }
940 }
941 Ok(fields)
942 }
943
944 fn order_list_until(&mut self, stop: &[&str]) -> Result<String, ParseError> {
945 let mut parts = Vec::new();
946 loop {
947 let mut p = self.field_ref()?;
948 if self.eat_kw("desc") {
949 p.push_str(" desc");
950 } else if self.eat_kw("asc") {
951 p.push_str(" asc");
952 }
953 parts.push(p);
954 if !self.eat_sym(',') || self.next_is_stop(stop) {
955 break;
956 }
957 }
958 Ok(parts.join(", "))
959 }
960
961 fn field_ref(&mut self) -> Result<String, ParseError> {
962 let first = self.expect_ident("column name")?;
963 if self.eat_sym('.') {
964 let second = self.expect_ident("qualified column name")?;
965 Ok(format!("{first}.{second}"))
966 } else {
967 Ok(format!(".{first}"))
968 }
969 }
970
971 fn expr_until(&mut self, stop: &[&str]) -> Result<String, ParseError> {
972 self.expr_bp(0, stop)
973 }
974
975 fn expr_bp(&mut self, min_bp: u8, stop: &[&str]) -> Result<String, ParseError> {
976 self.depth += 1;
980 if self.depth > MAX_SQL_NESTING_DEPTH {
981 return Err(ParseError::NestingDepthExceeded {
982 max: MAX_SQL_NESTING_DEPTH,
983 });
984 }
985 let mut lhs = if self.eat_kw("not") {
986 format!("not ({})", self.expr_bp(5, stop)?)
992 } else if self.eat_kw("exists") {
993 if self.eat_sym('(') {
994 if self.is_kw("select") {
995 return Err(ParseError::Unsupported {
996 feature:
997 "SQL EXISTS subqueries are not supported yet; use PowQL EXISTS for now"
998 .into(),
999 });
1000 }
1001 return Err(ParseError::Syntax {
1002 message: "expected subquery after EXISTS".into(),
1003 });
1004 }
1005 return Err(ParseError::Syntax {
1006 message: "expected EXISTS (...)".into(),
1007 });
1008 } else if self.eat_sym('(') {
1009 if self.is_kw("select") {
1010 return Err(ParseError::Unsupported {
1011 feature:
1012 "SQL scalar subqueries are not supported yet; use PowQL subqueries for now"
1013 .into(),
1014 });
1015 }
1016 let inner = self.expr_bp(0, stop)?;
1017 self.expect_sym(')')?;
1018 format!("({inner})")
1019 } else {
1020 self.primary_expr()?
1021 };
1022
1023 loop {
1024 if self.next_is_stop(stop)
1025 || self.at_end()
1026 || matches!(self.peek(), Some(SqlTok::Symbol(')' | ',')))
1027 {
1028 break;
1029 }
1030 if self.eat_kw("is") {
1031 let not = self.eat_kw("not");
1032 self.expect_kw("null")?;
1033 lhs = if not {
1034 format!("{lhs} != null")
1035 } else {
1036 format!("{lhs} = null")
1037 };
1038 continue;
1039 }
1040 if self.eat_kw("not") {
1041 if self.eat_kw("in") {
1042 return Err(ParseError::Unsupported {
1043 feature:
1044 "SQL IN lists/subqueries are not supported yet in the SQL frontend"
1045 .into(),
1046 });
1047 }
1048 if self.eat_kw("like") {
1049 let rhs = self.expr_bp(6, stop)?;
1050 lhs = format!("{lhs} not like {rhs}");
1051 continue;
1052 }
1053 if self.eat_kw("between") {
1054 return Err(ParseError::Unsupported {
1055 feature: "SQL BETWEEN is not supported yet in the SQL frontend".into(),
1056 });
1057 }
1058 return Err(ParseError::UnexpectedToken {
1059 expected: "IN, LIKE, or BETWEEN after NOT".into(),
1060 got: self
1061 .peek()
1062 .map(|t| t.display())
1063 .unwrap_or_else(|| "<eof>".into()),
1064 });
1065 }
1066 if self.eat_kw("in") {
1067 return Err(ParseError::Unsupported {
1068 feature: "SQL IN lists/subqueries are not supported yet in the SQL frontend"
1069 .into(),
1070 });
1071 }
1072 if self.eat_kw("between") {
1073 return Err(ParseError::Unsupported {
1074 feature: "SQL BETWEEN is not supported yet in the SQL frontend".into(),
1075 });
1076 }
1077 if self.eat_kw("like") {
1078 let (l_bp, r_bp) = (5, 6);
1079 if l_bp < min_bp {
1080 self.pos -= 1;
1081 break;
1082 }
1083 let rhs = self.expr_bp(r_bp, stop)?;
1084 lhs = format!("{lhs} like {rhs}");
1085 continue;
1086 }
1087
1088 let op = if self.eat_kw("or") {
1089 "or".to_string()
1090 } else if self.eat_kw("and") {
1091 "and".to_string()
1092 } else if let Some(SqlTok::Op(op)) = self.peek().cloned() {
1093 self.pos += 1;
1094 op
1095 } else if self.eat_sym('*') {
1096 "*".into()
1097 } else {
1098 break;
1099 };
1100 let (l_bp, r_bp) = infix_bp(&op).ok_or_else(|| ParseError::Syntax {
1101 message: format!("unsupported SQL operator `{op}`"),
1102 })?;
1103 if l_bp < min_bp {
1104 self.pos -= 1;
1105 break;
1106 }
1107 let rhs = self.expr_bp(r_bp, stop)?;
1108 lhs = format!("{lhs} {op} {rhs}");
1109 }
1110 self.depth -= 1;
1111 Ok(lhs)
1112 }
1113
1114 fn primary_expr(&mut self) -> Result<String, ParseError> {
1115 match self.bump() {
1116 Some(SqlTok::Word(w)) if w.eq_ignore_ascii_case("null") => Ok("null".into()),
1117 Some(SqlTok::Word(w))
1118 if w.eq_ignore_ascii_case("true") || w.eq_ignore_ascii_case("false") =>
1119 {
1120 Ok(w.to_ascii_lowercase())
1121 }
1122 Some(SqlTok::Word(w)) => {
1123 if self.eat_sym('(') {
1124 let func = w.to_ascii_lowercase();
1125 if func == "count" && self.eat_sym('*') {
1126 self.expect_sym(')')?;
1127 return Ok("count(*)".into());
1128 }
1129 let mut args = Vec::new();
1130 while !self.eat_sym(')') {
1131 args.push(self.expr_bp(0, &[])?);
1132 let _ = self.eat_sym(',');
1133 }
1134 return Ok(format!("{}({})", func, args.join(", ")));
1135 }
1136 if self.eat_sym('.') {
1137 let f = self.expect_ident("qualified column name")?;
1138 Ok(format!("{w}.{f}"))
1139 } else {
1140 Ok(format!(".{w}"))
1141 }
1142 }
1143 Some(SqlTok::Number(n)) => Ok(n),
1144 Some(SqlTok::String(s)) => Ok(quote_powql_string(&s)),
1145 Some(SqlTok::Param(p)) => Ok(format!("${p}")),
1146 Some(SqlTok::Symbol('*')) => Ok("*".into()),
1147 Some(t) => Err(ParseError::Syntax {
1148 message: format!("unexpected SQL token in expression: {}", t.display()),
1149 }),
1150 None => Err(ParseError::UnexpectedToken {
1151 expected: "expression".into(),
1152 got: "<eof>".into(),
1153 }),
1154 }
1155 }
1156
1157 fn next_is_stop(&self, stop: &[&str]) -> bool {
1158 matches!(self.peek(), Some(SqlTok::Word(w)) if stop.iter().any(|kw| w.eq_ignore_ascii_case(kw)))
1159 }
1160}
1161
1162fn infix_bp(op: &str) -> Option<(u8, u8)> {
1163 Some(match op.to_ascii_lowercase().as_str() {
1164 "or" => (1, 2),
1165 "and" => (3, 4),
1166 "=" | "!=" | "<" | ">" | "<=" | ">=" => (5, 6),
1167 "+" | "-" => (7, 8),
1168 "*" | "/" => (9, 10),
1169 _ => return None,
1170 })
1171}
1172
1173fn quote_powql_string(s: &str) -> String {
1174 format!(
1175 "\"{}\"",
1176 s.replace('\\', "\\\\")
1177 .replace('"', "\\\"")
1178 .replace('\n', "\\n")
1179 .replace('\t', "\\t")
1180 )
1181}
1182
1183fn is_clause_kw(w: &str) -> bool {
1184 matches!(
1185 w.to_ascii_lowercase().as_str(),
1186 "where"
1187 | "group"
1188 | "having"
1189 | "order"
1190 | "limit"
1191 | "offset"
1192 | "join"
1193 | "inner"
1194 | "left"
1195 | "right"
1196 | "cross"
1197 | "on"
1198 | "values"
1199 | "set"
1200 )
1201}
1202fn is_join_modifier(w: &str) -> bool {
1203 matches!(
1204 w.to_ascii_lowercase().as_str(),
1205 "join" | "inner" | "left" | "right" | "cross" | "outer"
1206 )
1207}
1208fn is_reserved_identifier(w: &str) -> bool {
1209 matches!(
1210 w.to_ascii_lowercase().as_str(),
1211 "select"
1212 | "from"
1213 | "where"
1214 | "insert"
1215 | "into"
1216 | "values"
1217 | "update"
1218 | "set"
1219 | "delete"
1220 | "create"
1221 | "table"
1222 | "drop"
1223 | "alter"
1224 )
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229 use super::*;
1230
1231 #[test]
1232 fn select_lowers_to_powql_ast() {
1233 let sql = parse_sql_with_canonical(
1234 "SELECT name, age FROM User WHERE age > 25 ORDER BY age DESC LIMIT 10",
1235 )
1236 .unwrap();
1237 assert_eq!(
1238 sql.canonical_powql,
1239 "User filter .age > 25 order .age desc limit 10 { .name, .age }"
1240 );
1241 assert_eq!(
1242 sql.statement,
1243 parser::parse("User filter .age > 25 order .age desc limit 10 { .name, .age }")
1244 .unwrap()
1245 );
1246 }
1247
1248 #[test]
1249 fn insert_update_delete_and_ddl_lower_to_existing_ast() {
1250 assert!(matches!(
1251 parse_sql("CREATE TABLE User (id INTEGER NOT NULL UNIQUE, name TEXT)").unwrap(),
1252 Statement::CreateType(_)
1253 ));
1254 assert!(matches!(
1255 parse_sql("INSERT INTO User (id, name) VALUES (1, 'Ada')").unwrap(),
1256 Statement::Insert(_)
1257 ));
1258 assert!(matches!(
1259 parse_sql("UPDATE User SET name = 'Grace' WHERE id = 1").unwrap(),
1260 Statement::UpdateQuery(_)
1261 ));
1262 assert!(matches!(
1263 parse_sql("DELETE FROM User WHERE id = 1").unwrap(),
1264 Statement::DeleteQuery(_)
1265 ));
1266 }
1267
1268 #[test]
1269 fn unsupported_sql_gets_explicit_error() {
1270 let err = parse_sql("SELECT name FROM User WHERE id IN (SELECT user_id FROM Orders)")
1271 .unwrap_err();
1272 assert!(err.to_string().contains("SQL IN"));
1273 }
1274}