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