1use alloc::boxed::Box;
13use alloc::format;
14use alloc::string::{String, ToString};
15use alloc::vec;
16use alloc::vec::Vec;
17use core::fmt;
18use core::mem;
19
20use crate::ast::{
21 BinOp, CastTarget, ColumnDef, ColumnName, ColumnTypeName, CreateIndexStatement,
22 CreatePublicationStatement, CreateSubscriptionStatement, CreateTableStatement, Expr,
23 ExtractField, FkAction, ForeignKeyConstraint, FrameBound, FrameKind, FromClause, FromJoin,
24 IndexMethod, InsertStatement, JoinKind, Literal, NullTreatment, OrderBy, PublicationScope,
25 SelectItem, SelectStatement, Statement, TableRef, UnOp, UnionKind, VecEncoding, WindowFrame,
26};
27use crate::lexer::{self, LexError, Token};
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ParseError {
31 pub message: String,
32 pub token_pos: usize,
34}
35
36impl fmt::Display for ParseError {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(
39 f,
40 "parse error at token #{}: {}",
41 self.token_pos, self.message
42 )
43 }
44}
45
46impl From<LexError> for ParseError {
47 fn from(e: LexError) -> Self {
48 Self {
49 message: format!("lex: {e}"),
50 token_pos: 0,
51 }
52 }
53}
54
55pub fn parse_statement(input: &str) -> Result<Statement, ParseError> {
58 let tokens = lexer::tokenize(input)?;
59 let mut p = Parser::new(tokens);
60 let stmt = p.parse_one_statement()?;
61 if matches!(p.peek(), Token::Semicolon) {
62 p.advance();
63 }
64 p.expect_eof()?;
65 Ok(stmt)
66}
67
68struct Parser {
69 tokens: Vec<Token>,
70 pos: usize,
71}
72
73impl Parser {
74 fn new(tokens: Vec<Token>) -> Self {
75 Self { tokens, pos: 0 }
76 }
77
78 fn peek(&self) -> &Token {
79 &self.tokens[self.pos]
81 }
82
83 fn advance(&mut self) -> Token {
84 let t = mem::replace(&mut self.tokens[self.pos], Token::Eof);
85 if self.pos + 1 < self.tokens.len() {
86 self.pos += 1;
87 }
88 t
89 }
90
91 fn err(&self, message: String) -> ParseError {
92 ParseError {
93 message,
94 token_pos: self.pos,
95 }
96 }
97
98 fn expect_eof(&self) -> Result<(), ParseError> {
99 if matches!(self.peek(), Token::Eof) {
100 Ok(())
101 } else {
102 Err(self.err(format!("expected end of input, got {:?}", self.peek())))
103 }
104 }
105
106 fn expect_ident_like(&mut self) -> Result<String, ParseError> {
107 match self.advance() {
108 Token::Ident(s) | Token::QuotedIdent(s) => Ok(s),
109 other => Err(ParseError {
110 message: format!("expected identifier, got {other:?}"),
111 token_pos: self.pos.saturating_sub(1),
112 }),
113 }
114 }
115
116 #[allow(clippy::too_many_lines)]
117 fn parse_one_statement(&mut self) -> Result<Statement, ParseError> {
118 match self.peek() {
119 Token::Select => self.parse_select_stmt(),
120 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("with") => {
124 self.advance();
125 self.parse_with_cte_then_select()
126 }
127 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("explain") => {
130 self.advance();
131 let mut analyze = false;
132 let mut suggest = false;
133 if matches!(self.peek(), Token::LParen) {
135 self.advance();
136 let opt = match self.peek().clone() {
137 Token::Ident(s) | Token::QuotedIdent(s) => s,
138 other => {
139 return Err(self.err(format!(
140 "expected option keyword inside EXPLAIN (…), got {other:?}"
141 )));
142 }
143 };
144 if !opt.eq_ignore_ascii_case("suggest") {
145 return Err(self.err(format!(
146 "unknown EXPLAIN option {opt:?}; v6.8.3 supports SUGGEST"
147 )));
148 }
149 self.advance();
150 if !matches!(self.peek(), Token::RParen) {
151 return Err(self.err(format!(
152 "expected ')' after EXPLAIN option, got {:?}",
153 self.peek()
154 )));
155 }
156 self.advance();
157 suggest = true;
158 } else if let Token::Ident(s) | Token::QuotedIdent(s) = self.peek()
159 && (s.eq_ignore_ascii_case("analyze") || s.eq_ignore_ascii_case("analyse"))
160 {
161 self.advance();
162 analyze = true;
163 }
164 let inner = self.parse_select_stmt()?;
165 let Statement::Select(s) = inner else {
166 return Err(self.err(format!("EXPLAIN body must be a SELECT, got {inner:?}")));
167 };
168 Ok(Statement::Explain(crate::ast::ExplainStatement {
169 analyze,
170 inner: Box::new(s),
171 suggest,
172 }))
173 }
174 Token::Create => self.parse_create_stmt(),
175 Token::Insert => self.parse_insert_stmt(),
176 Token::Begin => {
177 self.advance();
178 Ok(Statement::Begin)
179 }
180 Token::Commit => {
181 self.advance();
182 Ok(Statement::Commit)
183 }
184 Token::Rollback => {
185 self.advance();
186 if matches!(self.peek(), Token::To) {
190 self.advance();
191 if matches!(self.peek(), Token::Savepoint) {
192 self.advance();
193 }
194 let name = self.expect_ident_like()?;
195 Ok(Statement::RollbackToSavepoint(name))
196 } else {
197 Ok(Statement::Rollback)
198 }
199 }
200 Token::Savepoint => {
201 self.advance();
202 let name = self.expect_ident_like()?;
203 Ok(Statement::Savepoint(name))
204 }
205 Token::Release => {
206 self.advance();
207 if matches!(self.peek(), Token::Savepoint) {
210 self.advance();
211 }
212 let name = self.expect_ident_like()?;
213 Ok(Statement::ReleaseSavepoint(name))
214 }
215 Token::Show => {
216 self.advance();
217 let target = match self.advance() {
223 Token::Tables => "tables".to_string(),
224 Token::Ident(s) | Token::QuotedIdent(s) => s.to_ascii_lowercase(),
225 other => {
226 return Err(self.err(format!(
227 "expected SHOW target, got {other:?}"
228 )));
229 }
230 };
231 match target.as_str() {
232 "tables" => Ok(Statement::ShowTables),
233 "users" => Ok(Statement::ShowUsers),
234 "publications" => Ok(Statement::ShowPublications),
239 "subscriptions" => Ok(Statement::ShowSubscriptions),
241 "columns" => {
242 if !matches!(self.peek(), Token::From) {
243 return Err(self.err(format!(
244 "expected FROM after SHOW COLUMNS, got {:?}",
245 self.peek()
246 )));
247 }
248 self.advance();
249 let table = self.expect_ident_like()?;
250 Ok(Statement::ShowColumns(table))
251 }
252 other => Err(self.err(format!(
253 "unknown SHOW target {other:?}; supported: TABLES, COLUMNS, USERS, PUBLICATIONS"
254 ))),
255 }
256 }
257 Token::Drop => {
263 self.advance();
264 match self.peek() {
265 Token::Publication => {
266 self.advance();
267 let name = self.expect_ident_or_string()?;
268 Ok(Statement::DropPublication(name))
269 }
270 Token::Subscription => {
271 self.advance();
272 let name = self.expect_ident_or_string()?;
273 Ok(Statement::DropSubscription(name))
274 }
275 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("user") => {
276 self.advance();
277 let name = self.expect_ident_or_string()?;
278 Ok(Statement::DropUser(name))
279 }
280 other => Err(self.err(format!(
281 "expected USER / PUBLICATION / SUBSCRIPTION after DROP, got {other:?}"
282 ))),
283 }
284 }
285 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("update") => {
286 self.advance();
287 self.parse_update_after_keyword()
288 }
289 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("delete") => {
290 self.advance();
291 self.parse_delete_after_keyword()
292 }
293 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("alter") => {
297 self.advance();
298 self.parse_alter_after_keyword()
299 }
300 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("wait") => {
304 self.advance();
305 self.parse_wait_after_keyword()
306 }
307 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("compact") => {
318 self.advance();
319 let next = self.peek().clone();
320 let cold = match next {
321 Token::Ident(s) | Token::QuotedIdent(s) => s,
322 _ => {
323 return Err(
324 self.err(format!("expected COLD after COMPACT, got {:?}", self.peek()))
325 );
326 }
327 };
328 if !cold.eq_ignore_ascii_case("cold") {
329 return Err(self.err(format!("expected COLD after COMPACT, got {cold:?}")));
330 }
331 self.advance();
332 let next = self.peek().clone();
333 let segments = match next {
334 Token::Ident(s) | Token::QuotedIdent(s) => s,
335 _ => {
336 return Err(self.err(format!(
337 "expected SEGMENTS after COMPACT COLD, got {:?}",
338 self.peek()
339 )));
340 }
341 };
342 if !segments.eq_ignore_ascii_case("segments") {
343 return Err(self.err(format!(
344 "expected SEGMENTS after COMPACT COLD, got {segments:?}"
345 )));
346 }
347 self.advance();
348 Ok(Statement::CompactColdSegments)
349 }
350 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("analyze") => {
351 self.advance();
352 let target = match self.peek() {
353 Token::Eof | Token::Semicolon => None,
354 Token::Ident(_) | Token::QuotedIdent(_) => {
355 Some(self.expect_ident_like()?)
356 }
357 other => {
358 return Err(self.err(format!(
359 "expected table name or end of statement after ANALYZE, got {other:?}"
360 )));
361 }
362 };
363 Ok(Statement::Analyze(target))
364 }
365 other => Err(self.err(format!(
366 "expected SELECT / CREATE / DROP / INSERT / UPDATE / DELETE / ALTER / BEGIN / COMMIT / \
367 ROLLBACK / SAVEPOINT / RELEASE / SHOW at start of statement, got {other:?}"
368 ))),
369 }
370 }
371
372 fn parse_create_stmt(&mut self) -> Result<Statement, ParseError> {
373 debug_assert!(matches!(self.peek(), Token::Create));
374 self.advance();
375 match self.peek() {
376 Token::Table => self.parse_create_table_stmt_after_create(),
377 Token::Index => self.parse_create_index_stmt_after_create(),
378 Token::Publication => {
379 self.advance();
380 self.parse_create_publication_after_keyword()
381 }
382 Token::Subscription => {
383 self.advance();
384 self.parse_create_subscription_after_keyword()
385 }
386 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("user") => {
390 self.advance();
391 self.parse_create_user_after_keyword()
392 }
393 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("extension") => {
397 self.advance();
398 self.parse_create_extension_after_keyword()
399 }
400 other => Err(self.err(format!(
401 "expected TABLE / INDEX / USER / EXTENSION / PUBLICATION / SUBSCRIPTION after CREATE, got {other:?}"
402 ))),
403 }
404 }
405
406 fn parse_create_extension_after_keyword(&mut self) -> Result<Statement, ParseError> {
411 self.consume_if_not_exists();
413 let name = self.expect_ident_like()?;
414 loop {
417 match self.peek() {
418 Token::Ident(s) if s.eq_ignore_ascii_case("with") => {
419 self.advance();
420 continue;
421 }
422 Token::Ident(s) if s.eq_ignore_ascii_case("schema") => {
423 self.advance();
424 let _ = self.expect_ident_like()?;
425 continue;
426 }
427 Token::Ident(s) if s.eq_ignore_ascii_case("version") => {
428 self.advance();
429 let _ = self.advance();
431 continue;
432 }
433 Token::Ident(s) if s.eq_ignore_ascii_case("from") => {
434 self.advance();
435 let _ = self.advance();
436 continue;
437 }
438 Token::Ident(s) if s.eq_ignore_ascii_case("cascade") => {
439 self.advance();
440 continue;
441 }
442 _ => break,
443 }
444 }
445 Ok(Statement::CreateExtension(name))
446 }
447
448 fn parse_create_publication_after_keyword(&mut self) -> Result<Statement, ParseError> {
455 let name = self.expect_ident_or_string()?;
456 let scope = if matches!(self.peek(), Token::For) {
459 self.advance();
460 if matches!(self.peek(), Token::All) {
461 self.advance();
462 if !matches!(self.peek(), Token::Tables) {
463 return Err(self.err(format!(
464 "expected TABLES after FOR ALL, got {:?}",
465 self.peek()
466 )));
467 }
468 self.advance();
469 if matches!(self.peek(), Token::Except) {
470 self.advance();
471 let tables = self.parse_publication_table_list()?;
472 PublicationScope::AllTablesExcept(tables)
473 } else {
474 PublicationScope::AllTables
475 }
476 } else if matches!(self.peek(), Token::Table | Token::Tables) {
477 self.advance();
480 let tables = self.parse_publication_table_list()?;
481 PublicationScope::ForTables(tables)
482 } else {
483 return Err(self.err(format!(
484 "expected ALL TABLES or TABLE <list> after FOR, got {:?}",
485 self.peek()
486 )));
487 }
488 } else {
489 PublicationScope::AllTables
490 };
491 Ok(Statement::CreatePublication(CreatePublicationStatement {
492 name,
493 scope,
494 }))
495 }
496
497 fn parse_publication_table_list(&mut self) -> Result<Vec<String>, ParseError> {
502 let first = self.expect_ident_like()?;
503 let mut out = alloc::vec![first];
504 while matches!(self.peek(), Token::Comma) {
505 self.advance();
506 out.push(self.expect_ident_like()?);
507 }
508 Ok(out)
509 }
510
511 fn parse_create_subscription_after_keyword(&mut self) -> Result<Statement, ParseError> {
519 let name = self.expect_ident_or_string()?;
520 if !matches!(self.peek(), Token::Connection) {
521 return Err(self.err(format!(
522 "expected CONNECTION after CREATE SUBSCRIPTION <name>, got {:?}",
523 self.peek()
524 )));
525 }
526 self.advance();
527 let conn_str = self.expect_string_literal()?;
528 if !matches!(self.peek(), Token::Publication) {
529 return Err(self.err(format!(
530 "expected PUBLICATION after CONNECTION '<conn>', got {:?}",
531 self.peek()
532 )));
533 }
534 self.advance();
535 let first = self.expect_ident_like()?;
538 let mut publications = alloc::vec![first];
539 while matches!(self.peek(), Token::Comma) {
540 self.advance();
541 publications.push(self.expect_ident_like()?);
542 }
543 Ok(Statement::CreateSubscription(
544 CreateSubscriptionStatement {
545 name,
546 conn_str,
547 publications,
548 },
549 ))
550 }
551
552 fn parse_wait_after_keyword(&mut self) -> Result<Statement, ParseError> {
557 if !matches!(self.peek(), Token::For) {
561 return Err(self.err(format!(
562 "expected FOR after WAIT, got {:?}",
563 self.peek()
564 )));
565 }
566 self.advance();
567 self.expect_keyword_ident("wal")?;
568 self.expect_keyword_ident("position")?;
569 let pos = self.expect_u64_literal()?;
570 let timeout_ms = if matches!(self.peek(), Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("with"))
571 {
572 self.advance();
573 self.expect_keyword_ident("timeout")?;
574 Some(self.expect_u64_literal()?)
575 } else {
576 None
577 };
578 Ok(Statement::WaitForWalPosition { pos, timeout_ms })
579 }
580
581 fn expect_u64_literal(&mut self) -> Result<u64, ParseError> {
585 match self.advance() {
586 Token::Integer(n) if n >= 0 => Ok(n as u64),
587 Token::Integer(n) => Err(ParseError {
588 message: format!("expected non-negative integer, got {n}"),
589 token_pos: self.pos.saturating_sub(1),
590 }),
591 other => Err(ParseError {
592 message: format!("expected integer literal, got {other:?}"),
593 token_pos: self.pos.saturating_sub(1),
594 }),
595 }
596 }
597
598 fn parse_create_user_after_keyword(&mut self) -> Result<Statement, ParseError> {
602 let name = self.expect_ident_or_string()?;
603 self.expect_keyword_ident("with")?;
604 self.expect_keyword_ident("password")?;
605 let password = self.expect_string_literal()?;
606 let role = if let Token::Ident(s) | Token::QuotedIdent(s) = self.peek()
607 && s.eq_ignore_ascii_case("role")
608 {
609 self.advance();
610 self.expect_string_literal()?
611 } else {
612 "readonly".to_string()
613 };
614 Ok(Statement::CreateUser(crate::ast::CreateUserStatement {
615 name,
616 password,
617 role,
618 }))
619 }
620
621 fn parse_update_after_keyword(&mut self) -> Result<Statement, ParseError> {
624 let table = self.expect_ident_like()?;
625 self.expect_keyword_ident("set")?;
626 let mut assignments = Vec::new();
627 loop {
628 let col = self.expect_ident_like()?;
629 if !matches!(self.peek(), Token::Eq) {
630 return Err(self.err(format!(
631 "expected `=` after column name in UPDATE SET, got {:?}",
632 self.peek()
633 )));
634 }
635 self.advance();
636 let value = self.parse_expr(0)?;
637 assignments.push((col, value));
638 if matches!(self.peek(), Token::Comma) {
639 self.advance();
640 continue;
641 }
642 break;
643 }
644 let where_ = if matches!(self.peek(), Token::Where) {
645 self.advance();
646 Some(self.parse_expr(0)?)
647 } else {
648 None
649 };
650 let returning = self.parse_optional_returning()?;
651 Ok(Statement::Update(crate::ast::UpdateStatement {
652 table,
653 assignments,
654 where_,
655 returning,
656 }))
657 }
658
659 fn parse_delete_after_keyword(&mut self) -> Result<Statement, ParseError> {
662 if !matches!(self.peek(), Token::From) {
663 return Err(self.err(format!("expected FROM after DELETE, got {:?}", self.peek())));
664 }
665 self.advance();
666 let table = self.expect_ident_like()?;
667 let where_ = if matches!(self.peek(), Token::Where) {
668 self.advance();
669 Some(self.parse_expr(0)?)
670 } else {
671 None
672 };
673 let returning = self.parse_optional_returning()?;
674 Ok(Statement::Delete(crate::ast::DeleteStatement {
675 table,
676 where_,
677 returning,
678 }))
679 }
680
681 fn parse_optional_returning(&mut self) -> Result<Option<Vec<crate::ast::SelectItem>>, ParseError> {
686 let is_returning_kw = matches!(
687 self.peek(),
688 Token::Ident(s) if s.eq_ignore_ascii_case("returning")
689 );
690 if !is_returning_kw {
691 return Ok(None);
692 }
693 self.advance();
694 let mut items = Vec::new();
695 loop {
696 items.push(self.parse_select_item()?);
697 if matches!(self.peek(), Token::Comma) {
698 self.advance();
699 continue;
700 }
701 break;
702 }
703 Ok(Some(items))
704 }
705
706 fn parse_alter_after_keyword(&mut self) -> Result<Statement, ParseError> {
714 match self.advance() {
716 Token::Index => {}
717 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("index") => {}
718 Token::Table => return self.parse_alter_table_after_keyword(),
720 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("table") => {
721 return self.parse_alter_table_after_keyword();
722 }
723 other => {
724 return Err(self.err(format!("expected INDEX or TABLE after ALTER, got {other:?}")));
725 }
726 }
727 let name = self.expect_ident_like()?;
728 self.expect_keyword_ident("rebuild")?;
730 let encoding = if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("with")) {
732 self.advance();
733 if !matches!(self.peek(), Token::LParen) {
734 return Err(self.err(format!(
735 "expected '(' after WITH in ALTER INDEX REBUILD, got {:?}",
736 self.peek()
737 )));
738 }
739 self.advance();
740 self.expect_keyword_ident("encoding")?;
741 if !matches!(self.peek(), Token::Eq) {
742 return Err(self.err(format!(
743 "expected '=' after encoding in ALTER INDEX REBUILD, got {:?}",
744 self.peek()
745 )));
746 }
747 self.advance();
748 let enc_ident = match self.advance() {
749 Token::Ident(s) | Token::QuotedIdent(s) => s,
750 other => {
751 return Err(self.err(format!("expected encoding name after =, got {other:?}")));
752 }
753 };
754 let enc = match enc_ident.to_ascii_lowercase().as_str() {
755 "f32" => VecEncoding::F32,
756 "sq8" => VecEncoding::Sq8,
757 "half" => VecEncoding::F16,
758 other => {
759 return Err(self.err(format!(
760 "unknown vector encoding {other:?} in ALTER INDEX REBUILD; supported: F32, SQ8, HALF"
761 )));
762 }
763 };
764 if !matches!(self.peek(), Token::RParen) {
765 return Err(self.err(format!(
766 "expected ')' after encoding value, got {:?}",
767 self.peek()
768 )));
769 }
770 self.advance();
771 Some(enc)
772 } else {
773 None
774 };
775 Ok(Statement::AlterIndex(crate::ast::AlterIndexStatement {
776 name,
777 target: crate::ast::AlterIndexTarget::Rebuild { encoding },
778 }))
779 }
780
781 fn parse_alter_table_after_keyword(&mut self) -> Result<Statement, ParseError> {
785 let table_name = self.expect_ident_like()?;
786 match self.peek() {
790 Token::Ident(s) if s.eq_ignore_ascii_case("set") => {
791 self.advance();
792 let setting = self.expect_ident_like()?;
793 if !setting.eq_ignore_ascii_case("hot_tier_bytes") {
794 return Err(self.err(alloc::format!(
795 "ALTER TABLE SET: unknown setting {setting:?}; supported: hot_tier_bytes"
796 )));
797 }
798 if !matches!(self.peek(), Token::Eq) {
799 return Err(self.err(alloc::format!(
800 "expected '=' after hot_tier_bytes, got {:?}",
801 self.peek()
802 )));
803 }
804 self.advance();
805 let n = self.expect_u64_literal()?;
806 Ok(Statement::AlterTable(crate::ast::AlterTableStatement {
807 name: table_name,
808 target: crate::ast::AlterTableTarget::SetHotTierBytes(n),
809 }))
810 }
811 Token::Ident(s) if s.eq_ignore_ascii_case("add") => {
812 self.advance();
813 let fk = self.parse_table_level_fk()?;
816 Ok(Statement::AlterTable(crate::ast::AlterTableStatement {
817 name: table_name,
818 target: crate::ast::AlterTableTarget::AddForeignKey(fk),
819 }))
820 }
821 Token::Drop => {
822 self.advance();
823 match self.advance() {
824 Token::Ident(s) if s.eq_ignore_ascii_case("constraint") => {}
825 other => {
826 return Err(self.err(alloc::format!(
827 "expected CONSTRAINT after DROP in ALTER TABLE, got {other:?}"
828 )));
829 }
830 }
831 let cname = self.expect_ident_like()?;
832 Ok(Statement::AlterTable(crate::ast::AlterTableStatement {
833 name: table_name,
834 target: crate::ast::AlterTableTarget::DropForeignKey(cname),
835 }))
836 }
837 other => Err(self.err(alloc::format!(
838 "expected SET / ADD / DROP in ALTER TABLE, got {other:?}"
839 ))),
840 }
841 }
842
843 fn expect_keyword_ident(&mut self, kw: &str) -> Result<(), ParseError> {
845 match self.advance() {
846 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case(kw) => Ok(()),
847 other => Err(ParseError {
848 message: format!("expected {kw:?}, got {other:?}"),
849 token_pos: self.pos.saturating_sub(1),
850 }),
851 }
852 }
853
854 fn expect_ident_or_string(&mut self) -> Result<String, ParseError> {
858 match self.advance() {
859 Token::Ident(s) | Token::QuotedIdent(s) | Token::String(s) => Ok(s),
860 other => Err(ParseError {
861 message: format!("expected identifier or string, got {other:?}"),
862 token_pos: self.pos.saturating_sub(1),
863 }),
864 }
865 }
866
867 fn expect_string_literal(&mut self) -> Result<String, ParseError> {
868 match self.advance() {
869 Token::String(s) => Ok(s),
870 other => Err(ParseError {
871 message: format!("expected quoted string, got {other:?}"),
872 token_pos: self.pos.saturating_sub(1),
873 }),
874 }
875 }
876
877 fn parse_select_stmt(&mut self) -> Result<Statement, ParseError> {
878 let mut head = self.parse_bare_select()?;
883 while matches!(self.peek(), Token::Union) {
884 self.advance();
885 let kind = if matches!(self.peek(), Token::All) {
886 self.advance();
887 UnionKind::All
888 } else {
889 UnionKind::Distinct
890 };
891 let peer = self.parse_bare_select()?;
892 head.unions.push((kind, peer));
893 }
894 head.order_by = if matches!(self.peek(), Token::Order) {
895 self.advance();
896 if !matches!(self.peek(), Token::By) {
897 return Err(self.err(format!("expected BY after ORDER, got {:?}", self.peek())));
898 }
899 self.advance();
900 let mut keys = Vec::new();
903 loop {
904 let expr = self.parse_expr(0)?;
905 let desc = if matches!(self.peek(), Token::Desc) {
906 self.advance();
907 true
908 } else if matches!(self.peek(), Token::Asc) {
909 self.advance();
910 false
911 } else {
912 false
913 };
914 keys.push(OrderBy { expr, desc });
915 if matches!(self.peek(), Token::Comma) {
916 self.advance();
917 } else {
918 break;
919 }
920 }
921 keys
922 } else {
923 Vec::new()
924 };
925 head.limit = if matches!(self.peek(), Token::Limit) {
926 self.advance();
927 let n = self.expect_u32_literal("LIMIT")?;
928 Some(n)
929 } else {
930 None
931 };
932 head.offset = if matches!(self.peek(), Token::Offset) {
933 self.advance();
934 let n = self.expect_u32_literal("OFFSET")?;
935 Some(n)
936 } else {
937 None
938 };
939 Ok(Statement::Select(head))
940 }
941
942 fn expect_u32_literal(&mut self, label: &str) -> Result<u32, ParseError> {
943 match self.advance() {
944 Token::Integer(n) if n >= 0 => u32::try_from(n).map_err(|_| ParseError {
945 message: format!("{label} value too large: {n}"),
946 token_pos: self.pos.saturating_sub(1),
947 }),
948 other => Err(ParseError {
949 message: format!("expected non-negative integer after {label}, got {other:?}"),
950 token_pos: self.pos.saturating_sub(1),
951 }),
952 }
953 }
954
955 fn parse_bare_select(&mut self) -> Result<SelectStatement, ParseError> {
960 if !matches!(self.peek(), Token::Select) {
961 return Err(self.err(format!(
962 "expected SELECT to start a query block, got {:?}",
963 self.peek()
964 )));
965 }
966 self.advance();
967 let distinct = if matches!(self.peek(), Token::Distinct) {
968 self.advance();
969 true
970 } else {
971 false
972 };
973 let items = self.parse_select_list()?;
974 let from = if matches!(self.peek(), Token::From) {
975 self.advance();
976 Some(self.parse_from_clause()?)
977 } else {
978 None
979 };
980 let where_ = if matches!(self.peek(), Token::Where) {
981 self.advance();
982 Some(self.parse_expr(0)?)
983 } else {
984 None
985 };
986 let mut group_by_all = false;
987 let group_by = if matches!(self.peek(), Token::Group) {
988 self.advance();
989 if !matches!(self.peek(), Token::By) {
990 return Err(self.err(format!("expected BY after GROUP, got {:?}", self.peek())));
991 }
992 self.advance();
993 if matches!(self.peek(), Token::All) {
996 self.advance();
997 group_by_all = true;
998 None
999 } else {
1000 let mut groups = Vec::new();
1001 loop {
1002 groups.push(self.parse_expr(0)?);
1003 if matches!(self.peek(), Token::Comma) {
1004 self.advance();
1005 } else {
1006 break;
1007 }
1008 }
1009 Some(groups)
1010 }
1011 } else {
1012 None
1013 };
1014 let having = if matches!(self.peek(), Token::Having) {
1015 self.advance();
1016 Some(self.parse_expr(0)?)
1017 } else {
1018 None
1019 };
1020 Ok(SelectStatement {
1021 ctes: Vec::new(),
1022 distinct,
1023 items,
1024 from,
1025 where_,
1026 group_by,
1027 group_by_all,
1028 having,
1029 unions: Vec::new(),
1030 order_by: Vec::new(),
1031 limit: None,
1032 offset: None,
1033 })
1034 }
1035
1036 fn parse_create_table_stmt_after_create(&mut self) -> Result<Statement, ParseError> {
1037 debug_assert!(matches!(self.peek(), Token::Table));
1039 self.advance();
1040 let if_not_exists = self.consume_if_not_exists();
1041 let name = self.expect_ident_like()?;
1042 if !matches!(self.peek(), Token::LParen) {
1043 return Err(self.err(format!(
1044 "expected '(' after table name, got {:?}",
1045 self.peek()
1046 )));
1047 }
1048 self.advance();
1049 let mut columns = Vec::new();
1050 let mut foreign_keys: Vec<ForeignKeyConstraint> = Vec::new();
1051 loop {
1052 if self.peek_constraint_or_fk_start() {
1057 foreign_keys.push(self.parse_table_level_fk()?);
1058 } else {
1059 let (col, col_level_fk) = self.parse_column_def_with_fk()?;
1060 columns.push(col);
1061 if let Some(fk) = col_level_fk {
1062 foreign_keys.push(fk);
1063 }
1064 }
1065 match self.peek() {
1066 Token::Comma => {
1067 self.advance();
1068 }
1069 Token::RParen => {
1070 self.advance();
1071 break;
1072 }
1073 other => {
1074 return Err(
1075 self.err(format!("expected ',' or ')' in column list, got {other:?}"))
1076 );
1077 }
1078 }
1079 }
1080 if columns.is_empty() {
1081 return Err(self.err("CREATE TABLE requires at least one column".into()));
1082 }
1083 Ok(Statement::CreateTable(CreateTableStatement {
1084 name,
1085 columns,
1086 if_not_exists,
1087 foreign_keys,
1088 }))
1089 }
1090
1091 fn peek_constraint_or_fk_start(&self) -> bool {
1096 let is_constraint_kw = matches!(
1097 self.peek(),
1098 Token::Ident(s) if s.eq_ignore_ascii_case("constraint")
1099 );
1100 let is_foreign_kw = matches!(
1101 self.peek(),
1102 Token::Ident(s) if s.eq_ignore_ascii_case("foreign")
1103 );
1104 is_constraint_kw || is_foreign_kw
1105 }
1106
1107 fn parse_table_level_fk(&mut self) -> Result<ForeignKeyConstraint, ParseError> {
1111 let mut name: Option<String> = None;
1112 if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("constraint")) {
1113 self.advance();
1114 name = Some(self.expect_ident_like()?);
1115 }
1116 match self.advance() {
1118 Token::Ident(s) if s.eq_ignore_ascii_case("foreign") => {}
1119 other => return Err(self.err(format!("expected FOREIGN, got {other:?}"))),
1120 }
1121 match self.advance() {
1123 Token::Ident(s) if s.eq_ignore_ascii_case("key") => {}
1124 other => return Err(self.err(format!("expected KEY after FOREIGN, got {other:?}"))),
1125 }
1126 if !matches!(self.peek(), Token::LParen) {
1128 return Err(self.err(format!("expected '(' after FOREIGN KEY, got {:?}", self.peek())));
1129 }
1130 self.advance();
1131 let mut columns = Vec::new();
1132 loop {
1133 columns.push(self.expect_ident_like()?);
1134 match self.peek() {
1135 Token::Comma => {
1136 self.advance();
1137 }
1138 Token::RParen => {
1139 self.advance();
1140 break;
1141 }
1142 other => return Err(self.err(format!("expected ',' or ')' in FK column list, got {other:?}"))),
1143 }
1144 }
1145 if columns.is_empty() {
1146 return Err(self.err("FOREIGN KEY requires at least one column".into()));
1147 }
1148 let (parent_table, parent_columns, on_delete, on_update) =
1149 self.parse_references_tail(columns.len())?;
1150 Ok(ForeignKeyConstraint {
1151 name,
1152 columns,
1153 parent_table,
1154 parent_columns,
1155 on_delete,
1156 on_update,
1157 })
1158 }
1159
1160 fn parse_references_tail(
1165 &mut self,
1166 expected_arity: usize,
1167 ) -> Result<(String, Vec<String>, FkAction, FkAction), ParseError> {
1168 match self.advance() {
1169 Token::Ident(s) if s.eq_ignore_ascii_case("references") => {}
1170 other => return Err(self.err(format!("expected REFERENCES, got {other:?}"))),
1171 }
1172 let parent_table = self.expect_ident_like()?;
1173 let mut parent_columns: Vec<String> = Vec::new();
1174 if matches!(self.peek(), Token::LParen) {
1175 self.advance();
1176 loop {
1177 parent_columns.push(self.expect_ident_like()?);
1178 match self.peek() {
1179 Token::Comma => {
1180 self.advance();
1181 }
1182 Token::RParen => {
1183 self.advance();
1184 break;
1185 }
1186 other => return Err(self.err(format!("expected ',' or ')' in REFERENCES column list, got {other:?}"))),
1187 }
1188 }
1189 }
1190 if !parent_columns.is_empty() && parent_columns.len() != expected_arity {
1191 return Err(self.err(format!(
1192 "FK arity mismatch: {} local column(s) vs {} parent column(s)",
1193 expected_arity,
1194 parent_columns.len()
1195 )));
1196 }
1197 loop {
1203 if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("deferrable")) {
1204 return Err(self.err(
1205 "DEFERRABLE constraints are not supported (SPG is single-writer; \
1206 constraints are always evaluated immediately at commit)"
1207 .into(),
1208 ));
1209 }
1210 if matches!(self.peek(), Token::Not) {
1211 let look = self.tokens.get(self.pos + 1);
1212 if matches!(look, Some(Token::Ident(s)) if s.eq_ignore_ascii_case("deferrable")) {
1213 self.advance();
1216 self.advance();
1217 if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("initially"))
1219 {
1220 self.advance();
1221 match self.advance() {
1222 Token::Ident(s) if s.eq_ignore_ascii_case("immediate") => {}
1223 other => {
1224 return Err(self.err(format!(
1225 "expected IMMEDIATE after INITIALLY for NOT DEFERRABLE, \
1226 got {other:?}"
1227 )));
1228 }
1229 }
1230 }
1231 continue;
1232 }
1233 break;
1234 }
1235 break;
1236 }
1237 let mut on_delete = FkAction::Restrict;
1240 let mut on_update = FkAction::Restrict;
1241 let mut seen_on_delete = false;
1242 let mut seen_on_update = false;
1243 loop {
1244 if !matches!(self.peek(), Token::On) {
1245 break;
1246 }
1247 self.advance();
1248 let which = self.advance();
1249 let action = self.parse_fk_action()?;
1250 match which {
1251 Token::Ident(ref s) if s.eq_ignore_ascii_case("delete") => {
1252 if seen_on_delete {
1253 return Err(self.err("ON DELETE specified twice".into()));
1254 }
1255 seen_on_delete = true;
1256 on_delete = action;
1257 }
1258 Token::Ident(ref s) if s.eq_ignore_ascii_case("update") => {
1259 if seen_on_update {
1260 return Err(self.err("ON UPDATE specified twice".into()));
1261 }
1262 seen_on_update = true;
1263 on_update = action;
1264 }
1265 other => {
1266 return Err(self.err(format!(
1267 "expected DELETE or UPDATE after ON, got {other:?}"
1268 )));
1269 }
1270 }
1271 }
1272 Ok((parent_table, parent_columns, on_delete, on_update))
1273 }
1274
1275 fn parse_fk_action(&mut self) -> Result<FkAction, ParseError> {
1278 match self.advance() {
1279 Token::Ident(s) if s.eq_ignore_ascii_case("cascade") => Ok(FkAction::Cascade),
1280 Token::Ident(s) if s.eq_ignore_ascii_case("restrict") => Ok(FkAction::Restrict),
1281 Token::Ident(s) if s.eq_ignore_ascii_case("set") => {
1282 match self.advance() {
1283 Token::Null => Ok(FkAction::SetNull),
1284 Token::Default => Ok(FkAction::SetDefault),
1285 other => Err(self.err(format!(
1286 "expected NULL or DEFAULT after SET in FK action, got {other:?}"
1287 ))),
1288 }
1289 }
1290 Token::Ident(s) if s.eq_ignore_ascii_case("no") => {
1291 match self.advance() {
1292 Token::Ident(s) if s.eq_ignore_ascii_case("action") => Ok(FkAction::NoAction),
1293 other => Err(self.err(format!(
1294 "expected ACTION after NO in FK action, got {other:?}"
1295 ))),
1296 }
1297 }
1298 other => Err(self.err(format!(
1299 "expected CASCADE | RESTRICT | SET NULL | SET DEFAULT | NO ACTION, got {other:?}"
1300 ))),
1301 }
1302 }
1303
1304 fn consume_if_not_exists(&mut self) -> bool {
1307 let looks_like_if = matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("if"));
1311 if !looks_like_if {
1312 return false;
1313 }
1314 if !matches!(self.tokens.get(self.pos + 1), Some(Token::Not)) {
1317 return false;
1318 }
1319 if !matches!(
1320 self.tokens.get(self.pos + 2),
1321 Some(Token::Ident(s)) if s.eq_ignore_ascii_case("exists")
1322 ) {
1323 return false;
1324 }
1325 self.advance(); self.advance(); self.advance(); true
1329 }
1330
1331 fn consume_optional_index_column_qualifiers(&mut self) {
1337 loop {
1338 match self.peek() {
1339 Token::Asc | Token::Desc => {
1340 self.advance();
1341 }
1342 Token::Ident(s) if s.eq_ignore_ascii_case("nulls") => {
1343 let look = self.tokens.get(self.pos + 1);
1344 if matches!(
1345 look,
1346 Some(Token::Ident(k)) if k.eq_ignore_ascii_case("first")
1347 || k.eq_ignore_ascii_case("last")
1348 ) {
1349 self.advance();
1350 self.advance();
1351 } else {
1352 break;
1353 }
1354 }
1355 _ => break,
1356 }
1357 }
1358 }
1359
1360 fn parse_create_index_stmt_after_create(&mut self) -> Result<Statement, ParseError> {
1361 debug_assert!(matches!(self.peek(), Token::Index));
1363 self.advance();
1364 let if_not_exists = self.consume_if_not_exists();
1365 let name = self.expect_ident_like()?;
1366 if !matches!(self.peek(), Token::On) {
1367 return Err(self.err(format!(
1368 "expected ON after CREATE INDEX <name>, got {:?}",
1369 self.peek()
1370 )));
1371 }
1372 self.advance();
1373 let table = self.expect_ident_like()?;
1374 let method = if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("using")) {
1379 self.advance();
1380 let m = self.expect_ident_like()?;
1381 match m.to_ascii_lowercase().as_str() {
1382 "hnsw" => IndexMethod::Hnsw,
1383 "btree" => IndexMethod::BTree,
1384 "brin" => IndexMethod::Brin,
1385 other => {
1386 return Err(self.err(alloc::format!(
1387 "unknown index method {other:?}; supported: hnsw, btree, brin"
1388 )));
1389 }
1390 }
1391 } else {
1392 IndexMethod::BTree
1393 };
1394 if !matches!(self.peek(), Token::LParen) {
1395 return Err(self.err(format!(
1396 "expected '(' before indexed column, got {:?}",
1397 self.peek()
1398 )));
1399 }
1400 self.advance();
1401 let (column, expression): (String, Option<Expr>) = match self.peek().clone() {
1410 Token::Ident(s) | Token::QuotedIdent(s)
1411 if matches!(self.tokens.get(self.pos + 1), Some(Token::RParen)) =>
1412 {
1413 self.advance();
1414 (s, None)
1415 }
1416 Token::Ident(_) | Token::QuotedIdent(_) => {
1417 let key_expr = self.parse_expr(0)?;
1418 let primary = extract_first_column(&key_expr).ok_or_else(|| {
1419 self.err(
1420 "expression index key must reference at least one column".into(),
1421 )
1422 })?;
1423 (primary, Some(key_expr))
1424 }
1425 other => {
1426 return Err(self.err(format!(
1427 "expected column ident or expression, got {other:?}"
1428 )));
1429 }
1430 };
1431 let mut extra_columns: Vec<String> = Vec::new();
1440 self.consume_optional_index_column_qualifiers();
1442 while matches!(self.peek(), Token::Comma) {
1443 self.advance();
1444 let extra = self.expect_ident_like()?;
1445 self.consume_optional_index_column_qualifiers();
1446 extra_columns.push(extra);
1447 }
1448 if !matches!(self.peek(), Token::RParen) {
1449 return Err(self.err(format!(
1450 "expected ')' after indexed column / expression, got {:?}",
1451 self.peek()
1452 )));
1453 }
1454 self.advance();
1455 let included_columns =
1459 if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("include")) {
1460 self.advance();
1461 if !matches!(self.peek(), Token::LParen) {
1462 return Err(self.err(format!(
1463 "expected '(' after INCLUDE, got {:?}",
1464 self.peek()
1465 )));
1466 }
1467 self.advance();
1468 let mut cols = Vec::new();
1469 loop {
1470 cols.push(self.expect_ident_like()?);
1471 match self.peek() {
1472 Token::Comma => {
1473 self.advance();
1474 }
1475 Token::RParen => {
1476 self.advance();
1477 break;
1478 }
1479 other => {
1480 return Err(self.err(format!(
1481 "expected ',' or ')' in INCLUDE list, got {other:?}"
1482 )));
1483 }
1484 }
1485 }
1486 cols
1487 } else {
1488 Vec::new()
1489 };
1490 let partial_predicate = if matches!(self.peek(), Token::Where) {
1492 self.advance();
1493 Some(self.parse_expr(0)?)
1494 } else {
1495 None
1496 };
1497 Ok(Statement::CreateIndex(CreateIndexStatement {
1498 name,
1499 table,
1500 column,
1501 method,
1502 if_not_exists,
1503 included_columns,
1504 partial_predicate,
1505 extra_columns: extra_columns.clone(),
1506 expression,
1507 }))
1508 }
1509
1510 fn parse_column_def_with_fk(
1515 &mut self,
1516 ) -> Result<(ColumnDef, Option<ForeignKeyConstraint>), ParseError> {
1517 let col = self.parse_column_def()?;
1518 let inline_references = matches!(
1520 self.peek(),
1521 Token::Ident(s) if s.eq_ignore_ascii_case("references")
1522 );
1523 if !inline_references {
1524 return Ok((col, None));
1525 }
1526 let (parent_table, parent_columns, on_delete, on_update) =
1527 self.parse_references_tail(1)?;
1528 let fk = ForeignKeyConstraint {
1529 name: None,
1530 columns: vec![col.name.clone()],
1531 parent_table,
1532 parent_columns,
1533 on_delete,
1534 on_update,
1535 };
1536 Ok((col, Some(fk)))
1537 }
1538
1539 fn parse_column_def(&mut self) -> Result<ColumnDef, ParseError> {
1540 let name = self.expect_ident_like()?;
1541 let ty_ident = match self.advance() {
1544 Token::Ident(s) => s,
1545 other => {
1546 return Err(ParseError {
1547 message: format!("expected column type, got {other:?}"),
1548 token_pos: self.pos.saturating_sub(1),
1549 });
1550 }
1551 };
1552 let mut implied_auto_increment = false;
1559 let mut implied_not_null = false;
1560 let ty = match ty_ident.as_str() {
1561 "smallserial" | "serial2" => {
1563 implied_auto_increment = true;
1564 implied_not_null = true;
1565 ColumnTypeName::SmallInt
1566 }
1567 "serial" | "serial4" => {
1568 implied_auto_increment = true;
1569 implied_not_null = true;
1570 ColumnTypeName::Int
1571 }
1572 "bigserial" | "serial8" => {
1573 implied_auto_increment = true;
1574 implied_not_null = true;
1575 ColumnTypeName::BigInt
1576 }
1577 "smallint" | "tinyint" => ColumnTypeName::SmallInt,
1583 "int" | "integer" | "mediumint" => ColumnTypeName::Int,
1585 "bigint" => ColumnTypeName::BigInt,
1586 "float" | "double" | "real" => ColumnTypeName::Float,
1588 "text" => ColumnTypeName::Text,
1589 "bool" | "boolean" => ColumnTypeName::Bool,
1590 "varchar" => ColumnTypeName::Varchar(self.parse_paren_size("VARCHAR")?),
1591 "char" => ColumnTypeName::Char(self.parse_paren_size("CHAR")?),
1592 "vector" => {
1593 let dim = self.parse_paren_size("VECTOR")?;
1594 let encoding = self.parse_optional_vector_encoding()?;
1595 ColumnTypeName::Vector { dim, encoding }
1596 }
1597 "numeric" => {
1598 let (precision, scale) = self.parse_optional_numeric_params()?;
1599 ColumnTypeName::Numeric(precision, scale)
1600 }
1601 "date" => ColumnTypeName::Date,
1602 "timestamp" | "datetime" => ColumnTypeName::Timestamp,
1605 "timestamptz" => ColumnTypeName::Timestamptz,
1609 "json" => ColumnTypeName::Json,
1614 "jsonb" => ColumnTypeName::Jsonb,
1615 other => {
1616 return Err(ParseError {
1617 message: format!("unsupported column type {other:?}"),
1618 token_pos: self.pos.saturating_sub(1),
1619 });
1620 }
1621 };
1622 if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("unsigned")) {
1627 self.advance();
1628 }
1629 let mut default: Option<Expr> = None;
1633 let mut nullable = !implied_not_null;
1634 let mut nullability_seen = implied_not_null;
1635 let mut auto_increment = implied_auto_increment;
1636 let mut is_primary_key = false;
1637 loop {
1638 if matches!(self.peek(), Token::Default) {
1639 if default.is_some() {
1640 return Err(self.err("DEFAULT specified twice".into()));
1641 }
1642 self.advance();
1643 default = Some(self.parse_expr(0)?);
1644 continue;
1645 }
1646 if matches!(self.peek(), Token::Not) {
1647 if nullability_seen {
1648 return Err(self.err("NOT NULL specified twice".into()));
1649 }
1650 self.advance();
1651 if !matches!(self.peek(), Token::Null) {
1652 return Err(self.err(format!(
1653 "expected NULL after NOT in column def, got {:?}",
1654 self.peek()
1655 )));
1656 }
1657 self.advance();
1658 nullable = false;
1659 nullability_seen = true;
1660 continue;
1661 }
1662 if let Token::Ident(s) = self.peek()
1665 && (s.eq_ignore_ascii_case("auto_increment")
1666 || s.eq_ignore_ascii_case("autoincrement"))
1667 {
1668 if auto_increment {
1669 return Err(self.err("AUTO_INCREMENT specified twice".into()));
1670 }
1671 self.advance();
1672 auto_increment = true;
1673 continue;
1674 }
1675 if let Token::Ident(s) = self.peek()
1680 && s.eq_ignore_ascii_case("primary")
1681 {
1682 if is_primary_key {
1683 return Err(self.err("PRIMARY KEY specified twice".into()));
1684 }
1685 let next = self.tokens.get(self.pos + 1);
1687 let next_is_key = matches!(
1688 next,
1689 Some(Token::Ident(k)) if k.eq_ignore_ascii_case("key")
1690 );
1691 if !next_is_key {
1692 return Err(self.err(format!(
1693 "expected KEY after PRIMARY in column def, got {:?}",
1694 next
1695 )));
1696 }
1697 self.advance(); self.advance(); is_primary_key = true;
1700 if nullability_seen && nullable {
1701 return Err(self.err(
1702 "column declared NULL but inline PRIMARY KEY implies NOT NULL".into(),
1703 ));
1704 }
1705 nullable = false;
1706 nullability_seen = true;
1707 continue;
1708 }
1709 break;
1710 }
1711 Ok(ColumnDef {
1712 name,
1713 ty,
1714 nullable,
1715 default,
1716 auto_increment,
1717 is_primary_key,
1718 })
1719 }
1720
1721 fn parse_optional_numeric_params(&mut self) -> Result<(u8, u8), ParseError> {
1725 if !matches!(self.peek(), Token::LParen) {
1726 return Ok((0, 0));
1730 }
1731 self.advance();
1732 let precision = match self.advance() {
1733 Token::Integer(n) if (1..=38).contains(&n) => u8::try_from(n).expect("range-checked"),
1734 other => {
1735 return Err(ParseError {
1736 message: format!(
1737 "NUMERIC precision must be an integer in 1..=38, got {other:?}"
1738 ),
1739 token_pos: self.pos.saturating_sub(1),
1740 });
1741 }
1742 };
1743 let scale = if matches!(self.peek(), Token::Comma) {
1744 self.advance();
1745 match self.advance() {
1746 Token::Integer(n) if (0..=i64::from(precision)).contains(&n) => {
1747 u8::try_from(n).expect("range-checked")
1748 }
1749 other => {
1750 return Err(ParseError {
1751 message: format!(
1752 "NUMERIC scale must be a non-negative integer ≤ precision, got {other:?}"
1753 ),
1754 token_pos: self.pos.saturating_sub(1),
1755 });
1756 }
1757 }
1758 } else {
1759 0
1760 };
1761 if !matches!(self.peek(), Token::RParen) {
1762 return Err(self.err(format!(
1763 "expected ')' to close NUMERIC params, got {:?}",
1764 self.peek()
1765 )));
1766 }
1767 self.advance();
1768 Ok((precision, scale))
1769 }
1770
1771 fn parse_optional_vector_encoding(&mut self) -> Result<VecEncoding, ParseError> {
1779 if !matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("using")) {
1780 return Ok(VecEncoding::F32);
1781 }
1782 self.advance();
1783 let enc_ident = match self.advance() {
1784 Token::Ident(s) => s,
1785 other => {
1786 return Err(self.err(format!(
1787 "expected vector encoding after USING, got {other:?}"
1788 )));
1789 }
1790 };
1791 match enc_ident.to_ascii_lowercase().as_str() {
1792 "sq8" => Ok(VecEncoding::Sq8),
1793 "half" => Ok(VecEncoding::F16),
1796 other => Err(self.err(format!(
1797 "unknown vector encoding {other:?}; supported: SQ8, HALF"
1798 ))),
1799 }
1800 }
1801
1802 fn parse_paren_size(&mut self, label: &str) -> Result<u32, ParseError> {
1803 if !matches!(self.peek(), Token::LParen) {
1804 return Err(self.err(format!("{label} type requires (N), got {:?}", self.peek())));
1805 }
1806 self.advance();
1807 let n = match self.advance() {
1808 Token::Integer(n) if n > 0 => u32::try_from(n).map_err(|_| ParseError {
1809 message: format!("{label} size too large: {n}"),
1810 token_pos: self.pos.saturating_sub(1),
1811 })?,
1812 other => {
1813 return Err(ParseError {
1814 message: format!("expected positive integer {label} size, got {other:?}"),
1815 token_pos: self.pos.saturating_sub(1),
1816 });
1817 }
1818 };
1819 if !matches!(self.peek(), Token::RParen) {
1820 return Err(self.err(format!(
1821 "expected ')' after {label} size, got {:?}",
1822 self.peek()
1823 )));
1824 }
1825 self.advance();
1826 Ok(n)
1827 }
1828
1829 fn parse_insert_stmt(&mut self) -> Result<Statement, ParseError> {
1830 debug_assert!(matches!(self.peek(), Token::Insert));
1831 self.advance();
1832 if !matches!(self.peek(), Token::Into) {
1833 return Err(self.err(format!("expected INTO after INSERT, got {:?}", self.peek())));
1834 }
1835 self.advance();
1836 let table = self.expect_ident_like()?;
1837 let columns = if matches!(self.peek(), Token::LParen) {
1839 self.advance();
1840 let mut names = Vec::new();
1841 loop {
1842 names.push(self.expect_ident_like()?);
1843 match self.peek() {
1844 Token::Comma => {
1845 self.advance();
1846 }
1847 Token::RParen => {
1848 self.advance();
1849 break;
1850 }
1851 other => {
1852 return Err(self.err(format!(
1853 "expected ',' or ')' in INSERT column list, got {other:?}"
1854 )));
1855 }
1856 }
1857 }
1858 Some(names)
1859 } else {
1860 None
1861 };
1862 if !matches!(self.peek(), Token::Values) {
1863 return Err(self.err(format!(
1864 "expected VALUES after table name, got {:?}",
1865 self.peek()
1866 )));
1867 }
1868 self.advance();
1869 if !matches!(self.peek(), Token::LParen) {
1870 return Err(self.err(format!("expected '(' after VALUES, got {:?}", self.peek())));
1871 }
1872 let mut rows = Vec::new();
1873 loop {
1874 if !matches!(self.peek(), Token::LParen) {
1876 return Err(self.err(format!(
1877 "expected '(' for next VALUES tuple, got {:?}",
1878 self.peek()
1879 )));
1880 }
1881 self.advance();
1882 let mut tuple = Vec::new();
1883 loop {
1884 tuple.push(self.parse_expr(0)?);
1885 match self.peek() {
1886 Token::Comma => {
1887 self.advance();
1888 }
1889 Token::RParen => {
1890 self.advance();
1891 break;
1892 }
1893 other => {
1894 return Err(self.err(format!(
1895 "expected ',' or ')' in VALUES tuple, got {other:?}"
1896 )));
1897 }
1898 }
1899 }
1900 if tuple.is_empty() {
1901 return Err(self.err("INSERT VALUES tuple requires at least one value".into()));
1902 }
1903 rows.push(tuple);
1904 if matches!(self.peek(), Token::Comma) {
1906 self.advance();
1907 } else {
1908 break;
1909 }
1910 }
1911 let on_conflict = self.parse_optional_on_conflict()?;
1912 let returning = self.parse_optional_returning()?;
1913 Ok(Statement::Insert(InsertStatement {
1914 table,
1915 columns,
1916 rows,
1917 on_conflict,
1918 returning,
1919 }))
1920 }
1921
1922 fn parse_optional_on_conflict(
1927 &mut self,
1928 ) -> Result<Option<crate::ast::OnConflictClause>, ParseError> {
1929 if !matches!(self.peek(), Token::On) {
1930 return Ok(None);
1931 }
1932 let next_is_conflict = matches!(
1935 self.tokens.get(self.pos + 1),
1936 Some(Token::Ident(s) | Token::QuotedIdent(s)) if s.eq_ignore_ascii_case("conflict")
1937 );
1938 if !next_is_conflict {
1939 return Ok(None);
1940 }
1941 self.advance(); self.advance(); let mut target_columns: Vec<String> = Vec::new();
1945 if matches!(self.peek(), Token::LParen) {
1946 self.advance();
1947 loop {
1948 target_columns.push(self.expect_ident_like()?);
1949 match self.peek() {
1950 Token::Comma => {
1951 self.advance();
1952 }
1953 Token::RParen => {
1954 self.advance();
1955 break;
1956 }
1957 other => {
1958 return Err(self.err(alloc::format!(
1959 "expected ',' or ')' in ON CONFLICT target list, got {other:?}"
1960 )));
1961 }
1962 }
1963 }
1964 }
1965 match self.advance() {
1967 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("do") => {}
1968 other => {
1969 return Err(self.err(alloc::format!(
1970 "expected DO after ON CONFLICT [(…)], got {other:?}"
1971 )));
1972 }
1973 }
1974 let action = match self.advance() {
1976 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("nothing") => {
1977 crate::ast::OnConflictAction::Nothing
1978 }
1979 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("update") => {
1980 self.parse_on_conflict_update_action()?
1981 }
1982 other => {
1983 return Err(self.err(alloc::format!(
1984 "expected NOTHING or UPDATE after ON CONFLICT DO, got {other:?}"
1985 )));
1986 }
1987 };
1988 Ok(Some(crate::ast::OnConflictClause {
1989 target_columns,
1990 action,
1991 }))
1992 }
1993
1994 fn parse_on_conflict_update_action(
1998 &mut self,
1999 ) -> Result<crate::ast::OnConflictAction, ParseError> {
2000 match self.advance() {
2002 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("set") => {}
2003 other => {
2004 return Err(self.err(alloc::format!(
2005 "expected SET after ON CONFLICT DO UPDATE, got {other:?}"
2006 )));
2007 }
2008 }
2009 let mut assignments: Vec<(String, Expr)> = Vec::new();
2010 loop {
2011 let col = self.expect_ident_like()?;
2012 if !matches!(self.peek(), Token::Eq) {
2013 return Err(self.err(alloc::format!(
2014 "expected `=` after column in ON CONFLICT DO UPDATE SET, got {:?}",
2015 self.peek()
2016 )));
2017 }
2018 self.advance();
2019 let value = self.parse_expr(0)?;
2020 assignments.push((col, value));
2021 if matches!(self.peek(), Token::Comma) {
2022 self.advance();
2023 continue;
2024 }
2025 break;
2026 }
2027 let where_ = if matches!(self.peek(), Token::Where) {
2028 self.advance();
2029 Some(self.parse_expr(0)?)
2030 } else {
2031 None
2032 };
2033 Ok(crate::ast::OnConflictAction::Update {
2034 assignments,
2035 where_,
2036 })
2037 }
2038
2039 fn parse_select_list(&mut self) -> Result<Vec<SelectItem>, ParseError> {
2040 let mut items = Vec::new();
2041 loop {
2042 items.push(self.parse_select_item()?);
2043 if matches!(self.peek(), Token::Comma) {
2044 self.advance();
2045 } else {
2046 break;
2047 }
2048 }
2049 Ok(items)
2050 }
2051
2052 fn parse_select_item(&mut self) -> Result<SelectItem, ParseError> {
2053 if matches!(self.peek(), Token::Star) {
2054 self.advance();
2055 return Ok(SelectItem::Wildcard);
2056 }
2057 let expr = self.parse_expr(0)?;
2058 let alias = self.parse_optional_alias();
2059 Ok(SelectItem::Expr { expr, alias })
2060 }
2061
2062 fn parse_table_ref(&mut self) -> Result<TableRef, ParseError> {
2063 let name = self.expect_ident_like()?;
2064 let as_of_segment = if matches!(self.peek(), Token::As)
2070 && matches!(self.tokens.get(self.pos + 1), Some(Token::Ident(s) | Token::QuotedIdent(s)) if s.eq_ignore_ascii_case("of"))
2071 {
2072 self.advance(); self.advance(); let kw = match self.peek().clone() {
2075 Token::Ident(s) | Token::QuotedIdent(s) => s,
2076 other => {
2077 return Err(self.err(format!(
2078 "expected SEGMENT after AS OF, got {other:?}"
2079 )));
2080 }
2081 };
2082 if !kw.eq_ignore_ascii_case("segment") {
2083 return Err(self.err(format!(
2084 "expected SEGMENT after AS OF, got {kw:?}; v6.10.2 supports SEGMENT only"
2085 )));
2086 }
2087 self.advance();
2088 let id = match self.advance() {
2091 Token::String(s) => s
2092 .parse::<u32>()
2093 .map_err(|e| self.err(format!("AS OF SEGMENT id parse: {e}")))?,
2094 Token::Integer(n) => u32::try_from(n).map_err(|e| {
2095 self.err(format!("AS OF SEGMENT id parse: {e}"))
2096 })?,
2097 other => {
2098 return Err(self.err(format!(
2099 "expected segment id literal after AS OF SEGMENT, got {other:?}"
2100 )));
2101 }
2102 };
2103 Some(id)
2104 } else {
2105 None
2106 };
2107 let alias = self.parse_optional_alias();
2108 Ok(TableRef {
2109 name,
2110 alias,
2111 as_of_segment,
2112 })
2113 }
2114
2115 fn parse_from_clause(&mut self) -> Result<FromClause, ParseError> {
2120 let primary = self.parse_table_ref()?;
2121 let mut joins = Vec::new();
2122 loop {
2123 if matches!(self.peek(), Token::Comma) {
2125 self.advance();
2126 let table = self.parse_table_ref()?;
2127 joins.push(FromJoin {
2128 kind: JoinKind::Cross,
2129 table,
2130 on: None,
2131 });
2132 continue;
2133 }
2134 let kind =
2137 match self.peek() {
2138 Token::Inner => {
2139 self.advance();
2140 if !matches!(self.peek(), Token::Join) {
2141 return Err(self
2142 .err(format!("expected JOIN after INNER, got {:?}", self.peek())));
2143 }
2144 self.advance();
2145 JoinKind::Inner
2146 }
2147 Token::Left => {
2148 self.advance();
2149 if matches!(self.peek(), Token::Outer) {
2150 self.advance();
2151 }
2152 if !matches!(self.peek(), Token::Join) {
2153 return Err(self.err(format!(
2154 "expected JOIN after LEFT [OUTER], got {:?}",
2155 self.peek()
2156 )));
2157 }
2158 self.advance();
2159 JoinKind::Left
2160 }
2161 Token::Cross => {
2162 self.advance();
2163 if !matches!(self.peek(), Token::Join) {
2164 return Err(self
2165 .err(format!("expected JOIN after CROSS, got {:?}", self.peek())));
2166 }
2167 self.advance();
2168 JoinKind::Cross
2169 }
2170 Token::Join => {
2171 self.advance();
2172 JoinKind::Inner
2173 }
2174 _ => break,
2175 };
2176 let table = self.parse_table_ref()?;
2177 let on = if matches!(self.peek(), Token::On) {
2178 self.advance();
2179 Some(self.parse_expr(0)?)
2180 } else if kind == JoinKind::Cross {
2181 None
2182 } else {
2183 return Err(self.err(format!(
2184 "expected ON after {:?} JOIN, got {:?}",
2185 kind,
2186 self.peek()
2187 )));
2188 };
2189 joins.push(FromJoin { kind, table, on });
2190 }
2191 Ok(FromClause { primary, joins })
2192 }
2193
2194 fn parse_optional_alias(&mut self) -> Option<String> {
2199 if matches!(self.peek(), Token::As) {
2200 self.advance();
2201 if let Token::Ident(_) | Token::QuotedIdent(_) = self.peek() {
2206 return self.expect_ident_like().ok();
2207 }
2208 return None;
2209 }
2210 if let Token::Ident(_) | Token::QuotedIdent(_) = self.peek() {
2211 return self.expect_ident_like().ok();
2212 }
2213 None
2214 }
2215
2216 fn parse_expr(&mut self, min_prec: u8) -> Result<Expr, ParseError> {
2218 let mut lhs = self.parse_unary()?;
2219 while let Some((op, prec)) = binop_from(self.peek()) {
2220 if prec < min_prec {
2221 break;
2222 }
2223 self.advance();
2224 let rhs = self.parse_expr(prec + 1)?;
2225 lhs = Expr::Binary {
2226 lhs: Box::new(lhs),
2227 op,
2228 rhs: Box::new(rhs),
2229 };
2230 }
2231 Ok(lhs)
2232 }
2233
2234 fn parse_unary(&mut self) -> Result<Expr, ParseError> {
2235 match self.peek() {
2236 Token::Not => {
2237 self.advance();
2238 let e = self.parse_expr(3)?;
2241 Ok(Expr::Unary {
2242 op: UnOp::Not,
2243 expr: Box::new(e),
2244 })
2245 }
2246 Token::Minus => {
2247 self.advance();
2248 let e = self.parse_expr(8)?;
2251 Ok(Expr::Unary {
2252 op: UnOp::Neg,
2253 expr: Box::new(e),
2254 })
2255 }
2256 _ => self.parse_atom(),
2257 }
2258 }
2259
2260 fn parse_atom(&mut self) -> Result<Expr, ParseError> {
2261 let tok_pos = self.pos;
2262 match self.advance() {
2263 Token::Integer(n) => Ok(Expr::Literal(Literal::Integer(n))),
2264 Token::Float(x) => Ok(Expr::Literal(Literal::Float(x))),
2265 Token::String(s) => Ok(Expr::Literal(Literal::String(s))),
2266 Token::True => Ok(Expr::Literal(Literal::Bool(true))),
2267 Token::False => Ok(Expr::Literal(Literal::Bool(false))),
2268 Token::Null => Ok(Expr::Literal(Literal::Null)),
2269 Token::Placeholder(n) => Ok(Expr::Placeholder(n)),
2273 Token::LParen => {
2274 if matches!(self.peek(), Token::Select) {
2278 let inner = self.parse_select_stmt()?;
2279 match self.advance() {
2280 Token::RParen => {
2281 let Statement::Select(s) = inner else {
2282 unreachable!("parse_select_stmt returns Select")
2283 };
2284 Ok(Expr::ScalarSubquery(Box::new(s)))
2285 }
2286 other => Err(ParseError {
2287 message: format!("expected ')' after scalar subquery, got {other:?}"),
2288 token_pos: self.pos.saturating_sub(1),
2289 }),
2290 }
2291 } else {
2292 let e = self.parse_expr(0)?;
2293 match self.advance() {
2294 Token::RParen => Ok(e),
2295 other => Err(ParseError {
2296 message: format!("expected ')', got {other:?}"),
2297 token_pos: self.pos.saturating_sub(1),
2298 }),
2299 }
2300 }
2301 }
2302 Token::LBracket => self.parse_vector_literal_body(),
2303 Token::Extract => self.parse_extract_atom(),
2304 Token::Interval => self.parse_interval_atom(),
2305 Token::Ident(s) | Token::QuotedIdent(s) if s.eq_ignore_ascii_case("exists") => {
2310 self.parse_exists_atom(false)
2311 }
2312 Token::Ident(s) | Token::QuotedIdent(s) => self.finish_ident_atom(s),
2313 other => Err(ParseError {
2314 message: format!("unexpected token {other:?} in expression"),
2315 token_pos: tok_pos,
2316 }),
2317 }
2318 .and_then(|atom| self.finish_postfix_casts(atom))
2320 }
2321
2322 fn finish_postfix_casts(&mut self, mut expr: Expr) -> Result<Expr, ParseError> {
2325 loop {
2326 if matches!(self.peek(), Token::DoubleColon) {
2327 self.advance();
2328 let target = match self.advance() {
2329 Token::Ident(s) => match s.as_str() {
2330 "int" => CastTarget::Int,
2331 "bigint" => CastTarget::BigInt,
2332 "float" => CastTarget::Float,
2333 "text" => CastTarget::Text,
2334 "bool" => CastTarget::Bool,
2335 "vector" => CastTarget::Vector,
2336 "date" => CastTarget::Date,
2337 "timestamp" | "datetime" => CastTarget::Timestamp,
2338 other => {
2339 return Err(ParseError {
2340 message: format!("unsupported cast target `::{other}`"),
2341 token_pos: self.pos.saturating_sub(1),
2342 });
2343 }
2344 },
2345 other => {
2346 return Err(ParseError {
2347 message: format!("expected type ident after `::`, got {other:?}"),
2348 token_pos: self.pos.saturating_sub(1),
2349 });
2350 }
2351 };
2352 expr = Expr::Cast {
2353 expr: Box::new(expr),
2354 target,
2355 };
2356 continue;
2357 }
2358 if matches!(self.peek(), Token::Is) {
2359 self.advance();
2360 let negated = if matches!(self.peek(), Token::Not) {
2361 self.advance();
2362 true
2363 } else {
2364 false
2365 };
2366 if !matches!(self.peek(), Token::Null) {
2367 return Err(self.err(format!(
2368 "expected NULL after IS{}, got {:?}",
2369 if negated { " NOT" } else { "" },
2370 self.peek()
2371 )));
2372 }
2373 self.advance();
2374 expr = Expr::IsNull {
2375 expr: Box::new(expr),
2376 negated,
2377 };
2378 continue;
2379 }
2380 let negated = if matches!(self.peek(), Token::Not) {
2384 let next = self.tokens.get(self.pos + 1);
2385 matches!(next, Some(Token::Between | Token::In | Token::Like))
2386 } else {
2387 false
2388 };
2389 if negated {
2390 self.advance();
2391 }
2392 if matches!(self.peek(), Token::Between) {
2393 expr = self.parse_between_tail(expr, negated)?;
2394 continue;
2395 }
2396 if matches!(self.peek(), Token::In) {
2397 expr = self.parse_in_tail(expr, negated)?;
2398 continue;
2399 }
2400 if matches!(self.peek(), Token::Like) {
2401 self.advance();
2402 let pattern = self.parse_expr(5)?;
2405 expr = Expr::Like {
2406 expr: Box::new(expr),
2407 pattern: Box::new(pattern),
2408 negated,
2409 };
2410 continue;
2411 }
2412 return Ok(expr);
2413 }
2414 }
2415
2416 fn parse_between_tail(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
2420 self.advance(); let low = self.parse_expr(5)?;
2422 if !matches!(self.peek(), Token::And) {
2423 return Err(self.err(format!(
2424 "expected AND after BETWEEN low bound, got {:?}",
2425 self.peek()
2426 )));
2427 }
2428 self.advance();
2429 let high = self.parse_expr(5)?;
2430 let target = Box::new(expr);
2431 let combined = Expr::Binary {
2432 lhs: Box::new(Expr::Binary {
2433 lhs: target.clone(),
2434 op: BinOp::GtEq,
2435 rhs: Box::new(low),
2436 }),
2437 op: BinOp::And,
2438 rhs: Box::new(Expr::Binary {
2439 lhs: target,
2440 op: BinOp::LtEq,
2441 rhs: Box::new(high),
2442 }),
2443 };
2444 Ok(maybe_not(combined, negated))
2445 }
2446
2447 fn parse_with_cte_then_select(&mut self) -> Result<Statement, ParseError> {
2452 let mut recursive = false;
2457 if let Token::Ident(s) | Token::QuotedIdent(s) = self.peek()
2458 && s.eq_ignore_ascii_case("recursive")
2459 {
2460 self.advance();
2461 recursive = true;
2462 }
2463 let mut ctes = Vec::new();
2464 loop {
2465 let name = self.expect_ident_like()?;
2466 let column_overrides: Vec<String> = if matches!(self.peek(), Token::LParen) {
2470 self.advance();
2471 let mut names = Vec::new();
2472 loop {
2473 names.push(self.expect_ident_like()?);
2474 if matches!(self.peek(), Token::Comma) {
2475 self.advance();
2476 continue;
2477 }
2478 break;
2479 }
2480 if !matches!(self.peek(), Token::RParen) {
2481 return Err(self.err(format!(
2482 "expected ')' to close CTE column list, got {:?}",
2483 self.peek()
2484 )));
2485 }
2486 self.advance();
2487 names
2488 } else {
2489 Vec::new()
2490 };
2491 if !matches!(self.peek(), Token::As) {
2495 return Err(self.err(format!(
2496 "expected AS after CTE name {name:?}, got {:?}",
2497 self.peek()
2498 )));
2499 }
2500 self.advance();
2501 if !matches!(self.peek(), Token::LParen) {
2502 return Err(self.err(format!(
2503 "expected '(' after AS in WITH clause, got {:?}",
2504 self.peek()
2505 )));
2506 }
2507 self.advance();
2508 if !matches!(self.peek(), Token::Select) {
2509 return Err(self.err(format!("WITH body must be a SELECT, got {:?}", self.peek())));
2510 }
2511 let inner = self.parse_select_stmt()?;
2512 if !matches!(self.peek(), Token::RParen) {
2513 return Err(self.err(format!(
2514 "expected ')' after CTE body, got {:?}",
2515 self.peek()
2516 )));
2517 }
2518 self.advance();
2519 let Statement::Select(body) = inner else {
2520 unreachable!("parse_select_stmt returns Select")
2521 };
2522 ctes.push(crate::ast::Cte {
2523 name,
2524 body,
2525 recursive,
2526 column_overrides,
2527 });
2528 if matches!(self.peek(), Token::Comma) {
2529 self.advance();
2530 continue;
2531 }
2532 break;
2533 }
2534 if !matches!(self.peek(), Token::Select) {
2536 return Err(self.err(format!(
2537 "expected SELECT after WITH clause, got {:?}",
2538 self.peek()
2539 )));
2540 }
2541 let body_stmt = self.parse_select_stmt()?;
2542 let Statement::Select(mut body) = body_stmt else {
2543 unreachable!()
2544 };
2545 body.ctes = ctes;
2546 Ok(Statement::Select(body))
2547 }
2548
2549 fn parse_exists_atom(&mut self, negated: bool) -> Result<Expr, ParseError> {
2553 if !matches!(self.peek(), Token::LParen) {
2554 return Err(self.err(format!("expected '(' after EXISTS, got {:?}", self.peek())));
2555 }
2556 self.advance();
2557 let inner = self.parse_select_stmt()?;
2558 if !matches!(self.peek(), Token::RParen) {
2559 return Err(self.err(format!(
2560 "expected ')' after EXISTS-subquery, got {:?}",
2561 self.peek()
2562 )));
2563 }
2564 self.advance();
2565 let Statement::Select(s) = inner else {
2566 unreachable!("parse_select_stmt returns Select")
2567 };
2568 Ok(Expr::Exists {
2569 subquery: Box::new(s),
2570 negated,
2571 })
2572 }
2573
2574 fn parse_in_tail(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
2575 self.advance(); if !matches!(self.peek(), Token::LParen) {
2577 return Err(self.err(format!("expected '(' after IN, got {:?}", self.peek())));
2578 }
2579 self.advance();
2580 if matches!(self.peek(), Token::Select) {
2582 let inner = self.parse_select_stmt()?;
2583 if !matches!(self.peek(), Token::RParen) {
2584 return Err(self.err(format!(
2585 "expected ')' after IN-subquery, got {:?}",
2586 self.peek()
2587 )));
2588 }
2589 self.advance();
2590 let Statement::Select(s) = inner else {
2591 unreachable!("parse_select_stmt always returns Statement::Select")
2592 };
2593 return Ok(Expr::InSubquery {
2594 expr: Box::new(expr),
2595 subquery: Box::new(s),
2596 negated,
2597 });
2598 }
2599 let mut elements = Vec::new();
2600 if !matches!(self.peek(), Token::RParen) {
2601 loop {
2602 elements.push(self.parse_expr(0)?);
2603 match self.peek() {
2604 Token::Comma => {
2605 self.advance();
2606 }
2607 Token::RParen => break,
2608 other => {
2609 return Err(
2610 self.err(format!("expected ',' or ')' in IN list, got {other:?}"))
2611 );
2612 }
2613 }
2614 }
2615 }
2616 self.advance(); let target = Box::new(expr);
2618 let combined = if elements.is_empty() {
2619 Expr::Literal(Literal::Bool(false))
2620 } else {
2621 let mut iter = elements.into_iter();
2622 let first = iter.next().unwrap();
2623 let mut acc = Expr::Binary {
2624 lhs: target.clone(),
2625 op: BinOp::Eq,
2626 rhs: Box::new(first),
2627 };
2628 for elt in iter {
2629 acc = Expr::Binary {
2630 lhs: Box::new(acc),
2631 op: BinOp::Or,
2632 rhs: Box::new(Expr::Binary {
2633 lhs: target.clone(),
2634 op: BinOp::Eq,
2635 rhs: Box::new(elt),
2636 }),
2637 };
2638 }
2639 acc
2640 };
2641 Ok(maybe_not(combined, negated))
2642 }
2643
2644 fn parse_extract_atom(&mut self) -> Result<Expr, ParseError> {
2652 if !matches!(self.peek(), Token::LParen) {
2653 return Err(self.err(format!("expected '(' after EXTRACT, got {:?}", self.peek())));
2654 }
2655 self.advance();
2656 let field_name = self.expect_ident_like()?;
2657 let field = match field_name.to_ascii_lowercase().as_str() {
2658 "year" => ExtractField::Year,
2659 "month" => ExtractField::Month,
2660 "day" => ExtractField::Day,
2661 "hour" => ExtractField::Hour,
2662 "minute" => ExtractField::Minute,
2663 "second" => ExtractField::Second,
2664 "microsecond" | "microseconds" => ExtractField::Microsecond,
2665 other => {
2666 return Err(self.err(format!(
2667 "unknown EXTRACT field {other:?}; \
2668 supported: YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MICROSECOND"
2669 )));
2670 }
2671 };
2672 if !matches!(self.peek(), Token::From) {
2673 return Err(self.err(format!(
2674 "expected FROM after EXTRACT field, got {:?}",
2675 self.peek()
2676 )));
2677 }
2678 self.advance();
2679 let source = self.parse_expr(0)?;
2680 if !matches!(self.peek(), Token::RParen) {
2681 return Err(self.err(format!(
2682 "expected ')' to close EXTRACT, got {:?}",
2683 self.peek()
2684 )));
2685 }
2686 self.advance();
2687 Ok(Expr::Extract {
2688 field,
2689 source: Box::new(source),
2690 })
2691 }
2692
2693 fn parse_interval_atom(&mut self) -> Result<Expr, ParseError> {
2698 let tok = self.advance();
2699 let Token::String(text) = tok else {
2700 return Err(self.err(format!(
2701 "expected string literal after INTERVAL, got {tok:?}"
2702 )));
2703 };
2704 let (months, micros) = parse_interval_text(&text).ok_or_else(|| ParseError {
2705 message: format!(
2706 "cannot parse INTERVAL {text:?}; \
2707 expected `<n> <unit> [<n> <unit> ...]` with units \
2708 microsecond[s], millisecond[s], second[s], minute[s], \
2709 hour[s], day[s], week[s], month[s], year[s]"
2710 ),
2711 token_pos: self.pos.saturating_sub(1),
2712 })?;
2713 Ok(Expr::Literal(Literal::Interval {
2714 months,
2715 micros,
2716 text,
2717 }))
2718 }
2719
2720 fn parse_vector_literal_body(&mut self) -> Result<Expr, ParseError> {
2721 let mut elems = Vec::new();
2722 if matches!(self.peek(), Token::RBracket) {
2723 self.advance();
2724 return Ok(Expr::Literal(Literal::Vector(elems)));
2725 }
2726 loop {
2727 let e = self.parse_expr(0)?;
2728 let x = extract_numeric_literal(&e).ok_or_else(|| ParseError {
2729 message: format!("vector element must be a numeric literal, got {e:?}"),
2730 token_pos: self.pos,
2731 })?;
2732 elems.push(x);
2733 match self.peek() {
2734 Token::Comma => {
2735 self.advance();
2736 }
2737 Token::RBracket => {
2738 self.advance();
2739 break;
2740 }
2741 other => {
2742 return Err(self.err(format!("expected ',' or ']' in vector, got {other:?}")));
2743 }
2744 }
2745 }
2746 Ok(Expr::Literal(Literal::Vector(elems)))
2747 }
2748
2749 fn parse_null_treatment_modifier(&mut self) -> NullTreatment {
2758 let Token::Ident(s) = self.peek().clone() else {
2759 return NullTreatment::Respect;
2760 };
2761 let is_ignore = s.eq_ignore_ascii_case("ignore");
2762 let is_respect = s.eq_ignore_ascii_case("respect");
2763 if !is_ignore && !is_respect {
2764 return NullTreatment::Respect;
2765 }
2766 if self.pos + 1 < self.tokens.len()
2769 && let Token::Ident(s2) = &self.tokens[self.pos + 1]
2770 && s2.eq_ignore_ascii_case("nulls")
2771 {
2772 self.advance();
2773 self.advance();
2774 return if is_ignore {
2775 NullTreatment::Ignore
2776 } else {
2777 NullTreatment::Respect
2778 };
2779 }
2780 NullTreatment::Respect
2781 }
2782
2783 #[allow(clippy::type_complexity)] fn parse_over_clause(
2786 &mut self,
2787 ) -> Result<(Vec<Expr>, Vec<(Expr, bool)>, Option<WindowFrame>), ParseError> {
2788 if !matches!(self.peek(), Token::LParen) {
2789 return Err(self.err(format!("expected '(' after OVER, got {:?}", self.peek())));
2790 }
2791 self.advance();
2792 let mut partition_by = Vec::new();
2793 let mut order_by = Vec::new();
2794 if let Token::Ident(s) | Token::QuotedIdent(s) = self.peek()
2796 && s.eq_ignore_ascii_case("partition")
2797 {
2798 self.advance();
2799 if !matches!(self.peek(), Token::By) {
2800 return Err(self.err(format!(
2801 "expected BY after PARTITION, got {:?}",
2802 self.peek()
2803 )));
2804 }
2805 self.advance();
2806 loop {
2807 partition_by.push(self.parse_expr(0)?);
2808 if matches!(self.peek(), Token::Comma) {
2809 self.advance();
2810 continue;
2811 }
2812 break;
2813 }
2814 }
2815 if matches!(self.peek(), Token::Order) {
2817 self.advance();
2818 if !matches!(self.peek(), Token::By) {
2819 return Err(self.err(format!("expected BY after ORDER, got {:?}", self.peek())));
2820 }
2821 self.advance();
2822 loop {
2823 let e = self.parse_expr(0)?;
2824 let desc = if matches!(self.peek(), Token::Desc) {
2825 self.advance();
2826 true
2827 } else if matches!(self.peek(), Token::Asc) {
2828 self.advance();
2829 false
2830 } else {
2831 false
2832 };
2833 order_by.push((e, desc));
2834 if matches!(self.peek(), Token::Comma) {
2835 self.advance();
2836 continue;
2837 }
2838 break;
2839 }
2840 }
2841 let mut frame: Option<WindowFrame> = None;
2845 if let Token::Ident(s) | Token::QuotedIdent(s) = self.peek() {
2846 let kind = if s.eq_ignore_ascii_case("rows") {
2847 Some(FrameKind::Rows)
2848 } else if s.eq_ignore_ascii_case("range") {
2849 Some(FrameKind::Range)
2850 } else {
2851 None
2852 };
2853 if let Some(kind) = kind {
2854 self.advance();
2855 frame = Some(self.parse_frame_tail(kind)?);
2856 }
2857 }
2858 if !matches!(self.peek(), Token::RParen) {
2859 return Err(self.err(format!(
2860 "expected ')' to close OVER clause, got {:?}",
2861 self.peek()
2862 )));
2863 }
2864 self.advance();
2865 Ok((partition_by, order_by, frame))
2866 }
2867
2868 fn parse_frame_tail(&mut self, kind: FrameKind) -> Result<WindowFrame, ParseError> {
2874 if matches!(self.peek(), Token::Between) {
2875 self.advance();
2876 let start = self.parse_frame_bound()?;
2877 if !matches!(self.peek(), Token::And) {
2878 return Err(self.err(format!("expected AND in frame spec, got {:?}", self.peek())));
2879 }
2880 self.advance();
2881 let end = self.parse_frame_bound()?;
2882 Ok(WindowFrame {
2883 kind,
2884 start,
2885 end: Some(end),
2886 })
2887 } else {
2888 let start = self.parse_frame_bound()?;
2889 Ok(WindowFrame {
2890 kind,
2891 start,
2892 end: None,
2893 })
2894 }
2895 }
2896
2897 fn parse_frame_bound(&mut self) -> Result<FrameBound, ParseError> {
2900 if let Token::Integer(n) = *self.peek() {
2902 self.advance();
2903 let n: u64 = u64::try_from(n).map_err(|_| {
2904 self.err(format!(
2905 "invalid frame offset {n} — expected non-negative integer"
2906 ))
2907 })?;
2908 let dir = self.expect_ident_like()?;
2909 return if dir.eq_ignore_ascii_case("preceding") {
2910 Ok(FrameBound::OffsetPreceding(n))
2911 } else if dir.eq_ignore_ascii_case("following") {
2912 Ok(FrameBound::OffsetFollowing(n))
2913 } else {
2914 Err(self.err(format!(
2915 "expected PRECEDING or FOLLOWING after offset, got {dir:?}"
2916 )))
2917 };
2918 }
2919 let first = self.expect_ident_like()?;
2920 if first.eq_ignore_ascii_case("unbounded") {
2921 let dir = self.expect_ident_like()?;
2922 return if dir.eq_ignore_ascii_case("preceding") {
2923 Ok(FrameBound::UnboundedPreceding)
2924 } else if dir.eq_ignore_ascii_case("following") {
2925 Ok(FrameBound::UnboundedFollowing)
2926 } else {
2927 Err(self.err(format!(
2928 "expected PRECEDING or FOLLOWING after UNBOUNDED, got {dir:?}"
2929 )))
2930 };
2931 }
2932 if first.eq_ignore_ascii_case("current") {
2933 let row = self.expect_ident_like()?;
2934 if !row.eq_ignore_ascii_case("row") {
2935 return Err(self.err(format!("expected ROW after CURRENT, got {row:?}")));
2936 }
2937 return Ok(FrameBound::CurrentRow);
2938 }
2939 Err(self.err(format!(
2940 "expected frame bound (UNBOUNDED/CURRENT/<n>), got {first:?}"
2941 )))
2942 }
2943
2944 fn finish_ident_atom(&mut self, first: String) -> Result<Expr, ParseError> {
2945 if matches!(self.peek(), Token::Dot) {
2946 self.advance();
2947 let name = self.expect_ident_like()?;
2948 return Ok(Expr::Column(ColumnName {
2949 qualifier: Some(first),
2950 name,
2951 }));
2952 }
2953 if matches!(self.peek(), Token::LParen) {
2954 self.advance();
2955 if first.eq_ignore_ascii_case("count") && matches!(self.peek(), Token::Star) {
2959 self.advance();
2960 if !matches!(self.peek(), Token::RParen) {
2961 return Err(self.err(format!(
2962 "expected ')' after COUNT(*), got {:?}",
2963 self.peek()
2964 )));
2965 }
2966 self.advance();
2967 let null_treatment = self.parse_null_treatment_modifier();
2969 if let Token::Ident(s) | Token::QuotedIdent(s) = self.peek()
2970 && s.eq_ignore_ascii_case("over")
2971 {
2972 self.advance();
2973 let (partition_by, order_by, frame) = self.parse_over_clause()?;
2974 return Ok(Expr::WindowFunction {
2975 name: "count_star".into(),
2976 args: Vec::new(),
2977 partition_by,
2978 order_by,
2979 frame,
2980 null_treatment,
2981 });
2982 }
2983 return Ok(Expr::FunctionCall {
2984 name: "count_star".into(),
2985 args: Vec::new(),
2986 });
2987 }
2988 let mut args = Vec::new();
2990 if !matches!(self.peek(), Token::RParen) {
2991 loop {
2992 args.push(self.parse_expr(0)?);
2993 match self.peek() {
2994 Token::Comma => {
2995 self.advance();
2996 }
2997 Token::RParen => break,
2998 other => {
2999 return Err(self.err(format!(
3000 "expected ',' or ')' in function args, got {other:?}"
3001 )));
3002 }
3003 }
3004 }
3005 }
3006 self.advance(); let null_treatment = self.parse_null_treatment_modifier();
3014 if let Token::Ident(s) | Token::QuotedIdent(s) = self.peek()
3015 && s.eq_ignore_ascii_case("over")
3016 {
3017 self.advance();
3018 let (partition_by, order_by, frame) = self.parse_over_clause()?;
3019 return Ok(Expr::WindowFunction {
3020 name: first,
3021 args,
3022 partition_by,
3023 order_by,
3024 frame,
3025 null_treatment,
3026 });
3027 }
3028 return Ok(Expr::FunctionCall { name: first, args });
3029 }
3030 Ok(Expr::Column(ColumnName {
3031 qualifier: None,
3032 name: first,
3033 }))
3034 }
3035}
3036
3037fn extract_first_column(expr: &Expr) -> Option<String> {
3045 match expr {
3046 Expr::Column(cn) => Some(cn.name.clone()),
3047 Expr::FunctionCall { args, .. } => args.iter().find_map(extract_first_column),
3048 Expr::Binary { lhs, rhs, .. } => {
3049 extract_first_column(lhs).or_else(|| extract_first_column(rhs))
3050 }
3051 Expr::Unary { expr: e, .. } => extract_first_column(e),
3052 _ => None,
3053 }
3054}
3055
3056fn maybe_not(expr: Expr, negated: bool) -> Expr {
3057 if negated {
3058 Expr::Unary {
3059 op: UnOp::Not,
3060 expr: Box::new(expr),
3061 }
3062 } else {
3063 expr
3064 }
3065}
3066
3067fn binop_from(tok: &Token) -> Option<(BinOp, u8)> {
3068 let pair = match tok {
3069 Token::Or => (BinOp::Or, 1),
3070 Token::And => (BinOp::And, 2),
3071 Token::Eq => (BinOp::Eq, 4),
3072 Token::NotEq => (BinOp::NotEq, 4),
3073 Token::Lt => (BinOp::Lt, 4),
3074 Token::LtEq => (BinOp::LtEq, 4),
3075 Token::Gt => (BinOp::Gt, 4),
3076 Token::GtEq => (BinOp::GtEq, 4),
3077 Token::L2Distance => (BinOp::L2Distance, 5),
3080 Token::InnerProduct => (BinOp::InnerProduct, 5),
3081 Token::CosineDistance => (BinOp::CosineDistance, 5),
3082 Token::Plus => (BinOp::Add, 6),
3083 Token::Minus => (BinOp::Sub, 6),
3084 Token::Concat => (BinOp::Concat, 6),
3087 Token::Star => (BinOp::Mul, 7),
3088 Token::Slash => (BinOp::Div, 7),
3089 Token::JsonGet => (BinOp::JsonGet, 7),
3093 Token::JsonGetText => (BinOp::JsonGetText, 7),
3094 Token::JsonGetPath => (BinOp::JsonGetPath, 7),
3095 Token::JsonGetPathText => (BinOp::JsonGetPathText, 7),
3096 Token::JsonContains => (BinOp::JsonContains, 7),
3097 _ => return None,
3098 };
3099 Some(pair)
3100}
3101
3102#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
3103fn extract_numeric_literal(e: &Expr) -> Option<f32> {
3108 match e {
3109 Expr::Literal(Literal::Integer(n)) => Some(*n as f32),
3110 Expr::Literal(Literal::Float(x)) => Some(*x as f32),
3111 Expr::Unary {
3112 op: UnOp::Neg,
3113 expr,
3114 } => extract_numeric_literal(expr).map(|x| -x),
3115 _ => None,
3116 }
3117}
3118
3119pub fn parse_interval_text(s: &str) -> Option<(i32, i64)> {
3127 let parts: Vec<&str> = s.split_whitespace().collect();
3128 if parts.is_empty() || !parts.len().is_multiple_of(2) {
3129 return None;
3130 }
3131 let mut months: i32 = 0;
3132 let mut micros: i64 = 0;
3133 let mut i = 0;
3134 while i < parts.len() {
3135 let n: i64 = parts[i].parse().ok()?;
3136 let unit = parts[i + 1].to_ascii_lowercase();
3137 let unit_stripped = unit.strip_suffix('s').unwrap_or(&unit);
3138 match unit_stripped {
3139 "microsecond" => micros = micros.checked_add(n)?,
3140 "millisecond" => micros = micros.checked_add(n.checked_mul(1_000)?)?,
3141 "second" => micros = micros.checked_add(n.checked_mul(1_000_000)?)?,
3142 "minute" => micros = micros.checked_add(n.checked_mul(60_000_000)?)?,
3143 "hour" => micros = micros.checked_add(n.checked_mul(3_600_000_000)?)?,
3144 "day" => micros = micros.checked_add(n.checked_mul(86_400_000_000)?)?,
3145 "week" => micros = micros.checked_add(n.checked_mul(604_800_000_000)?)?,
3146 "month" => {
3147 let n32 = i32::try_from(n).ok()?;
3148 months = months.checked_add(n32)?;
3149 }
3150 "year" => {
3151 let n32 = i32::try_from(n).ok()?;
3152 months = months.checked_add(n32.checked_mul(12)?)?;
3153 }
3154 _ => return None,
3155 }
3156 i += 2;
3157 }
3158 Some((months, micros))
3159}
3160
3161#[cfg(test)]
3162mod tests {
3163 use super::*;
3164 use alloc::string::ToString;
3165
3166 fn parse(s: &str) -> Statement {
3167 parse_statement(s).expect("parse ok")
3168 }
3169
3170 fn lit_int(n: i64) -> Expr {
3171 Expr::Literal(Literal::Integer(n))
3172 }
3173
3174 fn col(name: &str) -> Expr {
3175 Expr::Column(ColumnName {
3176 qualifier: None,
3177 name: name.into(),
3178 })
3179 }
3180
3181 #[test]
3182 fn select_single_integer() {
3183 let s = parse("SELECT 1");
3184 let Statement::Select(s) = s else {
3185 panic!("expected SELECT")
3186 };
3187 assert_eq!(s.items.len(), 1);
3188 assert!(s.from.is_none());
3189 assert!(s.where_.is_none());
3190 }
3191
3192 #[test]
3193 fn select_multiple_literal_kinds() {
3194 let s = parse("SELECT 1, 'hi', NULL, TRUE, 1.5");
3195 let Statement::Select(s) = s else {
3196 panic!("expected SELECT")
3197 };
3198 assert_eq!(s.items.len(), 5);
3199 }
3200
3201 #[test]
3202 fn select_wildcard_from_table() {
3203 let s = parse("SELECT * FROM users");
3204 let Statement::Select(s) = s else {
3205 panic!("expected SELECT")
3206 };
3207 assert!(matches!(s.items[..], [SelectItem::Wildcard]));
3208 assert_eq!(s.from.as_ref().unwrap().primary.name, "users");
3209 }
3210
3211 #[test]
3212 fn select_with_table_alias() {
3213 let s = parse("SELECT * FROM users AS u");
3214 let Statement::Select(s) = s else {
3215 panic!("expected SELECT")
3216 };
3217 let t = &s.from.as_ref().unwrap().primary;
3218 assert_eq!(t.name, "users");
3219 assert_eq!(t.alias.as_deref(), Some("u"));
3220 }
3221
3222 #[test]
3223 fn select_with_where_eq() {
3224 let s = parse("SELECT a FROM t WHERE a = 1");
3225 let Statement::Select(s) = s else {
3226 panic!("expected SELECT")
3227 };
3228 let w = s.where_.unwrap();
3229 assert_eq!(
3230 w,
3231 Expr::Binary {
3232 lhs: Box::new(col("a")),
3233 op: BinOp::Eq,
3234 rhs: Box::new(lit_int(1)),
3235 }
3236 );
3237 }
3238
3239 #[test]
3240 fn arithmetic_precedence() {
3241 let s = parse("SELECT 1 + 2 * 3");
3242 let Statement::Select(s) = s else {
3243 panic!("expected SELECT")
3244 };
3245 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3246 panic!("wildcard?")
3247 };
3248 assert_eq!(
3249 expr,
3250 &Expr::Binary {
3251 lhs: Box::new(lit_int(1)),
3252 op: BinOp::Add,
3253 rhs: Box::new(Expr::Binary {
3254 lhs: Box::new(lit_int(2)),
3255 op: BinOp::Mul,
3256 rhs: Box::new(lit_int(3)),
3257 }),
3258 }
3259 );
3260 }
3261
3262 #[test]
3263 fn parentheses_override_precedence() {
3264 let s = parse("SELECT (1 + 2) * 3");
3265 let Statement::Select(s) = s else {
3266 panic!("expected SELECT")
3267 };
3268 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3269 panic!()
3270 };
3271 assert_eq!(
3272 expr,
3273 &Expr::Binary {
3274 lhs: Box::new(Expr::Binary {
3275 lhs: Box::new(lit_int(1)),
3276 op: BinOp::Add,
3277 rhs: Box::new(lit_int(2)),
3278 }),
3279 op: BinOp::Mul,
3280 rhs: Box::new(lit_int(3)),
3281 }
3282 );
3283 }
3284
3285 #[test]
3286 fn not_binds_below_comparison() {
3287 let s = parse("SELECT NOT a = 1 FROM t");
3289 let Statement::Select(s) = s else {
3290 panic!("expected SELECT")
3291 };
3292 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3293 panic!()
3294 };
3295 assert_eq!(
3296 expr,
3297 &Expr::Unary {
3298 op: UnOp::Not,
3299 expr: Box::new(Expr::Binary {
3300 lhs: Box::new(col("a")),
3301 op: BinOp::Eq,
3302 rhs: Box::new(lit_int(1)),
3303 }),
3304 }
3305 );
3306 }
3307
3308 #[test]
3309 fn unary_minus_binds_above_multiplication() {
3310 let s = parse("SELECT -a * 2 FROM t");
3312 let Statement::Select(s) = s else {
3313 panic!("expected SELECT")
3314 };
3315 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3316 panic!()
3317 };
3318 assert_eq!(
3319 expr,
3320 &Expr::Binary {
3321 lhs: Box::new(Expr::Unary {
3322 op: UnOp::Neg,
3323 expr: Box::new(col("a")),
3324 }),
3325 op: BinOp::Mul,
3326 rhs: Box::new(lit_int(2)),
3327 }
3328 );
3329 }
3330
3331 #[test]
3332 fn qualified_column() {
3333 let s = parse("SELECT t.col FROM t");
3334 let Statement::Select(s) = s else {
3335 panic!("expected SELECT")
3336 };
3337 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3338 panic!()
3339 };
3340 assert_eq!(
3341 expr,
3342 &Expr::Column(ColumnName {
3343 qualifier: Some("t".into()),
3344 name: "col".into()
3345 })
3346 );
3347 }
3348
3349 #[test]
3350 fn select_item_alias_with_as() {
3351 let s = parse("SELECT a AS y FROM t");
3352 let Statement::Select(s) = s else {
3353 panic!("expected SELECT")
3354 };
3355 let SelectItem::Expr { alias, .. } = &s.items[0] else {
3356 panic!()
3357 };
3358 assert_eq!(alias.as_deref(), Some("y"));
3359 }
3360
3361 #[test]
3362 fn trailing_semicolon_accepted() {
3363 let s = parse("SELECT 1;");
3364 let Statement::Select(s) = s else {
3365 panic!("expected SELECT")
3366 };
3367 assert_eq!(s.items.len(), 1);
3368 }
3369
3370 #[test]
3371 fn boolean_chain_with_and_or_not() {
3372 let s = parse("SELECT NOT a OR b AND NOT c FROM t");
3374 let Statement::Select(s) = s else {
3375 panic!("expected SELECT")
3376 };
3377 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3378 panic!()
3379 };
3380 let expected = Expr::Binary {
3381 lhs: Box::new(Expr::Unary {
3382 op: UnOp::Not,
3383 expr: Box::new(col("a")),
3384 }),
3385 op: BinOp::Or,
3386 rhs: Box::new(Expr::Binary {
3387 lhs: Box::new(col("b")),
3388 op: BinOp::And,
3389 rhs: Box::new(Expr::Unary {
3390 op: UnOp::Not,
3391 expr: Box::new(col("c")),
3392 }),
3393 }),
3394 };
3395 assert_eq!(expr, &expected);
3396 }
3397
3398 #[test]
3399 fn empty_input_errors() {
3400 let err = parse_statement("").unwrap_err();
3401 assert!(err.message.contains("SELECT"));
3402 }
3403
3404 #[test]
3405 fn unmatched_paren_errors() {
3406 assert!(parse_statement("SELECT (1 + 2").is_err());
3407 }
3408
3409 #[test]
3410 fn display_round_trip_simple_select() {
3411 let original = parse("SELECT a + 1 FROM t WHERE a > 0");
3412 let text = original.to_string();
3413 let again = parse_statement(&text).expect("re-parse");
3414 assert_eq!(original, again);
3415 }
3416
3417 #[test]
3420 fn create_table_single_column() {
3421 let s = parse("CREATE TABLE foo (a INT)");
3422 let Statement::CreateTable(c) = s else {
3423 panic!("expected CreateTable")
3424 };
3425 assert_eq!(c.name, "foo");
3426 assert_eq!(c.columns.len(), 1);
3427 assert_eq!(c.columns[0].name, "a");
3428 assert_eq!(c.columns[0].ty, ColumnTypeName::Int);
3429 assert!(c.columns[0].nullable);
3430 }
3431
3432 #[test]
3433 fn create_table_multi_column_with_not_null_mix() {
3434 let s = parse("CREATE TABLE u (id INT NOT NULL, name TEXT, score FLOAT NOT NULL, ok BOOL)");
3435 let Statement::CreateTable(c) = s else {
3436 panic!()
3437 };
3438 assert_eq!(c.columns.len(), 4);
3439 assert_eq!(c.columns[0].ty, ColumnTypeName::Int);
3440 assert!(!c.columns[0].nullable);
3441 assert_eq!(c.columns[1].ty, ColumnTypeName::Text);
3442 assert!(c.columns[1].nullable);
3443 assert_eq!(c.columns[2].ty, ColumnTypeName::Float);
3444 assert!(!c.columns[2].nullable);
3445 assert_eq!(c.columns[3].ty, ColumnTypeName::Bool);
3446 }
3447
3448 #[test]
3449 fn create_table_bigint_supported() {
3450 let s = parse("CREATE TABLE accounts (id BIGINT NOT NULL)");
3451 let Statement::CreateTable(c) = s else {
3452 panic!()
3453 };
3454 assert_eq!(c.columns[0].ty, ColumnTypeName::BigInt);
3455 }
3456
3457 #[test]
3458 fn create_table_vector_default_is_f32() {
3459 let s = parse("CREATE TABLE t (v VECTOR(128))");
3460 let Statement::CreateTable(c) = s else {
3461 panic!()
3462 };
3463 assert_eq!(
3464 c.columns[0].ty,
3465 ColumnTypeName::Vector {
3466 dim: 128,
3467 encoding: VecEncoding::F32,
3468 },
3469 );
3470 }
3471
3472 #[test]
3473 fn create_table_vector_using_sq8() {
3474 for sql in [
3477 "CREATE TABLE t (v VECTOR(128) USING SQ8)",
3478 "CREATE TABLE t (v VECTOR(128) using sq8)",
3479 ] {
3480 let s = parse(sql);
3481 let Statement::CreateTable(c) = s else {
3482 panic!()
3483 };
3484 assert_eq!(
3485 c.columns[0].ty,
3486 ColumnTypeName::Vector {
3487 dim: 128,
3488 encoding: VecEncoding::Sq8,
3489 },
3490 "{sql}",
3491 );
3492 }
3493 }
3494
3495 #[test]
3496 fn create_table_vector_using_unknown_errors() {
3497 let err = parse_statement("CREATE TABLE t (v VECTOR(8) USING PQ8)").unwrap_err();
3498 assert!(
3499 err.message.contains("unknown vector encoding"),
3500 "got: {}",
3501 err.message
3502 );
3503 }
3504
3505 #[test]
3506 fn vector_using_sq8_display_roundtrips() {
3507 let s = parse("CREATE TABLE t (v VECTOR(64) USING SQ8)");
3510 let Statement::CreateTable(c) = s else {
3511 panic!()
3512 };
3513 assert_eq!(c.columns[0].ty.to_string(), "VECTOR(64) USING SQ8");
3514 }
3515
3516 #[test]
3517 fn parser_recognises_placeholders() {
3518 use crate::ast::{Expr, SelectItem, Statement};
3519 let s = parse("SELECT $1, $2 + 1 FROM t WHERE x = $3");
3521 let Statement::Select(sel) = s else { panic!() };
3522 assert!(matches!(
3523 sel.items[0],
3524 SelectItem::Expr {
3525 expr: Expr::Placeholder(1),
3526 alias: None
3527 }
3528 ));
3529 let SelectItem::Expr {
3531 expr: Expr::Binary { lhs, rhs, .. },
3532 ..
3533 } = &sel.items[1]
3534 else {
3535 panic!()
3536 };
3537 assert!(matches!(**lhs, Expr::Placeholder(2)));
3538 assert!(matches!(**rhs, Expr::Literal(Literal::Integer(1))));
3539 let Some(Expr::Binary { rhs, .. }) = sel.where_.as_ref() else {
3541 panic!()
3542 };
3543 assert!(matches!(**rhs, Expr::Placeholder(3)));
3544 }
3545
3546 #[test]
3547 fn parser_rejects_dollar_zero() {
3548 assert!(parse_statement("SELECT $0").is_err());
3550 }
3551
3552 #[test]
3553 fn placeholder_display_roundtrips() {
3554 let s = parse("SELECT $42 FROM t");
3557 let printed = s.to_string();
3558 assert!(printed.contains("$42"));
3559 let again = parse(&printed);
3560 assert_eq!(s, again);
3561 }
3562
3563 #[test]
3564 fn alter_index_rebuild_bare() {
3565 use crate::ast::{AlterIndexTarget, Statement};
3566 let s = parse("ALTER INDEX my_idx REBUILD");
3567 let Statement::AlterIndex(a) = s else {
3568 panic!("expected AlterIndex, got {s:?}")
3569 };
3570 assert_eq!(a.name, "my_idx");
3571 assert_eq!(a.target, AlterIndexTarget::Rebuild { encoding: None });
3572 }
3573
3574 #[test]
3575 fn alter_index_rebuild_with_encoding() {
3576 use crate::ast::{AlterIndexTarget, Statement};
3577 for (sql, want) in [
3578 (
3579 "ALTER INDEX my_idx REBUILD WITH (encoding = F32)",
3580 VecEncoding::F32,
3581 ),
3582 (
3583 "ALTER INDEX my_idx REBUILD WITH (encoding = sq8)",
3584 VecEncoding::Sq8,
3585 ),
3586 (
3587 "ALTER INDEX my_idx REBUILD WITH (encoding = HALF)",
3588 VecEncoding::F16,
3589 ),
3590 ] {
3591 let s = parse(sql);
3592 let Statement::AlterIndex(a) = s else {
3593 panic!("{sql}: expected AlterIndex")
3594 };
3595 assert_eq!(a.name, "my_idx");
3596 assert_eq!(
3597 a.target,
3598 AlterIndexTarget::Rebuild {
3599 encoding: Some(want)
3600 },
3601 "{sql}"
3602 );
3603 }
3604 }
3605
3606 #[test]
3607 fn alter_index_rebuild_unknown_encoding_errors() {
3608 let err = parse_statement("ALTER INDEX my_idx REBUILD WITH (encoding = PQ8)").unwrap_err();
3609 assert!(
3610 err.message.contains("unknown vector encoding"),
3611 "got: {}",
3612 err.message
3613 );
3614 }
3615
3616 #[test]
3617 fn alter_index_rebuild_display_roundtrips() {
3618 for (input, want) in [
3619 ("ALTER INDEX my_idx REBUILD", "ALTER INDEX my_idx REBUILD"),
3620 (
3621 "ALTER INDEX my_idx REBUILD WITH (encoding = SQ8)",
3622 "ALTER INDEX my_idx REBUILD WITH (encoding = SQ8)",
3623 ),
3624 (
3625 "ALTER INDEX my_idx REBUILD WITH (encoding = HALF)",
3626 "ALTER INDEX my_idx REBUILD WITH (encoding = HALF)",
3627 ),
3628 ] {
3629 let s = parse(input);
3630 assert_eq!(s.to_string(), want);
3631 }
3632 }
3633
3634 #[test]
3635 fn create_table_unknown_type_errors() {
3636 let err = parse_statement("CREATE TABLE x (a xml)").unwrap_err();
3639 assert!(err.message.contains("unsupported column type"));
3640 }
3641
3642 #[test]
3643 fn create_table_missing_table_keyword_errors() {
3644 assert!(parse_statement("CREATE x (a INT)").is_err());
3645 }
3646
3647 #[test]
3648 fn insert_single_value() {
3649 let s = parse("INSERT INTO foo VALUES (42)");
3650 let Statement::Insert(i) = s else {
3651 panic!("expected Insert")
3652 };
3653 assert_eq!(i.table, "foo");
3654 assert_eq!(i.rows.len(), 1);
3655 assert_eq!(i.rows[0].len(), 1);
3656 assert!(matches!(i.rows[0][0], Expr::Literal(Literal::Integer(42))));
3657 }
3658
3659 #[test]
3660 fn insert_multi_value_with_mixed_literals() {
3661 let s = parse("INSERT INTO foo VALUES (1, 'hi', 3.14, TRUE, NULL)");
3662 let Statement::Insert(i) = s else { panic!() };
3663 assert_eq!(i.rows.len(), 1);
3664 assert_eq!(i.rows[0].len(), 5);
3665 }
3666
3667 #[test]
3668 fn insert_missing_into_errors() {
3669 assert!(parse_statement("INSERT foo VALUES (1)").is_err());
3670 }
3671
3672 #[test]
3673 fn create_table_round_trip() {
3674 let original =
3675 parse("CREATE TABLE foo (id BIGINT NOT NULL, label TEXT, score FLOAT NOT NULL)");
3676 let text = original.to_string();
3677 let again = parse_statement(&text).expect("re-parse");
3678 assert_eq!(original, again);
3679 }
3680
3681 #[test]
3682 fn insert_round_trip_with_negation_and_string() {
3683 let original = parse("INSERT INTO t VALUES (-1, 'it''s', NULL)");
3684 let text = original.to_string();
3685 let again = parse_statement(&text).expect("re-parse");
3686 assert_eq!(original, again);
3687 }
3688
3689 #[test]
3690 fn unknown_keyword_at_statement_start_errors() {
3691 let err = parse_statement("FROBNICATE foo SET x = 1").unwrap_err();
3694 assert!(err.message.contains("expected SELECT"));
3695 }
3696
3697 #[test]
3700 fn create_index_basic() {
3701 let s = parse("CREATE INDEX idx_id ON users (id)");
3702 let Statement::CreateIndex(c) = s else {
3703 panic!("expected CreateIndex")
3704 };
3705 assert_eq!(c.name, "idx_id");
3706 assert_eq!(c.table, "users");
3707 assert_eq!(c.column, "id");
3708 }
3709
3710 #[test]
3711 fn create_index_missing_on_errors() {
3712 assert!(parse_statement("CREATE INDEX foo users (id)").is_err());
3713 }
3714
3715 #[test]
3716 fn create_index_missing_paren_errors() {
3717 assert!(parse_statement("CREATE INDEX foo ON users id").is_err());
3718 }
3719
3720 #[test]
3721 fn create_index_round_trip() {
3722 let original = parse("CREATE INDEX by_name ON users (name)");
3723 let again = parse_statement(&original.to_string()).unwrap();
3724 assert_eq!(original, again);
3725 }
3726
3727 #[test]
3730 fn begin_commit_rollback_parse_as_unit_variants() {
3731 assert_eq!(parse("BEGIN"), Statement::Begin);
3732 assert_eq!(parse("COMMIT"), Statement::Commit);
3733 assert_eq!(parse("ROLLBACK"), Statement::Rollback);
3734 assert_eq!(parse("BEGIN;"), Statement::Begin);
3736 }
3737
3738 #[test]
3741 fn inner_product_binop_parses() {
3742 let s = parse("SELECT v <#> [1.0, 2.0] FROM t");
3743 let Statement::Select(s) = s else { panic!() };
3744 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3745 panic!()
3746 };
3747 assert!(matches!(
3748 expr,
3749 Expr::Binary {
3750 op: BinOp::InnerProduct,
3751 ..
3752 }
3753 ));
3754 }
3755
3756 #[test]
3757 fn cosine_distance_binop_parses() {
3758 let s = parse("SELECT v <=> [1.0, 2.0] FROM t");
3759 let Statement::Select(s) = s else { panic!() };
3760 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3761 panic!()
3762 };
3763 assert!(matches!(
3764 expr,
3765 Expr::Binary {
3766 op: BinOp::CosineDistance,
3767 ..
3768 }
3769 ));
3770 }
3771
3772 #[test]
3773 fn vector_cast_postfix_wraps_string_literal() {
3774 let s = parse("SELECT '[1,2,3]'::vector FROM t");
3775 let Statement::Select(s) = s else { panic!() };
3776 let SelectItem::Expr { expr, .. } = &s.items[0] else {
3777 panic!()
3778 };
3779 assert!(matches!(
3780 expr,
3781 Expr::Cast {
3782 target: CastTarget::Vector,
3783 ..
3784 }
3785 ));
3786 }
3787
3788 #[test]
3789 fn unsupported_cast_target_errors() {
3790 let err = parse_statement("SELECT 1::numeric FROM t").unwrap_err();
3792 assert!(err.message.contains("unsupported cast target"));
3793 }
3794
3795 #[test]
3796 fn tx_statements_round_trip() {
3797 for q in ["BEGIN", "COMMIT", "ROLLBACK"] {
3798 let original = parse(q);
3799 let again = parse_statement(&original.to_string()).unwrap();
3800 assert_eq!(original, again);
3801 }
3802 }
3803
3804 #[test]
3805 fn interval_text_parsing_units() {
3806 assert_eq!(parse_interval_text("1 day"), Some((0, 86_400_000_000)));
3808 assert_eq!(parse_interval_text("1 second"), Some((0, 1_000_000)));
3809 assert_eq!(parse_interval_text("1 month"), Some((1, 0)));
3810 assert_eq!(parse_interval_text("2 years"), Some((24, 0)));
3811 assert_eq!(parse_interval_text("1 year 6 months"), Some((18, 0)));
3813 assert_eq!(
3814 parse_interval_text("1 day 2 hours"),
3815 Some((0, 86_400_000_000 + 7_200_000_000))
3816 );
3817 assert_eq!(parse_interval_text("-1 day"), Some((0, -86_400_000_000)));
3819 assert_eq!(parse_interval_text(""), None);
3821 assert_eq!(parse_interval_text("garbage"), None);
3822 assert_eq!(parse_interval_text("1 fortnight"), None);
3823 assert_eq!(parse_interval_text("1"), None);
3824 }
3825
3826 #[test]
3827 fn interval_literal_roundtrips_via_display() {
3828 let parsed = parse("SELECT INTERVAL '1 day 2 hours'");
3829 let s = parsed.to_string();
3830 assert!(s.contains("INTERVAL '1 day 2 hours'"), "got: {s}");
3832 let again = parse_statement(&s).unwrap();
3834 assert_eq!(parsed, again);
3835 }
3836
3837 #[test]
3840 fn parser_recognises_create_publication_bare() {
3841 let s = parse("CREATE PUBLICATION pub_a");
3842 let Statement::CreatePublication(p) = s else {
3843 panic!("expected CreatePublication, got {s:?}")
3844 };
3845 assert_eq!(p.name, "pub_a");
3846 assert_eq!(p.scope, PublicationScope::AllTables);
3847 }
3848
3849 #[test]
3850 fn parser_recognises_create_publication_for_all_tables() {
3851 let s = parse("CREATE PUBLICATION pub_a FOR ALL TABLES");
3852 let Statement::CreatePublication(p) = s else {
3853 panic!("expected CreatePublication, got {s:?}")
3854 };
3855 assert_eq!(p.name, "pub_a");
3856 assert_eq!(p.scope, PublicationScope::AllTables);
3857 }
3858
3859 #[test]
3860 fn parser_recognises_drop_publication() {
3861 let s = parse("DROP PUBLICATION pub_a");
3862 let Statement::DropPublication(name) = s else {
3863 panic!("expected DropPublication, got {s:?}")
3864 };
3865 assert_eq!(name, "pub_a");
3866 }
3867
3868 #[test]
3869 fn parser_recognises_for_table_list() {
3870 let s = parse("CREATE PUBLICATION pub_a FOR TABLE t1, t2, t3");
3871 let Statement::CreatePublication(p) = s else {
3872 panic!("expected CreatePublication, got {s:?}")
3873 };
3874 assert_eq!(p.name, "pub_a");
3875 let PublicationScope::ForTables(ts) = p.scope else {
3876 panic!("expected ForTables scope")
3877 };
3878 assert_eq!(ts, alloc::vec!["t1", "t2", "t3"]);
3879 }
3880
3881 #[test]
3882 fn parser_recognises_for_tables_plural() {
3883 let s = parse("CREATE PUBLICATION pub_a FOR TABLES t1, t2");
3885 let Statement::CreatePublication(p) = s else {
3886 panic!("expected CreatePublication, got {s:?}")
3887 };
3888 let PublicationScope::ForTables(ts) = p.scope else {
3889 panic!("expected ForTables")
3890 };
3891 assert_eq!(ts, alloc::vec!["t1", "t2"]);
3892 }
3893
3894 #[test]
3895 fn parser_recognises_for_all_tables_except_list() {
3896 let s = parse("CREATE PUBLICATION p FOR ALL TABLES EXCEPT t1, t2");
3897 let Statement::CreatePublication(p) = s else {
3898 panic!()
3899 };
3900 let PublicationScope::AllTablesExcept(ts) = p.scope else {
3901 panic!("expected AllTablesExcept")
3902 };
3903 assert_eq!(ts, alloc::vec!["t1", "t2"]);
3904 }
3905
3906 #[test]
3907 fn parser_rejects_for_table_with_empty_list() {
3908 let err = parse_statement("CREATE PUBLICATION p FOR TABLE")
3910 .expect_err("must error on empty list");
3911 assert!(!err.message.is_empty());
3914 }
3915
3916 #[test]
3917 fn parser_recognises_show_publications() {
3918 let s = parse("SHOW PUBLICATIONS");
3921 assert!(matches!(s, Statement::ShowPublications));
3922 }
3923
3924 #[test]
3927 fn parser_recognises_create_subscription_single_publication() {
3928 let s = parse("CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a");
3929 let Statement::CreateSubscription(c) = s else {
3930 panic!("expected CreateSubscription, got {s:?}")
3931 };
3932 assert_eq!(c.name, "sub_a");
3933 assert_eq!(c.conn_str, "host=127.0.0.1 port=20002");
3934 assert_eq!(c.publications, alloc::vec!["pub_a"]);
3935 }
3936
3937 #[test]
3938 fn parser_recognises_create_subscription_multi_publication() {
3939 let s = parse(
3940 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=h' PUBLICATION p1, p2, p3",
3941 );
3942 let Statement::CreateSubscription(c) = s else {
3943 panic!()
3944 };
3945 assert_eq!(c.publications, alloc::vec!["p1", "p2", "p3"]);
3946 }
3947
3948 #[test]
3949 fn parser_rejects_create_subscription_missing_connection() {
3950 let err = parse_statement("CREATE SUBSCRIPTION s PUBLICATION p")
3951 .expect_err("must error on missing CONNECTION");
3952 assert!(err.message.contains("CONNECTION"), "got: {}", err.message);
3953 }
3954
3955 #[test]
3956 fn parser_rejects_create_subscription_missing_publication() {
3957 let err = parse_statement("CREATE SUBSCRIPTION s CONNECTION 'host=x'")
3958 .expect_err("must error on missing PUBLICATION");
3959 assert!(err.message.contains("PUBLICATION"), "got: {}", err.message);
3960 }
3961
3962 #[test]
3963 fn parser_recognises_drop_subscription() {
3964 let s = parse("DROP SUBSCRIPTION sub_a");
3965 let Statement::DropSubscription(name) = s else {
3966 panic!("expected DropSubscription, got {s:?}")
3967 };
3968 assert_eq!(name, "sub_a");
3969 }
3970
3971 #[test]
3972 fn parser_recognises_show_subscriptions() {
3973 let s = parse("SHOW SUBSCRIPTIONS");
3974 assert!(matches!(s, Statement::ShowSubscriptions));
3975 }
3976
3977 #[test]
3978 fn parser_recognises_wait_for_wal_position_no_timeout() {
3979 let s = parse("WAIT FOR WAL POSITION 12345");
3980 let Statement::WaitForWalPosition { pos, timeout_ms } = s else {
3981 panic!("expected WaitForWalPosition, got {s:?}")
3982 };
3983 assert_eq!(pos, 12345);
3984 assert!(timeout_ms.is_none());
3985 }
3986
3987 #[test]
3988 fn parser_recognises_wait_for_wal_position_with_timeout() {
3989 let s = parse("WAIT FOR WAL POSITION 67890 WITH TIMEOUT 5000");
3990 let Statement::WaitForWalPosition { pos, timeout_ms } = s else {
3991 panic!()
3992 };
3993 assert_eq!(pos, 67890);
3994 assert_eq!(timeout_ms, Some(5000));
3995 }
3996
3997 #[test]
3998 fn parser_rejects_wait_with_negative_position() {
3999 let err = parse_statement("WAIT FOR WAL POSITION -1").unwrap_err();
4005 assert!(!err.message.is_empty());
4006 }
4007
4008 #[test]
4009 fn parser_recognises_bare_analyze() {
4010 let s = parse("ANALYZE");
4011 assert!(matches!(s, Statement::Analyze(None)));
4012 }
4013
4014 #[test]
4015 fn parser_recognises_analyze_with_table() {
4016 let s = parse("ANALYZE users");
4017 let Statement::Analyze(Some(name)) = s else {
4018 panic!("expected Analyze, got {s:?}")
4019 };
4020 assert_eq!(name, "users");
4021 }
4022
4023 #[test]
4024 fn parser_recognises_analyze_with_quoted_table() {
4025 let s = parse("ANALYZE \"Mixed Case\"");
4026 let Statement::Analyze(Some(name)) = s else {
4027 panic!()
4028 };
4029 assert_eq!(name, "Mixed Case");
4030 }
4031
4032 #[test]
4033 fn parser_rejects_analyze_with_garbage_token() {
4034 let err = parse_statement("ANALYZE 42").expect_err("must error");
4035 assert!(!err.message.is_empty());
4036 }
4037
4038 #[test]
4039 fn analyze_display_roundtrips() {
4040 for sql in ["ANALYZE", "ANALYZE users"] {
4041 let s = parse(sql);
4042 let printed = s.to_string();
4043 let again = parse_statement(&printed)
4044 .unwrap_or_else(|e| panic!("re-parse failed for {printed:?}: {e}"));
4045 assert_eq!(s, again);
4046 }
4047 }
4048
4049 #[test]
4050 fn wait_for_display_roundtrips() {
4051 for sql in [
4052 "WAIT FOR WAL POSITION 12345",
4053 "WAIT FOR WAL POSITION 67890 WITH TIMEOUT 5000",
4054 ] {
4055 let s = parse(sql);
4056 let printed = s.to_string();
4057 let again = parse_statement(&printed)
4058 .unwrap_or_else(|e| panic!("re-parse failed for {printed:?}: {e}"));
4059 assert_eq!(s, again, "round-trip mismatch for {sql:?}");
4060 }
4061 }
4062
4063 #[test]
4064 fn subscription_ddl_display_roundtrips() {
4065 for sql in [
4066 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=h port=20002' PUBLICATION pub_a",
4067 "CREATE SUBSCRIPTION sub_b CONNECTION 'host=h' PUBLICATION p1, p2",
4068 "DROP SUBSCRIPTION sub_a",
4069 "SHOW SUBSCRIPTIONS",
4070 ] {
4071 let s = parse(sql);
4072 let printed = s.to_string();
4073 let again = parse_statement(&printed)
4074 .unwrap_or_else(|e| panic!("re-parse failed for {printed:?}: {e}"));
4075 assert_eq!(s, again, "round-trip mismatch for {sql:?}");
4076 }
4077 }
4078
4079 #[test]
4080 fn parser_drop_dispatches_user_vs_publication() {
4081 let s = parse("DROP USER 'alice'");
4084 let Statement::DropUser(name) = s else {
4085 panic!("expected DropUser, got {s:?}")
4086 };
4087 assert_eq!(name, "alice");
4088 let s = parse("DROP PUBLICATION p1");
4090 assert!(matches!(s, Statement::DropPublication(_)));
4091 }
4092
4093 #[test]
4094 fn publication_ddl_display_roundtrips() {
4095 for sql in [
4098 "CREATE PUBLICATION pub_a",
4099 "CREATE PUBLICATION pub_a FOR ALL TABLES",
4100 "CREATE PUBLICATION pub_a FOR TABLE t1, t2",
4101 "CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t1",
4102 "DROP PUBLICATION pub_a",
4103 "SHOW PUBLICATIONS",
4104 ] {
4105 let s = parse(sql);
4106 let printed = s.to_string();
4107 let again = parse_statement(&printed)
4108 .unwrap_or_else(|e| panic!("re-parse failed for {printed:?}: {e}"));
4109 assert_eq!(s, again, "round-trip mismatch for {sql:?}");
4110 }
4111 }
4112}