Skip to main content

fsqlite_parser/
parser.rs

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