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