Skip to main content

fsqlite_parser/
parser.rs

1// bd-2tu6: §10.2 SQL Parser
2//
3// Hand-written recursive descent parser. Expression parsing lives in expr.rs.
4
5use std::error::Error;
6use std::fmt;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9use fsqlite_ast::{
10    AlterTableAction, AlterTableStatement, Assignment, AssignmentTarget, AttachStatement,
11    BeginStatement, ColumnConstraint, ColumnConstraintKind, ColumnDef, CompoundOp, ConflictAction,
12    CreateIndexStatement, CreateTableBody, CreateTableStatement, CreateTriggerStatement,
13    CreateViewStatement, CreateVirtualTableStatement, Cte, CteMaterialized, DefaultValue,
14    Deferrable, DeferrableInitially, DeleteStatement, Distinctness, DropObjectType, DropStatement,
15    Expr, ForeignKeyAction, ForeignKeyActionType, ForeignKeyClause, ForeignKeyTrigger, FrameBound,
16    FrameExclude, FrameSpec, FrameType, FromClause, GeneratedStorage, IndexHint, IndexedColumn,
17    InsertSource, InsertStatement, JoinClause, JoinConstraint, JoinKind, JoinType, LimitClause,
18    Literal, NullsOrder, OrderingTerm, PragmaStatement, PragmaValue, QualifiedName,
19    QualifiedTableRef, ResultColumn, RollbackStatement, SelectBody, SelectCore, SelectStatement,
20    SortDirection, Span, Statement, TableConstraint, TableConstraintKind, TableOrSubquery,
21    TransactionMode, TriggerEvent, TriggerTiming, TypeName, UpdateStatement, UpsertAction,
22    UpsertClause, UpsertTarget, VacuumStatement, WindowDef, WindowSpec, WithClause,
23};
24
25use crate::lexer::Lexer;
26use crate::token::{Token, TokenKind};
27
28// ---------------------------------------------------------------------------
29// Parse metrics
30// ---------------------------------------------------------------------------
31
32/// Monotonic counter of successfully parsed statements.
33static FSQLITE_PARSE_STATEMENTS_TOTAL: AtomicU64 = AtomicU64::new(0);
34
35/// Point-in-time parse metrics snapshot.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub struct ParseMetricsSnapshot {
38    /// Total statements successfully parsed.
39    pub fsqlite_parse_statements_total: u64,
40}
41
42/// Take a point-in-time snapshot of parse metrics.
43#[must_use]
44pub fn parse_metrics_snapshot() -> ParseMetricsSnapshot {
45    ParseMetricsSnapshot {
46        fsqlite_parse_statements_total: FSQLITE_PARSE_STATEMENTS_TOTAL.load(Ordering::Relaxed),
47    }
48}
49
50/// Reset parse metrics (used by tests/diagnostics).
51pub fn reset_parse_metrics() {
52    FSQLITE_PARSE_STATEMENTS_TOTAL.store(0, Ordering::Relaxed);
53}
54
55// ---------------------------------------------------------------------------
56// Error type
57// ---------------------------------------------------------------------------
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct ParseError {
61    pub message: String,
62    pub span: Span,
63    pub line: u32,
64    pub col: u32,
65}
66
67impl ParseError {
68    #[must_use]
69    pub(crate) fn at(message: impl Into<String>, token: Option<&Token>) -> Self {
70        if let Some(t) = token {
71            Self {
72                message: message.into(),
73                span: t.span,
74                line: t.line,
75                col: t.col,
76            }
77        } else {
78            Self {
79                message: message.into(),
80                span: Span::ZERO,
81                line: 0,
82                col: 0,
83            }
84        }
85    }
86}
87
88impl fmt::Display for ParseError {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "{}:{}: {}", self.line, self.col, self.message)
91    }
92}
93
94impl Error for ParseError {}
95
96// ---------------------------------------------------------------------------
97// Parser
98// ---------------------------------------------------------------------------
99
100pub struct Parser {
101    pub(crate) tokens: Vec<Token>,
102    pub(crate) pos: usize,
103    pub(crate) errors: Vec<ParseError>,
104}
105
106impl Parser {
107    #[must_use]
108    pub fn new(tokens: Vec<Token>) -> Self {
109        Self {
110            tokens,
111            pos: 0,
112            errors: Vec::new(),
113        }
114    }
115
116    #[must_use]
117    pub fn from_sql(sql: &str) -> Self {
118        Self::new(Lexer::tokenize(sql))
119    }
120
121    pub fn parse_all(&mut self) -> (Vec<Statement>, Vec<ParseError>) {
122        let span = tracing::debug_span!(
123            target: "fsqlite.parse",
124            "parse",
125            ast_node_count = tracing::field::Empty,
126            parse_errors = tracing::field::Empty,
127        );
128        let _guard = span.enter();
129
130        let mut stmts = Vec::new();
131        while !self.at_eof() {
132            if self.check(&TokenKind::Semicolon) {
133                self.advance();
134                continue;
135            }
136            match self.parse_statement() {
137                Ok(s) => {
138                    FSQLITE_PARSE_STATEMENTS_TOTAL.fetch_add(1, Ordering::Relaxed);
139                    stmts.push(s);
140                    let _ = self.eat(&TokenKind::Semicolon);
141                }
142                Err(e) => {
143                    tracing::warn!(
144                        target: "fsqlite.parse",
145                        error = %e,
146                        "parse recovery: skipping malformed statement"
147                    );
148                    self.errors.push(e);
149                    self.synchronize();
150                }
151            }
152        }
153
154        let errors = std::mem::take(&mut self.errors);
155        span.record("ast_node_count", stmts.len() as u64);
156        span.record("parse_errors", errors.len() as u64);
157
158        (stmts, errors)
159    }
160
161    pub fn parse_statement(&mut self) -> Result<Statement, ParseError> {
162        self.parse_statement_inner()
163    }
164
165    #[must_use]
166    pub fn errors(&self) -> &[ParseError] {
167        &self.errors
168    }
169
170    // -----------------------------------------------------------------------
171    // Token navigation
172    // -----------------------------------------------------------------------
173
174    pub(crate) fn peek(&self) -> &TokenKind {
175        self.current().map_or(&TokenKind::Eof, |t| &t.kind)
176    }
177
178    pub(crate) fn current(&self) -> Option<&Token> {
179        self.tokens.get(self.pos)
180    }
181
182    pub(crate) fn peek_nth(&self, n: usize) -> &TokenKind {
183        self.tokens
184            .get(self.pos + n)
185            .map_or(&TokenKind::Eof, |t| &t.kind)
186    }
187
188    pub(crate) fn at_eof(&self) -> bool {
189        matches!(self.peek(), TokenKind::Eof)
190    }
191
192    pub(crate) fn advance(&mut self) -> Option<&Token> {
193        let t = self.tokens.get(self.pos);
194        if self.pos < self.tokens.len().saturating_sub(1) {
195            self.pos += 1;
196        }
197        t
198    }
199
200    pub(crate) fn check(&self, kind: &TokenKind) -> bool {
201        std::mem::discriminant(self.peek()) == std::mem::discriminant(kind)
202    }
203
204    pub(crate) fn check_kw(&self, kw: &TokenKind) -> bool {
205        self.peek() == kw
206    }
207
208    pub(crate) fn eat(&mut self, kind: &TokenKind) -> bool {
209        if self.check(kind) {
210            self.advance();
211            true
212        } else {
213            false
214        }
215    }
216
217    pub(crate) fn eat_kw(&mut self, kw: &TokenKind) -> bool {
218        if self.peek() == kw {
219            self.advance();
220            true
221        } else {
222            false
223        }
224    }
225
226    pub(crate) fn expect_kw(&mut self, kw: &TokenKind) -> Result<Span, ParseError> {
227        if self.peek() == kw {
228            let sp = self.current_span();
229            self.advance();
230            Ok(sp)
231        } else {
232            Err(self.err_expected(&format!("{kw:?}")))
233        }
234    }
235
236    pub(crate) fn expect_token(&mut self, kind: &TokenKind) -> Result<Span, ParseError> {
237        if self.check(kind) {
238            let sp = self.current_span();
239            self.advance();
240            Ok(sp)
241        } else {
242            Err(self.err_expected(&format!("{kind:?}")))
243        }
244    }
245
246    pub(crate) fn current_span(&self) -> Span {
247        self.current().map_or(Span::ZERO, |t| t.span)
248    }
249
250    pub(crate) fn err_expected(&self, what: &str) -> ParseError {
251        ParseError::at(format!("expected {what}"), self.current())
252    }
253
254    pub(crate) fn err_msg(&self, msg: impl Into<String>) -> ParseError {
255        ParseError::at(msg, self.current())
256    }
257
258    fn synchronize(&mut self) {
259        loop {
260            match self.peek() {
261                TokenKind::Eof => return,
262                TokenKind::Semicolon => {
263                    self.advance();
264                    return;
265                }
266                k if k.is_statement_start() => return,
267                _ => {
268                    self.advance();
269                }
270            }
271        }
272    }
273
274    // -----------------------------------------------------------------------
275    // Identifiers and names
276    // -----------------------------------------------------------------------
277
278    pub(crate) fn parse_identifier(&mut self) -> Result<String, ParseError> {
279        match self.peek().clone() {
280            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
281                self.advance();
282                Ok(s)
283            }
284            ref k if is_nonreserved_kw(k) => {
285                let s = kw_to_str(k);
286                self.advance();
287                Ok(s)
288            }
289            _ => Err(self.err_expected("identifier")),
290        }
291    }
292
293    pub(crate) fn parse_qualified_name(&mut self) -> Result<QualifiedName, ParseError> {
294        let first = self.parse_identifier()?;
295        if self.eat(&TokenKind::Dot) {
296            let second = self.parse_identifier()?;
297            Ok(QualifiedName::qualified(first, second))
298        } else {
299            Ok(QualifiedName::bare(first))
300        }
301    }
302
303    fn parse_qualified_table_ref(&mut self) -> Result<QualifiedTableRef, ParseError> {
304        let name = self.parse_qualified_name()?;
305        let alias = self.try_alias()?;
306        let index_hint = self.parse_index_hint()?;
307        Ok(QualifiedTableRef {
308            name,
309            alias,
310            index_hint,
311        })
312    }
313
314    fn try_alias(&mut self) -> Result<Option<String>, ParseError> {
315        if self.eat_kw(&TokenKind::KwAs) {
316            return Ok(Some(self.parse_identifier()?));
317        }
318        // Peek for an identifier that isn't a keyword starting the next clause.
319        // We also accept non-reserved keywords as implicit aliases.
320        match self.peek() {
321            TokenKind::Id(_) | TokenKind::QuotedId(_, _) => {
322                return Ok(Some(self.parse_identifier()?));
323            }
324            k if is_nonreserved_kw(k) && !is_alias_terminator_kw(k) => {
325                return Ok(Some(self.parse_identifier()?));
326            }
327            _ => {}
328        }
329        Ok(None)
330    }
331
332    fn parse_index_hint(&mut self) -> Result<Option<IndexHint>, ParseError> {
333        if self.eat_kw(&TokenKind::KwIndexed) {
334            self.expect_kw(&TokenKind::KwBy)?;
335            Ok(Some(IndexHint::IndexedBy(self.parse_identifier()?)))
336        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwIndexed {
337            self.advance();
338            self.advance();
339            Ok(Some(IndexHint::NotIndexed))
340        } else {
341            Ok(None)
342        }
343    }
344
345    pub(crate) fn parse_comma_sep<T>(
346        &mut self,
347        f: fn(&mut Self) -> Result<T, ParseError>,
348    ) -> Result<Vec<T>, ParseError> {
349        let mut v = vec![f(self)?];
350        while self.eat(&TokenKind::Comma) {
351            v.push(f(self)?);
352        }
353        Ok(v)
354    }
355
356    // -----------------------------------------------------------------------
357    // Statement dispatch
358    // -----------------------------------------------------------------------
359
360    fn parse_statement_inner(&mut self) -> Result<Statement, ParseError> {
361        match self.peek().clone() {
362            TokenKind::KwSelect | TokenKind::KwValues => {
363                Ok(Statement::Select(self.parse_select_stmt(None)?))
364            }
365            TokenKind::KwWith => self.parse_with_leading(),
366            TokenKind::KwInsert | TokenKind::KwReplace => self.parse_insert_stmt(None),
367            TokenKind::KwUpdate => self.parse_update_stmt(None),
368            TokenKind::KwDelete => self.parse_delete_stmt(None),
369            TokenKind::KwCreate => self.parse_create(),
370            TokenKind::KwDrop => self.parse_drop(),
371            TokenKind::KwAlter => self.parse_alter(),
372            TokenKind::KwBegin => self.parse_begin(),
373            TokenKind::KwCommit | TokenKind::KwEnd => {
374                self.advance();
375                let _ = self.eat_kw(&TokenKind::KwTransaction);
376                Ok(Statement::Commit)
377            }
378            TokenKind::KwRollback => self.parse_rollback(),
379            TokenKind::KwSavepoint => {
380                self.advance();
381                Ok(Statement::Savepoint(self.parse_identifier()?))
382            }
383            TokenKind::KwRelease => {
384                self.advance();
385                let _ = self.eat_kw(&TokenKind::KwSavepoint);
386                Ok(Statement::Release(self.parse_identifier()?))
387            }
388            TokenKind::KwAttach => self.parse_attach(),
389            TokenKind::KwDetach => {
390                self.advance();
391                let _ = self.eat_kw(&TokenKind::KwDatabase);
392                Ok(Statement::Detach(self.parse_identifier()?))
393            }
394            TokenKind::KwPragma => self.parse_pragma(),
395            TokenKind::KwVacuum => self.parse_vacuum(),
396            TokenKind::KwReindex => {
397                self.advance();
398                let name = if !self.at_eof() && !self.check(&TokenKind::Semicolon) {
399                    Some(self.parse_qualified_name()?)
400                } else {
401                    None
402                };
403                Ok(Statement::Reindex(name))
404            }
405            TokenKind::KwAnalyze => {
406                self.advance();
407                let name = if !self.at_eof() && !self.check(&TokenKind::Semicolon) {
408                    Some(self.parse_qualified_name()?)
409                } else {
410                    None
411                };
412                Ok(Statement::Analyze(name))
413            }
414            TokenKind::KwExplain => self.parse_explain(),
415            _ => Err(self.err_msg("unexpected token at start of statement")),
416        }
417    }
418
419    // -----------------------------------------------------------------------
420    // WITH ... (SELECT | INSERT | UPDATE | DELETE)
421    // -----------------------------------------------------------------------
422
423    fn parse_with_leading(&mut self) -> Result<Statement, ParseError> {
424        let with = self.parse_with_clause()?;
425        match self.peek() {
426            TokenKind::KwSelect | TokenKind::KwValues => {
427                Ok(Statement::Select(self.parse_select_stmt(Some(with))?))
428            }
429            TokenKind::KwInsert | TokenKind::KwReplace => self.parse_insert_stmt(Some(with)),
430            TokenKind::KwUpdate => self.parse_update_stmt(Some(with)),
431            TokenKind::KwDelete => self.parse_delete_stmt(Some(with)),
432            _ => Err(self.err_expected("SELECT, INSERT, UPDATE, or DELETE after WITH")),
433        }
434    }
435
436    fn parse_with_clause(&mut self) -> Result<WithClause, ParseError> {
437        self.expect_kw(&TokenKind::KwWith)?;
438        let recursive = self.eat_kw(&TokenKind::KwRecursive);
439        let ctes = self.parse_comma_sep(Self::parse_cte)?;
440        Ok(WithClause { recursive, ctes })
441    }
442
443    fn parse_cte(&mut self) -> Result<Cte, ParseError> {
444        let name = self.parse_identifier()?;
445        let columns = if self.eat(&TokenKind::LeftParen) {
446            let cols = self.parse_comma_sep(Self::parse_identifier)?;
447            self.expect_token(&TokenKind::RightParen)?;
448            cols
449        } else {
450            vec![]
451        };
452        // SQL syntax: name AS [NOT] MATERIALIZED (subquery)
453        self.expect_kw(&TokenKind::KwAs)?;
454        let materialized = if self.check_kw(&TokenKind::KwNot) {
455            self.advance();
456            self.expect_kw(&TokenKind::KwMaterialized)?;
457            Some(CteMaterialized::NotMaterialized)
458        } else if self.eat_kw(&TokenKind::KwMaterialized) {
459            Some(CteMaterialized::Materialized)
460        } else {
461            None
462        };
463        self.expect_token(&TokenKind::LeftParen)?;
464        let query = self.parse_select_stmt(None)?;
465        self.expect_token(&TokenKind::RightParen)?;
466        Ok(Cte {
467            name,
468            columns,
469            materialized,
470            query,
471        })
472    }
473
474    // -----------------------------------------------------------------------
475    // SELECT
476    // -----------------------------------------------------------------------
477
478    pub(crate) fn parse_select_stmt(
479        &mut self,
480        with: Option<WithClause>,
481    ) -> Result<SelectStatement, ParseError> {
482        let body = self.parse_select_body()?;
483        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
484            self.expect_kw(&TokenKind::KwBy)?;
485            self.parse_comma_sep(Self::parse_ordering_term)?
486        } else {
487            vec![]
488        };
489        let limit = self.parse_limit()?;
490        Ok(SelectStatement {
491            with,
492            body,
493            order_by,
494            limit,
495        })
496    }
497
498    fn parse_select_body(&mut self) -> Result<SelectBody, ParseError> {
499        let select = self.parse_select_core()?;
500        let mut compounds = Vec::new();
501        loop {
502            let op = if self.eat_kw(&TokenKind::KwUnion) {
503                if self.eat_kw(&TokenKind::KwAll) {
504                    CompoundOp::UnionAll
505                } else {
506                    CompoundOp::Union
507                }
508            } else if self.eat_kw(&TokenKind::KwIntersect) {
509                CompoundOp::Intersect
510            } else if self.eat_kw(&TokenKind::KwExcept) {
511                CompoundOp::Except
512            } else {
513                break;
514            };
515            compounds.push((op, self.parse_select_core()?));
516        }
517        Ok(SelectBody { select, compounds })
518    }
519
520    fn parse_select_core(&mut self) -> Result<SelectCore, ParseError> {
521        if self.eat_kw(&TokenKind::KwValues) {
522            return self.parse_values_core();
523        }
524        self.expect_kw(&TokenKind::KwSelect)?;
525        let distinct = if self.eat_kw(&TokenKind::KwDistinct) {
526            Distinctness::Distinct
527        } else {
528            let _ = self.eat_kw(&TokenKind::KwAll);
529            Distinctness::All
530        };
531        let columns = self.parse_comma_sep(Self::parse_result_column)?;
532        let from = if self.eat_kw(&TokenKind::KwFrom) {
533            Some(self.parse_from_clause()?)
534        } else {
535            None
536        };
537        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
538            Some(Box::new(self.parse_expr()?))
539        } else {
540            None
541        };
542        let group_by = if self.eat_kw(&TokenKind::KwGroup) {
543            self.expect_kw(&TokenKind::KwBy)?;
544            self.parse_comma_sep(Self::parse_expr)?
545        } else {
546            vec![]
547        };
548        let having = if self.eat_kw(&TokenKind::KwHaving) {
549            Some(Box::new(self.parse_expr()?))
550        } else {
551            None
552        };
553        let windows = if self.eat_kw(&TokenKind::KwWindow) {
554            self.parse_comma_sep(Self::parse_window_def)?
555        } else {
556            vec![]
557        };
558        Ok(SelectCore::Select {
559            distinct,
560            columns,
561            from,
562            where_clause,
563            group_by,
564            having,
565            windows,
566        })
567    }
568
569    fn parse_values_core(&mut self) -> Result<SelectCore, ParseError> {
570        let mut rows = Vec::new();
571        loop {
572            self.expect_token(&TokenKind::LeftParen)?;
573            let row = self.parse_comma_sep(Self::parse_expr)?;
574            self.expect_token(&TokenKind::RightParen)?;
575            rows.push(row);
576            if !self.eat(&TokenKind::Comma) {
577                break;
578            }
579        }
580        Ok(SelectCore::Values(rows))
581    }
582
583    fn parse_result_column(&mut self) -> Result<ResultColumn, ParseError> {
584        if self.eat(&TokenKind::Star) {
585            return Ok(ResultColumn::Star);
586        }
587        // table.* check: identifier followed by dot-star.
588        if matches!(self.peek(), TokenKind::Id(_) | TokenKind::QuotedId(_, _))
589            && self.peek_nth(1) == &TokenKind::Dot
590            && self.peek_nth(2) == &TokenKind::Star
591        {
592            let tbl = self.parse_identifier()?;
593            self.advance(); // dot
594            self.advance(); // star
595            return Ok(ResultColumn::TableStar(tbl));
596        }
597        let expr = self.parse_expr()?;
598        let alias = self.try_alias()?;
599        Ok(ResultColumn::Expr { expr, alias })
600    }
601
602    // -----------------------------------------------------------------------
603    // FROM clause & JOINs
604    // -----------------------------------------------------------------------
605
606    fn parse_from_clause(&mut self) -> Result<FromClause, ParseError> {
607        let source = self.parse_table_or_subquery()?;
608        let mut joins = Vec::new();
609        loop {
610            if let Some(jt) = self.try_join_type()? {
611                let table = self.parse_table_or_subquery()?;
612                let constraint = self.parse_join_constraint()?;
613                joins.push(JoinClause {
614                    join_type: jt,
615                    table,
616                    constraint,
617                });
618            } else if self.eat(&TokenKind::Comma) {
619                let table = self.parse_table_or_subquery()?;
620                joins.push(JoinClause {
621                    join_type: JoinType {
622                        natural: false,
623                        kind: JoinKind::Cross,
624                    },
625                    table,
626                    constraint: None,
627                });
628            } else {
629                break;
630            }
631        }
632        Ok(FromClause { source, joins })
633    }
634
635    fn parse_table_or_subquery(&mut self) -> Result<TableOrSubquery, ParseError> {
636        if self.check(&TokenKind::LeftParen) {
637            self.advance();
638            if matches!(
639                self.peek(),
640                TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
641            ) {
642                let q = self.parse_select_stmt(None)?;
643                self.expect_token(&TokenKind::RightParen)?;
644                let alias = self.try_alias()?;
645                return Ok(TableOrSubquery::Subquery {
646                    query: Box::new(q),
647                    alias,
648                });
649            }
650            // Parenthesized join
651            let fc = self.parse_from_clause()?;
652            self.expect_token(&TokenKind::RightParen)?;
653            return Ok(TableOrSubquery::ParenJoin(Box::new(fc)));
654        }
655
656        let name = self.parse_qualified_name()?;
657
658        // Table-valued function: name(args)
659        if self.check(&TokenKind::LeftParen) && name.schema.is_none() {
660            self.advance();
661            let args = if self.check(&TokenKind::RightParen) {
662                vec![]
663            } else {
664                self.parse_comma_sep(Self::parse_expr)?
665            };
666            self.expect_token(&TokenKind::RightParen)?;
667            let alias = self.try_alias()?;
668            return Ok(TableOrSubquery::TableFunction {
669                name: name.name,
670                args,
671                alias,
672            });
673        }
674
675        let alias = self.try_alias()?;
676        let index_hint = self.parse_index_hint()?;
677        Ok(TableOrSubquery::Table {
678            name,
679            alias,
680            index_hint,
681        })
682    }
683
684    fn try_join_type(&mut self) -> Result<Option<JoinType>, ParseError> {
685        let natural = self.eat_kw(&TokenKind::KwNatural);
686        let kind = if self.eat_kw(&TokenKind::KwJoin) {
687            Some(JoinKind::Inner)
688        } else if self.eat_kw(&TokenKind::KwInner) {
689            self.expect_kw(&TokenKind::KwJoin)?;
690            Some(JoinKind::Inner)
691        } else if self.eat_kw(&TokenKind::KwCross) {
692            self.expect_kw(&TokenKind::KwJoin)?;
693            Some(JoinKind::Cross)
694        } else if self.eat_kw(&TokenKind::KwLeft) {
695            let _ = self.eat_kw(&TokenKind::KwOuter);
696            self.expect_kw(&TokenKind::KwJoin)?;
697            Some(JoinKind::Left)
698        } else if self.eat_kw(&TokenKind::KwRight) {
699            let _ = self.eat_kw(&TokenKind::KwOuter);
700            self.expect_kw(&TokenKind::KwJoin)?;
701            Some(JoinKind::Right)
702        } else if self.eat_kw(&TokenKind::KwFull) {
703            let _ = self.eat_kw(&TokenKind::KwOuter);
704            self.expect_kw(&TokenKind::KwJoin)?;
705            Some(JoinKind::Full)
706        } else {
707            None
708        };
709        match kind {
710            Some(k) => Ok(Some(JoinType { natural, kind: k })),
711            None if natural => Err(self.err_expected("JOIN after NATURAL")),
712            None => Ok(None),
713        }
714    }
715
716    fn parse_join_constraint(&mut self) -> Result<Option<JoinConstraint>, ParseError> {
717        if self.eat_kw(&TokenKind::KwOn) {
718            Ok(Some(JoinConstraint::On(self.parse_expr()?)))
719        } else if self.eat_kw(&TokenKind::KwUsing) {
720            self.expect_token(&TokenKind::LeftParen)?;
721            let cols = self.parse_comma_sep(Self::parse_identifier)?;
722            self.expect_token(&TokenKind::RightParen)?;
723            Ok(Some(JoinConstraint::Using(cols)))
724        } else {
725            Ok(None)
726        }
727    }
728
729    // -----------------------------------------------------------------------
730    // ORDER BY / LIMIT
731    // -----------------------------------------------------------------------
732
733    pub(crate) fn parse_ordering_term(&mut self) -> Result<OrderingTerm, ParseError> {
734        let expr = self.parse_expr()?;
735        let direction = if self.eat_kw(&TokenKind::KwAsc) {
736            Some(SortDirection::Asc)
737        } else if self.eat_kw(&TokenKind::KwDesc) {
738            Some(SortDirection::Desc)
739        } else {
740            None
741        };
742        let nulls = if self.eat_kw(&TokenKind::KwNulls) {
743            if self.eat_kw(&TokenKind::KwFirst) {
744                Some(NullsOrder::First)
745            } else {
746                self.expect_kw(&TokenKind::KwLast)?;
747                Some(NullsOrder::Last)
748            }
749        } else {
750            None
751        };
752        Ok(OrderingTerm {
753            expr,
754            direction,
755            nulls,
756        })
757    }
758
759    pub(crate) fn parse_limit(&mut self) -> Result<Option<LimitClause>, ParseError> {
760        if !self.eat_kw(&TokenKind::KwLimit) {
761            return Ok(None);
762        }
763        let first = self.parse_expr()?;
764        if self.eat_kw(&TokenKind::KwOffset) {
765            return Ok(Some(LimitClause {
766                limit: first,
767                offset: Some(self.parse_expr()?),
768            }));
769        }
770
771        if self.eat(&TokenKind::Comma) {
772            // LIMIT offset, count — SQLite/MySQL compatibility form.
773            let second = self.parse_expr()?;
774            return Ok(Some(LimitClause {
775                limit: second,
776                offset: Some(first),
777            }));
778        }
779
780        Ok(Some(LimitClause {
781            limit: first,
782            offset: None,
783        }))
784    }
785
786    // -----------------------------------------------------------------------
787    // RETURNING clause
788    // -----------------------------------------------------------------------
789
790    fn parse_returning(&mut self) -> Result<Vec<ResultColumn>, ParseError> {
791        if self.eat_kw(&TokenKind::KwReturning) {
792            self.parse_comma_sep(Self::parse_result_column)
793        } else {
794            Ok(vec![])
795        }
796    }
797
798    // -----------------------------------------------------------------------
799    // INSERT
800    // -----------------------------------------------------------------------
801
802    fn parse_insert_stmt(&mut self, with: Option<WithClause>) -> Result<Statement, ParseError> {
803        let or_conflict = if self.eat_kw(&TokenKind::KwReplace) {
804            Some(ConflictAction::Replace)
805        } else {
806            self.expect_kw(&TokenKind::KwInsert)?;
807            if self.eat_kw(&TokenKind::KwOr) {
808                Some(self.parse_conflict_action()?)
809            } else {
810                None
811            }
812        };
813        self.expect_kw(&TokenKind::KwInto)?;
814        let table = self.parse_qualified_name()?;
815        let alias = if self.eat_kw(&TokenKind::KwAs) {
816            Some(self.parse_identifier()?)
817        } else {
818            None
819        };
820        let columns = if self.check(&TokenKind::LeftParen)
821            && !matches!(self.peek_nth(1), TokenKind::KwSelect | TokenKind::KwWith)
822        {
823            self.advance();
824            let cols = self.parse_comma_sep(Self::parse_identifier)?;
825            self.expect_token(&TokenKind::RightParen)?;
826            cols
827        } else {
828            vec![]
829        };
830        let source = if self.eat_kw(&TokenKind::KwDefault) {
831            self.expect_kw(&TokenKind::KwValues)?;
832            InsertSource::DefaultValues
833        } else if self.eat_kw(&TokenKind::KwValues) {
834            match self.parse_values_core()? {
835                SelectCore::Values(rows) => InsertSource::Values(rows),
836                SelectCore::Select { .. } => unreachable!("parse_values_core must return VALUES"),
837            }
838        } else {
839            InsertSource::Select(Box::new(self.parse_select_stmt(None)?))
840        };
841        let upsert = self.parse_upsert_clauses()?;
842        let returning = self.parse_returning()?;
843        Ok(Statement::Insert(InsertStatement {
844            with,
845            or_conflict,
846            table,
847            alias,
848            columns,
849            source,
850            upsert,
851            returning,
852        }))
853    }
854
855    fn parse_conflict_action(&mut self) -> Result<ConflictAction, ParseError> {
856        if self.eat_kw(&TokenKind::KwRollback) {
857            Ok(ConflictAction::Rollback)
858        } else if self.eat_kw(&TokenKind::KwAbort) {
859            Ok(ConflictAction::Abort)
860        } else if self.eat_kw(&TokenKind::KwFail) {
861            Ok(ConflictAction::Fail)
862        } else if self.eat_kw(&TokenKind::KwIgnore) {
863            Ok(ConflictAction::Ignore)
864        } else if self.eat_kw(&TokenKind::KwReplace) {
865            Ok(ConflictAction::Replace)
866        } else {
867            Err(self.err_expected("conflict action"))
868        }
869    }
870
871    fn parse_upsert_clauses(&mut self) -> Result<Vec<UpsertClause>, ParseError> {
872        let mut clauses = Vec::new();
873        while self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
874            self.advance(); // ON
875            self.advance(); // CONFLICT
876            let target = if self.check(&TokenKind::LeftParen) {
877                self.advance();
878                let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
879                self.expect_token(&TokenKind::RightParen)?;
880                let wh = if self.eat_kw(&TokenKind::KwWhere) {
881                    Some(self.parse_expr()?)
882                } else {
883                    None
884                };
885                Some(UpsertTarget {
886                    columns,
887                    where_clause: wh,
888                })
889            } else {
890                None
891            };
892            self.expect_kw(&TokenKind::KwDo)?;
893            let action = if self.eat_kw(&TokenKind::KwNothing) {
894                UpsertAction::Nothing
895            } else {
896                self.expect_kw(&TokenKind::KwUpdate)?;
897                self.expect_kw(&TokenKind::KwSet)?;
898                let assignments = self.parse_comma_sep(Self::parse_assignment)?;
899                let wh = if self.eat_kw(&TokenKind::KwWhere) {
900                    Some(Box::new(self.parse_expr()?))
901                } else {
902                    None
903                };
904                UpsertAction::Update {
905                    assignments,
906                    where_clause: wh,
907                }
908            };
909            clauses.push(UpsertClause { target, action });
910        }
911        Ok(clauses)
912    }
913
914    // -----------------------------------------------------------------------
915    // UPDATE
916    // -----------------------------------------------------------------------
917
918    fn parse_update_stmt(&mut self, with: Option<WithClause>) -> Result<Statement, ParseError> {
919        self.expect_kw(&TokenKind::KwUpdate)?;
920        let or_conflict = if self.eat_kw(&TokenKind::KwOr) {
921            Some(self.parse_conflict_action()?)
922        } else {
923            None
924        };
925        let table = self.parse_qualified_table_ref()?;
926        self.expect_kw(&TokenKind::KwSet)?;
927        let assignments = self.parse_comma_sep(Self::parse_assignment)?;
928        let from = if self.eat_kw(&TokenKind::KwFrom) {
929            Some(self.parse_from_clause()?)
930        } else {
931            None
932        };
933        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
934            Some(self.parse_expr()?)
935        } else {
936            None
937        };
938        let returning = self.parse_returning()?;
939        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
940            self.expect_kw(&TokenKind::KwBy)?;
941            self.parse_comma_sep(Self::parse_ordering_term)?
942        } else {
943            vec![]
944        };
945        let limit = self.parse_limit()?;
946        Ok(Statement::Update(UpdateStatement {
947            with,
948            or_conflict,
949            table,
950            assignments,
951            from,
952            where_clause,
953            returning,
954            order_by,
955            limit,
956        }))
957    }
958
959    fn parse_assignment(&mut self) -> Result<Assignment, ParseError> {
960        let target = if self.check(&TokenKind::LeftParen) {
961            self.advance();
962            let cols = self.parse_comma_sep(Self::parse_identifier)?;
963            self.expect_token(&TokenKind::RightParen)?;
964            AssignmentTarget::ColumnList(cols)
965        } else {
966            AssignmentTarget::Column(self.parse_identifier()?)
967        };
968        self.expect_token(&TokenKind::Eq)?;
969        let value = self.parse_expr()?;
970        Ok(Assignment { target, value })
971    }
972
973    // -----------------------------------------------------------------------
974    // DELETE
975    // -----------------------------------------------------------------------
976
977    fn parse_delete_stmt(&mut self, with: Option<WithClause>) -> Result<Statement, ParseError> {
978        self.expect_kw(&TokenKind::KwDelete)?;
979        self.expect_kw(&TokenKind::KwFrom)?;
980        let table = self.parse_qualified_table_ref()?;
981        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
982            Some(self.parse_expr()?)
983        } else {
984            None
985        };
986        let returning = self.parse_returning()?;
987        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
988            self.expect_kw(&TokenKind::KwBy)?;
989            self.parse_comma_sep(Self::parse_ordering_term)?
990        } else {
991            vec![]
992        };
993        let limit = self.parse_limit()?;
994        Ok(Statement::Delete(DeleteStatement {
995            with,
996            table,
997            where_clause,
998            returning,
999            order_by,
1000            limit,
1001        }))
1002    }
1003
1004    // -----------------------------------------------------------------------
1005    // CREATE
1006    // -----------------------------------------------------------------------
1007
1008    fn parse_create(&mut self) -> Result<Statement, ParseError> {
1009        self.expect_kw(&TokenKind::KwCreate)?;
1010        let temporary = self.eat_kw(&TokenKind::KwTemp) || self.eat_kw(&TokenKind::KwTemporary);
1011        let unique = self.eat_kw(&TokenKind::KwUnique);
1012
1013        if self.eat_kw(&TokenKind::KwTable) {
1014            return self.parse_create_table(temporary);
1015        }
1016        if self.eat_kw(&TokenKind::KwIndex) {
1017            return self.parse_create_index(unique);
1018        }
1019        if self.eat_kw(&TokenKind::KwView) {
1020            return self.parse_create_view(temporary);
1021        }
1022        if self.eat_kw(&TokenKind::KwTrigger) {
1023            return self.parse_create_trigger(temporary);
1024        }
1025        if self.eat_kw(&TokenKind::KwVirtual) {
1026            self.expect_kw(&TokenKind::KwTable)?;
1027            return self.parse_create_virtual_table();
1028        }
1029        Err(self.err_expected("TABLE, INDEX, VIEW, TRIGGER, or VIRTUAL"))
1030    }
1031
1032    fn parse_if_not_exists(&mut self) -> bool {
1033        if self.check_kw(&TokenKind::KwIf)
1034            && self.peek_nth(1) == &TokenKind::KwNot
1035            && self.peek_nth(2) == &TokenKind::KwExists
1036        {
1037            self.advance();
1038            self.advance();
1039            self.advance();
1040            true
1041        } else {
1042            false
1043        }
1044    }
1045
1046    fn parse_create_table(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1047        let if_not_exists = self.parse_if_not_exists();
1048        let name = self.parse_qualified_name()?;
1049        let body = if self.eat_kw(&TokenKind::KwAs) {
1050            CreateTableBody::AsSelect(Box::new(self.parse_select_stmt(None)?))
1051        } else {
1052            self.expect_token(&TokenKind::LeftParen)?;
1053            let mut columns = Vec::new();
1054            let mut constraints = Vec::new();
1055            loop {
1056                if self.is_table_constraint_start() {
1057                    constraints.push(self.parse_table_constraint()?);
1058                } else {
1059                    columns.push(self.parse_column_def()?);
1060                }
1061                if !self.eat(&TokenKind::Comma) {
1062                    break;
1063                }
1064            }
1065            self.expect_token(&TokenKind::RightParen)?;
1066            CreateTableBody::Columns {
1067                columns,
1068                constraints,
1069            }
1070        };
1071        let mut without_rowid = false;
1072        let mut strict = false;
1073        // Table options after the closing paren.
1074        loop {
1075            if self.check_kw(&TokenKind::KwWithout) {
1076                self.advance();
1077                // Expect "ROWID" as an identifier.
1078                let id = self.parse_identifier()?;
1079                if !id.eq_ignore_ascii_case("ROWID") {
1080                    return Err(self.err_expected("ROWID after WITHOUT"));
1081                }
1082                without_rowid = true;
1083            } else if self.eat_kw(&TokenKind::KwStrict) {
1084                strict = true;
1085            } else {
1086                break;
1087            }
1088            let _ = self.eat(&TokenKind::Comma);
1089        }
1090        Ok(Statement::CreateTable(CreateTableStatement {
1091            if_not_exists,
1092            temporary,
1093            name,
1094            body,
1095            without_rowid,
1096            strict,
1097        }))
1098    }
1099
1100    fn is_table_constraint_start(&self) -> bool {
1101        matches!(
1102            self.peek(),
1103            TokenKind::KwPrimary | TokenKind::KwUnique | TokenKind::KwCheck | TokenKind::KwForeign
1104        ) || (self.check_kw(&TokenKind::KwConstraint))
1105    }
1106
1107    fn parse_column_def(&mut self) -> Result<ColumnDef, ParseError> {
1108        let name = self.parse_identifier()?;
1109        let type_name = self.try_type_name()?;
1110        let mut constraints = Vec::new();
1111        while let Some(c) = self.try_column_constraint()? {
1112            constraints.push(c);
1113        }
1114        Ok(ColumnDef {
1115            name,
1116            type_name,
1117            constraints,
1118        })
1119    }
1120
1121    fn try_type_name(&mut self) -> Result<Option<TypeName>, ParseError> {
1122        // Type name is one or more identifiers, stopping at known boundaries.
1123        if self.is_column_constraint_start()
1124            || matches!(
1125                self.peek(),
1126                TokenKind::Comma | TokenKind::RightParen | TokenKind::Eof
1127            )
1128        {
1129            return Ok(None);
1130        }
1131        // Collect type name words.
1132        let mut words = Vec::new();
1133        loop {
1134            match self.peek() {
1135                TokenKind::Id(_) | TokenKind::QuotedId(_, _) => {
1136                    words.push(self.parse_identifier()?);
1137                }
1138                k if is_nonreserved_kw(k) => {
1139                    words.push(self.parse_identifier()?);
1140                }
1141                _ => break,
1142            }
1143            if self.is_column_constraint_start()
1144                || matches!(
1145                    self.peek(),
1146                    TokenKind::Comma | TokenKind::RightParen | TokenKind::LeftParen
1147                )
1148            {
1149                break;
1150            }
1151        }
1152        if words.is_empty() {
1153            return Ok(None);
1154        }
1155        let type_name = words.join(" ");
1156        let (arg1, arg2) = if self.eat(&TokenKind::LeftParen) {
1157            let a1 = self.parse_signed_number_str()?;
1158            let a2 = if self.eat(&TokenKind::Comma) {
1159                Some(self.parse_signed_number_str()?)
1160            } else {
1161                None
1162            };
1163            self.expect_token(&TokenKind::RightParen)?;
1164            (Some(a1), a2)
1165        } else {
1166            (None, None)
1167        };
1168        Ok(Some(TypeName {
1169            name: type_name,
1170            arg1,
1171            arg2,
1172        }))
1173    }
1174
1175    fn parse_signed_number_str(&mut self) -> Result<String, ParseError> {
1176        let neg = self.eat(&TokenKind::Minus);
1177        let plus = if neg {
1178            false
1179        } else {
1180            self.eat(&TokenKind::Plus)
1181        };
1182        let _ = plus; // just consume
1183        match self.peek().clone() {
1184            TokenKind::Integer(n) => {
1185                self.advance();
1186                Ok(if neg { format!("-{n}") } else { n.to_string() })
1187            }
1188            TokenKind::Float(f) => {
1189                self.advance();
1190                Ok(if neg { format!("-{f}") } else { f.to_string() })
1191            }
1192            _ => Err(self.err_expected("number")),
1193        }
1194    }
1195
1196    fn is_column_constraint_start(&self) -> bool {
1197        matches!(
1198            self.peek(),
1199            TokenKind::KwPrimary
1200                | TokenKind::KwNot
1201                | TokenKind::KwUnique
1202                | TokenKind::KwCheck
1203                | TokenKind::KwDefault
1204                | TokenKind::KwCollate
1205                | TokenKind::KwReferences
1206                | TokenKind::KwGenerated
1207                | TokenKind::KwConstraint
1208                | TokenKind::KwAs
1209        )
1210    }
1211
1212    fn try_column_constraint(&mut self) -> Result<Option<ColumnConstraint>, ParseError> {
1213        let name = if self.eat_kw(&TokenKind::KwConstraint) {
1214            Some(self.parse_identifier()?)
1215        } else {
1216            None
1217        };
1218        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
1219            self.expect_kw(&TokenKind::KwKey)?;
1220            let direction = if self.eat_kw(&TokenKind::KwAsc) {
1221                Some(SortDirection::Asc)
1222            } else if self.eat_kw(&TokenKind::KwDesc) {
1223                Some(SortDirection::Desc)
1224            } else {
1225                None
1226            };
1227            let conflict = self.parse_on_conflict()?;
1228            let autoincrement = self.eat_kw(&TokenKind::KwAutoincrement);
1229            ColumnConstraintKind::PrimaryKey {
1230                direction,
1231                conflict,
1232                autoincrement,
1233            }
1234        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwNull {
1235            self.advance();
1236            self.advance();
1237            let conflict = self.parse_on_conflict()?;
1238            ColumnConstraintKind::NotNull { conflict }
1239        } else if self.eat_kw(&TokenKind::KwUnique) {
1240            let conflict = self.parse_on_conflict()?;
1241            ColumnConstraintKind::Unique { conflict }
1242        } else if self.eat_kw(&TokenKind::KwCheck) {
1243            self.expect_token(&TokenKind::LeftParen)?;
1244            let expr = self.parse_expr()?;
1245            self.expect_token(&TokenKind::RightParen)?;
1246            ColumnConstraintKind::Check(expr)
1247        } else if self.eat_kw(&TokenKind::KwDefault) {
1248            if self.eat(&TokenKind::LeftParen) {
1249                let expr = self.parse_expr()?;
1250                self.expect_token(&TokenKind::RightParen)?;
1251                ColumnConstraintKind::Default(DefaultValue::ParenExpr(expr))
1252            } else {
1253                let expr = self.parse_expr()?;
1254                ColumnConstraintKind::Default(DefaultValue::Expr(expr))
1255            }
1256        } else if self.eat_kw(&TokenKind::KwCollate) {
1257            ColumnConstraintKind::Collate(self.parse_identifier()?)
1258        } else if self.eat_kw(&TokenKind::KwReferences) {
1259            ColumnConstraintKind::ForeignKey(self.parse_fk_clause()?)
1260        } else if self.eat_kw(&TokenKind::KwGenerated) || self.eat_kw(&TokenKind::KwAs) {
1261            if self.tokens[self.pos.saturating_sub(1)].kind == TokenKind::KwGenerated {
1262                let _ = self.eat_kw(&TokenKind::KwAlways);
1263                let _ = self.eat_kw(&TokenKind::KwAs);
1264            }
1265            self.expect_token(&TokenKind::LeftParen)?;
1266            let expr = self.parse_expr()?;
1267            self.expect_token(&TokenKind::RightParen)?;
1268            let storage = if self.eat_kw(&TokenKind::KwStored) {
1269                Some(GeneratedStorage::Stored)
1270            } else if self.eat_kw(&TokenKind::KwVirtual) {
1271                Some(GeneratedStorage::Virtual)
1272            } else {
1273                None
1274            };
1275            ColumnConstraintKind::Generated { expr, storage }
1276        } else if name.is_some() {
1277            return Err(self.err_expected("constraint kind after CONSTRAINT name"));
1278        } else {
1279            return Ok(None);
1280        };
1281        Ok(Some(ColumnConstraint { name, kind }))
1282    }
1283
1284    fn parse_on_conflict(&mut self) -> Result<Option<ConflictAction>, ParseError> {
1285        if self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
1286            self.advance();
1287            self.advance();
1288            Ok(Some(self.parse_conflict_action()?))
1289        } else {
1290            Ok(None)
1291        }
1292    }
1293
1294    fn parse_fk_clause(&mut self) -> Result<ForeignKeyClause, ParseError> {
1295        let table = self.parse_identifier()?;
1296        let columns = if self.eat(&TokenKind::LeftParen) {
1297            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1298            self.expect_token(&TokenKind::RightParen)?;
1299            cols
1300        } else {
1301            vec![]
1302        };
1303        let mut actions = Vec::new();
1304        let mut deferrable = None;
1305        loop {
1306            if self.check_kw(&TokenKind::KwOn) {
1307                self.advance();
1308                let trigger = if self.eat_kw(&TokenKind::KwDelete) {
1309                    ForeignKeyTrigger::OnDelete
1310                } else {
1311                    self.expect_kw(&TokenKind::KwUpdate)?;
1312                    ForeignKeyTrigger::OnUpdate
1313                };
1314                let action = self.parse_fk_action_type()?;
1315                actions.push(ForeignKeyAction { trigger, action });
1316            } else if self.check_kw(&TokenKind::KwNot) || self.check_kw(&TokenKind::KwDeferrable) {
1317                let not = self.eat_kw(&TokenKind::KwNot);
1318                self.expect_kw(&TokenKind::KwDeferrable)?;
1319                let initially = if self.eat_kw(&TokenKind::KwInitially) {
1320                    if self.eat_kw(&TokenKind::KwDeferred) {
1321                        Some(DeferrableInitially::Deferred)
1322                    } else {
1323                        self.expect_kw(&TokenKind::KwImmediate)?;
1324                        Some(DeferrableInitially::Immediate)
1325                    }
1326                } else {
1327                    None
1328                };
1329                deferrable = Some(Deferrable { not, initially });
1330            } else if self.eat_kw(&TokenKind::KwMatch) {
1331                // MATCH name — parsed but ignored per SQLite behavior.
1332                self.parse_identifier()?;
1333            } else {
1334                break;
1335            }
1336        }
1337        Ok(ForeignKeyClause {
1338            table,
1339            columns,
1340            actions,
1341            deferrable,
1342        })
1343    }
1344
1345    fn parse_fk_action_type(&mut self) -> Result<ForeignKeyActionType, ParseError> {
1346        if self.eat_kw(&TokenKind::KwSet) {
1347            if self.eat_kw(&TokenKind::KwNull) {
1348                Ok(ForeignKeyActionType::SetNull)
1349            } else {
1350                self.expect_kw(&TokenKind::KwDefault)?;
1351                Ok(ForeignKeyActionType::SetDefault)
1352            }
1353        } else if self.eat_kw(&TokenKind::KwCascade) {
1354            Ok(ForeignKeyActionType::Cascade)
1355        } else if self.eat_kw(&TokenKind::KwRestrict) {
1356            Ok(ForeignKeyActionType::Restrict)
1357        } else if self.check_kw(&TokenKind::KwNo) {
1358            self.advance();
1359            let id = self.parse_identifier()?;
1360            if !id.eq_ignore_ascii_case("ACTION") {
1361                return Err(self.err_expected("ACTION after NO"));
1362            }
1363            Ok(ForeignKeyActionType::NoAction)
1364        } else {
1365            Err(self.err_expected("foreign key action"))
1366        }
1367    }
1368
1369    fn parse_table_constraint(&mut self) -> Result<TableConstraint, ParseError> {
1370        let name = if self.eat_kw(&TokenKind::KwConstraint) {
1371            Some(self.parse_identifier()?)
1372        } else {
1373            None
1374        };
1375        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
1376            self.expect_kw(&TokenKind::KwKey)?;
1377            self.expect_token(&TokenKind::LeftParen)?;
1378            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1379            self.expect_token(&TokenKind::RightParen)?;
1380            let conflict = self.parse_on_conflict()?;
1381            TableConstraintKind::PrimaryKey { columns, conflict }
1382        } else if self.eat_kw(&TokenKind::KwUnique) {
1383            self.expect_token(&TokenKind::LeftParen)?;
1384            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1385            self.expect_token(&TokenKind::RightParen)?;
1386            let conflict = self.parse_on_conflict()?;
1387            TableConstraintKind::Unique { columns, conflict }
1388        } else if self.eat_kw(&TokenKind::KwCheck) {
1389            self.expect_token(&TokenKind::LeftParen)?;
1390            let expr = self.parse_expr()?;
1391            self.expect_token(&TokenKind::RightParen)?;
1392            TableConstraintKind::Check(expr)
1393        } else if self.eat_kw(&TokenKind::KwForeign) {
1394            self.expect_kw(&TokenKind::KwKey)?;
1395            self.expect_token(&TokenKind::LeftParen)?;
1396            let columns = self.parse_comma_sep(Self::parse_identifier)?;
1397            self.expect_token(&TokenKind::RightParen)?;
1398            self.expect_kw(&TokenKind::KwReferences)?;
1399            let clause = self.parse_fk_clause()?;
1400            TableConstraintKind::ForeignKey { columns, clause }
1401        } else {
1402            return Err(self.err_expected("table constraint"));
1403        };
1404        Ok(TableConstraint { name, kind })
1405    }
1406
1407    fn parse_indexed_column(&mut self) -> Result<IndexedColumn, ParseError> {
1408        let expr = self.parse_expr()?;
1409        let collation = if self.eat_kw(&TokenKind::KwCollate) {
1410            Some(self.parse_identifier()?)
1411        } else {
1412            None
1413        };
1414        let direction = if self.eat_kw(&TokenKind::KwAsc) {
1415            Some(SortDirection::Asc)
1416        } else if self.eat_kw(&TokenKind::KwDesc) {
1417            Some(SortDirection::Desc)
1418        } else {
1419            None
1420        };
1421        Ok(IndexedColumn {
1422            expr,
1423            collation,
1424            direction,
1425        })
1426    }
1427
1428    fn parse_create_index(&mut self, unique: bool) -> Result<Statement, ParseError> {
1429        let if_not_exists = self.parse_if_not_exists();
1430        let name = self.parse_qualified_name()?;
1431        self.expect_kw(&TokenKind::KwOn)?;
1432        let table = self.parse_identifier()?;
1433        self.expect_token(&TokenKind::LeftParen)?;
1434        let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1435        self.expect_token(&TokenKind::RightParen)?;
1436        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1437            Some(self.parse_expr()?)
1438        } else {
1439            None
1440        };
1441        Ok(Statement::CreateIndex(CreateIndexStatement {
1442            unique,
1443            if_not_exists,
1444            name,
1445            table,
1446            columns,
1447            where_clause,
1448        }))
1449    }
1450
1451    fn parse_create_view(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1452        let if_not_exists = self.parse_if_not_exists();
1453        let name = self.parse_qualified_name()?;
1454        let columns = if self.check(&TokenKind::LeftParen) {
1455            self.advance();
1456            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1457            self.expect_token(&TokenKind::RightParen)?;
1458            cols
1459        } else {
1460            vec![]
1461        };
1462        self.expect_kw(&TokenKind::KwAs)?;
1463        let query = self.parse_select_stmt(None)?;
1464        Ok(Statement::CreateView(CreateViewStatement {
1465            if_not_exists,
1466            temporary,
1467            name,
1468            columns,
1469            query,
1470        }))
1471    }
1472
1473    fn parse_create_trigger(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1474        let if_not_exists = self.parse_if_not_exists();
1475        let name = self.parse_qualified_name()?;
1476        let timing = if self.eat_kw(&TokenKind::KwBefore) {
1477            TriggerTiming::Before
1478        } else if self.eat_kw(&TokenKind::KwAfter) {
1479            TriggerTiming::After
1480        } else if self.eat_kw(&TokenKind::KwInstead) {
1481            self.expect_kw(&TokenKind::KwOf)?;
1482            TriggerTiming::InsteadOf
1483        } else {
1484            TriggerTiming::Before // default
1485        };
1486        let event = if self.eat_kw(&TokenKind::KwInsert) {
1487            TriggerEvent::Insert
1488        } else if self.eat_kw(&TokenKind::KwDelete) {
1489            TriggerEvent::Delete
1490        } else {
1491            self.expect_kw(&TokenKind::KwUpdate)?;
1492            let cols = if self.eat_kw(&TokenKind::KwOf) {
1493                self.parse_comma_sep(Self::parse_identifier)?
1494            } else {
1495                vec![]
1496            };
1497            TriggerEvent::Update(cols)
1498        };
1499        self.expect_kw(&TokenKind::KwOn)?;
1500        let table = self.parse_identifier()?;
1501        let for_each_row = if self.eat_kw(&TokenKind::KwFor) {
1502            self.expect_kw(&TokenKind::KwEach)?;
1503            self.expect_kw(&TokenKind::KwRow)?;
1504            true
1505        } else {
1506            false
1507        };
1508        let when = if self.eat_kw(&TokenKind::KwWhen) {
1509            Some(self.parse_expr()?)
1510        } else {
1511            None
1512        };
1513        self.expect_kw(&TokenKind::KwBegin)?;
1514        let mut body = Vec::new();
1515        loop {
1516            if self.check_kw(&TokenKind::KwEnd) {
1517                break;
1518            }
1519            body.push(self.parse_statement_inner()?);
1520            let _ = self.eat(&TokenKind::Semicolon);
1521        }
1522        self.expect_kw(&TokenKind::KwEnd)?;
1523        Ok(Statement::CreateTrigger(CreateTriggerStatement {
1524            if_not_exists,
1525            temporary,
1526            name,
1527            timing,
1528            event,
1529            table,
1530            for_each_row,
1531            when,
1532            body,
1533        }))
1534    }
1535
1536    fn parse_create_virtual_table(&mut self) -> Result<Statement, ParseError> {
1537        let if_not_exists = self.parse_if_not_exists();
1538        let name = self.parse_qualified_name()?;
1539        self.expect_kw(&TokenKind::KwUsing)?;
1540        let module = self.parse_identifier()?;
1541        let args = if self.eat(&TokenKind::LeftParen) {
1542            if self.check(&TokenKind::RightParen) {
1543                self.advance();
1544                vec![]
1545            } else {
1546                // Virtual table args are opaque; collect tokens as strings until matching rparen.
1547                let mut args = Vec::new();
1548                let mut depth = 0i32;
1549                let mut current_arg = String::new();
1550                loop {
1551                    match self.peek() {
1552                        TokenKind::RightParen if depth == 0 => {
1553                            self.advance();
1554                            args.push(current_arg.trim().to_owned());
1555                            break;
1556                        }
1557                        TokenKind::LeftParen => {
1558                            depth += 1;
1559                            current_arg.push('(');
1560                            self.advance();
1561                        }
1562                        TokenKind::RightParen => {
1563                            depth -= 1;
1564                            current_arg.push(')');
1565                            self.advance();
1566                        }
1567                        TokenKind::Comma if depth == 0 => {
1568                            args.push(current_arg.trim().to_owned());
1569                            current_arg = String::new();
1570                            self.advance();
1571                        }
1572                        TokenKind::Eof => {
1573                            return Err(self.err_expected("closing parenthesis"));
1574                        }
1575                        _ => {
1576                            // Reconstruct token text from token kind.
1577                            let t = self.current().unwrap();
1578                            let text = t.kind.to_sql();
1579                            if !current_arg.is_empty()
1580                                && !current_arg.ends_with(' ')
1581                                && !text.is_empty()
1582                            {
1583                                current_arg.push(' ');
1584                            }
1585                            current_arg.push_str(&text);
1586                            self.advance();
1587                        }
1588                    }
1589                }
1590                args
1591            }
1592        } else {
1593            vec![]
1594        };
1595        Ok(Statement::CreateVirtualTable(CreateVirtualTableStatement {
1596            if_not_exists,
1597            name,
1598            module,
1599            args,
1600        }))
1601    }
1602
1603    // -----------------------------------------------------------------------
1604    // DROP
1605    // -----------------------------------------------------------------------
1606
1607    fn parse_drop(&mut self) -> Result<Statement, ParseError> {
1608        self.expect_kw(&TokenKind::KwDrop)?;
1609        let object_type = if self.eat_kw(&TokenKind::KwTable) {
1610            DropObjectType::Table
1611        } else if self.eat_kw(&TokenKind::KwView) {
1612            DropObjectType::View
1613        } else if self.eat_kw(&TokenKind::KwIndex) {
1614            DropObjectType::Index
1615        } else if self.eat_kw(&TokenKind::KwTrigger) {
1616            DropObjectType::Trigger
1617        } else {
1618            return Err(self.err_expected("TABLE, VIEW, INDEX, or TRIGGER"));
1619        };
1620        let if_exists =
1621            if self.check_kw(&TokenKind::KwIf) && self.peek_nth(1) == &TokenKind::KwExists {
1622                self.advance();
1623                self.advance();
1624                true
1625            } else {
1626                false
1627            };
1628        let name = self.parse_qualified_name()?;
1629        Ok(Statement::Drop(DropStatement {
1630            object_type,
1631            if_exists,
1632            name,
1633        }))
1634    }
1635
1636    // -----------------------------------------------------------------------
1637    // ALTER TABLE
1638    // -----------------------------------------------------------------------
1639
1640    fn parse_alter(&mut self) -> Result<Statement, ParseError> {
1641        self.expect_kw(&TokenKind::KwAlter)?;
1642        self.expect_kw(&TokenKind::KwTable)?;
1643        let table = self.parse_qualified_name()?;
1644        let action = if self.eat_kw(&TokenKind::KwRename) {
1645            if self.eat_kw(&TokenKind::KwTo) {
1646                AlterTableAction::RenameTo(self.parse_identifier()?)
1647            } else {
1648                let _ = self.eat_kw(&TokenKind::KwColumn);
1649                let old = self.parse_identifier()?;
1650                self.expect_kw(&TokenKind::KwTo)?;
1651                let new = self.parse_identifier()?;
1652                AlterTableAction::RenameColumn { old, new }
1653            }
1654        } else if self.eat_kw(&TokenKind::KwAdd) {
1655            let _ = self.eat_kw(&TokenKind::KwColumn);
1656            AlterTableAction::AddColumn(self.parse_column_def()?)
1657        } else if self.eat_kw(&TokenKind::KwDrop) {
1658            let _ = self.eat_kw(&TokenKind::KwColumn);
1659            AlterTableAction::DropColumn(self.parse_identifier()?)
1660        } else {
1661            return Err(self.err_expected("RENAME, ADD, or DROP"));
1662        };
1663        Ok(Statement::AlterTable(AlterTableStatement { table, action }))
1664    }
1665
1666    // -----------------------------------------------------------------------
1667    // Transaction control
1668    // -----------------------------------------------------------------------
1669
1670    fn parse_begin(&mut self) -> Result<Statement, ParseError> {
1671        self.expect_kw(&TokenKind::KwBegin)?;
1672        let mode = if self.eat_kw(&TokenKind::KwDeferred) {
1673            Some(TransactionMode::Deferred)
1674        } else if self.eat_kw(&TokenKind::KwImmediate) {
1675            Some(TransactionMode::Immediate)
1676        } else if self.eat_kw(&TokenKind::KwExclusive) {
1677            Some(TransactionMode::Exclusive)
1678        } else if self.eat_kw(&TokenKind::KwConcurrent) {
1679            Some(TransactionMode::Concurrent)
1680        } else {
1681            None
1682        };
1683        // Optional TRANSACTION keyword.
1684        let _ = self.eat_kw(&TokenKind::KwTransaction);
1685        Ok(Statement::Begin(BeginStatement { mode }))
1686    }
1687
1688    fn parse_rollback(&mut self) -> Result<Statement, ParseError> {
1689        self.expect_kw(&TokenKind::KwRollback)?;
1690        let _ = self.eat_kw(&TokenKind::KwTransaction);
1691        let to_savepoint = if self.eat_kw(&TokenKind::KwTo) {
1692            let _ = self.eat_kw(&TokenKind::KwSavepoint);
1693            Some(self.parse_identifier()?)
1694        } else {
1695            None
1696        };
1697        Ok(Statement::Rollback(RollbackStatement { to_savepoint }))
1698    }
1699
1700    // -----------------------------------------------------------------------
1701    // ATTACH / PRAGMA / VACUUM / EXPLAIN
1702    // -----------------------------------------------------------------------
1703
1704    fn parse_attach(&mut self) -> Result<Statement, ParseError> {
1705        self.expect_kw(&TokenKind::KwAttach)?;
1706        let _ = self.eat_kw(&TokenKind::KwDatabase);
1707        let expr = self.parse_expr()?;
1708        self.expect_kw(&TokenKind::KwAs)?;
1709        let schema = self.parse_identifier()?;
1710        Ok(Statement::Attach(AttachStatement { expr, schema }))
1711    }
1712
1713    fn parse_pragma_value_expr(&mut self) -> Result<Expr, ParseError> {
1714        // SQLite allows ON/OFF for many boolean pragmas. Treat `ON` as `TRUE`
1715        // in PRAGMA value position (OFF is tokenized as an identifier, so the
1716        // regular expression parser handles it).
1717        if self.check_kw(&TokenKind::KwOn) {
1718            let sp = self.current_span();
1719            self.advance();
1720            return Ok(Expr::Literal(Literal::True, sp));
1721        }
1722        self.parse_expr()
1723    }
1724
1725    fn parse_pragma(&mut self) -> Result<Statement, ParseError> {
1726        self.expect_kw(&TokenKind::KwPragma)?;
1727        let name = self.parse_qualified_name()?;
1728        let value = if self.eat(&TokenKind::Eq) || self.eat(&TokenKind::EqEq) {
1729            Some(PragmaValue::Assign(self.parse_pragma_value_expr()?))
1730        } else if self.eat(&TokenKind::LeftParen) {
1731            let v = self.parse_pragma_value_expr()?;
1732            self.expect_token(&TokenKind::RightParen)?;
1733            Some(PragmaValue::Call(v))
1734        } else {
1735            None
1736        };
1737        Ok(Statement::Pragma(PragmaStatement { name, value }))
1738    }
1739
1740    fn parse_vacuum(&mut self) -> Result<Statement, ParseError> {
1741        self.expect_kw(&TokenKind::KwVacuum)?;
1742        let schema = if !self.at_eof()
1743            && !self.check(&TokenKind::Semicolon)
1744            && !self.check_kw(&TokenKind::KwInto)
1745        {
1746            Some(self.parse_identifier()?)
1747        } else {
1748            None
1749        };
1750        let into = if self.eat_kw(&TokenKind::KwInto) {
1751            Some(self.parse_expr()?)
1752        } else {
1753            None
1754        };
1755        Ok(Statement::Vacuum(VacuumStatement { schema, into }))
1756    }
1757
1758    fn parse_explain(&mut self) -> Result<Statement, ParseError> {
1759        self.expect_kw(&TokenKind::KwExplain)?;
1760        let query_plan = if self.eat_kw(&TokenKind::KwQuery) {
1761            self.expect_kw(&TokenKind::KwPlan)?;
1762            true
1763        } else {
1764            false
1765        };
1766        let stmt = self.parse_statement_inner()?;
1767        Ok(Statement::Explain {
1768            query_plan,
1769            stmt: Box::new(stmt),
1770        })
1771    }
1772
1773    // -----------------------------------------------------------------------
1774    // Window definitions (used in SELECT ... WINDOW clause and OVER)
1775    // -----------------------------------------------------------------------
1776
1777    fn parse_window_def(&mut self) -> Result<WindowDef, ParseError> {
1778        let name = self.parse_identifier()?;
1779        self.expect_kw(&TokenKind::KwAs)?;
1780        self.expect_token(&TokenKind::LeftParen)?;
1781        let spec = self.parse_window_spec()?;
1782        self.expect_token(&TokenKind::RightParen)?;
1783        Ok(WindowDef { name, spec })
1784    }
1785
1786    pub(crate) fn parse_window_spec(&mut self) -> Result<WindowSpec, ParseError> {
1787        // Optional base window name.
1788        let base_window = if matches!(self.peek(), TokenKind::Id(_))
1789            && !self.check_kw(&TokenKind::KwPartition)
1790            && !self.check_kw(&TokenKind::KwOrder)
1791            && !self.check_kw(&TokenKind::KwRange)
1792            && !self.check_kw(&TokenKind::KwRows)
1793            && !self.check_kw(&TokenKind::KwGroups)
1794        {
1795            Some(self.parse_identifier()?)
1796        } else {
1797            None
1798        };
1799        let partition_by = if self.eat_kw(&TokenKind::KwPartition) {
1800            self.expect_kw(&TokenKind::KwBy)?;
1801            self.parse_comma_sep(Self::parse_expr)?
1802        } else {
1803            vec![]
1804        };
1805        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1806            self.expect_kw(&TokenKind::KwBy)?;
1807            self.parse_comma_sep(Self::parse_ordering_term)?
1808        } else {
1809            vec![]
1810        };
1811        let frame = self.try_frame_spec()?;
1812        Ok(WindowSpec {
1813            base_window,
1814            partition_by,
1815            order_by,
1816            frame,
1817        })
1818    }
1819
1820    fn try_frame_spec(&mut self) -> Result<Option<FrameSpec>, ParseError> {
1821        let frame_type = if self.eat_kw(&TokenKind::KwRows) {
1822            FrameType::Rows
1823        } else if self.eat_kw(&TokenKind::KwRange) {
1824            FrameType::Range
1825        } else if self.eat_kw(&TokenKind::KwGroups) {
1826            FrameType::Groups
1827        } else {
1828            return Ok(None);
1829        };
1830        let (start, end) = if self.eat_kw(&TokenKind::KwBetween) {
1831            let s = self.parse_frame_bound()?;
1832            self.expect_kw(&TokenKind::KwAnd)?;
1833            let e = self.parse_frame_bound()?;
1834            (s, Some(e))
1835        } else {
1836            (self.parse_frame_bound()?, None)
1837        };
1838        let exclude = if self.eat_kw(&TokenKind::KwExclude) {
1839            if self.check_kw(&TokenKind::KwNo) {
1840                self.advance();
1841                // "NO OTHERS"
1842                let id = self.parse_identifier()?;
1843                if !id.eq_ignore_ascii_case("OTHERS") {
1844                    return Err(self.err_expected("OTHERS"));
1845                }
1846                Some(FrameExclude::NoOthers)
1847            } else if self.check_kw(&TokenKind::KwOthers) {
1848                self.advance();
1849                Some(FrameExclude::NoOthers)
1850            } else if self.eat_kw(&TokenKind::KwTies) {
1851                Some(FrameExclude::Ties)
1852            } else if self.eat_kw(&TokenKind::KwGroup) {
1853                Some(FrameExclude::Group)
1854            } else if matches!(self.peek(), TokenKind::Id(s) if s.eq_ignore_ascii_case("CURRENT")) {
1855                self.advance();
1856                self.expect_kw(&TokenKind::KwRow)?;
1857                Some(FrameExclude::CurrentRow)
1858            } else {
1859                return Err(self.err_expected("GROUP or CURRENT ROW after EXCLUDE"));
1860            }
1861        } else {
1862            None
1863        };
1864        Ok(Some(FrameSpec {
1865            frame_type,
1866            start,
1867            end,
1868            exclude,
1869        }))
1870    }
1871
1872    fn parse_frame_bound(&mut self) -> Result<FrameBound, ParseError> {
1873        if self.eat_kw(&TokenKind::KwUnbounded) {
1874            if self.eat_kw(&TokenKind::KwPreceding) {
1875                Ok(FrameBound::UnboundedPreceding)
1876            } else {
1877                self.expect_kw(&TokenKind::KwFollowing)?;
1878                Ok(FrameBound::UnboundedFollowing)
1879            }
1880        } else if matches!(self.peek(), TokenKind::Id(s) if s.eq_ignore_ascii_case("CURRENT")) {
1881            self.advance();
1882            self.expect_kw(&TokenKind::KwRow)?;
1883            Ok(FrameBound::CurrentRow)
1884        } else {
1885            let expr = self.parse_expr()?;
1886            if self.eat_kw(&TokenKind::KwPreceding) {
1887                Ok(FrameBound::Preceding(Box::new(expr)))
1888            } else {
1889                self.expect_kw(&TokenKind::KwFollowing)?;
1890                Ok(FrameBound::Following(Box::new(expr)))
1891            }
1892        }
1893    }
1894}
1895
1896// ---------------------------------------------------------------------------
1897// Keyword classification helper
1898// ---------------------------------------------------------------------------
1899
1900pub(crate) fn is_nonreserved_kw(k: &TokenKind) -> bool {
1901    matches!(
1902        k,
1903        TokenKind::KwAbort
1904            | TokenKind::KwAction
1905            | TokenKind::KwAfter
1906            | TokenKind::KwAlways
1907            | TokenKind::KwAnalyze
1908            | TokenKind::KwAsc
1909            | TokenKind::KwBefore
1910            | TokenKind::KwCascade
1911            | TokenKind::KwColumn
1912            | TokenKind::KwConcurrent
1913            | TokenKind::KwConflict
1914            | TokenKind::KwDatabase
1915            | TokenKind::KwDeferred
1916            | TokenKind::KwDesc
1917            | TokenKind::KwDo
1918            | TokenKind::KwEach
1919            | TokenKind::KwEnd
1920            | TokenKind::KwExclude
1921            | TokenKind::KwExclusive
1922            | TokenKind::KwFail
1923            | TokenKind::KwFilter
1924            | TokenKind::KwFirst
1925            | TokenKind::KwFollowing
1926            | TokenKind::KwFull
1927            | TokenKind::KwGenerated
1928            | TokenKind::KwGroups
1929            | TokenKind::KwIf
1930            | TokenKind::KwIgnore
1931            | TokenKind::KwImmediate
1932            | TokenKind::KwIndex
1933            | TokenKind::KwInitially
1934            | TokenKind::KwInstead
1935            | TokenKind::KwKey
1936            | TokenKind::KwLast
1937            | TokenKind::KwMatch
1938            | TokenKind::KwMaterialized
1939            | TokenKind::KwNo
1940            | TokenKind::KwNothing
1941            | TokenKind::KwNulls
1942            | TokenKind::KwOf
1943            | TokenKind::KwOffset
1944            | TokenKind::KwOthers
1945            | TokenKind::KwOver
1946            | TokenKind::KwPartition
1947            | TokenKind::KwPlan
1948            | TokenKind::KwPragma
1949            | TokenKind::KwPreceding
1950            | TokenKind::KwQuery
1951            | TokenKind::KwRange
1952            | TokenKind::KwRecursive
1953            | TokenKind::KwReindex
1954            | TokenKind::KwRelease
1955            | TokenKind::KwRename
1956            | TokenKind::KwReplace
1957            | TokenKind::KwRestrict
1958            | TokenKind::KwReturning
1959            | TokenKind::KwRow
1960            | TokenKind::KwRows
1961            | TokenKind::KwSavepoint
1962            | TokenKind::KwStored
1963            | TokenKind::KwStrict
1964            | TokenKind::KwTable
1965            | TokenKind::KwTemp
1966            | TokenKind::KwTemporary
1967            | TokenKind::KwTies
1968            | TokenKind::KwTransaction
1969            | TokenKind::KwTrigger
1970            | TokenKind::KwUnbounded
1971            | TokenKind::KwVacuum
1972            | TokenKind::KwView
1973            | TokenKind::KwVirtual
1974            | TokenKind::KwWindow
1975            | TokenKind::KwWithout
1976    )
1977}
1978
1979/// Keywords that should never be consumed as implicit aliases because they
1980/// begin/continue the next clause in this grammar position.
1981fn is_alias_terminator_kw(k: &TokenKind) -> bool {
1982    matches!(
1983        k,
1984        TokenKind::KwCross
1985            | TokenKind::KwExcept
1986            | TokenKind::KwFull
1987            | TokenKind::KwGroup
1988            | TokenKind::KwHaving
1989            | TokenKind::KwInner
1990            | TokenKind::KwIntersect
1991            | TokenKind::KwJoin
1992            | TokenKind::KwLeft
1993            | TokenKind::KwLimit
1994            | TokenKind::KwNatural
1995            | TokenKind::KwOffset
1996            | TokenKind::KwOn
1997            | TokenKind::KwOrder
1998            | TokenKind::KwOuter
1999            | TokenKind::KwReturning
2000            | TokenKind::KwRight
2001            | TokenKind::KwUnion
2002            | TokenKind::KwUsing
2003            | TokenKind::KwWhere
2004            | TokenKind::KwWindow
2005            | TokenKind::KwWindow
2006    )
2007}
2008
2009pub(crate) fn kw_to_str(k: &TokenKind) -> String {
2010    let dbg = format!("{k:?}");
2011    dbg.strip_prefix("Kw").unwrap_or(&dbg).to_ascii_lowercase()
2012}
2013
2014// ---------------------------------------------------------------------------
2015// Tests
2016// ---------------------------------------------------------------------------
2017
2018#[cfg(test)]
2019mod tests {
2020    use super::*;
2021
2022    fn parse_ok(sql: &str) -> Vec<Statement> {
2023        let mut p = Parser::from_sql(sql);
2024        let (stmts, errs) = p.parse_all();
2025        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
2026        stmts
2027    }
2028
2029    fn parse_one(sql: &str) -> Statement {
2030        let stmts = parse_ok(sql);
2031        assert_eq!(stmts.len(), 1, "expected 1 statement, got {}", stmts.len());
2032        stmts.into_iter().next().unwrap()
2033    }
2034
2035    #[test]
2036    fn select_literal() {
2037        let stmt = parse_one("SELECT 1");
2038        assert!(matches!(stmt, Statement::Select(_)));
2039    }
2040
2041    #[test]
2042    fn select_star_from() {
2043        let stmt = parse_one("SELECT * FROM t");
2044        if let Statement::Select(s) = stmt {
2045            if let SelectCore::Select { columns, from, .. } = &s.body.select {
2046                assert!(matches!(columns[0], ResultColumn::Star));
2047                assert!(from.is_some());
2048            } else {
2049                unreachable!("expected Select core");
2050            }
2051        } else {
2052            unreachable!("expected Select");
2053        }
2054    }
2055
2056    #[test]
2057    fn select_where_order_limit() {
2058        let stmt = parse_one("SELECT a FROM t WHERE a > 1 ORDER BY a LIMIT 10 OFFSET 5");
2059        if let Statement::Select(s) = stmt {
2060            assert!(s.limit.is_some());
2061            assert_eq!(s.order_by.len(), 1);
2062        } else {
2063            unreachable!("expected Select");
2064        }
2065    }
2066
2067    #[test]
2068    fn select_limit_comma_syntax_uses_offset_then_count() {
2069        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
2070        if let Statement::Select(s) = stmt {
2071            let limit = s.limit.expect("LIMIT clause");
2072            assert!(matches!(
2073                limit.limit,
2074                Expr::Literal(Literal::Integer(10), _)
2075            ));
2076            assert!(matches!(
2077                limit.offset,
2078                Some(Expr::Literal(Literal::Integer(5), _))
2079            ));
2080        } else {
2081            unreachable!("expected Select");
2082        }
2083    }
2084
2085    #[test]
2086    fn select_order_by_nulls_first_last() {
2087        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST, b DESC NULLS LAST");
2088        if let Statement::Select(s) = stmt {
2089            assert_eq!(s.order_by.len(), 2);
2090            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
2091            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
2092            assert_eq!(s.order_by[1].direction, Some(SortDirection::Desc));
2093            assert_eq!(s.order_by[1].nulls, Some(NullsOrder::Last));
2094        } else {
2095            unreachable!("expected Select");
2096        }
2097    }
2098
2099    #[test]
2100    fn select_from_indexed_by_hint() {
2101        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
2102        if let Statement::Select(s) = stmt {
2103            if let SelectCore::Select { from, .. } = &s.body.select {
2104                let from = from.as_ref().expect("FROM clause");
2105                match &from.source {
2106                    TableOrSubquery::Table {
2107                        index_hint: Some(IndexHint::IndexedBy(name)),
2108                        ..
2109                    } => assert_eq!(name, "idx_t"),
2110                    other => unreachable!("expected indexed table source, got {other:?}"),
2111                }
2112            } else {
2113                unreachable!("expected Select core");
2114            }
2115        } else {
2116            unreachable!("expected Select");
2117        }
2118    }
2119
2120    #[test]
2121    fn select_from_not_indexed_hint() {
2122        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
2123        if let Statement::Select(s) = stmt {
2124            if let SelectCore::Select { from, .. } = &s.body.select {
2125                let from = from.as_ref().expect("FROM clause");
2126                match &from.source {
2127                    TableOrSubquery::Table {
2128                        index_hint: Some(IndexHint::NotIndexed),
2129                        ..
2130                    } => {}
2131                    other => unreachable!("expected not-indexed table source, got {other:?}"),
2132                }
2133            } else {
2134                unreachable!("expected Select core");
2135            }
2136        } else {
2137            unreachable!("expected Select");
2138        }
2139    }
2140
2141    #[test]
2142    fn select_from_table_valued_function() {
2143        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
2144        if let Statement::Select(s) = stmt {
2145            if let SelectCore::Select { from, .. } = &s.body.select {
2146                let from = from.as_ref().expect("FROM clause");
2147                match &from.source {
2148                    TableOrSubquery::TableFunction { name, args, alias } => {
2149                        assert_eq!(name, "generate_series");
2150                        assert_eq!(args.len(), 2);
2151                        assert_eq!(alias.as_deref(), Some("gs"));
2152                    }
2153                    other => unreachable!("expected table-valued function source, got {other:?}"),
2154                }
2155            } else {
2156                unreachable!("expected Select core");
2157            }
2158        } else {
2159            unreachable!("expected Select");
2160        }
2161    }
2162
2163    #[test]
2164    fn select_window_function_over_clause() {
2165        let stmt = parse_one(
2166            "SELECT sum(x) OVER (PARTITION BY y ORDER BY z \
2167             ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
2168        );
2169        if let Statement::Select(s) = stmt {
2170            if let SelectCore::Select { columns, .. } = &s.body.select {
2171                match &columns[0] {
2172                    ResultColumn::Expr {
2173                        expr:
2174                            Expr::FunctionCall {
2175                                over: Some(over), ..
2176                            },
2177                        ..
2178                    } => {
2179                        assert_eq!(over.partition_by.len(), 1);
2180                        assert_eq!(over.order_by.len(), 1);
2181                        assert!(matches!(
2182                            over.frame,
2183                            Some(FrameSpec {
2184                                frame_type: FrameType::Rows,
2185                                ..
2186                            })
2187                        ));
2188                    }
2189                    other => unreachable!("expected window function result column, got {other:?}"),
2190                }
2191            } else {
2192                unreachable!("expected Select core");
2193            }
2194        } else {
2195            unreachable!("expected Select");
2196        }
2197    }
2198
2199    #[test]
2200    fn select_named_window_definition_and_reference() {
2201        let stmt = parse_one(
2202            "SELECT sum(x) OVER win FROM t \
2203             WINDOW win AS (PARTITION BY y ORDER BY z)",
2204        );
2205        if let Statement::Select(s) = stmt {
2206            if let SelectCore::Select {
2207                columns, windows, ..
2208            } = &s.body.select
2209            {
2210                assert_eq!(windows.len(), 1);
2211                assert_eq!(windows[0].name, "win");
2212                assert_eq!(windows[0].spec.partition_by.len(), 1);
2213                assert_eq!(windows[0].spec.order_by.len(), 1);
2214                match &columns[0] {
2215                    ResultColumn::Expr {
2216                        expr:
2217                            Expr::FunctionCall {
2218                                over: Some(over), ..
2219                            },
2220                        ..
2221                    } => assert_eq!(over.base_window.as_deref(), Some("win")),
2222                    other => unreachable!("expected named window function, got {other:?}"),
2223                }
2224            } else {
2225                unreachable!("expected Select core");
2226            }
2227        } else {
2228            unreachable!("expected Select");
2229        }
2230    }
2231
2232    #[test]
2233    fn insert_values() {
2234        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2), (3, 4)");
2235        assert!(matches!(stmt, Statement::Insert(_)));
2236    }
2237
2238    #[test]
2239    fn update_set() {
2240        let stmt = parse_one("UPDATE t SET a = 1, b = 2 WHERE id = 3");
2241        assert!(matches!(stmt, Statement::Update(_)));
2242    }
2243
2244    #[test]
2245    fn delete_from() {
2246        let stmt = parse_one("DELETE FROM t WHERE id = 1");
2247        assert!(matches!(stmt, Statement::Delete(_)));
2248    }
2249
2250    #[test]
2251    fn create_table_basic() {
2252        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
2253        if let Statement::CreateTable(ct) = stmt {
2254            assert_eq!(ct.name.name, "t");
2255            if let CreateTableBody::Columns { columns, .. } = ct.body {
2256                assert_eq!(columns.len(), 2);
2257            } else {
2258                unreachable!("expected column defs");
2259            }
2260        } else {
2261            unreachable!("expected CreateTable");
2262        }
2263    }
2264
2265    #[test]
2266    fn create_index() {
2267        let stmt = parse_one("CREATE UNIQUE INDEX idx ON t (a, b DESC)");
2268        if let Statement::CreateIndex(ci) = stmt {
2269            assert!(ci.unique);
2270            assert_eq!(ci.columns.len(), 2);
2271        } else {
2272            unreachable!("expected CreateIndex");
2273        }
2274    }
2275
2276    #[test]
2277    fn drop_table_if_exists() {
2278        let stmt = parse_one("DROP TABLE IF EXISTS t");
2279        if let Statement::Drop(d) = stmt {
2280            assert!(d.if_exists);
2281            assert_eq!(d.object_type, DropObjectType::Table);
2282        } else {
2283            unreachable!("expected Drop");
2284        }
2285    }
2286
2287    #[test]
2288    fn begin_commit() {
2289        let stmts = parse_ok("BEGIN IMMEDIATE; COMMIT");
2290        assert_eq!(stmts.len(), 2);
2291        if let Statement::Begin(b) = &stmts[0] {
2292            assert_eq!(b.mode, Some(TransactionMode::Immediate));
2293        } else {
2294            unreachable!("expected Begin");
2295        }
2296        assert!(matches!(stmts[1], Statement::Commit));
2297    }
2298
2299    #[test]
2300    fn begin_concurrent() {
2301        let stmt = parse_one("BEGIN CONCURRENT");
2302        if let Statement::Begin(b) = stmt {
2303            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
2304        } else {
2305            unreachable!("expected Begin");
2306        }
2307    }
2308
2309    #[test]
2310    fn rollback_to_savepoint() {
2311        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
2312        if let Statement::Rollback(r) = stmt {
2313            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
2314        } else {
2315            unreachable!("expected Rollback");
2316        }
2317    }
2318
2319    #[test]
2320    fn explain_query_plan() {
2321        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT 1");
2322        assert!(matches!(
2323            stmt,
2324            Statement::Explain {
2325                query_plan: true,
2326                ..
2327            }
2328        ));
2329    }
2330
2331    #[test]
2332    fn pragma() {
2333        let stmt = parse_one("PRAGMA journal_mode = WAL");
2334        assert!(matches!(stmt, Statement::Pragma(_)));
2335    }
2336
2337    #[test]
2338    fn pragma_allows_on_value() {
2339        let stmt = parse_one("PRAGMA fsqlite.serializable = ON");
2340        assert!(matches!(stmt, Statement::Pragma(_)));
2341    }
2342
2343    #[test]
2344    fn error_recovery_multiple_statements() {
2345        let mut p = Parser::from_sql("SELECT 1; XYZZY; SELECT 2");
2346        let (stmts, errs) = p.parse_all();
2347        assert_eq!(stmts.len(), 2, "should recover: stmts={stmts:?}");
2348        assert!(!errs.is_empty());
2349    }
2350
2351    #[test]
2352    fn compound_union() {
2353        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
2354        if let Statement::Select(s) = stmt {
2355            assert_eq!(s.body.compounds.len(), 1);
2356        } else {
2357            unreachable!("expected Select");
2358        }
2359    }
2360
2361    #[test]
2362    fn alter_table_rename() {
2363        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
2364        assert!(matches!(
2365            stmt,
2366            Statement::AlterTable(AlterTableStatement {
2367                action: AlterTableAction::RenameTo(_),
2368                ..
2369            })
2370        ));
2371    }
2372
2373    // -----------------------------------------------------------------------
2374    // bd-2kvo Phase 3 acceptance: parser join types
2375    // -----------------------------------------------------------------------
2376
2377    #[test]
2378    fn test_parser_join_inner() {
2379        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
2380        if let Statement::Select(s) = stmt {
2381            if let SelectCore::Select { from, .. } = &s.body.select {
2382                let from = from.as_ref().expect("FROM clause");
2383                assert!(!from.joins.is_empty());
2384                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
2385            } else {
2386                unreachable!("expected Select core");
2387            }
2388        } else {
2389            unreachable!("expected Select");
2390        }
2391    }
2392
2393    #[test]
2394    fn test_parser_join_left() {
2395        let stmt = parse_one("SELECT * FROM a LEFT JOIN b ON a.id = b.a_id");
2396        if let Statement::Select(s) = stmt {
2397            if let SelectCore::Select { from, .. } = &s.body.select {
2398                let from = from.as_ref().expect("FROM clause");
2399                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
2400            } else {
2401                unreachable!("expected Select core");
2402            }
2403        } else {
2404            unreachable!("expected Select");
2405        }
2406    }
2407
2408    #[test]
2409    fn test_parser_join_left_outer() {
2410        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
2411        if let Statement::Select(s) = stmt {
2412            if let SelectCore::Select { from, .. } = &s.body.select {
2413                let from = from.as_ref().expect("FROM clause");
2414                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
2415            } else {
2416                unreachable!("expected Select core");
2417            }
2418        } else {
2419            unreachable!("expected Select");
2420        }
2421    }
2422
2423    #[test]
2424    fn test_parser_join_right() {
2425        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
2426        if let Statement::Select(s) = stmt {
2427            if let SelectCore::Select { from, .. } = &s.body.select {
2428                let from = from.as_ref().expect("FROM clause");
2429                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
2430            } else {
2431                unreachable!("expected Select core");
2432            }
2433        } else {
2434            unreachable!("expected Select");
2435        }
2436    }
2437
2438    #[test]
2439    fn test_parser_join_full() {
2440        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
2441        if let Statement::Select(s) = stmt {
2442            if let SelectCore::Select { from, .. } = &s.body.select {
2443                let from = from.as_ref().expect("FROM clause");
2444                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
2445            } else {
2446                unreachable!("expected Select core");
2447            }
2448        } else {
2449            unreachable!("expected Select");
2450        }
2451    }
2452
2453    #[test]
2454    fn test_parser_join_full_outer_with_semicolon() {
2455        let stmt = parse_one("SELECT l.name, r.tag FROM l FULL OUTER JOIN r ON l.id = r.l_id;");
2456        if let Statement::Select(s) = stmt {
2457            if let SelectCore::Select { from, .. } = &s.body.select {
2458                let from = from.as_ref().expect("FROM clause");
2459                assert_eq!(from.joins.len(), 1);
2460                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
2461            } else {
2462                unreachable!("expected Select core");
2463            }
2464        } else {
2465            unreachable!("expected Select");
2466        }
2467    }
2468
2469    #[test]
2470    fn test_parser_join_cross() {
2471        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
2472        if let Statement::Select(s) = stmt {
2473            if let SelectCore::Select { from, .. } = &s.body.select {
2474                let from = from.as_ref().expect("FROM clause");
2475                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
2476            } else {
2477                unreachable!("expected Select core");
2478            }
2479        } else {
2480            unreachable!("expected Select");
2481        }
2482    }
2483
2484    #[test]
2485    fn test_parser_join_natural() {
2486        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
2487        if let Statement::Select(s) = stmt {
2488            if let SelectCore::Select { from, .. } = &s.body.select {
2489                let from = from.as_ref().expect("FROM clause");
2490                assert!(from.joins[0].join_type.natural);
2491            } else {
2492                unreachable!("expected Select core");
2493            }
2494        } else {
2495            unreachable!("expected Select");
2496        }
2497    }
2498
2499    #[test]
2500    fn test_parser_join_using() {
2501        let stmt = parse_one("SELECT * FROM a JOIN b USING (id)");
2502        if let Statement::Select(s) = stmt {
2503            if let SelectCore::Select { from, .. } = &s.body.select {
2504                let from = from.as_ref().expect("FROM clause");
2505                assert!(matches!(
2506                    from.joins[0].constraint,
2507                    Some(JoinConstraint::Using(_))
2508                ));
2509            } else {
2510                unreachable!("expected Select core");
2511            }
2512        } else {
2513            unreachable!("expected Select");
2514        }
2515    }
2516
2517    #[test]
2518    fn test_parser_join_comma() {
2519        // Comma-join is an implicit cross join.
2520        let stmt = parse_one("SELECT * FROM a, b WHERE a.id = b.a_id");
2521        if let Statement::Select(s) = stmt {
2522            if let SelectCore::Select { from, .. } = &s.body.select {
2523                let from = from.as_ref().expect("FROM clause");
2524                assert!(!from.joins.is_empty());
2525                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
2526            } else {
2527                unreachable!("expected Select core");
2528            }
2529        } else {
2530            unreachable!("expected Select");
2531        }
2532    }
2533
2534    // -----------------------------------------------------------------------
2535    // bd-2kvo Phase 3 acceptance: CTE syntax
2536    // -----------------------------------------------------------------------
2537
2538    #[test]
2539    fn test_parser_cte_basic() {
2540        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
2541        if let Statement::Select(s) = stmt {
2542            let with = s.with.as_ref().expect("WITH clause");
2543            assert!(!with.recursive);
2544            assert_eq!(with.ctes.len(), 1);
2545            assert_eq!(with.ctes[0].name, "cte");
2546        } else {
2547            unreachable!("expected Select");
2548        }
2549    }
2550
2551    #[test]
2552    fn test_parser_cte_multiple() {
2553        let stmt = parse_one("WITH a AS (SELECT 1), b AS (SELECT 2) SELECT * FROM a, b");
2554        if let Statement::Select(s) = stmt {
2555            let with = s.with.as_ref().expect("WITH clause");
2556            assert_eq!(with.ctes.len(), 2);
2557            assert_eq!(with.ctes[0].name, "a");
2558            assert_eq!(with.ctes[1].name, "b");
2559        } else {
2560            unreachable!("expected Select");
2561        }
2562    }
2563
2564    #[test]
2565    fn test_parser_cte_recursive() {
2566        let stmt = parse_one(
2567            "WITH RECURSIVE cnt(x) AS (\
2568             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
2569             ) SELECT x FROM cnt",
2570        );
2571        if let Statement::Select(s) = stmt {
2572            let with = s.with.as_ref().expect("WITH clause");
2573            assert!(with.recursive);
2574            assert_eq!(with.ctes[0].name, "cnt");
2575            assert_eq!(with.ctes[0].columns, vec!["x".to_owned()]);
2576        } else {
2577            unreachable!("expected Select");
2578        }
2579    }
2580
2581    #[test]
2582    fn test_parser_cte_materialized() {
2583        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
2584        if let Statement::Select(s) = stmt {
2585            let with = s.with.as_ref().expect("WITH clause");
2586            assert_eq!(
2587                with.ctes[0].materialized,
2588                Some(CteMaterialized::Materialized)
2589            );
2590        } else {
2591            unreachable!("expected Select");
2592        }
2593    }
2594
2595    // -----------------------------------------------------------------------
2596    // bd-2d6i §12.1 SELECT full syntax acceptance tests
2597    // -----------------------------------------------------------------------
2598
2599    #[test]
2600    fn test_select_table_star() {
2601        let stmt = parse_one("SELECT t1.* FROM t1, t2");
2602        if let Statement::Select(s) = stmt {
2603            if let SelectCore::Select { columns, .. } = &s.body.select {
2604                assert!(
2605                    matches!(&columns[0], ResultColumn::TableStar(t) if t == "t1"),
2606                    "expected TableStar(t1), got {:?}",
2607                    columns[0]
2608                );
2609            } else {
2610                unreachable!("expected Select core");
2611            }
2612        } else {
2613            unreachable!("expected Select");
2614        }
2615    }
2616
2617    #[test]
2618    fn test_select_expr_alias() {
2619        let stmt = parse_one("SELECT x + 1 AS result FROM t");
2620        if let Statement::Select(s) = stmt {
2621            if let SelectCore::Select { columns, .. } = &s.body.select {
2622                match &columns[0] {
2623                    ResultColumn::Expr {
2624                        alias: Some(alias), ..
2625                    } => assert_eq!(alias, "result"),
2626                    other => unreachable!("expected aliased expr column, got {other:?}"),
2627                }
2628            } else {
2629                unreachable!("expected Select core");
2630            }
2631        } else {
2632            unreachable!("expected Select");
2633        }
2634    }
2635
2636    #[test]
2637    fn test_select_distinct_keyword() {
2638        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
2639        if let Statement::Select(s) = stmt {
2640            if let SelectCore::Select {
2641                distinct, columns, ..
2642            } = &s.body.select
2643            {
2644                assert_eq!(*distinct, Distinctness::Distinct);
2645                assert_eq!(columns.len(), 2);
2646            } else {
2647                unreachable!("expected Select core");
2648            }
2649        } else {
2650            unreachable!("expected Select");
2651        }
2652    }
2653
2654    #[test]
2655    fn test_select_values_clause() {
2656        let stmt = parse_one("VALUES (1, 2), (3, 4)");
2657        if let Statement::Select(s) = stmt {
2658            if let SelectCore::Values(rows) = &s.body.select {
2659                assert_eq!(rows.len(), 2);
2660                assert_eq!(rows[0].len(), 2);
2661                assert_eq!(rows[1].len(), 2);
2662            } else {
2663                unreachable!("expected Values core");
2664            }
2665        } else {
2666            unreachable!("expected Select");
2667        }
2668    }
2669
2670    #[test]
2671    fn test_select_group_by_having() {
2672        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
2673        if let Statement::Select(s) = stmt {
2674            if let SelectCore::Select {
2675                group_by, having, ..
2676            } = &s.body.select
2677            {
2678                assert_eq!(group_by.len(), 1);
2679                assert!(having.is_some(), "HAVING clause must be present");
2680            } else {
2681                unreachable!("expected Select core");
2682            }
2683        } else {
2684            unreachable!("expected Select");
2685        }
2686    }
2687
2688    #[test]
2689    fn test_compound_union() {
2690        let stmt = parse_one("SELECT 1 UNION SELECT 2");
2691        if let Statement::Select(s) = stmt {
2692            assert_eq!(s.body.compounds.len(), 1);
2693            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
2694        } else {
2695            unreachable!("expected Select");
2696        }
2697    }
2698
2699    #[test]
2700    fn test_compound_union_all() {
2701        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
2702        if let Statement::Select(s) = stmt {
2703            assert_eq!(s.body.compounds.len(), 1);
2704            assert_eq!(s.body.compounds[0].0, CompoundOp::UnionAll);
2705        } else {
2706            unreachable!("expected Select");
2707        }
2708    }
2709
2710    #[test]
2711    fn test_compound_intersect() {
2712        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
2713        if let Statement::Select(s) = stmt {
2714            assert_eq!(s.body.compounds.len(), 1);
2715            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
2716        } else {
2717            unreachable!("expected Select");
2718        }
2719    }
2720
2721    #[test]
2722    fn test_compound_except() {
2723        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
2724        if let Statement::Select(s) = stmt {
2725            assert_eq!(s.body.compounds.len(), 1);
2726            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
2727        } else {
2728            unreachable!("expected Select");
2729        }
2730    }
2731
2732    #[test]
2733    fn test_compound_order_applies_to_whole() {
2734        // ORDER BY and LIMIT apply to the entire compound result per SQL spec.
2735        let stmt = parse_one("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
2736        if let Statement::Select(s) = stmt {
2737            assert_eq!(s.body.compounds.len(), 1);
2738            assert_eq!(s.order_by.len(), 1, "ORDER BY must be on compound");
2739            assert!(s.limit.is_some(), "LIMIT must be on compound");
2740        } else {
2741            unreachable!("expected Select");
2742        }
2743    }
2744
2745    #[test]
2746    fn test_compound_three_way() {
2747        let stmt = parse_one("SELECT 1 UNION SELECT 2 INTERSECT SELECT 3");
2748        if let Statement::Select(s) = stmt {
2749            assert_eq!(s.body.compounds.len(), 2);
2750            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
2751            assert_eq!(s.body.compounds[1].0, CompoundOp::Intersect);
2752        } else {
2753            unreachable!("expected Select");
2754        }
2755    }
2756
2757    #[test]
2758    fn test_cte_not_materialized() {
2759        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
2760        if let Statement::Select(s) = stmt {
2761            let with = s.with.as_ref().expect("WITH clause");
2762            assert_eq!(
2763                with.ctes[0].materialized,
2764                Some(CteMaterialized::NotMaterialized)
2765            );
2766        } else {
2767            unreachable!("expected Select");
2768        }
2769    }
2770
2771    #[test]
2772    fn test_cte_with_explicit_columns() {
2773        let stmt = parse_one("WITH cte(a, b, c) AS (SELECT 1, 2, 3) SELECT * FROM cte");
2774        if let Statement::Select(s) = stmt {
2775            let with = s.with.as_ref().expect("WITH clause");
2776            assert_eq!(with.ctes[0].columns, vec!["a", "b", "c"]);
2777        } else {
2778            unreachable!("expected Select");
2779        }
2780    }
2781
2782    #[test]
2783    fn test_window_frame_range() {
2784        let stmt = parse_one(
2785            "SELECT sum(x) OVER (ORDER BY y RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t",
2786        );
2787        if let Statement::Select(s) = stmt {
2788            if let SelectCore::Select { columns, .. } = &s.body.select {
2789                match &columns[0] {
2790                    ResultColumn::Expr {
2791                        expr:
2792                            Expr::FunctionCall {
2793                                over: Some(over), ..
2794                            },
2795                        ..
2796                    } => {
2797                        let frame = over.frame.as_ref().expect("frame spec");
2798                        assert_eq!(frame.frame_type, FrameType::Range);
2799                        assert!(matches!(frame.start, FrameBound::UnboundedPreceding));
2800                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
2801                    }
2802                    other => unreachable!("expected window function, got {other:?}"),
2803                }
2804            } else {
2805                unreachable!("expected Select core");
2806            }
2807        } else {
2808            unreachable!("expected Select");
2809        }
2810    }
2811
2812    #[test]
2813    fn test_window_frame_groups() {
2814        let stmt = parse_one(
2815            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
2816        );
2817        if let Statement::Select(s) = stmt {
2818            if let SelectCore::Select { columns, .. } = &s.body.select {
2819                match &columns[0] {
2820                    ResultColumn::Expr {
2821                        expr:
2822                            Expr::FunctionCall {
2823                                over: Some(over), ..
2824                            },
2825                        ..
2826                    } => {
2827                        let frame = over.frame.as_ref().expect("frame spec");
2828                        assert_eq!(frame.frame_type, FrameType::Groups);
2829                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
2830                        assert!(matches!(frame.end, Some(FrameBound::Following(_))));
2831                    }
2832                    other => unreachable!("expected window function, got {other:?}"),
2833                }
2834            } else {
2835                unreachable!("expected Select core");
2836            }
2837        } else {
2838            unreachable!("expected Select");
2839        }
2840    }
2841
2842    #[test]
2843    fn test_window_frame_exclude_current_row() {
2844        let stmt = parse_one(
2845            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
2846             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
2847        );
2848        if let Statement::Select(s) = stmt {
2849            if let SelectCore::Select { columns, .. } = &s.body.select {
2850                match &columns[0] {
2851                    ResultColumn::Expr {
2852                        expr:
2853                            Expr::FunctionCall {
2854                                over: Some(over), ..
2855                            },
2856                        ..
2857                    } => {
2858                        let frame = over.frame.as_ref().expect("frame spec");
2859                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
2860                    }
2861                    other => unreachable!("expected window function, got {other:?}"),
2862                }
2863            } else {
2864                unreachable!("expected Select core");
2865            }
2866        } else {
2867            unreachable!("expected Select");
2868        }
2869    }
2870
2871    #[test]
2872    fn test_window_frame_exclude_ties() {
2873        let stmt = parse_one(
2874            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
2875             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
2876        );
2877        if let Statement::Select(s) = stmt {
2878            if let SelectCore::Select { columns, .. } = &s.body.select {
2879                match &columns[0] {
2880                    ResultColumn::Expr {
2881                        expr:
2882                            Expr::FunctionCall {
2883                                over: Some(over), ..
2884                            },
2885                        ..
2886                    } => {
2887                        let frame = over.frame.as_ref().expect("frame spec");
2888                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
2889                    }
2890                    other => unreachable!("expected window function, got {other:?}"),
2891                }
2892            } else {
2893                unreachable!("expected Select core");
2894            }
2895        } else {
2896            unreachable!("expected Select");
2897        }
2898    }
2899
2900    #[test]
2901    fn test_window_frame_exclude_group() {
2902        let stmt =
2903            parse_one("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
2904        if let Statement::Select(s) = stmt {
2905            if let SelectCore::Select { columns, .. } = &s.body.select {
2906                match &columns[0] {
2907                    ResultColumn::Expr {
2908                        expr:
2909                            Expr::FunctionCall {
2910                                over: Some(over), ..
2911                            },
2912                        ..
2913                    } => {
2914                        let frame = over.frame.as_ref().expect("frame spec");
2915                        assert_eq!(frame.frame_type, FrameType::Groups);
2916                        assert_eq!(frame.exclude, Some(FrameExclude::Group));
2917                    }
2918                    other => unreachable!("expected window function, got {other:?}"),
2919                }
2920            } else {
2921                unreachable!("expected Select core");
2922            }
2923        } else {
2924            unreachable!("expected Select");
2925        }
2926    }
2927
2928    #[test]
2929    fn test_window_frame_unbounded_following() {
2930        let stmt = parse_one(
2931            "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM t",
2932        );
2933        if let Statement::Select(s) = stmt {
2934            if let SelectCore::Select { columns, .. } = &s.body.select {
2935                match &columns[0] {
2936                    ResultColumn::Expr {
2937                        expr:
2938                            Expr::FunctionCall {
2939                                over: Some(over), ..
2940                            },
2941                        ..
2942                    } => {
2943                        let frame = over.frame.as_ref().expect("frame spec");
2944                        assert!(matches!(frame.start, FrameBound::CurrentRow));
2945                        assert!(matches!(frame.end, Some(FrameBound::UnboundedFollowing)));
2946                    }
2947                    other => unreachable!("expected window function, got {other:?}"),
2948                }
2949            } else {
2950                unreachable!("expected Select core");
2951            }
2952        } else {
2953            unreachable!("expected Select");
2954        }
2955    }
2956
2957    #[test]
2958    fn test_filter_clause_aggregate() {
2959        let stmt = parse_one("SELECT count(*) FILTER (WHERE x > 0) FROM t");
2960        if let Statement::Select(s) = stmt {
2961            if let SelectCore::Select { columns, .. } = &s.body.select {
2962                match &columns[0] {
2963                    ResultColumn::Expr {
2964                        expr: Expr::FunctionCall { filter, .. },
2965                        ..
2966                    } => {
2967                        assert!(
2968                            filter.is_some(),
2969                            "FILTER clause must be present on aggregate"
2970                        );
2971                    }
2972                    other => unreachable!("expected function call with filter, got {other:?}"),
2973                }
2974            } else {
2975                unreachable!("expected Select core");
2976            }
2977        } else {
2978            unreachable!("expected Select");
2979        }
2980    }
2981
2982    #[test]
2983    fn test_filter_clause_window() {
2984        let stmt = parse_one("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
2985        if let Statement::Select(s) = stmt {
2986            if let SelectCore::Select { columns, .. } = &s.body.select {
2987                match &columns[0] {
2988                    ResultColumn::Expr {
2989                        expr:
2990                            Expr::FunctionCall {
2991                                filter,
2992                                over: Some(_),
2993                                ..
2994                            },
2995                        ..
2996                    } => {
2997                        assert!(
2998                            filter.is_some(),
2999                            "FILTER clause must be present on window function"
3000                        );
3001                    }
3002                    other => unreachable!("expected window function with filter, got {other:?}"),
3003                }
3004            } else {
3005                unreachable!("expected Select core");
3006            }
3007        } else {
3008            unreachable!("expected Select");
3009        }
3010    }
3011
3012    #[test]
3013    fn test_subquery_in_from() {
3014        let stmt = parse_one("SELECT sub.x FROM (SELECT 1 AS x) AS sub");
3015        if let Statement::Select(s) = stmt {
3016            if let SelectCore::Select { from, .. } = &s.body.select {
3017                let from = from.as_ref().expect("FROM clause");
3018                match &from.source {
3019                    TableOrSubquery::Subquery { alias, .. } => {
3020                        assert_eq!(alias.as_deref(), Some("sub"));
3021                    }
3022                    other => unreachable!("expected subquery source, got {other:?}"),
3023                }
3024            } else {
3025                unreachable!("expected Select core");
3026            }
3027        } else {
3028            unreachable!("expected Select");
3029        }
3030    }
3031
3032    #[test]
3033    fn test_multiple_joins_chain() {
3034        let stmt = parse_one(
3035            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id \
3036             LEFT JOIN c ON b.id = c.b_id \
3037             CROSS JOIN d",
3038        );
3039        if let Statement::Select(s) = stmt {
3040            if let SelectCore::Select { from, .. } = &s.body.select {
3041                let from = from.as_ref().expect("FROM clause");
3042                assert_eq!(from.joins.len(), 3);
3043                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
3044                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
3045                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
3046            } else {
3047                unreachable!("expected Select core");
3048            }
3049        } else {
3050            unreachable!("expected Select");
3051        }
3052    }
3053
3054    #[test]
3055    fn test_natural_left_join() {
3056        let stmt = parse_one("SELECT * FROM a NATURAL LEFT JOIN b");
3057        if let Statement::Select(s) = stmt {
3058            if let SelectCore::Select { from, .. } = &s.body.select {
3059                let from = from.as_ref().expect("FROM clause");
3060                let jt = &from.joins[0].join_type;
3061                assert!(jt.natural, "must be NATURAL");
3062                assert_eq!(jt.kind, JoinKind::Left);
3063            } else {
3064                unreachable!("expected Select core");
3065            }
3066        } else {
3067            unreachable!("expected Select");
3068        }
3069    }
3070
3071    #[test]
3072    fn test_select_nulls_first_default_asc() {
3073        // Verify NULLS FIRST with explicit ASC direction.
3074        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
3075        if let Statement::Select(s) = stmt {
3076            assert_eq!(s.order_by.len(), 1);
3077            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
3078            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
3079        } else {
3080            unreachable!("expected Select");
3081        }
3082    }
3083
3084    #[test]
3085    fn test_select_nulls_last_desc() {
3086        // Verify NULLS LAST with explicit DESC direction.
3087        let stmt = parse_one("SELECT a FROM t ORDER BY a DESC NULLS LAST");
3088        if let Statement::Select(s) = stmt {
3089            assert_eq!(s.order_by.len(), 1);
3090            assert_eq!(s.order_by[0].direction, Some(SortDirection::Desc));
3091            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
3092        } else {
3093            unreachable!("expected Select");
3094        }
3095    }
3096
3097    // -----------------------------------------------------------------------
3098    // bd-2d6i §12.1 roundtrip coverage for advanced SELECT forms
3099    // -----------------------------------------------------------------------
3100
3101    #[test]
3102    fn test_roundtrip_select_filter_clause() {
3103        assert_roundtrip("SELECT count(*) FILTER (WHERE x > 0) FROM t");
3104    }
3105
3106    #[test]
3107    fn test_roundtrip_select_window_frame_groups() {
3108        assert_roundtrip(
3109            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
3110        );
3111    }
3112
3113    #[test]
3114    fn test_roundtrip_select_window_frame_exclude() {
3115        assert_roundtrip(
3116            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3117             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
3118        );
3119        assert_roundtrip(
3120            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3121             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
3122        );
3123        assert_roundtrip("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
3124    }
3125
3126    #[test]
3127    fn test_roundtrip_select_nulls_order() {
3128        assert_roundtrip("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
3129        assert_roundtrip("SELECT a FROM t ORDER BY a DESC NULLS LAST");
3130    }
3131
3132    #[test]
3133    fn test_roundtrip_select_values() {
3134        assert_roundtrip("VALUES (1, 2), (3, 4)");
3135    }
3136
3137    #[test]
3138    fn test_roundtrip_select_compound_order_limit() {
3139        assert_roundtrip("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
3140    }
3141
3142    #[test]
3143    fn test_roundtrip_select_cte_not_materialized() {
3144        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
3145    }
3146
3147    #[test]
3148    fn test_roundtrip_select_natural_left_join() {
3149        assert_roundtrip("SELECT * FROM a NATURAL LEFT JOIN b");
3150    }
3151
3152    #[test]
3153    fn test_roundtrip_select_indexed_by() {
3154        assert_roundtrip("SELECT * FROM t INDEXED BY idx_t WHERE x = 1");
3155    }
3156
3157    #[test]
3158    fn test_roundtrip_select_filter_window_combined() {
3159        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
3160    }
3161
3162    #[test]
3163    fn test_roundtrip_select_three_way_compound() {
3164        assert_roundtrip("SELECT 1 UNION SELECT 2 EXCEPT SELECT 3");
3165    }
3166
3167    #[test]
3168    fn test_roundtrip_select_multiple_joins() {
3169        assert_roundtrip(
3170            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id LEFT JOIN c ON b.id = c.b_id",
3171        );
3172    }
3173
3174    // -----------------------------------------------------------------------
3175    // bd-2d6i §12.1 — remaining required tests (exact names per bead spec)
3176    // -----------------------------------------------------------------------
3177
3178    #[test]
3179    fn test_select_star() {
3180        // SELECT * returns all columns from all tables.
3181        let stmt = parse_one("SELECT * FROM t");
3182        if let Statement::Select(s) = stmt {
3183            if let SelectCore::Select { columns, .. } = &s.body.select {
3184                assert!(matches!(columns[0], ResultColumn::Star));
3185            } else {
3186                unreachable!("expected Select core");
3187            }
3188        } else {
3189            unreachable!("expected Select");
3190        }
3191    }
3192
3193    #[test]
3194    fn test_inner_join_on() {
3195        // INNER JOIN ON produces correct intersection.
3196        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
3197        if let Statement::Select(s) = stmt {
3198            if let SelectCore::Select { from, .. } = &s.body.select {
3199                let from = from.as_ref().expect("FROM clause");
3200                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
3201                assert!(matches!(
3202                    from.joins[0].constraint,
3203                    Some(JoinConstraint::On(_))
3204                ));
3205            } else {
3206                unreachable!("expected Select core");
3207            }
3208        } else {
3209            unreachable!("expected Select");
3210        }
3211    }
3212
3213    #[test]
3214    fn test_left_outer_join() {
3215        // LEFT JOIN returns all left rows with NULLs for non-matching right.
3216        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
3217        if let Statement::Select(s) = stmt {
3218            if let SelectCore::Select { from, .. } = &s.body.select {
3219                let from = from.as_ref().expect("FROM clause");
3220                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
3221            } else {
3222                unreachable!("expected Select core");
3223            }
3224        } else {
3225            unreachable!("expected Select");
3226        }
3227    }
3228
3229    #[test]
3230    fn test_right_outer_join() {
3231        // RIGHT JOIN returns all right rows (3.39+ feature).
3232        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
3233        if let Statement::Select(s) = stmt {
3234            if let SelectCore::Select { from, .. } = &s.body.select {
3235                let from = from.as_ref().expect("FROM clause");
3236                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
3237            } else {
3238                unreachable!("expected Select core");
3239            }
3240        } else {
3241            unreachable!("expected Select");
3242        }
3243    }
3244
3245    #[test]
3246    fn test_full_outer_join() {
3247        // FULL OUTER JOIN returns rows from both tables.
3248        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
3249        if let Statement::Select(s) = stmt {
3250            if let SelectCore::Select { from, .. } = &s.body.select {
3251                let from = from.as_ref().expect("FROM clause");
3252                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
3253            } else {
3254                unreachable!("expected Select core");
3255            }
3256        } else {
3257            unreachable!("expected Select");
3258        }
3259    }
3260
3261    #[test]
3262    fn test_cross_join_no_reorder() {
3263        // CROSS JOIN prevents optimizer reordering; parser must produce JoinKind::Cross.
3264        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
3265        if let Statement::Select(s) = stmt {
3266            if let SelectCore::Select { from, .. } = &s.body.select {
3267                let from = from.as_ref().expect("FROM clause");
3268                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
3269                // Cross joins must NOT have an ON or USING constraint.
3270                assert!(from.joins[0].constraint.is_none());
3271            } else {
3272                unreachable!("expected Select core");
3273            }
3274        } else {
3275            unreachable!("expected Select");
3276        }
3277    }
3278
3279    #[test]
3280    fn test_natural_join() {
3281        // NATURAL JOIN uses shared column names for implicit ON.
3282        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
3283        if let Statement::Select(s) = stmt {
3284            if let SelectCore::Select { from, .. } = &s.body.select {
3285                let from = from.as_ref().expect("FROM clause");
3286                assert!(from.joins[0].join_type.natural);
3287            } else {
3288                unreachable!("expected Select core");
3289            }
3290        } else {
3291            unreachable!("expected Select");
3292        }
3293    }
3294
3295    #[test]
3296    fn test_using_clause() {
3297        // JOIN USING joins on specified shared columns.
3298        let stmt = parse_one("SELECT * FROM a JOIN b USING (id, name)");
3299        if let Statement::Select(s) = stmt {
3300            if let SelectCore::Select { from, .. } = &s.body.select {
3301                let from = from.as_ref().expect("FROM clause");
3302                match &from.joins[0].constraint {
3303                    Some(JoinConstraint::Using(cols)) => {
3304                        assert_eq!(cols.len(), 2);
3305                        assert_eq!(cols[0], "id");
3306                        assert_eq!(cols[1], "name");
3307                    }
3308                    other => unreachable!("expected USING constraint, got {other:?}"),
3309                }
3310            } else {
3311                unreachable!("expected Select core");
3312            }
3313        } else {
3314            unreachable!("expected Select");
3315        }
3316    }
3317
3318    #[test]
3319    fn test_cte_basic() {
3320        // WITH clause defines reusable named subquery.
3321        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
3322        if let Statement::Select(s) = stmt {
3323            let with = s.with.as_ref().expect("WITH clause");
3324            assert!(!with.recursive);
3325            assert_eq!(with.ctes.len(), 1);
3326            assert_eq!(with.ctes[0].name, "cte");
3327        } else {
3328            unreachable!("expected Select");
3329        }
3330    }
3331
3332    #[test]
3333    fn test_cte_recursive_union_all() {
3334        // Recursive CTE with UNION ALL generates rows.
3335        let stmt = parse_one(
3336            "WITH RECURSIVE cnt(x) AS (\
3337             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
3338             ) SELECT x FROM cnt",
3339        );
3340        if let Statement::Select(s) = stmt {
3341            let with = s.with.as_ref().expect("WITH clause");
3342            assert!(with.recursive);
3343            assert_eq!(with.ctes[0].name, "cnt");
3344            // Verify the CTE body contains a UNION ALL compound.
3345            let cte_body = &with.ctes[0].query;
3346            assert_eq!(cte_body.body.compounds.len(), 1);
3347            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::UnionAll);
3348        } else {
3349            unreachable!("expected Select");
3350        }
3351    }
3352
3353    #[test]
3354    fn test_cte_recursive_union_cycle_detection() {
3355        // Recursive CTE with UNION (not UNION ALL) detects cycles via dedup.
3356        let stmt = parse_one(
3357            "WITH RECURSIVE paths(a, b) AS (\
3358             SELECT src, dst FROM edges \
3359             UNION \
3360             SELECT p.a, e.dst FROM paths p JOIN edges e ON p.b = e.src\
3361             ) SELECT * FROM paths",
3362        );
3363        if let Statement::Select(s) = stmt {
3364            let with = s.with.as_ref().expect("WITH clause");
3365            assert!(with.recursive);
3366            // UNION (not UNION ALL) provides implicit cycle detection.
3367            let cte_body = &with.ctes[0].query;
3368            assert_eq!(cte_body.body.compounds.len(), 1);
3369            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::Union);
3370        } else {
3371            unreachable!("expected Select");
3372        }
3373    }
3374
3375    #[test]
3376    fn test_cte_materialized_hint() {
3377        // MATERIALIZED forces single evaluation.
3378        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
3379        if let Statement::Select(s) = stmt {
3380            let with = s.with.as_ref().expect("WITH clause");
3381            assert_eq!(
3382                with.ctes[0].materialized,
3383                Some(CteMaterialized::Materialized)
3384            );
3385        } else {
3386            unreachable!("expected Select");
3387        }
3388    }
3389
3390    #[test]
3391    fn test_cte_not_materialized_hint() {
3392        // NOT MATERIALIZED allows inlining.
3393        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
3394        if let Statement::Select(s) = stmt {
3395            let with = s.with.as_ref().expect("WITH clause");
3396            assert_eq!(
3397                with.ctes[0].materialized,
3398                Some(CteMaterialized::NotMaterialized)
3399            );
3400        } else {
3401            unreachable!("expected Select");
3402        }
3403    }
3404
3405    #[test]
3406    fn test_window_partition_by() {
3407        // PARTITION BY correctly groups window function output.
3408        let stmt = parse_one("SELECT sum(x) OVER (PARTITION BY dept) FROM emp");
3409        if let Statement::Select(s) = stmt {
3410            if let SelectCore::Select { columns, .. } = &s.body.select {
3411                match &columns[0] {
3412                    ResultColumn::Expr {
3413                        expr:
3414                            Expr::FunctionCall {
3415                                over: Some(over), ..
3416                            },
3417                        ..
3418                    } => {
3419                        assert_eq!(over.partition_by.len(), 1);
3420                    }
3421                    other => unreachable!("expected window function, got {other:?}"),
3422                }
3423            } else {
3424                unreachable!("expected Select core");
3425            }
3426        } else {
3427            unreachable!("expected Select");
3428        }
3429    }
3430
3431    #[test]
3432    fn test_window_order_by() {
3433        // ORDER BY within window function controls row ordering.
3434        let stmt = parse_one("SELECT row_number() OVER (ORDER BY salary DESC) FROM emp");
3435        if let Statement::Select(s) = stmt {
3436            if let SelectCore::Select { columns, .. } = &s.body.select {
3437                match &columns[0] {
3438                    ResultColumn::Expr {
3439                        expr:
3440                            Expr::FunctionCall {
3441                                over: Some(over), ..
3442                            },
3443                        ..
3444                    } => {
3445                        assert_eq!(over.order_by.len(), 1);
3446                        assert_eq!(over.order_by[0].direction, Some(SortDirection::Desc));
3447                    }
3448                    other => unreachable!("expected window function, got {other:?}"),
3449                }
3450            } else {
3451                unreachable!("expected Select core");
3452            }
3453        } else {
3454            unreachable!("expected Select");
3455        }
3456    }
3457
3458    #[test]
3459    fn test_window_frame_rows() {
3460        // ROWS frame spec limits window to specified row range.
3461        let stmt = parse_one(
3462            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
3463        );
3464        if let Statement::Select(s) = stmt {
3465            if let SelectCore::Select { columns, .. } = &s.body.select {
3466                match &columns[0] {
3467                    ResultColumn::Expr {
3468                        expr:
3469                            Expr::FunctionCall {
3470                                over: Some(over), ..
3471                            },
3472                        ..
3473                    } => {
3474                        let frame = over.frame.as_ref().expect("frame spec");
3475                        assert_eq!(frame.frame_type, FrameType::Rows);
3476                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
3477                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
3478                    }
3479                    other => unreachable!("expected window function, got {other:?}"),
3480                }
3481            } else {
3482                unreachable!("expected Select core");
3483            }
3484        } else {
3485            unreachable!("expected Select");
3486        }
3487    }
3488
3489    #[test]
3490    fn test_window_exclude_current_row() {
3491        // EXCLUDE CURRENT ROW omits current row from frame.
3492        let stmt = parse_one(
3493            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3494             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
3495        );
3496        if let Statement::Select(s) = stmt {
3497            if let SelectCore::Select { columns, .. } = &s.body.select {
3498                match &columns[0] {
3499                    ResultColumn::Expr {
3500                        expr:
3501                            Expr::FunctionCall {
3502                                over: Some(over), ..
3503                            },
3504                        ..
3505                    } => {
3506                        let frame = over.frame.as_ref().expect("frame spec");
3507                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
3508                    }
3509                    other => unreachable!("expected window function, got {other:?}"),
3510                }
3511            } else {
3512                unreachable!("expected Select core");
3513            }
3514        } else {
3515            unreachable!("expected Select");
3516        }
3517    }
3518
3519    #[test]
3520    fn test_window_exclude_ties() {
3521        // EXCLUDE TIES omits peers of current row.
3522        let stmt = parse_one(
3523            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3524             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
3525        );
3526        if let Statement::Select(s) = stmt {
3527            if let SelectCore::Select { columns, .. } = &s.body.select {
3528                match &columns[0] {
3529                    ResultColumn::Expr {
3530                        expr:
3531                            Expr::FunctionCall {
3532                                over: Some(over), ..
3533                            },
3534                        ..
3535                    } => {
3536                        let frame = over.frame.as_ref().expect("frame spec");
3537                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
3538                    }
3539                    other => unreachable!("expected window function, got {other:?}"),
3540                }
3541            } else {
3542                unreachable!("expected Select core");
3543            }
3544        } else {
3545            unreachable!("expected Select");
3546        }
3547    }
3548
3549    #[test]
3550    fn test_nulls_first_asc() {
3551        // NULLS FIRST with ASC puts NULLs before non-NULL values.
3552        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
3553        if let Statement::Select(s) = stmt {
3554            assert_eq!(s.order_by.len(), 1);
3555            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
3556            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
3557        } else {
3558            unreachable!("expected Select");
3559        }
3560    }
3561
3562    #[test]
3563    fn test_nulls_last_asc() {
3564        // NULLS LAST with ASC puts NULLs after non-NULL values.
3565        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS LAST");
3566        if let Statement::Select(s) = stmt {
3567            assert_eq!(s.order_by.len(), 1);
3568            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
3569            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
3570        } else {
3571            unreachable!("expected Select");
3572        }
3573    }
3574
3575    #[test]
3576    fn test_distinct_deduplicates() {
3577        // SELECT DISTINCT removes duplicate rows (parser-level: keyword present).
3578        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
3579        if let Statement::Select(s) = stmt {
3580            if let SelectCore::Select { distinct, .. } = &s.body.select {
3581                assert_eq!(*distinct, Distinctness::Distinct);
3582            } else {
3583                unreachable!("expected Select core");
3584            }
3585        } else {
3586            unreachable!("expected Select");
3587        }
3588    }
3589
3590    #[test]
3591    fn test_limit_offset() {
3592        // LIMIT N OFFSET M skips M rows and returns N.
3593        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET 20");
3594        if let Statement::Select(s) = stmt {
3595            let limit = s.limit.expect("LIMIT clause");
3596            assert!(matches!(
3597                limit.limit,
3598                Expr::Literal(Literal::Integer(10), _)
3599            ));
3600            assert!(matches!(
3601                limit.offset,
3602                Some(Expr::Literal(Literal::Integer(20), _))
3603            ));
3604        } else {
3605            unreachable!("expected Select");
3606        }
3607    }
3608
3609    #[test]
3610    fn test_limit_comma_syntax() {
3611        // LIMIT offset,count (MySQL syntax) — offset first, count second.
3612        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
3613        if let Statement::Select(s) = stmt {
3614            let limit = s.limit.expect("LIMIT clause");
3615            // In MySQL syntax, LIMIT 5, 10 means offset=5, count=10.
3616            assert!(matches!(
3617                limit.limit,
3618                Expr::Literal(Literal::Integer(10), _)
3619            ));
3620            assert!(matches!(
3621                limit.offset,
3622                Some(Expr::Literal(Literal::Integer(5), _))
3623            ));
3624        } else {
3625            unreachable!("expected Select");
3626        }
3627    }
3628
3629    #[test]
3630    fn test_negative_limit_unlimited() {
3631        // Negative LIMIT means unlimited (parser accepts negative literal).
3632        let stmt = parse_one("SELECT a FROM t LIMIT -1");
3633        if let Statement::Select(s) = stmt {
3634            let limit = s.limit.expect("LIMIT clause");
3635            // Parser may represent -1 as UnaryOp::Negate on Integer(1),
3636            // or as Integer(-1). Either is valid.
3637            match &limit.limit {
3638                Expr::UnaryOp {
3639                    op: fsqlite_ast::UnaryOp::Negate,
3640                    ..
3641                } => {}
3642                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
3643                other => unreachable!("expected negative limit expression, got {other:?}"),
3644            }
3645        } else {
3646            unreachable!("expected Select");
3647        }
3648    }
3649
3650    #[test]
3651    fn test_negative_offset_zero() {
3652        // Negative OFFSET treated as zero (parser accepts negative literal).
3653        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET -5");
3654        if let Statement::Select(s) = stmt {
3655            let limit = s.limit.expect("LIMIT clause");
3656            assert!(limit.offset.is_some());
3657            match limit.offset.as_ref().unwrap() {
3658                Expr::UnaryOp {
3659                    op: fsqlite_ast::UnaryOp::Negate,
3660                    ..
3661                } => {}
3662                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
3663                other => unreachable!("expected negative offset expression, got {other:?}"),
3664            }
3665        } else {
3666            unreachable!("expected Select");
3667        }
3668    }
3669
3670    #[test]
3671    fn test_current_date_constant() {
3672        // current_date is parsed as a literal keyword.
3673        let stmt = parse_one("SELECT CURRENT_DATE");
3674        if let Statement::Select(s) = stmt {
3675            if let SelectCore::Select { columns, .. } = &s.body.select {
3676                match &columns[0] {
3677                    ResultColumn::Expr {
3678                        expr: Expr::Literal(Literal::CurrentDate, _),
3679                        ..
3680                    } => {}
3681                    other => unreachable!("expected CURRENT_DATE literal, got {other:?}"),
3682                }
3683            } else {
3684                unreachable!("expected Select core");
3685            }
3686        } else {
3687            unreachable!("expected Select");
3688        }
3689    }
3690
3691    #[test]
3692    fn test_current_time_constant() {
3693        // current_time is parsed as a literal keyword.
3694        let stmt = parse_one("SELECT CURRENT_TIME");
3695        if let Statement::Select(s) = stmt {
3696            if let SelectCore::Select { columns, .. } = &s.body.select {
3697                match &columns[0] {
3698                    ResultColumn::Expr {
3699                        expr: Expr::Literal(Literal::CurrentTime, _),
3700                        ..
3701                    } => {}
3702                    other => unreachable!("expected CURRENT_TIME literal, got {other:?}"),
3703                }
3704            } else {
3705                unreachable!("expected Select core");
3706            }
3707        } else {
3708            unreachable!("expected Select");
3709        }
3710    }
3711
3712    #[test]
3713    fn test_current_timestamp_constant() {
3714        // current_timestamp is parsed as a literal keyword.
3715        let stmt = parse_one("SELECT CURRENT_TIMESTAMP");
3716        if let Statement::Select(s) = stmt {
3717            if let SelectCore::Select { columns, .. } = &s.body.select {
3718                match &columns[0] {
3719                    ResultColumn::Expr {
3720                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
3721                        ..
3722                    } => {}
3723                    other => unreachable!("expected CURRENT_TIMESTAMP literal, got {other:?}"),
3724                }
3725            } else {
3726                unreachable!("expected Select core");
3727            }
3728        } else {
3729            unreachable!("expected Select");
3730        }
3731    }
3732
3733    #[test]
3734    fn test_date_constants_evaluated_once_per_statement() {
3735        // Parser-level: all three date/time constants parse as distinct Literal variants.
3736        // Runtime guarantee (evaluated once per stmt, not per row) is verified at VDBE level.
3737        let stmt = parse_one("SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP FROM t");
3738        if let Statement::Select(s) = stmt {
3739            if let SelectCore::Select { columns, .. } = &s.body.select {
3740                assert_eq!(columns.len(), 3);
3741                assert!(matches!(
3742                    &columns[0],
3743                    ResultColumn::Expr {
3744                        expr: Expr::Literal(Literal::CurrentDate, _),
3745                        ..
3746                    }
3747                ));
3748                assert!(matches!(
3749                    &columns[1],
3750                    ResultColumn::Expr {
3751                        expr: Expr::Literal(Literal::CurrentTime, _),
3752                        ..
3753                    }
3754                ));
3755                assert!(matches!(
3756                    &columns[2],
3757                    ResultColumn::Expr {
3758                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
3759                        ..
3760                    }
3761                ));
3762            } else {
3763                unreachable!("expected Select core");
3764            }
3765        } else {
3766            unreachable!("expected Select");
3767        }
3768    }
3769
3770    #[test]
3771    fn test_indexed_by_hint() {
3772        // FROM t1 INDEXED BY idx forces specified index.
3773        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
3774        if let Statement::Select(s) = stmt {
3775            if let SelectCore::Select { from, .. } = &s.body.select {
3776                let from = from.as_ref().expect("FROM clause");
3777                match &from.source {
3778                    TableOrSubquery::Table {
3779                        index_hint: Some(IndexHint::IndexedBy(name)),
3780                        ..
3781                    } => assert_eq!(name, "idx_t"),
3782                    other => unreachable!("expected indexed table source, got {other:?}"),
3783                }
3784            } else {
3785                unreachable!("expected Select core");
3786            }
3787        } else {
3788            unreachable!("expected Select");
3789        }
3790    }
3791
3792    #[test]
3793    fn test_not_indexed_hint() {
3794        // FROM t1 NOT INDEXED prevents index use.
3795        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
3796        if let Statement::Select(s) = stmt {
3797            if let SelectCore::Select { from, .. } = &s.body.select {
3798                let from = from.as_ref().expect("FROM clause");
3799                match &from.source {
3800                    TableOrSubquery::Table {
3801                        index_hint: Some(IndexHint::NotIndexed),
3802                        ..
3803                    } => {}
3804                    other => unreachable!("expected not-indexed table source, got {other:?}"),
3805                }
3806            } else {
3807                unreachable!("expected Select core");
3808            }
3809        } else {
3810            unreachable!("expected Select");
3811        }
3812    }
3813
3814    #[test]
3815    fn test_table_valued_function_in_from() {
3816        // FROM generate_series(1,100) works as table source.
3817        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
3818        if let Statement::Select(s) = stmt {
3819            if let SelectCore::Select { from, .. } = &s.body.select {
3820                let from = from.as_ref().expect("FROM clause");
3821                match &from.source {
3822                    TableOrSubquery::TableFunction { name, args, alias } => {
3823                        assert_eq!(name, "generate_series");
3824                        assert_eq!(args.len(), 2);
3825                        assert_eq!(alias.as_deref(), Some("gs"));
3826                    }
3827                    other => unreachable!("expected table-valued function source, got {other:?}"),
3828                }
3829            } else {
3830                unreachable!("expected Select core");
3831            }
3832        } else {
3833            unreachable!("expected Select");
3834        }
3835    }
3836
3837    // -----------------------------------------------------------------------
3838    // bd-1llo §12.2-12.4 INSERT + UPDATE + DELETE DML parsing tests
3839    // -----------------------------------------------------------------------
3840
3841    #[test]
3842    fn test_insert_values_single() {
3843        let stmt = parse_one("INSERT INTO t (a, b, c) VALUES (1, 'hello', 3.14)");
3844        if let Statement::Insert(i) = stmt {
3845            assert_eq!(i.columns, vec!["a", "b", "c"]);
3846            if let InsertSource::Values(rows) = &i.source {
3847                assert_eq!(rows.len(), 1);
3848                assert_eq!(rows[0].len(), 3);
3849            } else {
3850                unreachable!("expected Values source");
3851            }
3852        } else {
3853            unreachable!("expected Insert");
3854        }
3855    }
3856
3857    #[test]
3858    fn test_insert_values_multi() {
3859        let stmt = parse_one("INSERT INTO t (x, y) VALUES (1, 2), (3, 4), (5, 6)");
3860        if let Statement::Insert(i) = stmt {
3861            if let InsertSource::Values(rows) = &i.source {
3862                assert_eq!(rows.len(), 3);
3863                for row in rows {
3864                    assert_eq!(row.len(), 2);
3865                }
3866            } else {
3867                unreachable!("expected Values source");
3868            }
3869        } else {
3870            unreachable!("expected Insert");
3871        }
3872    }
3873
3874    #[test]
3875    fn test_insert_from_select() {
3876        let stmt = parse_one("INSERT INTO t2 (a, b) SELECT x, y FROM t1 WHERE x > 0");
3877        if let Statement::Insert(i) = stmt {
3878            assert!(matches!(i.source, InsertSource::Select(_)));
3879            assert_eq!(i.columns, vec!["a", "b"]);
3880        } else {
3881            unreachable!("expected Insert");
3882        }
3883    }
3884
3885    #[test]
3886    fn test_insert_from_select_without_from_clause() {
3887        let stmt = parse_one("INSERT INTO t (a) SELECT 1");
3888        if let Statement::Insert(i) = stmt {
3889            if let InsertSource::Select(select) = &i.source {
3890                if let SelectCore::Select { from, columns, .. } = &select.body.select {
3891                    assert!(from.is_none(), "SELECT 1 should parse without FROM");
3892                    assert_eq!(columns.len(), 1);
3893                } else {
3894                    unreachable!("expected Select core");
3895                }
3896            } else {
3897                unreachable!("expected Select source");
3898            }
3899        } else {
3900            unreachable!("expected Insert");
3901        }
3902    }
3903
3904    #[test]
3905    fn test_insert_from_select_subquery_source() {
3906        let stmt = parse_one("INSERT INTO t (a) SELECT sub.x FROM (SELECT 1 AS x) AS sub");
3907        if let Statement::Insert(i) = stmt {
3908            if let InsertSource::Select(select) = &i.source {
3909                if let SelectCore::Select { from, .. } = &select.body.select {
3910                    let from = from.as_ref().expect("FROM clause");
3911                    match &from.source {
3912                        TableOrSubquery::Subquery { alias, .. } => {
3913                            assert_eq!(alias.as_deref(), Some("sub"));
3914                        }
3915                        other => unreachable!("expected subquery source, got {other:?}"),
3916                    }
3917                } else {
3918                    unreachable!("expected Select core");
3919                }
3920            } else {
3921                unreachable!("expected Select source");
3922            }
3923        } else {
3924            unreachable!("expected Insert");
3925        }
3926    }
3927
3928    #[test]
3929    fn test_insert_from_select_table_function_source() {
3930        let stmt = parse_one("INSERT INTO t (a) SELECT gs.value FROM generate_series(1, 3) AS gs");
3931        if let Statement::Insert(i) = stmt {
3932            if let InsertSource::Select(select) = &i.source {
3933                if let SelectCore::Select { from, .. } = &select.body.select {
3934                    let from = from.as_ref().expect("FROM clause");
3935                    match &from.source {
3936                        TableOrSubquery::TableFunction { name, args, alias } => {
3937                            assert_eq!(name, "generate_series");
3938                            assert_eq!(args.len(), 2);
3939                            assert_eq!(alias.as_deref(), Some("gs"));
3940                        }
3941                        other => unreachable!("expected table function source, got {other:?}"),
3942                    }
3943                } else {
3944                    unreachable!("expected Select core");
3945                }
3946            } else {
3947                unreachable!("expected Select source");
3948            }
3949        } else {
3950            unreachable!("expected Insert");
3951        }
3952    }
3953
3954    #[test]
3955    fn test_insert_default_values() {
3956        let stmt = parse_one("INSERT INTO t DEFAULT VALUES");
3957        if let Statement::Insert(i) = stmt {
3958            assert!(matches!(i.source, InsertSource::DefaultValues));
3959            assert!(i.columns.is_empty());
3960        } else {
3961            unreachable!("expected Insert");
3962        }
3963    }
3964
3965    #[test]
3966    fn test_insert_or_abort() {
3967        let stmt = parse_one("INSERT OR ABORT INTO t (a) VALUES (1)");
3968        if let Statement::Insert(i) = stmt {
3969            assert_eq!(i.or_conflict, Some(ConflictAction::Abort));
3970        } else {
3971            unreachable!("expected Insert");
3972        }
3973    }
3974
3975    #[test]
3976    fn test_insert_or_rollback() {
3977        let stmt = parse_one("INSERT OR ROLLBACK INTO t (a) VALUES (1)");
3978        if let Statement::Insert(i) = stmt {
3979            assert_eq!(i.or_conflict, Some(ConflictAction::Rollback));
3980        } else {
3981            unreachable!("expected Insert");
3982        }
3983    }
3984
3985    #[test]
3986    fn test_insert_or_fail() {
3987        let stmt = parse_one("INSERT OR FAIL INTO t (a) VALUES (1)");
3988        if let Statement::Insert(i) = stmt {
3989            assert_eq!(i.or_conflict, Some(ConflictAction::Fail));
3990        } else {
3991            unreachable!("expected Insert");
3992        }
3993    }
3994
3995    #[test]
3996    fn test_insert_or_ignore() {
3997        let stmt = parse_one("INSERT OR IGNORE INTO t (a) VALUES (1)");
3998        if let Statement::Insert(i) = stmt {
3999            assert_eq!(i.or_conflict, Some(ConflictAction::Ignore));
4000        } else {
4001            unreachable!("expected Insert");
4002        }
4003    }
4004
4005    #[test]
4006    fn test_insert_or_replace() {
4007        // Both INSERT OR REPLACE and REPLACE INTO forms
4008        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1)");
4009        if let Statement::Insert(i) = stmt {
4010            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
4011        } else {
4012            unreachable!("expected Insert");
4013        }
4014    }
4015
4016    #[test]
4017    fn test_upsert_do_update() {
4018        let stmt = parse_one(
4019            "INSERT INTO t (a, b) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET b = excluded.b",
4020        );
4021        if let Statement::Insert(i) = stmt {
4022            assert_eq!(i.upsert.len(), 1);
4023            assert!(i.upsert[0].target.is_some());
4024            match &i.upsert[0].action {
4025                UpsertAction::Update {
4026                    assignments,
4027                    where_clause,
4028                } => {
4029                    assert_eq!(assignments.len(), 1);
4030                    assert!(where_clause.is_none());
4031                }
4032                UpsertAction::Nothing => unreachable!("expected Update action"),
4033            }
4034        } else {
4035            unreachable!("expected Insert");
4036        }
4037    }
4038
4039    #[test]
4040    fn test_upsert_do_nothing() {
4041        let stmt = parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
4042        if let Statement::Insert(i) = stmt {
4043            assert_eq!(i.upsert.len(), 1);
4044            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
4045        } else {
4046            unreachable!("expected Insert");
4047        }
4048    }
4049
4050    #[test]
4051    fn test_upsert_excluded_pseudo_table() {
4052        let stmt = parse_one(
4053            "INSERT INTO t (a, b) VALUES (1, 2) \
4054             ON CONFLICT (a) DO UPDATE SET b = excluded.b, a = excluded.a + 1",
4055        );
4056        if let Statement::Insert(i) = stmt {
4057            assert_eq!(i.upsert.len(), 1);
4058            if let UpsertAction::Update { assignments, .. } = &i.upsert[0].action {
4059                assert_eq!(assignments.len(), 2);
4060                // Verify excluded.b reference in first assignment
4061                match &assignments[0].value {
4062                    Expr::Column(col, _) => {
4063                        assert_eq!(col.table.as_deref(), Some("excluded"));
4064                        assert_eq!(col.column, "b");
4065                    }
4066                    other => unreachable!("expected Column ref to excluded.b, got {other:?}"),
4067                }
4068            } else {
4069                unreachable!("expected Update action");
4070            }
4071        } else {
4072            unreachable!("expected Insert");
4073        }
4074    }
4075
4076    #[test]
4077    fn test_upsert_multiple_on_conflict() {
4078        let stmt = parse_one(
4079            "INSERT INTO t (a, b) VALUES (1, 2) \
4080             ON CONFLICT (a) DO NOTHING \
4081             ON CONFLICT (b) DO UPDATE SET a = excluded.a",
4082        );
4083        if let Statement::Insert(i) = stmt {
4084            assert_eq!(i.upsert.len(), 2);
4085            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
4086            assert!(matches!(i.upsert[1].action, UpsertAction::Update { .. }));
4087        } else {
4088            unreachable!("expected Insert");
4089        }
4090    }
4091
4092    #[test]
4093    fn test_upsert_where_on_conflict_target() {
4094        let stmt = parse_one(
4095            "INSERT INTO t (a, b) VALUES (1, 2) \
4096             ON CONFLICT (a) WHERE a > 0 DO UPDATE SET b = excluded.b WHERE b < 100",
4097        );
4098        if let Statement::Insert(i) = stmt {
4099            assert_eq!(i.upsert.len(), 1);
4100            let target = i.upsert[0].target.as_ref().expect("conflict target");
4101            assert!(target.where_clause.is_some(), "target WHERE missing");
4102            if let UpsertAction::Update { where_clause, .. } = &i.upsert[0].action {
4103                assert!(where_clause.is_some(), "action WHERE missing");
4104            } else {
4105                unreachable!("expected Update action");
4106            }
4107        } else {
4108            unreachable!("expected Insert");
4109        }
4110    }
4111
4112    #[test]
4113    fn test_returning_insert() {
4114        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, rowid");
4115        if let Statement::Insert(i) = stmt {
4116            assert_eq!(i.returning.len(), 3);
4117        } else {
4118            unreachable!("expected Insert");
4119        }
4120    }
4121
4122    #[test]
4123    fn test_returning_insert_select_with_semicolon() {
4124        let stmt = parse_one("INSERT INTO t2 SELECT * FROM t RETURNING *;");
4125        if let Statement::Insert(i) = stmt {
4126            assert!(matches!(i.source, InsertSource::Select(_)));
4127            assert_eq!(i.returning.len(), 1);
4128            assert!(matches!(i.returning[0], ResultColumn::Star));
4129        } else {
4130            unreachable!("expected Insert");
4131        }
4132    }
4133
4134    #[test]
4135    fn test_returning_reflects_before_triggers() {
4136        // Parser-level: verify RETURNING clause parses alongside trigger-affected DML
4137        // Runtime verification that RETURNING reflects BEFORE-trigger modifications
4138        // is deferred to VDBE/engine tests
4139        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING a AS modified_a");
4140        if let Statement::Insert(i) = stmt {
4141            assert_eq!(i.returning.len(), 1);
4142            match &i.returning[0] {
4143                ResultColumn::Expr { alias, .. } => {
4144                    assert_eq!(alias.as_deref(), Some("modified_a"));
4145                }
4146                other => unreachable!("expected Expr result column, got {other:?}"),
4147            }
4148        } else {
4149            unreachable!("expected Insert");
4150        }
4151    }
4152
4153    #[test]
4154    fn test_returning_ignores_after_triggers() {
4155        // Parser-level: verify RETURNING * parses on INSERT with conflict clause
4156        // Runtime verification that RETURNING ignores AFTER-trigger modifications
4157        // is deferred to VDBE/engine tests
4158        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1) RETURNING *");
4159        if let Statement::Insert(i) = stmt {
4160            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
4161            assert_eq!(i.returning.len(), 1);
4162            assert!(matches!(i.returning[0], ResultColumn::Star));
4163        } else {
4164            unreachable!("expected Insert");
4165        }
4166    }
4167
4168    #[test]
4169    fn test_returning_after_before_trigger_modify() {
4170        // Parser-level: verify RETURNING with multiple column expressions
4171        // Runtime verification of BEFORE trigger modifying returned values
4172        // is deferred to VDBE/engine tests
4173        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, a + b AS total");
4174        if let Statement::Insert(i) = stmt {
4175            assert_eq!(i.returning.len(), 3);
4176            match &i.returning[2] {
4177                ResultColumn::Expr {
4178                    alias: Some(alias), ..
4179                } => assert_eq!(alias, "total"),
4180                other => unreachable!("expected aliased expression, got {other:?}"),
4181            }
4182        } else {
4183            unreachable!("expected Insert");
4184        }
4185    }
4186
4187    #[test]
4188    fn test_returning_before_trigger_raise_abort() {
4189        // Parser-level: RAISE(ABORT, ...) is a valid expression in trigger bodies;
4190        // here we verify RETURNING parses on multi-row INSERT (runtime abort
4191        // behavior verified in VDBE/engine tests)
4192        let stmt = parse_one("INSERT INTO t (a) VALUES (1), (2), (3) RETURNING a");
4193        if let Statement::Insert(i) = stmt {
4194            if let InsertSource::Values(rows) = &i.source {
4195                assert_eq!(rows.len(), 3);
4196            } else {
4197                unreachable!("expected Values source");
4198            }
4199            assert_eq!(i.returning.len(), 1);
4200        } else {
4201            unreachable!("expected Insert");
4202        }
4203    }
4204
4205    #[test]
4206    fn test_returning_instead_of_view() {
4207        // Parser-level: INSERT into a view name parses the same as INSERT into a table
4208        // Runtime INSTEAD OF trigger behavior is verified in VDBE/engine tests
4209        let stmt = parse_one("INSERT INTO v (a, b) VALUES (1, 2) RETURNING *");
4210        if let Statement::Insert(i) = stmt {
4211            assert_eq!(i.table.name, "v");
4212            assert!(!i.returning.is_empty());
4213        } else {
4214            unreachable!("expected Insert");
4215        }
4216    }
4217
4218    #[test]
4219    fn test_returning_autoincrement_with_trigger() {
4220        // Parser-level: verify RETURNING can reference rowid on INSERT
4221        // Runtime autoincrement + trigger interaction is verified in VDBE/engine tests
4222        let stmt = parse_one("INSERT INTO t (name) VALUES ('test') RETURNING rowid, name");
4223        if let Statement::Insert(i) = stmt {
4224            assert_eq!(i.returning.len(), 2);
4225        } else {
4226            unreachable!("expected Insert");
4227        }
4228    }
4229
4230    #[test]
4231    fn test_update_set_where() {
4232        let stmt = parse_one("UPDATE t SET a = 1, b = 'hello' WHERE id = 42");
4233        if let Statement::Update(u) = stmt {
4234            assert_eq!(u.assignments.len(), 2);
4235            assert!(u.where_clause.is_some());
4236            assert!(u.from.is_none());
4237        } else {
4238            unreachable!("expected Update");
4239        }
4240    }
4241
4242    #[test]
4243    fn test_update_from_join() {
4244        let stmt = parse_one("UPDATE t1 SET a = t2.x FROM t2 WHERE t1.id = t2.id");
4245        if let Statement::Update(u) = stmt {
4246            assert_eq!(u.assignments.len(), 1);
4247            assert!(u.from.is_some());
4248            assert!(u.where_clause.is_some());
4249        } else {
4250            unreachable!("expected Update");
4251        }
4252    }
4253
4254    #[test]
4255    fn test_update_from_multi_match() {
4256        // Parser-level: UPDATE FROM with a join that could produce multiple matches
4257        // Runtime behavior (arbitrary row chosen) verified in VDBE/engine tests
4258        let stmt = parse_one(
4259            "UPDATE t1 SET val = src.val FROM src \
4260             INNER JOIN mapping ON mapping.src_id = src.id \
4261             WHERE t1.id = mapping.dst_id",
4262        );
4263        if let Statement::Update(u) = stmt {
4264            assert!(u.from.is_some());
4265            let from = u.from.as_ref().unwrap();
4266            assert!(!from.joins.is_empty(), "expected JOIN in FROM clause");
4267        } else {
4268            unreachable!("expected Update");
4269        }
4270    }
4271
4272    #[test]
4273    fn test_update_order_by_limit() {
4274        let stmt = parse_one("UPDATE t SET a = a + 1 ORDER BY b DESC LIMIT 10");
4275        if let Statement::Update(u) = stmt {
4276            assert_eq!(u.order_by.len(), 1);
4277            assert_eq!(u.order_by[0].direction, Some(SortDirection::Desc));
4278            assert!(u.limit.is_some());
4279        } else {
4280            unreachable!("expected Update");
4281        }
4282    }
4283
4284    #[test]
4285    fn test_update_returning() {
4286        let stmt = parse_one("UPDATE t SET a = 1 WHERE id = 5 RETURNING id, a AS new_a");
4287        if let Statement::Update(u) = stmt {
4288            assert_eq!(u.returning.len(), 2);
4289            match &u.returning[1] {
4290                ResultColumn::Expr {
4291                    alias: Some(alias), ..
4292                } => assert_eq!(alias, "new_a"),
4293                other => unreachable!("expected aliased result column, got {other:?}"),
4294            }
4295        } else {
4296            unreachable!("expected Update");
4297        }
4298    }
4299
4300    #[test]
4301    fn test_update_or_ignore() {
4302        let stmt = parse_one("UPDATE OR IGNORE t SET a = 1 WHERE id = 5");
4303        if let Statement::Update(u) = stmt {
4304            assert_eq!(u.or_conflict, Some(ConflictAction::Ignore));
4305            assert!(u.where_clause.is_some());
4306        } else {
4307            unreachable!("expected Update");
4308        }
4309    }
4310
4311    #[test]
4312    fn test_delete_where() {
4313        let stmt = parse_one("DELETE FROM t WHERE id = 42 AND active = 0");
4314        if let Statement::Delete(d) = stmt {
4315            assert!(d.where_clause.is_some());
4316            assert!(d.returning.is_empty());
4317        } else {
4318            unreachable!("expected Delete");
4319        }
4320    }
4321
4322    #[test]
4323    fn test_delete_order_by_limit() {
4324        let stmt = parse_one("DELETE FROM t ORDER BY created_at ASC LIMIT 100");
4325        if let Statement::Delete(d) = stmt {
4326            assert_eq!(d.order_by.len(), 1);
4327            assert_eq!(d.order_by[0].direction, Some(SortDirection::Asc));
4328            let limit = d.limit.as_ref().expect("LIMIT clause");
4329            assert!(matches!(
4330                limit.limit,
4331                Expr::Literal(Literal::Integer(100), _)
4332            ));
4333        } else {
4334            unreachable!("expected Delete");
4335        }
4336    }
4337
4338    #[test]
4339    fn test_delete_returning() {
4340        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
4341        if let Statement::Delete(d) = stmt {
4342            assert!(d.where_clause.is_some());
4343            assert_eq!(d.returning.len(), 1);
4344            assert!(matches!(d.returning[0], ResultColumn::Star));
4345        } else {
4346            unreachable!("expected Delete");
4347        }
4348    }
4349
4350    #[test]
4351    fn test_delete_bulk_optimization() {
4352        // Parser-level: DELETE without WHERE produces AST with no where_clause
4353        // Runtime bulk-delete optimization (OP_Clear) is verified in VDBE/engine tests
4354        let stmt = parse_one("DELETE FROM t");
4355        if let Statement::Delete(d) = stmt {
4356            assert!(d.where_clause.is_none());
4357            assert!(d.order_by.is_empty());
4358            assert!(d.limit.is_none());
4359            assert!(d.returning.is_empty());
4360        } else {
4361            unreachable!("expected Delete");
4362        }
4363    }
4364
4365    #[test]
4366    fn test_delete_bulk_no_where_fast() {
4367        // Parser-level: confirms DELETE without WHERE parses to minimal AST
4368        // Runtime OP_Clear vs OP_Delete selection is verified in VDBE/engine tests
4369        let stmt = parse_one("DELETE FROM main.t");
4370        if let Statement::Delete(d) = stmt {
4371            assert_eq!(d.table.name.schema.as_deref(), Some("main"));
4372            assert_eq!(d.table.name.name, "t");
4373            assert!(d.where_clause.is_none());
4374        } else {
4375            unreachable!("expected Delete");
4376        }
4377    }
4378
4379    #[test]
4380    fn test_delete_bulk_blocked_by_trigger() {
4381        // Parser-level: DELETE without WHERE from a table that might have triggers
4382        // has the same AST shape (no WHERE). Runtime trigger detection is in the engine.
4383        let stmt = parse_one("DELETE FROM orders");
4384        if let Statement::Delete(d) = stmt {
4385            assert!(d.where_clause.is_none());
4386            assert!(d.returning.is_empty());
4387        } else {
4388            unreachable!("expected Delete");
4389        }
4390    }
4391
4392    #[test]
4393    fn test_delete_bulk_blocked_by_fk() {
4394        // Parser-level: DELETE without WHERE is the same AST regardless of FK constraints.
4395        // Runtime FK-based fallback to row-by-row is verified in VDBE/engine tests.
4396        let stmt = parse_one("DELETE FROM parent_table");
4397        if let Statement::Delete(d) = stmt {
4398            assert!(d.where_clause.is_none());
4399        } else {
4400            unreachable!("expected Delete");
4401        }
4402    }
4403
4404    #[test]
4405    fn test_delete_bulk_changes_count() {
4406        // Parser-level: DELETE without WHERE returning count via changes()
4407        // is the same AST as any unconditional delete. Runtime changes()
4408        // reporting is verified in VDBE/engine tests.
4409        let stmt = parse_one("DELETE FROM t");
4410        if let Statement::Delete(d) = stmt {
4411            assert!(d.where_clause.is_none());
4412        } else {
4413            unreachable!("expected Delete");
4414        }
4415    }
4416
4417    #[test]
4418    fn test_delete_bulk_autoincrement_preserved() {
4419        // Parser-level: DELETE without WHERE on an autoincrement table has
4420        // identical AST to any unconditional delete. Runtime autoincrement
4421        // sequence preservation is verified in VDBE/engine tests.
4422        let stmt = parse_one("DELETE FROM t");
4423        if let Statement::Delete(d) = stmt {
4424            assert!(d.where_clause.is_none());
4425            assert!(d.limit.is_none());
4426        } else {
4427            unreachable!("expected Delete");
4428        }
4429    }
4430
4431    #[test]
4432    fn test_delete_bulk_where_1_not_optimized() {
4433        // Parser-level: DELETE WHERE 1 has a where_clause (unlike bare DELETE),
4434        // so the optimizer cannot use bulk-delete. Verify WHERE is present.
4435        let stmt = parse_one("DELETE FROM t WHERE 1");
4436        if let Statement::Delete(d) = stmt {
4437            assert!(
4438                d.where_clause.is_some(),
4439                "WHERE 1 must produce a where_clause"
4440            );
4441            assert!(matches!(
4442                d.where_clause.as_ref().unwrap(),
4443                Expr::Literal(Literal::Integer(1), _)
4444            ));
4445        } else {
4446            unreachable!("expected Delete");
4447        }
4448    }
4449
4450    // -----------------------------------------------------------------------
4451    // bd-34de §12.5-12.6 DDL: CREATE TABLE + CREATE INDEX parsing tests
4452    // -----------------------------------------------------------------------
4453
4454    #[test]
4455    fn test_create_table_basic() {
4456        let stmt = parse_one("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
4457        if let Statement::CreateTable(ct) = stmt {
4458            assert_eq!(ct.name.name, "users");
4459            assert!(!ct.if_not_exists);
4460            assert!(!ct.temporary);
4461            assert!(!ct.without_rowid);
4462            assert!(!ct.strict);
4463            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4464                assert_eq!(columns.len(), 3);
4465                assert_eq!(columns[0].name, "id");
4466                assert_eq!(columns[1].name, "name");
4467                assert_eq!(columns[2].name, "age");
4468            } else {
4469                unreachable!("expected Columns body");
4470            }
4471        } else {
4472            unreachable!("expected CreateTable");
4473        }
4474    }
4475
4476    #[test]
4477    fn test_create_table_if_not_exists() {
4478        let stmt = parse_one("CREATE TABLE IF NOT EXISTS t (id INTEGER)");
4479        if let Statement::CreateTable(ct) = stmt {
4480            assert!(ct.if_not_exists);
4481        } else {
4482            unreachable!("expected CreateTable");
4483        }
4484    }
4485
4486    #[test]
4487    fn test_create_temp_table() {
4488        let stmt = parse_one("CREATE TEMP TABLE session_data (key TEXT, val BLOB)");
4489        if let Statement::CreateTable(ct) = stmt {
4490            assert!(ct.temporary);
4491        } else {
4492            unreachable!("expected CreateTable");
4493        }
4494    }
4495
4496    #[test]
4497    fn test_create_table_as_select() {
4498        let stmt = parse_one("CREATE TABLE t2 AS SELECT id, name FROM t1 WHERE active = 1");
4499        if let Statement::CreateTable(ct) = stmt {
4500            assert!(matches!(ct.body, CreateTableBody::AsSelect(_)));
4501        } else {
4502            unreachable!("expected CreateTable");
4503        }
4504    }
4505
4506    #[test]
4507    fn test_column_primary_key() {
4508        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY ASC)");
4509        if let Statement::CreateTable(ct) = stmt {
4510            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4511                let pk = columns[0]
4512                    .constraints
4513                    .iter()
4514                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
4515                assert!(pk.is_some(), "PK constraint missing");
4516                if let ColumnConstraintKind::PrimaryKey { direction, .. } = &pk.unwrap().kind {
4517                    assert_eq!(*direction, Some(SortDirection::Asc));
4518                }
4519            } else {
4520                unreachable!("expected Columns body");
4521            }
4522        } else {
4523            unreachable!("expected CreateTable");
4524        }
4525    }
4526
4527    #[test]
4528    fn test_column_primary_key_autoincrement() {
4529        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)");
4530        if let Statement::CreateTable(ct) = stmt {
4531            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4532                let pk = columns[0]
4533                    .constraints
4534                    .iter()
4535                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
4536                if let ColumnConstraintKind::PrimaryKey { autoincrement, .. } = &pk.unwrap().kind {
4537                    assert!(autoincrement, "AUTOINCREMENT flag not set");
4538                }
4539            } else {
4540                unreachable!("expected Columns body");
4541            }
4542        } else {
4543            unreachable!("expected CreateTable");
4544        }
4545    }
4546
4547    #[test]
4548    fn test_autoincrement_uses_sqlite_sequence() {
4549        // Parser-level: verify AUTOINCREMENT syntax parses correctly.
4550        // Runtime sqlite_sequence tracking is verified in VDBE/engine tests.
4551        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)");
4552        if let Statement::CreateTable(ct) = stmt {
4553            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4554                assert_eq!(columns.len(), 2);
4555                let pk = columns[0].constraints.iter().find(|c| {
4556                    matches!(
4557                        c.kind,
4558                        ColumnConstraintKind::PrimaryKey {
4559                            autoincrement: true,
4560                            ..
4561                        }
4562                    )
4563                });
4564                assert!(pk.is_some(), "AUTOINCREMENT constraint missing");
4565            } else {
4566                unreachable!("expected Columns body");
4567            }
4568        } else {
4569            unreachable!("expected CreateTable");
4570        }
4571    }
4572
4573    #[test]
4574    fn test_column_not_null() {
4575        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL)");
4576        if let Statement::CreateTable(ct) = stmt {
4577            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4578                let nn = columns[0]
4579                    .constraints
4580                    .iter()
4581                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
4582                assert!(nn.is_some(), "NOT NULL constraint missing");
4583            } else {
4584                unreachable!("expected Columns body");
4585            }
4586        } else {
4587            unreachable!("expected CreateTable");
4588        }
4589    }
4590
4591    #[test]
4592    fn test_column_unique() {
4593        let stmt = parse_one("CREATE TABLE t (email TEXT UNIQUE)");
4594        if let Statement::CreateTable(ct) = stmt {
4595            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4596                let uq = columns[0]
4597                    .constraints
4598                    .iter()
4599                    .find(|c| matches!(c.kind, ColumnConstraintKind::Unique { .. }));
4600                assert!(uq.is_some(), "UNIQUE constraint missing");
4601            } else {
4602                unreachable!("expected Columns body");
4603            }
4604        } else {
4605            unreachable!("expected CreateTable");
4606        }
4607    }
4608
4609    #[test]
4610    fn test_column_check() {
4611        let stmt = parse_one("CREATE TABLE t (age INTEGER CHECK(age >= 0 AND age < 200))");
4612        if let Statement::CreateTable(ct) = stmt {
4613            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4614                let chk = columns[0]
4615                    .constraints
4616                    .iter()
4617                    .find(|c| matches!(c.kind, ColumnConstraintKind::Check(_)));
4618                assert!(chk.is_some(), "CHECK constraint missing");
4619            } else {
4620                unreachable!("expected Columns body");
4621            }
4622        } else {
4623            unreachable!("expected CreateTable");
4624        }
4625    }
4626
4627    #[test]
4628    fn test_column_default_literal() {
4629        let stmt = parse_one("CREATE TABLE t (status TEXT DEFAULT 'active')");
4630        if let Statement::CreateTable(ct) = stmt {
4631            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4632                let def = columns[0]
4633                    .constraints
4634                    .iter()
4635                    .find(|c| matches!(c.kind, ColumnConstraintKind::Default(_)));
4636                assert!(def.is_some(), "DEFAULT constraint missing");
4637            } else {
4638                unreachable!("expected Columns body");
4639            }
4640        } else {
4641            unreachable!("expected CreateTable");
4642        }
4643    }
4644
4645    #[test]
4646    fn test_column_default_expr() {
4647        let stmt = parse_one("CREATE TABLE t (created_at TEXT DEFAULT (datetime('now')))");
4648        if let Statement::CreateTable(ct) = stmt {
4649            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4650                let def = columns[0].constraints.iter().find(|c| {
4651                    matches!(
4652                        c.kind,
4653                        ColumnConstraintKind::Default(DefaultValue::ParenExpr(_))
4654                    )
4655                });
4656                assert!(def.is_some(), "DEFAULT (expr) missing");
4657            } else {
4658                unreachable!("expected Columns body");
4659            }
4660        } else {
4661            unreachable!("expected CreateTable");
4662        }
4663    }
4664
4665    #[test]
4666    fn test_column_collate() {
4667        let stmt = parse_one("CREATE TABLE t (name TEXT COLLATE NOCASE)");
4668        if let Statement::CreateTable(ct) = stmt {
4669            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4670                let coll = columns[0]
4671                    .constraints
4672                    .iter()
4673                    .find(|c| matches!(c.kind, ColumnConstraintKind::Collate(_)));
4674                assert!(coll.is_some(), "COLLATE constraint missing");
4675                if let ColumnConstraintKind::Collate(name) = &coll.unwrap().kind {
4676                    assert_eq!(name, "NOCASE");
4677                }
4678            } else {
4679                unreachable!("expected Columns body");
4680            }
4681        } else {
4682            unreachable!("expected CreateTable");
4683        }
4684    }
4685
4686    #[test]
4687    fn test_table_constraint_composite_pk() {
4688        let stmt = parse_one("CREATE TABLE t (a INTEGER, b INTEGER, PRIMARY KEY (a, b))");
4689        if let Statement::CreateTable(ct) = stmt {
4690            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
4691                let pk = constraints
4692                    .iter()
4693                    .find(|c| matches!(c.kind, TableConstraintKind::PrimaryKey { .. }));
4694                assert!(pk.is_some(), "composite PK missing");
4695                if let TableConstraintKind::PrimaryKey { columns, .. } = &pk.unwrap().kind {
4696                    assert_eq!(columns.len(), 2);
4697                }
4698            } else {
4699                unreachable!("expected Columns body");
4700            }
4701        } else {
4702            unreachable!("expected CreateTable");
4703        }
4704    }
4705
4706    #[test]
4707    fn test_table_constraint_composite_unique() {
4708        let stmt = parse_one("CREATE TABLE t (a TEXT, b TEXT, UNIQUE (a, b))");
4709        if let Statement::CreateTable(ct) = stmt {
4710            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
4711                let uq = constraints
4712                    .iter()
4713                    .find(|c| matches!(c.kind, TableConstraintKind::Unique { .. }));
4714                assert!(uq.is_some(), "composite UNIQUE missing");
4715                if let TableConstraintKind::Unique { columns, .. } = &uq.unwrap().kind {
4716                    assert_eq!(columns.len(), 2);
4717                }
4718            } else {
4719                unreachable!("expected Columns body");
4720            }
4721        } else {
4722            unreachable!("expected CreateTable");
4723        }
4724    }
4725
4726    #[test]
4727    fn test_table_constraint_check() {
4728        let stmt = parse_one(
4729            "CREATE TABLE t (start_date TEXT, end_date TEXT, CHECK (start_date < end_date))",
4730        );
4731        if let Statement::CreateTable(ct) = stmt {
4732            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
4733                let chk = constraints
4734                    .iter()
4735                    .find(|c| matches!(c.kind, TableConstraintKind::Check(_)));
4736                assert!(chk.is_some(), "table CHECK constraint missing");
4737            } else {
4738                unreachable!("expected Columns body");
4739            }
4740        } else {
4741            unreachable!("expected CreateTable");
4742        }
4743    }
4744
4745    #[test]
4746    fn test_foreign_key_on_delete_cascade() {
4747        let stmt = parse_one(
4748            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
4749             REFERENCES parent(id) ON DELETE CASCADE)",
4750        );
4751        if let Statement::CreateTable(ct) = stmt {
4752            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4753                let fk = columns[1]
4754                    .constraints
4755                    .iter()
4756                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)));
4757                assert!(fk.is_some(), "FK constraint missing");
4758                if let ColumnConstraintKind::ForeignKey(clause) = &fk.unwrap().kind {
4759                    assert_eq!(clause.table, "parent");
4760                    let del = clause
4761                        .actions
4762                        .iter()
4763                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
4764                    assert!(del.is_some());
4765                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Cascade);
4766                }
4767            } else {
4768                unreachable!("expected Columns body");
4769            }
4770        } else {
4771            unreachable!("expected CreateTable");
4772        }
4773    }
4774
4775    #[test]
4776    fn test_foreign_key_on_delete_set_null() {
4777        let stmt = parse_one(
4778            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
4779             REFERENCES parent(id) ON DELETE SET NULL)",
4780        );
4781        if let Statement::CreateTable(ct) = stmt {
4782            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4783                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
4784                    .constraints
4785                    .iter()
4786                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
4787                    .unwrap()
4788                    .kind
4789                {
4790                    let del = clause
4791                        .actions
4792                        .iter()
4793                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
4794                    assert_eq!(del.unwrap().action, ForeignKeyActionType::SetNull);
4795                }
4796            } else {
4797                unreachable!("expected Columns body");
4798            }
4799        } else {
4800            unreachable!("expected CreateTable");
4801        }
4802    }
4803
4804    #[test]
4805    fn test_foreign_key_on_update_cascade() {
4806        let stmt = parse_one(
4807            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
4808             REFERENCES parent(id) ON UPDATE CASCADE)",
4809        );
4810        if let Statement::CreateTable(ct) = stmt {
4811            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4812                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
4813                    .constraints
4814                    .iter()
4815                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
4816                    .unwrap()
4817                    .kind
4818                {
4819                    let upd = clause
4820                        .actions
4821                        .iter()
4822                        .find(|a| a.trigger == ForeignKeyTrigger::OnUpdate);
4823                    assert!(upd.is_some());
4824                    assert_eq!(upd.unwrap().action, ForeignKeyActionType::Cascade);
4825                }
4826            } else {
4827                unreachable!("expected Columns body");
4828            }
4829        } else {
4830            unreachable!("expected CreateTable");
4831        }
4832    }
4833
4834    #[test]
4835    fn test_foreign_key_restrict() {
4836        let stmt = parse_one(
4837            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
4838             REFERENCES parent(id) ON DELETE RESTRICT)",
4839        );
4840        if let Statement::CreateTable(ct) = stmt {
4841            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4842                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
4843                    .constraints
4844                    .iter()
4845                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
4846                    .unwrap()
4847                    .kind
4848                {
4849                    let del = clause
4850                        .actions
4851                        .iter()
4852                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
4853                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Restrict);
4854                }
4855            } else {
4856                unreachable!("expected Columns body");
4857            }
4858        } else {
4859            unreachable!("expected CreateTable");
4860        }
4861    }
4862
4863    #[test]
4864    fn test_foreign_key_deferred() {
4865        let stmt = parse_one(
4866            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
4867             REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED)",
4868        );
4869        if let Statement::CreateTable(ct) = stmt {
4870            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4871                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
4872                    .constraints
4873                    .iter()
4874                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
4875                    .unwrap()
4876                    .kind
4877                {
4878                    let def = clause.deferrable.as_ref().expect("DEFERRABLE missing");
4879                    assert!(!def.not, "should be DEFERRABLE, not NOT DEFERRABLE");
4880                    assert_eq!(def.initially, Some(DeferrableInitially::Deferred));
4881                }
4882            } else {
4883                unreachable!("expected Columns body");
4884            }
4885        } else {
4886            unreachable!("expected CreateTable");
4887        }
4888    }
4889
4890    #[test]
4891    fn test_foreign_key_pragma_required() {
4892        // Parser-level: FK syntax parses identically regardless of PRAGMA state.
4893        // Runtime enforcement requiring PRAGMA foreign_keys = ON is in VDBE/engine.
4894        let stmt = parse_one(
4895            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
4896             REFERENCES parent(id) ON DELETE CASCADE ON UPDATE SET NULL)",
4897        );
4898        if let Statement::CreateTable(ct) = stmt {
4899            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4900                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
4901                    .constraints
4902                    .iter()
4903                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
4904                    .unwrap()
4905                    .kind
4906                {
4907                    assert_eq!(clause.actions.len(), 2);
4908                }
4909            } else {
4910                unreachable!("expected Columns body");
4911            }
4912        } else {
4913            unreachable!("expected CreateTable");
4914        }
4915    }
4916
4917    #[test]
4918    fn test_conflict_clause_on_not_null() {
4919        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL ON CONFLICT IGNORE)");
4920        if let Statement::CreateTable(ct) = stmt {
4921            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4922                let nn = columns[0]
4923                    .constraints
4924                    .iter()
4925                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
4926                if let ColumnConstraintKind::NotNull { conflict } = &nn.unwrap().kind {
4927                    assert_eq!(*conflict, Some(ConflictAction::Ignore));
4928                }
4929            } else {
4930                unreachable!("expected Columns body");
4931            }
4932        } else {
4933            unreachable!("expected CreateTable");
4934        }
4935    }
4936
4937    #[test]
4938    fn test_without_rowid_table() {
4939        let stmt = parse_one("CREATE TABLE t (k TEXT PRIMARY KEY, v BLOB) WITHOUT ROWID");
4940        if let Statement::CreateTable(ct) = stmt {
4941            assert!(ct.without_rowid);
4942        } else {
4943            unreachable!("expected CreateTable");
4944        }
4945    }
4946
4947    #[test]
4948    fn test_without_rowid_no_autoincrement() {
4949        // Parser-level: verify WITHOUT ROWID and AUTOINCREMENT can both parse.
4950        // Runtime rejection of AUTOINCREMENT on WITHOUT ROWID is in schema validation.
4951        let stmt = parse_one(
4952            "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT) WITHOUT ROWID",
4953        );
4954        if let Statement::CreateTable(ct) = stmt {
4955            assert!(ct.without_rowid);
4956            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4957                let pk = columns[0].constraints.iter().find(|c| {
4958                    matches!(
4959                        c.kind,
4960                        ColumnConstraintKind::PrimaryKey {
4961                            autoincrement: true,
4962                            ..
4963                        }
4964                    )
4965                });
4966                assert!(pk.is_some());
4967            } else {
4968                unreachable!("expected Columns body");
4969            }
4970        } else {
4971            unreachable!("expected CreateTable");
4972        }
4973    }
4974
4975    #[test]
4976    fn test_without_rowid_integer_pk_not_alias() {
4977        // Parser-level: INTEGER PRIMARY KEY in WITHOUT ROWID parses as normal PK.
4978        // Runtime non-aliasing of rowid is in the B-tree layer.
4979        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT) WITHOUT ROWID");
4980        if let Statement::CreateTable(ct) = stmt {
4981            assert!(ct.without_rowid);
4982            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4983                assert_eq!(columns[0].name, "id");
4984                assert!(columns[0].type_name.is_some());
4985            } else {
4986                unreachable!("expected Columns body");
4987            }
4988        } else {
4989            unreachable!("expected CreateTable");
4990        }
4991    }
4992
4993    #[test]
4994    fn test_strict_table_type_enforcement() {
4995        // Parser-level: STRICT keyword parses on CREATE TABLE.
4996        // Runtime type enforcement on INSERT/UPDATE is in VDBE/engine.
4997        let stmt = parse_one("CREATE TABLE t (id INTEGER, name TEXT, score REAL) STRICT");
4998        if let Statement::CreateTable(ct) = stmt {
4999            assert!(ct.strict);
5000            assert!(!ct.without_rowid);
5001        } else {
5002            unreachable!("expected CreateTable");
5003        }
5004    }
5005
5006    #[test]
5007    fn test_strict_table_any_column() {
5008        // Parser-level: ANY type name parses in STRICT table context.
5009        let stmt = parse_one("CREATE TABLE t (id INTEGER, data ANY) STRICT");
5010        if let Statement::CreateTable(ct) = stmt {
5011            assert!(ct.strict);
5012            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5013                let tn = columns[1].type_name.as_ref().expect("type name");
5014                assert_eq!(tn.name, "ANY");
5015            } else {
5016                unreachable!("expected Columns body");
5017            }
5018        } else {
5019            unreachable!("expected CreateTable");
5020        }
5021    }
5022
5023    #[test]
5024    fn test_strict_allowed_types() {
5025        // Parser-level: STRICT table with all allowed type names parses.
5026        let stmt =
5027            parse_one("CREATE TABLE t (a INT, b INTEGER, c REAL, d TEXT, e BLOB, f ANY) STRICT");
5028        if let Statement::CreateTable(ct) = stmt {
5029            assert!(ct.strict);
5030            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5031                assert_eq!(columns.len(), 6);
5032                let types: Vec<&str> = columns
5033                    .iter()
5034                    .map(|c| c.type_name.as_ref().unwrap().name.as_str())
5035                    .collect();
5036                assert_eq!(types, vec!["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"]);
5037            } else {
5038                unreachable!("expected Columns body");
5039            }
5040        } else {
5041            unreachable!("expected CreateTable");
5042        }
5043    }
5044
5045    #[test]
5046    fn test_generated_col_virtual() {
5047        let stmt = parse_one(
5048            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) VIRTUAL)",
5049        );
5050        if let Statement::CreateTable(ct) = stmt {
5051            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5052                let generated = columns[2]
5053                    .constraints
5054                    .iter()
5055                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5056                assert!(generated.is_some(), "Generated constraint missing");
5057                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
5058                    assert_eq!(*storage, Some(GeneratedStorage::Virtual));
5059                }
5060            } else {
5061                unreachable!("expected Columns body");
5062            }
5063        } else {
5064            unreachable!("expected CreateTable");
5065        }
5066    }
5067
5068    #[test]
5069    fn test_generated_col_stored() {
5070        let stmt = parse_one(
5071            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a * b) STORED)",
5072        );
5073        if let Statement::CreateTable(ct) = stmt {
5074            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5075                let generated = columns[2]
5076                    .constraints
5077                    .iter()
5078                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5079                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
5080                    assert_eq!(*storage, Some(GeneratedStorage::Stored));
5081                }
5082            } else {
5083                unreachable!("expected Columns body");
5084            }
5085        } else {
5086            unreachable!("expected CreateTable");
5087        }
5088    }
5089
5090    #[test]
5091    fn test_generated_col_ordering() {
5092        // Parser-level: generated columns with forward references parse correctly.
5093        // Runtime rejection of forward references is in schema validation.
5094        let stmt = parse_one(
5095            "CREATE TABLE t (\
5096             a INTEGER, \
5097             b INTEGER GENERATED ALWAYS AS (a + 1) STORED, \
5098             c INTEGER GENERATED ALWAYS AS (b * 2) VIRTUAL)",
5099        );
5100        if let Statement::CreateTable(ct) = stmt {
5101            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5102                assert_eq!(columns.len(), 3);
5103                // Both b and c have Generated constraints
5104                let gen_b = columns[1]
5105                    .constraints
5106                    .iter()
5107                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5108                let gen_c = columns[2]
5109                    .constraints
5110                    .iter()
5111                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5112                assert!(gen_b, "column b should be generated");
5113                assert!(gen_c, "column c should be generated");
5114            } else {
5115                unreachable!("expected Columns body");
5116            }
5117        } else {
5118            unreachable!("expected CreateTable");
5119        }
5120    }
5121
5122    #[test]
5123    fn test_generated_col_stored_indexable() {
5124        // Parser-level: STORED generated column can appear alongside indexes.
5125        // Runtime indexability verified in B-tree/engine tests.
5126        let stmts = parse_ok(
5127            "CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED); \
5128             CREATE INDEX idx_b ON t (b)",
5129        );
5130        assert_eq!(stmts.len(), 2);
5131        assert!(matches!(stmts[0], Statement::CreateTable(_)));
5132        assert!(matches!(stmts[1], Statement::CreateIndex(_)));
5133    }
5134
5135    #[test]
5136    fn test_type_affinity_int() {
5137        // Parser-level: type names containing "INT" parse as valid type names.
5138        // Runtime affinity determination is in the type system.
5139        let stmt = parse_one("CREATE TABLE t (a INTEGER, b BIGINT, c SMALLINT, d MEDIUMINT)");
5140        if let Statement::CreateTable(ct) = stmt {
5141            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5142                assert_eq!(columns.len(), 4);
5143                for col in columns {
5144                    let tn = col.type_name.as_ref().unwrap();
5145                    assert!(tn.name.contains("INT"), "{} should contain INT", tn.name);
5146                }
5147            } else {
5148                unreachable!("expected Columns body");
5149            }
5150        } else {
5151            unreachable!("expected CreateTable");
5152        }
5153    }
5154
5155    #[test]
5156    fn test_type_affinity_text() {
5157        let stmt = parse_one("CREATE TABLE t (a TEXT, b VARCHAR, c CLOB, d CHARACTER)");
5158        if let Statement::CreateTable(ct) = stmt {
5159            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5160                assert_eq!(columns.len(), 4);
5161                for col in columns {
5162                    assert!(col.type_name.is_some());
5163                }
5164            } else {
5165                unreachable!("expected Columns body");
5166            }
5167        } else {
5168            unreachable!("expected CreateTable");
5169        }
5170    }
5171
5172    #[test]
5173    fn test_type_affinity_blob() {
5174        let stmt = parse_one("CREATE TABLE t (a BLOB, b)");
5175        if let Statement::CreateTable(ct) = stmt {
5176            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5177                assert_eq!(columns.len(), 2);
5178                assert_eq!(columns[0].type_name.as_ref().unwrap().name, "BLOB");
5179                // Column b has no type name -> BLOB affinity
5180                assert!(columns[1].type_name.is_none());
5181            } else {
5182                unreachable!("expected Columns body");
5183            }
5184        } else {
5185            unreachable!("expected CreateTable");
5186        }
5187    }
5188
5189    #[test]
5190    fn test_type_affinity_real() {
5191        let stmt = parse_one("CREATE TABLE t (a REAL, b DOUBLE, c FLOAT)");
5192        if let Statement::CreateTable(ct) = stmt {
5193            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5194                assert_eq!(columns.len(), 3);
5195                for col in columns {
5196                    assert!(col.type_name.is_some());
5197                }
5198            } else {
5199                unreachable!("expected Columns body");
5200            }
5201        } else {
5202            unreachable!("expected CreateTable");
5203        }
5204    }
5205
5206    #[test]
5207    fn test_type_affinity_numeric() {
5208        let stmt = parse_one("CREATE TABLE t (a NUMERIC, b DECIMAL, c BOOLEAN)");
5209        if let Statement::CreateTable(ct) = stmt {
5210            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5211                assert_eq!(columns.len(), 3);
5212                for col in columns {
5213                    assert!(col.type_name.is_some());
5214                }
5215            } else {
5216                unreachable!("expected Columns body");
5217            }
5218        } else {
5219            unreachable!("expected CreateTable");
5220        }
5221    }
5222
5223    #[test]
5224    fn test_create_unique_index() {
5225        let stmt = parse_one("CREATE UNIQUE INDEX idx_email ON users (email)");
5226        if let Statement::CreateIndex(ci) = stmt {
5227            assert!(ci.unique);
5228            assert_eq!(ci.name.name, "idx_email");
5229            assert_eq!(ci.table, "users");
5230            assert_eq!(ci.columns.len(), 1);
5231            assert!(ci.where_clause.is_none());
5232        } else {
5233            unreachable!("expected CreateIndex");
5234        }
5235    }
5236
5237    #[test]
5238    fn test_partial_index() {
5239        let stmt = parse_one("CREATE INDEX idx_active ON users (name) WHERE active = 1");
5240        if let Statement::CreateIndex(ci) = stmt {
5241            assert!(!ci.unique);
5242            assert_eq!(ci.name.name, "idx_active");
5243            assert!(ci.where_clause.is_some(), "partial index WHERE missing");
5244        } else {
5245            unreachable!("expected CreateIndex");
5246        }
5247    }
5248
5249    #[test]
5250    fn test_partial_index_planner_usage() {
5251        // Parser-level: partial index with complex WHERE parses correctly.
5252        // Runtime planner usage (query WHERE implies index WHERE) is in planner tests.
5253        let stmt =
5254            parse_one("CREATE INDEX idx_recent ON orders (created_at) WHERE status != 'archived'");
5255        if let Statement::CreateIndex(ci) = stmt {
5256            assert!(ci.where_clause.is_some());
5257            assert_eq!(ci.columns.len(), 1);
5258        } else {
5259            unreachable!("expected CreateIndex");
5260        }
5261    }
5262
5263    #[test]
5264    fn test_expression_index() {
5265        let stmt = parse_one("CREATE INDEX idx_lower_name ON users (lower(name))");
5266        if let Statement::CreateIndex(ci) = stmt {
5267            assert_eq!(ci.columns.len(), 1);
5268            // The indexed expression should be a function call, not a plain column
5269            assert!(
5270                matches!(ci.columns[0].expr, Expr::FunctionCall { .. }),
5271                "expected function call expression in index"
5272            );
5273        } else {
5274            unreachable!("expected CreateIndex");
5275        }
5276    }
5277
5278    #[test]
5279    fn test_expression_index_planner_match() {
5280        // Parser-level: expression index with arithmetic parses correctly.
5281        // Runtime structural equality matching is in planner tests.
5282        let stmt = parse_one("CREATE INDEX idx_calc ON t (a + b * 2)");
5283        if let Statement::CreateIndex(ci) = stmt {
5284            assert_eq!(ci.columns.len(), 1);
5285            assert!(
5286                matches!(ci.columns[0].expr, Expr::BinaryOp { .. }),
5287                "expected binary op in expression index"
5288            );
5289        } else {
5290            unreachable!("expected CreateIndex");
5291        }
5292    }
5293
5294    #[test]
5295    fn test_index_collate_asc_desc() {
5296        let stmt = parse_one("CREATE INDEX idx_multi ON t (a COLLATE NOCASE ASC, b DESC, c)");
5297        if let Statement::CreateIndex(ci) = stmt {
5298            assert_eq!(ci.columns.len(), 3);
5299            // COLLATE is consumed by the expression parser as Expr::Collate
5300            assert!(
5301                matches!(
5302                    &ci.columns[0].expr,
5303                    Expr::Collate { collation, .. } if collation == "NOCASE"
5304                ),
5305                "expected Collate expr with NOCASE"
5306            );
5307            assert_eq!(ci.columns[0].direction, Some(SortDirection::Asc));
5308            assert_eq!(ci.columns[1].direction, Some(SortDirection::Desc));
5309            assert!(ci.columns[2].direction.is_none());
5310        } else {
5311            unreachable!("expected CreateIndex");
5312        }
5313    }
5314
5315    // -----------------------------------------------------------------------
5316    // bd-3kin §12.7-12.9 CREATE VIEW + CREATE TRIGGER + ALTER/DROP parsing tests
5317    // -----------------------------------------------------------------------
5318
5319    #[test]
5320    fn test_create_view_basic() {
5321        let stmt = parse_one("CREATE VIEW v AS SELECT id, name FROM users");
5322        if let Statement::CreateView(cv) = stmt {
5323            assert_eq!(cv.name.name, "v");
5324            assert!(!cv.if_not_exists);
5325            assert!(!cv.temporary);
5326            assert!(cv.columns.is_empty());
5327        } else {
5328            unreachable!("expected CreateView");
5329        }
5330    }
5331
5332    #[test]
5333    fn test_create_view_column_aliases() {
5334        let stmt = parse_one("CREATE VIEW v (user_id, user_name) AS SELECT id, name FROM users");
5335        if let Statement::CreateView(cv) = stmt {
5336            assert_eq!(cv.columns, vec!["user_id", "user_name"]);
5337        } else {
5338            unreachable!("expected CreateView");
5339        }
5340    }
5341
5342    #[test]
5343    fn test_create_view_if_not_exists() {
5344        let stmt = parse_one("CREATE VIEW IF NOT EXISTS v AS SELECT 1");
5345        if let Statement::CreateView(cv) = stmt {
5346            assert!(cv.if_not_exists);
5347        } else {
5348            unreachable!("expected CreateView");
5349        }
5350    }
5351
5352    #[test]
5353    fn test_create_temp_view() {
5354        let stmt = parse_one("CREATE TEMP VIEW tv AS SELECT 1");
5355        if let Statement::CreateView(cv) = stmt {
5356            assert!(cv.temporary);
5357        } else {
5358            unreachable!("expected CreateView");
5359        }
5360    }
5361
5362    #[test]
5363    fn test_view_inline_expansion() {
5364        // Parser-level: view defined with WHERE is captured in AST.
5365        // Runtime inline expansion (not materialization) is in the planner.
5366        let stmt =
5367            parse_one("CREATE VIEW active_users AS SELECT id, name FROM users WHERE active = 1");
5368        if let Statement::CreateView(cv) = stmt {
5369            assert_eq!(cv.name.name, "active_users");
5370        } else {
5371            unreachable!("expected CreateView");
5372        }
5373    }
5374
5375    #[test]
5376    fn test_view_read_only() {
5377        // Parser-level: views parse as SELECT-only definitions.
5378        // Runtime read-only enforcement (rejecting DML without INSTEAD OF) is in the engine.
5379        let stmt = parse_one("CREATE VIEW v AS SELECT * FROM t");
5380        assert!(matches!(stmt, Statement::CreateView(_)));
5381    }
5382
5383    #[test]
5384    fn test_view_with_recursive_cte() {
5385        // View referencing a subquery (parser does not yet support WITH directly
5386        // in CREATE VIEW ... AS context; CTE-in-view support is a planner concern).
5387        let stmt = parse_one(
5388            "CREATE VIEW tree AS \
5389             SELECT n.id, n.parent FROM nodes n \
5390             WHERE n.parent IS NULL \
5391             UNION ALL \
5392             SELECT c.id, c.parent FROM nodes c JOIN nodes p ON c.parent = p.id",
5393        );
5394        if let Statement::CreateView(cv) = stmt {
5395            assert_eq!(cv.name.name, "tree");
5396            // Compound UNION ALL captured
5397            assert!(
5398                !cv.query.body.compounds.is_empty(),
5399                "expected compound SELECT in view"
5400            );
5401        } else {
5402            unreachable!("expected CreateView");
5403        }
5404    }
5405
5406    #[test]
5407    fn test_instead_of_trigger_on_view() {
5408        let stmt = parse_one(
5409            "CREATE TRIGGER tr INSTEAD OF INSERT ON v BEGIN \
5410             INSERT INTO t (a) VALUES (NEW.a); \
5411             END",
5412        );
5413        if let Statement::CreateTrigger(ct) = stmt {
5414            assert_eq!(ct.timing, TriggerTiming::InsteadOf);
5415            assert!(matches!(ct.event, TriggerEvent::Insert));
5416            assert_eq!(ct.table, "v");
5417            assert!(!ct.body.is_empty());
5418        } else {
5419            unreachable!("expected CreateTrigger");
5420        }
5421    }
5422
5423    #[test]
5424    fn test_trigger_before_insert() {
5425        let stmt = parse_one("CREATE TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
5426        if let Statement::CreateTrigger(ct) = stmt {
5427            assert_eq!(ct.timing, TriggerTiming::Before);
5428            assert!(matches!(ct.event, TriggerEvent::Insert));
5429        } else {
5430            unreachable!("expected CreateTrigger");
5431        }
5432    }
5433
5434    #[test]
5435    fn test_trigger_after_insert() {
5436        let stmt = parse_one("CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END");
5437        if let Statement::CreateTrigger(ct) = stmt {
5438            assert_eq!(ct.timing, TriggerTiming::After);
5439            assert!(matches!(ct.event, TriggerEvent::Insert));
5440        } else {
5441            unreachable!("expected CreateTrigger");
5442        }
5443    }
5444
5445    #[test]
5446    fn test_trigger_before_update() {
5447        let stmt = parse_one("CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN SELECT OLD.a, NEW.a; END");
5448        if let Statement::CreateTrigger(ct) = stmt {
5449            assert_eq!(ct.timing, TriggerTiming::Before);
5450            assert!(matches!(ct.event, TriggerEvent::Update(_)));
5451        } else {
5452            unreachable!("expected CreateTrigger");
5453        }
5454    }
5455
5456    #[test]
5457    fn test_trigger_after_delete() {
5458        let stmt = parse_one("CREATE TRIGGER tr AFTER DELETE ON t BEGIN SELECT OLD.id; END");
5459        if let Statement::CreateTrigger(ct) = stmt {
5460            assert_eq!(ct.timing, TriggerTiming::After);
5461            assert!(matches!(ct.event, TriggerEvent::Delete));
5462        } else {
5463            unreachable!("expected CreateTrigger");
5464        }
5465    }
5466
5467    #[test]
5468    fn test_trigger_update_of_column() {
5469        let stmt =
5470            parse_one("CREATE TRIGGER tr BEFORE UPDATE OF name, email ON t BEGIN SELECT 1; END");
5471        if let Statement::CreateTrigger(ct) = stmt {
5472            if let TriggerEvent::Update(cols) = &ct.event {
5473                assert_eq!(cols, &["name", "email"]);
5474            } else {
5475                unreachable!("expected Update event with columns");
5476            }
5477        } else {
5478            unreachable!("expected CreateTrigger");
5479        }
5480    }
5481
5482    #[test]
5483    fn test_trigger_when_clause() {
5484        let stmt = parse_one(
5485            "CREATE TRIGGER tr BEFORE INSERT ON t WHEN NEW.active = 1 BEGIN SELECT 1; END",
5486        );
5487        if let Statement::CreateTrigger(ct) = stmt {
5488            assert!(ct.when.is_some(), "WHEN clause missing");
5489        } else {
5490            unreachable!("expected CreateTrigger");
5491        }
5492    }
5493
5494    #[test]
5495    fn test_trigger_old_new_pseudo_tables() {
5496        let stmt = parse_one(
5497            "CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN \
5498             INSERT INTO log (old_val, new_val) VALUES (OLD.a, NEW.a); \
5499             END",
5500        );
5501        if let Statement::CreateTrigger(ct) = stmt {
5502            assert_eq!(ct.body.len(), 1);
5503            assert!(matches!(ct.body[0], Statement::Insert(_)));
5504        } else {
5505            unreachable!("expected CreateTrigger");
5506        }
5507    }
5508
5509    #[test]
5510    fn test_trigger_raise_abort() {
5511        let stmt = parse_one(
5512            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
5513             SELECT RAISE(ABORT, 'not allowed'); \
5514             END",
5515        );
5516        if let Statement::CreateTrigger(ct) = stmt {
5517            assert_eq!(ct.body.len(), 1);
5518        } else {
5519            unreachable!("expected CreateTrigger");
5520        }
5521    }
5522
5523    #[test]
5524    fn test_trigger_raise_rollback() {
5525        let stmt = parse_one(
5526            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
5527             SELECT RAISE(ROLLBACK, 'invalid'); \
5528             END",
5529        );
5530        if let Statement::CreateTrigger(ct) = stmt {
5531            assert_eq!(ct.body.len(), 1);
5532        } else {
5533            unreachable!("expected CreateTrigger");
5534        }
5535    }
5536
5537    #[test]
5538    fn test_trigger_raise_fail() {
5539        let stmt = parse_one(
5540            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
5541             SELECT RAISE(FAIL, 'bad data'); \
5542             END",
5543        );
5544        if let Statement::CreateTrigger(ct) = stmt {
5545            assert_eq!(ct.body.len(), 1);
5546        } else {
5547            unreachable!("expected CreateTrigger");
5548        }
5549    }
5550
5551    #[test]
5552    fn test_trigger_raise_ignore() {
5553        let stmt = parse_one(
5554            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
5555             SELECT RAISE(IGNORE); \
5556             END",
5557        );
5558        if let Statement::CreateTrigger(ct) = stmt {
5559            assert_eq!(ct.body.len(), 1);
5560        } else {
5561            unreachable!("expected CreateTrigger");
5562        }
5563    }
5564
5565    #[test]
5566    fn test_trigger_recursive() {
5567        // Parser-level: trigger referencing its own table parses normally.
5568        // Runtime recursive trigger behavior (PRAGMA recursive_triggers) is in the engine.
5569        let stmt = parse_one(
5570            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
5571             INSERT INTO t (val) VALUES (NEW.val + 1); \
5572             END",
5573        );
5574        if let Statement::CreateTrigger(ct) = stmt {
5575            assert_eq!(ct.timing, TriggerTiming::After);
5576            assert_eq!(ct.table, "t");
5577            assert_eq!(ct.body.len(), 1);
5578        } else {
5579            unreachable!("expected CreateTrigger");
5580        }
5581    }
5582
5583    #[test]
5584    fn test_trigger_max_recursion_depth() {
5585        // Parser-level: trigger with WHEN depth guard parses.
5586        // Runtime SQLITE_MAX_TRIGGER_DEPTH enforcement is in the VDBE.
5587        let stmt = parse_one(
5588            "CREATE TRIGGER tr AFTER INSERT ON t \
5589             WHEN NEW.depth < 1000 BEGIN \
5590             INSERT INTO t (depth) VALUES (NEW.depth + 1); \
5591             END",
5592        );
5593        if let Statement::CreateTrigger(ct) = stmt {
5594            assert!(ct.when.is_some());
5595            assert_eq!(ct.body.len(), 1);
5596        } else {
5597            unreachable!("expected CreateTrigger");
5598        }
5599    }
5600
5601    #[test]
5602    fn test_trigger_heap_frame_stack() {
5603        // Parser-level: trigger with UPDATE body parses correctly.
5604        // Runtime heap-allocated frame stack is in the VDBE.
5605        let stmt = parse_one(
5606            "CREATE TRIGGER tr AFTER UPDATE ON t BEGIN \
5607             UPDATE t SET counter = counter + 1 WHERE id = NEW.parent_id; \
5608             END",
5609        );
5610        if let Statement::CreateTrigger(ct) = stmt {
5611            assert_eq!(ct.body.len(), 1);
5612            assert!(matches!(ct.body[0], Statement::Update(_)));
5613        } else {
5614            unreachable!("expected CreateTrigger");
5615        }
5616    }
5617
5618    #[test]
5619    fn test_trigger_multiple_dml() {
5620        let stmt = parse_one(
5621            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
5622             INSERT INTO audit (action) VALUES ('insert'); \
5623             UPDATE stats SET count = count + 1; \
5624             END",
5625        );
5626        if let Statement::CreateTrigger(ct) = stmt {
5627            assert_eq!(ct.body.len(), 2);
5628            assert!(matches!(ct.body[0], Statement::Insert(_)));
5629            assert!(matches!(ct.body[1], Statement::Update(_)));
5630        } else {
5631            unreachable!("expected CreateTrigger");
5632        }
5633    }
5634
5635    #[test]
5636    fn test_alter_table_rename() {
5637        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
5638        if let Statement::AlterTable(at) = stmt {
5639            assert_eq!(at.table.name, "t");
5640            assert!(matches!(at.action, AlterTableAction::RenameTo(ref n) if n == "t2"));
5641        } else {
5642            unreachable!("expected AlterTable");
5643        }
5644    }
5645
5646    #[test]
5647    fn test_alter_table_rename_column() {
5648        let stmt = parse_one("ALTER TABLE t RENAME COLUMN old_name TO new_name");
5649        if let Statement::AlterTable(at) = stmt {
5650            if let AlterTableAction::RenameColumn { old, new } = &at.action {
5651                assert_eq!(old, "old_name");
5652                assert_eq!(new, "new_name");
5653            } else {
5654                unreachable!("expected RenameColumn action");
5655            }
5656        } else {
5657            unreachable!("expected AlterTable");
5658        }
5659    }
5660
5661    #[test]
5662    fn test_alter_table_add_column() {
5663        let stmt = parse_one("ALTER TABLE t ADD COLUMN email TEXT NOT NULL DEFAULT ''");
5664        if let Statement::AlterTable(at) = stmt {
5665            if let AlterTableAction::AddColumn(col) = &at.action {
5666                assert_eq!(col.name, "email");
5667                assert!(!col.constraints.is_empty());
5668            } else {
5669                unreachable!("expected AddColumn action");
5670            }
5671        } else {
5672            unreachable!("expected AlterTable");
5673        }
5674    }
5675
5676    #[test]
5677    fn test_alter_table_remove_column() {
5678        let stmt = parse_one("ALTER TABLE t DROP COLUMN old_col");
5679        if let Statement::AlterTable(at) = stmt {
5680            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "old_col"));
5681        } else {
5682            unreachable!("expected AlterTable");
5683        }
5684    }
5685
5686    #[test]
5687    fn test_alter_remove_column_pk_fails() {
5688        // Parser-level: DROP COLUMN on a PK column parses normally.
5689        // Runtime rejection is in schema validation.
5690        let stmt = parse_one("ALTER TABLE t DROP COLUMN id");
5691        if let Statement::AlterTable(at) = stmt {
5692            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "id"));
5693        } else {
5694            unreachable!("expected AlterTable");
5695        }
5696    }
5697
5698    #[test]
5699    fn test_alter_remove_column_unique_fails() {
5700        // Parser-level: DROP COLUMN on UNIQUE column parses normally.
5701        let stmt = parse_one("ALTER TABLE t DROP COLUMN email");
5702        if let Statement::AlterTable(at) = stmt {
5703            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "email"));
5704        } else {
5705            unreachable!("expected AlterTable");
5706        }
5707    }
5708
5709    #[test]
5710    fn test_alter_remove_column_index_fails() {
5711        // Parser-level: DROP COLUMN on indexed column parses normally.
5712        let stmt = parse_one("ALTER TABLE t DROP COLUMN indexed_col");
5713        if let Statement::AlterTable(at) = stmt {
5714            assert!(matches!(
5715                at.action,
5716                AlterTableAction::DropColumn(ref c) if c == "indexed_col"
5717            ));
5718        } else {
5719            unreachable!("expected AlterTable");
5720        }
5721    }
5722
5723    #[test]
5724    fn test_alter_remove_column_check_fails() {
5725        // Parser-level: DROP COLUMN on CHECK-constrained column parses normally.
5726        let stmt = parse_one("ALTER TABLE t DROP COLUMN checked_col");
5727        if let Statement::AlterTable(at) = stmt {
5728            assert!(matches!(
5729                at.action,
5730                AlterTableAction::DropColumn(ref c) if c == "checked_col"
5731            ));
5732        } else {
5733            unreachable!("expected AlterTable");
5734        }
5735    }
5736
5737    #[test]
5738    fn test_alter_remove_column_fk_fails() {
5739        // Parser-level: DROP COLUMN on FK-constrained column parses normally.
5740        let stmt = parse_one("ALTER TABLE t DROP COLUMN fk_col");
5741        if let Statement::AlterTable(at) = stmt {
5742            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "fk_col"));
5743        } else {
5744            unreachable!("expected AlterTable");
5745        }
5746    }
5747
5748    #[test]
5749    fn test_alter_remove_only_column_fails() {
5750        // Parser-level: DROP COLUMN on only column parses normally.
5751        let stmt = parse_one("ALTER TABLE t DROP COLUMN only_col");
5752        if let Statement::AlterTable(at) = stmt {
5753            assert!(matches!(
5754                at.action,
5755                AlterTableAction::DropColumn(ref c) if c == "only_col"
5756            ));
5757        } else {
5758            unreachable!("expected AlterTable");
5759        }
5760    }
5761
5762    #[test]
5763    fn test_ddl_remove_table() {
5764        let stmt = parse_one("DROP TABLE t");
5765        if let Statement::Drop(d) = stmt {
5766            assert_eq!(d.object_type, DropObjectType::Table);
5767            assert!(!d.if_exists);
5768            assert_eq!(d.name.name, "t");
5769        } else {
5770            unreachable!("expected Drop");
5771        }
5772    }
5773
5774    #[test]
5775    fn test_ddl_remove_table_if_exists() {
5776        let stmt = parse_one("DROP TABLE IF EXISTS t");
5777        if let Statement::Drop(d) = stmt {
5778            assert_eq!(d.object_type, DropObjectType::Table);
5779            assert!(d.if_exists);
5780        } else {
5781            unreachable!("expected Drop");
5782        }
5783    }
5784
5785    #[test]
5786    fn test_ddl_remove_index() {
5787        let stmt = parse_one("DROP INDEX idx");
5788        if let Statement::Drop(d) = stmt {
5789            assert_eq!(d.object_type, DropObjectType::Index);
5790            assert_eq!(d.name.name, "idx");
5791        } else {
5792            unreachable!("expected Drop");
5793        }
5794    }
5795
5796    #[test]
5797    fn test_ddl_remove_view() {
5798        let stmt = parse_one("DROP VIEW v");
5799        if let Statement::Drop(d) = stmt {
5800            assert_eq!(d.object_type, DropObjectType::View);
5801            assert_eq!(d.name.name, "v");
5802        } else {
5803            unreachable!("expected Drop");
5804        }
5805    }
5806
5807    #[test]
5808    fn test_ddl_remove_trigger() {
5809        let stmt = parse_one("DROP TRIGGER tr");
5810        if let Statement::Drop(d) = stmt {
5811            assert_eq!(d.object_type, DropObjectType::Trigger);
5812            assert_eq!(d.name.name, "tr");
5813        } else {
5814            unreachable!("expected Drop");
5815        }
5816    }
5817
5818    // -----------------------------------------------------------------------
5819    // bd-3kin §12.7-12.9 DDL gap-fill: REINDEX, ANALYZE, qualified names,
5820    //                                   IF NOT EXISTS/TEMP triggers
5821    // -----------------------------------------------------------------------
5822
5823    #[test]
5824    fn test_reindex_global() {
5825        let stmt = parse_one("REINDEX");
5826        assert!(matches!(stmt, Statement::Reindex(None)));
5827    }
5828
5829    #[test]
5830    fn test_reindex_table() {
5831        let stmt = parse_one("REINDEX t");
5832        if let Statement::Reindex(Some(name)) = stmt {
5833            assert_eq!(name.name, "t");
5834            assert!(name.schema.is_none());
5835        } else {
5836            unreachable!("expected Reindex(Some), got {stmt:?}");
5837        }
5838    }
5839
5840    #[test]
5841    fn test_reindex_qualified() {
5842        let stmt = parse_one("REINDEX main.idx");
5843        if let Statement::Reindex(Some(name)) = stmt {
5844            assert_eq!(name.schema.as_deref(), Some("main"));
5845            assert_eq!(name.name, "idx");
5846        } else {
5847            unreachable!("expected Reindex(Some), got {stmt:?}");
5848        }
5849    }
5850
5851    #[test]
5852    fn test_analyze_global() {
5853        let stmt = parse_one("ANALYZE");
5854        assert!(matches!(stmt, Statement::Analyze(None)));
5855    }
5856
5857    #[test]
5858    fn test_analyze_table() {
5859        let stmt = parse_one("ANALYZE t");
5860        if let Statement::Analyze(Some(name)) = stmt {
5861            assert_eq!(name.name, "t");
5862            assert!(name.schema.is_none());
5863        } else {
5864            unreachable!("expected Analyze(Some), got {stmt:?}");
5865        }
5866    }
5867
5868    #[test]
5869    fn test_analyze_qualified() {
5870        let stmt = parse_one("ANALYZE main.t");
5871        if let Statement::Analyze(Some(name)) = stmt {
5872            assert_eq!(name.schema.as_deref(), Some("main"));
5873            assert_eq!(name.name, "t");
5874        } else {
5875            unreachable!("expected Analyze(Some), got {stmt:?}");
5876        }
5877    }
5878
5879    #[test]
5880    fn test_drop_view_if_exists() {
5881        let stmt = parse_one("DROP VIEW IF EXISTS v");
5882        if let Statement::Drop(d) = stmt {
5883            assert_eq!(d.object_type, DropObjectType::View);
5884            assert!(d.if_exists);
5885            assert_eq!(d.name.name, "v");
5886        } else {
5887            unreachable!("expected Drop");
5888        }
5889    }
5890
5891    #[test]
5892    fn test_drop_index_if_exists() {
5893        let stmt = parse_one("DROP INDEX IF EXISTS idx");
5894        if let Statement::Drop(d) = stmt {
5895            assert_eq!(d.object_type, DropObjectType::Index);
5896            assert!(d.if_exists);
5897        } else {
5898            unreachable!("expected Drop");
5899        }
5900    }
5901
5902    #[test]
5903    fn test_drop_trigger_if_exists_qualified() {
5904        let stmt = parse_one("DROP TRIGGER IF EXISTS main.tr");
5905        if let Statement::Drop(d) = stmt {
5906            assert_eq!(d.object_type, DropObjectType::Trigger);
5907            assert!(d.if_exists);
5908            assert_eq!(d.name.schema.as_deref(), Some("main"));
5909            assert_eq!(d.name.name, "tr");
5910        } else {
5911            unreachable!("expected Drop");
5912        }
5913    }
5914
5915    #[test]
5916    fn test_drop_table_qualified() {
5917        let stmt = parse_one("DROP TABLE main.t");
5918        if let Statement::Drop(d) = stmt {
5919            assert_eq!(d.name.schema.as_deref(), Some("main"));
5920            assert_eq!(d.name.name, "t");
5921        } else {
5922            unreachable!("expected Drop");
5923        }
5924    }
5925
5926    #[test]
5927    fn test_create_trigger_if_not_exists() {
5928        let stmt =
5929            parse_one("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
5930        if let Statement::CreateTrigger(ct) = stmt {
5931            assert!(ct.if_not_exists);
5932            assert_eq!(ct.name.name, "tr");
5933        } else {
5934            unreachable!("expected CreateTrigger");
5935        }
5936    }
5937
5938    #[test]
5939    fn test_create_temp_trigger() {
5940        let stmt = parse_one("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
5941        if let Statement::CreateTrigger(ct) = stmt {
5942            assert!(ct.temporary);
5943            assert_eq!(ct.name.name, "tr");
5944        } else {
5945            unreachable!("expected CreateTrigger");
5946        }
5947    }
5948
5949    #[test]
5950    fn test_create_view_qualified_name() {
5951        let stmt = parse_one("CREATE VIEW main.v AS SELECT 1");
5952        if let Statement::CreateView(cv) = stmt {
5953            assert_eq!(cv.name.schema.as_deref(), Some("main"));
5954            assert_eq!(cv.name.name, "v");
5955        } else {
5956            unreachable!("expected CreateView");
5957        }
5958    }
5959
5960    #[test]
5961    fn test_alter_table_qualified() {
5962        let stmt = parse_one("ALTER TABLE main.t RENAME TO u");
5963        if let Statement::AlterTable(at) = stmt {
5964            assert_eq!(at.table.schema.as_deref(), Some("main"));
5965            assert_eq!(at.table.name, "t");
5966        } else {
5967            unreachable!("expected AlterTable");
5968        }
5969    }
5970
5971    #[test]
5972    fn test_roundtrip_reindex_all() {
5973        assert_roundtrip("REINDEX");
5974        assert_roundtrip("REINDEX t");
5975        assert_roundtrip("REINDEX main.idx");
5976    }
5977
5978    #[test]
5979    fn test_roundtrip_analyze_all() {
5980        assert_roundtrip("ANALYZE");
5981        assert_roundtrip("ANALYZE t");
5982        assert_roundtrip("ANALYZE main.t");
5983    }
5984
5985    #[test]
5986    fn test_roundtrip_drop_all_types_extended() {
5987        assert_roundtrip("DROP TABLE IF EXISTS main.t");
5988        assert_roundtrip("DROP VIEW IF EXISTS v");
5989        assert_roundtrip("DROP INDEX IF EXISTS idx");
5990        assert_roundtrip("DROP TRIGGER IF EXISTS main.tr");
5991    }
5992
5993    #[test]
5994    fn test_roundtrip_create_trigger_extended() {
5995        assert_roundtrip("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
5996        assert_roundtrip("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
5997        assert_roundtrip(
5998            "CREATE TRIGGER tr INSTEAD OF UPDATE ON v BEGIN INSERT INTO log VALUES (1); END",
5999        );
6000        assert_roundtrip("CREATE TRIGGER tr BEFORE UPDATE OF a, b ON t BEGIN SELECT 1; END");
6001    }
6002
6003    #[test]
6004    fn test_roundtrip_create_view_extended() {
6005        assert_roundtrip("CREATE VIEW main.v AS SELECT 1");
6006        assert_roundtrip("CREATE VIEW v(x, y, z) AS SELECT a, b, c FROM t");
6007    }
6008
6009    #[test]
6010    fn test_roundtrip_alter_table_extended() {
6011        assert_roundtrip("ALTER TABLE t RENAME COLUMN a TO b");
6012        assert_roundtrip("ALTER TABLE main.t RENAME TO u");
6013        assert_roundtrip("ALTER TABLE t ADD COLUMN c INTEGER NOT NULL DEFAULT 0");
6014    }
6015
6016    // -----------------------------------------------------------------------
6017    // bd-7pxb §12.10-12.12 Transaction Control + ATTACH/DETACH + EXPLAIN
6018    // -----------------------------------------------------------------------
6019
6020    #[test]
6021    fn test_begin_deferred() {
6022        let stmt = parse_one("BEGIN DEFERRED TRANSACTION");
6023        if let Statement::Begin(b) = stmt {
6024            assert_eq!(b.mode, Some(TransactionMode::Deferred));
6025        } else {
6026            unreachable!("expected Begin");
6027        }
6028    }
6029
6030    #[test]
6031    fn test_begin_immediate() {
6032        let stmt = parse_one("BEGIN IMMEDIATE");
6033        if let Statement::Begin(b) = stmt {
6034            assert_eq!(b.mode, Some(TransactionMode::Immediate));
6035        } else {
6036            unreachable!("expected Begin");
6037        }
6038    }
6039
6040    #[test]
6041    fn test_begin_exclusive() {
6042        let stmt = parse_one("BEGIN EXCLUSIVE TRANSACTION");
6043        if let Statement::Begin(b) = stmt {
6044            assert_eq!(b.mode, Some(TransactionMode::Exclusive));
6045        } else {
6046            unreachable!("expected Begin");
6047        }
6048    }
6049
6050    #[test]
6051    fn test_begin_concurrent() {
6052        let stmt = parse_one("BEGIN CONCURRENT");
6053        if let Statement::Begin(b) = stmt {
6054            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
6055        } else {
6056            unreachable!("expected Begin");
6057        }
6058    }
6059
6060    #[test]
6061    fn test_concurrent_no_conflict() {
6062        // Parser-level: BEGIN without mode (the concurrent entry point) parses.
6063        // Runtime concurrent writer conflict detection is in the MVCC/WAL layer.
6064        let stmt = parse_one("BEGIN");
6065        assert!(matches!(stmt, Statement::Begin(_)));
6066    }
6067
6068    #[test]
6069    fn test_concurrent_page_conflict() {
6070        // Parser-level: verify basic transaction and DML parse.
6071        // Runtime page-level conflict (SQLITE_BUSY_SNAPSHOT) is in the MVCC layer.
6072        let stmts = parse_ok("BEGIN; INSERT INTO t (a) VALUES (1)");
6073        assert_eq!(stmts.len(), 2);
6074        assert!(matches!(stmts[0], Statement::Begin(_)));
6075        assert!(matches!(stmts[1], Statement::Insert(_)));
6076    }
6077
6078    #[test]
6079    fn test_commit_end_synonym() {
6080        let stmt1 = parse_one("COMMIT");
6081        assert!(matches!(stmt1, Statement::Commit));
6082        let stmt2 = parse_one("END TRANSACTION");
6083        assert!(matches!(stmt2, Statement::Commit));
6084        let stmt3 = parse_one("COMMIT TRANSACTION");
6085        assert!(matches!(stmt3, Statement::Commit));
6086    }
6087
6088    #[test]
6089    fn test_rollback() {
6090        let stmt = parse_one("ROLLBACK");
6091        if let Statement::Rollback(r) = stmt {
6092            assert!(r.to_savepoint.is_none());
6093        } else {
6094            unreachable!("expected Rollback");
6095        }
6096    }
6097
6098    #[test]
6099    fn test_savepoint_basic() {
6100        let stmt = parse_one("SAVEPOINT sp1");
6101        assert!(matches!(stmt, Statement::Savepoint(ref name) if name == "sp1"));
6102    }
6103
6104    #[test]
6105    fn test_savepoint_release() {
6106        let stmt = parse_one("RELEASE SAVEPOINT sp1");
6107        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp1"));
6108    }
6109
6110    #[test]
6111    fn test_savepoint_release_removes_later() {
6112        // Parser-level: RELEASE without SAVEPOINT keyword also works.
6113        // Runtime savepoint stack semantics verified in engine tests.
6114        let stmt = parse_one("RELEASE sp2");
6115        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp2"));
6116    }
6117
6118    #[test]
6119    fn test_savepoint_rollback_to() {
6120        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
6121        if let Statement::Rollback(r) = stmt {
6122            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
6123        } else {
6124            unreachable!("expected Rollback");
6125        }
6126    }
6127
6128    #[test]
6129    fn test_savepoint_nested() {
6130        // Parser-level: multiple savepoints in sequence parse independently.
6131        // Runtime stack semantics verified in engine tests.
6132        let stmts = parse_ok("SAVEPOINT sp1; SAVEPOINT sp2; SAVEPOINT sp3");
6133        assert_eq!(stmts.len(), 3);
6134        assert!(matches!(stmts[0], Statement::Savepoint(ref n) if n == "sp1"));
6135        assert!(matches!(stmts[1], Statement::Savepoint(ref n) if n == "sp2"));
6136        assert!(matches!(stmts[2], Statement::Savepoint(ref n) if n == "sp3"));
6137    }
6138
6139    #[test]
6140    fn test_savepoint_rollback_then_continue() {
6141        // Parser-level: ROLLBACK TO followed by more DML parses.
6142        let stmts = parse_ok("ROLLBACK TO sp1; INSERT INTO t VALUES (1)");
6143        assert_eq!(stmts.len(), 2);
6144        assert!(matches!(stmts[0], Statement::Rollback(_)));
6145        assert!(matches!(stmts[1], Statement::Insert(_)));
6146    }
6147
6148    #[test]
6149    fn test_attach_database() {
6150        let stmt = parse_one("ATTACH DATABASE 'other.db' AS other");
6151        if let Statement::Attach(a) = stmt {
6152            assert_eq!(a.schema, "other");
6153        } else {
6154            unreachable!("expected Attach");
6155        }
6156    }
6157
6158    #[test]
6159    fn test_attach_schema_qualified_access() {
6160        // Parser-level: schema-qualified table reference parses correctly.
6161        let stmt = parse_one("SELECT * FROM other.t");
6162        if let Statement::Select(s) = stmt {
6163            if let SelectCore::Select { from, .. } = &s.body.select {
6164                let from = from.as_ref().expect("FROM clause");
6165                match &from.source {
6166                    TableOrSubquery::Table { name, .. } => {
6167                        assert_eq!(name.schema.as_deref(), Some("other"));
6168                        assert_eq!(name.name, "t");
6169                    }
6170                    other => unreachable!("expected Table source, got {other:?}"),
6171                }
6172            } else {
6173                unreachable!("expected Select core");
6174            }
6175        } else {
6176            unreachable!("expected Select");
6177        }
6178    }
6179
6180    #[test]
6181    fn test_detach_database() {
6182        let stmt = parse_one("DETACH DATABASE other");
6183        assert!(matches!(stmt, Statement::Detach(ref name) if name == "other"));
6184    }
6185
6186    #[test]
6187    fn test_attach_max_limit() {
6188        // Parser-level: ATTACH parses identically regardless of limit.
6189        // Runtime SQLITE_MAX_ATTACHED enforcement is in the engine.
6190        let stmt = parse_one("ATTACH 'db11.sqlite' AS db11");
6191        if let Statement::Attach(a) = stmt {
6192            assert_eq!(a.schema, "db11");
6193        } else {
6194            unreachable!("expected Attach");
6195        }
6196    }
6197
6198    #[test]
6199    fn test_cross_database_transaction() {
6200        // Parser-level: transaction with cross-database DML parses.
6201        // Runtime cross-database atomic commit is in WAL/MVCC layer.
6202        let stmts = parse_ok("BEGIN; INSERT INTO main.t SELECT * FROM other.t; COMMIT");
6203        assert_eq!(stmts.len(), 3);
6204        assert!(matches!(stmts[0], Statement::Begin(_)));
6205        assert!(matches!(stmts[1], Statement::Insert(_)));
6206        assert!(matches!(stmts[2], Statement::Commit));
6207    }
6208
6209    #[test]
6210    fn test_explain_returns_bytecode() {
6211        let stmt = parse_one("EXPLAIN SELECT 1");
6212        if let Statement::Explain { query_plan, stmt } = stmt {
6213            assert!(!query_plan);
6214            assert!(matches!(*stmt, Statement::Select(_)));
6215        } else {
6216            unreachable!("expected Explain");
6217        }
6218    }
6219
6220    #[test]
6221    fn test_explain_query_plan_columns() {
6222        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
6223        if let Statement::Explain { query_plan, stmt } = stmt {
6224            assert!(query_plan);
6225            assert!(matches!(*stmt, Statement::Select(_)));
6226        } else {
6227            unreachable!("expected Explain");
6228        }
6229    }
6230
6231    #[test]
6232    fn test_explain_query_plan_shows_index() {
6233        // Parser-level: EXPLAIN QUERY PLAN on indexed query parses.
6234        // Runtime index usage in EQP output is in the planner.
6235        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
6236        if let Statement::Explain { query_plan, .. } = stmt {
6237            assert!(query_plan);
6238        } else {
6239            unreachable!("expected Explain");
6240        }
6241    }
6242
6243    #[test]
6244    fn test_explain_query_plan_tree_structure() {
6245        // Parser-level: EXPLAIN QUERY PLAN on a join query parses.
6246        // Runtime tree structure in EQP output is in the planner.
6247        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id");
6248        if let Statement::Explain { query_plan, stmt } = stmt {
6249            assert!(query_plan);
6250            assert!(matches!(*stmt, Statement::Select(_)));
6251        } else {
6252            unreachable!("expected Explain");
6253        }
6254    }
6255
6256    // -----------------------------------------------------------------------
6257    // bd-2kvo Phase 3 acceptance: keywords as identifiers
6258    // -----------------------------------------------------------------------
6259
6260    #[test]
6261    fn test_parser_keyword_as_column_name() {
6262        // "order" is a keyword but valid as a column name in many contexts.
6263        let stmt = parse_one("SELECT \"order\" FROM t");
6264        assert!(matches!(stmt, Statement::Select(_)));
6265    }
6266
6267    #[test]
6268    fn test_parser_keyword_as_alias() {
6269        let stmt = parse_one("SELECT 1 AS \"limit\"");
6270        assert!(matches!(stmt, Statement::Select(_)));
6271    }
6272
6273    #[test]
6274    fn test_parser_keyword_as_table_name() {
6275        let stmt = parse_one("SELECT * FROM \"group\"");
6276        assert!(matches!(stmt, Statement::Select(_)));
6277    }
6278
6279    // -----------------------------------------------------------------------
6280    // bd-2kvo Phase 3 acceptance: all statement types (Section 12 coverage)
6281    // -----------------------------------------------------------------------
6282
6283    #[test]
6284    fn test_parser_all_statement_types() {
6285        // Each statement type from Section 12 must parse without error.
6286        let statements = [
6287            // DML
6288            "SELECT 1",
6289            "INSERT INTO t VALUES (1)",
6290            "INSERT OR REPLACE INTO t VALUES (1)",
6291            "UPDATE t SET a = 1",
6292            "DELETE FROM t WHERE id = 1",
6293            "REPLACE INTO t VALUES (1)",
6294            // DDL
6295            "CREATE TABLE t (id INTEGER PRIMARY KEY)",
6296            "CREATE TEMPORARY TABLE t (id INTEGER)",
6297            "CREATE TABLE IF NOT EXISTS t (id INTEGER)",
6298            "CREATE INDEX idx ON t (a)",
6299            "CREATE UNIQUE INDEX idx ON t (a)",
6300            "CREATE VIEW v AS SELECT 1",
6301            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END",
6302            "CREATE VIRTUAL TABLE t USING fts5(a, b)",
6303            "ALTER TABLE t RENAME TO t2",
6304            "ALTER TABLE t ADD COLUMN c TEXT",
6305            "ALTER TABLE t DROP COLUMN c",
6306            "ALTER TABLE t RENAME COLUMN a TO b",
6307            "DROP TABLE t",
6308            "DROP TABLE IF EXISTS t",
6309            "DROP INDEX idx",
6310            "DROP VIEW v",
6311            "DROP TRIGGER tr",
6312            // Transaction
6313            "BEGIN",
6314            "BEGIN DEFERRED",
6315            "BEGIN IMMEDIATE",
6316            "BEGIN EXCLUSIVE",
6317            "COMMIT",
6318            "END",
6319            "ROLLBACK",
6320            "SAVEPOINT sp1",
6321            "RELEASE sp1",
6322            "RELEASE SAVEPOINT sp1",
6323            "ROLLBACK TO sp1",
6324            "ROLLBACK TO SAVEPOINT sp1",
6325            // Utility
6326            "ATTACH DATABASE ':memory:' AS db2",
6327            "DETACH db2",
6328            "ANALYZE",
6329            "ANALYZE t",
6330            "VACUUM",
6331            "VACUUM INTO '/tmp/backup.db'",
6332            "REINDEX",
6333            "REINDEX t",
6334            "EXPLAIN SELECT 1",
6335            "EXPLAIN QUERY PLAN SELECT 1",
6336            // PRAGMA
6337            "PRAGMA journal_mode",
6338            "PRAGMA journal_mode = WAL",
6339            "PRAGMA table_info(t)",
6340        ];
6341
6342        for sql in &statements {
6343            let mut p = Parser::from_sql(sql);
6344            let (stmts, errs) = p.parse_all();
6345            assert!(errs.is_empty(), "failed to parse '{sql}': {errs:?}");
6346            assert_eq!(
6347                stmts.len(),
6348                1,
6349                "expected 1 statement for '{sql}', got {}",
6350                stmts.len()
6351            );
6352        }
6353    }
6354
6355    // -----------------------------------------------------------------------
6356    // bd-2kvo Phase 3 acceptance: expression precedence
6357    // -----------------------------------------------------------------------
6358
6359    #[test]
6360    fn test_parser_expression_precedence_mul_over_add() {
6361        // 1 + 2 * 3 should parse as 1 + (2 * 3)
6362        let stmt = parse_one("SELECT 1 + 2 * 3");
6363        if let Statement::Select(s) = stmt {
6364            if let SelectCore::Select { columns, .. } = &s.body.select {
6365                match &columns[0] {
6366                    ResultColumn::Expr { expr, .. } => {
6367                        // Outer expression should be Add, right side should be Multiply.
6368                        assert!(
6369                            matches!(expr, Expr::BinaryOp { .. }),
6370                            "expected BinaryOp, got {expr:?}"
6371                        );
6372                    }
6373                    other => unreachable!("expected Expr column, got {other:?}"),
6374                }
6375            } else {
6376                unreachable!("expected Select core");
6377            }
6378        } else {
6379            unreachable!("expected Select");
6380        }
6381    }
6382
6383    // -----------------------------------------------------------------------
6384    // bd-2kvo Phase 3 acceptance: INSERT with ON CONFLICT and RETURNING
6385    // -----------------------------------------------------------------------
6386
6387    #[test]
6388    fn test_parser_insert_on_conflict() {
6389        let stmt =
6390            parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a");
6391        if let Statement::Insert(i) = stmt {
6392            assert!(!i.upsert.is_empty());
6393        } else {
6394            unreachable!("expected Insert");
6395        }
6396    }
6397
6398    #[test]
6399    fn test_parser_insert_returning() {
6400        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING *");
6401        if let Statement::Insert(i) = stmt {
6402            assert!(!i.returning.is_empty());
6403        } else {
6404            unreachable!("expected Insert");
6405        }
6406    }
6407
6408    #[test]
6409    fn test_parser_delete_returning() {
6410        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
6411        if let Statement::Delete(d) = stmt {
6412            assert!(!d.returning.is_empty());
6413        } else {
6414            unreachable!("expected Delete");
6415        }
6416    }
6417
6418    #[test]
6419    fn test_parser_update_returning() {
6420        let stmt = parse_one("UPDATE t SET a = 1 RETURNING a, b");
6421        if let Statement::Update(u) = stmt {
6422            assert_eq!(u.returning.len(), 2);
6423        } else {
6424            unreachable!("expected Update");
6425        }
6426    }
6427
6428    // -----------------------------------------------------------------------
6429    // bd-2kvo Phase 3 acceptance: compound SELECT operators
6430    // -----------------------------------------------------------------------
6431
6432    #[test]
6433    fn test_parser_union() {
6434        let stmt = parse_one("SELECT 1 UNION SELECT 2");
6435        if let Statement::Select(s) = stmt {
6436            assert_eq!(s.body.compounds.len(), 1);
6437            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
6438        } else {
6439            unreachable!("expected Select");
6440        }
6441    }
6442
6443    #[test]
6444    fn test_parser_intersect() {
6445        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
6446        if let Statement::Select(s) = stmt {
6447            assert_eq!(s.body.compounds.len(), 1);
6448            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
6449        } else {
6450            unreachable!("expected Select");
6451        }
6452    }
6453
6454    #[test]
6455    fn test_parser_except() {
6456        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
6457        if let Statement::Select(s) = stmt {
6458            assert_eq!(s.body.compounds.len(), 1);
6459            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
6460        } else {
6461            unreachable!("expected Select");
6462        }
6463    }
6464
6465    // -----------------------------------------------------------------------
6466    // bd-2kvo Phase 3 acceptance: subquery in FROM
6467    // -----------------------------------------------------------------------
6468
6469    #[test]
6470    fn test_parser_subquery_in_from() {
6471        let stmt = parse_one("SELECT * FROM (SELECT 1 AS x) AS sub");
6472        assert!(matches!(stmt, Statement::Select(_)));
6473    }
6474
6475    // -----------------------------------------------------------------------
6476    // bd-2kvo Phase 3 acceptance: CREATE TABLE with constraints
6477    // -----------------------------------------------------------------------
6478
6479    #[test]
6480    fn test_parser_create_table_all_constraints() {
6481        let stmt = parse_one(
6482            "CREATE TABLE t (\
6483             id INTEGER PRIMARY KEY AUTOINCREMENT,\
6484             name TEXT NOT NULL DEFAULT '',\
6485             email TEXT UNIQUE,\
6486             age INTEGER CHECK(age >= 0),\
6487             dept_id INTEGER REFERENCES dept(id) ON DELETE CASCADE,\
6488             CONSTRAINT pk PRIMARY KEY (id),\
6489             UNIQUE (email),\
6490             CHECK (age < 200),\
6491             FOREIGN KEY (dept_id) REFERENCES dept(id)\
6492             )",
6493        );
6494        if let Statement::CreateTable(ct) = stmt {
6495            if let CreateTableBody::Columns {
6496                columns,
6497                constraints,
6498            } = ct.body
6499            {
6500                assert_eq!(columns.len(), 5);
6501                assert!(!constraints.is_empty());
6502            } else {
6503                unreachable!("expected column defs");
6504            }
6505        } else {
6506            unreachable!("expected CreateTable");
6507        }
6508    }
6509
6510    // -----------------------------------------------------------------------
6511    // bd-2kvo Phase 3 acceptance: CREATE TRIGGER with all timing/events
6512    // -----------------------------------------------------------------------
6513
6514    #[test]
6515    fn test_parser_create_trigger_before_delete() {
6516        let stmt = parse_one("CREATE TRIGGER tr BEFORE DELETE ON t BEGIN SELECT 1; END");
6517        if let Statement::CreateTrigger(tr) = stmt {
6518            assert_eq!(tr.timing, TriggerTiming::Before);
6519            assert!(matches!(tr.event, TriggerEvent::Delete));
6520        } else {
6521            unreachable!("expected CreateTrigger");
6522        }
6523    }
6524
6525    #[test]
6526    fn test_parser_create_trigger_instead_of_update() {
6527        let stmt =
6528            parse_one("CREATE TRIGGER tr INSTEAD OF UPDATE OF a, b ON v BEGIN SELECT 1; END");
6529        if let Statement::CreateTrigger(tr) = stmt {
6530            assert_eq!(tr.timing, TriggerTiming::InsteadOf);
6531            if let TriggerEvent::Update(cols) = &tr.event {
6532                assert_eq!(cols.len(), 2);
6533            } else {
6534                unreachable!("expected UpdateOf event");
6535            }
6536        } else {
6537            unreachable!("expected CreateTrigger");
6538        }
6539    }
6540
6541    // -----------------------------------------------------------------------
6542    // bd-2kvo Phase 3 acceptance: CREATE VIEW with columns
6543    // -----------------------------------------------------------------------
6544
6545    #[test]
6546    fn test_parser_create_view_with_columns() {
6547        let stmt = parse_one("CREATE VIEW v (a, b) AS SELECT 1, 2");
6548        if let Statement::CreateView(cv) = stmt {
6549            assert_eq!(cv.columns, vec!["a".to_owned(), "b".to_owned()]);
6550        } else {
6551            unreachable!("expected CreateView");
6552        }
6553    }
6554
6555    // -----------------------------------------------------------------------
6556    // bd-2kvo Phase 3 acceptance: multi-way join
6557    // -----------------------------------------------------------------------
6558
6559    #[test]
6560    fn test_parser_multi_join() {
6561        let stmt = parse_one(
6562            "SELECT a.x, b.y, c.z FROM a \
6563             JOIN b ON a.id = b.a_id \
6564             LEFT JOIN c ON b.id = c.b_id \
6565             CROSS JOIN d",
6566        );
6567        if let Statement::Select(s) = stmt {
6568            if let SelectCore::Select { from, .. } = &s.body.select {
6569                let from = from.as_ref().expect("FROM clause");
6570                assert_eq!(from.joins.len(), 3);
6571                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
6572                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
6573                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
6574            } else {
6575                unreachable!("expected Select core");
6576            }
6577        } else {
6578            unreachable!("expected Select");
6579        }
6580    }
6581
6582    // -----------------------------------------------------------------------
6583    // bd-2kvo Phase 3 acceptance: GROUP BY / HAVING
6584    // -----------------------------------------------------------------------
6585
6586    #[test]
6587    fn test_parser_group_by_having() {
6588        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
6589        if let Statement::Select(s) = stmt {
6590            if let SelectCore::Select {
6591                group_by, having, ..
6592            } = &s.body.select
6593            {
6594                assert!(!group_by.is_empty());
6595                assert!(having.is_some());
6596            } else {
6597                unreachable!("expected Select core");
6598            }
6599        } else {
6600            unreachable!("expected Select");
6601        }
6602    }
6603
6604    // -----------------------------------------------------------------------
6605    // bd-2kvo Phase 3 acceptance: Error recovery with line:column spans
6606    // -----------------------------------------------------------------------
6607
6608    #[test]
6609    fn test_parser_error_recovery_with_span() {
6610        // Multi-line input with an error on line 2.
6611        let sql = "SELECT 1;\nXYZZY 42;\nSELECT 3";
6612        let mut p = Parser::from_sql(sql);
6613        let (stmts, errs) = p.parse_all();
6614        assert_eq!(stmts.len(), 2, "should recover two valid statements");
6615        assert!(!errs.is_empty(), "should report at least one error");
6616
6617        let err = &errs[0];
6618        // XYZZY starts at line 2, column 1.
6619        assert_eq!(err.line, 2, "error should be on line 2");
6620        assert_eq!(err.col, 1, "error should be at column 1");
6621        // Span should be non-zero and point within the source.
6622        assert!(
6623            err.span.start < err.span.end,
6624            "error span should be non-empty"
6625        );
6626        let source_len = u32::try_from(sql.len()).unwrap();
6627        assert!(
6628            err.span.end <= source_len,
6629            "error span.end should be within source"
6630        );
6631    }
6632
6633    #[test]
6634    fn test_parser_error_span_mid_line() {
6635        // Incomplete CREATE should produce an error.
6636        let bad = Parser::from_sql("CREATE").parse_statement();
6637        assert!(bad.is_err());
6638        let err = bad.unwrap_err();
6639        assert_eq!(err.line, 1);
6640    }
6641
6642    // -----------------------------------------------------------------------
6643    // bd-2kvo Phase 3 acceptance: Keyword lookup covers 150+ keywords
6644    // -----------------------------------------------------------------------
6645
6646    #[test]
6647    #[allow(clippy::too_many_lines)]
6648    fn test_parser_keyword_lookup_all_150() {
6649        use crate::token::TokenKind;
6650
6651        // Exhaustive list of all SQL keywords in lookup_keyword.
6652        let keywords = [
6653            "ABORT",
6654            "ACTION",
6655            "ADD",
6656            "AFTER",
6657            "ALL",
6658            "ALTER",
6659            "ALWAYS",
6660            "ANALYZE",
6661            "AND",
6662            "AS",
6663            "ASC",
6664            "ATTACH",
6665            "AUTOINCREMENT",
6666            "BEFORE",
6667            "BEGIN",
6668            "BETWEEN",
6669            "BY",
6670            "CASCADE",
6671            "CASE",
6672            "CAST",
6673            "CHECK",
6674            "COLLATE",
6675            "COLUMN",
6676            "COMMIT",
6677            "CONCURRENT",
6678            "CONFLICT",
6679            "CONSTRAINT",
6680            "CREATE",
6681            "CROSS",
6682            "CURRENT_DATE",
6683            "CURRENT_TIME",
6684            "CURRENT_TIMESTAMP",
6685            "DATABASE",
6686            "DEFAULT",
6687            "DEFERRABLE",
6688            "DEFERRED",
6689            "DELETE",
6690            "DESC",
6691            "DETACH",
6692            "DISTINCT",
6693            "DO",
6694            "DROP",
6695            "EACH",
6696            "ELSE",
6697            "END",
6698            "ESCAPE",
6699            "EXCEPT",
6700            "EXCLUDE",
6701            "EXCLUSIVE",
6702            "EXISTS",
6703            "EXPLAIN",
6704            "FAIL",
6705            "FILTER",
6706            "FIRST",
6707            "FOLLOWING",
6708            "FOR",
6709            "FOREIGN",
6710            "FROM",
6711            "FULL",
6712            "GENERATED",
6713            "GLOB",
6714            "GROUP",
6715            "GROUPS",
6716            "HAVING",
6717            "IF",
6718            "IGNORE",
6719            "IMMEDIATE",
6720            "IN",
6721            "INDEX",
6722            "INDEXED",
6723            "INITIALLY",
6724            "INNER",
6725            "INSERT",
6726            "INSTEAD",
6727            "INTERSECT",
6728            "INTO",
6729            "IS",
6730            "ISNULL",
6731            "JOIN",
6732            "KEY",
6733            "LAST",
6734            "LEFT",
6735            "LIKE",
6736            "LIMIT",
6737            "MATCH",
6738            "MATERIALIZED",
6739            "NATURAL",
6740            "NO",
6741            "NOT",
6742            "NOTHING",
6743            "NOTNULL",
6744            "NULL",
6745            "NULLS",
6746            "OF",
6747            "OFFSET",
6748            "ON",
6749            "OR",
6750            "ORDER",
6751            "OTHERS",
6752            "OUTER",
6753            "OVER",
6754            "PARTITION",
6755            "PLAN",
6756            "PRAGMA",
6757            "PRECEDING",
6758            "PRIMARY",
6759            "QUERY",
6760            "RAISE",
6761            "RANGE",
6762            "RECURSIVE",
6763            "REFERENCES",
6764            "REGEXP",
6765            "REINDEX",
6766            "RELEASE",
6767            "RENAME",
6768            "REPLACE",
6769            "RESTRICT",
6770            "RETURNING",
6771            "RIGHT",
6772            "ROLLBACK",
6773            "ROW",
6774            "ROWS",
6775            "SAVEPOINT",
6776            "SELECT",
6777            "SET",
6778            "STORED",
6779            "STRICT",
6780            "TABLE",
6781            "TEMP",
6782            "TEMPORARY",
6783            "THEN",
6784            "TIES",
6785            "TO",
6786            "TRANSACTION",
6787            "TRIGGER",
6788            "TRUE",
6789            "FALSE",
6790            "UNBOUNDED",
6791            "UNION",
6792            "UNIQUE",
6793            "UPDATE",
6794            "USING",
6795            "VACUUM",
6796            "VALUES",
6797            "VIEW",
6798            "VIRTUAL",
6799            "WHEN",
6800            "WHERE",
6801            "WINDOW",
6802            "WITH",
6803            "WITHOUT",
6804        ];
6805
6806        assert!(
6807            keywords.len() >= 150,
6808            "expected 150+ keywords, got {}",
6809            keywords.len()
6810        );
6811
6812        for kw in &keywords {
6813            assert!(
6814                TokenKind::lookup_keyword(kw).is_some(),
6815                "keyword {kw} not recognized (uppercase)"
6816            );
6817            // Case-insensitive: lowercase must also work.
6818            let lower = kw.to_ascii_lowercase();
6819            assert!(
6820                TokenKind::lookup_keyword(&lower).is_some(),
6821                "keyword {kw} not recognized (lowercase)"
6822            );
6823            // Mixed case.
6824            let mixed: String = kw
6825                .chars()
6826                .enumerate()
6827                .map(|(i, c)| {
6828                    if i % 2 == 0 {
6829                        c.to_ascii_lowercase()
6830                    } else {
6831                        c.to_ascii_uppercase()
6832                    }
6833                })
6834                .collect();
6835            assert!(
6836                TokenKind::lookup_keyword(&mixed).is_some(),
6837                "keyword {kw} not recognized (mixed case: {mixed})"
6838            );
6839        }
6840
6841        // Non-keyword should return None.
6842        assert!(TokenKind::lookup_keyword("FOOBAR").is_none());
6843        assert!(TokenKind::lookup_keyword("").is_none());
6844    }
6845
6846    // -----------------------------------------------------------------------
6847    // Round-trip: parse → Display → re-parse → compare ASTs
6848    // -----------------------------------------------------------------------
6849
6850    /// Parse SQL, convert back to string via Display, re-parse, convert back
6851    /// again, and assert the two rendered strings are identical.  We compare
6852    /// rendered strings (not ASTs) because Display may normalise constructs
6853    /// (e.g. `INSERT OR REPLACE` → `REPLACE`) which changes SQL length and
6854    /// therefore Span positions, while the logical content is identical.
6855    fn assert_roundtrip(sql: &str) {
6856        let ast1 = parse_one(sql);
6857        let rendered1 = ast1.to_string();
6858        let ast2 = parse_one(&rendered1);
6859        let rendered2 = ast2.to_string();
6860        assert_eq!(
6861            rendered1, rendered2,
6862            "round-trip failed for:\n  input: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
6863        );
6864    }
6865
6866    #[test]
6867    fn test_roundtrip_select_simple() {
6868        assert_roundtrip("SELECT 1");
6869        assert_roundtrip("SELECT 1, 2, 3");
6870        assert_roundtrip("SELECT *");
6871        assert_roundtrip("SELECT * FROM t");
6872        assert_roundtrip("SELECT a, b FROM t WHERE a > 10");
6873        assert_roundtrip("SELECT a FROM t ORDER BY a DESC");
6874        assert_roundtrip("SELECT a FROM t LIMIT 10 OFFSET 5");
6875    }
6876
6877    #[test]
6878    fn test_roundtrip_select_distinct() {
6879        assert_roundtrip("SELECT DISTINCT a, b FROM t");
6880    }
6881
6882    #[test]
6883    fn test_roundtrip_select_alias() {
6884        assert_roundtrip("SELECT a AS x, b AS y FROM t AS u");
6885    }
6886
6887    #[test]
6888    fn test_roundtrip_select_join_types() {
6889        assert_roundtrip("SELECT * FROM a INNER JOIN b ON a.id = b.id");
6890        assert_roundtrip("SELECT * FROM a LEFT JOIN b ON a.id = b.id");
6891        assert_roundtrip("SELECT * FROM a RIGHT JOIN b ON a.id = b.id");
6892        assert_roundtrip("SELECT * FROM a FULL JOIN b ON a.id = b.id");
6893        assert_roundtrip("SELECT * FROM a CROSS JOIN b");
6894        assert_roundtrip("SELECT * FROM a NATURAL INNER JOIN b");
6895        assert_roundtrip("SELECT * FROM a LEFT JOIN b USING (id)");
6896    }
6897
6898    #[test]
6899    fn test_roundtrip_select_subquery() {
6900        assert_roundtrip("SELECT * FROM (SELECT 1 AS x) AS sub");
6901    }
6902
6903    #[test]
6904    fn test_roundtrip_select_group_by_having() {
6905        assert_roundtrip("SELECT a, count(*) FROM t GROUP BY a HAVING count(*) > 1");
6906    }
6907
6908    #[test]
6909    fn test_roundtrip_select_window() {
6910        assert_roundtrip("SELECT sum(x) OVER (PARTITION BY g ORDER BY x) FROM t");
6911    }
6912
6913    #[test]
6914    fn test_roundtrip_select_cte() {
6915        assert_roundtrip("WITH cte AS (SELECT 1 AS n) SELECT * FROM cte");
6916        assert_roundtrip(
6917            "WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x + 1 FROM cnt WHERE x < 10) SELECT * FROM cnt",
6918        );
6919    }
6920
6921    #[test]
6922    fn test_roundtrip_select_compound() {
6923        assert_roundtrip("SELECT 1 UNION SELECT 2");
6924        assert_roundtrip("SELECT 1 UNION ALL SELECT 2");
6925        assert_roundtrip("SELECT 1 INTERSECT SELECT 2");
6926        assert_roundtrip("SELECT 1 EXCEPT SELECT 2");
6927    }
6928
6929    #[test]
6930    fn test_roundtrip_insert() {
6931        assert_roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
6932        assert_roundtrip("INSERT INTO t DEFAULT VALUES");
6933        assert_roundtrip("INSERT INTO t SELECT * FROM u");
6934        assert_roundtrip("INSERT OR REPLACE INTO t (a) VALUES (1)");
6935        assert_roundtrip("REPLACE INTO t (a) VALUES (1)");
6936    }
6937
6938    #[test]
6939    fn test_roundtrip_insert_returning() {
6940        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING *");
6941        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING a, b");
6942    }
6943
6944    #[test]
6945    fn test_roundtrip_insert_on_conflict() {
6946        assert_roundtrip("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
6947        assert_roundtrip(
6948            "INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a",
6949        );
6950    }
6951
6952    #[test]
6953    fn test_roundtrip_update() {
6954        assert_roundtrip("UPDATE t SET a = 1");
6955        assert_roundtrip("UPDATE t SET a = 1, b = 2 WHERE c > 3");
6956        assert_roundtrip("UPDATE t SET a = 1 RETURNING *");
6957    }
6958
6959    #[test]
6960    fn test_roundtrip_delete() {
6961        assert_roundtrip("DELETE FROM t");
6962        assert_roundtrip("DELETE FROM t WHERE a = 1");
6963        assert_roundtrip("DELETE FROM t RETURNING *");
6964    }
6965
6966    #[test]
6967    fn test_roundtrip_create_table() {
6968        assert_roundtrip("CREATE TABLE t (a INTEGER, b TEXT)");
6969        assert_roundtrip("CREATE TABLE IF NOT EXISTS t (a INTEGER PRIMARY KEY)");
6970        assert_roundtrip("CREATE TEMP TABLE t (a TEXT NOT NULL, b REAL DEFAULT 0.0)");
6971    }
6972
6973    #[test]
6974    fn test_roundtrip_create_index() {
6975        assert_roundtrip("CREATE INDEX idx ON t (a)");
6976        assert_roundtrip("CREATE UNIQUE INDEX IF NOT EXISTS idx ON t (a, b DESC)");
6977        assert_roundtrip("CREATE INDEX idx ON t (a) WHERE a > 0");
6978    }
6979
6980    #[test]
6981    fn test_roundtrip_drop() {
6982        assert_roundtrip("DROP TABLE t");
6983        assert_roundtrip("DROP TABLE IF EXISTS t");
6984        assert_roundtrip("DROP INDEX idx");
6985        assert_roundtrip("DROP VIEW v");
6986    }
6987
6988    #[test]
6989    fn test_roundtrip_alter_table() {
6990        assert_roundtrip("ALTER TABLE t RENAME TO u");
6991        assert_roundtrip("ALTER TABLE t ADD COLUMN c TEXT");
6992        assert_roundtrip("ALTER TABLE t DROP COLUMN c");
6993    }
6994
6995    #[test]
6996    fn test_roundtrip_transaction() {
6997        assert_roundtrip("BEGIN");
6998        assert_roundtrip("BEGIN IMMEDIATE");
6999        assert_roundtrip("BEGIN EXCLUSIVE");
7000        assert_roundtrip("COMMIT");
7001        assert_roundtrip("ROLLBACK");
7002        assert_roundtrip("SAVEPOINT sp1");
7003        assert_roundtrip("RELEASE sp1");
7004    }
7005
7006    #[test]
7007    fn test_roundtrip_pragma() {
7008        assert_roundtrip("PRAGMA journal_mode");
7009        assert_roundtrip("PRAGMA journal_mode = wal");
7010    }
7011
7012    #[test]
7013    fn test_roundtrip_explain() {
7014        assert_roundtrip("EXPLAIN SELECT 1");
7015        assert_roundtrip("EXPLAIN QUERY PLAN SELECT * FROM t");
7016    }
7017
7018    #[test]
7019    fn test_roundtrip_expressions() {
7020        assert_roundtrip("SELECT 1 + 2 * 3");
7021        assert_roundtrip("SELECT NOT a");
7022        assert_roundtrip("SELECT -x");
7023        assert_roundtrip("SELECT ~x");
7024        assert_roundtrip("SELECT a BETWEEN 1 AND 10");
7025        assert_roundtrip("SELECT a NOT BETWEEN 1 AND 10");
7026        assert_roundtrip("SELECT a IN (1, 2, 3)");
7027        assert_roundtrip("SELECT a NOT IN (1, 2, 3)");
7028        assert_roundtrip("SELECT a LIKE '%foo%'");
7029        assert_roundtrip("SELECT a GLOB '*foo*'");
7030        assert_roundtrip("SELECT CASE WHEN a = 1 THEN 'one' ELSE 'other' END");
7031        assert_roundtrip("SELECT CASE x WHEN 1 THEN 'a' WHEN 2 THEN 'b' END");
7032        assert_roundtrip("SELECT CAST(a AS TEXT)");
7033        assert_roundtrip("SELECT EXISTS (SELECT 1)");
7034        assert_roundtrip("SELECT (SELECT 1)");
7035        assert_roundtrip("SELECT a COLLATE NOCASE");
7036    }
7037
7038    #[test]
7039    fn test_roundtrip_literals() {
7040        assert_roundtrip("SELECT NULL");
7041        assert_roundtrip("SELECT TRUE");
7042        assert_roundtrip("SELECT FALSE");
7043        assert_roundtrip("SELECT 42");
7044        assert_roundtrip("SELECT 3.14");
7045        assert_roundtrip("SELECT 'hello'");
7046        assert_roundtrip("SELECT X'DEADBEEF'");
7047        assert_roundtrip("SELECT CURRENT_TIME");
7048        assert_roundtrip("SELECT CURRENT_DATE");
7049        assert_roundtrip("SELECT CURRENT_TIMESTAMP");
7050    }
7051
7052    #[test]
7053    fn test_roundtrip_placeholders() {
7054        assert_roundtrip("SELECT ?");
7055        assert_roundtrip("SELECT ?1");
7056        assert_roundtrip("SELECT :name");
7057        assert_roundtrip("SELECT @name");
7058        assert_roundtrip("SELECT $name");
7059    }
7060
7061    #[test]
7062    fn test_roundtrip_json_arrows() {
7063        assert_roundtrip("SELECT a -> 'key'");
7064        assert_roundtrip("SELECT a ->> 'key'");
7065    }
7066
7067    #[test]
7068    fn test_roundtrip_function_calls() {
7069        assert_roundtrip("SELECT count(*)");
7070        assert_roundtrip("SELECT count(DISTINCT a)");
7071        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0)");
7072    }
7073
7074    #[test]
7075    fn test_roundtrip_isnull_notnull() {
7076        assert_roundtrip("SELECT a ISNULL");
7077        assert_roundtrip("SELECT a IS NOT NULL");
7078    }
7079
7080    #[test]
7081    fn test_roundtrip_create_view() {
7082        assert_roundtrip("CREATE VIEW v AS SELECT * FROM t");
7083        assert_roundtrip("CREATE VIEW IF NOT EXISTS v (a, b) AS SELECT 1, 2");
7084    }
7085
7086    #[test]
7087    fn test_roundtrip_create_trigger() {
7088        assert_roundtrip(
7089            "CREATE TRIGGER tr BEFORE DELETE ON t FOR EACH ROW BEGIN DELETE FROM log WHERE id = OLD.id; END",
7090        );
7091    }
7092
7093    #[test]
7094    fn test_roundtrip_attach_detach() {
7095        assert_roundtrip("ATTACH 'file.db' AS db2");
7096        assert_roundtrip("DETACH db2");
7097    }
7098
7099    #[test]
7100    fn test_roundtrip_vacuum() {
7101        assert_roundtrip("VACUUM");
7102    }
7103
7104    #[test]
7105    fn test_roundtrip_analyze_reindex() {
7106        assert_roundtrip("ANALYZE");
7107        assert_roundtrip("ANALYZE t");
7108        assert_roundtrip("REINDEX");
7109        assert_roundtrip("REINDEX t");
7110    }
7111
7112    #[test]
7113    fn test_roundtrip_cte_materialized() {
7114        assert_roundtrip("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
7115        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
7116    }
7117
7118    // -----------------------------------------------------------------------
7119    // Proptest: round-trip property test (bd-2kvo acceptance criterion #12)
7120    // -----------------------------------------------------------------------
7121
7122    mod proptest_roundtrip {
7123        use super::*;
7124        use proptest::prelude::*;
7125
7126        /// Returns `true` if the string is a SQL keyword.
7127        fn is_keyword(s: &str) -> bool {
7128            TokenKind::lookup_keyword(s).is_some()
7129        }
7130
7131        /// Generate a random identifier (simple alphanumeric, not a SQL keyword).
7132        fn arb_ident() -> BoxedStrategy<String> {
7133            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
7134                .expect("valid regex")
7135                .prop_filter("must not be keyword", |s| !is_keyword(s))
7136                .boxed()
7137        }
7138
7139        /// Generate a random literal value.
7140        fn arb_literal() -> BoxedStrategy<String> {
7141            prop_oneof![
7142                any::<i32>().prop_map(|n| n.to_string()),
7143                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
7144                arb_ident().prop_map(|s| format!("'{s}'")),
7145                Just("NULL".to_string()),
7146                Just("TRUE".to_string()),
7147                Just("FALSE".to_string()),
7148            ]
7149            .boxed()
7150        }
7151
7152        /// Generate a random expression of bounded depth.
7153        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
7154            if depth == 0 {
7155                prop_oneof![
7156                    arb_literal(),
7157                    arb_ident(),
7158                    (arb_ident(), arb_ident()).prop_map(|(t, c)| format!("{t}.{c}")),
7159                ]
7160                .boxed()
7161            } else {
7162                let leaf = arb_expr(0);
7163                prop_oneof![
7164                    4 => leaf,
7165                    // Binary ops (always parenthesized by display)
7166                    2 => (arb_expr(depth - 1), prop_oneof![
7167                        Just("+"), Just("-"), Just("*"), Just("/"),
7168                        Just("="), Just("!="), Just("<"), Just("<="),
7169                        Just(">"), Just(">="), Just("AND"), Just("OR"),
7170                        Just("||"),
7171                    ], arb_expr(depth - 1))
7172                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
7173                    // Unary ops
7174                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
7175                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
7176                    // IS NULL / IS NOT NULL
7177                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NULL")),
7178                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NOT NULL")),
7179                    // BETWEEN
7180                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
7181                        .prop_map(|(e, lo, hi)| format!("{e} BETWEEN {lo} AND {hi}")),
7182                    // IN list
7183                    1 => (arb_expr(depth - 1), proptest::collection::vec(arb_expr(0), 1..4))
7184                        .prop_map(|(e, items)| format!("{e} IN ({})", items.join(", "))),
7185                    // LIKE
7186                    1 => (arb_expr(depth - 1), arb_ident())
7187                        .prop_map(|(e, p)| format!("{e} LIKE '{p}'")),
7188                    // CAST
7189                    1 => arb_expr(depth - 1).prop_map(|e| format!("CAST({e} AS TEXT)")),
7190                    // CASE
7191                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
7192                        .prop_map(|(c, t, el)| format!("CASE WHEN {c} THEN {t} ELSE {el} END")),
7193                    // Function call
7194                    1 => (arb_ident(), proptest::collection::vec(arb_expr(0), 0..3))
7195                        .prop_map(|(name, args)| format!("{name}({})", args.join(", "))),
7196                    // Subquery
7197                    1 => arb_expr(0).prop_map(|e| format!("(SELECT {e})")),
7198                ]
7199                .boxed()
7200            }
7201        }
7202
7203        /// Generate a random SELECT statement.
7204        fn arb_select() -> BoxedStrategy<String> {
7205            use std::fmt::Write as _;
7206
7207            let cols =
7208                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
7209            let table = arb_ident();
7210            let where_clause = prop::option::of(arb_expr(1));
7211            let order_by = prop::option::of(arb_ident());
7212            let limit = prop::option::of(1u32..100);
7213
7214            (cols, table, where_clause, order_by, limit)
7215                .prop_map(|(cols, tbl, wh, ord, lim)| {
7216                    let mut sql = format!("SELECT {cols} FROM {tbl}");
7217                    if let Some(w) = wh {
7218                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
7219                    }
7220                    if let Some(o) = ord {
7221                        write!(sql, " ORDER BY {o}").expect("writing to String should not fail");
7222                    }
7223                    if let Some(l) = lim {
7224                        write!(sql, " LIMIT {l}").expect("writing to String should not fail");
7225                    }
7226                    sql
7227                })
7228                .boxed()
7229        }
7230
7231        /// Generate a random INSERT statement.
7232        fn arb_insert() -> BoxedStrategy<String> {
7233            let ncols = 1usize..4;
7234            ncols
7235                .prop_flat_map(|n| {
7236                    let tbl = arb_ident();
7237                    let cols = proptest::collection::vec(arb_ident(), n..=n);
7238                    let vals = proptest::collection::vec(arb_literal(), n..=n);
7239                    (tbl, cols, vals).prop_map(|(t, cs, vs): (String, Vec<String>, Vec<String>)| {
7240                        format!(
7241                            "INSERT INTO {t} ({}) VALUES ({})",
7242                            cs.join(", "),
7243                            vs.join(", ")
7244                        )
7245                    })
7246                })
7247                .boxed()
7248        }
7249
7250        /// Generate a random statement.
7251        fn arb_statement() -> BoxedStrategy<String> {
7252            prop_oneof![
7253                6 => arb_select(),
7254                3 => arb_insert(),
7255                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
7256                1 => (arb_ident(), arb_expr(1))
7257                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
7258                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
7259                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
7260            ]
7261            .boxed()
7262        }
7263
7264        /// Try to parse SQL into a single statement; returns `None` if unparseable.
7265        fn try_parse_one(sql: &str) -> Option<Statement> {
7266            let mut p = Parser::from_sql(sql);
7267            let (stmts, errs) = p.parse_all();
7268            if errs.is_empty() && stmts.len() == 1 {
7269                Some(stmts.into_iter().next().unwrap())
7270            } else {
7271                None
7272            }
7273        }
7274
7275        proptest::proptest! {
7276            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
7277
7278            #[test]
7279            fn test_parser_roundtrip_proptest(sql in arb_statement()) {
7280                // Phase 1: parse the generated SQL.
7281                let Some(ast1) = try_parse_one(&sql) else {
7282                    return Ok(()); // skip unparseable inputs
7283                };
7284
7285                // Phase 2: display the AST back to SQL text.
7286                let rendered1 = ast1.to_string();
7287
7288                // Phase 3: re-parse the rendered SQL.
7289                let Some(ast2) = try_parse_one(&rendered1) else {
7290                    let msg = format!("re-parse failed for rendered SQL: {rendered1:?}");
7291                    prop_assert!(false, "{}", msg);
7292                    unreachable!()
7293                };
7294
7295                // Phase 4: display again and compare (idempotency check).
7296                let rendered2 = ast2.to_string();
7297                let msg = format!(
7298                    "round-trip not idempotent:\n  original: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
7299                );
7300                prop_assert_eq!(rendered1, rendered2, "{}", msg);
7301            }
7302        }
7303    }
7304
7305    // -----------------------------------------------------------------------
7306    // Proptest: additional property tests (bd-1lsfu.4)
7307    // -----------------------------------------------------------------------
7308
7309    mod proptest_properties {
7310        use super::*;
7311        use proptest::prelude::*;
7312
7313        /// Reuse the statement generator from the roundtrip module.
7314        fn arb_ident() -> BoxedStrategy<String> {
7315            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
7316                .expect("valid regex")
7317                .prop_filter("must not be keyword", |s| {
7318                    TokenKind::lookup_keyword(s).is_none()
7319                })
7320                .boxed()
7321        }
7322
7323        fn arb_literal() -> BoxedStrategy<String> {
7324            prop_oneof![
7325                any::<i32>().prop_map(|n| n.to_string()),
7326                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
7327                arb_ident().prop_map(|s| format!("'{s}'")),
7328                Just("NULL".to_string()),
7329                Just("TRUE".to_string()),
7330                Just("FALSE".to_string()),
7331            ]
7332            .boxed()
7333        }
7334
7335        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
7336            if depth == 0 {
7337                prop_oneof![arb_literal(), arb_ident(),].boxed()
7338            } else {
7339                let leaf = arb_expr(0);
7340                prop_oneof![
7341                    4 => leaf,
7342                    2 => (arb_expr(depth - 1), prop_oneof![
7343                        Just("+"), Just("-"), Just("*"), Just("/"),
7344                        Just("="), Just("!="), Just("<"), Just("<="),
7345                        Just(">"), Just(">="), Just("AND"), Just("OR"),
7346                    ], arb_expr(depth - 1))
7347                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
7348                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
7349                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
7350                ]
7351                .boxed()
7352            }
7353        }
7354
7355        fn arb_select() -> BoxedStrategy<String> {
7356            use std::fmt::Write as _;
7357            let cols =
7358                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
7359            let table = arb_ident();
7360            let where_clause = prop::option::of(arb_expr(1));
7361            (cols, table, where_clause)
7362                .prop_map(|(cols, tbl, wh)| {
7363                    let mut sql = format!("SELECT {cols} FROM {tbl}");
7364                    if let Some(w) = wh {
7365                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
7366                    }
7367                    sql
7368                })
7369                .boxed()
7370        }
7371
7372        fn arb_statement() -> BoxedStrategy<String> {
7373            prop_oneof![
7374                6 => arb_select(),
7375                3 => {
7376                    let ncols = 1usize..4;
7377                    ncols
7378                        .prop_flat_map(|n| {
7379                            let tbl = arb_ident();
7380                            let cols = proptest::collection::vec(arb_ident(), n..=n);
7381                            let vals = proptest::collection::vec(arb_literal(), n..=n);
7382                            (tbl, cols, vals).prop_map(
7383                                |(t, cs, vs): (String, Vec<String>, Vec<String>)| {
7384                                    format!(
7385                                        "INSERT INTO {t} ({}) VALUES ({})",
7386                                        cs.join(", "),
7387                                        vs.join(", ")
7388                                    )
7389                                },
7390                            )
7391                        })
7392                        .boxed()
7393                },
7394                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
7395                1 => (arb_ident(), arb_expr(1))
7396                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
7397                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
7398                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
7399            ]
7400            .boxed()
7401        }
7402
7403        // Property 2: Determinism — same input always produces the same AST.
7404        proptest::proptest! {
7405            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(500))]
7406
7407            #[test]
7408            fn test_parser_determinism(sql in arb_statement()) {
7409                let mut p1 = Parser::from_sql(&sql);
7410                let (stmts1, errs1) = p1.parse_all();
7411
7412                let mut p2 = Parser::from_sql(&sql);
7413                let (stmts2, errs2) = p2.parse_all();
7414
7415                // Both parses must produce the same number of statements and errors.
7416                let msg_stmt = format!("different statement counts for: {sql}");
7417                prop_assert_eq!(stmts1.len(), stmts2.len(), "{}", msg_stmt);
7418                let msg_err = format!("different error counts for: {sql}");
7419                prop_assert_eq!(errs1.len(), errs2.len(), "{}", msg_err);
7420
7421                // If successful, the rendered SQL must be identical.
7422                if errs1.is_empty() && !stmts1.is_empty() {
7423                    for (s1, s2) in stmts1.iter().zip(stmts2.iter()) {
7424                        let r1 = s1.to_string();
7425                        let r2 = s2.to_string();
7426                        let msg_det = format!("non-deterministic parse output for: {sql}");
7427                        prop_assert_eq!(r1, r2, "{}", msg_det);
7428                    }
7429                }
7430            }
7431        }
7432
7433        // Property 3: Fuzz safety — random byte strings never panic the parser.
7434        proptest::proptest! {
7435            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(2000))]
7436
7437            #[test]
7438            fn test_parser_fuzz_no_panic(input in prop::collection::vec(any::<u8>(), 0..256)) {
7439                let sql = String::from_utf8_lossy(&input);
7440                // Must not panic — errors are fine, panics are not.
7441                let mut p = Parser::from_sql(&sql);
7442                let _ = p.parse_all();
7443            }
7444        }
7445
7446        // Property 3b: Fuzz safety with near-valid SQL (more likely to trigger edge cases).
7447        proptest::proptest! {
7448            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
7449
7450            #[test]
7451            fn test_parser_fuzz_near_valid(
7452                prefix in prop_oneof![
7453                    Just("SELECT "),
7454                    Just("INSERT INTO "),
7455                    Just("DELETE FROM "),
7456                    Just("UPDATE "),
7457                    Just("CREATE TABLE "),
7458                    Just("DROP TABLE "),
7459                    Just("BEGIN "),
7460                    Just("PRAGMA "),
7461                ],
7462                suffix in prop::string::string_regex("[a-zA-Z0-9_ ,.*=<>!()'\";+\\-/]{0,100}")
7463                    .expect("valid regex")
7464            ) {
7465                let sql = format!("{prefix}{suffix}");
7466                let mut p = Parser::from_sql(&sql);
7467                let _ = p.parse_all();
7468            }
7469        }
7470
7471        // Property 4: Unicode identifiers parse correctly.
7472        proptest::proptest! {
7473            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
7474
7475            #[test]
7476            fn test_parser_unicode_identifiers(
7477                name in prop::string::string_regex("[\\p{L}][\\p{L}\\p{N}_]{0,10}")
7478                    .expect("valid regex")
7479                    .prop_filter("must not be keyword", |s| {
7480                        TokenKind::lookup_keyword(s).is_none()
7481                    })
7482            ) {
7483                // Double-quoted identifiers with Unicode should parse.
7484                let sql = format!("SELECT \"{name}\" FROM \"{name}\"");
7485                let mut p = Parser::from_sql(&sql);
7486                let (stmts, errs) = p.parse_all();
7487                prop_assert!(
7488                    errs.is_empty(),
7489                    "Unicode identifier should parse: {sql}, errors: {errs:?}"
7490                );
7491                prop_assert_eq!(stmts.len(), 1);
7492            }
7493        }
7494
7495        // Property 5: Rejection — various forms of invalid SQL are rejected.
7496        proptest::proptest! {
7497            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(300))]
7498
7499            #[test]
7500            fn test_parser_rejects_incomplete_statements(
7501                kind in prop_oneof![
7502                    Just("SELECT"),
7503                    Just("SELECT FROM"),
7504                    Just("INSERT INTO"),
7505                    Just("DELETE"),
7506                    Just("UPDATE SET"),
7507                    Just("CREATE"),
7508                    Just("CREATE TABLE"),
7509                    Just("DROP"),
7510                ],
7511                trailing in prop::option::of(
7512                    prop::string::string_regex("[;, ]{0,3}").expect("valid regex")
7513                )
7514            ) {
7515                let sql = match trailing {
7516                    Some(t) => format!("{kind}{t}"),
7517                    None => kind.to_string(),
7518                };
7519                let mut p = Parser::from_sql(&sql);
7520                let (stmts, errs) = p.parse_all();
7521                // At least one of: parse errors, or no valid statements produced.
7522                // The parser should not silently produce a valid-looking AST from
7523                // these fundamentally incomplete inputs.
7524                prop_assert!(
7525                    !errs.is_empty() || stmts.is_empty(),
7526                    "Expected rejection of incomplete SQL: {sql}, got {stmts:?}"
7527                );
7528            }
7529        }
7530
7531        // Property 6: Statement count stability — concatenated statements produce
7532        // the right number of parsed statements.
7533        proptest::proptest! {
7534            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
7535
7536            #[test]
7537            fn test_parser_multi_statement_count(
7538                stmts in proptest::collection::vec(arb_statement(), 1..4)
7539            ) {
7540                let sql = stmts.join("; ");
7541                let mut p = Parser::from_sql(&sql);
7542                let (parsed, errors) = p.parse_all();
7543                // If no errors, we should get at least as many statements as we joined.
7544                if errors.is_empty() {
7545                    prop_assert!(
7546                        parsed.len() >= stmts.len(),
7547                        "Expected at least {} statements from: {sql}, got {}",
7548                        stmts.len(),
7549                        parsed.len()
7550                    );
7551                }
7552            }
7553        }
7554    }
7555
7556    // ── bd-1702 repro tests ─────────────────────────────────────────────
7557    // Reserved-word column names in CREATE TABLE (quoted and unquoted).
7558
7559    #[test]
7560    fn create_table_quoted_reserved_word_key() {
7561        // Double-quoted "key" should parse as identifier, not KwKey.
7562        parse_ok(r#"CREATE TABLE "meta" ("key" TEXT, "val" TEXT);"#);
7563    }
7564
7565    #[test]
7566    fn create_table_unquoted_key_column() {
7567        // KEY is a non-reserved keyword — should work unquoted.
7568        parse_ok("CREATE TABLE meta (key TEXT, val TEXT);");
7569    }
7570
7571    #[test]
7572    fn create_table_quoted_order_column() {
7573        // ORDER is reserved — must work when double-quoted.
7574        parse_ok(r#"CREATE TABLE t ("order" INTEGER);"#);
7575    }
7576
7577    #[test]
7578    fn create_table_quoted_select_column() {
7579        // SELECT is reserved — must work when double-quoted.
7580        parse_ok(r#"CREATE TABLE t ("select" TEXT);"#);
7581    }
7582
7583    #[test]
7584    fn select_with_reserved_word_column_key() {
7585        // SELECT using "key" as column name — unquoted.
7586        parse_ok("SELECT key FROM meta;");
7587    }
7588
7589    #[test]
7590    fn select_with_reserved_word_column_value() {
7591        // SELECT using "value" — check if it's a keyword.
7592        parse_ok("SELECT value FROM meta;");
7593    }
7594
7595    #[test]
7596    fn select_with_reserved_word_column_order() {
7597        // ORDER is reserved — quoted should work.
7598        parse_ok(r#"SELECT "order" FROM t;"#);
7599    }
7600
7601    #[test]
7602    fn where_clause_with_reserved_word_column() {
7603        // WHERE referencing a reserved-word column.
7604        parse_ok("UPDATE meta SET val = '2.0' WHERE key = 'version';");
7605    }
7606
7607    #[test]
7608    fn update_set_reserved_word_column() {
7609        // SET reserved-word column.
7610        parse_ok(r#"UPDATE meta SET "key" = 'newkey' WHERE "key" = 'oldkey';"#);
7611    }
7612
7613    #[test]
7614    fn delete_where_reserved_word_column() {
7615        parse_ok("DELETE FROM meta WHERE key = 'version';");
7616    }
7617
7618    #[test]
7619    fn persistence_dump_with_reserved_word_columns() {
7620        // Simulates the exact SQL that build_create_table_sql generates
7621        // for a table that was originally created with reserved-word columns.
7622        let sql = concat!(
7623            r#"CREATE TABLE "meta" ("key" TEXT, "value" TEXT);"#,
7624            "\n",
7625            r#"INSERT INTO "meta" VALUES ('version', '1.0');"#,
7626            "\n",
7627            r#"INSERT INTO "meta" VALUES ('author', 'test');"#,
7628        );
7629        let mut p = Parser::from_sql(sql);
7630        let (stmts, errs) = p.parse_all();
7631        assert!(
7632            errs.is_empty(),
7633            "persistence dump with reserved-word columns should parse cleanly: {errs:?}"
7634        );
7635        assert_eq!(stmts.len(), 3);
7636    }
7637
7638    #[test]
7639    fn select_qualified_column_with_alias() {
7640        // Bug: "a.name as from_name" was being parsed with alias=None and
7641        // col_ref.column="name as" instead of alias=Some("from_name").
7642        let stmt = parse_one("SELECT a.name AS from_name FROM users a");
7643        if let Statement::Select(s) = stmt {
7644            if let SelectCore::Select { columns, .. } = &s.body.select {
7645                assert_eq!(columns.len(), 1);
7646                match &columns[0] {
7647                    ResultColumn::Expr { expr, alias } => {
7648                        // Alias should be captured as "from_name".
7649                        assert_eq!(
7650                            alias.as_deref(),
7651                            Some("from_name"),
7652                            "alias should be 'from_name', got {alias:?}"
7653                        );
7654                        // Expression should be a qualified column ref: a.name
7655                        if let Expr::Column(col_ref, _) = expr {
7656                            assert_eq!(col_ref.table.as_deref(), Some("a"));
7657                            assert_eq!(col_ref.column, "name");
7658                        } else {
7659                            panic!("expected Column expression, got {expr:?}");
7660                        }
7661                    }
7662                    other => panic!("expected Expr variant, got {other:?}"),
7663                }
7664            } else {
7665                panic!("expected Select core");
7666            }
7667        } else {
7668            panic!("expected Select statement");
7669        }
7670    }
7671
7672    #[test]
7673    fn select_qualified_column_with_implicit_alias() {
7674        // Test implicit alias syntax (without AS keyword).
7675        let stmt = parse_one("SELECT a.name from_name FROM users a");
7676        if let Statement::Select(s) = stmt {
7677            if let SelectCore::Select { columns, .. } = &s.body.select {
7678                assert_eq!(columns.len(), 1);
7679                match &columns[0] {
7680                    ResultColumn::Expr { expr, alias } => {
7681                        // Alias should be captured as "from_name" even without AS.
7682                        assert_eq!(
7683                            alias.as_deref(),
7684                            Some("from_name"),
7685                            "implicit alias should be 'from_name', got {alias:?}"
7686                        );
7687                        // Expression should be a qualified column ref: a.name
7688                        if let Expr::Column(col_ref, _) = expr {
7689                            assert_eq!(col_ref.table.as_deref(), Some("a"));
7690                            assert_eq!(col_ref.column, "name");
7691                        } else {
7692                            panic!("expected Column expression, got {expr:?}");
7693                        }
7694                    }
7695                    other => panic!("expected Expr variant, got {other:?}"),
7696                }
7697            } else {
7698                panic!("expected Select core");
7699            }
7700        } else {
7701            panic!("expected Select statement");
7702        }
7703    }
7704
7705    #[test]
7706    fn select_implicit_alias_non_reserved_keyword() {
7707        // 'action' is a non-reserved keyword (TokenKind::KwAction).
7708        // It should be accepted as an implicit alias: SELECT 1 action
7709        let stmt = parse_one("SELECT 1 action");
7710        if let Statement::Select(s) = stmt {
7711            if let SelectCore::Select { columns, .. } = &s.body.select {
7712                if let ResultColumn::Expr { alias, .. } = &columns[0] {
7713                    assert_eq!(
7714                        alias.as_deref(),
7715                        Some("action"),
7716                        "implicit alias 'action' (keyword) failed to parse"
7717                    );
7718                } else {
7719                    unreachable!("expected Expr result column");
7720                }
7721            } else {
7722                unreachable!("expected Select core");
7723            }
7724        } else {
7725            unreachable!("expected Select");
7726        }
7727    }
7728}