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 Ok(format!("insert {table} {}", rows.join(", ")))
533 }
534
535 fn update(&mut self) -> Result<String, ParseError> {
536 self.expect_kw("update")?;
537 let table = self.expect_ident("table name")?;
538 self.expect_kw("set")?;
539 let assigns = self.assignment_list_until(&["where"])?;
540 let filter = if self.eat_kw("where") {
541 Some(self.expr_until(&[])?)
542 } else {
543 None
544 };
545 let mut out = table;
546 if let Some(f) = filter {
547 out.push_str(" filter ");
548 out.push_str(&f);
549 }
550 out.push_str(" update { ");
551 out.push_str(&assigns.join(", "));
552 out.push_str(" }");
553 Ok(out)
554 }
555
556 fn delete(&mut self) -> Result<String, ParseError> {
557 self.expect_kw("delete")?;
558 self.expect_kw("from")?;
559 let table = self.expect_ident("table name")?;
560 let filter = if self.eat_kw("where") {
561 Some(self.expr_until(&[])?)
562 } else {
563 None
564 };
565 let mut out = table;
566 if let Some(f) = filter {
567 out.push_str(" filter ");
568 out.push_str(&f);
569 }
570 out.push_str(" delete");
571 Ok(out)
572 }
573
574 fn create(&mut self) -> Result<String, ParseError> {
575 self.expect_kw("create")?;
576 if self.eat_kw("table") {
577 let table = self.expect_ident("table name")?;
578 self.expect_sym('(')?;
579 let mut fields = Vec::new();
580 while !self.eat_sym(')') {
581 if self.is_kw("primary") || self.is_kw("foreign") || self.is_kw("constraint") {
582 return Err(ParseError::Unsupported { feature: "SQL table constraints are not supported; declare UNIQUE columns or add indexes explicitly".into() });
583 }
584 let name = self.expect_ident("column name")?;
585 let ty = self.sql_type()?;
586 let mut required = false;
587 let mut unique = false;
588 loop {
589 if self.eat_kw("not") {
590 self.expect_kw("null")?;
591 required = true;
592 } else if self.eat_kw("unique") {
593 unique = true;
594 } else if self.eat_kw("null") {
595 } else {
596 break;
597 }
598 }
599 let mut mods = Vec::new();
600 if required {
601 mods.push("required");
602 }
603 if unique {
604 mods.push("unique");
605 }
606 let prefix = if mods.is_empty() {
607 String::new()
608 } else {
609 format!("{} ", mods.join(" "))
610 };
611 fields.push(format!("{prefix}{name}: {ty}"));
612 let _ = self.eat_sym(',');
613 }
614 return Ok(format!("type {table} {{ {} }}", fields.join(", ")));
615 }
616 let unique = self.eat_kw("unique");
617 self.expect_kw("index")?;
618 let _idx = self.expect_ident("index name")?;
619 self.expect_kw("on")?;
620 let table = self.expect_ident("table name")?;
621 self.expect_sym('(')?;
622 let col = self.expect_ident("column name")?;
623 self.expect_sym(')')?;
624 Ok(if unique {
625 format!("alter {table} add unique .{col}")
626 } else {
627 format!("alter {table} add index .{col}")
628 })
629 }
630
631 fn drop_stmt(&mut self) -> Result<String, ParseError> {
632 self.expect_kw("drop")?;
633 if self.eat_kw("table") {
634 let table = self.expect_ident("table name")?;
635 Ok(format!("drop {table}"))
636 } else if self.eat_kw("view") {
637 let view = self.expect_ident("view name")?;
638 Ok(format!("drop view {view}"))
639 } else {
640 Err(ParseError::UnexpectedToken {
641 expected: "TABLE or VIEW".into(),
642 got: self
643 .peek()
644 .map(|t| t.display())
645 .unwrap_or_else(|| "<eof>".into()),
646 })
647 }
648 }
649
650 fn alter(&mut self) -> Result<String, ParseError> {
651 self.expect_kw("alter")?;
652 self.expect_kw("table")?;
653 let table = self.expect_ident("table name")?;
654 if self.eat_kw("add") {
655 let _ = self.eat_kw("column");
656 let name = self.expect_ident("column name")?;
657 let ty = self.sql_type()?;
658 let mut required = false;
659 if self.eat_kw("not") {
660 self.expect_kw("null")?;
661 required = true;
662 }
663 let prefix = if required { "required " } else { "" };
664 Ok(format!("alter {table} add column {prefix}{name}: {ty}"))
665 } else if self.eat_kw("drop") {
666 let _ = self.eat_kw("column");
667 let name = self.expect_ident("column name")?;
668 Ok(format!("alter {table} drop column {name}"))
669 } else {
670 Err(ParseError::UnexpectedToken {
671 expected: "ADD or DROP".into(),
672 got: self
673 .peek()
674 .map(|t| t.display())
675 .unwrap_or_else(|| "<eof>".into()),
676 })
677 }
678 }
679
680 fn sql_type(&mut self) -> Result<String, ParseError> {
681 let raw = self.expect_ident("type name")?;
682 if self.eat_sym('(') {
684 while !self.eat_sym(')') {
685 if self.at_end() {
686 return Err(ParseError::Syntax {
687 message: "unterminated SQL type length".into(),
688 });
689 }
690 self.bump();
691 }
692 }
693 let ty = match raw.to_ascii_lowercase().as_str() {
694 "text" | "varchar" | "char" | "string" | "str" => "str",
695 "int" | "integer" | "bigint" | "smallint" => "int",
696 "real" | "double" | "float" | "decimal" | "numeric" => "float",
697 "bool" | "boolean" => "bool",
698 "datetime" | "timestamp" => "datetime",
699 "uuid" => "uuid",
700 "blob" | "bytes" => "bytes",
701 other => {
702 return Err(ParseError::Unsupported {
703 feature: format!("unsupported SQL type `{other}`"),
704 })
705 }
706 };
707 Ok(ty.into())
708 }
709
710 fn assignment_list_until(&mut self, stop: &[&str]) -> Result<Vec<String>, ParseError> {
711 let mut out = Vec::new();
712 loop {
713 let name = self.expect_ident("column name")?;
714 match self.bump() {
715 Some(SqlTok::Op(op)) if op == "=" => {}
716 Some(t) => {
717 return Err(ParseError::UnexpectedToken {
718 expected: "=".into(),
719 got: t.display(),
720 })
721 }
722 None => {
723 return Err(ParseError::UnexpectedToken {
724 expected: "=".into(),
725 got: "<eof>".into(),
726 })
727 }
728 }
729 let v = self.expr_until(stop)?;
730 out.push(format!("{name} := {v}"));
731 if !self.eat_sym(',') {
732 break;
733 }
734 }
735 Ok(out)
736 }
737
738 fn field_list_until(&mut self, stop: &[&str]) -> Result<Vec<String>, ParseError> {
739 let mut fields = Vec::new();
740 loop {
741 fields.push(self.field_ref()?);
742 if !self.eat_sym(',') || self.next_is_stop(stop) {
743 break;
744 }
745 }
746 Ok(fields)
747 }
748
749 fn order_list_until(&mut self, stop: &[&str]) -> Result<String, ParseError> {
750 let mut parts = Vec::new();
751 loop {
752 let mut p = self.field_ref()?;
753 if self.eat_kw("desc") {
754 p.push_str(" desc");
755 } else if self.eat_kw("asc") {
756 p.push_str(" asc");
757 }
758 parts.push(p);
759 if !self.eat_sym(',') || self.next_is_stop(stop) {
760 break;
761 }
762 }
763 Ok(parts.join(", "))
764 }
765
766 fn field_ref(&mut self) -> Result<String, ParseError> {
767 let first = self.expect_ident("column name")?;
768 if self.eat_sym('.') {
769 let second = self.expect_ident("qualified column name")?;
770 Ok(format!("{first}.{second}"))
771 } else {
772 Ok(format!(".{first}"))
773 }
774 }
775
776 fn expr_until(&mut self, stop: &[&str]) -> Result<String, ParseError> {
777 self.expr_bp(0, stop)
778 }
779
780 fn expr_bp(&mut self, min_bp: u8, stop: &[&str]) -> Result<String, ParseError> {
781 self.depth += 1;
785 if self.depth > MAX_SQL_NESTING_DEPTH {
786 return Err(ParseError::NestingDepthExceeded {
787 max: MAX_SQL_NESTING_DEPTH,
788 });
789 }
790 let mut lhs = if self.eat_kw("not") {
791 format!("not ({})", self.expr_bp(5, stop)?)
797 } else if self.eat_kw("exists") {
798 if self.eat_sym('(') {
799 if self.is_kw("select") {
800 return Err(ParseError::Unsupported {
801 feature:
802 "SQL EXISTS subqueries are not supported yet; use PowQL EXISTS for now"
803 .into(),
804 });
805 }
806 return Err(ParseError::Syntax {
807 message: "expected subquery after EXISTS".into(),
808 });
809 }
810 return Err(ParseError::Syntax {
811 message: "expected EXISTS (...)".into(),
812 });
813 } else if self.eat_sym('(') {
814 if self.is_kw("select") {
815 return Err(ParseError::Unsupported {
816 feature:
817 "SQL scalar subqueries are not supported yet; use PowQL subqueries for now"
818 .into(),
819 });
820 }
821 let inner = self.expr_bp(0, stop)?;
822 self.expect_sym(')')?;
823 format!("({inner})")
824 } else {
825 self.primary_expr()?
826 };
827
828 loop {
829 if self.next_is_stop(stop)
830 || self.at_end()
831 || matches!(self.peek(), Some(SqlTok::Symbol(')' | ',')))
832 {
833 break;
834 }
835 if self.eat_kw("is") {
836 let not = self.eat_kw("not");
837 self.expect_kw("null")?;
838 lhs = if not {
839 format!("{lhs} != null")
840 } else {
841 format!("{lhs} = null")
842 };
843 continue;
844 }
845 if self.eat_kw("not") {
846 if self.eat_kw("in") {
847 return Err(ParseError::Unsupported {
848 feature:
849 "SQL IN lists/subqueries are not supported yet in the SQL frontend"
850 .into(),
851 });
852 }
853 if self.eat_kw("like") {
854 let rhs = self.expr_bp(6, stop)?;
855 lhs = format!("{lhs} not like {rhs}");
856 continue;
857 }
858 if self.eat_kw("between") {
859 return Err(ParseError::Unsupported {
860 feature: "SQL BETWEEN is not supported yet in the SQL frontend".into(),
861 });
862 }
863 return Err(ParseError::UnexpectedToken {
864 expected: "IN, LIKE, or BETWEEN after NOT".into(),
865 got: self
866 .peek()
867 .map(|t| t.display())
868 .unwrap_or_else(|| "<eof>".into()),
869 });
870 }
871 if self.eat_kw("in") {
872 return Err(ParseError::Unsupported {
873 feature: "SQL IN lists/subqueries are not supported yet in the SQL frontend"
874 .into(),
875 });
876 }
877 if self.eat_kw("between") {
878 return Err(ParseError::Unsupported {
879 feature: "SQL BETWEEN is not supported yet in the SQL frontend".into(),
880 });
881 }
882 if self.eat_kw("like") {
883 let (l_bp, r_bp) = (5, 6);
884 if l_bp < min_bp {
885 self.pos -= 1;
886 break;
887 }
888 let rhs = self.expr_bp(r_bp, stop)?;
889 lhs = format!("{lhs} like {rhs}");
890 continue;
891 }
892
893 let op = if self.eat_kw("or") {
894 "or".to_string()
895 } else if self.eat_kw("and") {
896 "and".to_string()
897 } else if let Some(SqlTok::Op(op)) = self.peek().cloned() {
898 self.pos += 1;
899 op
900 } else if self.eat_sym('*') {
901 "*".into()
902 } else {
903 break;
904 };
905 let (l_bp, r_bp) = infix_bp(&op).ok_or_else(|| ParseError::Syntax {
906 message: format!("unsupported SQL operator `{op}`"),
907 })?;
908 if l_bp < min_bp {
909 self.pos -= 1;
910 break;
911 }
912 let rhs = self.expr_bp(r_bp, stop)?;
913 lhs = format!("{lhs} {op} {rhs}");
914 }
915 self.depth -= 1;
916 Ok(lhs)
917 }
918
919 fn primary_expr(&mut self) -> Result<String, ParseError> {
920 match self.bump() {
921 Some(SqlTok::Word(w)) if w.eq_ignore_ascii_case("null") => Ok("null".into()),
922 Some(SqlTok::Word(w))
923 if w.eq_ignore_ascii_case("true") || w.eq_ignore_ascii_case("false") =>
924 {
925 Ok(w.to_ascii_lowercase())
926 }
927 Some(SqlTok::Word(w)) => {
928 if self.eat_sym('(') {
929 let func = w.to_ascii_lowercase();
930 if func == "count" && self.eat_sym('*') {
931 self.expect_sym(')')?;
932 return Ok("count(*)".into());
933 }
934 let mut args = Vec::new();
935 while !self.eat_sym(')') {
936 args.push(self.expr_bp(0, &[])?);
937 let _ = self.eat_sym(',');
938 }
939 return Ok(format!("{}({})", func, args.join(", ")));
940 }
941 if self.eat_sym('.') {
942 let f = self.expect_ident("qualified column name")?;
943 Ok(format!("{w}.{f}"))
944 } else {
945 Ok(format!(".{w}"))
946 }
947 }
948 Some(SqlTok::Number(n)) => Ok(n),
949 Some(SqlTok::String(s)) => Ok(quote_powql_string(&s)),
950 Some(SqlTok::Param(p)) => Ok(format!("${p}")),
951 Some(SqlTok::Symbol('*')) => Ok("*".into()),
952 Some(t) => Err(ParseError::Syntax {
953 message: format!("unexpected SQL token in expression: {}", t.display()),
954 }),
955 None => Err(ParseError::UnexpectedToken {
956 expected: "expression".into(),
957 got: "<eof>".into(),
958 }),
959 }
960 }
961
962 fn next_is_stop(&self, stop: &[&str]) -> bool {
963 matches!(self.peek(), Some(SqlTok::Word(w)) if stop.iter().any(|kw| w.eq_ignore_ascii_case(kw)))
964 }
965}
966
967fn infix_bp(op: &str) -> Option<(u8, u8)> {
968 Some(match op.to_ascii_lowercase().as_str() {
969 "or" => (1, 2),
970 "and" => (3, 4),
971 "=" | "!=" | "<" | ">" | "<=" | ">=" => (5, 6),
972 "+" | "-" => (7, 8),
973 "*" | "/" => (9, 10),
974 _ => return None,
975 })
976}
977
978fn quote_powql_string(s: &str) -> String {
979 format!(
980 "\"{}\"",
981 s.replace('\\', "\\\\")
982 .replace('"', "\\\"")
983 .replace('\n', "\\n")
984 .replace('\t', "\\t")
985 )
986}
987
988fn is_clause_kw(w: &str) -> bool {
989 matches!(
990 w.to_ascii_lowercase().as_str(),
991 "where"
992 | "group"
993 | "having"
994 | "order"
995 | "limit"
996 | "offset"
997 | "join"
998 | "inner"
999 | "left"
1000 | "right"
1001 | "cross"
1002 | "on"
1003 | "values"
1004 | "set"
1005 )
1006}
1007fn is_join_modifier(w: &str) -> bool {
1008 matches!(
1009 w.to_ascii_lowercase().as_str(),
1010 "join" | "inner" | "left" | "right" | "cross" | "outer"
1011 )
1012}
1013fn is_reserved_identifier(w: &str) -> bool {
1014 matches!(
1015 w.to_ascii_lowercase().as_str(),
1016 "select"
1017 | "from"
1018 | "where"
1019 | "insert"
1020 | "into"
1021 | "values"
1022 | "update"
1023 | "set"
1024 | "delete"
1025 | "create"
1026 | "table"
1027 | "drop"
1028 | "alter"
1029 )
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035
1036 #[test]
1037 fn select_lowers_to_powql_ast() {
1038 let sql = parse_sql_with_canonical(
1039 "SELECT name, age FROM User WHERE age > 25 ORDER BY age DESC LIMIT 10",
1040 )
1041 .unwrap();
1042 assert_eq!(
1043 sql.canonical_powql,
1044 "User filter .age > 25 order .age desc limit 10 { .name, .age }"
1045 );
1046 assert_eq!(
1047 sql.statement,
1048 parser::parse("User filter .age > 25 order .age desc limit 10 { .name, .age }")
1049 .unwrap()
1050 );
1051 }
1052
1053 #[test]
1054 fn insert_update_delete_and_ddl_lower_to_existing_ast() {
1055 assert!(matches!(
1056 parse_sql("CREATE TABLE User (id INTEGER NOT NULL UNIQUE, name TEXT)").unwrap(),
1057 Statement::CreateType(_)
1058 ));
1059 assert!(matches!(
1060 parse_sql("INSERT INTO User (id, name) VALUES (1, 'Ada')").unwrap(),
1061 Statement::Insert(_)
1062 ));
1063 assert!(matches!(
1064 parse_sql("UPDATE User SET name = 'Grace' WHERE id = 1").unwrap(),
1065 Statement::UpdateQuery(_)
1066 ));
1067 assert!(matches!(
1068 parse_sql("DELETE FROM User WHERE id = 1").unwrap(),
1069 Statement::DeleteQuery(_)
1070 ));
1071 }
1072
1073 #[test]
1074 fn unsupported_sql_gets_explicit_error() {
1075 let err = parse_sql("SELECT name FROM User WHERE id IN (SELECT user_id FROM Orders)")
1076 .unwrap_err();
1077 assert!(err.to_string().contains("SQL IN"));
1078 }
1079}