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