Skip to main content

fsqlite_parser/
parser.rs

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