Skip to main content

fsqlite_parser/
parser.rs

1// bd-2tu6: §10.2 SQL Parser
2//
3// Hand-written statement and DDL grammar. The iterative expression and SELECT
4// state machine lives in expr.rs.
5
6use std::error::Error;
7use std::fmt;
8use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
9
10use fsqlite_ast::{
11    AlterTableAction, AlterTableStatement, Assignment, AssignmentTarget, AttachStatement,
12    BeginStatement, ColumnConstraint, ColumnConstraintKind, ColumnDef, ColumnRef, ConflictAction,
13    CreateIndexStatement, CreateTableBody, CreateTableStatement, CreateTriggerStatement,
14    CreateViewStatement, CreateVirtualTableStatement, DefaultValue, Deferrable,
15    DeferrableInitially, DeleteStatement, DropObjectType, DropStatement, Expr, ForeignKeyAction,
16    ForeignKeyActionType, ForeignKeyClause, ForeignKeyTrigger, GeneratedStorage, IndexHint,
17    IndexedColumn, InsertSource, InsertStatement, JoinKind, JoinType, LimitClause, Literal,
18    NullsOrder, OrderingTerm, PragmaStatement, PragmaValue, QualifiedName, QualifiedTableRef,
19    ResultColumn, RollbackStatement, SelectCore, SelectStatement, SortDirection, Span, Statement,
20    TableConstraint, TableConstraintKind, TimeTravelClause, TimeTravelTarget, TransactionMode,
21    TriggerEvent, TriggerTiming, TypeName, UpdateStatement, UpsertAction, UpsertClause,
22    UpsertTarget, VacuumStatement, ValuesClause, WithClause,
23};
24#[cfg(test)]
25use fsqlite_ast::{
26    CompoundOp, CteMaterialized, Distinctness, FrameBound, FrameExclude, FrameSpec, FrameType,
27    FromClause, JoinClause, JoinConstraint, SelectBody, TableOrSubquery, UnaryOp,
28    ValuesRepresentation, WindowDef, WindowReference, WindowSpec,
29};
30
31#[cfg(test)]
32use crate::expr::{ParsedFrameBound, validate_frame_end, validate_frame_start};
33use crate::lexer::Lexer;
34use crate::token::{Token, TokenKind};
35
36// ---------------------------------------------------------------------------
37// Parse metrics
38// ---------------------------------------------------------------------------
39
40/// Monotonic counter of successfully parsed statements.
41static FSQLITE_PARSE_STATEMENTS_TOTAL: AtomicU64 = AtomicU64::new(0);
42/// Monotonic counter of tokens consumed by successful parse_all() calls.
43static FSQLITE_PARSE_TOKENS_TOTAL: AtomicU64 = AtomicU64::new(0);
44/// Monotonic counter of parse errors encountered by parse_all().
45static FSQLITE_PARSE_ERRORS_TOTAL: AtomicU64 = AtomicU64::new(0);
46/// Whether parse metrics should be collected on the hot path.
47///
48/// These counters are currently used only for diagnostics/tests, so leave
49/// them disabled by default to avoid shared-state bookkeeping on ordinary
50/// parse calls.
51static FSQLITE_PARSE_METRICS_ENABLED: AtomicBool = AtomicBool::new(false);
52
53/// Point-in-time parse metrics snapshot.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
55pub struct ParseMetricsSnapshot {
56    /// Total statements successfully parsed.
57    pub fsqlite_parse_statements_total: u64,
58    /// Total tokens observed by successful parse_all() calls.
59    pub fsqlite_parse_tokens_total: u64,
60    /// Total parse errors observed by parse_all() calls.
61    pub fsqlite_parse_errors_total: u64,
62}
63
64/// Take a point-in-time snapshot of parse metrics.
65#[must_use]
66pub fn parse_metrics_snapshot() -> ParseMetricsSnapshot {
67    ParseMetricsSnapshot {
68        fsqlite_parse_statements_total: FSQLITE_PARSE_STATEMENTS_TOTAL.load(Ordering::Relaxed),
69        fsqlite_parse_tokens_total: FSQLITE_PARSE_TOKENS_TOTAL.load(Ordering::Relaxed),
70        fsqlite_parse_errors_total: FSQLITE_PARSE_ERRORS_TOTAL.load(Ordering::Relaxed),
71    }
72}
73
74/// Enable or disable parse metrics collection on the hot path.
75pub fn set_parse_metrics_enabled(enabled: bool) {
76    FSQLITE_PARSE_METRICS_ENABLED.store(enabled, Ordering::Relaxed);
77}
78
79/// Return whether parse metrics collection is enabled.
80#[must_use]
81pub fn parse_metrics_enabled() -> bool {
82    FSQLITE_PARSE_METRICS_ENABLED.load(Ordering::Relaxed)
83}
84
85/// Reset parse metrics (used by tests/diagnostics).
86pub fn reset_parse_metrics() {
87    FSQLITE_PARSE_STATEMENTS_TOTAL.store(0, Ordering::Relaxed);
88    FSQLITE_PARSE_TOKENS_TOTAL.store(0, Ordering::Relaxed);
89    FSQLITE_PARSE_ERRORS_TOTAL.store(0, Ordering::Relaxed);
90}
91
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93enum DmlParseContext {
94    TopLevel,
95    TriggerBody,
96}
97
98// ---------------------------------------------------------------------------
99// Error type
100// ---------------------------------------------------------------------------
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum ParseErrorKind {
104    Syntax,
105    /// A tokenizer (lexer) error — an unterminated/unrecognized/malformed token.
106    /// Its `message` is already SQLite's stock form (`unrecognized token: "X"`)
107    /// and must be surfaced verbatim, with NO `SQL error at offset N:` prefix.
108    /// bd-parser-syntax-error-format-6w6kp (Part A).
109    Tokenizer,
110    /// A generic "unexpected token" parse error. The connection boundary renders
111    /// this as stock `near "<lexeme>": syntax error`, or `incomplete input` when
112    /// the offending token is end-of-input (empty span at the tail).
113    /// bd-parser-syntax-error-format-6w6kp (Part B).
114    UnexpectedToken,
115    /// A semantic parse-error with a fixed, stock-matching message (e.g.
116    /// `a NATURAL join may not have an ON or USING clause`). The connection
117    /// boundary surfaces `message` VERBATIM, with NO `SQL error at offset N:`
118    /// prefix. bd-parser-syntax-error-format-6w6kp (Part C).
119    Semantic,
120    ExpressionTooDeep {
121        max: u32,
122    },
123    RecursionLimit,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct ParseError {
128    pub kind: ParseErrorKind,
129    pub message: String,
130    pub span: Span,
131    pub line: u32,
132    pub col: u32,
133}
134
135impl ParseError {
136    #[must_use]
137    pub(crate) fn at(message: impl Into<String>, token: Option<&Token>) -> Self {
138        if let Some(t) = token {
139            Self {
140                kind: ParseErrorKind::Syntax,
141                message: message.into(),
142                span: t.span,
143                line: t.line,
144                col: t.col,
145            }
146        } else {
147            Self {
148                kind: ParseErrorKind::Syntax,
149                message: message.into(),
150                span: Span::ZERO,
151                line: 0,
152                col: 0,
153            }
154        }
155    }
156
157    /// A grammar "expected/unexpected" parse error anchored at a SPECIFIC
158    /// token (not the parser's current token). Tagged UnexpectedToken so the
159    /// connection boundary renders it as stock `near "<lexeme>": syntax error`
160    /// (or `incomplete input` when that token is end-of-input / has an empty
161    /// span). bd-parser-syntax-error-format-6w6kp (Part B).
162    #[must_use]
163    pub(crate) fn unexpected_at(message: impl Into<String>, token: Option<&Token>) -> Self {
164        let mut error = Self::at(message, token);
165        error.kind = ParseErrorKind::UnexpectedToken;
166        error
167    }
168
169    /// A semantic parse-error anchored at a SPECIFIC token, carrying a fixed
170    /// message that already matches stock SQLite verbatim (e.g. `unsupported
171    /// frame specification`). Tagged Semantic so the connection boundary
172    /// surfaces `message` VERBATIM, with NO `SQL error at offset N:` prefix.
173    /// bd-parser-errfmt-residuals-3giu1.
174    #[must_use]
175    pub(crate) fn semantic_at(message: impl Into<String>, token: Option<&Token>) -> Self {
176        let mut error = Self::at(message, token);
177        error.kind = ParseErrorKind::Semantic;
178        error
179    }
180
181    #[must_use]
182    pub(crate) fn expression_too_deep(max: u32, token: Option<&Token>) -> Self {
183        let mut error = Self::at(
184            format!("Expression tree is too large (maximum depth {max})"),
185            token,
186        );
187        error.kind = ParseErrorKind::ExpressionTooDeep { max };
188        error
189    }
190
191    #[must_use]
192    fn recursion_limit(token: Option<&Token>) -> Self {
193        // Stock SQLite surfaces a parser-recursion/stack overflow verbatim as
194        // `parser stack overflow` (sqlite3_errmsg; the CLI adds its own
195        // `Parse error near line N:` frame). The connection boundary routes
196        // RecursionLimit to FunctionError so this message is emitted with NO
197        // `SQL error at offset N:` prefix. bd-parser-errfmt-residuals-3giu1.
198        let mut error = Self::at("parser stack overflow", token);
199        error.kind = ParseErrorKind::RecursionLimit;
200        error
201    }
202
203    #[must_use]
204    pub const fn is_expression_too_deep(&self) -> bool {
205        matches!(self.kind, ParseErrorKind::ExpressionTooDeep { .. })
206    }
207}
208
209impl fmt::Display for ParseError {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        write!(f, "{}:{}: {}", self.line, self.col, self.message)
212    }
213}
214
215impl Error for ParseError {}
216
217/// Caller-owned statement parse scratch.
218///
219/// The parser still allocates AST nodes normally, but repeated statement-cache
220/// misses can now keep token/error vectors hot and reset them wholesale at the
221/// statement boundary instead of rebuilding fresh `Vec`s on every parse.
222#[derive(Debug, Default)]
223pub struct StatementParseScratch {
224    tokens: Vec<Token>,
225    errors: Vec<ParseError>,
226    identifier_interner: crate::lexer::IdentifierInterner,
227}
228
229impl StatementParseScratch {
230    /// Clear logical contents while retaining backing allocations for reuse.
231    pub fn reset(&mut self) {
232        self.tokens.clear();
233        self.errors.clear();
234        self.identifier_interner.reset();
235    }
236
237    #[must_use]
238    pub fn token_capacity(&self) -> usize {
239        self.tokens.capacity()
240    }
241
242    #[must_use]
243    pub fn error_capacity(&self) -> usize {
244        self.errors.capacity()
245    }
246
247    #[must_use]
248    pub fn retained_bytes(&self) -> usize {
249        self.tokens
250            .capacity()
251            .saturating_mul(std::mem::size_of::<Token>())
252            .saturating_add(
253                self.errors
254                    .capacity()
255                    .saturating_mul(std::mem::size_of::<ParseError>()),
256            )
257            .saturating_add(self.identifier_interner.retained_bytes())
258    }
259
260    #[cfg(test)]
261    fn identifier_interner_is_empty(&self) -> bool {
262        self.identifier_interner.is_empty()
263    }
264
265    #[cfg(test)]
266    fn identifier_interner_len(&self) -> usize {
267        self.identifier_interner.len()
268    }
269}
270
271// ---------------------------------------------------------------------------
272// Parser
273// ---------------------------------------------------------------------------
274
275/// Maximum expression nesting depth.
276///
277/// Matches C SQLite's default `SQLITE_MAX_EXPR_DEPTH` (1000). C SQLite
278/// allows compile-time override; this constant could be made generic or
279/// builder-configurable if needed.
280pub const MAX_PARSE_DEPTH: u32 = 1000;
281
282/// Native parser-call guard.
283///
284/// This is deliberately distinct from [`MAX_PARSE_DEPTH`]. Statement, SELECT,
285/// and grammar helper frames consume this defensive implementation budget, but
286/// they do not contribute nodes to SQLite's semantic expression-height limit.
287const MAX_NATIVE_PARSE_DEPTH: u32 = 1000;
288
289pub(crate) struct HeightTracked<T> {
290    pub(crate) value: T,
291    pub(crate) height: u32,
292}
293
294pub struct Parser {
295    pub(crate) tokens: Vec<Token>,
296    // Cache PRAGMAs interpret numeric token spelling, not the decoded number.
297    // Only from_sql can retain that spelling; caller-built token streams have
298    // already lost e.g. the distinction between 1e3 and 1000.0.
299    pragma_source_sql: Option<String>,
300    pub(crate) pos: usize,
301    pub(crate) errors: Vec<ParseError>,
302    pub(crate) depth: u32,
303    pub(crate) has_with: bool,
304}
305
306impl Parser {
307    #[must_use]
308    pub fn new(mut tokens: Vec<Token>) -> Self {
309        // The public constructor accepts caller-built token streams. Normalize
310        // every such stream to one physical EOF at the end so an embedded
311        // sentinel cannot silently hide later tokens from parse/tail checks.
312        let terminal_eof = tokens
313            .last()
314            .filter(|token| matches!(token.kind, TokenKind::Eof))
315            .cloned();
316        tokens.retain(|token| !matches!(token.kind, TokenKind::Eof));
317        if let Some(eof) = terminal_eof {
318            tokens.push(eof);
319        } else {
320            let (offset, line, col) = tokens.last().map_or((0, 1, 1), |token| {
321                (
322                    token.span.end,
323                    token.line,
324                    token.col.saturating_add(token.span.len()),
325                )
326            });
327            tokens.push(Token {
328                kind: TokenKind::Eof,
329                span: Span::new(offset, offset),
330                line,
331                col,
332            });
333        }
334        Self {
335            tokens,
336            pragma_source_sql: None,
337            pos: 0,
338            errors: Vec::new(),
339            depth: 0,
340            has_with: false,
341        }
342    }
343
344    pub(crate) fn enter_recursion(&mut self) -> Result<(), ParseError> {
345        if self.depth >= MAX_NATIVE_PARSE_DEPTH {
346            return Err(ParseError::recursion_limit(self.current()));
347        }
348        self.depth += 1;
349        Ok(())
350    }
351
352    pub(crate) fn leave_recursion(&mut self) {
353        self.depth = self.depth.saturating_sub(1);
354    }
355
356    pub(crate) fn checked_cached_parent_height(
357        &self,
358        max_child_height: u32,
359    ) -> Result<u32, ParseError> {
360        let height = max_child_height.saturating_add(1);
361        if height > MAX_PARSE_DEPTH {
362            return Err(ParseError::expression_too_deep(
363                MAX_PARSE_DEPTH,
364                self.current(),
365            ));
366        }
367        Ok(height)
368    }
369
370    pub(crate) fn with_recursion_guard<T>(
371        &mut self,
372        f: impl FnOnce(&mut Self) -> Result<T, ParseError>,
373    ) -> Result<T, ParseError> {
374        self.enter_recursion()?;
375        let result = f(self);
376        self.leave_recursion();
377        result
378    }
379
380    #[must_use]
381    pub fn from_sql(sql: &str) -> Self {
382        let mut parser = Self::new(Lexer::tokenize(sql));
383        parser.preserve_pragma_source(sql);
384        parser
385    }
386
387    fn preserve_pragma_source(&mut self, sql: &str) {
388        // Ordinary SQL does not need a second copy of its source text.
389        if self
390            .tokens
391            .iter()
392            .any(|token| token.kind == TokenKind::KwPragma)
393        {
394            self.pragma_source_sql = Some(sql.to_owned());
395        }
396    }
397
398    pub fn parse_all(&mut self) -> (Vec<Statement>, Vec<ParseError>) {
399        let parse_debug_enabled = tracing::enabled!(target: "fsqlite.parse", tracing::Level::DEBUG);
400        let collect_parse_metrics = parse_metrics_enabled();
401        if collect_parse_metrics {
402            let token_count = u64::try_from(self.tokens.len()).unwrap_or(u64::MAX);
403            FSQLITE_PARSE_TOKENS_TOTAL.fetch_add(token_count, Ordering::Relaxed);
404        }
405        let span = parse_debug_enabled.then(|| {
406            tracing::debug_span!(
407                target: "fsqlite.parse",
408                "parse",
409                ast_node_count = tracing::field::Empty,
410                parse_errors = tracing::field::Empty,
411            )
412        });
413        let _guard = span.as_ref().map(|span| span.enter());
414
415        let mut stmts = Vec::new();
416        while !self.at_eof() {
417            if self.check(&TokenKind::Semicolon) {
418                self.advance();
419                continue;
420            }
421            match self.parse_statement() {
422                Ok(s) => {
423                    if collect_parse_metrics {
424                        FSQLITE_PARSE_STATEMENTS_TOTAL.fetch_add(1, Ordering::Relaxed);
425                    }
426                    stmts.push(s);
427                    if self.at_eof() || self.eat(&TokenKind::Semicolon) {
428                        continue;
429                    }
430
431                    let error = self.err_unexpected(
432                        "unexpected token after end of statement; expected ';' separator",
433                    );
434                    if collect_parse_metrics {
435                        FSQLITE_PARSE_ERRORS_TOTAL.fetch_add(1, Ordering::Relaxed);
436                    }
437                    tracing::warn!(
438                        target: "fsqlite.parse",
439                        error = %error,
440                        "parse recovery: missing statement separator"
441                    );
442                    self.errors.push(error);
443                    self.synchronize();
444                }
445                Err(e) => {
446                    if collect_parse_metrics {
447                        FSQLITE_PARSE_ERRORS_TOTAL.fetch_add(1, Ordering::Relaxed);
448                    }
449                    tracing::warn!(
450                        target: "fsqlite.parse",
451                        error = %e,
452                        "parse recovery: skipping malformed statement"
453                    );
454                    self.errors.push(e);
455                    self.synchronize();
456                }
457            }
458        }
459
460        let errors = std::mem::take(&mut self.errors);
461        if let Some(span) = span.as_ref() {
462            span.record("ast_node_count", stmts.len() as u64);
463            span.record("parse_errors", errors.len() as u64);
464        }
465
466        (stmts, errors)
467    }
468
469    pub fn parse_statement(&mut self) -> Result<Statement, ParseError> {
470        // SQLite's WITH marker is sticky within one statement because it can
471        // change how later VALUES rows are represented. It must not leak into
472        // the next statement parsed from the same token stream.
473        self.has_with = false;
474        self.parse_statement_inner()
475    }
476
477    #[must_use]
478    pub fn errors(&self) -> &[ParseError] {
479        &self.errors
480    }
481
482    // -----------------------------------------------------------------------
483    // Token navigation
484    // -----------------------------------------------------------------------
485
486    pub(crate) fn peek(&self) -> &TokenKind {
487        self.current().map_or(&TokenKind::Eof, |t| &t.kind)
488    }
489
490    pub(crate) fn current(&self) -> Option<&Token> {
491        self.tokens.get(self.pos)
492    }
493
494    pub(crate) fn peek_nth(&self, n: usize) -> &TokenKind {
495        self.tokens
496            .get(self.pos + n)
497            .map_or(&TokenKind::Eof, |t| &t.kind)
498    }
499
500    pub(crate) fn at_eof(&self) -> bool {
501        matches!(self.peek(), TokenKind::Eof)
502    }
503
504    pub(crate) fn advance(&mut self) -> Option<&Token> {
505        let t = self.tokens.get(self.pos);
506        if self.pos < self.tokens.len().saturating_sub(1) {
507            self.pos += 1;
508        }
509        t
510    }
511
512    pub(crate) fn check(&self, kind: &TokenKind) -> bool {
513        std::mem::discriminant(self.peek()) == std::mem::discriminant(kind)
514    }
515
516    pub(crate) fn check_kw(&self, kw: &TokenKind) -> bool {
517        self.peek() == kw
518    }
519
520    pub(crate) fn eat(&mut self, kind: &TokenKind) -> bool {
521        if self.check(kind) {
522            self.advance();
523            true
524        } else {
525            false
526        }
527    }
528
529    pub(crate) fn eat_kw(&mut self, kw: &TokenKind) -> bool {
530        if self.peek() == kw {
531            self.advance();
532            true
533        } else {
534            false
535        }
536    }
537
538    pub(crate) fn expect_kw(&mut self, kw: &TokenKind) -> Result<Span, ParseError> {
539        if self.peek() == kw {
540            let sp = self.current_span();
541            self.advance();
542            Ok(sp)
543        } else {
544            Err(self.err_expected(&format!("{kw:?}")))
545        }
546    }
547
548    pub(crate) fn expect_token(&mut self, kind: &TokenKind) -> Result<Span, ParseError> {
549        if self.check(kind) {
550            let sp = self.current_span();
551            self.advance();
552            Ok(sp)
553        } else {
554            Err(self.err_expected(&format!("{kind:?}")))
555        }
556    }
557
558    pub(crate) fn current_span(&self) -> Span {
559        self.current().map_or(Span::ZERO, |t| t.span)
560    }
561
562    pub(crate) fn err_expected(&self, what: &str) -> ParseError {
563        // Stock's grammar reports every "expected X" grammar failure as
564        // `near "<offending-token>": syntax error` (or `incomplete input` at
565        // EOF). Tag UnexpectedToken so the connection boundary renders it that
566        // way. bd-parser-syntax-error-format-6w6kp (Part B).
567        let mut error = ParseError::at(format!("expected {what}"), self.current());
568        error.kind = ParseErrorKind::UnexpectedToken;
569        error
570    }
571
572    pub(crate) fn err_msg(&self, msg: impl Into<String>) -> ParseError {
573        ParseError::at(msg, self.current())
574    }
575
576    /// A generic unexpected-token parse error anchored at the current token.
577    /// The connection boundary renders it as stock `near "<lexeme>": syntax
578    /// error` (or `incomplete input` at EOF), keyed on the tagged kind.
579    /// bd-parser-syntax-error-format-6w6kp (Part B).
580    pub(crate) fn err_unexpected(&self, msg: impl Into<String>) -> ParseError {
581        let mut error = ParseError::at(msg, self.current());
582        error.kind = ParseErrorKind::UnexpectedToken;
583        error
584    }
585
586    /// A semantic parse-error whose fixed message matches stock SQLite verbatim.
587    /// The connection boundary surfaces it with NO offset prefix.
588    /// bd-parser-syntax-error-format-6w6kp (Part C).
589    pub(crate) fn err_semantic(&self, msg: impl Into<String>) -> ParseError {
590        let mut error = ParseError::at(msg, self.current());
591        error.kind = ParseErrorKind::Semantic;
592        error
593    }
594
595    fn synchronize(&mut self) {
596        loop {
597            match self.peek() {
598                TokenKind::Eof => return,
599                TokenKind::Semicolon => {
600                    self.advance();
601                    return;
602                }
603                k if k.is_statement_start() => return,
604                _ => {
605                    self.advance();
606                }
607            }
608        }
609    }
610
611    fn starts_nested_trigger_definition(&self) -> bool {
612        if !self.check_kw(&TokenKind::KwCreate) {
613            return false;
614        }
615
616        let mut offset = 1_usize;
617        while offset <= 2
618            && matches!(
619                self.peek_nth(offset),
620                TokenKind::KwTemp | TokenKind::KwTemporary | TokenKind::KwUnique
621            )
622        {
623            offset += 1;
624        }
625        self.peek_nth(offset) == &TokenKind::KwTrigger
626    }
627
628    fn recover_trigger_body_after_error(&mut self, statement_start: usize) {
629        // Rewind to the rejected body statement's first token. The enclosing
630        // trigger END is then the first END observed at a statement boundary;
631        // END used as an alias or CASE terminator remains inside its statement.
632        self.pos = statement_start.min(self.tokens.len().saturating_sub(1));
633        let mut at_statement_boundary = true;
634        let mut nested_trigger_header = false;
635        let mut nested_trigger_depth = 0_usize;
636
637        loop {
638            match self.peek() {
639                TokenKind::Eof => return,
640                TokenKind::Semicolon => {
641                    self.advance();
642                    at_statement_boundary = true;
643                    nested_trigger_header = false;
644                }
645                TokenKind::KwBegin if nested_trigger_header => {
646                    nested_trigger_depth = nested_trigger_depth.saturating_add(1);
647                    self.advance();
648                    at_statement_boundary = true;
649                    nested_trigger_header = false;
650                }
651                TokenKind::KwEnd if at_statement_boundary && nested_trigger_depth > 0 => {
652                    nested_trigger_depth -= 1;
653                    self.advance();
654                    let _ = self.eat(&TokenKind::Semicolon);
655                    at_statement_boundary = true;
656                }
657                TokenKind::KwEnd if at_statement_boundary => {
658                    self.advance();
659                    let _ = self.eat(&TokenKind::Semicolon);
660                    return;
661                }
662                _ => {
663                    if at_statement_boundary {
664                        nested_trigger_header = self.starts_nested_trigger_definition();
665                    }
666                    self.advance();
667                    at_statement_boundary = false;
668                }
669            }
670        }
671    }
672
673    // -----------------------------------------------------------------------
674    // Identifiers and names
675    // -----------------------------------------------------------------------
676
677    pub(crate) fn parse_identifier(&mut self) -> Result<String, ParseError> {
678        match self.peek().clone() {
679            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
680                self.advance();
681                Ok(s.to_string())
682            }
683            TokenKind::String(s) => {
684                self.advance();
685                Ok(s)
686            }
687            ref k if starts_post_dot_identifier(k) => {
688                let s = kw_to_str(k);
689                self.advance();
690                Ok(s)
691            }
692            _ => Err(self.err_expected("identifier")),
693        }
694    }
695
696    pub(crate) fn parse_table_star_qualifier(&mut self) -> Result<String, ParseError> {
697        match self.peek().clone() {
698            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
699                self.advance();
700                Ok(s.to_string())
701            }
702            TokenKind::String(s) => {
703                self.advance();
704                Ok(s)
705            }
706            ref k if starts_table_star_qualifier(k) => {
707                let s = kw_to_str(k);
708                self.advance();
709                Ok(s)
710            }
711            _ => Err(self.err_expected("table-star qualifier")),
712        }
713    }
714
715    pub(crate) fn parse_window_name(&mut self) -> Result<String, ParseError> {
716        match self.peek().clone() {
717            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
718                self.advance();
719                Ok(s.to_string())
720            }
721            TokenKind::String(s) => {
722                self.advance();
723                Ok(s)
724            }
725            ref k if starts_bare_window_name(k) => {
726                let s = kw_to_str(k);
727                self.advance();
728                Ok(s)
729            }
730            _ => Err(self.err_expected("window name")),
731        }
732    }
733
734    pub(crate) fn parse_qualified_name(&mut self) -> Result<QualifiedName, ParseError> {
735        let first = self.parse_identifier()?;
736        if self.eat(&TokenKind::Dot) {
737            let second = self.parse_identifier()?;
738            Ok(QualifiedName::qualified(first, second))
739        } else {
740            Ok(QualifiedName::bare(first))
741        }
742    }
743
744    fn parse_dml_target_name(
745        &mut self,
746        context: DmlParseContext,
747    ) -> Result<QualifiedName, ParseError> {
748        let first = self.parse_identifier()?;
749        if self.check(&TokenKind::Dot) {
750            if context == DmlParseContext::TriggerBody {
751                return Err(self.err_semantic(
752                    "qualified table names are not allowed on INSERT, UPDATE, and DELETE \
753                     statements within triggers",
754                ));
755            }
756            self.advance();
757            let second = self.parse_identifier()?;
758            Ok(QualifiedName::qualified(first, second))
759        } else {
760            Ok(QualifiedName::bare(first))
761        }
762    }
763
764    fn parse_qualified_table_ref(
765        &mut self,
766        context: DmlParseContext,
767    ) -> Result<QualifiedTableRef, ParseError> {
768        let name = self.parse_dml_target_name(context)?;
769        let alias = if context == DmlParseContext::TriggerBody {
770            if self.eat_kw(&TokenKind::KwAs) {
771                Some(self.parse_identifier()?)
772            } else {
773                None
774            }
775        } else {
776            self.try_table_alias()?
777        };
778        if context == DmlParseContext::TriggerBody
779            && (self.check_kw(&TokenKind::KwIndexed)
780                || (self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwIndexed))
781        {
782            // Stock emits a distinct verbatim message per clause.
783            // bd-parser-syntax-error-format-6w6kp (Part C).
784            let clause = if self.check_kw(&TokenKind::KwIndexed) {
785                "INDEXED BY"
786            } else {
787                "NOT INDEXED"
788            };
789            return Err(self.err_semantic(format!(
790                "the {clause} clause is not allowed on UPDATE or DELETE statements within triggers"
791            )));
792        }
793        let index_hint = self.parse_index_hint()?;
794        let time_travel = self.parse_time_travel_clause()?;
795        Ok(QualifiedTableRef {
796            name,
797            alias,
798            index_hint,
799            time_travel,
800        })
801    }
802
803    fn parse_alias_name(&mut self) -> Result<String, ParseError> {
804        match self.peek().clone() {
805            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
806                self.advance();
807                Ok(s.to_string())
808            }
809            TokenKind::String(s) => {
810                self.advance();
811                Ok(s)
812            }
813            ref k if starts_explicit_alias_name(k) => {
814                let s = kw_to_str(k);
815                self.advance();
816                Ok(s)
817            }
818            _ => Err(self.err_expected("alias")),
819        }
820    }
821
822    fn starts_window_clause(&self) -> bool {
823        self.check_kw(&TokenKind::KwWindow)
824            && starts_bare_window_name(self.peek_nth(1))
825            && self.peek_nth(2) == &TokenKind::KwAs
826    }
827
828    fn starts_time_travel_clause(&self) -> bool {
829        self.check_kw(&TokenKind::KwFor)
830            && matches!(
831                self.peek_nth(1),
832                TokenKind::Id(name) if name.eq_ignore_ascii_case("SYSTEM_TIME")
833            )
834    }
835
836    pub(crate) fn try_result_alias(&mut self) -> Result<Option<String>, ParseError> {
837        if self.eat_kw(&TokenKind::KwAs) {
838            return Ok(Some(self.parse_alias_name()?));
839        }
840        if self.starts_window_clause() {
841            return Ok(None);
842        }
843        if starts_result_alias(self.peek()) {
844            return Ok(Some(self.parse_alias_name()?));
845        }
846        Ok(None)
847    }
848
849    pub(crate) fn try_table_alias(&mut self) -> Result<Option<String>, ParseError> {
850        if self.eat_kw(&TokenKind::KwAs) {
851            return Ok(Some(self.parse_alias_name()?));
852        }
853        if self.starts_window_clause() || self.starts_time_travel_clause() {
854            return Ok(None);
855        }
856        if starts_table_alias(self.peek()) {
857            return Ok(Some(self.parse_alias_name()?));
858        }
859        Ok(None)
860    }
861
862    pub(crate) fn parse_index_hint(&mut self) -> Result<Option<IndexHint>, ParseError> {
863        if self.eat_kw(&TokenKind::KwIndexed) {
864            self.expect_kw(&TokenKind::KwBy)?;
865            Ok(Some(IndexHint::IndexedBy(self.parse_identifier()?)))
866        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwIndexed {
867            self.advance();
868            self.advance();
869            Ok(Some(IndexHint::NotIndexed))
870        } else {
871            Ok(None)
872        }
873    }
874
875    /// Parse an optional `FOR SYSTEM_TIME AS OF ...` clause (SQL:2011 temporal query).
876    ///
877    /// Grammar:
878    /// ```text
879    /// time_travel_clause ::= FOR SYSTEM_TIME AS OF COMMITSEQ integer
880    ///                      | FOR SYSTEM_TIME AS OF string_literal
881    /// ```
882    pub(crate) fn parse_time_travel_clause(
883        &mut self,
884    ) -> Result<Option<TimeTravelClause>, ParseError> {
885        if !self.check_kw(&TokenKind::KwFor) {
886            return Ok(None);
887        }
888        // Lookahead: FOR must be followed by SYSTEM_TIME (contextual identifier).
889        if !matches!(self.peek_nth(1), TokenKind::Id(s) if s.eq_ignore_ascii_case("SYSTEM_TIME")) {
890            return Ok(None);
891        }
892        self.advance(); // consume FOR
893        self.advance(); // consume SYSTEM_TIME
894        self.expect_kw(&TokenKind::KwAs)?;
895        self.expect_kw(&TokenKind::KwOf)?;
896
897        let target = if self.eat_kw(&TokenKind::KwCommitseq) {
898            match self.peek().clone() {
899                TokenKind::Integer(n) if n >= 0 => {
900                    self.advance();
901                    TimeTravelTarget::CommitSequence(n as u64)
902                }
903                TokenKind::OversizedInt(s) => {
904                    if let Ok(n) = s.parse::<u64>() {
905                        self.advance();
906                        TimeTravelTarget::CommitSequence(n)
907                    } else {
908                        return Err(self.err_expected("non-negative integer after COMMITSEQ"));
909                    }
910                }
911                _ => return Err(self.err_expected("non-negative integer after COMMITSEQ")),
912            }
913        } else {
914            match self.peek().clone() {
915                TokenKind::String(s) => {
916                    self.advance();
917                    TimeTravelTarget::Timestamp(s)
918                }
919                _ => {
920                    return Err(self.err_expected(
921                        "COMMITSEQ <n> or '<timestamp>' after FOR SYSTEM_TIME AS OF",
922                    ));
923                }
924            }
925        };
926
927        Ok(Some(TimeTravelClause { target }))
928    }
929
930    pub(crate) fn parse_comma_sep<T>(
931        &mut self,
932        f: fn(&mut Self) -> Result<T, ParseError>,
933    ) -> Result<Vec<T>, ParseError> {
934        let mut v = Vec::with_capacity(4);
935        v.push(f(self)?);
936        while self.eat(&TokenKind::Comma) {
937            v.push(f(self)?);
938        }
939        Ok(v)
940    }
941
942    // -----------------------------------------------------------------------
943    // Statement dispatch
944    // -----------------------------------------------------------------------
945
946    fn parse_statement_inner(&mut self) -> Result<Statement, ParseError> {
947        self.with_recursion_guard(|parser| match parser.peek().clone() {
948            TokenKind::KwSelect | TokenKind::KwValues => {
949                Ok(Statement::Select(parser.parse_select_stmt(None)?))
950            }
951            TokenKind::KwWith => parser.parse_with_leading(),
952            TokenKind::KwInsert | TokenKind::KwReplace => {
953                parser.parse_insert_stmt(None, DmlParseContext::TopLevel)
954            }
955            TokenKind::KwUpdate => parser.parse_update_stmt(None, DmlParseContext::TopLevel),
956            TokenKind::KwDelete => parser.parse_delete_stmt(None, DmlParseContext::TopLevel),
957            TokenKind::KwCreate => parser.parse_create(),
958            TokenKind::KwDrop => parser.parse_drop(),
959            TokenKind::KwAlter => parser.parse_alter(),
960            TokenKind::KwBegin => parser.parse_begin(),
961            TokenKind::KwCommit | TokenKind::KwEnd => {
962                parser.advance();
963                let _ = parser.eat_kw(&TokenKind::KwTransaction);
964                Ok(Statement::Commit)
965            }
966            TokenKind::KwRollback => parser.parse_rollback(),
967            TokenKind::KwSavepoint => {
968                parser.advance();
969                Ok(Statement::Savepoint(parser.parse_identifier()?))
970            }
971            TokenKind::KwRelease => {
972                parser.advance();
973                let _ = parser.eat_kw(&TokenKind::KwSavepoint);
974                Ok(Statement::Release(parser.parse_identifier()?))
975            }
976            TokenKind::KwAttach => parser.parse_attach(),
977            TokenKind::KwDetach => {
978                parser.advance();
979                let _ = parser.eat_kw(&TokenKind::KwDatabase);
980                Ok(Statement::Detach(parser.parse_identifier()?))
981            }
982            TokenKind::KwPragma => parser.parse_pragma(),
983            TokenKind::KwVacuum => parser.parse_vacuum(),
984            TokenKind::KwReindex => {
985                parser.advance();
986                let name = if !parser.at_eof() && !parser.check(&TokenKind::Semicolon) {
987                    Some(parser.parse_qualified_name()?)
988                } else {
989                    None
990                };
991                Ok(Statement::Reindex(name))
992            }
993            TokenKind::KwAnalyze => {
994                parser.advance();
995                let name = if !parser.at_eof() && !parser.check(&TokenKind::Semicolon) {
996                    Some(parser.parse_qualified_name()?)
997                } else {
998                    None
999                };
1000                Ok(Statement::Analyze(name))
1001            }
1002            TokenKind::KwExplain => parser.parse_explain(),
1003            _ => Err(parser.err_unexpected("unexpected token at start of statement")),
1004        })
1005    }
1006
1007    // -----------------------------------------------------------------------
1008    // WITH ... (SELECT | INSERT | UPDATE | DELETE)
1009    // -----------------------------------------------------------------------
1010
1011    fn parse_with_leading(&mut self) -> Result<Statement, ParseError> {
1012        let with = self.parse_with_clause()?;
1013        match self.peek() {
1014            TokenKind::KwSelect | TokenKind::KwValues => {
1015                Ok(Statement::Select(self.parse_select_stmt(Some(with))?))
1016            }
1017            TokenKind::KwInsert | TokenKind::KwReplace => {
1018                self.parse_insert_stmt(Some(with), DmlParseContext::TopLevel)
1019            }
1020            TokenKind::KwUpdate => self.parse_update_stmt(Some(with), DmlParseContext::TopLevel),
1021            TokenKind::KwDelete => self.parse_delete_stmt(Some(with), DmlParseContext::TopLevel),
1022            _ => Err(self.err_expected("SELECT, INSERT, UPDATE, or DELETE after WITH")),
1023        }
1024    }
1025
1026    pub(crate) fn parse_with_clause(&mut self) -> Result<WithClause, ParseError> {
1027        self.parse_with_clause_machine()
1028    }
1029
1030    // -----------------------------------------------------------------------
1031    // SELECT
1032    // -----------------------------------------------------------------------
1033
1034    pub(crate) fn parse_select_stmt(
1035        &mut self,
1036        with: Option<WithClause>,
1037    ) -> Result<SelectStatement, ParseError> {
1038        self.parse_select_stmt_tracked(with)
1039            .map(|tracked| tracked.value)
1040    }
1041
1042    pub(crate) fn parse_select_stmt_tracked(
1043        &mut self,
1044        with: Option<WithClause>,
1045    ) -> Result<HeightTracked<SelectStatement>, ParseError> {
1046        self.parse_select_tracked_machine(with)
1047    }
1048
1049    #[cfg(test)]
1050    pub(crate) fn parse_select_stmt_inner_tracked(
1051        &mut self,
1052        with: Option<WithClause>,
1053    ) -> Result<HeightTracked<SelectStatement>, ParseError> {
1054        let body = self.parse_select_body_tracked()?;
1055        let mut height = body.height;
1056        let final_core = body
1057            .value
1058            .compounds
1059            .last()
1060            .map_or(&body.value.select, |(_, core)| core);
1061        if matches!(final_core, SelectCore::Values(_))
1062            && matches!(self.peek(), TokenKind::KwOrder | TokenKind::KwLimit)
1063        {
1064            return Err(
1065                self.err_unexpected("ORDER BY / LIMIT clause is not allowed after a VALUES term")
1066            );
1067        }
1068        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1069            self.expect_kw(&TokenKind::KwBy)?;
1070            let parsed = self.parse_comma_sep(Self::parse_ordering_term_tracked)?;
1071            let mut terms = Vec::with_capacity(parsed.len());
1072            for tracked in parsed {
1073                height = height.max(tracked.height);
1074                terms.push(tracked.value);
1075            }
1076            terms
1077        } else {
1078            vec![]
1079        };
1080        let limit = self.parse_limit_tracked()?;
1081        height = height.max(limit.height);
1082        // bd-tp6ia: SQLite's grammar attaches ORDER BY / LIMIT to a SELECT,
1083        // but a final VALUES term has no such slot. This rejects both standalone
1084        // `VALUES (1) ORDER BY 1` and compounds such as
1085        // `SELECT 1 UNION VALUES (2),(3) ORDER BY 1`.
1086        if matches!(final_core, SelectCore::Values(_))
1087            && (!order_by.is_empty() || limit.value.is_some())
1088        {
1089            return Err(
1090                self.err_unexpected("ORDER BY / LIMIT clause is not allowed after a VALUES term")
1091            );
1092        }
1093        Ok(HeightTracked {
1094            value: SelectStatement {
1095                with,
1096                body: body.value,
1097                order_by,
1098                limit: limit.value,
1099            },
1100            height,
1101        })
1102    }
1103
1104    #[cfg(test)]
1105    fn parse_select_body_tracked(&mut self) -> Result<HeightTracked<SelectBody>, ParseError> {
1106        let select = self.parse_select_core_tracked()?;
1107        let mut height = select.height;
1108        let mut compounds = Vec::new();
1109        loop {
1110            let op = if self.eat_kw(&TokenKind::KwUnion) {
1111                if self.eat_kw(&TokenKind::KwAll) {
1112                    CompoundOp::UnionAll
1113                } else {
1114                    CompoundOp::Union
1115                }
1116            } else if self.eat_kw(&TokenKind::KwIntersect) {
1117                CompoundOp::Intersect
1118            } else if self.eat_kw(&TokenKind::KwExcept) {
1119                CompoundOp::Except
1120            } else {
1121                break;
1122            };
1123            let core = self.parse_select_core_tracked()?;
1124            height = height.max(core.height);
1125            compounds.push((op, core.value));
1126        }
1127        Ok(HeightTracked {
1128            value: SelectBody {
1129                select: select.value,
1130                compounds,
1131            },
1132            height,
1133        })
1134    }
1135
1136    #[cfg(test)]
1137    fn parse_select_core_tracked(&mut self) -> Result<HeightTracked<SelectCore>, ParseError> {
1138        if self.eat_kw(&TokenKind::KwValues) {
1139            return self.parse_values_core_tracked();
1140        }
1141        self.expect_kw(&TokenKind::KwSelect)?;
1142        let distinct = if self.eat_kw(&TokenKind::KwDistinct) {
1143            Distinctness::Distinct
1144        } else {
1145            let _ = self.eat_kw(&TokenKind::KwAll);
1146            Distinctness::All
1147        };
1148        let parsed_columns = self.parse_comma_sep(Self::parse_result_column_tracked)?;
1149        let mut height = 0;
1150        let mut columns = Vec::with_capacity(parsed_columns.len());
1151        for tracked in parsed_columns {
1152            height = height.max(tracked.height);
1153            columns.push(tracked.value);
1154        }
1155        let from = if self.eat_kw(&TokenKind::KwFrom) {
1156            Some(self.parse_from_clause()?)
1157        } else {
1158            None
1159        };
1160        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1161            let parsed = self.parse_expr_tracked()?;
1162            height = height.max(parsed.height);
1163            Some(Box::new(parsed.expr))
1164        } else {
1165            None
1166        };
1167        let group_by = if self.eat_kw(&TokenKind::KwGroup) {
1168            self.expect_kw(&TokenKind::KwBy)?;
1169            let parsed = self.parse_comma_sep(Self::parse_expr_tracked)?;
1170            let mut expressions = Vec::with_capacity(parsed.len());
1171            for tracked in parsed {
1172                height = height.max(tracked.height);
1173                expressions.push(tracked.expr);
1174            }
1175            expressions
1176        } else {
1177            vec![]
1178        };
1179        let having = if self.eat_kw(&TokenKind::KwHaving) {
1180            let parsed = self.parse_expr_tracked()?;
1181            height = height.max(parsed.height);
1182            Some(Box::new(parsed.expr))
1183        } else {
1184            None
1185        };
1186        let windows = if self.eat_kw(&TokenKind::KwWindow) {
1187            self.parse_comma_sep(Self::parse_window_def)?
1188        } else {
1189            vec![]
1190        };
1191        Ok(HeightTracked {
1192            value: SelectCore::Select {
1193                distinct,
1194                columns,
1195                from,
1196                where_clause,
1197                group_by,
1198                having,
1199                windows,
1200            },
1201            height,
1202        })
1203    }
1204
1205    fn parse_values_core(&mut self) -> Result<SelectCore, ParseError> {
1206        self.parse_values_core_tracked()
1207            .map(|tracked| tracked.value)
1208    }
1209
1210    fn parse_values_core_tracked(&mut self) -> Result<HeightTracked<SelectCore>, ParseError> {
1211        let mut rows = Vec::new();
1212        let mut height = 0;
1213        let mut force_union_all_from = None;
1214        loop {
1215            self.expect_token(&TokenKind::LeftParen)?;
1216            let parsed = self.parse_comma_sep(Self::parse_expr_tracked)?;
1217            let mut row = Vec::with_capacity(parsed.len());
1218            for tracked in parsed {
1219                height = height.max(tracked.height);
1220                row.push(tracked.expr);
1221            }
1222            self.expect_token(&TokenKind::RightParen)?;
1223            if force_union_all_from.is_none() && self.has_with {
1224                force_union_all_from = Some(rows.len());
1225            }
1226            rows.push(row);
1227            if !self.eat(&TokenKind::Comma) {
1228                break;
1229            }
1230        }
1231        Ok(HeightTracked {
1232            value: SelectCore::Values(ValuesClause::parsed(rows, force_union_all_from)),
1233            height,
1234        })
1235    }
1236
1237    fn parse_result_column_tracked(&mut self) -> Result<HeightTracked<ResultColumn>, ParseError> {
1238        if self.eat(&TokenKind::Star) {
1239            return Ok(HeightTracked {
1240                value: ResultColumn::Star,
1241                height: 0,
1242            });
1243        }
1244        if starts_table_star_qualifier(self.peek()) && self.peek_nth(1) == &TokenKind::Dot {
1245            if self.peek_nth(2) == &TokenKind::Star {
1246                let table = self.parse_table_star_qualifier()?;
1247                self.expect_token(&TokenKind::Dot)?;
1248                self.expect_token(&TokenKind::Star)?;
1249                return Ok(HeightTracked {
1250                    value: ResultColumn::TableStar(QualifiedName::bare(table)),
1251                    height: 0,
1252                });
1253            }
1254            if starts_table_star_qualifier(self.peek_nth(2))
1255                && self.peek_nth(3) == &TokenKind::Dot
1256                && self.peek_nth(4) == &TokenKind::Star
1257            {
1258                let schema = self.parse_table_star_qualifier()?;
1259                self.expect_token(&TokenKind::Dot)?;
1260                let table = self.parse_table_star_qualifier()?;
1261                self.expect_token(&TokenKind::Dot)?;
1262                self.expect_token(&TokenKind::Star)?;
1263                return Ok(HeightTracked {
1264                    value: ResultColumn::TableStar(QualifiedName::qualified(schema, table)),
1265                    height: 0,
1266                });
1267            }
1268        }
1269        let parsed = self.parse_expr_tracked()?;
1270        let alias = self.try_result_alias()?;
1271        Ok(HeightTracked {
1272            value: ResultColumn::Expr {
1273                expr: parsed.expr,
1274                alias,
1275            },
1276            height: parsed.height,
1277        })
1278    }
1279
1280    fn parse_result_column(&mut self) -> Result<ResultColumn, ParseError> {
1281        self.parse_result_column_tracked()
1282            .map(|tracked| tracked.value)
1283    }
1284
1285    // -----------------------------------------------------------------------
1286    // FROM clause & JOINs
1287    // -----------------------------------------------------------------------
1288
1289    #[cfg(test)]
1290    fn parse_from_clause(&mut self) -> Result<FromClause, ParseError> {
1291        let source = self.parse_table_or_subquery()?;
1292        let mut joins = Vec::new();
1293        loop {
1294            if let Some(jt) = self.try_join_type()? {
1295                let table = self.parse_table_or_subquery()?;
1296                let constraint = self.parse_join_constraint()?;
1297                if jt.natural && constraint.is_some() {
1298                    return Err(self.err_msg("a NATURAL join may not have an ON or USING clause"));
1299                }
1300                joins.push(JoinClause {
1301                    join_type: jt,
1302                    table,
1303                    constraint,
1304                });
1305            } else if self.eat(&TokenKind::Comma) {
1306                let table = self.parse_table_or_subquery()?;
1307                let constraint = self.parse_join_constraint()?;
1308                joins.push(JoinClause {
1309                    join_type: JoinType {
1310                        natural: false,
1311                        kind: JoinKind::Cross,
1312                    },
1313                    table,
1314                    constraint,
1315                });
1316            } else {
1317                break;
1318            }
1319        }
1320        Ok(FromClause { source, joins })
1321    }
1322
1323    #[cfg(test)]
1324    fn parse_table_or_subquery(&mut self) -> Result<TableOrSubquery, ParseError> {
1325        self.with_recursion_guard(|parser| parser.parse_table_or_subquery_inner())
1326    }
1327
1328    #[cfg(test)]
1329    fn parse_table_or_subquery_inner(&mut self) -> Result<TableOrSubquery, ParseError> {
1330        if self.check(&TokenKind::LeftParen) {
1331            self.advance();
1332            if matches!(
1333                self.peek(),
1334                TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
1335            ) {
1336                let with = if self.check_kw(&TokenKind::KwWith) {
1337                    Some(self.parse_with_clause()?)
1338                } else {
1339                    None
1340                };
1341                let q = self.parse_select_stmt(with)?;
1342                self.expect_token(&TokenKind::RightParen)?;
1343                let alias = self.try_table_alias()?;
1344                return Ok(TableOrSubquery::Subquery {
1345                    query: Box::new(q),
1346                    alias,
1347                });
1348            }
1349            // Parenthesized join
1350            let fc = self.parse_from_clause()?;
1351            self.expect_token(&TokenKind::RightParen)?;
1352            return Ok(TableOrSubquery::ParenJoin(Box::new(fc)));
1353        }
1354
1355        let name = self.parse_qualified_name()?;
1356
1357        // Table-valued function: name(args)
1358        if self.check(&TokenKind::LeftParen) && name.schema.is_none() {
1359            self.advance();
1360            let args = if self.check(&TokenKind::RightParen) {
1361                vec![]
1362            } else {
1363                self.parse_comma_sep(Self::parse_expr)?
1364            };
1365            self.expect_token(&TokenKind::RightParen)?;
1366            let alias = self.try_table_alias()?;
1367            return Ok(TableOrSubquery::TableFunction {
1368                name: name.name,
1369                args,
1370                alias,
1371            });
1372        }
1373
1374        let alias = self.try_table_alias()?;
1375        let index_hint = self.parse_index_hint()?;
1376        let time_travel = self.parse_time_travel_clause()?;
1377        Ok(TableOrSubquery::Table {
1378            name,
1379            alias,
1380            index_hint,
1381            time_travel,
1382        })
1383    }
1384
1385    pub(crate) fn try_join_type(&mut self) -> Result<Option<JoinType>, ParseError> {
1386        let natural = self.eat_kw(&TokenKind::KwNatural);
1387        let kind = if self.eat_kw(&TokenKind::KwJoin) {
1388            Some(JoinKind::Inner)
1389        } else if self.eat_kw(&TokenKind::KwInner) {
1390            self.expect_kw(&TokenKind::KwJoin)?;
1391            Some(JoinKind::Inner)
1392        } else if self.eat_kw(&TokenKind::KwCross) {
1393            self.expect_kw(&TokenKind::KwJoin)?;
1394            Some(JoinKind::Cross)
1395        } else if self.eat_kw(&TokenKind::KwLeft) {
1396            let _ = self.eat_kw(&TokenKind::KwOuter);
1397            self.expect_kw(&TokenKind::KwJoin)?;
1398            Some(JoinKind::Left)
1399        } else if self.eat_kw(&TokenKind::KwRight) {
1400            let _ = self.eat_kw(&TokenKind::KwOuter);
1401            self.expect_kw(&TokenKind::KwJoin)?;
1402            Some(JoinKind::Right)
1403        } else if self.eat_kw(&TokenKind::KwFull) {
1404            let _ = self.eat_kw(&TokenKind::KwOuter);
1405            self.expect_kw(&TokenKind::KwJoin)?;
1406            Some(JoinKind::Full)
1407        } else {
1408            None
1409        };
1410        match kind {
1411            Some(k) => Ok(Some(JoinType { natural, kind: k })),
1412            None if natural => Err(self.err_expected("JOIN after NATURAL")),
1413            None => Ok(None),
1414        }
1415    }
1416
1417    #[cfg(test)]
1418    fn parse_join_constraint(&mut self) -> Result<Option<JoinConstraint>, ParseError> {
1419        if self.eat_kw(&TokenKind::KwOn) {
1420            Ok(Some(JoinConstraint::On(self.parse_expr()?)))
1421        } else if self.eat_kw(&TokenKind::KwUsing) {
1422            self.expect_token(&TokenKind::LeftParen)?;
1423            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1424            self.expect_token(&TokenKind::RightParen)?;
1425            Ok(Some(JoinConstraint::Using(cols)))
1426        } else {
1427            Ok(None)
1428        }
1429    }
1430
1431    // -----------------------------------------------------------------------
1432    // ORDER BY / LIMIT
1433    // -----------------------------------------------------------------------
1434
1435    pub(crate) fn parse_ordering_term(&mut self) -> Result<OrderingTerm, ParseError> {
1436        self.parse_ordering_term_tracked()
1437            .map(|tracked| tracked.value)
1438    }
1439
1440    fn parse_ordering_term_tracked(&mut self) -> Result<HeightTracked<OrderingTerm>, ParseError> {
1441        let parsed = self.parse_expr_tracked()?;
1442        let direction = if self.eat_kw(&TokenKind::KwAsc) {
1443            Some(SortDirection::Asc)
1444        } else if self.eat_kw(&TokenKind::KwDesc) {
1445            Some(SortDirection::Desc)
1446        } else {
1447            None
1448        };
1449        let nulls = if self.eat_kw(&TokenKind::KwNulls) {
1450            if self.eat_kw(&TokenKind::KwFirst) {
1451                Some(NullsOrder::First)
1452            } else {
1453                self.expect_kw(&TokenKind::KwLast)?;
1454                Some(NullsOrder::Last)
1455            }
1456        } else {
1457            None
1458        };
1459        Ok(HeightTracked {
1460            value: OrderingTerm {
1461                expr: parsed.expr,
1462                direction,
1463                nulls,
1464            },
1465            height: parsed.height,
1466        })
1467    }
1468
1469    pub(crate) fn parse_limit(&mut self) -> Result<Option<LimitClause>, ParseError> {
1470        self.parse_limit_tracked().map(|tracked| tracked.value)
1471    }
1472
1473    fn parse_limit_tracked(&mut self) -> Result<HeightTracked<Option<LimitClause>>, ParseError> {
1474        if !self.eat_kw(&TokenKind::KwLimit) {
1475            return Ok(HeightTracked {
1476                value: None,
1477                height: 0,
1478            });
1479        }
1480        let first = self.parse_expr_tracked()?;
1481        if self.eat_kw(&TokenKind::KwOffset) {
1482            let offset = self.parse_expr_tracked()?;
1483            let height = self.checked_cached_parent_height(first.height.max(offset.height))?;
1484            return Ok(HeightTracked {
1485                value: Some(LimitClause {
1486                    limit: first.expr,
1487                    offset: Some(offset.expr),
1488                }),
1489                height,
1490            });
1491        }
1492
1493        if self.eat(&TokenKind::Comma) {
1494            // LIMIT offset, count — SQLite/MySQL compatibility form.
1495            let second = self.parse_expr_tracked()?;
1496            let height = self.checked_cached_parent_height(first.height.max(second.height))?;
1497            return Ok(HeightTracked {
1498                value: Some(LimitClause {
1499                    limit: second.expr,
1500                    offset: Some(first.expr),
1501                }),
1502                height,
1503            });
1504        }
1505
1506        let height = self.checked_cached_parent_height(first.height)?;
1507        Ok(HeightTracked {
1508            value: Some(LimitClause {
1509                limit: first.expr,
1510                offset: None,
1511            }),
1512            height,
1513        })
1514    }
1515
1516    // -----------------------------------------------------------------------
1517    // RETURNING clause
1518    // -----------------------------------------------------------------------
1519
1520    fn parse_returning(
1521        &mut self,
1522        context: DmlParseContext,
1523        dml_is_insert: bool,
1524    ) -> Result<Vec<ResultColumn>, ParseError> {
1525        if self.check_kw(&TokenKind::KwReturning) && context == DmlParseContext::TriggerBody {
1526            // Stock diverges by statement: a trigger-body INSERT parses RETURNING
1527            // grammatically and then rejects it with a fixed semantic message,
1528            // while the trigger UPDATE/DELETE grammar has no RETURNING slot at
1529            // all, so RETURNING is just an unexpected token there.
1530            // bd-parser-syntax-error-format-6w6kp (Part C).
1531            return Err(if dml_is_insert {
1532                self.err_semantic("cannot use RETURNING in a trigger")
1533            } else {
1534                self.err_unexpected("RETURNING is not allowed in trigger body statements")
1535            });
1536        }
1537        if self.eat_kw(&TokenKind::KwReturning) {
1538            self.parse_comma_sep(Self::parse_result_column)
1539        } else {
1540            Ok(vec![])
1541        }
1542    }
1543
1544    // -----------------------------------------------------------------------
1545    // INSERT
1546    // -----------------------------------------------------------------------
1547
1548    fn parse_insert_stmt(
1549        &mut self,
1550        with: Option<WithClause>,
1551        context: DmlParseContext,
1552    ) -> Result<Statement, ParseError> {
1553        let or_conflict = if self.eat_kw(&TokenKind::KwReplace) {
1554            Some(ConflictAction::Replace)
1555        } else {
1556            self.expect_kw(&TokenKind::KwInsert)?;
1557            if self.eat_kw(&TokenKind::KwOr) {
1558                Some(self.parse_conflict_action()?)
1559            } else {
1560                None
1561            }
1562        };
1563        self.eat_kw(&TokenKind::KwInto);
1564        let table = self.parse_dml_target_name(context)?;
1565        let alias = if self.eat_kw(&TokenKind::KwAs) {
1566            Some(self.parse_identifier()?)
1567        } else {
1568            None
1569        };
1570        let columns = if self.check(&TokenKind::LeftParen)
1571            && !matches!(
1572                self.peek_nth(1),
1573                TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
1574            ) {
1575            self.advance();
1576            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1577            self.expect_token(&TokenKind::RightParen)?;
1578            cols
1579        } else {
1580            vec![]
1581        };
1582        let source = if self.check_kw(&TokenKind::KwDefault)
1583            && context == DmlParseContext::TriggerBody
1584        {
1585            return Err(
1586                self.err_unexpected("DEFAULT VALUES is not allowed in trigger body statements")
1587            );
1588        } else if self.eat_kw(&TokenKind::KwDefault) {
1589            self.expect_kw(&TokenKind::KwValues)?;
1590            InsertSource::DefaultValues
1591        } else if self.eat_kw(&TokenKind::KwValues) {
1592            match self.parse_values_core()? {
1593                SelectCore::Values(rows) => InsertSource::Values(rows.into_rows()),
1594                SelectCore::Select { .. } => unreachable!("parse_values_core must return VALUES"),
1595            }
1596        } else {
1597            let inner_with = if self.check_kw(&TokenKind::KwWith) {
1598                Some(self.parse_with_clause()?)
1599            } else {
1600                None
1601            };
1602            InsertSource::Select(Box::new(self.parse_select_stmt(inner_with)?))
1603        };
1604        let upsert = self.parse_upsert_clauses()?;
1605        let returning = self.parse_returning(context, true)?;
1606        Ok(Statement::Insert(InsertStatement {
1607            with,
1608            or_conflict,
1609            table,
1610            alias,
1611            columns,
1612            source,
1613            upsert,
1614            returning,
1615        }))
1616    }
1617
1618    fn parse_conflict_action(&mut self) -> Result<ConflictAction, ParseError> {
1619        if self.eat_kw(&TokenKind::KwRollback) {
1620            Ok(ConflictAction::Rollback)
1621        } else if self.eat_kw(&TokenKind::KwAbort) {
1622            Ok(ConflictAction::Abort)
1623        } else if self.eat_kw(&TokenKind::KwFail) {
1624            Ok(ConflictAction::Fail)
1625        } else if self.eat_kw(&TokenKind::KwIgnore) {
1626            Ok(ConflictAction::Ignore)
1627        } else if self.eat_kw(&TokenKind::KwReplace) {
1628            Ok(ConflictAction::Replace)
1629        } else {
1630            Err(self.err_expected("conflict action"))
1631        }
1632    }
1633
1634    fn parse_upsert_clauses(&mut self) -> Result<Vec<UpsertClause>, ParseError> {
1635        let mut clauses = Vec::new();
1636        while self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
1637            // SQLite 3.35+: a clause without a conflict target is only valid as
1638            // the final ON CONFLICT clause of the INSERT.
1639            if clauses
1640                .last()
1641                .is_some_and(|clause: &UpsertClause| clause.target.is_none())
1642            {
1643                return Err(self.err_msg(
1644                    "ON CONFLICT clause without a conflict target must be the last ON CONFLICT clause",
1645                ));
1646            }
1647            self.advance(); // ON
1648            self.advance(); // CONFLICT
1649            let target = if self.check(&TokenKind::LeftParen) {
1650                self.advance();
1651                let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1652                self.expect_token(&TokenKind::RightParen)?;
1653                let wh = if self.eat_kw(&TokenKind::KwWhere) {
1654                    Some(self.parse_expr()?)
1655                } else {
1656                    None
1657                };
1658                Some(UpsertTarget {
1659                    columns,
1660                    where_clause: wh,
1661                })
1662            } else {
1663                None
1664            };
1665            self.expect_kw(&TokenKind::KwDo)?;
1666            let action = if self.eat_kw(&TokenKind::KwNothing) {
1667                UpsertAction::Nothing
1668            } else {
1669                self.expect_kw(&TokenKind::KwUpdate)?;
1670                self.expect_kw(&TokenKind::KwSet)?;
1671                let assignments = self.parse_comma_sep(Self::parse_assignment)?;
1672                let wh = if self.eat_kw(&TokenKind::KwWhere) {
1673                    Some(Box::new(self.parse_expr()?))
1674                } else {
1675                    None
1676                };
1677                UpsertAction::Update {
1678                    assignments,
1679                    where_clause: wh,
1680                }
1681            };
1682            clauses.push(UpsertClause { target, action });
1683        }
1684        Ok(clauses)
1685    }
1686
1687    // -----------------------------------------------------------------------
1688    // UPDATE
1689    // -----------------------------------------------------------------------
1690
1691    fn parse_update_stmt(
1692        &mut self,
1693        with: Option<WithClause>,
1694        context: DmlParseContext,
1695    ) -> Result<Statement, ParseError> {
1696        self.expect_kw(&TokenKind::KwUpdate)?;
1697        let or_conflict = if self.eat_kw(&TokenKind::KwOr) {
1698            Some(self.parse_conflict_action()?)
1699        } else {
1700            None
1701        };
1702        let table = self.parse_qualified_table_ref(context)?;
1703        self.expect_kw(&TokenKind::KwSet)?;
1704        let assignments = self.parse_comma_sep(Self::parse_assignment)?;
1705        let from = if self.eat_kw(&TokenKind::KwFrom) {
1706            Some(self.parse_from_clause_machine()?)
1707        } else {
1708            None
1709        };
1710        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1711            Some(self.parse_expr()?)
1712        } else {
1713            None
1714        };
1715        let returning = self.parse_returning(context, false)?;
1716        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwOrder) {
1717            return Err(
1718                self.err_unexpected("ORDER BY is not allowed in trigger body UPDATE statements")
1719            );
1720        }
1721        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1722            self.expect_kw(&TokenKind::KwBy)?;
1723            self.parse_comma_sep(Self::parse_ordering_term)?
1724        } else {
1725            vec![]
1726        };
1727        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwLimit) {
1728            return Err(
1729                self.err_unexpected("LIMIT is not allowed in trigger body UPDATE statements")
1730            );
1731        }
1732        let limit = self.parse_limit()?;
1733        Ok(Statement::Update(UpdateStatement {
1734            with,
1735            or_conflict,
1736            table,
1737            assignments,
1738            from,
1739            where_clause,
1740            returning,
1741            order_by,
1742            limit,
1743        }))
1744    }
1745
1746    fn parse_assignment(&mut self) -> Result<Assignment, ParseError> {
1747        let target = if self.check(&TokenKind::LeftParen) {
1748            self.advance();
1749            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1750            self.expect_token(&TokenKind::RightParen)?;
1751            AssignmentTarget::ColumnList(cols)
1752        } else {
1753            AssignmentTarget::Column(self.parse_identifier()?)
1754        };
1755        self.expect_token(&TokenKind::Eq)?;
1756        let value = self.parse_expr()?;
1757        Ok(Assignment { target, value })
1758    }
1759
1760    // -----------------------------------------------------------------------
1761    // DELETE
1762    // -----------------------------------------------------------------------
1763
1764    fn parse_delete_stmt(
1765        &mut self,
1766        with: Option<WithClause>,
1767        context: DmlParseContext,
1768    ) -> Result<Statement, ParseError> {
1769        self.expect_kw(&TokenKind::KwDelete)?;
1770        self.expect_kw(&TokenKind::KwFrom)?;
1771        let table = self.parse_qualified_table_ref(context)?;
1772        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1773            Some(self.parse_expr()?)
1774        } else {
1775            None
1776        };
1777        let returning = self.parse_returning(context, false)?;
1778        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwOrder) {
1779            return Err(
1780                self.err_unexpected("ORDER BY is not allowed in trigger body DELETE statements")
1781            );
1782        }
1783        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1784            self.expect_kw(&TokenKind::KwBy)?;
1785            self.parse_comma_sep(Self::parse_ordering_term)?
1786        } else {
1787            vec![]
1788        };
1789        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwLimit) {
1790            return Err(
1791                self.err_unexpected("LIMIT is not allowed in trigger body DELETE statements")
1792            );
1793        }
1794        let limit = self.parse_limit()?;
1795        Ok(Statement::Delete(DeleteStatement {
1796            with,
1797            table,
1798            where_clause,
1799            returning,
1800            order_by,
1801            limit,
1802        }))
1803    }
1804
1805    // -----------------------------------------------------------------------
1806    // CREATE
1807    // -----------------------------------------------------------------------
1808
1809    fn parse_create(&mut self) -> Result<Statement, ParseError> {
1810        self.expect_kw(&TokenKind::KwCreate)?;
1811        let temporary = self.eat_kw(&TokenKind::KwTemp) || self.eat_kw(&TokenKind::KwTemporary);
1812        let unique = self.eat_kw(&TokenKind::KwUnique);
1813
1814        if self.eat_kw(&TokenKind::KwTable) {
1815            if unique {
1816                return Err(self.err_expected("INDEX after UNIQUE"));
1817            }
1818            return self.parse_create_table(temporary);
1819        }
1820        if self.eat_kw(&TokenKind::KwIndex) {
1821            if temporary {
1822                return Err(self.err_expected("TABLE, VIEW, or TRIGGER after TEMP"));
1823            }
1824            return self.parse_create_index(unique);
1825        }
1826        if self.eat_kw(&TokenKind::KwView) {
1827            if unique {
1828                return Err(self.err_expected("INDEX after UNIQUE"));
1829            }
1830            return self.parse_create_view(temporary);
1831        }
1832        if self.eat_kw(&TokenKind::KwTrigger) {
1833            if unique {
1834                return Err(self.err_expected("INDEX after UNIQUE"));
1835            }
1836            return self.parse_create_trigger(temporary);
1837        }
1838        if self.eat_kw(&TokenKind::KwVirtual) {
1839            if temporary || unique {
1840                return Err(self.err_expected("TABLE, INDEX, VIEW, or TRIGGER"));
1841            }
1842            self.expect_kw(&TokenKind::KwTable)?;
1843            return self.parse_create_virtual_table();
1844        }
1845        Err(self.err_expected("TABLE, INDEX, VIEW, TRIGGER, or VIRTUAL"))
1846    }
1847
1848    fn parse_if_not_exists(&mut self) -> bool {
1849        if self.check_kw(&TokenKind::KwIf)
1850            && self.peek_nth(1) == &TokenKind::KwNot
1851            && self.peek_nth(2) == &TokenKind::KwExists
1852        {
1853            self.advance();
1854            self.advance();
1855            self.advance();
1856            true
1857        } else {
1858            false
1859        }
1860    }
1861
1862    fn parse_create_table(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1863        let if_not_exists = self.parse_if_not_exists();
1864        let name = self.parse_qualified_name()?;
1865        let body = if self.eat_kw(&TokenKind::KwAs) {
1866            let with = if self.check_kw(&TokenKind::KwWith) {
1867                Some(self.parse_with_clause()?)
1868            } else {
1869                None
1870            };
1871            CreateTableBody::AsSelect(Box::new(self.parse_select_stmt(with)?))
1872        } else {
1873            self.expect_token(&TokenKind::LeftParen)?;
1874            let mut columns = Vec::new();
1875            let mut constraints = Vec::new();
1876            loop {
1877                if self.is_table_constraint_start() {
1878                    constraints.push(self.parse_table_constraint()?);
1879                } else {
1880                    columns.push(self.parse_column_def()?);
1881                }
1882                if !self.eat(&TokenKind::Comma) {
1883                    break;
1884                }
1885            }
1886            self.expect_token(&TokenKind::RightParen)?;
1887            CreateTableBody::Columns {
1888                columns,
1889                constraints,
1890            }
1891        };
1892        let mut without_rowid = false;
1893        let mut strict = false;
1894        // Table options after the closing paren.
1895        if self.check_kw(&TokenKind::KwWithout) || self.check_kw(&TokenKind::KwStrict) {
1896            loop {
1897                if self.check_kw(&TokenKind::KwWithout) {
1898                    self.advance();
1899                    // Expect "ROWID" as an identifier.
1900                    let id = self.parse_identifier()?;
1901                    if !id.eq_ignore_ascii_case("ROWID") {
1902                        return Err(self.err_expected("ROWID after WITHOUT"));
1903                    }
1904                    without_rowid = true;
1905                } else if self.eat_kw(&TokenKind::KwStrict) {
1906                    strict = true;
1907                } else {
1908                    return Err(self.err_expected("table option"));
1909                }
1910                if !self.eat(&TokenKind::Comma) {
1911                    break;
1912                }
1913            }
1914        }
1915        Ok(Statement::CreateTable(CreateTableStatement {
1916            if_not_exists,
1917            temporary,
1918            name,
1919            body,
1920            without_rowid,
1921            strict,
1922        }))
1923    }
1924
1925    fn is_table_constraint_start(&self) -> bool {
1926        matches!(
1927            self.peek(),
1928            TokenKind::KwPrimary | TokenKind::KwUnique | TokenKind::KwCheck | TokenKind::KwForeign
1929        ) || (self.check_kw(&TokenKind::KwConstraint))
1930    }
1931
1932    fn parse_column_def(&mut self) -> Result<ColumnDef, ParseError> {
1933        let name = self.parse_identifier()?;
1934        let type_name = self.try_type_name()?;
1935        let mut constraints = Vec::new();
1936        while let Some(c) = self.try_column_constraint()? {
1937            constraints.push(c);
1938        }
1939        Ok(ColumnDef {
1940            name,
1941            type_name,
1942            constraints,
1943        })
1944    }
1945
1946    fn try_type_name(&mut self) -> Result<Option<TypeName>, ParseError> {
1947        // Type name is one or more identifiers, stopping at known boundaries.
1948        if self.is_column_constraint_start()
1949            || matches!(
1950                self.peek(),
1951                TokenKind::Comma | TokenKind::RightParen | TokenKind::Eof
1952            )
1953        {
1954            return Ok(None);
1955        }
1956        // Collect type name words.
1957        let mut words = Vec::new();
1958        loop {
1959            match self.peek() {
1960                TokenKind::Id(_) | TokenKind::QuotedId(_, _) => {
1961                    words.push(self.parse_identifier()?);
1962                }
1963                k if is_nonreserved_kw(k) => {
1964                    words.push(self.parse_identifier()?);
1965                }
1966                _ => break,
1967            }
1968            if self.is_column_constraint_start()
1969                || matches!(
1970                    self.peek(),
1971                    TokenKind::Comma | TokenKind::RightParen | TokenKind::LeftParen
1972                )
1973            {
1974                break;
1975            }
1976        }
1977        if words.is_empty() {
1978            return Ok(None);
1979        }
1980        let type_name = words.join(" ");
1981        let (arg1, arg2) = if self.eat(&TokenKind::LeftParen) {
1982            let a1 = self.parse_signed_number_str()?;
1983            let a2 = if self.eat(&TokenKind::Comma) {
1984                Some(self.parse_signed_number_str()?)
1985            } else {
1986                None
1987            };
1988            self.expect_token(&TokenKind::RightParen)?;
1989            (Some(a1), a2)
1990        } else {
1991            (None, None)
1992        };
1993        Ok(Some(TypeName {
1994            name: type_name,
1995            arg1,
1996            arg2,
1997        }))
1998    }
1999
2000    fn parse_signed_number_str(&mut self) -> Result<String, ParseError> {
2001        let neg = self.eat(&TokenKind::Minus);
2002        let plus = if neg {
2003            false
2004        } else {
2005            self.eat(&TokenKind::Plus)
2006        };
2007        let _ = plus; // just consume
2008        match self.peek().clone() {
2009            TokenKind::Integer(n) => {
2010                self.advance();
2011                Ok(if neg { format!("-{n}") } else { n.to_string() })
2012            }
2013            TokenKind::OversizedInt(s) => {
2014                self.advance();
2015                Ok(if neg { format!("-{s}") } else { s.clone() })
2016            }
2017            TokenKind::Float(f) => {
2018                self.advance();
2019                Ok(if neg { format!("-{f}") } else { f.to_string() })
2020            }
2021            _ => Err(self.err_expected("number")),
2022        }
2023    }
2024
2025    fn is_column_constraint_start(&self) -> bool {
2026        matches!(
2027            self.peek(),
2028            TokenKind::KwPrimary
2029                | TokenKind::KwNot
2030                | TokenKind::KwNull
2031                | TokenKind::KwUnique
2032                | TokenKind::KwCheck
2033                | TokenKind::KwDefault
2034                | TokenKind::KwCollate
2035                | TokenKind::KwReferences
2036                | TokenKind::KwGenerated
2037                | TokenKind::KwConstraint
2038                | TokenKind::KwAs
2039        )
2040    }
2041
2042    fn try_column_constraint(&mut self) -> Result<Option<ColumnConstraint>, ParseError> {
2043        let name = if self.eat_kw(&TokenKind::KwConstraint) {
2044            Some(self.parse_identifier()?)
2045        } else {
2046            None
2047        };
2048        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
2049            self.expect_kw(&TokenKind::KwKey)?;
2050            let direction = if self.eat_kw(&TokenKind::KwAsc) {
2051                Some(SortDirection::Asc)
2052            } else if self.eat_kw(&TokenKind::KwDesc) {
2053                Some(SortDirection::Desc)
2054            } else {
2055                None
2056            };
2057            let conflict = self.parse_on_conflict()?;
2058            let autoincrement = self.eat_kw(&TokenKind::KwAutoincrement);
2059            ColumnConstraintKind::PrimaryKey {
2060                direction,
2061                conflict,
2062                autoincrement,
2063            }
2064        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwNull {
2065            self.advance();
2066            self.advance();
2067            let conflict = self.parse_on_conflict()?;
2068            ColumnConstraintKind::NotNull { conflict }
2069        } else if self.eat_kw(&TokenKind::KwNull) {
2070            ColumnConstraintKind::Null
2071        } else if self.eat_kw(&TokenKind::KwUnique) {
2072            let conflict = self.parse_on_conflict()?;
2073            ColumnConstraintKind::Unique { conflict }
2074        } else if self.eat_kw(&TokenKind::KwCheck) {
2075            self.expect_token(&TokenKind::LeftParen)?;
2076            let expr = self.parse_expr()?;
2077            self.expect_token(&TokenKind::RightParen)?;
2078            ColumnConstraintKind::Check(expr)
2079        } else if self.eat_kw(&TokenKind::KwDefault) {
2080            if self.eat(&TokenKind::LeftParen) {
2081                let expr = self.parse_expr()?;
2082                self.expect_token(&TokenKind::RightParen)?;
2083                ColumnConstraintKind::Default(DefaultValue::ParenExpr(expr))
2084            } else if let TokenKind::Id(name) = self.peek().clone() {
2085                // SQLite quirk: a BARE (unparenthesized, unquoted) identifier after
2086                // DEFAULT is treated as a STRING LITERAL, not a column reference — a
2087                // column DEFAULT cannot reference other columns. `DEFAULT abc` yields
2088                // the string 'abc'; `DEFAULT (abc)` stays an expression and is later
2089                // rejected as non-constant. Keyword constants (TRUE/FALSE/NULL/
2090                // CURRENT_*) lex as keywords (not Id), so they fall through to the
2091                // normal expression parse below and keep their literal meaning.
2092                let span = self.current_span();
2093                self.advance();
2094                ColumnConstraintKind::Default(DefaultValue::Expr(Expr::Literal(
2095                    Literal::String(name.to_string()),
2096                    span,
2097                )))
2098            } else {
2099                let expr = self.parse_expr()?;
2100                ColumnConstraintKind::Default(DefaultValue::Expr(expr))
2101            }
2102        } else if self.eat_kw(&TokenKind::KwCollate) {
2103            ColumnConstraintKind::Collate(self.parse_identifier()?)
2104        } else if self.eat_kw(&TokenKind::KwReferences) {
2105            ColumnConstraintKind::ForeignKey(self.parse_fk_clause()?)
2106        } else if self.eat_kw(&TokenKind::KwGenerated) || self.eat_kw(&TokenKind::KwAs) {
2107            if self.tokens[self.pos.saturating_sub(1)].kind == TokenKind::KwGenerated {
2108                let _ = self.eat_kw(&TokenKind::KwAlways);
2109                let _ = self.eat_kw(&TokenKind::KwAs);
2110            }
2111            self.expect_token(&TokenKind::LeftParen)?;
2112            let expr = self.parse_expr()?;
2113            self.expect_token(&TokenKind::RightParen)?;
2114            let storage = if self.eat_kw(&TokenKind::KwStored) {
2115                Some(GeneratedStorage::Stored)
2116            } else if self.eat_kw(&TokenKind::KwVirtual) {
2117                Some(GeneratedStorage::Virtual)
2118            } else {
2119                None
2120            };
2121            ColumnConstraintKind::Generated { expr, storage }
2122        } else if name.is_some() {
2123            return Err(self.err_expected("constraint kind after CONSTRAINT name"));
2124        } else {
2125            return Ok(None);
2126        };
2127        Ok(Some(ColumnConstraint { name, kind }))
2128    }
2129
2130    fn parse_on_conflict(&mut self) -> Result<Option<ConflictAction>, ParseError> {
2131        if self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
2132            self.advance();
2133            self.advance();
2134            Ok(Some(self.parse_conflict_action()?))
2135        } else {
2136            Ok(None)
2137        }
2138    }
2139
2140    fn parse_fk_clause(&mut self) -> Result<ForeignKeyClause, ParseError> {
2141        let table = self.parse_identifier()?;
2142        let columns = if self.eat(&TokenKind::LeftParen) {
2143            let cols = self.parse_comma_sep(Self::parse_identifier)?;
2144            self.expect_token(&TokenKind::RightParen)?;
2145            cols
2146        } else {
2147            vec![]
2148        };
2149        let mut actions = Vec::new();
2150        let mut deferrable = None;
2151        loop {
2152            if self.check_kw(&TokenKind::KwOn) {
2153                self.advance();
2154                let trigger = if self.eat_kw(&TokenKind::KwDelete) {
2155                    ForeignKeyTrigger::OnDelete
2156                } else {
2157                    self.expect_kw(&TokenKind::KwUpdate)?;
2158                    ForeignKeyTrigger::OnUpdate
2159                };
2160                let action = self.parse_fk_action_type()?;
2161                actions.push(ForeignKeyAction { trigger, action });
2162            } else if self.check_kw(&TokenKind::KwNot) || self.check_kw(&TokenKind::KwDeferrable) {
2163                let not = self.eat_kw(&TokenKind::KwNot);
2164                self.expect_kw(&TokenKind::KwDeferrable)?;
2165                let initially = if self.eat_kw(&TokenKind::KwInitially) {
2166                    if self.eat_kw(&TokenKind::KwDeferred) {
2167                        Some(DeferrableInitially::Deferred)
2168                    } else {
2169                        self.expect_kw(&TokenKind::KwImmediate)?;
2170                        Some(DeferrableInitially::Immediate)
2171                    }
2172                } else {
2173                    None
2174                };
2175                deferrable = Some(Deferrable { not, initially });
2176            } else if self.eat_kw(&TokenKind::KwMatch) {
2177                // MATCH name — parsed but ignored per SQLite behavior.
2178                self.parse_identifier()?;
2179            } else {
2180                break;
2181            }
2182        }
2183        Ok(ForeignKeyClause {
2184            table,
2185            columns,
2186            actions,
2187            deferrable,
2188        })
2189    }
2190
2191    fn parse_fk_action_type(&mut self) -> Result<ForeignKeyActionType, ParseError> {
2192        if self.eat_kw(&TokenKind::KwSet) {
2193            if self.eat_kw(&TokenKind::KwNull) {
2194                Ok(ForeignKeyActionType::SetNull)
2195            } else {
2196                self.expect_kw(&TokenKind::KwDefault)?;
2197                Ok(ForeignKeyActionType::SetDefault)
2198            }
2199        } else if self.eat_kw(&TokenKind::KwCascade) {
2200            Ok(ForeignKeyActionType::Cascade)
2201        } else if self.eat_kw(&TokenKind::KwRestrict) {
2202            Ok(ForeignKeyActionType::Restrict)
2203        } else if self.check_kw(&TokenKind::KwNo) {
2204            self.advance();
2205            let id = self.parse_identifier()?;
2206            if !id.eq_ignore_ascii_case("ACTION") {
2207                return Err(self.err_expected("ACTION after NO"));
2208            }
2209            Ok(ForeignKeyActionType::NoAction)
2210        } else {
2211            Err(self.err_expected("foreign key action"))
2212        }
2213    }
2214
2215    fn parse_table_constraint(&mut self) -> Result<TableConstraint, ParseError> {
2216        let name = if self.eat_kw(&TokenKind::KwConstraint) {
2217            Some(self.parse_identifier()?)
2218        } else {
2219            None
2220        };
2221        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
2222            self.expect_kw(&TokenKind::KwKey)?;
2223            self.expect_token(&TokenKind::LeftParen)?;
2224            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
2225            self.expect_token(&TokenKind::RightParen)?;
2226            let conflict = self.parse_on_conflict()?;
2227            TableConstraintKind::PrimaryKey { columns, conflict }
2228        } else if self.eat_kw(&TokenKind::KwUnique) {
2229            self.expect_token(&TokenKind::LeftParen)?;
2230            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
2231            self.expect_token(&TokenKind::RightParen)?;
2232            let conflict = self.parse_on_conflict()?;
2233            TableConstraintKind::Unique { columns, conflict }
2234        } else if self.eat_kw(&TokenKind::KwCheck) {
2235            self.expect_token(&TokenKind::LeftParen)?;
2236            let expr = self.parse_expr()?;
2237            self.expect_token(&TokenKind::RightParen)?;
2238            // A table CHECK constraint may carry a trailing conflict clause
2239            // (`CHECK(expr) ON CONFLICT <action>`), accepted by stock sqlite3.
2240            // Consume it for parse parity so a stock-created schema stays
2241            // loadable on reopen (mirrors PRIMARY KEY / UNIQUE above).
2242            let _conflict = self.parse_on_conflict()?;
2243            TableConstraintKind::Check(expr)
2244        } else if self.eat_kw(&TokenKind::KwForeign) {
2245            self.expect_kw(&TokenKind::KwKey)?;
2246            self.expect_token(&TokenKind::LeftParen)?;
2247            let columns = self.parse_comma_sep(Self::parse_identifier)?;
2248            self.expect_token(&TokenKind::RightParen)?;
2249            self.expect_kw(&TokenKind::KwReferences)?;
2250            let clause = self.parse_fk_clause()?;
2251            TableConstraintKind::ForeignKey { columns, clause }
2252        } else {
2253            return Err(self.err_expected("table constraint"));
2254        };
2255        Ok(TableConstraint { name, kind })
2256    }
2257
2258    fn parse_indexed_column(&mut self) -> Result<IndexedColumn, ParseError> {
2259        let expr = self.parse_expr()?;
2260        let collation = if self.eat_kw(&TokenKind::KwCollate) {
2261            Some(self.parse_identifier()?)
2262        } else {
2263            None
2264        };
2265        let direction = if self.eat_kw(&TokenKind::KwAsc) {
2266            Some(SortDirection::Asc)
2267        } else if self.eat_kw(&TokenKind::KwDesc) {
2268            Some(SortDirection::Desc)
2269        } else {
2270            None
2271        };
2272        Ok(IndexedColumn {
2273            expr,
2274            collation,
2275            direction,
2276        })
2277    }
2278
2279    fn parse_create_index(&mut self, unique: bool) -> Result<Statement, ParseError> {
2280        let if_not_exists = self.parse_if_not_exists();
2281        let name = self.parse_qualified_name()?;
2282        self.expect_kw(&TokenKind::KwOn)?;
2283        let table = self.parse_identifier()?;
2284        self.expect_token(&TokenKind::LeftParen)?;
2285        let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
2286        self.expect_token(&TokenKind::RightParen)?;
2287        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
2288            Some(self.parse_expr()?)
2289        } else {
2290            None
2291        };
2292        Ok(Statement::CreateIndex(CreateIndexStatement {
2293            unique,
2294            if_not_exists,
2295            name,
2296            table,
2297            columns,
2298            where_clause,
2299        }))
2300    }
2301
2302    fn parse_create_view(&mut self, temporary: bool) -> Result<Statement, ParseError> {
2303        let if_not_exists = self.parse_if_not_exists();
2304        let name = self.parse_qualified_name()?;
2305        let columns = if self.check(&TokenKind::LeftParen) {
2306            self.advance();
2307            let cols = self.parse_comma_sep(Self::parse_identifier)?;
2308            self.expect_token(&TokenKind::RightParen)?;
2309            cols
2310        } else {
2311            vec![]
2312        };
2313        self.expect_kw(&TokenKind::KwAs)?;
2314        let with = if self.check_kw(&TokenKind::KwWith) {
2315            Some(self.parse_with_clause()?)
2316        } else {
2317            None
2318        };
2319        let query = self.parse_select_stmt(with)?;
2320        Ok(Statement::CreateView(CreateViewStatement {
2321            if_not_exists,
2322            temporary,
2323            name,
2324            columns,
2325            query,
2326        }))
2327    }
2328
2329    fn parse_trigger_body_statement_inner(&mut self) -> Result<Statement, ParseError> {
2330        match self.peek().clone() {
2331            TokenKind::KwSelect | TokenKind::KwValues => {
2332                Ok(Statement::Select(self.parse_select_stmt(None)?))
2333            }
2334            TokenKind::KwWith => {
2335                // Stock SQLite (3.46.1, verified) only allows a WITH (CTE) clause
2336                // ahead of SELECT/VALUES in a trigger body; `WITH ... INSERT/
2337                // UPDATE/DELETE` is a syntax error ("near INSERT"). Keep parity.
2338                let with = self.parse_with_clause()?;
2339                if matches!(self.peek(), TokenKind::KwSelect | TokenKind::KwValues) {
2340                    Ok(Statement::Select(self.parse_select_stmt(Some(with))?))
2341                } else {
2342                    Err(self.err_expected("SELECT or VALUES after WITH in a trigger body"))
2343                }
2344            }
2345            TokenKind::KwInsert | TokenKind::KwReplace => {
2346                self.parse_insert_stmt(None, DmlParseContext::TriggerBody)
2347            }
2348            TokenKind::KwUpdate => self.parse_update_stmt(None, DmlParseContext::TriggerBody),
2349            TokenKind::KwDelete => self.parse_delete_stmt(None, DmlParseContext::TriggerBody),
2350            _ => Err(self.err_unexpected(
2351                "trigger body statement must be SELECT, INSERT, UPDATE, or DELETE",
2352            )),
2353        }
2354    }
2355
2356    fn parse_trigger_body_statement(&mut self) -> Result<Statement, ParseError> {
2357        self.parse_trigger_body_statement_inner()
2358    }
2359
2360    fn parse_create_trigger(&mut self, temporary: bool) -> Result<Statement, ParseError> {
2361        let if_not_exists = self.parse_if_not_exists();
2362        let name = self.parse_qualified_name()?;
2363        let timing = if self.eat_kw(&TokenKind::KwBefore) {
2364            TriggerTiming::Before
2365        } else if self.eat_kw(&TokenKind::KwAfter) {
2366            TriggerTiming::After
2367        } else if self.eat_kw(&TokenKind::KwInstead) {
2368            self.expect_kw(&TokenKind::KwOf)?;
2369            TriggerTiming::InsteadOf
2370        } else {
2371            TriggerTiming::Before // default
2372        };
2373        let event = if self.eat_kw(&TokenKind::KwInsert) {
2374            TriggerEvent::Insert
2375        } else if self.eat_kw(&TokenKind::KwDelete) {
2376            TriggerEvent::Delete
2377        } else {
2378            self.expect_kw(&TokenKind::KwUpdate)?;
2379            let cols = if self.eat_kw(&TokenKind::KwOf) {
2380                self.parse_comma_sep(Self::parse_identifier)?
2381            } else {
2382                vec![]
2383            };
2384            TriggerEvent::Update(cols)
2385        };
2386        self.expect_kw(&TokenKind::KwOn)?;
2387        let table = self.parse_identifier()?;
2388        let for_each_row = if self.eat_kw(&TokenKind::KwFor) {
2389            self.expect_kw(&TokenKind::KwEach)?;
2390            self.expect_kw(&TokenKind::KwRow)?;
2391            true
2392        } else {
2393            false
2394        };
2395        let when = if self.eat_kw(&TokenKind::KwWhen) {
2396            Some(self.parse_expr()?)
2397        } else {
2398            None
2399        };
2400        self.expect_kw(&TokenKind::KwBegin)?;
2401        let mut body = Vec::new();
2402        if self.check_kw(&TokenKind::KwEnd) {
2403            let error = self.err_unexpected("trigger body must contain at least one statement");
2404            self.recover_trigger_body_after_error(self.pos);
2405            return Err(error);
2406        }
2407        loop {
2408            if self.check_kw(&TokenKind::KwEnd) {
2409                break;
2410            }
2411            let statement_start = self.pos;
2412            let stmt = match self.parse_trigger_body_statement() {
2413                Ok(stmt) => stmt,
2414                Err(err) => {
2415                    self.recover_trigger_body_after_error(statement_start);
2416                    return Err(err);
2417                }
2418            };
2419            body.push(stmt);
2420            if !self.eat(&TokenKind::Semicolon) {
2421                let error = self.err_expected("';' after trigger body statement");
2422                // The statement parsed successfully, so the current token is
2423                // already a statement boundary. In particular, UPDATE/INSERT/
2424                // DELETE leave the enclosing trigger END current here.
2425                self.recover_trigger_body_after_error(self.pos);
2426                return Err(error);
2427            }
2428        }
2429        self.expect_kw(&TokenKind::KwEnd)?;
2430        Ok(Statement::CreateTrigger(CreateTriggerStatement {
2431            if_not_exists,
2432            temporary,
2433            name,
2434            timing,
2435            event,
2436            table,
2437            for_each_row,
2438            when,
2439            body,
2440        }))
2441    }
2442
2443    fn parse_create_virtual_table(&mut self) -> Result<Statement, ParseError> {
2444        let if_not_exists = self.parse_if_not_exists();
2445        let name = self.parse_qualified_name()?;
2446        self.expect_kw(&TokenKind::KwUsing)?;
2447        let module = self.parse_identifier()?;
2448        let args = if self.eat(&TokenKind::LeftParen) {
2449            if self.check(&TokenKind::RightParen) {
2450                self.advance();
2451                vec![]
2452            } else {
2453                // Virtual table args are opaque; collect tokens as strings until matching rparen.
2454                let mut args = Vec::new();
2455                let mut depth = 0i32;
2456                let mut current_arg = String::new();
2457                loop {
2458                    match self.peek() {
2459                        TokenKind::RightParen if depth == 0 => {
2460                            self.advance();
2461                            args.push(current_arg.trim().to_owned());
2462                            break;
2463                        }
2464                        TokenKind::LeftParen => {
2465                            depth += 1;
2466                            current_arg.push('(');
2467                            self.advance();
2468                        }
2469                        TokenKind::RightParen => {
2470                            depth -= 1;
2471                            current_arg.push(')');
2472                            self.advance();
2473                        }
2474                        TokenKind::Comma if depth == 0 => {
2475                            args.push(current_arg.trim().to_owned());
2476                            current_arg = String::new();
2477                            self.advance();
2478                        }
2479                        TokenKind::Eof => {
2480                            return Err(self.err_expected("closing parenthesis"));
2481                        }
2482                        _ => {
2483                            // Reconstruct token text from token kind.
2484                            let t = self
2485                                .current()
2486                                .ok_or_else(|| self.err_expected("virtual table argument token"))?;
2487                            let text = t.kind.to_sql();
2488                            if !current_arg.is_empty()
2489                                && !current_arg.ends_with(' ')
2490                                && !text.is_empty()
2491                            {
2492                                current_arg.push(' ');
2493                            }
2494                            current_arg.push_str(&text);
2495                            self.advance();
2496                        }
2497                    }
2498                }
2499                args
2500            }
2501        } else {
2502            vec![]
2503        };
2504        Ok(Statement::CreateVirtualTable(CreateVirtualTableStatement {
2505            if_not_exists,
2506            name,
2507            module,
2508            args,
2509        }))
2510    }
2511
2512    // -----------------------------------------------------------------------
2513    // DROP
2514    // -----------------------------------------------------------------------
2515
2516    fn parse_drop(&mut self) -> Result<Statement, ParseError> {
2517        self.expect_kw(&TokenKind::KwDrop)?;
2518        let object_type = if self.eat_kw(&TokenKind::KwTable) {
2519            DropObjectType::Table
2520        } else if self.eat_kw(&TokenKind::KwView) {
2521            DropObjectType::View
2522        } else if self.eat_kw(&TokenKind::KwIndex) {
2523            DropObjectType::Index
2524        } else if self.eat_kw(&TokenKind::KwTrigger) {
2525            DropObjectType::Trigger
2526        } else {
2527            return Err(self.err_expected("TABLE, VIEW, INDEX, or TRIGGER"));
2528        };
2529        let if_exists =
2530            if self.check_kw(&TokenKind::KwIf) && self.peek_nth(1) == &TokenKind::KwExists {
2531                self.advance();
2532                self.advance();
2533                true
2534            } else {
2535                false
2536            };
2537        let name = self.parse_qualified_name()?;
2538        Ok(Statement::Drop(DropStatement {
2539            object_type,
2540            if_exists,
2541            name,
2542        }))
2543    }
2544
2545    // -----------------------------------------------------------------------
2546    // ALTER TABLE
2547    // -----------------------------------------------------------------------
2548
2549    fn parse_alter(&mut self) -> Result<Statement, ParseError> {
2550        self.expect_kw(&TokenKind::KwAlter)?;
2551        self.expect_kw(&TokenKind::KwTable)?;
2552        let table = self.parse_qualified_name()?;
2553        let action = if self.eat_kw(&TokenKind::KwRename) {
2554            if self.eat_kw(&TokenKind::KwTo) {
2555                AlterTableAction::RenameTo(self.parse_identifier()?)
2556            } else {
2557                let _ = self.eat_kw(&TokenKind::KwColumn);
2558                let old = self.parse_identifier()?;
2559                self.expect_kw(&TokenKind::KwTo)?;
2560                let new = self.parse_identifier()?;
2561                AlterTableAction::RenameColumn { old, new }
2562            }
2563        } else if self.eat_kw(&TokenKind::KwAdd) {
2564            let _ = self.eat_kw(&TokenKind::KwColumn);
2565            AlterTableAction::AddColumn(self.parse_column_def()?)
2566        } else if self.eat_kw(&TokenKind::KwDrop) {
2567            let _ = self.eat_kw(&TokenKind::KwColumn);
2568            AlterTableAction::DropColumn(self.parse_identifier()?)
2569        } else {
2570            return Err(self.err_expected("RENAME, ADD, or DROP"));
2571        };
2572        Ok(Statement::AlterTable(AlterTableStatement { table, action }))
2573    }
2574
2575    // -----------------------------------------------------------------------
2576    // Transaction control
2577    // -----------------------------------------------------------------------
2578
2579    fn parse_begin(&mut self) -> Result<Statement, ParseError> {
2580        self.expect_kw(&TokenKind::KwBegin)?;
2581        let mode = if self.eat_kw(&TokenKind::KwDeferred) {
2582            Some(TransactionMode::Deferred)
2583        } else if self.eat_kw(&TokenKind::KwImmediate) {
2584            Some(TransactionMode::Immediate)
2585        } else if self.eat_kw(&TokenKind::KwExclusive) {
2586            Some(TransactionMode::Exclusive)
2587        } else if self.eat_kw(&TokenKind::KwConcurrent) {
2588            Some(TransactionMode::Concurrent)
2589        } else {
2590            None
2591        };
2592        // Optional TRANSACTION keyword.
2593        let _ = self.eat_kw(&TokenKind::KwTransaction);
2594        Ok(Statement::Begin(BeginStatement { mode }))
2595    }
2596
2597    fn parse_rollback(&mut self) -> Result<Statement, ParseError> {
2598        self.expect_kw(&TokenKind::KwRollback)?;
2599        let _ = self.eat_kw(&TokenKind::KwTransaction);
2600        let to_savepoint = if self.eat_kw(&TokenKind::KwTo) {
2601            let _ = self.eat_kw(&TokenKind::KwSavepoint);
2602            Some(self.parse_identifier()?)
2603        } else {
2604            None
2605        };
2606        Ok(Statement::Rollback(RollbackStatement { to_savepoint }))
2607    }
2608
2609    // -----------------------------------------------------------------------
2610    // ATTACH / PRAGMA / VACUUM / EXPLAIN
2611    // -----------------------------------------------------------------------
2612
2613    fn parse_attach(&mut self) -> Result<Statement, ParseError> {
2614        self.expect_kw(&TokenKind::KwAttach)?;
2615        let _ = self.eat_kw(&TokenKind::KwDatabase);
2616        let expr = self.parse_expr()?;
2617        self.expect_kw(&TokenKind::KwAs)?;
2618        let schema = self.parse_identifier()?;
2619        Ok(Statement::Attach(AttachStatement { expr, schema }))
2620    }
2621
2622    fn parse_pragma_value_expr(
2623        &mut self,
2624        preserve_numeric_spelling: bool,
2625    ) -> Result<Expr, ParseError> {
2626        if preserve_numeric_spelling && let Some(sql) = &self.pragma_source_sql {
2627            let signed = matches!(self.peek(), TokenKind::Plus | TokenKind::Minus);
2628            let offset = usize::from(signed);
2629            if matches!(
2630                self.peek_nth(offset),
2631                TokenKind::Integer(_) | TokenKind::OversizedInt(_) | TokenKind::Float(_)
2632            ) {
2633                let token = &self.tokens[self.pos + offset];
2634                let raw = &sql[token.span.start as usize..token.span.end as usize];
2635                if raw.contains('_') {
2636                    let mut error = ParseError::at(
2637                        "numeric separators are not valid PRAGMA arguments",
2638                        Some(token),
2639                    );
2640                    error.kind = ParseErrorKind::UnexpectedToken;
2641                    return Err(error);
2642                }
2643                // SQLite's plus_num grammar drops unary '+', but its minus_num
2644                // grammar prepends '-'. Whitespace/comments between sign and
2645                // number are not part of the numeric argument.
2646                let value = if matches!(self.peek(), TokenKind::Minus) {
2647                    format!("-{raw}")
2648                } else {
2649                    raw.to_owned()
2650                };
2651                let span = Span::new(self.current_span().start, token.span.end);
2652                if signed {
2653                    self.advance();
2654                }
2655                self.advance();
2656                return Ok(Expr::Literal(Literal::String(value), span));
2657            }
2658        }
2659        // SQLite allows ON/OFF for many boolean pragmas. Treat `ON` as `TRUE`
2660        // in PRAGMA value position (OFF is tokenized as an identifier, so the
2661        // regular expression parser handles it).
2662        if self.check_kw(&TokenKind::KwOn) {
2663            let sp = self.current_span();
2664            self.advance();
2665            return Ok(Expr::Literal(Literal::True, sp));
2666        }
2667        // SQLite's pragma value grammar (nmnum ::= plus_num | nm | ON | DELETE |
2668        // DEFAULT) treats the reserved keywords DELETE and DEFAULT as names in
2669        // value position — e.g. `PRAGMA journal_mode=DELETE`, `PRAGMA
2670        // temp_store=DEFAULT`. The general expression parser rejects reserved
2671        // keyword tokens, so accept them here as identifier-valued pragma
2672        // arguments (matching how WAL/TRUNCATE/PERSIST/MEMORY/OFF lex as plain
2673        // identifiers and already work).
2674        let pragma_value_keyword = match self.peek() {
2675            TokenKind::KwDelete => Some("delete"),
2676            TokenKind::KwDefault => Some("default"),
2677            _ => None,
2678        };
2679        if let Some(name) = pragma_value_keyword {
2680            let sp = self.current_span();
2681            self.advance();
2682            return Ok(Expr::Column(ColumnRef::bare(name), sp));
2683        }
2684        self.parse_expr()
2685    }
2686
2687    fn parse_pragma(&mut self) -> Result<Statement, ParseError> {
2688        self.expect_kw(&TokenKind::KwPragma)?;
2689        let name = self.parse_qualified_name()?;
2690        let preserve_numeric_spelling = name.name.eq_ignore_ascii_case("cache_size")
2691            || name.name.eq_ignore_ascii_case("default_cache_size");
2692        let value = if self.eat(&TokenKind::Eq) || self.eat(&TokenKind::EqEq) {
2693            Some(PragmaValue::Assign(
2694                self.parse_pragma_value_expr(preserve_numeric_spelling)?,
2695            ))
2696        } else if self.eat(&TokenKind::LeftParen) {
2697            let v = self.parse_pragma_value_expr(preserve_numeric_spelling)?;
2698            self.expect_token(&TokenKind::RightParen)?;
2699            Some(PragmaValue::Call(v))
2700        } else {
2701            None
2702        };
2703        Ok(Statement::Pragma(PragmaStatement { name, value }))
2704    }
2705
2706    fn parse_vacuum(&mut self) -> Result<Statement, ParseError> {
2707        self.expect_kw(&TokenKind::KwVacuum)?;
2708        let schema = if !self.at_eof()
2709            && !self.check(&TokenKind::Semicolon)
2710            && !self.check_kw(&TokenKind::KwInto)
2711        {
2712            Some(self.parse_identifier()?)
2713        } else {
2714            None
2715        };
2716        let into = if self.eat_kw(&TokenKind::KwInto) {
2717            Some(self.parse_expr()?)
2718        } else {
2719            None
2720        };
2721        Ok(Statement::Vacuum(VacuumStatement { schema, into }))
2722    }
2723
2724    fn parse_explain(&mut self) -> Result<Statement, ParseError> {
2725        self.expect_kw(&TokenKind::KwExplain)?;
2726        let query_plan = if self.eat_kw(&TokenKind::KwQuery) {
2727            self.expect_kw(&TokenKind::KwPlan)?;
2728            true
2729        } else {
2730            false
2731        };
2732        if self.check_kw(&TokenKind::KwExplain) {
2733            return Err(self.err_unexpected("nested EXPLAIN is not allowed"));
2734        }
2735        let stmt = self.parse_statement_inner()?;
2736        Ok(Statement::Explain {
2737            query_plan,
2738            stmt: Box::new(stmt),
2739        })
2740    }
2741
2742    // -----------------------------------------------------------------------
2743    // Window definitions (used in SELECT ... WINDOW clause and OVER)
2744    // -----------------------------------------------------------------------
2745
2746    #[cfg(test)]
2747    fn parse_window_def(&mut self) -> Result<WindowDef, ParseError> {
2748        let name = self.parse_window_name()?;
2749        self.expect_kw(&TokenKind::KwAs)?;
2750        self.expect_token(&TokenKind::LeftParen)?;
2751        let spec = self.parse_window_spec()?;
2752        self.expect_token(&TokenKind::RightParen)?;
2753        Ok(WindowDef { name, spec })
2754    }
2755
2756    #[cfg(test)]
2757    pub(crate) fn parse_window_spec(&mut self) -> Result<WindowSpec, ParseError> {
2758        // Optional base window name.
2759        let has_base_window = starts_window_base_name(self.peek());
2760        let base_window = if has_base_window {
2761            Some(self.parse_window_name()?)
2762        } else {
2763            None
2764        };
2765        let partition_by = if self.eat_kw(&TokenKind::KwPartition) {
2766            self.expect_kw(&TokenKind::KwBy)?;
2767            self.parse_comma_sep(Self::parse_expr)?
2768        } else {
2769            vec![]
2770        };
2771        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
2772            self.expect_kw(&TokenKind::KwBy)?;
2773            self.parse_comma_sep(Self::parse_ordering_term)?
2774        } else {
2775            vec![]
2776        };
2777        let frame = self.try_frame_spec()?;
2778        Ok(WindowSpec {
2779            window_ref: base_window.map(WindowReference::Base),
2780            partition_by,
2781            order_by,
2782            frame,
2783        })
2784    }
2785
2786    #[cfg(test)]
2787    fn try_frame_spec(&mut self) -> Result<Option<FrameSpec>, ParseError> {
2788        let frame_type = if self.eat_kw(&TokenKind::KwRows) {
2789            FrameType::Rows
2790        } else if self.eat_kw(&TokenKind::KwRange) {
2791            FrameType::Range
2792        } else if self.eat_kw(&TokenKind::KwGroups) {
2793            FrameType::Groups
2794        } else {
2795            return Ok(None);
2796        };
2797        let (start, end) = if self.eat_kw(&TokenKind::KwBetween) {
2798            let start = self.parse_frame_bound()?;
2799            validate_frame_start(&start, true)?;
2800            self.expect_kw(&TokenKind::KwAnd)?;
2801            let end = self.parse_frame_bound()?;
2802            validate_frame_end(&start, &end)?;
2803            (start, Some(end))
2804        } else {
2805            let start = self.parse_frame_bound()?;
2806            validate_frame_start(&start, false)?;
2807            (start, None)
2808        };
2809        let exclude = if self.eat_kw(&TokenKind::KwExclude) {
2810            if self.check_kw(&TokenKind::KwNo) {
2811                self.advance();
2812                // "NO OTHERS"
2813                let id = self.parse_identifier()?;
2814                if !id.eq_ignore_ascii_case("OTHERS") {
2815                    return Err(self.err_expected("OTHERS"));
2816                }
2817                Some(FrameExclude::NoOthers)
2818            } else if self.eat_kw(&TokenKind::KwTies) {
2819                Some(FrameExclude::Ties)
2820            } else if self.eat_kw(&TokenKind::KwGroup) {
2821                Some(FrameExclude::Group)
2822            } else if matches!(self.peek(), TokenKind::Id(s) if s.eq_ignore_ascii_case("CURRENT")) {
2823                self.advance();
2824                self.expect_kw(&TokenKind::KwRow)?;
2825                Some(FrameExclude::CurrentRow)
2826            } else {
2827                return Err(
2828                    self.err_expected("NO OTHERS, TIES, GROUP, or CURRENT ROW after EXCLUDE")
2829                );
2830            }
2831        } else {
2832            None
2833        };
2834        Ok(Some(FrameSpec {
2835            frame_type,
2836            start: start.value,
2837            end: end.map(|bound| bound.value),
2838            exclude,
2839        }))
2840    }
2841
2842    #[cfg(test)]
2843    fn parse_frame_bound(&mut self) -> Result<ParsedFrameBound, ParseError> {
2844        let origin = self
2845            .current()
2846            .cloned()
2847            .ok_or_else(|| self.err_expected("window frame bound"))?;
2848        let value = if self.eat_kw(&TokenKind::KwUnbounded) {
2849            if self.eat_kw(&TokenKind::KwPreceding) {
2850                FrameBound::UnboundedPreceding
2851            } else {
2852                self.expect_kw(&TokenKind::KwFollowing)?;
2853                FrameBound::UnboundedFollowing
2854            }
2855        } else if matches!(self.peek(), TokenKind::Id(s) if s.eq_ignore_ascii_case("CURRENT")) {
2856            self.advance();
2857            self.expect_kw(&TokenKind::KwRow)?;
2858            FrameBound::CurrentRow
2859        } else {
2860            let expr = self.parse_expr()?;
2861            if self.eat_kw(&TokenKind::KwPreceding) {
2862                FrameBound::Preceding(Box::new(expr))
2863            } else {
2864                self.expect_kw(&TokenKind::KwFollowing)?;
2865                FrameBound::Following(Box::new(expr))
2866            }
2867        };
2868        Ok(ParsedFrameBound { value, origin })
2869    }
2870}
2871
2872fn parse_statements_with_scratch_inner(
2873    sql: &str,
2874    scratch: &mut StatementParseScratch,
2875) -> (Vec<Statement>, Option<ParseError>) {
2876    Lexer::tokenize_into_with_interner(sql, &mut scratch.tokens, &mut scratch.identifier_interner);
2877    let mut parser = Parser {
2878        tokens: std::mem::take(&mut scratch.tokens),
2879        pragma_source_sql: None,
2880        pos: 0,
2881        errors: std::mem::take(&mut scratch.errors),
2882        depth: 0,
2883        has_with: false,
2884    };
2885    parser.preserve_pragma_source(sql);
2886    let (statements, errors) = parser.parse_all();
2887    scratch.tokens = parser.tokens;
2888    scratch.tokens.clear();
2889    scratch.identifier_interner.prepare_for_next_parse();
2890    scratch.errors = errors;
2891    let first_error = scratch.errors.first().cloned();
2892    scratch.errors.clear();
2893    (statements, first_error)
2894}
2895
2896/// Parse all statements from `sql` using caller-owned token/error lookaside.
2897///
2898/// Semantically-empty input (empty string, whitespace, comments, or bare
2899/// `;` separators) parses to an empty statement list, matching stock SQLite:
2900/// `sqlite3_prepare_v2` of such input returns `SQLITE_OK` with a NULL
2901/// statement — a no-op, never a parse error. The single-statement callers
2902/// below still reject an empty list on their own where exactly one statement
2903/// is required. bd-parser-errfmt-residuals-3giu1.
2904pub fn parse_statements_with_scratch(
2905    sql: &str,
2906    scratch: &mut StatementParseScratch,
2907) -> Result<Vec<Statement>, ParseError> {
2908    let (statements, first_error) = parse_statements_with_scratch_inner(sql, scratch);
2909    if let Some(error) = first_error {
2910        return Err(error);
2911    }
2912    Ok(statements)
2913}
2914
2915/// Parse exactly one statement from `sql` using caller-owned lookaside.
2916pub fn parse_single_statement_with_scratch(
2917    sql: &str,
2918    scratch: &mut StatementParseScratch,
2919) -> Result<Statement, ParseError> {
2920    let statements = parse_statements_with_scratch(sql, scratch)?;
2921    let mut iter = statements.into_iter();
2922    let statement = iter
2923        .next()
2924        .ok_or_else(|| ParseError::at("no SQL statement provided", None))?;
2925    if iter.next().is_some() {
2926        return Err(ParseError::at(
2927            "multiple statements are not supported in this API path",
2928            None,
2929        ));
2930    }
2931    Ok(statement)
2932}
2933
2934/// Parse only the first top-level SQL statement from `sql` and report the byte
2935/// offset immediately after the consumed statement text.
2936///
2937/// Returns `Ok(None)` when the input contains no statement text (for example
2938/// whitespace, comments, or empty statements only). The returned tail offset is
2939/// suitable for `sqlite3_prepare_v2`-style APIs that must leave any remaining
2940/// SQL untouched.
2941pub fn parse_first_statement_with_tail(
2942    sql: &str,
2943) -> Result<Option<(Statement, usize)>, ParseError> {
2944    let mut parser = Parser::from_sql(sql);
2945
2946    while parser.eat(&TokenKind::Semicolon) {}
2947    if parser.at_eof() {
2948        return Ok(None);
2949    }
2950
2951    let statement = parser.parse_statement()?;
2952    let tail_offset = if parser.eat(&TokenKind::Semicolon) {
2953        parser
2954            .tokens
2955            .get(parser.pos.saturating_sub(1))
2956            .map_or(sql.len(), |token| token.span.end as usize)
2957    } else if parser.at_eof() {
2958        sql.len()
2959    } else {
2960        return Err(parser
2961            .err_unexpected("unexpected token after end of statement; expected ';' separator"));
2962    };
2963
2964    Ok(Some((statement, tail_offset)))
2965}
2966
2967// ---------------------------------------------------------------------------
2968// Keyword classification helper
2969// ---------------------------------------------------------------------------
2970
2971/// Whether `OVER` can consume the next token as a bare window name.
2972///
2973/// SQLite's fallback-name grammar is contextual here: it accepts several hard
2974/// keywords that the generic identifier parser rejects, while `FILTER`,
2975/// `NOTHING`, and `TRANSACTION` remain structural/reserved in this position.
2976pub(crate) fn starts_bare_window_name(k: &TokenKind) -> bool {
2977    matches!(
2978        k,
2979        TokenKind::Id(_) | TokenKind::QuotedId(_, _) | TokenKind::String(_)
2980    ) || (is_nonreserved_kw(k) && !matches!(k, TokenKind::KwFilter))
2981        || matches!(
2982            k,
2983            TokenKind::KwAttach
2984                | TokenKind::KwBegin
2985                | TokenKind::KwBy
2986                | TokenKind::KwCast
2987                | TokenKind::KwCurrentDate
2988                | TokenKind::KwCurrentTime
2989                | TokenKind::KwCurrentTimestamp
2990                | TokenKind::KwCross
2991                | TokenKind::KwDetach
2992                | TokenKind::KwExplain
2993                | TokenKind::KwFalse
2994                | TokenKind::KwFor
2995                | TokenKind::KwGlob
2996                | TokenKind::KwInner
2997                | TokenKind::KwLeft
2998                | TokenKind::KwLike
2999                | TokenKind::KwNatural
3000                | TokenKind::KwOuter
3001                | TokenKind::KwRaise
3002                | TokenKind::KwRegexp
3003                | TokenKind::KwRight
3004                | TokenKind::KwRollback
3005                | TokenKind::KwTrue
3006                | TokenKind::KwWith
3007        )
3008}
3009
3010/// Whether a token can begin the optional base name inside `OVER (...)`.
3011///
3012/// This uses the same contextual fallback set as a bare name, minus the five
3013/// structural window-spec delimiters.
3014pub(crate) fn starts_window_base_name(k: &TokenKind) -> bool {
3015    starts_bare_window_name(k)
3016        && !matches!(
3017            k,
3018            TokenKind::KwPartition
3019                | TokenKind::KwOrder
3020                | TokenKind::KwRange
3021                | TokenKind::KwRows
3022                | TokenKind::KwGroups
3023        )
3024}
3025
3026pub(crate) fn starts_post_dot_identifier(k: &TokenKind) -> bool {
3027    matches!(
3028        k,
3029        TokenKind::Id(_) | TokenKind::QuotedId(_, _) | TokenKind::String(_)
3030    ) || (k.keyword_str().is_some()
3031        && !matches!(
3032            k,
3033            TokenKind::KwAdd
3034                | TokenKind::KwAll
3035                | TokenKind::KwAlter
3036                | TokenKind::KwAnd
3037                | TokenKind::KwAs
3038                | TokenKind::KwAutoincrement
3039                | TokenKind::KwBetween
3040                | TokenKind::KwCase
3041                | TokenKind::KwCheck
3042                | TokenKind::KwCollate
3043                | TokenKind::KwCommit
3044                | TokenKind::KwConstraint
3045                | TokenKind::KwCreate
3046                | TokenKind::KwDefault
3047                | TokenKind::KwDeferrable
3048                | TokenKind::KwDelete
3049                | TokenKind::KwDistinct
3050                | TokenKind::KwDrop
3051                | TokenKind::KwElse
3052                | TokenKind::KwEscape
3053                | TokenKind::KwExcept
3054                | TokenKind::KwExists
3055                | TokenKind::KwForeign
3056                | TokenKind::KwFrom
3057                | TokenKind::KwGroup
3058                | TokenKind::KwHaving
3059                | TokenKind::KwIn
3060                | TokenKind::KwIndex
3061                | TokenKind::KwInsert
3062                | TokenKind::KwIntersect
3063                | TokenKind::KwInto
3064                | TokenKind::KwIs
3065                | TokenKind::KwIsnull
3066                | TokenKind::KwJoin
3067                | TokenKind::KwLimit
3068                | TokenKind::KwNot
3069                | TokenKind::KwNothing
3070                | TokenKind::KwNotnull
3071                | TokenKind::KwNull
3072                | TokenKind::KwOn
3073                | TokenKind::KwOr
3074                | TokenKind::KwOrder
3075                | TokenKind::KwPrimary
3076                | TokenKind::KwReferences
3077                | TokenKind::KwReturning
3078                | TokenKind::KwSelect
3079                | TokenKind::KwSet
3080                | TokenKind::KwTable
3081                | TokenKind::KwThen
3082                | TokenKind::KwTo
3083                | TokenKind::KwTransaction
3084                | TokenKind::KwUnion
3085                | TokenKind::KwUnique
3086                | TokenKind::KwUpdate
3087                | TokenKind::KwUsing
3088                | TokenKind::KwValues
3089                | TokenKind::KwWhen
3090                | TokenKind::KwWhere
3091        ))
3092}
3093
3094pub(crate) fn starts_table_star_qualifier(k: &TokenKind) -> bool {
3095    matches!(
3096        k,
3097        TokenKind::Id(_)
3098            | TokenKind::QuotedId(_, _)
3099            | TokenKind::String(_)
3100            | TokenKind::KwAbort
3101            | TokenKind::KwAction
3102            | TokenKind::KwAfter
3103            | TokenKind::KwAlways
3104            | TokenKind::KwAnalyze
3105            | TokenKind::KwAsc
3106            | TokenKind::KwAttach
3107            | TokenKind::KwBefore
3108            | TokenKind::KwBegin
3109            | TokenKind::KwBy
3110            | TokenKind::KwCascade
3111            | TokenKind::KwColumn
3112            | TokenKind::KwCommitseq
3113            | TokenKind::KwConcurrent
3114            | TokenKind::KwConflict
3115            | TokenKind::KwCross
3116            | TokenKind::KwDatabase
3117            | TokenKind::KwDeferred
3118            | TokenKind::KwDesc
3119            | TokenKind::KwDetach
3120            | TokenKind::KwDo
3121            | TokenKind::KwEach
3122            | TokenKind::KwEnd
3123            | TokenKind::KwExclude
3124            | TokenKind::KwExclusive
3125            | TokenKind::KwExplain
3126            | TokenKind::KwFail
3127            | TokenKind::KwFalse
3128            | TokenKind::KwFilter
3129            | TokenKind::KwFirst
3130            | TokenKind::KwFollowing
3131            | TokenKind::KwFor
3132            | TokenKind::KwFull
3133            | TokenKind::KwGenerated
3134            | TokenKind::KwGlob
3135            | TokenKind::KwGroups
3136            | TokenKind::KwIf
3137            | TokenKind::KwIgnore
3138            | TokenKind::KwImmediate
3139            | TokenKind::KwIndexed
3140            | TokenKind::KwInitially
3141            | TokenKind::KwInner
3142            | TokenKind::KwInstead
3143            | TokenKind::KwKey
3144            | TokenKind::KwLast
3145            | TokenKind::KwLeft
3146            | TokenKind::KwLike
3147            | TokenKind::KwMatch
3148            | TokenKind::KwMaterialized
3149            | TokenKind::KwNatural
3150            | TokenKind::KwNo
3151            | TokenKind::KwNulls
3152            | TokenKind::KwOf
3153            | TokenKind::KwOffset
3154            | TokenKind::KwOthers
3155            | TokenKind::KwOuter
3156            | TokenKind::KwOver
3157            | TokenKind::KwPartition
3158            | TokenKind::KwPlan
3159            | TokenKind::KwPragma
3160            | TokenKind::KwPreceding
3161            | TokenKind::KwQuery
3162            | TokenKind::KwRange
3163            | TokenKind::KwRecursive
3164            | TokenKind::KwRegexp
3165            | TokenKind::KwReindex
3166            | TokenKind::KwRelease
3167            | TokenKind::KwRename
3168            | TokenKind::KwReplace
3169            | TokenKind::KwRestrict
3170            | TokenKind::KwRight
3171            | TokenKind::KwRollback
3172            | TokenKind::KwRow
3173            | TokenKind::KwRows
3174            | TokenKind::KwSavepoint
3175            | TokenKind::KwStored
3176            | TokenKind::KwStrict
3177            | TokenKind::KwTemp
3178            | TokenKind::KwTemporary
3179            | TokenKind::KwTies
3180            | TokenKind::KwTrigger
3181            | TokenKind::KwTrue
3182            | TokenKind::KwUnbounded
3183            | TokenKind::KwVacuum
3184            | TokenKind::KwView
3185            | TokenKind::KwVirtual
3186            | TokenKind::KwWindow
3187            | TokenKind::KwWith
3188            | TokenKind::KwWithout
3189    )
3190}
3191
3192pub(crate) fn is_nonreserved_kw(k: &TokenKind) -> bool {
3193    matches!(
3194        k,
3195        TokenKind::KwAbort
3196            | TokenKind::KwAction
3197            | TokenKind::KwAfter
3198            | TokenKind::KwAlways
3199            | TokenKind::KwAnalyze
3200            | TokenKind::KwAsc
3201            | TokenKind::KwBefore
3202            | TokenKind::KwCascade
3203            | TokenKind::KwColumn
3204            | TokenKind::KwConcurrent
3205            | TokenKind::KwConflict
3206            | TokenKind::KwDatabase
3207            | TokenKind::KwDeferred
3208            | TokenKind::KwDesc
3209            | TokenKind::KwDo
3210            | TokenKind::KwEach
3211            | TokenKind::KwEnd
3212            | TokenKind::KwExclude
3213            | TokenKind::KwExclusive
3214            | TokenKind::KwFail
3215            | TokenKind::KwFilter
3216            | TokenKind::KwFirst
3217            | TokenKind::KwFollowing
3218            | TokenKind::KwFull
3219            | TokenKind::KwGenerated
3220            | TokenKind::KwGroups
3221            | TokenKind::KwIf
3222            | TokenKind::KwIgnore
3223            | TokenKind::KwImmediate
3224            | TokenKind::KwInitially
3225            | TokenKind::KwInstead
3226            | TokenKind::KwKey
3227            | TokenKind::KwLast
3228            | TokenKind::KwMatch
3229            | TokenKind::KwMaterialized
3230            | TokenKind::KwNo
3231            | TokenKind::KwNulls
3232            | TokenKind::KwOf
3233            | TokenKind::KwOffset
3234            | TokenKind::KwOthers
3235            | TokenKind::KwOver
3236            | TokenKind::KwPartition
3237            | TokenKind::KwPlan
3238            | TokenKind::KwPragma
3239            | TokenKind::KwPreceding
3240            | TokenKind::KwQuery
3241            | TokenKind::KwRange
3242            | TokenKind::KwRecursive
3243            | TokenKind::KwReindex
3244            | TokenKind::KwRelease
3245            | TokenKind::KwRename
3246            | TokenKind::KwReplace
3247            | TokenKind::KwRestrict
3248            | TokenKind::KwRow
3249            | TokenKind::KwRows
3250            | TokenKind::KwSavepoint
3251            | TokenKind::KwStored
3252            | TokenKind::KwStrict
3253            | TokenKind::KwTemp
3254            | TokenKind::KwTemporary
3255            | TokenKind::KwTies
3256            | TokenKind::KwTrigger
3257            | TokenKind::KwUnbounded
3258            | TokenKind::KwVacuum
3259            | TokenKind::KwView
3260            | TokenKind::KwVirtual
3261            | TokenKind::KwWindow
3262            | TokenKind::KwWithout
3263    )
3264}
3265
3266/// Whether a token can be used as an alias after an explicit `AS`.
3267///
3268/// SQLite's fallback-name grammar accepts more hard keywords here than the
3269/// old generic identifier grammar, while operator-shaped and structural
3270/// keywords remain reserved.
3271fn starts_explicit_alias_name(k: &TokenKind) -> bool {
3272    starts_post_dot_identifier(k)
3273}
3274
3275/// Whether a token can be consumed as an implicit result-column alias.
3276///
3277/// Pattern-matching tokens remain expression operators in this position.
3278/// `WINDOW` is admitted here only after `Parser::starts_window_clause` has
3279/// ruled out an actual named-window clause.
3280fn starts_result_alias(k: &TokenKind) -> bool {
3281    starts_explicit_alias_name(k)
3282        && !matches!(
3283            k,
3284            TokenKind::KwCross
3285                | TokenKind::KwFull
3286                | TokenKind::KwGlob
3287                | TokenKind::KwIndexed
3288                | TokenKind::KwInner
3289                | TokenKind::KwLeft
3290                | TokenKind::KwLike
3291                | TokenKind::KwMatch
3292                | TokenKind::KwNatural
3293                | TokenKind::KwOuter
3294                | TokenKind::KwRegexp
3295                | TokenKind::KwRight
3296        )
3297}
3298
3299/// Whether a token can be consumed as an implicit table/source alias.
3300///
3301/// Join prefixes, `INDEXED`, and FrankenSQLite's `FOR SYSTEM_TIME` introducer
3302/// must remain available to the surrounding FROM grammar. Unlike a result
3303/// alias, MATCH-family tokens are unambiguous source aliases here.
3304fn starts_table_alias(k: &TokenKind) -> bool {
3305    starts_explicit_alias_name(k)
3306        && !matches!(
3307            k,
3308            TokenKind::KwCross
3309                | TokenKind::KwFull
3310                | TokenKind::KwIndexed
3311                | TokenKind::KwInner
3312                | TokenKind::KwIsnull
3313                | TokenKind::KwLeft
3314                | TokenKind::KwNatural
3315                | TokenKind::KwNotnull
3316                | TokenKind::KwOuter
3317                | TokenKind::KwRight
3318        )
3319}
3320
3321pub(crate) fn kw_to_str(k: &TokenKind) -> String {
3322    k.keyword_str()
3323        .map(|s| s.to_ascii_lowercase())
3324        .unwrap_or_else(|| {
3325            let dbg = format!("{k:?}");
3326            dbg.strip_prefix("Kw").unwrap_or(&dbg).to_ascii_lowercase()
3327        })
3328}
3329
3330// ---------------------------------------------------------------------------
3331// Tests
3332// ---------------------------------------------------------------------------
3333
3334#[cfg(test)]
3335mod tests {
3336    use super::*;
3337
3338    static PARSE_OBSERVABILITY_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
3339
3340    fn parse_ok(sql: &str) -> Vec<Statement> {
3341        let mut p = Parser::from_sql(sql);
3342        let (stmts, errs) = p.parse_all();
3343        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
3344        stmts
3345    }
3346
3347    fn parse_one(sql: &str) -> Statement {
3348        let stmts = parse_ok(sql);
3349        assert_eq!(stmts.len(), 1, "expected 1 statement, got {}", stmts.len());
3350        stmts.into_iter().next().unwrap()
3351    }
3352
3353    fn parse_full_select(sql: &str) -> SelectStatement {
3354        let Some((statement, tail_offset)) =
3355            parse_first_statement_with_tail(sql).expect("full SELECT statement must parse")
3356        else {
3357            panic!("expected one SELECT statement");
3358        };
3359        assert_eq!(
3360            tail_offset,
3361            sql.len(),
3362            "public parser must consume the full statement"
3363        );
3364        let Statement::Select(select) = statement else {
3365            panic!("expected SELECT AST");
3366        };
3367        select
3368    }
3369
3370    fn top_level_values(statement: &Statement) -> &ValuesClause {
3371        let Statement::Select(select) = statement else {
3372            panic!("expected SELECT statement");
3373        };
3374        let SelectCore::Values(values) = &select.body.select else {
3375            panic!("expected top-level VALUES core");
3376        };
3377        values
3378    }
3379
3380    fn scalar_subquery_values(expr: &Expr) -> &ValuesClause {
3381        let Expr::Subquery(select, _) = expr else {
3382            panic!("expected scalar subquery");
3383        };
3384        let SelectCore::Values(values) = &select.body.select else {
3385            panic!("expected VALUES scalar subquery");
3386        };
3387        values
3388    }
3389
3390    fn only_join(select: &SelectStatement) -> &JoinClause {
3391        let SelectCore::Select {
3392            from: Some(from), ..
3393        } = &select.body.select
3394        else {
3395            panic!("expected SELECT with FROM clause");
3396        };
3397        let [join] = from.joins.as_slice() else {
3398            panic!("expected exactly one join");
3399        };
3400        assert_eq!(join.join_type.kind, JoinKind::Cross);
3401        assert!(!join.join_type.natural);
3402        join
3403    }
3404
3405    #[test]
3406    fn test_comma_join_accepts_on_constraint_and_consumes_full_statement() {
3407        let select = parse_full_select("SELECT * FROM a, b ON a.id = b.id");
3408        assert!(matches!(
3409            &only_join(&select).constraint,
3410            Some(JoinConstraint::On(Expr::BinaryOp {
3411                op: fsqlite_ast::BinaryOp::Eq,
3412                ..
3413            }))
3414        ));
3415    }
3416
3417    #[test]
3418    fn test_comma_join_accepts_using_constraint_and_consumes_full_statement() {
3419        let select = parse_full_select("SELECT * FROM a, b USING(id)");
3420        assert!(matches!(
3421            &only_join(&select).constraint,
3422            Some(JoinConstraint::Using(columns))
3423                if columns.len() == 1 && columns[0] == "id"
3424        ));
3425    }
3426
3427    #[test]
3428    fn test_explicit_cross_join_accepts_on_constraint_and_consumes_full_statement() {
3429        let select = parse_full_select("SELECT * FROM a CROSS JOIN b ON a.id = b.id");
3430        assert!(matches!(
3431            &only_join(&select).constraint,
3432            Some(JoinConstraint::On(Expr::BinaryOp {
3433                op: fsqlite_ast::BinaryOp::Eq,
3434                ..
3435            }))
3436        ));
3437    }
3438
3439    #[test]
3440    fn test_nonreserved_keyword_table_star_uses_wildcard_ast() {
3441        let select = parse_full_select("SELECT filter.* FROM t AS filter");
3442        let SelectCore::Select { columns, .. } = &select.body.select else {
3443            panic!("expected SELECT core");
3444        };
3445        assert!(matches!(
3446            columns.as_slice(),
3447            [ResultColumn::TableStar(name)] if name == &QualifiedName::bare("filter")
3448        ));
3449        assert_eq!(
3450            select.to_string(),
3451            "SELECT \"filter\".* FROM t AS \"filter\""
3452        );
3453    }
3454
3455    #[test]
3456    fn test_table_star_qualifier_uses_sqlite_contextual_keyword_matrix() {
3457        for name in [
3458            "abort",
3459            "action",
3460            "after",
3461            "always",
3462            "analyze",
3463            "asc",
3464            "attach",
3465            "before",
3466            "begin",
3467            "by",
3468            "cascade",
3469            "column",
3470            "commitseq",
3471            "concurrent",
3472            "conflict",
3473            "cross",
3474            "database",
3475            "deferred",
3476            "desc",
3477            "detach",
3478            "do",
3479            "each",
3480            "end",
3481            "exclude",
3482            "exclusive",
3483            "explain",
3484            "fail",
3485            "false",
3486            "filter",
3487            "first",
3488            "following",
3489            "for",
3490            "full",
3491            "generated",
3492            "glob",
3493            "groups",
3494            "if",
3495            "ignore",
3496            "immediate",
3497            "indexed",
3498            "initially",
3499            "inner",
3500            "instead",
3501            "key",
3502            "last",
3503            "left",
3504            "like",
3505            "match",
3506            "materialized",
3507            "natural",
3508            "no",
3509            "nulls",
3510            "of",
3511            "offset",
3512            "others",
3513            "outer",
3514            "over",
3515            "partition",
3516            "plan",
3517            "pragma",
3518            "preceding",
3519            "query",
3520            "range",
3521            "recursive",
3522            "regexp",
3523            "reindex",
3524            "release",
3525            "rename",
3526            "replace",
3527            "restrict",
3528            "right",
3529            "rollback",
3530            "row",
3531            "rows",
3532            "savepoint",
3533            "stored",
3534            "strict",
3535            "temp",
3536            "temporary",
3537            "ties",
3538            "trigger",
3539            "true",
3540            "unbounded",
3541            "vacuum",
3542            "view",
3543            "virtual",
3544            "window",
3545            "with",
3546            "without",
3547        ] {
3548            let sql = format!("SELECT {name}.* FROM t AS \"{name}\"");
3549            let select = parse_full_select(&sql);
3550            let SelectCore::Select { columns, .. } = &select.body.select else {
3551                panic!("expected SELECT core for `{sql}`");
3552            };
3553            assert!(matches!(
3554                columns.as_slice(),
3555                [ResultColumn::TableStar(qualifier)] if qualifier == &QualifiedName::bare(name)
3556            ));
3557        }
3558
3559        for name in ["nothing", "transaction", "select", "table"] {
3560            let sql = format!("SELECT {name}.* FROM t AS \"{name}\"");
3561            parse_first_statement_with_tail(&sql)
3562                .expect_err("non-fallback table-star keywords must be rejected");
3563        }
3564    }
3565
3566    #[test]
3567    fn test_qualified_star_is_rejected_outside_result_columns() {
3568        for sql in ["SELECT 1 WHERE t.*", "SELECT abs(t.*) FROM t"] {
3569            let error = parse_first_statement_with_tail(sql)
3570                .expect_err("qualified star must not become an ordinary column expression");
3571            // Stock reports the illegal `table.*` outside result columns as
3572            // `near "*": syntax error`. bd-parser-syntax-error-format-6w6kp.
3573            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3574            assert_eq!(
3575                &sql[error.span.start as usize..error.span.end as usize],
3576                "*",
3577                "the diagnostic for `{sql}` must point at the illegal wildcard"
3578            );
3579            assert!(
3580                error.message.contains("expected column name after '.'"),
3581                "unexpected diagnostic for `{sql}`: {error:?}"
3582            );
3583        }
3584
3585        let valid = parse_full_select("SELECT t.*, filter.* FROM t AS filter");
3586        let SelectCore::Select { columns, .. } = &valid.body.select else {
3587            panic!("expected SELECT core");
3588        };
3589        assert!(matches!(
3590            columns.as_slice(),
3591            [ResultColumn::TableStar(first), ResultColumn::TableStar(second)]
3592                if first == &QualifiedName::bare("t")
3593                    && second == &QualifiedName::bare("filter")
3594        ));
3595    }
3596
3597    #[test]
3598    fn test_single_quoted_qualified_identifiers_follow_identifier_context() {
3599        for (sql, expected) in [
3600            ("SELECT 't'.x FROM t", "SELECT t.x FROM t"),
3601            ("SELECT t.'x' FROM t", "SELECT t.x FROM t"),
3602            ("SELECT 't'.'x' FROM t", "SELECT t.x FROM t"),
3603            ("SELECT t.'select' FROM t", "SELECT t.\"select\" FROM t"),
3604            ("SELECT 't'.* FROM t", "SELECT t.* FROM t"),
3605        ] {
3606            let select = parse_full_select(sql);
3607            assert_eq!(
3608                select.to_string(),
3609                expected,
3610                "round-trip mismatch for `{sql}`"
3611            );
3612        }
3613    }
3614
3615    #[test]
3616    fn test_post_dot_identifier_classification_matches_unquoted_names() {
3617        for name in [
3618            "key",
3619            "window",
3620            "filter",
3621            "range",
3622            "rows",
3623            "groups",
3624            "match",
3625            "replace",
3626            "abort",
3627            "column",
3628            "strict",
3629            "true",
3630            "false",
3631            "current_date",
3632            "current_time",
3633            "current_timestamp",
3634            "like",
3635            "glob",
3636            "regexp",
3637        ] {
3638            parse_full_select(&format!("SELECT t.{name} FROM t"));
3639        }
3640
3641        for name in [
3642            "add",
3643            "all",
3644            "alter",
3645            "and",
3646            "as",
3647            "autoincrement",
3648            "between",
3649            "case",
3650            "check",
3651            "collate",
3652            "commit",
3653            "constraint",
3654            "create",
3655            "default",
3656            "deferrable",
3657            "delete",
3658            "distinct",
3659            "drop",
3660            "else",
3661            "escape",
3662            "except",
3663            "exists",
3664            "foreign",
3665            "select",
3666            "from",
3667            "group",
3668            "having",
3669            "in",
3670            "index",
3671            "insert",
3672            "intersect",
3673            "into",
3674            "is",
3675            "isnull",
3676            "join",
3677            "limit",
3678            "not",
3679            "nothing",
3680            "notnull",
3681            "null",
3682            "on",
3683            "or",
3684            "order",
3685            "primary",
3686            "references",
3687            "returning",
3688            "set",
3689            "table",
3690            "then",
3691            "to",
3692            "transaction",
3693            "union",
3694            "unique",
3695            "update",
3696            "using",
3697            "values",
3698            "when",
3699            "where",
3700        ] {
3701            let sql = format!("SELECT t.{name} FROM t");
3702            let error = parse_first_statement_with_tail(&sql)
3703                .expect_err("hard reserved names after a dot must require quoting");
3704            assert_eq!(
3705                &sql[error.span.start as usize..error.span.end as usize],
3706                name,
3707                "the diagnostic for `{sql}` must point at the rejected name"
3708            );
3709        }
3710
3711        for (sql, rejected) in [("SELECT t.1 FROM t", ".1"), ("SELECT t. 1 FROM t", "1")] {
3712            let error = parse_first_statement_with_tail(sql)
3713                .expect_err("numeric tokens after a dot must not become identifiers");
3714            assert_eq!(
3715                &sql[error.span.start as usize..error.span.end as usize],
3716                rejected
3717            );
3718        }
3719    }
3720
3721    #[test]
3722    fn test_leading_qualified_keyword_uses_dot_lookahead_only() {
3723        for name in [
3724            "attach", "begin", "by", "false", "filter", "glob", "inner", "left", "like", "natural",
3725            "outer", "regexp", "right", "rollback", "true", "with",
3726        ] {
3727            let sql = format!("SELECT {name}.x FROM (SELECT 1 AS x) AS \"{name}\"");
3728            let select = parse_full_select(&sql);
3729            let SelectCore::Select { columns, .. } = &select.body.select else {
3730                panic!("expected SELECT core for `{sql}`");
3731            };
3732            assert!(matches!(
3733                columns.as_slice(),
3734                [ResultColumn::Expr {
3735                    expr: Expr::Column(column, _),
3736                    alias: None,
3737                }] if column.table.as_deref() == Some(name) && column.column.as_ref() == "x"
3738            ));
3739        }
3740
3741        for name in ["cast", "current_date", "nothing", "raise", "transaction"] {
3742            let sql = format!("SELECT {name}.x FROM (SELECT 1 AS x) AS \"{name}\"");
3743            parse_first_statement_with_tail(&sql)
3744                .expect_err("non-fallback leading qualifiers must remain expressions or syntax");
3745        }
3746    }
3747
3748    #[test]
3749    fn test_result_alias_uses_sqlite_contextual_name_policy() {
3750        for (source, expected) in [
3751            ("'single quoted'", "single quoted"),
3752            ("attach", "attach"),
3753            ("cast", "cast"),
3754            ("current_date", "current_date"),
3755            ("false", "false"),
3756            ("raise", "raise"),
3757            ("rollback", "rollback"),
3758            ("true", "true"),
3759            ("with", "with"),
3760            ("window", "window"),
3761            ("offset", "offset"),
3762        ] {
3763            let sql = format!("SELECT 1 {source}");
3764            let select = parse_full_select(&sql);
3765            let SelectCore::Select { columns, .. } = &select.body.select else {
3766                panic!("expected SELECT core for `{sql}`");
3767            };
3768            assert!(matches!(
3769                columns.as_slice(),
3770                [ResultColumn::Expr {
3771                    alias: Some(alias),
3772                    ..
3773                }] if alias == expected
3774            ));
3775        }
3776
3777        for sql in [
3778            "SELECT 1 indexed",
3779            "SELECT 1 left",
3780            "SELECT 1 match",
3781            "SELECT 1 nothing",
3782            "SELECT 1 transaction",
3783            "SELECT 1 AS isnull",
3784            "SELECT 1 AS notnull",
3785        ] {
3786            parse_first_statement_with_tail(sql)
3787                .expect_err("operators and non-fallback names must not become result aliases");
3788        }
3789    }
3790
3791    #[test]
3792    fn test_table_alias_uses_sqlite_contextual_name_policy() {
3793        for (source, expected) in [
3794            ("'single quoted'", "single quoted"),
3795            ("attach", "attach"),
3796            ("cast", "cast"),
3797            ("current_date", "current_date"),
3798            ("false", "false"),
3799            ("for", "for"),
3800            ("match", "match"),
3801            ("raise", "raise"),
3802            ("rollback", "rollback"),
3803            ("true", "true"),
3804            ("with", "with"),
3805            ("window", "window"),
3806            ("offset", "offset"),
3807        ] {
3808            let sql = format!("SELECT * FROM (SELECT 1) {source}");
3809            let select = parse_full_select(&sql);
3810            let SelectCore::Select {
3811                from:
3812                    Some(FromClause {
3813                        source: TableOrSubquery::Subquery { alias, .. },
3814                        ..
3815                    }),
3816                ..
3817            } = &select.body.select
3818            else {
3819                panic!("expected aliased subquery for `{sql}`");
3820            };
3821            assert_eq!(
3822                alias.as_deref(),
3823                Some(expected),
3824                "alias mismatch for `{sql}`"
3825            );
3826        }
3827
3828        for sql in [
3829            "SELECT * FROM (SELECT 1) isnull",
3830            "SELECT * FROM (SELECT 1) notnull",
3831            "SELECT * FROM (SELECT 1) nothing",
3832            "SELECT * FROM (SELECT 1) transaction",
3833        ] {
3834            parse_first_statement_with_tail(sql)
3835                .expect_err("non-table-alias tokens must remain rejected");
3836        }
3837
3838        let select = parse_full_select("SELECT * FROM t WINDOW w AS ()");
3839        let SelectCore::Select { from, windows, .. } = &select.body.select else {
3840            panic!("expected SELECT core");
3841        };
3842        assert!(matches!(
3843            from,
3844            Some(FromClause {
3845                source: TableOrSubquery::Table { alias: None, .. },
3846                ..
3847            })
3848        ));
3849        assert_eq!(windows.len(), 1);
3850        assert_eq!(windows[0].name, "w");
3851
3852        let select = parse_full_select("SELECT * FROM t FOR SYSTEM_TIME AS OF COMMITSEQ 1");
3853        let SelectCore::Select { from, .. } = &select.body.select else {
3854            panic!("expected SELECT core");
3855        };
3856        assert!(matches!(
3857            from,
3858            Some(FromClause {
3859                source: TableOrSubquery::Table {
3860                    alias: None,
3861                    time_travel: Some(TimeTravelClause {
3862                        target: TimeTravelTarget::CommitSequence(1),
3863                    }),
3864                    ..
3865                },
3866                ..
3867            })
3868        ));
3869    }
3870
3871    #[test]
3872    fn test_indexed_is_explicit_name_but_not_implicit_alias() {
3873        for sql in [
3874            "SELECT 1 AS indexed",
3875            "SELECT * FROM (SELECT 1) AS indexed",
3876            "CREATE TABLE t(indexed); SELECT t.indexed FROM t",
3877        ] {
3878            let mut parser = Parser::from_sql(sql);
3879            let (statements, errors) = parser.parse_all();
3880            assert!(
3881                errors.is_empty(),
3882                "unexpected errors for `{sql}`: {errors:?}"
3883            );
3884            assert!(
3885                !statements.is_empty(),
3886                "explicit INDEXED name context must produce an AST for `{sql}`"
3887            );
3888        }
3889
3890        for sql in ["SELECT 1 indexed", "SELECT * FROM (SELECT 1) indexed"] {
3891            parse_first_statement_with_tail(sql)
3892                .expect_err("INDEXED must remain unavailable as an implicit alias");
3893        }
3894    }
3895
3896    #[test]
3897    fn test_nested_explain_is_rejected_at_the_second_explain() {
3898        for sql in [
3899            "EXPLAIN EXPLAIN SELECT 1",
3900            "EXPLAIN QUERY PLAN EXPLAIN SELECT 1",
3901        ] {
3902            let error = parse_first_statement_with_tail(sql)
3903                .expect_err("SQLite does not permit nested EXPLAIN statements");
3904            // Stock: `near "EXPLAIN": syntax error`. bd-parser-syntax-error-format-6w6kp.
3905            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3906            assert!(
3907                error.message.contains("nested EXPLAIN"),
3908                "unexpected diagnostic for `{sql}`: {error:?}"
3909            );
3910            let second_explain = sql
3911                .match_indices("EXPLAIN")
3912                .nth(1)
3913                .map(|(offset, _)| offset)
3914                .expect("test SQL must contain a second EXPLAIN");
3915            assert_eq!(
3916                &sql[error.span.start as usize..error.span.end as usize],
3917                &sql[second_explain..second_explain + "EXPLAIN".len()],
3918                "the nested-EXPLAIN diagnostic must point at the rejected keyword"
3919            );
3920        }
3921    }
3922
3923    #[test]
3924    fn test_generic_name_slots_use_sqlite_fallback_name_policy() {
3925        for sql in [
3926            "CREATE TABLE begin(x)",
3927            "SELECT * FROM begin",
3928            "DROP TABLE main.begin",
3929        ] {
3930            parse_first_statement_with_tail(sql)
3931                .expect("fallback-name keywords must parse in an established name slot");
3932        }
3933    }
3934
3935    #[test]
3936    fn test_hard_reserved_column_names_require_quoting_in_ddl() {
3937        for name in ["index", "nothing", "returning", "table", "transaction"] {
3938            let sql = format!("CREATE TABLE t({name} INTEGER)");
3939            let error = parse_first_statement_with_tail(&sql)
3940                .expect_err("hard reserved column names must require quoting");
3941            assert_eq!(
3942                &sql[error.span.start as usize..error.span.end as usize],
3943                name
3944            );
3945        }
3946    }
3947
3948    #[test]
3949    fn test_final_values_rejects_order_by_and_limit() {
3950        for (sql, clause) in [
3951            ("VALUES (1) ORDER BY 1", "ORDER"),
3952            ("VALUES (1) LIMIT 1", "LIMIT"),
3953            ("SELECT 1 UNION VALUES (2) ORDER BY 1", "ORDER"),
3954            ("SELECT 1 UNION VALUES (2) LIMIT 1", "LIMIT"),
3955        ] {
3956            let error = parse_first_statement_with_tail(sql)
3957                .expect_err("a trailing clause on a final VALUES term must be rejected");
3958            // Stock reports a trailing ORDER BY / LIMIT on a final VALUES term as
3959            // `near "ORDER"/"LIMIT": syntax error` (there is no such grammar slot),
3960            // so this is an UnexpectedToken. bd-parser-syntax-error-format-6w6kp.
3961            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3962            assert!(
3963                error.message.contains("not allowed after a VALUES term"),
3964                "unexpected diagnostic for `{sql}`: {error:?}"
3965            );
3966            assert_eq!(
3967                &sql[error.span.start as usize..error.span.end as usize],
3968                clause,
3969                "the primary error for `{sql}` must point at the forbidden clause"
3970            );
3971        }
3972
3973        let deeply_nested = format!(
3974            "VALUES (1) ORDER BY {}1{}",
3975            "(".repeat(1_200),
3976            ")".repeat(1_200)
3977        );
3978        let error = parse_first_statement_with_tail(&deeply_nested)
3979            .expect_err("the forbidden final-VALUES clause must win over expression depth");
3980        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3981        assert_eq!(
3982            &deeply_nested[error.span.start as usize..error.span.end as usize],
3983            "ORDER"
3984        );
3985
3986        let final_select = parse_full_select("VALUES (1) UNION SELECT 2 ORDER BY 1 LIMIT 1");
3987        assert_eq!(
3988            final_select.to_string(),
3989            "VALUES (1) UNION SELECT 2 ORDER BY 1 LIMIT 1"
3990        );
3991        let wrapped = parse_full_select("SELECT * FROM (VALUES (1)) ORDER BY 1 LIMIT 1");
3992        assert_eq!(
3993            wrapped.to_string(),
3994            "SELECT * FROM (VALUES (1)) ORDER BY 1 LIMIT 1"
3995        );
3996    }
3997
3998    #[test]
3999    fn test_count_star_rejects_aggregate_order_by() {
4000        let error = parse_first_statement_with_tail("SELECT count(* ORDER BY x) FROM t")
4001            .expect_err("aggregate ORDER BY after count(*) must be rejected");
4002        // Stock: `near "ORDER": syntax error` (the aggregate arg list has no
4003        // ORDER BY slot). bd-parser-syntax-error-format-6w6kp.
4004        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4005        assert!(
4006            error.message.contains("RightParen"),
4007            "unexpected diagnostic: {error:?}"
4008        );
4009
4010        let valid = parse_full_select("SELECT count(*) FILTER (WHERE x > 0) OVER () FROM t");
4011        assert_eq!(
4012            valid.to_string(),
4013            "SELECT count(*) FILTER (WHERE x > 0) OVER () FROM t"
4014        );
4015    }
4016
4017    #[test]
4018    fn test_parse_metrics_emitted_when_enabled() {
4019        let _guard = PARSE_OBSERVABILITY_LOCK
4020            .lock()
4021            .unwrap_or_else(|poisoned| poisoned.into_inner());
4022        let prev_metrics_enabled = parse_metrics_enabled();
4023        reset_parse_metrics();
4024        set_parse_metrics_enabled(true);
4025
4026        let mut parser = Parser::from_sql("SELECT 1; SELECT 2;");
4027        let (stmts, errs) = parser.parse_all();
4028        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
4029        assert_eq!(stmts.len(), 2);
4030
4031        let snapshot = parse_metrics_snapshot();
4032        assert!(snapshot.fsqlite_parse_statements_total >= 2);
4033
4034        set_parse_metrics_enabled(prev_metrics_enabled);
4035        reset_parse_metrics();
4036    }
4037
4038    #[test]
4039    fn test_public_parser_new_normalizes_empty_and_missing_eof_streams() {
4040        let error = Parser::new(Vec::new())
4041            .parse_expr()
4042            .expect_err("an empty public token stream must return an error, not panic");
4043        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4044        assert_eq!(error.span, Span::ZERO);
4045
4046        let integer = Token {
4047            kind: TokenKind::Integer(1),
4048            span: Span::new(0, 1),
4049            line: 1,
4050            col: 1,
4051        };
4052        let mut expression_parser = Parser::new(vec![integer]);
4053        assert!(matches!(
4054            expression_parser
4055                .parse_expr()
4056                .expect("a token stream without explicit EOF must be normalized"),
4057            Expr::Literal(Literal::Integer(1), _)
4058        ));
4059        assert!(expression_parser.at_eof());
4060
4061        let mut tokens = Lexer::tokenize("SELECT 1");
4062        assert!(matches!(
4063            tokens.pop(),
4064            Some(Token {
4065                kind: TokenKind::Eof,
4066                ..
4067            })
4068        ));
4069        let (statements, errors) = Parser::new(tokens).parse_all();
4070        assert!(errors.is_empty(), "unexpected errors: {errors:?}");
4071        assert_eq!(statements.len(), 1);
4072        assert_eq!(statements[0].to_string(), "SELECT 1");
4073
4074        let mut tokens = Lexer::tokenize("SELECT 1; SELECT 2");
4075        tokens.insert(
4076            2,
4077            Token {
4078                kind: TokenKind::Eof,
4079                span: Span::new(8, 8),
4080                line: 1,
4081                col: 9,
4082            },
4083        );
4084        let mut parser = Parser::new(tokens);
4085        assert_eq!(
4086            parser
4087                .tokens
4088                .iter()
4089                .filter(|token| token.kind == TokenKind::Eof)
4090                .count(),
4091            1
4092        );
4093        assert!(matches!(
4094            parser.tokens.last(),
4095            Some(Token {
4096                kind: TokenKind::Eof,
4097                ..
4098            })
4099        ));
4100        let (statements, errors) = parser.parse_all();
4101        assert!(
4102            errors.is_empty(),
4103            "embedded EOF normalization must not hide later tokens: {errors:?}"
4104        );
4105        assert_eq!(statements.len(), 2);
4106        assert_eq!(statements[1].to_string(), "SELECT 2");
4107
4108        let tokens = Lexer::tokenize("SELECT CASE WHEN 1 THEN 'a\nb'");
4109        let expected_eof = tokens
4110            .last()
4111            .cloned()
4112            .expect("the lexer must supply a terminal EOF");
4113        let mut parser = Parser::new(tokens);
4114        let normalized_eof = parser
4115            .tokens
4116            .last()
4117            .expect("the normalized stream must retain a terminal EOF");
4118        assert_eq!(normalized_eof, &expected_eof);
4119        assert_eq!(normalized_eof.line, 2);
4120        let (_, errors) = parser.parse_all();
4121        assert!(
4122            errors
4123                .iter()
4124                .any(|error| error.span == expected_eof.span && error.line == expected_eof.line),
4125            "the missing END diagnostic must retain the lexer's multiline EOF coordinates: \
4126             {errors:?}"
4127        );
4128    }
4129
4130    #[test]
4131    fn test_parse_metrics_can_be_disabled_off_hot_path() {
4132        let _guard = PARSE_OBSERVABILITY_LOCK
4133            .lock()
4134            .unwrap_or_else(|poisoned| poisoned.into_inner());
4135        let prev_metrics_enabled = parse_metrics_enabled();
4136        reset_parse_metrics();
4137        set_parse_metrics_enabled(false);
4138
4139        let mut parser = Parser::from_sql("SELECT 1; SELECT 2;");
4140        let (stmts, errs) = parser.parse_all();
4141        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
4142        assert_eq!(stmts.len(), 2);
4143
4144        let snapshot = parse_metrics_snapshot();
4145        assert_eq!(snapshot.fsqlite_parse_statements_total, 0);
4146
4147        set_parse_metrics_enabled(prev_metrics_enabled);
4148        reset_parse_metrics();
4149    }
4150
4151    #[test]
4152    fn test_parse_depth_overflow_does_not_poison_following_statement() {
4153        const OVER_LIMIT: usize = MAX_PARSE_DEPTH as usize + 1;
4154        let expression = std::iter::repeat_n("1", OVER_LIMIT)
4155            .collect::<Vec<_>>()
4156            .join(" + ");
4157        let sql = format!("SELECT {expression}; SELECT 42;");
4158        let mut parser = Parser::from_sql(&sql);
4159        let (statements, errors) = parser.parse_all();
4160
4161        assert_eq!(
4162            errors.len(),
4163            1,
4164            "only the height-1001 statement should be rejected: {errors:?}"
4165        );
4166        assert_eq!(
4167            errors[0].kind,
4168            ParseErrorKind::ExpressionTooDeep {
4169                max: MAX_PARSE_DEPTH
4170            }
4171        );
4172        assert_eq!(statements.len(), 1, "the valid statement must survive");
4173        assert_eq!(statements[0].to_string(), "SELECT 42");
4174        assert_eq!(
4175            parser.depth, 0,
4176            "expression-height recovery must not poison native parser depth"
4177        );
4178    }
4179
4180    #[test]
4181    fn test_parse_first_statement_with_tail_consumes_full_trigger_body() {
4182        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN INSERT INTO audit VALUES('first'); INSERT INTO audit VALUES('second'); END; SELECT 1;";
4183        let Some((statement, tail_offset)) =
4184            parse_first_statement_with_tail(sql).expect("trigger statement should parse")
4185        else {
4186            panic!("expected a trigger statement");
4187        };
4188
4189        assert!(matches!(statement, Statement::CreateTrigger(_)));
4190        assert_eq!(&sql[tail_offset..], " SELECT 1;");
4191    }
4192
4193    #[test]
4194    fn test_trigger_body_accepts_only_sqlite_trigger_commands() {
4195        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4196                   SELECT 1; \
4197                   VALUES (2); \
4198                   INSERT INTO audit VALUES (3); \
4199                   REPLACE INTO audit VALUES (4); \
4200                   UPDATE audit SET value = 5; \
4201                   DELETE FROM audit WHERE value = 6; \
4202                   WITH seed(value) AS (VALUES (7)) SELECT value FROM seed; \
4203                   SELECT 8 end; \
4204                   SELECT * FROM audit end; \
4205                   SELECT * FROM (SELECT 9) end; \
4206                   UPDATE audit SET value = source.value FROM source \
4207                     WHERE audit.id = source.id; \
4208                   END";
4209        let Statement::CreateTrigger(trigger) = parse_one(sql) else {
4210            panic!("expected CREATE TRIGGER");
4211        };
4212        assert_eq!(trigger.body.len(), 11);
4213        assert!(matches!(trigger.body[0], Statement::Select(_)));
4214        assert!(matches!(trigger.body[1], Statement::Select(_)));
4215        assert!(matches!(trigger.body[2], Statement::Insert(_)));
4216        assert!(matches!(trigger.body[3], Statement::Insert(_)));
4217        assert!(matches!(trigger.body[4], Statement::Update(_)));
4218        assert!(matches!(trigger.body[5], Statement::Delete(_)));
4219        assert!(matches!(trigger.body[6], Statement::Select(_)));
4220        assert!(matches!(trigger.body[7], Statement::Select(_)));
4221        assert!(matches!(trigger.body[8], Statement::Select(_)));
4222        assert!(matches!(trigger.body[9], Statement::Select(_)));
4223        assert!(matches!(trigger.body[10], Statement::Update(_)));
4224    }
4225
4226    #[test]
4227    fn test_trigger_body_accepts_explicit_table_aliases_only() {
4228        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4229                   UPDATE audit AS update_target SET value = 1; \
4230                   INSERT INTO audit AS insert_target(value) VALUES (2); \
4231                   DELETE FROM audit AS delete_target WHERE delete_target.value = 3; \
4232                   END";
4233        let Statement::CreateTrigger(trigger) = parse_one(sql) else {
4234            panic!("expected CREATE TRIGGER");
4235        };
4236
4237        assert!(matches!(
4238            &trigger.body[0],
4239            Statement::Update(UpdateStatement {
4240                table: QualifiedTableRef {
4241                    alias: Some(alias),
4242                    ..
4243                },
4244                ..
4245            }) if alias == "update_target"
4246        ));
4247        assert!(matches!(
4248            &trigger.body[1],
4249            Statement::Insert(InsertStatement {
4250                alias: Some(alias),
4251                ..
4252            }) if alias == "insert_target"
4253        ));
4254        assert!(matches!(
4255            &trigger.body[2],
4256            Statement::Delete(DeleteStatement {
4257                table: QualifiedTableRef {
4258                    alias: Some(alias),
4259                    ..
4260                },
4261                ..
4262            }) if alias == "delete_target"
4263        ));
4264
4265        for (body_statement, rejected) in [
4266            ("UPDATE audit target SET value = 1", "target"),
4267            ("INSERT INTO audit target(value) VALUES (1)", "target"),
4268            ("DELETE FROM audit target", "target"),
4269        ] {
4270            let sql = format!("CREATE TRIGGER trg AFTER INSERT ON t BEGIN {body_statement}; END");
4271            let error = parse_first_statement_with_tail(&sql)
4272                .expect_err("trigger-body table aliases without AS must remain rejected");
4273            assert_eq!(
4274                &sql[error.span.start as usize..error.span.end as usize],
4275                rejected,
4276                "the diagnostic for `{sql}` must identify the bare alias"
4277            );
4278        }
4279    }
4280
4281    #[test]
4282    fn test_trigger_body_rejects_empty_missing_semicolon_and_non_dml_commands() {
4283        for (sql, rejected) in [
4284            ("CREATE TRIGGER trg AFTER INSERT ON t BEGIN END", "END"),
4285            (
4286                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN CREATE TABLE bad(x); END",
4287                "CREATE",
4288            ),
4289            (
4290                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN BEGIN; END",
4291                "BEGIN",
4292            ),
4293            (
4294                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN PRAGMA foreign_keys; END",
4295                "PRAGMA",
4296            ),
4297            (
4298                "CREATE TRIGGER outer_trg AFTER INSERT ON t BEGIN \
4299                 CREATE TRIGGER inner_trg AFTER INSERT ON t BEGIN SELECT 1; END; END",
4300                "CREATE",
4301            ),
4302        ] {
4303            let error = parse_first_statement_with_tail(sql)
4304                .expect_err("invalid trigger-body grammar must fail closed");
4305            // Empty body and non-DML statements are reported as stock near-X
4306            // syntax errors at the offending token. bd-parser-syntax-error-format-6w6kp.
4307            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4308            assert_eq!(
4309                &sql[error.span.start as usize..error.span.end as usize],
4310                rejected,
4311                "the diagnostic for `{sql}` must identify the rejected token"
4312            );
4313        }
4314
4315        let missing_separator = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN SELECT 1 END";
4316        let error = parse_first_statement_with_tail(missing_separator)
4317            .expect_err("a trigger body statement still requires a semicolon");
4318        // An UnexpectedToken with an empty (EOF) span; the connection boundary
4319        // renders it as stock's `incomplete input`.
4320        // bd-parser-syntax-error-format-6w6kp (Part B).
4321        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4322        assert!(
4323            error
4324                .message
4325                .contains("expected ';' after trigger body statement"),
4326            "unexpected missing-separator diagnostic: {error:?}"
4327        );
4328        assert_eq!(
4329            error.span.start, error.span.end,
4330            "like stock SQLite's incomplete-input result, the parser must not reinterpret \
4331             the implicit END alias as the trigger terminator"
4332        );
4333    }
4334
4335    #[test]
4336    fn test_trigger_missing_separator_recovery_preserves_trailing_top_level_sql() {
4337        for body_statement in [
4338            "INSERT INTO audit VALUES (1)",
4339            "UPDATE audit SET value = 1",
4340            "DELETE FROM audit",
4341        ] {
4342            let sql = format!(
4343                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4344                 {body_statement} END; SELECT 42;"
4345            );
4346            let (statements, errors) = Parser::from_sql(&sql).parse_all();
4347
4348            assert_eq!(
4349                errors.len(),
4350                1,
4351                "the missing trigger-body separator must be reported: {sql}"
4352            );
4353            assert_eq!(
4354                statements.len(),
4355                1,
4356                "recovery must preserve the trailing top-level statement: {sql}"
4357            );
4358            assert_eq!(statements[0].to_string(), "SELECT 42");
4359        }
4360    }
4361
4362    #[test]
4363    fn test_trigger_body_rejects_stock_forbidden_dml_forms_at_exact_tokens() {
4364        for (sql, rejected) in [
4365            (
4366                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4367                 INSERT INTO main.audit VALUES (1); END",
4368                ".",
4369            ),
4370            (
4371                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4372                 UPDATE main.audit SET value = 1; END",
4373                ".",
4374            ),
4375            (
4376                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4377                 DELETE FROM main.audit; END",
4378                ".",
4379            ),
4380            (
4381                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4382                 INSERT INTO audit DEFAULT VALUES; END",
4383                "DEFAULT",
4384            ),
4385            (
4386                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4387                 UPDATE audit INDEXED BY audit_idx SET value = 1; END",
4388                "INDEXED",
4389            ),
4390            (
4391                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4392                 UPDATE audit NOT INDEXED SET value = 1; END",
4393                "NOT",
4394            ),
4395            (
4396                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4397                 DELETE FROM audit INDEXED BY audit_idx; END",
4398                "INDEXED",
4399            ),
4400            (
4401                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4402                 DELETE FROM audit NOT INDEXED; END",
4403                "NOT",
4404            ),
4405            (
4406                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4407                 UPDATE audit SET value = 1 ORDER BY value; END",
4408                "ORDER",
4409            ),
4410            (
4411                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4412                 UPDATE audit SET value = 1 LIMIT 1; END",
4413                "LIMIT",
4414            ),
4415            (
4416                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4417                 DELETE FROM audit ORDER BY value; END",
4418                "ORDER",
4419            ),
4420            (
4421                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4422                 DELETE FROM audit LIMIT 1; END",
4423                "LIMIT",
4424            ),
4425            (
4426                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4427                 INSERT INTO audit VALUES (1) RETURNING value; END",
4428                "RETURNING",
4429            ),
4430            (
4431                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4432                 UPDATE audit SET value = 1 RETURNING value; END",
4433                "RETURNING",
4434            ),
4435            (
4436                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4437                 DELETE FROM audit RETURNING value; END",
4438                "RETURNING",
4439            ),
4440            (
4441                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4442                 WITH seed(value) AS (VALUES (1)) \
4443                 INSERT INTO audit SELECT value FROM seed; END",
4444                "INSERT",
4445            ),
4446        ] {
4447            let error = parse_first_statement_with_tail(sql)
4448                .expect_err("stock-forbidden trigger DML must fail closed");
4449            // This test guards the exact-token SPAN. The kind varies by stock's
4450            // own behavior: near-X grammar errors (UnexpectedToken: DEFAULT
4451            // VALUES, ORDER BY/LIMIT, WITH..INSERT, UPDATE/DELETE RETURNING),
4452            // fixed verbatim messages (Semantic: qualified names, INDEXED BY /
4453            // NOT INDEXED, INSERT RETURNING). The exact user-facing messages
4454            // are pinned in the end-to-end keeper
4455            // (bd_parser_syntax_error_near_x). bd-parser-syntax-error-format-6w6kp.
4456            assert!(
4457                matches!(
4458                    error.kind,
4459                    ParseErrorKind::Syntax
4460                        | ParseErrorKind::UnexpectedToken
4461                        | ParseErrorKind::Semantic
4462                ),
4463                "unexpected kind for `{sql}`: {error:?}"
4464            );
4465            assert_eq!(
4466                &sql[error.span.start as usize..error.span.end as usize],
4467                rejected,
4468                "the diagnostic for `{sql}` must identify the forbidden token"
4469            );
4470        }
4471    }
4472
4473    #[test]
4474    fn test_trigger_dml_restrictions_do_not_leak_to_top_level_statements() {
4475        for sql in [
4476            "INSERT INTO main.audit DEFAULT VALUES RETURNING rowid",
4477            "UPDATE main.audit INDEXED BY audit_idx SET value = 1 \
4478             RETURNING value ORDER BY value LIMIT 1",
4479            "DELETE FROM main.audit NOT INDEXED RETURNING value ORDER BY value LIMIT 1",
4480        ] {
4481            parse_first_statement_with_tail(sql)
4482                .unwrap_or_else(|error| panic!("top-level DML must remain accepted: {error}"));
4483        }
4484    }
4485
4486    #[test]
4487    fn test_trigger_body_recovery_preserves_following_top_level_statement() {
4488        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4489                   CREATE TABLE bad(x); END; SELECT 42;";
4490        let (statements, errors) = Parser::from_sql(sql).parse_all();
4491        assert_eq!(errors.len(), 1, "expected one trigger-body grammar error");
4492        assert_eq!(
4493            statements.len(),
4494            1,
4495            "the malformed trigger must be discarded"
4496        );
4497        assert_eq!(statements[0].to_string(), "SELECT 42");
4498        assert_eq!(
4499            &sql[errors[0].span.start as usize..errors[0].span.end as usize],
4500            "CREATE"
4501        );
4502    }
4503
4504    #[test]
4505    fn test_parse_first_statement_with_tail_rejects_adjacent_statements_without_separator() {
4506        let error = parse_first_statement_with_tail("SELECT 1 SELECT 2")
4507            .expect_err("adjacent statements without a semicolon must be rejected");
4508
4509        assert!(
4510            error.message.contains("expected ';' separator"),
4511            "unexpected error: {error:?}"
4512        );
4513    }
4514
4515    #[test]
4516    fn test_parse_all_reports_and_recovers_from_missing_statement_separator() {
4517        let sql = "SELECT 1 SELECT 2";
4518        let mut parser = Parser::from_sql(sql);
4519        let (statements, errors) = parser.parse_all();
4520
4521        assert_eq!(
4522            statements.len(),
4523            2,
4524            "both independently valid statements should remain available for diagnostics"
4525        );
4526        assert_eq!(statements[0].to_string(), "SELECT 1");
4527        assert_eq!(statements[1].to_string(), "SELECT 2");
4528        assert_eq!(errors.len(), 1, "the missing separator must be reported");
4529        assert!(
4530            errors[0].message.contains("expected ';' separator"),
4531            "unexpected diagnostic: {:?}",
4532            errors[0]
4533        );
4534        assert_eq!(
4535            &sql[errors[0].span.start as usize..errors[0].span.end as usize],
4536            "SELECT",
4537            "the separator diagnostic must point at the second statement"
4538        );
4539    }
4540
4541    #[test]
4542    fn test_create_table_without_rowid_and_strict_round_trips_display() {
4543        let sql = "CREATE TABLE s (id INTEGER PRIMARY KEY) WITHOUT ROWID, STRICT";
4544        let Some((statement, _)) =
4545            parse_first_statement_with_tail(sql).expect("statement should parse")
4546        else {
4547            panic!("expected CREATE TABLE statement");
4548        };
4549
4550        assert_eq!(statement.to_string(), sql);
4551    }
4552
4553    #[test]
4554    fn test_error_recovery_does_not_fabricate_top_level_statements_from_trigger_body() {
4555        let mut parser = Parser::from_sql(
4556            "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4557             XYZZY; SELECT CASE WHEN 1 THEN 2 END AS end; SELECT 2; END; SELECT 3;",
4558        );
4559        let (stmts, errs) = parser.parse_all();
4560
4561        assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
4562        assert_eq!(
4563            stmts.len(),
4564            1,
4565            "only the trailing top-level SELECT should remain"
4566        );
4567        assert!(
4568            matches!(
4569                &stmts[0],
4570                Statement::Select(select)
4571                    if matches!(
4572                        &select.body.select,
4573                        SelectCore::Select { columns, .. }
4574                            if matches!(
4575                                columns.as_slice(),
4576                                [ResultColumn::Expr {
4577                                    expr: Expr::Literal(Literal::Integer(3), _),
4578                                    alias: None,
4579                                }]
4580                            )
4581                    )
4582            ),
4583            "parser must skip the malformed trigger instead of reinterpreting body tokens as top-level SQL: {stmts:?}"
4584        );
4585    }
4586
4587    #[test]
4588    fn test_error_recovery_skips_a_rejected_nested_trigger_before_outer_end() {
4589        for nested_prefix in ["CREATE TRIGGER", "CREATE UNIQUE TRIGGER"] {
4590            let sql = format!(
4591                "CREATE TRIGGER outer_trg AFTER INSERT ON t BEGIN \
4592                 {nested_prefix} inner_trg AFTER INSERT ON t BEGIN SELECT 1; END; \
4593                 END; SELECT 7;"
4594            );
4595            let (stmts, errs) = Parser::from_sql(&sql).parse_all();
4596
4597            assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
4598            assert_eq!(
4599                stmts.len(),
4600                1,
4601                "neither nested-trigger tokens nor the outer END may escape as top-level SQL: {sql}"
4602            );
4603            assert_eq!(stmts[0].to_string(), "SELECT 7");
4604        }
4605    }
4606
4607    #[test]
4608    fn test_error_recovery_recovers_values_statement_after_garbage() {
4609        let mut parser = Parser::from_sql("XYZZY VALUES (1);");
4610        let (stmts, errs) = parser.parse_all();
4611
4612        assert_eq!(errs.len(), 1, "expected one error for leading garbage");
4613        assert_eq!(stmts.len(), 1, "VALUES statement should still be recovered");
4614        assert!(matches!(stmts[0], Statement::Select(_)));
4615    }
4616
4617    #[test]
4618    fn test_error_recovery_does_not_swallow_top_level_sql_after_unbalanced_trigger_paren() {
4619        let mut parser = Parser::from_sql(
4620            "CREATE TRIGGER trg AFTER INSERT ON t BEGIN SELECT (1; END; SELECT 2;",
4621        );
4622        let (stmts, errs) = parser.parse_all();
4623
4624        assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
4625        assert_eq!(
4626            stmts.len(),
4627            1,
4628            "malformed trigger recovery must still preserve the trailing top-level SELECT"
4629        );
4630        assert!(
4631            matches!(
4632                &stmts[0],
4633                Statement::Select(select)
4634                    if matches!(
4635                        &select.body.select,
4636                        SelectCore::Select { columns, .. }
4637                            if matches!(
4638                                columns.as_slice(),
4639                                [ResultColumn::Expr {
4640                                    expr: Expr::Literal(Literal::Integer(2), _),
4641                                    alias: None,
4642                                }]
4643                            )
4644                    )
4645            ),
4646            "parser must stop at the trigger END even when parentheses are left unbalanced: {stmts:?}"
4647        );
4648    }
4649
4650    #[test]
4651    fn select_literal() {
4652        let stmt = parse_one("SELECT 1");
4653        assert!(matches!(stmt, Statement::Select(_)));
4654    }
4655
4656    #[test]
4657    fn select_star_from() {
4658        let stmt = parse_one("SELECT * FROM t");
4659        if let Statement::Select(s) = stmt {
4660            if let SelectCore::Select { columns, from, .. } = &s.body.select {
4661                assert!(matches!(columns[0], ResultColumn::Star));
4662                assert!(from.is_some());
4663            } else {
4664                unreachable!("expected Select core");
4665            }
4666        } else {
4667            unreachable!("expected Select");
4668        }
4669    }
4670
4671    #[test]
4672    fn select_where_order_limit() {
4673        let stmt = parse_one("SELECT a FROM t WHERE a > 1 ORDER BY a LIMIT 10 OFFSET 5");
4674        if let Statement::Select(s) = stmt {
4675            assert!(s.limit.is_some());
4676            assert_eq!(s.order_by.len(), 1);
4677        } else {
4678            unreachable!("expected Select");
4679        }
4680    }
4681
4682    #[test]
4683    fn select_limit_comma_syntax_uses_offset_then_count() {
4684        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
4685        if let Statement::Select(s) = stmt {
4686            let limit = s.limit.expect("LIMIT clause");
4687            assert!(matches!(
4688                limit.limit,
4689                Expr::Literal(Literal::Integer(10), _)
4690            ));
4691            assert!(matches!(
4692                limit.offset,
4693                Some(Expr::Literal(Literal::Integer(5), _))
4694            ));
4695        } else {
4696            unreachable!("expected Select");
4697        }
4698    }
4699
4700    #[test]
4701    fn select_order_by_nulls_first_last() {
4702        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST, b DESC NULLS LAST");
4703        if let Statement::Select(s) = stmt {
4704            assert_eq!(s.order_by.len(), 2);
4705            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
4706            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
4707            assert_eq!(s.order_by[1].direction, Some(SortDirection::Desc));
4708            assert_eq!(s.order_by[1].nulls, Some(NullsOrder::Last));
4709        } else {
4710            unreachable!("expected Select");
4711        }
4712    }
4713
4714    #[test]
4715    fn select_from_indexed_by_hint() {
4716        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
4717        if let Statement::Select(s) = stmt {
4718            if let SelectCore::Select { from, .. } = &s.body.select {
4719                let from = from.as_ref().expect("FROM clause");
4720                match &from.source {
4721                    TableOrSubquery::Table {
4722                        index_hint: Some(IndexHint::IndexedBy(name)),
4723                        ..
4724                    } => assert_eq!(name, "idx_t"),
4725                    other => unreachable!("expected indexed table source, got {other:?}"),
4726                }
4727            } else {
4728                unreachable!("expected Select core");
4729            }
4730        } else {
4731            unreachable!("expected Select");
4732        }
4733    }
4734
4735    #[test]
4736    fn select_from_not_indexed_hint() {
4737        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
4738        if let Statement::Select(s) = stmt {
4739            if let SelectCore::Select { from, .. } = &s.body.select {
4740                let from = from.as_ref().expect("FROM clause");
4741                match &from.source {
4742                    TableOrSubquery::Table {
4743                        index_hint: Some(IndexHint::NotIndexed),
4744                        ..
4745                    } => {}
4746                    other => unreachable!("expected not-indexed table source, got {other:?}"),
4747                }
4748            } else {
4749                unreachable!("expected Select core");
4750            }
4751        } else {
4752            unreachable!("expected Select");
4753        }
4754    }
4755
4756    #[test]
4757    fn select_from_table_valued_function() {
4758        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
4759        if let Statement::Select(s) = stmt {
4760            if let SelectCore::Select { from, .. } = &s.body.select {
4761                let from = from.as_ref().expect("FROM clause");
4762                match &from.source {
4763                    TableOrSubquery::TableFunction { name, args, alias } => {
4764                        assert_eq!(name, "generate_series");
4765                        assert_eq!(args.len(), 2);
4766                        assert_eq!(alias.as_deref(), Some("gs"));
4767                    }
4768                    other => unreachable!("expected table-valued function source, got {other:?}"),
4769                }
4770            } else {
4771                unreachable!("expected Select core");
4772            }
4773        } else {
4774            unreachable!("expected Select");
4775        }
4776    }
4777
4778    #[test]
4779    fn select_window_function_over_clause() {
4780        let stmt = parse_one(
4781            "SELECT sum(x) OVER (PARTITION BY y ORDER BY z \
4782             ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
4783        );
4784        if let Statement::Select(s) = stmt {
4785            if let SelectCore::Select { columns, .. } = &s.body.select {
4786                match &columns[0] {
4787                    ResultColumn::Expr {
4788                        expr:
4789                            Expr::FunctionCall {
4790                                over: Some(over), ..
4791                            },
4792                        ..
4793                    } => {
4794                        assert_eq!(over.partition_by.len(), 1);
4795                        assert_eq!(over.order_by.len(), 1);
4796                        assert!(matches!(
4797                            over.frame,
4798                            Some(FrameSpec {
4799                                frame_type: FrameType::Rows,
4800                                ..
4801                            })
4802                        ));
4803                    }
4804                    other => unreachable!("expected window function result column, got {other:?}"),
4805                }
4806            } else {
4807                unreachable!("expected Select core");
4808            }
4809        } else {
4810            unreachable!("expected Select");
4811        }
4812    }
4813
4814    #[test]
4815    fn select_named_window_definition_and_reference() {
4816        let stmt = parse_one(
4817            "SELECT sum(x) OVER win FROM t \
4818             WINDOW win AS (PARTITION BY y ORDER BY z)",
4819        );
4820        if let Statement::Select(s) = stmt {
4821            if let SelectCore::Select {
4822                columns, windows, ..
4823            } = &s.body.select
4824            {
4825                assert_eq!(windows.len(), 1);
4826                assert_eq!(windows[0].name, "win");
4827                assert_eq!(windows[0].spec.partition_by.len(), 1);
4828                assert_eq!(windows[0].spec.order_by.len(), 1);
4829                match &columns[0] {
4830                    ResultColumn::Expr {
4831                        expr:
4832                            Expr::FunctionCall {
4833                                over: Some(over), ..
4834                            },
4835                        ..
4836                    } => assert_eq!(
4837                        over.window_ref,
4838                        Some(WindowReference::Direct("win".to_owned()))
4839                    ),
4840                    other => unreachable!("expected named window function, got {other:?}"),
4841                }
4842            } else {
4843                unreachable!("expected Select core");
4844            }
4845        } else {
4846            unreachable!("expected Select");
4847        }
4848    }
4849
4850    #[test]
4851    fn select_named_window_reference_uses_sqlite_contextual_keyword_matrix() {
4852        let assert_reference = |reference: &str, name: &str| {
4853            let sql = format!("SELECT sum(1) OVER {reference} WINDOW {name} AS ()");
4854            let select = parse_full_select(&sql);
4855            let SelectCore::Select {
4856                columns, windows, ..
4857            } = &select.body.select
4858            else {
4859                panic!("expected SELECT core for `{sql}`");
4860            };
4861            assert_eq!(windows.len(), 1, "missing WINDOW definition for `{sql}`");
4862            assert_eq!(windows[0].name, name);
4863            let [
4864                ResultColumn::Expr {
4865                    expr:
4866                        Expr::FunctionCall {
4867                            over: Some(window), ..
4868                        },
4869                    alias: None,
4870                },
4871            ] = columns.as_slice()
4872            else {
4873                panic!("expected one named window call for `{sql}`");
4874            };
4875            let expected = if reference.starts_with('(') {
4876                WindowReference::Base(name.to_owned())
4877            } else {
4878                WindowReference::Direct(name.to_owned())
4879            };
4880            assert_eq!(
4881                window.window_ref,
4882                Some(expected),
4883                "wrong OVER form for `{sql}`"
4884            );
4885        };
4886
4887        for name in [
4888            "attach",
4889            "begin",
4890            "by",
4891            "cast",
4892            "current_date",
4893            "current_time",
4894            "current_timestamp",
4895            "cross",
4896            "detach",
4897            "explain",
4898            "false",
4899            "for",
4900            "glob",
4901            "inner",
4902            "left",
4903            "like",
4904            "natural",
4905            "outer",
4906            "over",
4907            "raise",
4908            "regexp",
4909            "right",
4910            "rollback",
4911            "key",
4912            "true",
4913            "window",
4914            "with",
4915        ] {
4916            assert_reference(name, name);
4917            assert_reference(&format!("({name})"), name);
4918        }
4919
4920        for name in ["partition", "range", "rows", "groups"] {
4921            assert_reference(name, name);
4922            let sql = format!("SELECT sum(1) OVER ({name})");
4923            parse_first_statement_with_tail(&sql)
4924                .expect_err("window-spec delimiters cannot be parenthesized base names");
4925        }
4926    }
4927
4928    #[test]
4929    fn select_named_window_reference_rejects_non_fallback_keywords() {
4930        for name in ["filter", "nothing", "transaction"] {
4931            for reference in [name.to_owned(), format!("({name})")] {
4932                let sql = format!("SELECT sum(1) OVER {reference}");
4933                parse_first_statement_with_tail(&sql)
4934                    .expect_err("reserved window-name tokens must not be consumed as names");
4935            }
4936            let sql = format!("SELECT sum(1) WINDOW {name} AS ()");
4937            parse_first_statement_with_tail(&sql)
4938                .expect_err("reserved WINDOW definition names must be rejected");
4939        }
4940    }
4941
4942    #[test]
4943    fn select_named_window_reference_accepts_string_and_parenthesized_names() {
4944        for (sql, expected_name) in [
4945            ("SELECT sum(1) OVER 'w' WINDOW 'w' AS ()", "w"),
4946            ("SELECT sum(1) OVER ('w') WINDOW 'w' AS ()", "w"),
4947            ("SELECT sum(1) OVER (window) WINDOW window AS ()", "window"),
4948        ] {
4949            let select = parse_full_select(sql);
4950            let SelectCore::Select {
4951                columns, windows, ..
4952            } = &select.body.select
4953            else {
4954                panic!("expected SELECT core for `{sql}`");
4955            };
4956            assert_eq!(windows.len(), 1, "missing WINDOW definition for `{sql}`");
4957            assert_eq!(windows[0].name, expected_name);
4958            let [
4959                ResultColumn::Expr {
4960                    expr:
4961                        Expr::FunctionCall {
4962                            over: Some(window), ..
4963                        },
4964                    alias: None,
4965                },
4966            ] = columns.as_slice()
4967            else {
4968                panic!("expected one named window call for `{sql}`");
4969            };
4970            let expected = if sql.contains("OVER (") {
4971                WindowReference::Base(expected_name.to_owned())
4972            } else {
4973                WindowReference::Direct(expected_name.to_owned())
4974            };
4975            assert_eq!(
4976                window.window_ref,
4977                Some(expected),
4978                "wrong OVER form for `{sql}`"
4979            );
4980        }
4981    }
4982
4983    #[test]
4984    fn over_window_prefers_named_window_reference_over_implicit_alias() {
4985        let sql = "WITH t(x) AS (VALUES (1), (2)) \
4986                   SELECT sum(x) OVER window FROM t WINDOW window AS ()";
4987        let select = parse_full_select(sql);
4988        let SelectCore::Select {
4989            columns, windows, ..
4990        } = &select.body.select
4991        else {
4992            panic!("expected SELECT core");
4993        };
4994        assert_eq!(windows.len(), 1);
4995        assert_eq!(windows[0].name, "window");
4996        assert!(matches!(
4997            columns.as_slice(),
4998            [ResultColumn::Expr {
4999                expr: Expr::FunctionCall {
5000                    over: Some(WindowSpec {
5001                        window_ref: Some(WindowReference::Direct(base_window)),
5002                        ..
5003                    }),
5004                    ..
5005                },
5006                alias: None,
5007            }] if base_window == "window"
5008        ));
5009
5010        let error = parse_first_statement_with_tail("SELECT sum(1) OVER WINDOW w AS ()")
5011            .expect_err("WINDOW after OVER is a window name, not an implicit OVER alias");
5012        // Stock: `near "WINDOW": syntax error`. bd-parser-syntax-error-format-6w6kp.
5013        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
5014    }
5015
5016    #[test]
5017    fn over_implicit_alias_disambiguation_is_preserved_at_real_boundaries() {
5018        for sql in ["SELECT sum(1) OVER, 2", "SELECT sum(1) OVER FROM t"] {
5019            let select = parse_full_select(sql);
5020            let SelectCore::Select { columns, .. } = &select.body.select else {
5021                panic!("expected SELECT core for `{sql}`");
5022            };
5023            assert!(matches!(
5024                columns.first(),
5025                Some(ResultColumn::Expr {
5026                    expr: Expr::FunctionCall { over: None, .. },
5027                    alias: Some(alias),
5028                }) if alias == "over"
5029            ));
5030        }
5031    }
5032
5033    #[test]
5034    fn overflowing_float_literals_round_trip_as_infinite_numbers() {
5035        fn assert_infinite_columns(select: &SelectStatement) {
5036            let SelectCore::Select { columns, .. } = &select.body.select else {
5037                panic!("expected SELECT core");
5038            };
5039            let [positive, negative] = columns.as_slice() else {
5040                panic!("expected positive and negative infinity columns");
5041            };
5042            assert!(matches!(
5043                positive,
5044                ResultColumn::Expr {
5045                    expr: Expr::Literal(Literal::Float(value), _),
5046                    ..
5047                } if value.is_infinite() && value.is_sign_positive()
5048            ));
5049            let ResultColumn::Expr {
5050                expr:
5051                    Expr::UnaryOp {
5052                        op: UnaryOp::Negate,
5053                        expr,
5054                        ..
5055                    },
5056                ..
5057            } = negative
5058            else {
5059                panic!("expected negative infinity to retain unary negation");
5060            };
5061            assert!(matches!(
5062                expr.as_ref(),
5063                Expr::Literal(Literal::Float(value), _)
5064                    if value.is_infinite() && value.is_sign_positive()
5065            ));
5066        }
5067
5068        let parsed = parse_full_select("SELECT 9e999, -9e999");
5069        assert_infinite_columns(&parsed);
5070        let rendered = parsed.to_string();
5071        assert_eq!(rendered, "SELECT 9e999, -9e999");
5072        let reparsed = parse_full_select(&rendered);
5073        assert_infinite_columns(&reparsed);
5074        assert_eq!(reparsed.to_string(), rendered);
5075    }
5076
5077    #[test]
5078    fn insert_values() {
5079        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2), (3, 4)");
5080        assert!(matches!(stmt, Statement::Insert(_)));
5081    }
5082
5083    #[test]
5084    fn update_set() {
5085        let stmt = parse_one("UPDATE t SET a = 1, b = 2 WHERE id = 3");
5086        assert!(matches!(stmt, Statement::Update(_)));
5087    }
5088
5089    #[test]
5090    fn delete_from() {
5091        let stmt = parse_one("DELETE FROM t WHERE id = 1");
5092        assert!(matches!(stmt, Statement::Delete(_)));
5093    }
5094
5095    #[test]
5096    fn create_table_basic() {
5097        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
5098        if let Statement::CreateTable(ct) = stmt {
5099            assert_eq!(ct.name.name, "t");
5100            if let CreateTableBody::Columns { columns, .. } = ct.body {
5101                assert_eq!(columns.len(), 2);
5102            } else {
5103                unreachable!("expected column defs");
5104            }
5105        } else {
5106            unreachable!("expected CreateTable");
5107        }
5108    }
5109
5110    #[test]
5111    fn create_index() {
5112        let stmt = parse_one("CREATE UNIQUE INDEX idx ON t (a, b DESC)");
5113        if let Statement::CreateIndex(ci) = stmt {
5114            assert!(ci.unique);
5115            assert_eq!(ci.columns.len(), 2);
5116        } else {
5117            unreachable!("expected CreateIndex");
5118        }
5119    }
5120
5121    #[test]
5122    fn drop_table_if_exists() {
5123        let stmt = parse_one("DROP TABLE IF EXISTS t");
5124        if let Statement::Drop(d) = stmt {
5125            assert!(d.if_exists);
5126            assert_eq!(d.object_type, DropObjectType::Table);
5127        } else {
5128            unreachable!("expected Drop");
5129        }
5130    }
5131
5132    #[test]
5133    fn begin_commit() {
5134        let stmts = parse_ok("BEGIN IMMEDIATE; COMMIT");
5135        assert_eq!(stmts.len(), 2);
5136        if let Statement::Begin(b) = &stmts[0] {
5137            assert_eq!(b.mode, Some(TransactionMode::Immediate));
5138        } else {
5139            unreachable!("expected Begin");
5140        }
5141        assert!(matches!(stmts[1], Statement::Commit));
5142    }
5143
5144    #[test]
5145    fn begin_concurrent() {
5146        let stmt = parse_one("BEGIN CONCURRENT");
5147        if let Statement::Begin(b) = stmt {
5148            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
5149        } else {
5150            unreachable!("expected Begin");
5151        }
5152    }
5153
5154    #[test]
5155    fn rollback_to_savepoint() {
5156        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
5157        if let Statement::Rollback(r) = stmt {
5158            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
5159        } else {
5160            unreachable!("expected Rollback");
5161        }
5162    }
5163
5164    #[test]
5165    fn explain_query_plan() {
5166        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT 1");
5167        assert!(matches!(
5168            stmt,
5169            Statement::Explain {
5170                query_plan: true,
5171                ..
5172            }
5173        ));
5174    }
5175
5176    #[test]
5177    fn pragma() {
5178        let stmt = parse_one("PRAGMA journal_mode = WAL");
5179        assert!(matches!(stmt, Statement::Pragma(_)));
5180    }
5181
5182    #[test]
5183    fn pragma_cache_size_preserves_numeric_spelling_across_sql_entrypoints() {
5184        fn argument(statement: Statement) -> String {
5185            let Statement::Pragma(pragma) = statement else {
5186                panic!("expected PRAGMA");
5187            };
5188            let Some(
5189                PragmaValue::Assign(Expr::Literal(Literal::String(value), _))
5190                | PragmaValue::Call(Expr::Literal(Literal::String(value), _)),
5191            ) = pragma.value
5192            else {
5193                panic!("expected preserved cache argument: {pragma:?}");
5194            };
5195            value
5196        }
5197
5198        let mut scratch = StatementParseScratch::default();
5199        for (sql, expected) in [
5200            ("PRAGMA cache_size=1e3", "1e3"),
5201            ("PRAGMA main.cache_size=-1E-3", "-1E-3"),
5202            ("PRAGMA temp.CACHE_SIZE(+ /* sign */ 0x10)", "0x10"),
5203            ("PRAGMA aux.default_cache_size=- 0x10", "-0x10"),
5204            (
5205                "PRAGMA cache_size=9223372036854775808",
5206                "9223372036854775808",
5207            ),
5208            (
5209                "PRAGMA cache_size=-9223372036854775809",
5210                "-9223372036854775809",
5211            ),
5212            ("PRAGMA cache_size='123abc'", "123abc"),
5213        ] {
5214            assert_eq!(argument(parse_one(sql)), expected, "ordinary: {sql}");
5215            let mut statements = parse_statements_with_scratch(sql, &mut scratch).expect(sql);
5216            assert_eq!(statements.len(), 1, "scratch: {sql}");
5217            assert_eq!(argument(statements.remove(0)), expected, "scratch: {sql}");
5218            let (statement, tail) = parse_first_statement_with_tail(sql).expect(sql).expect(sql);
5219            assert_eq!(tail, sql.len(), "tail: {sql}");
5220            assert_eq!(argument(statement), expected, "tail: {sql}");
5221        }
5222    }
5223
5224    #[test]
5225    fn pragma_cache_size_numeric_spelling_does_not_change_other_parsing() {
5226        let Statement::Pragma(pragma) = parse_one("PRAGMA busy_timeout=1e3") else {
5227            panic!("expected PRAGMA");
5228        };
5229        assert!(matches!(pragma.value,
5230            Some(PragmaValue::Assign(Expr::Literal(Literal::Float(value), _))) if value == 1000.0
5231        ));
5232
5233        // Public token-only callers cannot recover text the lexer discarded.
5234        let mut parser = Parser::new(Lexer::tokenize("PRAGMA cache_size=1e3"));
5235        let Statement::Pragma(pragma) = parser.parse_statement().expect("token-only parse") else {
5236            panic!("expected PRAGMA");
5237        };
5238        assert!(matches!(pragma.value,
5239            Some(PragmaValue::Assign(Expr::Literal(Literal::Float(value), _))) if value == 1000.0
5240        ));
5241
5242        for sql in ["PRAGMA cache_size=1_0", "PRAGMA default_cache_size=0x1_0"] {
5243            assert!(parse_first_statement_with_tail(sql).is_err(), "{sql}");
5244        }
5245        assert!(Parser::from_sql("SELECT 1e3").pragma_source_sql.is_none());
5246    }
5247
5248    #[test]
5249    fn pragma_allows_on_value() {
5250        let stmt = parse_one("PRAGMA fsqlite.serializable = ON");
5251        assert!(matches!(stmt, Statement::Pragma(_)));
5252    }
5253
5254    #[test]
5255    fn pragma_allows_delete_and_default_keyword_values() {
5256        // GH #276: DELETE and DEFAULT are reserved statement keywords but are
5257        // valid pragma values (SQLite's nmnum grammar). They must parse in value
5258        // position and resolve to their identifier name.
5259        for (sql, expected) in [
5260            ("PRAGMA journal_mode = DELETE", "delete"),
5261            ("PRAGMA temp_store = DEFAULT", "default"),
5262        ] {
5263            let Statement::Pragma(p) = parse_one(sql) else {
5264                unreachable!("expected Pragma for {sql}");
5265            };
5266            match p.value {
5267                Some(PragmaValue::Assign(Expr::Column(col, _))) => {
5268                    assert!(col.table.is_none(), "sql={sql}");
5269                    assert_eq!(&*col.column, expected, "sql={sql}");
5270                }
5271                other => unreachable!("expected Assign(Column) for {sql}, got {other:?}"),
5272            }
5273        }
5274    }
5275
5276    #[test]
5277    fn error_recovery_multiple_statements() {
5278        let mut p = Parser::from_sql("SELECT 1; XYZZY; SELECT 2");
5279        let (stmts, errs) = p.parse_all();
5280        assert_eq!(stmts.len(), 2, "should recover: stmts={stmts:?}");
5281        assert!(!errs.is_empty());
5282    }
5283
5284    #[test]
5285    fn compound_union() {
5286        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
5287        if let Statement::Select(s) = stmt {
5288            assert_eq!(s.body.compounds.len(), 1);
5289        } else {
5290            unreachable!("expected Select");
5291        }
5292    }
5293
5294    #[test]
5295    fn alter_table_rename() {
5296        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
5297        assert!(matches!(
5298            stmt,
5299            Statement::AlterTable(AlterTableStatement {
5300                action: AlterTableAction::RenameTo(_),
5301                ..
5302            })
5303        ));
5304    }
5305
5306    // -----------------------------------------------------------------------
5307    // bd-2kvo Phase 3 acceptance: parser join types
5308    // -----------------------------------------------------------------------
5309
5310    #[test]
5311    fn test_parser_join_inner() {
5312        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
5313        if let Statement::Select(s) = stmt {
5314            if let SelectCore::Select { from, .. } = &s.body.select {
5315                let from = from.as_ref().expect("FROM clause");
5316                assert!(!from.joins.is_empty());
5317                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
5318            } else {
5319                unreachable!("expected Select core");
5320            }
5321        } else {
5322            unreachable!("expected Select");
5323        }
5324    }
5325
5326    #[test]
5327    fn test_parser_join_left() {
5328        let stmt = parse_one("SELECT * FROM a LEFT JOIN b ON a.id = b.a_id");
5329        if let Statement::Select(s) = stmt {
5330            if let SelectCore::Select { from, .. } = &s.body.select {
5331                let from = from.as_ref().expect("FROM clause");
5332                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
5333            } else {
5334                unreachable!("expected Select core");
5335            }
5336        } else {
5337            unreachable!("expected Select");
5338        }
5339    }
5340
5341    #[test]
5342    fn test_parser_join_left_outer() {
5343        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
5344        if let Statement::Select(s) = stmt {
5345            if let SelectCore::Select { from, .. } = &s.body.select {
5346                let from = from.as_ref().expect("FROM clause");
5347                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
5348            } else {
5349                unreachable!("expected Select core");
5350            }
5351        } else {
5352            unreachable!("expected Select");
5353        }
5354    }
5355
5356    #[test]
5357    fn test_parser_join_right() {
5358        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
5359        if let Statement::Select(s) = stmt {
5360            if let SelectCore::Select { from, .. } = &s.body.select {
5361                let from = from.as_ref().expect("FROM clause");
5362                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
5363            } else {
5364                unreachable!("expected Select core");
5365            }
5366        } else {
5367            unreachable!("expected Select");
5368        }
5369    }
5370
5371    #[test]
5372    fn test_parser_join_full() {
5373        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
5374        if let Statement::Select(s) = stmt {
5375            if let SelectCore::Select { from, .. } = &s.body.select {
5376                let from = from.as_ref().expect("FROM clause");
5377                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
5378            } else {
5379                unreachable!("expected Select core");
5380            }
5381        } else {
5382            unreachable!("expected Select");
5383        }
5384    }
5385
5386    #[test]
5387    fn test_parser_join_full_outer_with_semicolon() {
5388        let stmt = parse_one("SELECT l.name, r.tag FROM l FULL OUTER JOIN r ON l.id = r.l_id;");
5389        if let Statement::Select(s) = stmt {
5390            if let SelectCore::Select { from, .. } = &s.body.select {
5391                let from = from.as_ref().expect("FROM clause");
5392                assert_eq!(from.joins.len(), 1);
5393                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
5394            } else {
5395                unreachable!("expected Select core");
5396            }
5397        } else {
5398            unreachable!("expected Select");
5399        }
5400    }
5401
5402    #[test]
5403    fn test_parser_join_cross() {
5404        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
5405        if let Statement::Select(s) = stmt {
5406            if let SelectCore::Select { from, .. } = &s.body.select {
5407                let from = from.as_ref().expect("FROM clause");
5408                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
5409            } else {
5410                unreachable!("expected Select core");
5411            }
5412        } else {
5413            unreachable!("expected Select");
5414        }
5415    }
5416
5417    #[test]
5418    fn test_parser_join_natural() {
5419        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
5420        if let Statement::Select(s) = stmt {
5421            if let SelectCore::Select { from, .. } = &s.body.select {
5422                let from = from.as_ref().expect("FROM clause");
5423                assert!(from.joins[0].join_type.natural);
5424            } else {
5425                unreachable!("expected Select core");
5426            }
5427        } else {
5428            unreachable!("expected Select");
5429        }
5430    }
5431
5432    #[test]
5433    fn test_parser_join_using() {
5434        let stmt = parse_one("SELECT * FROM a JOIN b USING (id)");
5435        if let Statement::Select(s) = stmt {
5436            if let SelectCore::Select { from, .. } = &s.body.select {
5437                let from = from.as_ref().expect("FROM clause");
5438                assert!(matches!(
5439                    from.joins[0].constraint,
5440                    Some(JoinConstraint::Using(_))
5441                ));
5442            } else {
5443                unreachable!("expected Select core");
5444            }
5445        } else {
5446            unreachable!("expected Select");
5447        }
5448    }
5449
5450    #[test]
5451    fn test_parser_join_comma() {
5452        // Comma-join is an implicit cross join.
5453        let stmt = parse_one("SELECT * FROM a, b WHERE a.id = b.a_id");
5454        if let Statement::Select(s) = stmt {
5455            if let SelectCore::Select { from, .. } = &s.body.select {
5456                let from = from.as_ref().expect("FROM clause");
5457                assert!(!from.joins.is_empty());
5458                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
5459            } else {
5460                unreachable!("expected Select core");
5461            }
5462        } else {
5463            unreachable!("expected Select");
5464        }
5465    }
5466
5467    // -----------------------------------------------------------------------
5468    // bd-2kvo Phase 3 acceptance: CTE syntax
5469    // -----------------------------------------------------------------------
5470
5471    #[test]
5472    fn test_parser_cte_basic() {
5473        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
5474        if let Statement::Select(s) = stmt {
5475            let with = s.with.as_ref().expect("WITH clause");
5476            assert!(!with.recursive);
5477            assert_eq!(with.ctes.len(), 1);
5478            assert_eq!(with.ctes[0].name, "cte");
5479        } else {
5480            unreachable!("expected Select");
5481        }
5482    }
5483
5484    #[test]
5485    fn test_parser_cte_multiple() {
5486        let stmt = parse_one("WITH a AS (SELECT 1), b AS (SELECT 2) SELECT * FROM a, b");
5487        if let Statement::Select(s) = stmt {
5488            let with = s.with.as_ref().expect("WITH clause");
5489            assert_eq!(with.ctes.len(), 2);
5490            assert_eq!(with.ctes[0].name, "a");
5491            assert_eq!(with.ctes[1].name, "b");
5492        } else {
5493            unreachable!("expected Select");
5494        }
5495    }
5496
5497    #[test]
5498    fn test_parser_cte_recursive() {
5499        let stmt = parse_one(
5500            "WITH RECURSIVE cnt(x) AS (\
5501             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
5502             ) SELECT x FROM cnt",
5503        );
5504        if let Statement::Select(s) = stmt {
5505            let with = s.with.as_ref().expect("WITH clause");
5506            assert!(with.recursive);
5507            assert_eq!(with.ctes[0].name, "cnt");
5508            assert_eq!(with.ctes[0].columns, vec!["x".to_owned()]);
5509        } else {
5510            unreachable!("expected Select");
5511        }
5512    }
5513
5514    #[test]
5515    fn test_parser_cte_materialized() {
5516        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
5517        if let Statement::Select(s) = stmt {
5518            let with = s.with.as_ref().expect("WITH clause");
5519            assert_eq!(
5520                with.ctes[0].materialized,
5521                Some(CteMaterialized::Materialized)
5522            );
5523        } else {
5524            unreachable!("expected Select");
5525        }
5526    }
5527
5528    // -----------------------------------------------------------------------
5529    // bd-2d6i §12.1 SELECT full syntax acceptance tests
5530    // -----------------------------------------------------------------------
5531
5532    #[test]
5533    fn test_select_table_star() {
5534        let stmt = parse_one("SELECT t1.* FROM t1, t2");
5535        if let Statement::Select(s) = stmt {
5536            if let SelectCore::Select { columns, .. } = &s.body.select {
5537                assert!(
5538                    matches!(&columns[0], ResultColumn::TableStar(t) if t == &QualifiedName::bare("t1")),
5539                    "expected TableStar(t1), got {:?}",
5540                    columns[0]
5541                );
5542            } else {
5543                unreachable!("expected Select core");
5544            }
5545        } else {
5546            unreachable!("expected Select");
5547        }
5548    }
5549
5550    #[test]
5551    fn test_select_schema_table_star() {
5552        let stmt = parse_one("SELECT aux.t1.* FROM aux.t1");
5553        if let Statement::Select(s) = stmt {
5554            if let SelectCore::Select { columns, .. } = &s.body.select {
5555                assert!(
5556                    matches!(&columns[0], ResultColumn::TableStar(t) if t == &QualifiedName::qualified("aux", "t1")),
5557                    "expected TableStar(aux.t1), got {:?}",
5558                    columns[0]
5559                );
5560            } else {
5561                unreachable!("expected Select core");
5562            }
5563        } else {
5564            unreachable!("expected Select");
5565        }
5566    }
5567
5568    #[test]
5569    fn test_select_expr_alias() {
5570        let stmt = parse_one("SELECT x + 1 AS result FROM t");
5571        if let Statement::Select(s) = stmt {
5572            if let SelectCore::Select { columns, .. } = &s.body.select {
5573                match &columns[0] {
5574                    ResultColumn::Expr {
5575                        alias: Some(alias), ..
5576                    } => assert_eq!(alias, "result"),
5577                    other => unreachable!("expected aliased expr column, got {other:?}"),
5578                }
5579            } else {
5580                unreachable!("expected Select core");
5581            }
5582        } else {
5583            unreachable!("expected Select");
5584        }
5585    }
5586
5587    #[test]
5588    fn test_select_distinct_keyword() {
5589        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
5590        if let Statement::Select(s) = stmt {
5591            if let SelectCore::Select {
5592                distinct, columns, ..
5593            } = &s.body.select
5594            {
5595                assert_eq!(*distinct, Distinctness::Distinct);
5596                assert_eq!(columns.len(), 2);
5597            } else {
5598                unreachable!("expected Select core");
5599            }
5600        } else {
5601            unreachable!("expected Select");
5602        }
5603    }
5604
5605    #[test]
5606    fn test_select_values_clause() {
5607        let stmt = parse_one("VALUES (1, 2), (3, 4)");
5608        if let Statement::Select(s) = stmt {
5609            if let SelectCore::Values(rows) = &s.body.select {
5610                assert_eq!(rows.len(), 2);
5611                assert_eq!(rows[0].len(), 2);
5612                assert_eq!(rows[1].len(), 2);
5613            } else {
5614                unreachable!("expected Values core");
5615            }
5616        } else {
5617            unreachable!("expected Select");
5618        }
5619    }
5620
5621    #[test]
5622    fn test_values_representation_captures_leading_and_nested_with_timing() {
5623        let plain = parse_one("VALUES (1), (2), (3)");
5624        assert_eq!(
5625            top_level_values(&plain).representation(),
5626            ValuesRepresentation::Deferred {
5627                force_union_all_from: None,
5628            }
5629        );
5630
5631        let leading = parse_one("WITH c(x) AS (SELECT 1) VALUES (2), (3)");
5632        assert_eq!(top_level_values(&leading).force_union_all_from(), Some(0));
5633
5634        let nested_first =
5635            parse_one("VALUES ((WITH c(x) AS (SELECT 1) SELECT x FROM c)), (2), (3)");
5636        assert_eq!(
5637            top_level_values(&nested_first).force_union_all_from(),
5638            Some(0)
5639        );
5640
5641        let nested_second =
5642            parse_one("VALUES (1), ((WITH c(x) AS (SELECT 2) SELECT x FROM c)), (3)");
5643        assert_eq!(
5644            top_level_values(&nested_second).force_union_all_from(),
5645            Some(1)
5646        );
5647    }
5648
5649    #[test]
5650    fn test_values_representation_is_sticky_but_not_retroactive_within_a_statement() {
5651        let later_with = parse_full_select(
5652            "SELECT (VALUES (1), (2)), (WITH c(x) AS (SELECT 3) SELECT x FROM c)",
5653        );
5654        let SelectCore::Select { columns, .. } = &later_with.body.select else {
5655            panic!("expected SELECT core");
5656        };
5657        let ResultColumn::Expr { expr, .. } = &columns[0] else {
5658            panic!("expected expression result column");
5659        };
5660        assert_eq!(scalar_subquery_values(expr).force_union_all_from(), None);
5661
5662        let earlier_with = parse_full_select(
5663            "SELECT (WITH c(x) AS (SELECT 3) SELECT x FROM c), (VALUES (1), (2))",
5664        );
5665        let SelectCore::Select { columns, .. } = &earlier_with.body.select else {
5666            panic!("expected SELECT core");
5667        };
5668        let ResultColumn::Expr { expr, .. } = &columns[1] else {
5669            panic!("expected expression result column");
5670        };
5671        assert_eq!(scalar_subquery_values(expr).force_union_all_from(), Some(0));
5672    }
5673
5674    #[test]
5675    fn test_values_with_state_resets_between_parse_all_statements() {
5676        let statements = parse_ok("WITH c(x) AS (SELECT 1) VALUES (2), (3); VALUES (4), (5);");
5677        assert_eq!(statements.len(), 2);
5678        assert_eq!(
5679            top_level_values(&statements[0]).force_union_all_from(),
5680            Some(0)
5681        );
5682        assert_eq!(
5683            top_level_values(&statements[1]).force_union_all_from(),
5684            None
5685        );
5686    }
5687
5688    #[test]
5689    fn test_direct_values_parser_captures_nested_with_row_boundary() {
5690        let mut parser =
5691            Parser::from_sql("VALUES (1), ((WITH c(x) AS (SELECT 2) SELECT x FROM c)), (3)");
5692        let parsed = parser
5693            .parse_select_core_tracked()
5694            .expect("direct VALUES parser must succeed");
5695        let SelectCore::Values(values) = parsed.value else {
5696            panic!("direct parser must return VALUES");
5697        };
5698
5699        assert_eq!(values.force_union_all_from(), Some(1));
5700        assert_eq!(values.len(), 3);
5701    }
5702
5703    #[test]
5704    fn test_insert_values_extraction_retains_nested_values_representation() {
5705        let statement =
5706            parse_one("WITH c(x) AS (SELECT 1) INSERT INTO t VALUES ((VALUES (2), (3)))");
5707        let Statement::Insert(insert) = statement else {
5708            panic!("expected INSERT statement");
5709        };
5710        let InsertSource::Values(rows) = insert.source else {
5711            panic!("expected INSERT VALUES source");
5712        };
5713        let [row] = rows.as_slice() else {
5714            panic!("expected one INSERT row");
5715        };
5716        let [expr] = row.as_slice() else {
5717            panic!("expected one INSERT column");
5718        };
5719
5720        assert_eq!(scalar_subquery_values(expr).force_union_all_from(), Some(0));
5721    }
5722
5723    #[test]
5724    fn test_select_group_by_having() {
5725        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
5726        if let Statement::Select(s) = stmt {
5727            if let SelectCore::Select {
5728                group_by, having, ..
5729            } = &s.body.select
5730            {
5731                assert_eq!(group_by.len(), 1);
5732                assert!(having.is_some(), "HAVING clause must be present");
5733            } else {
5734                unreachable!("expected Select core");
5735            }
5736        } else {
5737            unreachable!("expected Select");
5738        }
5739    }
5740
5741    #[test]
5742    fn test_compound_union() {
5743        let stmt = parse_one("SELECT 1 UNION SELECT 2");
5744        if let Statement::Select(s) = stmt {
5745            assert_eq!(s.body.compounds.len(), 1);
5746            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
5747        } else {
5748            unreachable!("expected Select");
5749        }
5750    }
5751
5752    #[test]
5753    fn test_compound_union_all() {
5754        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
5755        if let Statement::Select(s) = stmt {
5756            assert_eq!(s.body.compounds.len(), 1);
5757            assert_eq!(s.body.compounds[0].0, CompoundOp::UnionAll);
5758        } else {
5759            unreachable!("expected Select");
5760        }
5761    }
5762
5763    #[test]
5764    fn test_compound_intersect() {
5765        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
5766        if let Statement::Select(s) = stmt {
5767            assert_eq!(s.body.compounds.len(), 1);
5768            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
5769        } else {
5770            unreachable!("expected Select");
5771        }
5772    }
5773
5774    #[test]
5775    fn test_compound_except() {
5776        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
5777        if let Statement::Select(s) = stmt {
5778            assert_eq!(s.body.compounds.len(), 1);
5779            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
5780        } else {
5781            unreachable!("expected Select");
5782        }
5783    }
5784
5785    #[test]
5786    fn test_compound_order_applies_to_whole() {
5787        // ORDER BY and LIMIT apply to the entire compound result per SQL spec.
5788        let stmt = parse_one("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
5789        if let Statement::Select(s) = stmt {
5790            assert_eq!(s.body.compounds.len(), 1);
5791            assert_eq!(s.order_by.len(), 1, "ORDER BY must be on compound");
5792            assert!(s.limit.is_some(), "LIMIT must be on compound");
5793        } else {
5794            unreachable!("expected Select");
5795        }
5796    }
5797
5798    #[test]
5799    fn test_compound_three_way() {
5800        let stmt = parse_one("SELECT 1 UNION SELECT 2 INTERSECT SELECT 3");
5801        if let Statement::Select(s) = stmt {
5802            assert_eq!(s.body.compounds.len(), 2);
5803            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
5804            assert_eq!(s.body.compounds[1].0, CompoundOp::Intersect);
5805        } else {
5806            unreachable!("expected Select");
5807        }
5808    }
5809
5810    #[test]
5811    fn test_cte_not_materialized() {
5812        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
5813        if let Statement::Select(s) = stmt {
5814            let with = s.with.as_ref().expect("WITH clause");
5815            assert_eq!(
5816                with.ctes[0].materialized,
5817                Some(CteMaterialized::NotMaterialized)
5818            );
5819        } else {
5820            unreachable!("expected Select");
5821        }
5822    }
5823
5824    #[test]
5825    fn test_cte_with_explicit_columns() {
5826        let stmt = parse_one("WITH cte(a, b, c) AS (SELECT 1, 2, 3) SELECT * FROM cte");
5827        if let Statement::Select(s) = stmt {
5828            let with = s.with.as_ref().expect("WITH clause");
5829            assert_eq!(with.ctes[0].columns, vec!["a", "b", "c"]);
5830        } else {
5831            unreachable!("expected Select");
5832        }
5833    }
5834
5835    #[test]
5836    fn test_window_frame_range() {
5837        let stmt = parse_one(
5838            "SELECT sum(x) OVER (ORDER BY y RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t",
5839        );
5840        if let Statement::Select(s) = stmt {
5841            if let SelectCore::Select { columns, .. } = &s.body.select {
5842                match &columns[0] {
5843                    ResultColumn::Expr {
5844                        expr:
5845                            Expr::FunctionCall {
5846                                over: Some(over), ..
5847                            },
5848                        ..
5849                    } => {
5850                        let frame = over.frame.as_ref().expect("frame spec");
5851                        assert_eq!(frame.frame_type, FrameType::Range);
5852                        assert!(matches!(frame.start, FrameBound::UnboundedPreceding));
5853                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
5854                    }
5855                    other => unreachable!("expected window function, got {other:?}"),
5856                }
5857            } else {
5858                unreachable!("expected Select core");
5859            }
5860        } else {
5861            unreachable!("expected Select");
5862        }
5863    }
5864
5865    #[test]
5866    fn test_window_frame_groups() {
5867        let stmt = parse_one(
5868            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
5869        );
5870        if let Statement::Select(s) = stmt {
5871            if let SelectCore::Select { columns, .. } = &s.body.select {
5872                match &columns[0] {
5873                    ResultColumn::Expr {
5874                        expr:
5875                            Expr::FunctionCall {
5876                                over: Some(over), ..
5877                            },
5878                        ..
5879                    } => {
5880                        let frame = over.frame.as_ref().expect("frame spec");
5881                        assert_eq!(frame.frame_type, FrameType::Groups);
5882                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
5883                        assert!(matches!(frame.end, Some(FrameBound::Following(_))));
5884                    }
5885                    other => unreachable!("expected window function, got {other:?}"),
5886                }
5887            } else {
5888                unreachable!("expected Select core");
5889            }
5890        } else {
5891            unreachable!("expected Select");
5892        }
5893    }
5894
5895    #[test]
5896    fn test_window_frame_exclude_current_row() {
5897        let stmt = parse_one(
5898            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
5899             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
5900        );
5901        if let Statement::Select(s) = stmt {
5902            if let SelectCore::Select { columns, .. } = &s.body.select {
5903                match &columns[0] {
5904                    ResultColumn::Expr {
5905                        expr:
5906                            Expr::FunctionCall {
5907                                over: Some(over), ..
5908                            },
5909                        ..
5910                    } => {
5911                        let frame = over.frame.as_ref().expect("frame spec");
5912                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
5913                    }
5914                    other => unreachable!("expected window function, got {other:?}"),
5915                }
5916            } else {
5917                unreachable!("expected Select core");
5918            }
5919        } else {
5920            unreachable!("expected Select");
5921        }
5922    }
5923
5924    #[test]
5925    fn test_window_frame_exclude_ties() {
5926        let stmt = parse_one(
5927            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
5928             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
5929        );
5930        if let Statement::Select(s) = stmt {
5931            if let SelectCore::Select { columns, .. } = &s.body.select {
5932                match &columns[0] {
5933                    ResultColumn::Expr {
5934                        expr:
5935                            Expr::FunctionCall {
5936                                over: Some(over), ..
5937                            },
5938                        ..
5939                    } => {
5940                        let frame = over.frame.as_ref().expect("frame spec");
5941                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
5942                    }
5943                    other => unreachable!("expected window function, got {other:?}"),
5944                }
5945            } else {
5946                unreachable!("expected Select core");
5947            }
5948        } else {
5949            unreachable!("expected Select");
5950        }
5951    }
5952
5953    #[test]
5954    fn test_window_frame_exclude_group() {
5955        let stmt =
5956            parse_one("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
5957        if let Statement::Select(s) = stmt {
5958            if let SelectCore::Select { columns, .. } = &s.body.select {
5959                match &columns[0] {
5960                    ResultColumn::Expr {
5961                        expr:
5962                            Expr::FunctionCall {
5963                                over: Some(over), ..
5964                            },
5965                        ..
5966                    } => {
5967                        let frame = over.frame.as_ref().expect("frame spec");
5968                        assert_eq!(frame.frame_type, FrameType::Groups);
5969                        assert_eq!(frame.exclude, Some(FrameExclude::Group));
5970                    }
5971                    other => unreachable!("expected window function, got {other:?}"),
5972                }
5973            } else {
5974                unreachable!("expected Select core");
5975            }
5976        } else {
5977            unreachable!("expected Select");
5978        }
5979    }
5980
5981    #[test]
5982    fn test_window_frame_unbounded_following() {
5983        let stmt = parse_one(
5984            "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM t",
5985        );
5986        if let Statement::Select(s) = stmt {
5987            if let SelectCore::Select { columns, .. } = &s.body.select {
5988                match &columns[0] {
5989                    ResultColumn::Expr {
5990                        expr:
5991                            Expr::FunctionCall {
5992                                over: Some(over), ..
5993                            },
5994                        ..
5995                    } => {
5996                        let frame = over.frame.as_ref().expect("frame spec");
5997                        assert!(matches!(frame.start, FrameBound::CurrentRow));
5998                        assert!(matches!(frame.end, Some(FrameBound::UnboundedFollowing)));
5999                    }
6000                    other => unreachable!("expected window function, got {other:?}"),
6001                }
6002            } else {
6003                unreachable!("expected Select core");
6004            }
6005        } else {
6006            unreachable!("expected Select");
6007        }
6008    }
6009
6010    #[test]
6011    fn test_window_frame_rejects_illegal_bound_order_with_exact_span() {
6012        for (sql, rejected) in [
6013            (
6014                "SELECT sum(x) OVER (ROWS UNBOUNDED FOLLOWING) FROM t",
6015                "UNBOUNDED",
6016            ),
6017            ("SELECT sum(x) OVER (ROWS 1 FOLLOWING) FROM t", "1"),
6018            (
6019                "SELECT sum(x) OVER (ROWS BETWEEN UNBOUNDED FOLLOWING AND UNBOUNDED FOLLOWING) FROM t",
6020                "UNBOUNDED",
6021            ),
6022            (
6023                "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING) FROM t",
6024                "UNBOUNDED",
6025            ),
6026            (
6027                "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND 1 PRECEDING) FROM t",
6028                "1",
6029            ),
6030            (
6031                "SELECT sum(x) OVER (ROWS BETWEEN 1 FOLLOWING AND CURRENT ROW) FROM t",
6032                "CURRENT",
6033            ),
6034        ] {
6035            let error = parse_first_statement_with_tail(sql)
6036                .expect_err("illegal window-frame boundaries must be rejected");
6037            assert_eq!(error.kind, ParseErrorKind::Syntax);
6038            assert_eq!(
6039                &sql[error.span.start as usize..error.span.end as usize],
6040                rejected,
6041                "the diagnostic for `{sql}` must point at the illegal boundary"
6042            );
6043        }
6044    }
6045
6046    #[test]
6047    fn test_window_frame_accepts_legal_categorical_order_without_offset_comparison() {
6048        for sql in [
6049            "SELECT sum(x) OVER (ROWS 1 PRECEDING) FROM t",
6050            "SELECT sum(x) OVER (ROWS BETWEEN 1 PRECEDING AND 2 PRECEDING) FROM t",
6051            "SELECT sum(x) OVER (ROWS BETWEEN 2 FOLLOWING AND UNBOUNDED FOLLOWING) FROM t",
6052            "SELECT sum(x) OVER (RANGE BETWEEN CURRENT ROW AND 1 FOLLOWING) FROM t",
6053            "SELECT sum(x) OVER (GROUPS BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM t",
6054        ] {
6055            parse_full_select(sql);
6056        }
6057    }
6058
6059    #[test]
6060    fn test_filter_clause_aggregate() {
6061        let stmt = parse_one("SELECT count(*) FILTER (WHERE x > 0) FROM t");
6062        if let Statement::Select(s) = stmt {
6063            if let SelectCore::Select { columns, .. } = &s.body.select {
6064                match &columns[0] {
6065                    ResultColumn::Expr {
6066                        expr: Expr::FunctionCall { filter, .. },
6067                        ..
6068                    } => {
6069                        assert!(
6070                            filter.is_some(),
6071                            "FILTER clause must be present on aggregate"
6072                        );
6073                    }
6074                    other => unreachable!("expected function call with filter, got {other:?}"),
6075                }
6076            } else {
6077                unreachable!("expected Select core");
6078            }
6079        } else {
6080            unreachable!("expected Select");
6081        }
6082    }
6083
6084    #[test]
6085    fn test_filter_clause_window() {
6086        let stmt = parse_one("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
6087        if let Statement::Select(s) = stmt {
6088            if let SelectCore::Select { columns, .. } = &s.body.select {
6089                match &columns[0] {
6090                    ResultColumn::Expr {
6091                        expr:
6092                            Expr::FunctionCall {
6093                                filter,
6094                                over: Some(_),
6095                                ..
6096                            },
6097                        ..
6098                    } => {
6099                        assert!(
6100                            filter.is_some(),
6101                            "FILTER clause must be present on window function"
6102                        );
6103                    }
6104                    other => unreachable!("expected window function with filter, got {other:?}"),
6105                }
6106            } else {
6107                unreachable!("expected Select core");
6108            }
6109        } else {
6110            unreachable!("expected Select");
6111        }
6112    }
6113
6114    #[test]
6115    fn test_subquery_in_from() {
6116        let stmt = parse_one("SELECT sub.x FROM (SELECT 1 AS x) AS sub");
6117        if let Statement::Select(s) = stmt {
6118            if let SelectCore::Select { from, .. } = &s.body.select {
6119                let from = from.as_ref().expect("FROM clause");
6120                match &from.source {
6121                    TableOrSubquery::Subquery { alias, .. } => {
6122                        assert_eq!(alias.as_deref(), Some("sub"));
6123                    }
6124                    other => unreachable!("expected subquery source, got {other:?}"),
6125                }
6126            } else {
6127                unreachable!("expected Select core");
6128            }
6129        } else {
6130            unreachable!("expected Select");
6131        }
6132    }
6133
6134    #[test]
6135    fn test_multiple_joins_chain() {
6136        let stmt = parse_one(
6137            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id \
6138             LEFT JOIN c ON b.id = c.b_id \
6139             CROSS JOIN d",
6140        );
6141        if let Statement::Select(s) = stmt {
6142            if let SelectCore::Select { from, .. } = &s.body.select {
6143                let from = from.as_ref().expect("FROM clause");
6144                assert_eq!(from.joins.len(), 3);
6145                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
6146                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
6147                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
6148            } else {
6149                unreachable!("expected Select core");
6150            }
6151        } else {
6152            unreachable!("expected Select");
6153        }
6154    }
6155
6156    #[test]
6157    fn test_natural_left_join() {
6158        let stmt = parse_one("SELECT * FROM a NATURAL LEFT JOIN b");
6159        if let Statement::Select(s) = stmt {
6160            if let SelectCore::Select { from, .. } = &s.body.select {
6161                let from = from.as_ref().expect("FROM clause");
6162                let jt = &from.joins[0].join_type;
6163                assert!(jt.natural, "must be NATURAL");
6164                assert_eq!(jt.kind, JoinKind::Left);
6165            } else {
6166                unreachable!("expected Select core");
6167            }
6168        } else {
6169            unreachable!("expected Select");
6170        }
6171    }
6172
6173    #[test]
6174    fn test_select_nulls_first_default_asc() {
6175        // Verify NULLS FIRST with explicit ASC direction.
6176        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
6177        if let Statement::Select(s) = stmt {
6178            assert_eq!(s.order_by.len(), 1);
6179            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
6180            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
6181        } else {
6182            unreachable!("expected Select");
6183        }
6184    }
6185
6186    #[test]
6187    fn test_select_nulls_last_desc() {
6188        // Verify NULLS LAST with explicit DESC direction.
6189        let stmt = parse_one("SELECT a FROM t ORDER BY a DESC NULLS LAST");
6190        if let Statement::Select(s) = stmt {
6191            assert_eq!(s.order_by.len(), 1);
6192            assert_eq!(s.order_by[0].direction, Some(SortDirection::Desc));
6193            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
6194        } else {
6195            unreachable!("expected Select");
6196        }
6197    }
6198
6199    // -----------------------------------------------------------------------
6200    // bd-2d6i §12.1 roundtrip coverage for advanced SELECT forms
6201    // -----------------------------------------------------------------------
6202
6203    #[test]
6204    fn test_roundtrip_select_filter_clause() {
6205        assert_roundtrip("SELECT count(*) FILTER (WHERE x > 0) FROM t");
6206    }
6207
6208    #[test]
6209    fn test_roundtrip_select_window_frame_groups() {
6210        assert_roundtrip(
6211            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
6212        );
6213    }
6214
6215    #[test]
6216    fn test_roundtrip_select_window_frame_exclude() {
6217        assert_roundtrip(
6218            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6219             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
6220        );
6221        assert_roundtrip(
6222            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6223             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
6224        );
6225        assert_roundtrip("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
6226    }
6227
6228    #[test]
6229    fn test_roundtrip_select_nulls_order() {
6230        assert_roundtrip("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
6231        assert_roundtrip("SELECT a FROM t ORDER BY a DESC NULLS LAST");
6232    }
6233
6234    #[test]
6235    fn test_roundtrip_select_values() {
6236        assert_roundtrip("VALUES (1, 2), (3, 4)");
6237    }
6238
6239    #[test]
6240    fn test_roundtrip_select_compound_order_limit() {
6241        assert_roundtrip("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
6242    }
6243
6244    #[test]
6245    fn test_roundtrip_select_cte_not_materialized() {
6246        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
6247    }
6248
6249    #[test]
6250    fn test_roundtrip_select_natural_left_join() {
6251        assert_roundtrip("SELECT * FROM a NATURAL LEFT JOIN b");
6252    }
6253
6254    #[test]
6255    fn test_roundtrip_select_indexed_by() {
6256        assert_roundtrip("SELECT * FROM t INDEXED BY idx_t WHERE x = 1");
6257    }
6258
6259    #[test]
6260    fn test_roundtrip_select_filter_window_combined() {
6261        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
6262    }
6263
6264    #[test]
6265    fn test_serializer_regression_window_base_name_and_extensions_roundtrip() {
6266        assert_roundtrip("SELECT sum(x) OVER base FROM t");
6267        assert_roundtrip(
6268            "SELECT sum(x) OVER (base PARTITION BY p ORDER BY y \
6269             ROWS BETWEEN z PRECEDING AND CURRENT ROW) FROM t",
6270        );
6271    }
6272
6273    #[test]
6274    fn test_roundtrip_select_three_way_compound() {
6275        assert_roundtrip("SELECT 1 UNION SELECT 2 EXCEPT SELECT 3");
6276    }
6277
6278    #[test]
6279    fn test_roundtrip_select_multiple_joins() {
6280        assert_roundtrip(
6281            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id LEFT JOIN c ON b.id = c.b_id",
6282        );
6283    }
6284
6285    // -----------------------------------------------------------------------
6286    // bd-2d6i §12.1 — remaining required tests (exact names per bead spec)
6287    // -----------------------------------------------------------------------
6288
6289    #[test]
6290    fn test_select_star() {
6291        // SELECT * returns all columns from all tables.
6292        let stmt = parse_one("SELECT * FROM t");
6293        if let Statement::Select(s) = stmt {
6294            if let SelectCore::Select { columns, .. } = &s.body.select {
6295                assert!(matches!(columns[0], ResultColumn::Star));
6296            } else {
6297                unreachable!("expected Select core");
6298            }
6299        } else {
6300            unreachable!("expected Select");
6301        }
6302    }
6303
6304    #[test]
6305    fn test_inner_join_on() {
6306        // INNER JOIN ON produces correct intersection.
6307        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
6308        if let Statement::Select(s) = stmt {
6309            if let SelectCore::Select { from, .. } = &s.body.select {
6310                let from = from.as_ref().expect("FROM clause");
6311                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
6312                assert!(matches!(
6313                    from.joins[0].constraint,
6314                    Some(JoinConstraint::On(_))
6315                ));
6316            } else {
6317                unreachable!("expected Select core");
6318            }
6319        } else {
6320            unreachable!("expected Select");
6321        }
6322    }
6323
6324    #[test]
6325    fn test_left_outer_join() {
6326        // LEFT JOIN returns all left rows with NULLs for non-matching right.
6327        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
6328        if let Statement::Select(s) = stmt {
6329            if let SelectCore::Select { from, .. } = &s.body.select {
6330                let from = from.as_ref().expect("FROM clause");
6331                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
6332            } else {
6333                unreachable!("expected Select core");
6334            }
6335        } else {
6336            unreachable!("expected Select");
6337        }
6338    }
6339
6340    #[test]
6341    fn test_right_outer_join() {
6342        // RIGHT JOIN returns all right rows (3.39+ feature).
6343        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
6344        if let Statement::Select(s) = stmt {
6345            if let SelectCore::Select { from, .. } = &s.body.select {
6346                let from = from.as_ref().expect("FROM clause");
6347                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
6348            } else {
6349                unreachable!("expected Select core");
6350            }
6351        } else {
6352            unreachable!("expected Select");
6353        }
6354    }
6355
6356    #[test]
6357    fn test_full_outer_join() {
6358        // FULL OUTER JOIN returns rows from both tables.
6359        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
6360        if let Statement::Select(s) = stmt {
6361            if let SelectCore::Select { from, .. } = &s.body.select {
6362                let from = from.as_ref().expect("FROM clause");
6363                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
6364            } else {
6365                unreachable!("expected Select core");
6366            }
6367        } else {
6368            unreachable!("expected Select");
6369        }
6370    }
6371
6372    #[test]
6373    fn test_cross_join_no_reorder() {
6374        // CROSS JOIN prevents optimizer reordering; parser must produce JoinKind::Cross.
6375        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
6376        if let Statement::Select(s) = stmt {
6377            if let SelectCore::Select { from, .. } = &s.body.select {
6378                let from = from.as_ref().expect("FROM clause");
6379                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
6380                // Cross joins must NOT have an ON or USING constraint.
6381                assert!(from.joins[0].constraint.is_none());
6382            } else {
6383                unreachable!("expected Select core");
6384            }
6385        } else {
6386            unreachable!("expected Select");
6387        }
6388    }
6389
6390    #[test]
6391    fn test_natural_join() {
6392        // NATURAL JOIN uses shared column names for implicit ON.
6393        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
6394        if let Statement::Select(s) = stmt {
6395            if let SelectCore::Select { from, .. } = &s.body.select {
6396                let from = from.as_ref().expect("FROM clause");
6397                assert!(from.joins[0].join_type.natural);
6398            } else {
6399                unreachable!("expected Select core");
6400            }
6401        } else {
6402            unreachable!("expected Select");
6403        }
6404    }
6405
6406    #[test]
6407    fn test_using_clause() {
6408        // JOIN USING joins on specified shared columns.
6409        let stmt = parse_one("SELECT * FROM a JOIN b USING (id, name)");
6410        if let Statement::Select(s) = stmt {
6411            if let SelectCore::Select { from, .. } = &s.body.select {
6412                let from = from.as_ref().expect("FROM clause");
6413                match &from.joins[0].constraint {
6414                    Some(JoinConstraint::Using(cols)) => {
6415                        assert_eq!(cols.len(), 2);
6416                        assert_eq!(cols[0], "id");
6417                        assert_eq!(cols[1], "name");
6418                    }
6419                    other => unreachable!("expected USING constraint, got {other:?}"),
6420                }
6421            } else {
6422                unreachable!("expected Select core");
6423            }
6424        } else {
6425            unreachable!("expected Select");
6426        }
6427    }
6428
6429    #[test]
6430    fn test_cte_basic() {
6431        // WITH clause defines reusable named subquery.
6432        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
6433        if let Statement::Select(s) = stmt {
6434            let with = s.with.as_ref().expect("WITH clause");
6435            assert!(!with.recursive);
6436            assert_eq!(with.ctes.len(), 1);
6437            assert_eq!(with.ctes[0].name, "cte");
6438        } else {
6439            unreachable!("expected Select");
6440        }
6441    }
6442
6443    #[test]
6444    fn test_cte_recursive_union_all() {
6445        // Recursive CTE with UNION ALL generates rows.
6446        let stmt = parse_one(
6447            "WITH RECURSIVE cnt(x) AS (\
6448             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
6449             ) SELECT x FROM cnt",
6450        );
6451        if let Statement::Select(s) = stmt {
6452            let with = s.with.as_ref().expect("WITH clause");
6453            assert!(with.recursive);
6454            assert_eq!(with.ctes[0].name, "cnt");
6455            // Verify the CTE body contains a UNION ALL compound.
6456            let cte_body = &with.ctes[0].query;
6457            assert_eq!(cte_body.body.compounds.len(), 1);
6458            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::UnionAll);
6459        } else {
6460            unreachable!("expected Select");
6461        }
6462    }
6463
6464    #[test]
6465    fn test_cte_recursive_union_cycle_detection() {
6466        // Recursive CTE with UNION (not UNION ALL) detects cycles via dedup.
6467        let stmt = parse_one(
6468            "WITH RECURSIVE paths(a, b) AS (\
6469             SELECT src, dst FROM edges \
6470             UNION \
6471             SELECT p.a, e.dst FROM paths p JOIN edges e ON p.b = e.src\
6472             ) SELECT * FROM paths",
6473        );
6474        if let Statement::Select(s) = stmt {
6475            let with = s.with.as_ref().expect("WITH clause");
6476            assert!(with.recursive);
6477            // UNION (not UNION ALL) provides implicit cycle detection.
6478            let cte_body = &with.ctes[0].query;
6479            assert_eq!(cte_body.body.compounds.len(), 1);
6480            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::Union);
6481        } else {
6482            unreachable!("expected Select");
6483        }
6484    }
6485
6486    #[test]
6487    fn test_cte_materialized_hint() {
6488        // MATERIALIZED forces single evaluation.
6489        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
6490        if let Statement::Select(s) = stmt {
6491            let with = s.with.as_ref().expect("WITH clause");
6492            assert_eq!(
6493                with.ctes[0].materialized,
6494                Some(CteMaterialized::Materialized)
6495            );
6496        } else {
6497            unreachable!("expected Select");
6498        }
6499    }
6500
6501    #[test]
6502    fn test_cte_not_materialized_hint() {
6503        // NOT MATERIALIZED allows inlining.
6504        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
6505        if let Statement::Select(s) = stmt {
6506            let with = s.with.as_ref().expect("WITH clause");
6507            assert_eq!(
6508                with.ctes[0].materialized,
6509                Some(CteMaterialized::NotMaterialized)
6510            );
6511        } else {
6512            unreachable!("expected Select");
6513        }
6514    }
6515
6516    #[test]
6517    fn test_window_partition_by() {
6518        // PARTITION BY correctly groups window function output.
6519        let stmt = parse_one("SELECT sum(x) OVER (PARTITION BY dept) FROM emp");
6520        if let Statement::Select(s) = stmt {
6521            if let SelectCore::Select { columns, .. } = &s.body.select {
6522                match &columns[0] {
6523                    ResultColumn::Expr {
6524                        expr:
6525                            Expr::FunctionCall {
6526                                over: Some(over), ..
6527                            },
6528                        ..
6529                    } => {
6530                        assert_eq!(over.partition_by.len(), 1);
6531                    }
6532                    other => unreachable!("expected window function, got {other:?}"),
6533                }
6534            } else {
6535                unreachable!("expected Select core");
6536            }
6537        } else {
6538            unreachable!("expected Select");
6539        }
6540    }
6541
6542    #[test]
6543    fn test_window_order_by() {
6544        // ORDER BY within window function controls row ordering.
6545        let stmt = parse_one("SELECT row_number() OVER (ORDER BY salary DESC) FROM emp");
6546        if let Statement::Select(s) = stmt {
6547            if let SelectCore::Select { columns, .. } = &s.body.select {
6548                match &columns[0] {
6549                    ResultColumn::Expr {
6550                        expr:
6551                            Expr::FunctionCall {
6552                                over: Some(over), ..
6553                            },
6554                        ..
6555                    } => {
6556                        assert_eq!(over.order_by.len(), 1);
6557                        assert_eq!(over.order_by[0].direction, Some(SortDirection::Desc));
6558                    }
6559                    other => unreachable!("expected window function, got {other:?}"),
6560                }
6561            } else {
6562                unreachable!("expected Select core");
6563            }
6564        } else {
6565            unreachable!("expected Select");
6566        }
6567    }
6568
6569    #[test]
6570    fn test_window_frame_rows() {
6571        // ROWS frame spec limits window to specified row range.
6572        let stmt = parse_one(
6573            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
6574        );
6575        if let Statement::Select(s) = stmt {
6576            if let SelectCore::Select { columns, .. } = &s.body.select {
6577                match &columns[0] {
6578                    ResultColumn::Expr {
6579                        expr:
6580                            Expr::FunctionCall {
6581                                over: Some(over), ..
6582                            },
6583                        ..
6584                    } => {
6585                        let frame = over.frame.as_ref().expect("frame spec");
6586                        assert_eq!(frame.frame_type, FrameType::Rows);
6587                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
6588                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
6589                    }
6590                    other => unreachable!("expected window function, got {other:?}"),
6591                }
6592            } else {
6593                unreachable!("expected Select core");
6594            }
6595        } else {
6596            unreachable!("expected Select");
6597        }
6598    }
6599
6600    #[test]
6601    fn test_window_exclude_current_row() {
6602        // EXCLUDE CURRENT ROW omits current row from frame.
6603        let stmt = parse_one(
6604            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6605             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
6606        );
6607        if let Statement::Select(s) = stmt {
6608            if let SelectCore::Select { columns, .. } = &s.body.select {
6609                match &columns[0] {
6610                    ResultColumn::Expr {
6611                        expr:
6612                            Expr::FunctionCall {
6613                                over: Some(over), ..
6614                            },
6615                        ..
6616                    } => {
6617                        let frame = over.frame.as_ref().expect("frame spec");
6618                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
6619                    }
6620                    other => unreachable!("expected window function, got {other:?}"),
6621                }
6622            } else {
6623                unreachable!("expected Select core");
6624            }
6625        } else {
6626            unreachable!("expected Select");
6627        }
6628    }
6629
6630    #[test]
6631    fn test_window_exclude_ties() {
6632        // EXCLUDE TIES omits peers of current row.
6633        let stmt = parse_one(
6634            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6635             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
6636        );
6637        if let Statement::Select(s) = stmt {
6638            if let SelectCore::Select { columns, .. } = &s.body.select {
6639                match &columns[0] {
6640                    ResultColumn::Expr {
6641                        expr:
6642                            Expr::FunctionCall {
6643                                over: Some(over), ..
6644                            },
6645                        ..
6646                    } => {
6647                        let frame = over.frame.as_ref().expect("frame spec");
6648                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
6649                    }
6650                    other => unreachable!("expected window function, got {other:?}"),
6651                }
6652            } else {
6653                unreachable!("expected Select core");
6654            }
6655        } else {
6656            unreachable!("expected Select");
6657        }
6658    }
6659
6660    #[test]
6661    fn test_nulls_first_asc() {
6662        // NULLS FIRST with ASC puts NULLs before non-NULL values.
6663        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
6664        if let Statement::Select(s) = stmt {
6665            assert_eq!(s.order_by.len(), 1);
6666            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
6667            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
6668        } else {
6669            unreachable!("expected Select");
6670        }
6671    }
6672
6673    #[test]
6674    fn test_nulls_last_asc() {
6675        // NULLS LAST with ASC puts NULLs after non-NULL values.
6676        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS LAST");
6677        if let Statement::Select(s) = stmt {
6678            assert_eq!(s.order_by.len(), 1);
6679            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
6680            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
6681        } else {
6682            unreachable!("expected Select");
6683        }
6684    }
6685
6686    #[test]
6687    fn test_distinct_deduplicates() {
6688        // SELECT DISTINCT removes duplicate rows (parser-level: keyword present).
6689        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
6690        if let Statement::Select(s) = stmt {
6691            if let SelectCore::Select { distinct, .. } = &s.body.select {
6692                assert_eq!(*distinct, Distinctness::Distinct);
6693            } else {
6694                unreachable!("expected Select core");
6695            }
6696        } else {
6697            unreachable!("expected Select");
6698        }
6699    }
6700
6701    #[test]
6702    fn test_limit_offset() {
6703        // LIMIT N OFFSET M skips M rows and returns N.
6704        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET 20");
6705        if let Statement::Select(s) = stmt {
6706            let limit = s.limit.expect("LIMIT clause");
6707            assert!(matches!(
6708                limit.limit,
6709                Expr::Literal(Literal::Integer(10), _)
6710            ));
6711            assert!(matches!(
6712                limit.offset,
6713                Some(Expr::Literal(Literal::Integer(20), _))
6714            ));
6715        } else {
6716            unreachable!("expected Select");
6717        }
6718    }
6719
6720    #[test]
6721    fn test_limit_comma_syntax() {
6722        // LIMIT offset,count (MySQL syntax) — offset first, count second.
6723        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
6724        if let Statement::Select(s) = stmt {
6725            let limit = s.limit.expect("LIMIT clause");
6726            // In MySQL syntax, LIMIT 5, 10 means offset=5, count=10.
6727            assert!(matches!(
6728                limit.limit,
6729                Expr::Literal(Literal::Integer(10), _)
6730            ));
6731            assert!(matches!(
6732                limit.offset,
6733                Some(Expr::Literal(Literal::Integer(5), _))
6734            ));
6735        } else {
6736            unreachable!("expected Select");
6737        }
6738    }
6739
6740    #[test]
6741    fn test_negative_limit_unlimited() {
6742        // Negative LIMIT means unlimited (parser accepts negative literal).
6743        let stmt = parse_one("SELECT a FROM t LIMIT -1");
6744        if let Statement::Select(s) = stmt {
6745            let limit = s.limit.expect("LIMIT clause");
6746            // Parser may represent -1 as UnaryOp::Negate on Integer(1),
6747            // or as Integer(-1). Either is valid.
6748            match &limit.limit {
6749                Expr::UnaryOp {
6750                    op: fsqlite_ast::UnaryOp::Negate,
6751                    ..
6752                } => {}
6753                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
6754                other => unreachable!("expected negative limit expression, got {other:?}"),
6755            }
6756        } else {
6757            unreachable!("expected Select");
6758        }
6759    }
6760
6761    #[test]
6762    fn test_negative_offset_zero() {
6763        // Negative OFFSET treated as zero (parser accepts negative literal).
6764        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET -5");
6765        if let Statement::Select(s) = stmt {
6766            let limit = s.limit.expect("LIMIT clause");
6767            assert!(limit.offset.is_some());
6768            match limit.offset.as_ref().unwrap() {
6769                Expr::UnaryOp {
6770                    op: fsqlite_ast::UnaryOp::Negate,
6771                    ..
6772                } => {}
6773                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
6774                other => unreachable!("expected negative offset expression, got {other:?}"),
6775            }
6776        } else {
6777            unreachable!("expected Select");
6778        }
6779    }
6780
6781    #[test]
6782    fn test_current_date_constant() {
6783        // current_date is parsed as a literal keyword.
6784        let stmt = parse_one("SELECT CURRENT_DATE");
6785        if let Statement::Select(s) = stmt {
6786            if let SelectCore::Select { columns, .. } = &s.body.select {
6787                match &columns[0] {
6788                    ResultColumn::Expr {
6789                        expr: Expr::Literal(Literal::CurrentDate, _),
6790                        ..
6791                    } => {}
6792                    other => unreachable!("expected CURRENT_DATE literal, got {other:?}"),
6793                }
6794            } else {
6795                unreachable!("expected Select core");
6796            }
6797        } else {
6798            unreachable!("expected Select");
6799        }
6800    }
6801
6802    #[test]
6803    fn test_current_time_constant() {
6804        // current_time is parsed as a literal keyword.
6805        let stmt = parse_one("SELECT CURRENT_TIME");
6806        if let Statement::Select(s) = stmt {
6807            if let SelectCore::Select { columns, .. } = &s.body.select {
6808                match &columns[0] {
6809                    ResultColumn::Expr {
6810                        expr: Expr::Literal(Literal::CurrentTime, _),
6811                        ..
6812                    } => {}
6813                    other => unreachable!("expected CURRENT_TIME literal, got {other:?}"),
6814                }
6815            } else {
6816                unreachable!("expected Select core");
6817            }
6818        } else {
6819            unreachable!("expected Select");
6820        }
6821    }
6822
6823    #[test]
6824    fn test_current_timestamp_constant() {
6825        // current_timestamp is parsed as a literal keyword.
6826        let stmt = parse_one("SELECT CURRENT_TIMESTAMP");
6827        if let Statement::Select(s) = stmt {
6828            if let SelectCore::Select { columns, .. } = &s.body.select {
6829                match &columns[0] {
6830                    ResultColumn::Expr {
6831                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
6832                        ..
6833                    } => {}
6834                    other => unreachable!("expected CURRENT_TIMESTAMP literal, got {other:?}"),
6835                }
6836            } else {
6837                unreachable!("expected Select core");
6838            }
6839        } else {
6840            unreachable!("expected Select");
6841        }
6842    }
6843
6844    #[test]
6845    fn test_date_constants_evaluated_once_per_statement() {
6846        // Parser-level: all three date/time constants parse as distinct Literal variants.
6847        // Runtime guarantee (evaluated once per stmt, not per row) is verified at VDBE level.
6848        let stmt = parse_one("SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP FROM t");
6849        if let Statement::Select(s) = stmt {
6850            if let SelectCore::Select { columns, .. } = &s.body.select {
6851                assert_eq!(columns.len(), 3);
6852                assert!(matches!(
6853                    &columns[0],
6854                    ResultColumn::Expr {
6855                        expr: Expr::Literal(Literal::CurrentDate, _),
6856                        ..
6857                    }
6858                ));
6859                assert!(matches!(
6860                    &columns[1],
6861                    ResultColumn::Expr {
6862                        expr: Expr::Literal(Literal::CurrentTime, _),
6863                        ..
6864                    }
6865                ));
6866                assert!(matches!(
6867                    &columns[2],
6868                    ResultColumn::Expr {
6869                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
6870                        ..
6871                    }
6872                ));
6873            } else {
6874                unreachable!("expected Select core");
6875            }
6876        } else {
6877            unreachable!("expected Select");
6878        }
6879    }
6880
6881    #[test]
6882    fn test_indexed_by_hint() {
6883        // FROM t1 INDEXED BY idx forces specified index.
6884        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
6885        if let Statement::Select(s) = stmt {
6886            if let SelectCore::Select { from, .. } = &s.body.select {
6887                let from = from.as_ref().expect("FROM clause");
6888                match &from.source {
6889                    TableOrSubquery::Table {
6890                        index_hint: Some(IndexHint::IndexedBy(name)),
6891                        ..
6892                    } => assert_eq!(name, "idx_t"),
6893                    other => unreachable!("expected indexed table source, got {other:?}"),
6894                }
6895            } else {
6896                unreachable!("expected Select core");
6897            }
6898        } else {
6899            unreachable!("expected Select");
6900        }
6901    }
6902
6903    #[test]
6904    fn test_not_indexed_hint() {
6905        // FROM t1 NOT INDEXED prevents index use.
6906        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
6907        if let Statement::Select(s) = stmt {
6908            if let SelectCore::Select { from, .. } = &s.body.select {
6909                let from = from.as_ref().expect("FROM clause");
6910                match &from.source {
6911                    TableOrSubquery::Table {
6912                        index_hint: Some(IndexHint::NotIndexed),
6913                        ..
6914                    } => {}
6915                    other => unreachable!("expected not-indexed table source, got {other:?}"),
6916                }
6917            } else {
6918                unreachable!("expected Select core");
6919            }
6920        } else {
6921            unreachable!("expected Select");
6922        }
6923    }
6924
6925    #[test]
6926    fn test_table_valued_function_in_from() {
6927        // FROM generate_series(1,100) works as table source.
6928        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
6929        if let Statement::Select(s) = stmt {
6930            if let SelectCore::Select { from, .. } = &s.body.select {
6931                let from = from.as_ref().expect("FROM clause");
6932                match &from.source {
6933                    TableOrSubquery::TableFunction { name, args, alias } => {
6934                        assert_eq!(name, "generate_series");
6935                        assert_eq!(args.len(), 2);
6936                        assert_eq!(alias.as_deref(), Some("gs"));
6937                    }
6938                    other => unreachable!("expected table-valued function source, got {other:?}"),
6939                }
6940            } else {
6941                unreachable!("expected Select core");
6942            }
6943        } else {
6944            unreachable!("expected Select");
6945        }
6946    }
6947
6948    // -----------------------------------------------------------------------
6949    // bd-1llo §12.2-12.4 INSERT + UPDATE + DELETE DML parsing tests
6950    // -----------------------------------------------------------------------
6951
6952    #[test]
6953    fn test_insert_values_single() {
6954        let stmt = parse_one("INSERT INTO t (a, b, c) VALUES (1, 'hello', 3.14)");
6955        if let Statement::Insert(i) = stmt {
6956            assert_eq!(i.columns, vec!["a", "b", "c"]);
6957            if let InsertSource::Values(rows) = &i.source {
6958                assert_eq!(rows.len(), 1);
6959                assert_eq!(rows[0].len(), 3);
6960            } else {
6961                unreachable!("expected Values source");
6962            }
6963        } else {
6964            unreachable!("expected Insert");
6965        }
6966    }
6967
6968    #[test]
6969    fn test_insert_values_multi() {
6970        let stmt = parse_one("INSERT INTO t (x, y) VALUES (1, 2), (3, 4), (5, 6)");
6971        if let Statement::Insert(i) = stmt {
6972            if let InsertSource::Values(rows) = &i.source {
6973                assert_eq!(rows.len(), 3);
6974                for row in rows {
6975                    assert_eq!(row.len(), 2);
6976                }
6977            } else {
6978                unreachable!("expected Values source");
6979            }
6980        } else {
6981            unreachable!("expected Insert");
6982        }
6983    }
6984
6985    #[test]
6986    fn test_insert_from_select() {
6987        let stmt = parse_one("INSERT INTO t2 (a, b) SELECT x, y FROM t1 WHERE x > 0");
6988        if let Statement::Insert(i) = stmt {
6989            assert!(matches!(i.source, InsertSource::Select(_)));
6990            assert_eq!(i.columns, vec!["a", "b"]);
6991        } else {
6992            unreachable!("expected Insert");
6993        }
6994    }
6995
6996    #[test]
6997    fn test_insert_from_select_without_from_clause() {
6998        let stmt = parse_one("INSERT INTO t (a) SELECT 1");
6999        if let Statement::Insert(i) = stmt {
7000            if let InsertSource::Select(select) = &i.source {
7001                if let SelectCore::Select { from, columns, .. } = &select.body.select {
7002                    assert!(from.is_none(), "SELECT 1 should parse without FROM");
7003                    assert_eq!(columns.len(), 1);
7004                } else {
7005                    unreachable!("expected Select core");
7006                }
7007            } else {
7008                unreachable!("expected Select source");
7009            }
7010        } else {
7011            unreachable!("expected Insert");
7012        }
7013    }
7014
7015    #[test]
7016    fn test_insert_from_select_subquery_source() {
7017        let stmt = parse_one("INSERT INTO t (a) SELECT sub.x FROM (SELECT 1 AS x) AS sub");
7018        if let Statement::Insert(i) = stmt {
7019            if let InsertSource::Select(select) = &i.source {
7020                if let SelectCore::Select { from, .. } = &select.body.select {
7021                    let from = from.as_ref().expect("FROM clause");
7022                    match &from.source {
7023                        TableOrSubquery::Subquery { alias, .. } => {
7024                            assert_eq!(alias.as_deref(), Some("sub"));
7025                        }
7026                        other => unreachable!("expected subquery source, got {other:?}"),
7027                    }
7028                } else {
7029                    unreachable!("expected Select core");
7030                }
7031            } else {
7032                unreachable!("expected Select source");
7033            }
7034        } else {
7035            unreachable!("expected Insert");
7036        }
7037    }
7038
7039    #[test]
7040    fn test_insert_from_select_table_function_source() {
7041        let stmt = parse_one("INSERT INTO t (a) SELECT gs.value FROM generate_series(1, 3) AS gs");
7042        if let Statement::Insert(i) = stmt {
7043            if let InsertSource::Select(select) = &i.source {
7044                if let SelectCore::Select { from, .. } = &select.body.select {
7045                    let from = from.as_ref().expect("FROM clause");
7046                    match &from.source {
7047                        TableOrSubquery::TableFunction { name, args, alias } => {
7048                            assert_eq!(name, "generate_series");
7049                            assert_eq!(args.len(), 2);
7050                            assert_eq!(alias.as_deref(), Some("gs"));
7051                        }
7052                        other => unreachable!("expected table function source, got {other:?}"),
7053                    }
7054                } else {
7055                    unreachable!("expected Select core");
7056                }
7057            } else {
7058                unreachable!("expected Select source");
7059            }
7060        } else {
7061            unreachable!("expected Insert");
7062        }
7063    }
7064
7065    #[test]
7066    fn test_insert_default_values() {
7067        let stmt = parse_one("INSERT INTO t DEFAULT VALUES");
7068        if let Statement::Insert(i) = stmt {
7069            assert!(matches!(i.source, InsertSource::DefaultValues));
7070            assert!(i.columns.is_empty());
7071        } else {
7072            unreachable!("expected Insert");
7073        }
7074    }
7075
7076    #[test]
7077    fn test_insert_or_abort() {
7078        let stmt = parse_one("INSERT OR ABORT INTO t (a) VALUES (1)");
7079        if let Statement::Insert(i) = stmt {
7080            assert_eq!(i.or_conflict, Some(ConflictAction::Abort));
7081        } else {
7082            unreachable!("expected Insert");
7083        }
7084    }
7085
7086    #[test]
7087    fn test_insert_or_rollback() {
7088        let stmt = parse_one("INSERT OR ROLLBACK INTO t (a) VALUES (1)");
7089        if let Statement::Insert(i) = stmt {
7090            assert_eq!(i.or_conflict, Some(ConflictAction::Rollback));
7091        } else {
7092            unreachable!("expected Insert");
7093        }
7094    }
7095
7096    #[test]
7097    fn test_insert_or_fail() {
7098        let stmt = parse_one("INSERT OR FAIL INTO t (a) VALUES (1)");
7099        if let Statement::Insert(i) = stmt {
7100            assert_eq!(i.or_conflict, Some(ConflictAction::Fail));
7101        } else {
7102            unreachable!("expected Insert");
7103        }
7104    }
7105
7106    #[test]
7107    fn test_insert_or_ignore() {
7108        let stmt = parse_one("INSERT OR IGNORE INTO t (a) VALUES (1)");
7109        if let Statement::Insert(i) = stmt {
7110            assert_eq!(i.or_conflict, Some(ConflictAction::Ignore));
7111        } else {
7112            unreachable!("expected Insert");
7113        }
7114    }
7115
7116    #[test]
7117    fn test_insert_or_replace() {
7118        // Both INSERT OR REPLACE and REPLACE INTO forms
7119        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1)");
7120        if let Statement::Insert(i) = stmt {
7121            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
7122        } else {
7123            unreachable!("expected Insert");
7124        }
7125    }
7126
7127    #[test]
7128    fn test_upsert_do_update() {
7129        let stmt = parse_one(
7130            "INSERT INTO t (a, b) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET b = excluded.b",
7131        );
7132        if let Statement::Insert(i) = stmt {
7133            assert_eq!(i.upsert.len(), 1);
7134            assert!(i.upsert[0].target.is_some());
7135            match &i.upsert[0].action {
7136                UpsertAction::Update {
7137                    assignments,
7138                    where_clause,
7139                } => {
7140                    assert_eq!(assignments.len(), 1);
7141                    assert!(where_clause.is_none());
7142                }
7143                UpsertAction::Nothing => unreachable!("expected Update action"),
7144            }
7145        } else {
7146            unreachable!("expected Insert");
7147        }
7148    }
7149
7150    #[test]
7151    fn test_upsert_do_nothing() {
7152        let stmt = parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
7153        if let Statement::Insert(i) = stmt {
7154            assert_eq!(i.upsert.len(), 1);
7155            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
7156        } else {
7157            unreachable!("expected Insert");
7158        }
7159    }
7160
7161    #[test]
7162    fn test_upsert_excluded_pseudo_table() {
7163        let stmt = parse_one(
7164            "INSERT INTO t (a, b) VALUES (1, 2) \
7165             ON CONFLICT (a) DO UPDATE SET b = excluded.b, a = excluded.a + 1",
7166        );
7167        if let Statement::Insert(i) = stmt {
7168            assert_eq!(i.upsert.len(), 1);
7169            if let UpsertAction::Update { assignments, .. } = &i.upsert[0].action {
7170                assert_eq!(assignments.len(), 2);
7171                // Verify excluded.b reference in first assignment
7172                match &assignments[0].value {
7173                    Expr::Column(col, _) => {
7174                        assert_eq!(col.table.as_deref(), Some("excluded"));
7175                        assert_eq!(col.column.as_ref(), "b");
7176                    }
7177                    other => unreachable!("expected Column ref to excluded.b, got {other:?}"),
7178                }
7179            } else {
7180                unreachable!("expected Update action");
7181            }
7182        } else {
7183            unreachable!("expected Insert");
7184        }
7185    }
7186
7187    #[test]
7188    fn test_upsert_multiple_on_conflict() {
7189        let stmt = parse_one(
7190            "INSERT INTO t (a, b) VALUES (1, 2) \
7191             ON CONFLICT (a) DO NOTHING \
7192             ON CONFLICT (b) DO UPDATE SET a = excluded.a",
7193        );
7194        if let Statement::Insert(i) = stmt {
7195            assert_eq!(i.upsert.len(), 2);
7196            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
7197            assert!(matches!(i.upsert[1].action, UpsertAction::Update { .. }));
7198        } else {
7199            unreachable!("expected Insert");
7200        }
7201    }
7202
7203    #[test]
7204    fn test_upsert_where_on_conflict_target() {
7205        let stmt = parse_one(
7206            "INSERT INTO t (a, b) VALUES (1, 2) \
7207             ON CONFLICT (a) WHERE a > 0 DO UPDATE SET b = excluded.b WHERE b < 100",
7208        );
7209        if let Statement::Insert(i) = stmt {
7210            assert_eq!(i.upsert.len(), 1);
7211            let target = i.upsert[0].target.as_ref().expect("conflict target");
7212            assert!(target.where_clause.is_some(), "target WHERE missing");
7213            if let UpsertAction::Update { where_clause, .. } = &i.upsert[0].action {
7214                assert!(where_clause.is_some(), "action WHERE missing");
7215            } else {
7216                unreachable!("expected Update action");
7217            }
7218        } else {
7219            unreachable!("expected Insert");
7220        }
7221    }
7222
7223    #[test]
7224    fn test_returning_insert() {
7225        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, rowid");
7226        if let Statement::Insert(i) = stmt {
7227            assert_eq!(i.returning.len(), 3);
7228        } else {
7229            unreachable!("expected Insert");
7230        }
7231    }
7232
7233    #[test]
7234    fn test_returning_insert_select_with_semicolon() {
7235        let stmt = parse_one("INSERT INTO t2 SELECT * FROM t RETURNING *;");
7236        if let Statement::Insert(i) = stmt {
7237            assert!(matches!(i.source, InsertSource::Select(_)));
7238            assert_eq!(i.returning.len(), 1);
7239            assert!(matches!(i.returning[0], ResultColumn::Star));
7240        } else {
7241            unreachable!("expected Insert");
7242        }
7243    }
7244
7245    #[test]
7246    fn test_returning_reflects_before_triggers() {
7247        // Parser-level: verify RETURNING clause parses alongside trigger-affected DML
7248        // Runtime verification that RETURNING reflects BEFORE-trigger modifications
7249        // is deferred to VDBE/engine tests
7250        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING a AS modified_a");
7251        if let Statement::Insert(i) = stmt {
7252            assert_eq!(i.returning.len(), 1);
7253            match &i.returning[0] {
7254                ResultColumn::Expr { alias, .. } => {
7255                    assert_eq!(alias.as_deref(), Some("modified_a"));
7256                }
7257                other => unreachable!("expected Expr result column, got {other:?}"),
7258            }
7259        } else {
7260            unreachable!("expected Insert");
7261        }
7262    }
7263
7264    #[test]
7265    fn test_returning_ignores_after_triggers() {
7266        // Parser-level: verify RETURNING * parses on INSERT with conflict clause
7267        // Runtime verification that RETURNING ignores AFTER-trigger modifications
7268        // is deferred to VDBE/engine tests
7269        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1) RETURNING *");
7270        if let Statement::Insert(i) = stmt {
7271            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
7272            assert_eq!(i.returning.len(), 1);
7273            assert!(matches!(i.returning[0], ResultColumn::Star));
7274        } else {
7275            unreachable!("expected Insert");
7276        }
7277    }
7278
7279    #[test]
7280    fn test_returning_after_before_trigger_modify() {
7281        // Parser-level: verify RETURNING with multiple column expressions
7282        // Runtime verification of BEFORE trigger modifying returned values
7283        // is deferred to VDBE/engine tests
7284        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, a + b AS total");
7285        if let Statement::Insert(i) = stmt {
7286            assert_eq!(i.returning.len(), 3);
7287            match &i.returning[2] {
7288                ResultColumn::Expr {
7289                    alias: Some(alias), ..
7290                } => assert_eq!(alias, "total"),
7291                other => unreachable!("expected aliased expression, got {other:?}"),
7292            }
7293        } else {
7294            unreachable!("expected Insert");
7295        }
7296    }
7297
7298    #[test]
7299    fn test_returning_before_trigger_raise_abort() {
7300        // Parser-level: RAISE(ABORT, ...) is a valid expression in trigger bodies;
7301        // here we verify RETURNING parses on multi-row INSERT (runtime abort
7302        // behavior verified in VDBE/engine tests)
7303        let stmt = parse_one("INSERT INTO t (a) VALUES (1), (2), (3) RETURNING a");
7304        if let Statement::Insert(i) = stmt {
7305            if let InsertSource::Values(rows) = &i.source {
7306                assert_eq!(rows.len(), 3);
7307            } else {
7308                unreachable!("expected Values source");
7309            }
7310            assert_eq!(i.returning.len(), 1);
7311        } else {
7312            unreachable!("expected Insert");
7313        }
7314    }
7315
7316    #[test]
7317    fn test_returning_instead_of_view() {
7318        // Parser-level: INSERT into a view name parses the same as INSERT into a table
7319        // Runtime INSTEAD OF trigger behavior is verified in VDBE/engine tests
7320        let stmt = parse_one("INSERT INTO v (a, b) VALUES (1, 2) RETURNING *");
7321        if let Statement::Insert(i) = stmt {
7322            assert_eq!(i.table.name, "v");
7323            assert!(!i.returning.is_empty());
7324        } else {
7325            unreachable!("expected Insert");
7326        }
7327    }
7328
7329    #[test]
7330    fn test_returning_autoincrement_with_trigger() {
7331        // Parser-level: verify RETURNING can reference rowid on INSERT
7332        // Runtime autoincrement + trigger interaction is verified in VDBE/engine tests
7333        let stmt = parse_one("INSERT INTO t (name) VALUES ('test') RETURNING rowid, name");
7334        if let Statement::Insert(i) = stmt {
7335            assert_eq!(i.returning.len(), 2);
7336        } else {
7337            unreachable!("expected Insert");
7338        }
7339    }
7340
7341    #[test]
7342    fn test_update_set_where() {
7343        let stmt = parse_one("UPDATE t SET a = 1, b = 'hello' WHERE id = 42");
7344        if let Statement::Update(u) = stmt {
7345            assert_eq!(u.assignments.len(), 2);
7346            assert!(u.where_clause.is_some());
7347            assert!(u.from.is_none());
7348        } else {
7349            unreachable!("expected Update");
7350        }
7351    }
7352
7353    #[test]
7354    fn test_update_from_join() {
7355        let stmt = parse_one("UPDATE t1 SET a = t2.x FROM t2 WHERE t1.id = t2.id");
7356        if let Statement::Update(u) = stmt {
7357            assert_eq!(u.assignments.len(), 1);
7358            assert!(u.from.is_some());
7359            assert!(u.where_clause.is_some());
7360        } else {
7361            unreachable!("expected Update");
7362        }
7363    }
7364
7365    #[test]
7366    fn test_update_from_multi_match() {
7367        // Parser-level: UPDATE FROM with a join that could produce multiple matches
7368        // Runtime behavior (arbitrary row chosen) verified in VDBE/engine tests
7369        let stmt = parse_one(
7370            "UPDATE t1 SET val = src.val FROM src \
7371             INNER JOIN mapping ON mapping.src_id = src.id \
7372             WHERE t1.id = mapping.dst_id",
7373        );
7374        if let Statement::Update(u) = stmt {
7375            assert!(u.from.is_some());
7376            let from = u.from.as_ref().unwrap();
7377            assert!(!from.joins.is_empty(), "expected JOIN in FROM clause");
7378        } else {
7379            unreachable!("expected Update");
7380        }
7381    }
7382
7383    #[test]
7384    fn test_update_from_parentheses_are_stack_safe_at_1000_and_1001() {
7385        fn drop_update_from_iteratively(statement: Statement) {
7386            let Statement::Update(mut update) = statement else {
7387                panic!("expected UPDATE statement");
7388            };
7389            let Some(mut from) = update.from.take() else {
7390                panic!("expected UPDATE FROM clause");
7391            };
7392            drop(update);
7393            loop {
7394                let FromClause { source, joins } = from;
7395                assert!(joins.is_empty());
7396                match source {
7397                    TableOrSubquery::ParenJoin(inner) => from = *inner,
7398                    leaf => {
7399                        drop(leaf);
7400                        break;
7401                    }
7402                }
7403            }
7404        }
7405
7406        for height in [1000, 1001] {
7407            let sql = format!(
7408                "UPDATE target SET value = 1 FROM {}source{} WHERE target.id = source.id",
7409                "(".repeat(height),
7410                ")".repeat(height)
7411            );
7412            let (rendered, statement) = std::thread::Builder::new()
7413                .stack_size(1024 * 1024)
7414                .spawn(move || {
7415                    let statement = Parser::from_sql(&sql)
7416                        .parse_statement()
7417                        .expect("deep UPDATE FROM must parse");
7418                    let rendered = statement.to_string();
7419                    (rendered, statement)
7420                })
7421                .expect("1 MiB parser thread must spawn")
7422                .join()
7423                .expect("deep UPDATE FROM parsing and formatting must not overflow");
7424            assert!(rendered.starts_with("UPDATE target SET value = 1 FROM "));
7425            assert!(rendered.ends_with(" WHERE target.id = source.id"));
7426            assert_eq!(rendered.matches('(').count(), height);
7427            assert_eq!(rendered.matches(')').count(), height);
7428            drop_update_from_iteratively(statement);
7429        }
7430    }
7431
7432    #[test]
7433    fn test_malformed_deep_update_from_recovers_following_statement() {
7434        let sql = format!(
7435            "UPDATE target SET value = 1 FROM {}source{}; SELECT 42;",
7436            "(".repeat(1001),
7437            ")".repeat(1000)
7438        );
7439        let (statements, errors) = std::thread::Builder::new()
7440            .stack_size(1024 * 1024)
7441            .spawn(move || Parser::from_sql(&sql).parse_all())
7442            .expect("1 MiB parser thread must spawn")
7443            .join()
7444            .expect("malformed deep UPDATE FROM recovery must not overflow");
7445
7446        assert_eq!(errors.len(), 1, "expected one unbalanced-FROM error");
7447        assert_eq!(
7448            statements.len(),
7449            1,
7450            "the malformed UPDATE must be discarded"
7451        );
7452        assert_eq!(statements[0].to_string(), "SELECT 42");
7453    }
7454
7455    #[test]
7456    fn recursion_limit_error_uses_stock_parser_stack_overflow_text() {
7457        // bd-parser-errfmt-residuals-3giu1: the native parser-recursion guard
7458        // must surface stock SQLite's verbatim `parser stack overflow`
7459        // (sqlite3_errmsg text), not a frank-specific "recursion limit
7460        // exceeded" phrasing. Drive the depth counter straight to the cap so
7461        // the guard fires without any real stack recursion.
7462        let mut parser = Parser::from_sql("SELECT 1");
7463        for _ in 0..MAX_NATIVE_PARSE_DEPTH {
7464            parser
7465                .enter_recursion()
7466                .expect("depth below the cap must be accepted");
7467        }
7468        let error = parser
7469            .enter_recursion()
7470            .expect_err("exceeding the native recursion cap must be rejected");
7471        assert_eq!(error.kind, ParseErrorKind::RecursionLimit);
7472        assert_eq!(error.message, "parser stack overflow");
7473    }
7474
7475    #[test]
7476    fn semantically_empty_input_parses_to_no_statements() {
7477        // bd-parser-errfmt-residuals-3giu1: stock's sqlite3_prepare_v2 treats
7478        // empty / whitespace / comment / bare-`;` input as a no-op (SQLITE_OK,
7479        // NULL statement) rather than a parse error. The multi-statement parse
7480        // entry point mirrors that with an empty statement list and no error.
7481        let mut scratch = StatementParseScratch::default();
7482        for sql in [
7483            "",
7484            "   ",
7485            "\n\t ",
7486            ";",
7487            " ; ; ",
7488            "-- just a comment",
7489            "/* block */",
7490        ] {
7491            let statements = parse_statements_with_scratch(sql, &mut scratch)
7492                .unwrap_or_else(|error| panic!("empty input {sql:?} must not error: {error}"));
7493            assert!(
7494                statements.is_empty(),
7495                "empty input {sql:?} must parse to zero statements"
7496            );
7497        }
7498    }
7499
7500    #[test]
7501    fn test_update_order_by_limit() {
7502        let stmt = parse_one("UPDATE t SET a = a + 1 ORDER BY b DESC LIMIT 10");
7503        if let Statement::Update(u) = stmt {
7504            assert_eq!(u.order_by.len(), 1);
7505            assert_eq!(u.order_by[0].direction, Some(SortDirection::Desc));
7506            assert!(u.limit.is_some());
7507        } else {
7508            unreachable!("expected Update");
7509        }
7510    }
7511
7512    #[test]
7513    fn test_update_returning() {
7514        let stmt = parse_one("UPDATE t SET a = 1 WHERE id = 5 RETURNING id, a AS new_a");
7515        if let Statement::Update(u) = stmt {
7516            assert_eq!(u.returning.len(), 2);
7517            match &u.returning[1] {
7518                ResultColumn::Expr {
7519                    alias: Some(alias), ..
7520                } => assert_eq!(alias, "new_a"),
7521                other => unreachable!("expected aliased result column, got {other:?}"),
7522            }
7523        } else {
7524            unreachable!("expected Update");
7525        }
7526    }
7527
7528    #[test]
7529    fn test_update_or_ignore() {
7530        let stmt = parse_one("UPDATE OR IGNORE t SET a = 1 WHERE id = 5");
7531        if let Statement::Update(u) = stmt {
7532            assert_eq!(u.or_conflict, Some(ConflictAction::Ignore));
7533            assert!(u.where_clause.is_some());
7534        } else {
7535            unreachable!("expected Update");
7536        }
7537    }
7538
7539    #[test]
7540    fn test_delete_where() {
7541        let stmt = parse_one("DELETE FROM t WHERE id = 42 AND active = 0");
7542        if let Statement::Delete(d) = stmt {
7543            assert!(d.where_clause.is_some());
7544            assert!(d.returning.is_empty());
7545        } else {
7546            unreachable!("expected Delete");
7547        }
7548    }
7549
7550    #[test]
7551    fn test_delete_order_by_limit() {
7552        let stmt = parse_one("DELETE FROM t ORDER BY created_at ASC LIMIT 100");
7553        if let Statement::Delete(d) = stmt {
7554            assert_eq!(d.order_by.len(), 1);
7555            assert_eq!(d.order_by[0].direction, Some(SortDirection::Asc));
7556            let limit = d.limit.as_ref().expect("LIMIT clause");
7557            assert!(matches!(
7558                limit.limit,
7559                Expr::Literal(Literal::Integer(100), _)
7560            ));
7561        } else {
7562            unreachable!("expected Delete");
7563        }
7564    }
7565
7566    #[test]
7567    fn test_delete_returning() {
7568        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
7569        if let Statement::Delete(d) = stmt {
7570            assert!(d.where_clause.is_some());
7571            assert_eq!(d.returning.len(), 1);
7572            assert!(matches!(d.returning[0], ResultColumn::Star));
7573        } else {
7574            unreachable!("expected Delete");
7575        }
7576    }
7577
7578    #[test]
7579    fn test_delete_bulk_optimization() {
7580        // Parser-level: DELETE without WHERE produces AST with no where_clause
7581        // Runtime bulk-delete optimization (OP_Clear) is verified in VDBE/engine tests
7582        let stmt = parse_one("DELETE FROM t");
7583        if let Statement::Delete(d) = stmt {
7584            assert!(d.where_clause.is_none());
7585            assert!(d.order_by.is_empty());
7586            assert!(d.limit.is_none());
7587            assert!(d.returning.is_empty());
7588        } else {
7589            unreachable!("expected Delete");
7590        }
7591    }
7592
7593    #[test]
7594    fn test_delete_bulk_no_where_fast() {
7595        // Parser-level: confirms DELETE without WHERE parses to minimal AST
7596        // Runtime OP_Clear vs OP_Delete selection is verified in VDBE/engine tests
7597        let stmt = parse_one("DELETE FROM main.t");
7598        if let Statement::Delete(d) = stmt {
7599            assert_eq!(d.table.name.schema.as_deref(), Some("main"));
7600            assert_eq!(d.table.name.name, "t");
7601            assert!(d.where_clause.is_none());
7602        } else {
7603            unreachable!("expected Delete");
7604        }
7605    }
7606
7607    #[test]
7608    fn test_delete_bulk_blocked_by_trigger() {
7609        // Parser-level: DELETE without WHERE from a table that might have triggers
7610        // has the same AST shape (no WHERE). Runtime trigger detection is in the engine.
7611        let stmt = parse_one("DELETE FROM orders");
7612        if let Statement::Delete(d) = stmt {
7613            assert!(d.where_clause.is_none());
7614            assert!(d.returning.is_empty());
7615        } else {
7616            unreachable!("expected Delete");
7617        }
7618    }
7619
7620    #[test]
7621    fn test_delete_bulk_blocked_by_fk() {
7622        // Parser-level: DELETE without WHERE is the same AST regardless of FK constraints.
7623        // Runtime FK-based fallback to row-by-row is verified in VDBE/engine tests.
7624        let stmt = parse_one("DELETE FROM parent_table");
7625        if let Statement::Delete(d) = stmt {
7626            assert!(d.where_clause.is_none());
7627        } else {
7628            unreachable!("expected Delete");
7629        }
7630    }
7631
7632    #[test]
7633    fn test_delete_bulk_changes_count() {
7634        // Parser-level: DELETE without WHERE returning count via changes()
7635        // is the same AST as any unconditional delete. Runtime changes()
7636        // reporting is verified in VDBE/engine tests.
7637        let stmt = parse_one("DELETE FROM t");
7638        if let Statement::Delete(d) = stmt {
7639            assert!(d.where_clause.is_none());
7640        } else {
7641            unreachable!("expected Delete");
7642        }
7643    }
7644
7645    #[test]
7646    fn test_delete_bulk_autoincrement_preserved() {
7647        // Parser-level: DELETE without WHERE on an autoincrement table has
7648        // identical AST to any unconditional delete. Runtime autoincrement
7649        // sequence preservation is verified in VDBE/engine tests.
7650        let stmt = parse_one("DELETE FROM t");
7651        if let Statement::Delete(d) = stmt {
7652            assert!(d.where_clause.is_none());
7653            assert!(d.limit.is_none());
7654        } else {
7655            unreachable!("expected Delete");
7656        }
7657    }
7658
7659    #[test]
7660    fn test_delete_bulk_where_1_not_optimized() {
7661        // Parser-level: DELETE WHERE 1 has a where_clause (unlike bare DELETE),
7662        // so the optimizer cannot use bulk-delete. Verify WHERE is present.
7663        let stmt = parse_one("DELETE FROM t WHERE 1");
7664        if let Statement::Delete(d) = stmt {
7665            assert!(
7666                d.where_clause.is_some(),
7667                "WHERE 1 must produce a where_clause"
7668            );
7669            assert!(matches!(
7670                d.where_clause.as_ref().unwrap(),
7671                Expr::Literal(Literal::Integer(1), _)
7672            ));
7673        } else {
7674            unreachable!("expected Delete");
7675        }
7676    }
7677
7678    // -----------------------------------------------------------------------
7679    // bd-34de §12.5-12.6 DDL: CREATE TABLE + CREATE INDEX parsing tests
7680    // -----------------------------------------------------------------------
7681
7682    #[test]
7683    fn test_create_table_basic() {
7684        let stmt = parse_one("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
7685        if let Statement::CreateTable(ct) = stmt {
7686            assert_eq!(ct.name.name, "users");
7687            assert!(!ct.if_not_exists);
7688            assert!(!ct.temporary);
7689            assert!(!ct.without_rowid);
7690            assert!(!ct.strict);
7691            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7692                assert_eq!(columns.len(), 3);
7693                assert_eq!(columns[0].name, "id");
7694                assert_eq!(columns[1].name, "name");
7695                assert_eq!(columns[2].name, "age");
7696            } else {
7697                unreachable!("expected Columns body");
7698            }
7699        } else {
7700            unreachable!("expected CreateTable");
7701        }
7702    }
7703
7704    #[test]
7705    fn test_create_table_if_not_exists() {
7706        let stmt = parse_one("CREATE TABLE IF NOT EXISTS t (id INTEGER)");
7707        if let Statement::CreateTable(ct) = stmt {
7708            assert!(ct.if_not_exists);
7709        } else {
7710            unreachable!("expected CreateTable");
7711        }
7712    }
7713
7714    #[test]
7715    fn test_create_temp_table() {
7716        let stmt = parse_one("CREATE TEMP TABLE session_data (key TEXT, val BLOB)");
7717        if let Statement::CreateTable(ct) = stmt {
7718            assert!(ct.temporary);
7719        } else {
7720            unreachable!("expected CreateTable");
7721        }
7722    }
7723
7724    #[test]
7725    fn test_create_table_as_select() {
7726        let stmt = parse_one("CREATE TABLE t2 AS SELECT id, name FROM t1 WHERE active = 1");
7727        if let Statement::CreateTable(ct) = stmt {
7728            assert!(matches!(ct.body, CreateTableBody::AsSelect(_)));
7729        } else {
7730            unreachable!("expected CreateTable");
7731        }
7732    }
7733
7734    #[test]
7735    fn test_column_primary_key() {
7736        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY ASC)");
7737        if let Statement::CreateTable(ct) = stmt {
7738            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7739                let pk = columns[0]
7740                    .constraints
7741                    .iter()
7742                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
7743                assert!(pk.is_some(), "PK constraint missing");
7744                if let ColumnConstraintKind::PrimaryKey { direction, .. } = &pk.unwrap().kind {
7745                    assert_eq!(*direction, Some(SortDirection::Asc));
7746                }
7747            } else {
7748                unreachable!("expected Columns body");
7749            }
7750        } else {
7751            unreachable!("expected CreateTable");
7752        }
7753    }
7754
7755    #[test]
7756    fn test_column_primary_key_autoincrement() {
7757        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)");
7758        if let Statement::CreateTable(ct) = stmt {
7759            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7760                let pk = columns[0]
7761                    .constraints
7762                    .iter()
7763                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
7764                if let ColumnConstraintKind::PrimaryKey { autoincrement, .. } = &pk.unwrap().kind {
7765                    assert!(autoincrement, "AUTOINCREMENT flag not set");
7766                }
7767            } else {
7768                unreachable!("expected Columns body");
7769            }
7770        } else {
7771            unreachable!("expected CreateTable");
7772        }
7773    }
7774
7775    #[test]
7776    fn test_autoincrement_uses_sqlite_sequence() {
7777        // Parser-level: verify AUTOINCREMENT syntax parses correctly.
7778        // Runtime sqlite_sequence tracking is verified in VDBE/engine tests.
7779        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)");
7780        if let Statement::CreateTable(ct) = stmt {
7781            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7782                assert_eq!(columns.len(), 2);
7783                let pk = columns[0].constraints.iter().find(|c| {
7784                    matches!(
7785                        c.kind,
7786                        ColumnConstraintKind::PrimaryKey {
7787                            autoincrement: true,
7788                            ..
7789                        }
7790                    )
7791                });
7792                assert!(pk.is_some(), "AUTOINCREMENT constraint missing");
7793            } else {
7794                unreachable!("expected Columns body");
7795            }
7796        } else {
7797            unreachable!("expected CreateTable");
7798        }
7799    }
7800
7801    #[test]
7802    fn test_column_not_null() {
7803        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL)");
7804        if let Statement::CreateTable(ct) = stmt {
7805            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7806                let nn = columns[0]
7807                    .constraints
7808                    .iter()
7809                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
7810                assert!(nn.is_some(), "NOT NULL constraint missing");
7811            } else {
7812                unreachable!("expected Columns body");
7813            }
7814        } else {
7815            unreachable!("expected CreateTable");
7816        }
7817    }
7818
7819    #[test]
7820    fn test_column_unique() {
7821        let stmt = parse_one("CREATE TABLE t (email TEXT UNIQUE)");
7822        if let Statement::CreateTable(ct) = stmt {
7823            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7824                let uq = columns[0]
7825                    .constraints
7826                    .iter()
7827                    .find(|c| matches!(c.kind, ColumnConstraintKind::Unique { .. }));
7828                assert!(uq.is_some(), "UNIQUE constraint missing");
7829            } else {
7830                unreachable!("expected Columns body");
7831            }
7832        } else {
7833            unreachable!("expected CreateTable");
7834        }
7835    }
7836
7837    #[test]
7838    fn test_column_check() {
7839        let stmt = parse_one("CREATE TABLE t (age INTEGER CHECK(age >= 0 AND age < 200))");
7840        if let Statement::CreateTable(ct) = stmt {
7841            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7842                let chk = columns[0]
7843                    .constraints
7844                    .iter()
7845                    .find(|c| matches!(c.kind, ColumnConstraintKind::Check(_)));
7846                assert!(chk.is_some(), "CHECK constraint missing");
7847            } else {
7848                unreachable!("expected Columns body");
7849            }
7850        } else {
7851            unreachable!("expected CreateTable");
7852        }
7853    }
7854
7855    #[test]
7856    fn test_check_constraint_expression_height_fails_closed_at_1001() {
7857        const LIMIT: usize = MAX_PARSE_DEPTH as usize;
7858        let at_limit = std::iter::repeat_n("1", LIMIT)
7859            .collect::<Vec<_>>()
7860            .join(" + ");
7861        let statement = format!("CREATE TABLE t (value INTEGER CHECK({at_limit}))");
7862        assert!(
7863            matches!(parse_one(&statement), Statement::CreateTable(_)),
7864            "height-1000 CHECK must remain attached to its CREATE TABLE"
7865        );
7866
7867        let over_limit = std::iter::repeat_n("1", LIMIT + 1)
7868            .collect::<Vec<_>>()
7869            .join(" + ");
7870        let statement = format!("CREATE TABLE t (value INTEGER CHECK({over_limit}))");
7871        let mut parser = Parser::from_sql(&statement);
7872        let error = parser
7873            .parse_statement()
7874            .expect_err("height-1001 CHECK must reject the entire schema statement");
7875        assert_eq!(
7876            error.kind,
7877            ParseErrorKind::ExpressionTooDeep {
7878                max: MAX_PARSE_DEPTH
7879            }
7880        );
7881        assert_eq!(
7882            parser.depth, 0,
7883            "expression-height rejection must unwind parser recursion state"
7884        );
7885    }
7886
7887    #[test]
7888    fn test_expression_height_boundary_is_context_independent_on_one_mib_stack() {
7889        const LIMIT: usize = MAX_PARSE_DEPTH as usize;
7890        fn right_deep_expression(height: usize) -> String {
7891            format!("{}1{}", "1 + (".repeat(height - 1), ")".repeat(height - 1))
7892        }
7893        fn parse_on_one_mib_stack(sql: String) -> Result<(), ParseError> {
7894            std::thread::Builder::new()
7895                .stack_size(1024 * 1024)
7896                .spawn(move || {
7897                    let statement = Parser::from_sql(&sql).parse_statement()?;
7898                    // Destruction is part of the constrained-stack contract:
7899                    // a successful parse must not merely move a deep AST onto
7900                    // the caller's larger stack before dropping it.
7901                    drop(statement);
7902                    Ok(())
7903                })
7904                .expect("1 MiB parser thread must spawn")
7905                .join()
7906                .expect("schema-context parse must not overflow or panic")
7907        }
7908
7909        let at_limit = right_deep_expression(LIMIT);
7910        let over_limit = right_deep_expression(LIMIT + 1);
7911        let contexts = [
7912            (
7913                "SELECT",
7914                format!("SELECT {at_limit}"),
7915                format!("SELECT {over_limit}"),
7916            ),
7917            (
7918                "column CHECK",
7919                format!("CREATE TABLE t (value INTEGER CHECK({at_limit}))"),
7920                format!("CREATE TABLE t (value INTEGER CHECK({over_limit}))"),
7921            ),
7922            (
7923                "table CHECK",
7924                format!("CREATE TABLE t (value INTEGER, CHECK({at_limit}))"),
7925                format!("CREATE TABLE t (value INTEGER, CHECK({over_limit}))"),
7926            ),
7927            (
7928                "view",
7929                format!("CREATE VIEW v AS SELECT {at_limit}"),
7930                format!("CREATE VIEW v AS SELECT {over_limit}"),
7931            ),
7932            (
7933                "trigger",
7934                format!(
7935                    "CREATE TRIGGER tr BEFORE INSERT ON t WHEN {at_limit} \
7936                     BEGIN SELECT 1; END"
7937                ),
7938                format!(
7939                    "CREATE TRIGGER tr BEFORE INSERT ON t WHEN {over_limit} \
7940                     BEGIN SELECT 1; END"
7941                ),
7942            ),
7943        ];
7944
7945        for (context, accepted, rejected) in contexts {
7946            parse_on_one_mib_stack(accepted)
7947                .unwrap_or_else(|error| panic!("{context} height 1000 rejected: {error}"));
7948
7949            let error = parse_on_one_mib_stack(rejected)
7950                .expect_err("height 1001 must reject every expression-bearing context");
7951            assert_eq!(
7952                error.kind,
7953                ParseErrorKind::ExpressionTooDeep {
7954                    max: MAX_PARSE_DEPTH
7955                },
7956                "wrong error classification for {context}: {error}"
7957            );
7958        }
7959    }
7960
7961    #[test]
7962    fn test_column_default_literal() {
7963        let stmt = parse_one("CREATE TABLE t (status TEXT DEFAULT 'active')");
7964        if let Statement::CreateTable(ct) = stmt {
7965            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7966                let def = columns[0]
7967                    .constraints
7968                    .iter()
7969                    .find(|c| matches!(c.kind, ColumnConstraintKind::Default(_)));
7970                assert!(def.is_some(), "DEFAULT constraint missing");
7971            } else {
7972                unreachable!("expected Columns body");
7973            }
7974        } else {
7975            unreachable!("expected CreateTable");
7976        }
7977    }
7978
7979    #[test]
7980    fn test_column_default_expr() {
7981        let stmt = parse_one("CREATE TABLE t (created_at TEXT DEFAULT (datetime('now')))");
7982        if let Statement::CreateTable(ct) = stmt {
7983            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7984                let def = columns[0].constraints.iter().find(|c| {
7985                    matches!(
7986                        c.kind,
7987                        ColumnConstraintKind::Default(DefaultValue::ParenExpr(_))
7988                    )
7989                });
7990                assert!(def.is_some(), "DEFAULT (expr) missing");
7991            } else {
7992                unreachable!("expected Columns body");
7993            }
7994        } else {
7995            unreachable!("expected CreateTable");
7996        }
7997    }
7998
7999    #[test]
8000    fn test_column_collate() {
8001        let stmt = parse_one("CREATE TABLE t (name TEXT COLLATE NOCASE)");
8002        if let Statement::CreateTable(ct) = stmt {
8003            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8004                let coll = columns[0]
8005                    .constraints
8006                    .iter()
8007                    .find(|c| matches!(c.kind, ColumnConstraintKind::Collate(_)));
8008                assert!(coll.is_some(), "COLLATE constraint missing");
8009                if let ColumnConstraintKind::Collate(name) = &coll.unwrap().kind {
8010                    assert_eq!(name, "NOCASE");
8011                }
8012            } else {
8013                unreachable!("expected Columns body");
8014            }
8015        } else {
8016            unreachable!("expected CreateTable");
8017        }
8018    }
8019
8020    #[test]
8021    fn test_table_constraint_composite_pk() {
8022        let stmt = parse_one("CREATE TABLE t (a INTEGER, b INTEGER, PRIMARY KEY (a, b))");
8023        if let Statement::CreateTable(ct) = stmt {
8024            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
8025                let pk = constraints
8026                    .iter()
8027                    .find(|c| matches!(c.kind, TableConstraintKind::PrimaryKey { .. }));
8028                assert!(pk.is_some(), "composite PK missing");
8029                if let TableConstraintKind::PrimaryKey { columns, .. } = &pk.unwrap().kind {
8030                    assert_eq!(columns.len(), 2);
8031                }
8032            } else {
8033                unreachable!("expected Columns body");
8034            }
8035        } else {
8036            unreachable!("expected CreateTable");
8037        }
8038    }
8039
8040    #[test]
8041    fn test_table_constraint_composite_unique() {
8042        let stmt = parse_one("CREATE TABLE t (a TEXT, b TEXT, UNIQUE (a, b))");
8043        if let Statement::CreateTable(ct) = stmt {
8044            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
8045                let uq = constraints
8046                    .iter()
8047                    .find(|c| matches!(c.kind, TableConstraintKind::Unique { .. }));
8048                assert!(uq.is_some(), "composite UNIQUE missing");
8049                if let TableConstraintKind::Unique { columns, .. } = &uq.unwrap().kind {
8050                    assert_eq!(columns.len(), 2);
8051                }
8052            } else {
8053                unreachable!("expected Columns body");
8054            }
8055        } else {
8056            unreachable!("expected CreateTable");
8057        }
8058    }
8059
8060    #[test]
8061    fn test_table_constraint_check() {
8062        let stmt = parse_one(
8063            "CREATE TABLE t (start_date TEXT, end_date TEXT, CHECK (start_date < end_date))",
8064        );
8065        if let Statement::CreateTable(ct) = stmt {
8066            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
8067                let chk = constraints
8068                    .iter()
8069                    .find(|c| matches!(c.kind, TableConstraintKind::Check(_)));
8070                assert!(chk.is_some(), "table CHECK constraint missing");
8071            } else {
8072                unreachable!("expected Columns body");
8073            }
8074        } else {
8075            unreachable!("expected CreateTable");
8076        }
8077    }
8078
8079    #[test]
8080    fn test_create_rejects_modifiers_for_incompatible_object_kinds() {
8081        for sql in [
8082            "CREATE UNIQUE TABLE t(value INTEGER)",
8083            "CREATE UNIQUE VIEW v AS SELECT 1",
8084            "CREATE UNIQUE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END",
8085            "CREATE UNIQUE VIRTUAL TABLE vt USING fts5(content)",
8086            "CREATE TEMP INDEX i ON t(value)",
8087            "CREATE TEMP VIRTUAL TABLE vt USING fts5(content)",
8088        ] {
8089            Parser::from_sql(sql)
8090                .parse_statement()
8091                .expect_err("CREATE modifiers must not be discarded for incompatible objects");
8092        }
8093    }
8094
8095    #[test]
8096    fn test_invalid_create_modifier_recovers_next_statement() {
8097        let (statements, errors) =
8098            Parser::from_sql("CREATE UNIQUE TABLE t(value INTEGER); SELECT 42;").parse_all();
8099
8100        assert_eq!(errors.len(), 1);
8101        assert_eq!(statements.len(), 1);
8102        assert!(matches!(statements[0], Statement::Select(_)));
8103    }
8104
8105    #[test]
8106    fn test_foreign_key_on_delete_cascade() {
8107        let stmt = parse_one(
8108            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8109             REFERENCES parent(id) ON DELETE CASCADE)",
8110        );
8111        if let Statement::CreateTable(ct) = stmt {
8112            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8113                let fk = columns[1]
8114                    .constraints
8115                    .iter()
8116                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)));
8117                assert!(fk.is_some(), "FK constraint missing");
8118                if let ColumnConstraintKind::ForeignKey(clause) = &fk.unwrap().kind {
8119                    assert_eq!(clause.table, "parent");
8120                    let del = clause
8121                        .actions
8122                        .iter()
8123                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
8124                    assert!(del.is_some());
8125                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Cascade);
8126                }
8127            } else {
8128                unreachable!("expected Columns body");
8129            }
8130        } else {
8131            unreachable!("expected CreateTable");
8132        }
8133    }
8134
8135    #[test]
8136    fn test_foreign_key_on_delete_set_null() {
8137        let stmt = parse_one(
8138            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8139             REFERENCES parent(id) ON DELETE SET NULL)",
8140        );
8141        if let Statement::CreateTable(ct) = stmt {
8142            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8143                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8144                    .constraints
8145                    .iter()
8146                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8147                    .unwrap()
8148                    .kind
8149                {
8150                    let del = clause
8151                        .actions
8152                        .iter()
8153                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
8154                    assert_eq!(del.unwrap().action, ForeignKeyActionType::SetNull);
8155                }
8156            } else {
8157                unreachable!("expected Columns body");
8158            }
8159        } else {
8160            unreachable!("expected CreateTable");
8161        }
8162    }
8163
8164    #[test]
8165    fn test_foreign_key_on_update_cascade() {
8166        let stmt = parse_one(
8167            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8168             REFERENCES parent(id) ON UPDATE CASCADE)",
8169        );
8170        if let Statement::CreateTable(ct) = stmt {
8171            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8172                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8173                    .constraints
8174                    .iter()
8175                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8176                    .unwrap()
8177                    .kind
8178                {
8179                    let upd = clause
8180                        .actions
8181                        .iter()
8182                        .find(|a| a.trigger == ForeignKeyTrigger::OnUpdate);
8183                    assert!(upd.is_some());
8184                    assert_eq!(upd.unwrap().action, ForeignKeyActionType::Cascade);
8185                }
8186            } else {
8187                unreachable!("expected Columns body");
8188            }
8189        } else {
8190            unreachable!("expected CreateTable");
8191        }
8192    }
8193
8194    #[test]
8195    fn test_foreign_key_restrict() {
8196        let stmt = parse_one(
8197            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8198             REFERENCES parent(id) ON DELETE RESTRICT)",
8199        );
8200        if let Statement::CreateTable(ct) = stmt {
8201            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8202                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8203                    .constraints
8204                    .iter()
8205                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8206                    .unwrap()
8207                    .kind
8208                {
8209                    let del = clause
8210                        .actions
8211                        .iter()
8212                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
8213                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Restrict);
8214                }
8215            } else {
8216                unreachable!("expected Columns body");
8217            }
8218        } else {
8219            unreachable!("expected CreateTable");
8220        }
8221    }
8222
8223    #[test]
8224    fn test_foreign_key_deferred() {
8225        let stmt = parse_one(
8226            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8227             REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED)",
8228        );
8229        if let Statement::CreateTable(ct) = stmt {
8230            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8231                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8232                    .constraints
8233                    .iter()
8234                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8235                    .unwrap()
8236                    .kind
8237                {
8238                    let def = clause.deferrable.as_ref().expect("DEFERRABLE missing");
8239                    assert!(!def.not, "should be DEFERRABLE, not NOT DEFERRABLE");
8240                    assert_eq!(def.initially, Some(DeferrableInitially::Deferred));
8241                }
8242            } else {
8243                unreachable!("expected Columns body");
8244            }
8245        } else {
8246            unreachable!("expected CreateTable");
8247        }
8248    }
8249
8250    #[test]
8251    fn test_foreign_key_pragma_required() {
8252        // Parser-level: FK syntax parses identically regardless of PRAGMA state.
8253        // Runtime enforcement requiring PRAGMA foreign_keys = ON is in VDBE/engine.
8254        let stmt = parse_one(
8255            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8256             REFERENCES parent(id) ON DELETE CASCADE ON UPDATE SET NULL)",
8257        );
8258        if let Statement::CreateTable(ct) = stmt {
8259            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8260                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8261                    .constraints
8262                    .iter()
8263                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8264                    .unwrap()
8265                    .kind
8266                {
8267                    assert_eq!(clause.actions.len(), 2);
8268                }
8269            } else {
8270                unreachable!("expected Columns body");
8271            }
8272        } else {
8273            unreachable!("expected CreateTable");
8274        }
8275    }
8276
8277    #[test]
8278    fn test_conflict_clause_on_not_null() {
8279        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL ON CONFLICT IGNORE)");
8280        if let Statement::CreateTable(ct) = stmt {
8281            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8282                let nn = columns[0]
8283                    .constraints
8284                    .iter()
8285                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
8286                if let ColumnConstraintKind::NotNull { conflict } = &nn.unwrap().kind {
8287                    assert_eq!(*conflict, Some(ConflictAction::Ignore));
8288                }
8289            } else {
8290                unreachable!("expected Columns body");
8291            }
8292        } else {
8293            unreachable!("expected CreateTable");
8294        }
8295    }
8296
8297    #[test]
8298    fn test_without_rowid_table() {
8299        let stmt = parse_one("CREATE TABLE t (k TEXT PRIMARY KEY, v BLOB) WITHOUT ROWID");
8300        if let Statement::CreateTable(ct) = stmt {
8301            assert!(ct.without_rowid);
8302        } else {
8303            unreachable!("expected CreateTable");
8304        }
8305    }
8306
8307    #[test]
8308    fn test_without_rowid_no_autoincrement() {
8309        // Parser-level: verify WITHOUT ROWID and AUTOINCREMENT can both parse.
8310        // Runtime rejection of AUTOINCREMENT on WITHOUT ROWID is in schema validation.
8311        let stmt = parse_one(
8312            "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT) WITHOUT ROWID",
8313        );
8314        if let Statement::CreateTable(ct) = stmt {
8315            assert!(ct.without_rowid);
8316            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8317                let pk = columns[0].constraints.iter().find(|c| {
8318                    matches!(
8319                        c.kind,
8320                        ColumnConstraintKind::PrimaryKey {
8321                            autoincrement: true,
8322                            ..
8323                        }
8324                    )
8325                });
8326                assert!(pk.is_some());
8327            } else {
8328                unreachable!("expected Columns body");
8329            }
8330        } else {
8331            unreachable!("expected CreateTable");
8332        }
8333    }
8334
8335    #[test]
8336    fn test_without_rowid_integer_pk_not_alias() {
8337        // Parser-level: INTEGER PRIMARY KEY in WITHOUT ROWID parses as normal PK.
8338        // Runtime non-aliasing of rowid is in the B-tree layer.
8339        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT) WITHOUT ROWID");
8340        if let Statement::CreateTable(ct) = stmt {
8341            assert!(ct.without_rowid);
8342            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8343                assert_eq!(columns[0].name, "id");
8344                assert!(columns[0].type_name.is_some());
8345            } else {
8346                unreachable!("expected Columns body");
8347            }
8348        } else {
8349            unreachable!("expected CreateTable");
8350        }
8351    }
8352
8353    #[test]
8354    fn test_strict_table_type_enforcement() {
8355        // Parser-level: STRICT keyword parses on CREATE TABLE.
8356        // Runtime type enforcement on INSERT/UPDATE is in VDBE/engine.
8357        let stmt = parse_one("CREATE TABLE t (id INTEGER, name TEXT, score REAL) STRICT");
8358        if let Statement::CreateTable(ct) = stmt {
8359            assert!(ct.strict);
8360            assert!(!ct.without_rowid);
8361        } else {
8362            unreachable!("expected CreateTable");
8363        }
8364    }
8365
8366    #[test]
8367    fn test_strict_table_any_column() {
8368        // Parser-level: ANY type name parses in STRICT table context.
8369        let stmt = parse_one("CREATE TABLE t (id INTEGER, data ANY) STRICT");
8370        if let Statement::CreateTable(ct) = stmt {
8371            assert!(ct.strict);
8372            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8373                let tn = columns[1].type_name.as_ref().expect("type name");
8374                assert_eq!(tn.name, "ANY");
8375            } else {
8376                unreachable!("expected Columns body");
8377            }
8378        } else {
8379            unreachable!("expected CreateTable");
8380        }
8381    }
8382
8383    #[test]
8384    fn test_strict_allowed_types() {
8385        // Parser-level: STRICT table with all allowed type names parses.
8386        let stmt =
8387            parse_one("CREATE TABLE t (a INT, b INTEGER, c REAL, d TEXT, e BLOB, f ANY) STRICT");
8388        if let Statement::CreateTable(ct) = stmt {
8389            assert!(ct.strict);
8390            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8391                assert_eq!(columns.len(), 6);
8392                let types: Vec<&str> = columns
8393                    .iter()
8394                    .map(|c| c.type_name.as_ref().unwrap().name.as_str())
8395                    .collect();
8396                assert_eq!(types, vec!["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"]);
8397            } else {
8398                unreachable!("expected Columns body");
8399            }
8400        } else {
8401            unreachable!("expected CreateTable");
8402        }
8403    }
8404
8405    #[test]
8406    fn test_generated_col_virtual() {
8407        let stmt = parse_one(
8408            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) VIRTUAL)",
8409        );
8410        if let Statement::CreateTable(ct) = stmt {
8411            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8412                let generated = columns[2]
8413                    .constraints
8414                    .iter()
8415                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8416                assert!(generated.is_some(), "Generated constraint missing");
8417                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
8418                    assert_eq!(*storage, Some(GeneratedStorage::Virtual));
8419                }
8420            } else {
8421                unreachable!("expected Columns body");
8422            }
8423        } else {
8424            unreachable!("expected CreateTable");
8425        }
8426    }
8427
8428    #[test]
8429    fn test_generated_col_stored() {
8430        let stmt = parse_one(
8431            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a * b) STORED)",
8432        );
8433        if let Statement::CreateTable(ct) = stmt {
8434            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8435                let generated = columns[2]
8436                    .constraints
8437                    .iter()
8438                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8439                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
8440                    assert_eq!(*storage, Some(GeneratedStorage::Stored));
8441                }
8442            } else {
8443                unreachable!("expected Columns body");
8444            }
8445        } else {
8446            unreachable!("expected CreateTable");
8447        }
8448    }
8449
8450    #[test]
8451    fn test_generated_col_ordering() {
8452        // Parser-level: generated columns with forward references parse correctly.
8453        // Runtime rejection of forward references is in schema validation.
8454        let stmt = parse_one(
8455            "CREATE TABLE t (\
8456             a INTEGER, \
8457             b INTEGER GENERATED ALWAYS AS (a + 1) STORED, \
8458             c INTEGER GENERATED ALWAYS AS (b * 2) VIRTUAL)",
8459        );
8460        if let Statement::CreateTable(ct) = stmt {
8461            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8462                assert_eq!(columns.len(), 3);
8463                // Both b and c have Generated constraints
8464                let gen_b = columns[1]
8465                    .constraints
8466                    .iter()
8467                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8468                let gen_c = columns[2]
8469                    .constraints
8470                    .iter()
8471                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8472                assert!(gen_b, "column b should be generated");
8473                assert!(gen_c, "column c should be generated");
8474            } else {
8475                unreachable!("expected Columns body");
8476            }
8477        } else {
8478            unreachable!("expected CreateTable");
8479        }
8480    }
8481
8482    #[test]
8483    fn test_generated_col_stored_indexable() {
8484        // Parser-level: STORED generated column can appear alongside indexes.
8485        // Runtime indexability verified in B-tree/engine tests.
8486        let stmts = parse_ok(
8487            "CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED); \
8488             CREATE INDEX idx_b ON t (b)",
8489        );
8490        assert_eq!(stmts.len(), 2);
8491        assert!(matches!(stmts[0], Statement::CreateTable(_)));
8492        assert!(matches!(stmts[1], Statement::CreateIndex(_)));
8493    }
8494
8495    #[test]
8496    fn test_type_affinity_int() {
8497        // Parser-level: type names containing "INT" parse as valid type names.
8498        // Runtime affinity determination is in the type system.
8499        let stmt = parse_one("CREATE TABLE t (a INTEGER, b BIGINT, c SMALLINT, d MEDIUMINT)");
8500        if let Statement::CreateTable(ct) = stmt {
8501            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8502                assert_eq!(columns.len(), 4);
8503                for col in columns {
8504                    let tn = col.type_name.as_ref().unwrap();
8505                    assert!(tn.name.contains("INT"), "{} should contain INT", tn.name);
8506                }
8507            } else {
8508                unreachable!("expected Columns body");
8509            }
8510        } else {
8511            unreachable!("expected CreateTable");
8512        }
8513    }
8514
8515    #[test]
8516    fn test_type_affinity_text() {
8517        let stmt = parse_one("CREATE TABLE t (a TEXT, b VARCHAR, c CLOB, d CHARACTER)");
8518        if let Statement::CreateTable(ct) = stmt {
8519            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8520                assert_eq!(columns.len(), 4);
8521                for col in columns {
8522                    assert!(col.type_name.is_some());
8523                }
8524            } else {
8525                unreachable!("expected Columns body");
8526            }
8527        } else {
8528            unreachable!("expected CreateTable");
8529        }
8530    }
8531
8532    #[test]
8533    fn test_type_affinity_blob() {
8534        let stmt = parse_one("CREATE TABLE t (a BLOB, b)");
8535        if let Statement::CreateTable(ct) = stmt {
8536            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8537                assert_eq!(columns.len(), 2);
8538                assert_eq!(columns[0].type_name.as_ref().unwrap().name, "BLOB");
8539                // Column b has no type name -> BLOB affinity
8540                assert!(columns[1].type_name.is_none());
8541            } else {
8542                unreachable!("expected Columns body");
8543            }
8544        } else {
8545            unreachable!("expected CreateTable");
8546        }
8547    }
8548
8549    #[test]
8550    fn test_type_affinity_real() {
8551        let stmt = parse_one("CREATE TABLE t (a REAL, b DOUBLE, c FLOAT)");
8552        if let Statement::CreateTable(ct) = stmt {
8553            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8554                assert_eq!(columns.len(), 3);
8555                for col in columns {
8556                    assert!(col.type_name.is_some());
8557                }
8558            } else {
8559                unreachable!("expected Columns body");
8560            }
8561        } else {
8562            unreachable!("expected CreateTable");
8563        }
8564    }
8565
8566    #[test]
8567    fn test_type_affinity_numeric() {
8568        let stmt = parse_one("CREATE TABLE t (a NUMERIC, b DECIMAL, c BOOLEAN)");
8569        if let Statement::CreateTable(ct) = stmt {
8570            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8571                assert_eq!(columns.len(), 3);
8572                for col in columns {
8573                    assert!(col.type_name.is_some());
8574                }
8575            } else {
8576                unreachable!("expected Columns body");
8577            }
8578        } else {
8579            unreachable!("expected CreateTable");
8580        }
8581    }
8582
8583    #[test]
8584    fn test_create_unique_index() {
8585        let stmt = parse_one("CREATE UNIQUE INDEX idx_email ON users (email)");
8586        if let Statement::CreateIndex(ci) = stmt {
8587            assert!(ci.unique);
8588            assert_eq!(ci.name.name, "idx_email");
8589            assert_eq!(ci.table, "users");
8590            assert_eq!(ci.columns.len(), 1);
8591            assert!(ci.where_clause.is_none());
8592        } else {
8593            unreachable!("expected CreateIndex");
8594        }
8595    }
8596
8597    #[test]
8598    fn test_partial_index() {
8599        let stmt = parse_one("CREATE INDEX idx_active ON users (name) WHERE active = 1");
8600        if let Statement::CreateIndex(ci) = stmt {
8601            assert!(!ci.unique);
8602            assert_eq!(ci.name.name, "idx_active");
8603            assert!(ci.where_clause.is_some(), "partial index WHERE missing");
8604        } else {
8605            unreachable!("expected CreateIndex");
8606        }
8607    }
8608
8609    #[test]
8610    fn test_partial_index_planner_usage() {
8611        // Parser-level: partial index with complex WHERE parses correctly.
8612        // Runtime planner usage (query WHERE implies index WHERE) is in planner tests.
8613        let stmt =
8614            parse_one("CREATE INDEX idx_recent ON orders (created_at) WHERE status != 'archived'");
8615        if let Statement::CreateIndex(ci) = stmt {
8616            assert!(ci.where_clause.is_some());
8617            assert_eq!(ci.columns.len(), 1);
8618        } else {
8619            unreachable!("expected CreateIndex");
8620        }
8621    }
8622
8623    #[test]
8624    fn test_expression_index() {
8625        let stmt = parse_one("CREATE INDEX idx_lower_name ON users (lower(name))");
8626        if let Statement::CreateIndex(ci) = stmt {
8627            assert_eq!(ci.columns.len(), 1);
8628            // The indexed expression should be a function call, not a plain column
8629            assert!(
8630                matches!(ci.columns[0].expr, Expr::FunctionCall { .. }),
8631                "expected function call expression in index"
8632            );
8633        } else {
8634            unreachable!("expected CreateIndex");
8635        }
8636    }
8637
8638    #[test]
8639    fn test_expression_index_planner_match() {
8640        // Parser-level: expression index with arithmetic parses correctly.
8641        // Runtime structural equality matching is in planner tests.
8642        let stmt = parse_one("CREATE INDEX idx_calc ON t (a + b * 2)");
8643        if let Statement::CreateIndex(ci) = stmt {
8644            assert_eq!(ci.columns.len(), 1);
8645            assert!(
8646                matches!(ci.columns[0].expr, Expr::BinaryOp { .. }),
8647                "expected binary op in expression index"
8648            );
8649        } else {
8650            unreachable!("expected CreateIndex");
8651        }
8652    }
8653
8654    #[test]
8655    fn test_index_collate_asc_desc() {
8656        let stmt = parse_one("CREATE INDEX idx_multi ON t (a COLLATE NOCASE ASC, b DESC, c)");
8657        if let Statement::CreateIndex(ci) = stmt {
8658            assert_eq!(ci.columns.len(), 3);
8659            // COLLATE is consumed by the expression parser as Expr::Collate
8660            assert!(
8661                matches!(
8662                    &ci.columns[0].expr,
8663                    Expr::Collate { collation, .. } if collation == "NOCASE"
8664                ),
8665                "expected Collate expr with NOCASE"
8666            );
8667            assert_eq!(ci.columns[0].direction, Some(SortDirection::Asc));
8668            assert_eq!(ci.columns[1].direction, Some(SortDirection::Desc));
8669            assert!(ci.columns[2].direction.is_none());
8670        } else {
8671            unreachable!("expected CreateIndex");
8672        }
8673    }
8674
8675    // -----------------------------------------------------------------------
8676    // bd-3kin §12.7-12.9 CREATE VIEW + CREATE TRIGGER + ALTER/DROP parsing tests
8677    // -----------------------------------------------------------------------
8678
8679    #[test]
8680    fn test_create_view_basic() {
8681        let stmt = parse_one("CREATE VIEW v AS SELECT id, name FROM users");
8682        if let Statement::CreateView(cv) = stmt {
8683            assert_eq!(cv.name.name, "v");
8684            assert!(!cv.if_not_exists);
8685            assert!(!cv.temporary);
8686            assert!(cv.columns.is_empty());
8687        } else {
8688            unreachable!("expected CreateView");
8689        }
8690    }
8691
8692    #[test]
8693    fn test_create_view_column_aliases() {
8694        let stmt = parse_one("CREATE VIEW v (user_id, user_name) AS SELECT id, name FROM users");
8695        if let Statement::CreateView(cv) = stmt {
8696            assert_eq!(cv.columns, vec!["user_id", "user_name"]);
8697        } else {
8698            unreachable!("expected CreateView");
8699        }
8700    }
8701
8702    #[test]
8703    fn test_create_view_if_not_exists() {
8704        let stmt = parse_one("CREATE VIEW IF NOT EXISTS v AS SELECT 1");
8705        if let Statement::CreateView(cv) = stmt {
8706            assert!(cv.if_not_exists);
8707        } else {
8708            unreachable!("expected CreateView");
8709        }
8710    }
8711
8712    #[test]
8713    fn test_create_temp_view() {
8714        let stmt = parse_one("CREATE TEMP VIEW tv AS SELECT 1");
8715        if let Statement::CreateView(cv) = stmt {
8716            assert!(cv.temporary);
8717        } else {
8718            unreachable!("expected CreateView");
8719        }
8720    }
8721
8722    #[test]
8723    fn test_view_inline_expansion() {
8724        // Parser-level: view defined with WHERE is captured in AST.
8725        // Runtime inline expansion (not materialization) is in the planner.
8726        let stmt =
8727            parse_one("CREATE VIEW active_users AS SELECT id, name FROM users WHERE active = 1");
8728        if let Statement::CreateView(cv) = stmt {
8729            assert_eq!(cv.name.name, "active_users");
8730        } else {
8731            unreachable!("expected CreateView");
8732        }
8733    }
8734
8735    #[test]
8736    fn test_view_read_only() {
8737        // Parser-level: views parse as SELECT-only definitions.
8738        // Runtime read-only enforcement (rejecting DML without INSTEAD OF) is in the engine.
8739        let stmt = parse_one("CREATE VIEW v AS SELECT * FROM t");
8740        assert!(matches!(stmt, Statement::CreateView(_)));
8741    }
8742
8743    #[test]
8744    fn test_view_with_recursive_cte() {
8745        // View referencing a subquery (parser does not yet support WITH directly
8746        // in CREATE VIEW ... AS context; CTE-in-view support is a planner concern).
8747        let stmt = parse_one(
8748            "CREATE VIEW tree AS \
8749             SELECT n.id, n.parent FROM nodes n \
8750             WHERE n.parent IS NULL \
8751             UNION ALL \
8752             SELECT c.id, c.parent FROM nodes c JOIN nodes p ON c.parent = p.id",
8753        );
8754        if let Statement::CreateView(cv) = stmt {
8755            assert_eq!(cv.name.name, "tree");
8756            // Compound UNION ALL captured
8757            assert!(
8758                !cv.query.body.compounds.is_empty(),
8759                "expected compound SELECT in view"
8760            );
8761        } else {
8762            unreachable!("expected CreateView");
8763        }
8764    }
8765
8766    #[test]
8767    fn test_instead_of_trigger_on_view() {
8768        let stmt = parse_one(
8769            "CREATE TRIGGER tr INSTEAD OF INSERT ON v BEGIN \
8770             INSERT INTO t (a) VALUES (NEW.a); \
8771             END",
8772        );
8773        if let Statement::CreateTrigger(ct) = stmt {
8774            assert_eq!(ct.timing, TriggerTiming::InsteadOf);
8775            assert!(matches!(ct.event, TriggerEvent::Insert));
8776            assert_eq!(ct.table, "v");
8777            assert!(!ct.body.is_empty());
8778        } else {
8779            unreachable!("expected CreateTrigger");
8780        }
8781    }
8782
8783    #[test]
8784    fn test_trigger_before_insert() {
8785        let stmt = parse_one("CREATE TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
8786        if let Statement::CreateTrigger(ct) = stmt {
8787            assert_eq!(ct.timing, TriggerTiming::Before);
8788            assert!(matches!(ct.event, TriggerEvent::Insert));
8789        } else {
8790            unreachable!("expected CreateTrigger");
8791        }
8792    }
8793
8794    #[test]
8795    fn test_trigger_after_insert() {
8796        let stmt = parse_one("CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END");
8797        if let Statement::CreateTrigger(ct) = stmt {
8798            assert_eq!(ct.timing, TriggerTiming::After);
8799            assert!(matches!(ct.event, TriggerEvent::Insert));
8800        } else {
8801            unreachable!("expected CreateTrigger");
8802        }
8803    }
8804
8805    #[test]
8806    fn test_trigger_before_update() {
8807        let stmt = parse_one("CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN SELECT OLD.a, NEW.a; END");
8808        if let Statement::CreateTrigger(ct) = stmt {
8809            assert_eq!(ct.timing, TriggerTiming::Before);
8810            assert!(matches!(ct.event, TriggerEvent::Update(_)));
8811        } else {
8812            unreachable!("expected CreateTrigger");
8813        }
8814    }
8815
8816    #[test]
8817    fn test_trigger_after_delete() {
8818        let stmt = parse_one("CREATE TRIGGER tr AFTER DELETE ON t BEGIN SELECT OLD.id; END");
8819        if let Statement::CreateTrigger(ct) = stmt {
8820            assert_eq!(ct.timing, TriggerTiming::After);
8821            assert!(matches!(ct.event, TriggerEvent::Delete));
8822        } else {
8823            unreachable!("expected CreateTrigger");
8824        }
8825    }
8826
8827    #[test]
8828    fn test_trigger_update_of_column() {
8829        let stmt =
8830            parse_one("CREATE TRIGGER tr BEFORE UPDATE OF name, email ON t BEGIN SELECT 1; END");
8831        if let Statement::CreateTrigger(ct) = stmt {
8832            if let TriggerEvent::Update(cols) = &ct.event {
8833                assert_eq!(cols, &["name", "email"]);
8834            } else {
8835                unreachable!("expected Update event with columns");
8836            }
8837        } else {
8838            unreachable!("expected CreateTrigger");
8839        }
8840    }
8841
8842    #[test]
8843    fn test_trigger_when_clause() {
8844        let stmt = parse_one(
8845            "CREATE TRIGGER tr BEFORE INSERT ON t WHEN NEW.active = 1 BEGIN SELECT 1; END",
8846        );
8847        if let Statement::CreateTrigger(ct) = stmt {
8848            assert!(ct.when.is_some(), "WHEN clause missing");
8849        } else {
8850            unreachable!("expected CreateTrigger");
8851        }
8852    }
8853
8854    #[test]
8855    fn test_trigger_old_new_pseudo_tables() {
8856        let stmt = parse_one(
8857            "CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN \
8858             INSERT INTO log (old_val, new_val) VALUES (OLD.a, NEW.a); \
8859             END",
8860        );
8861        if let Statement::CreateTrigger(ct) = stmt {
8862            assert_eq!(ct.body.len(), 1);
8863            assert!(matches!(ct.body[0], Statement::Insert(_)));
8864        } else {
8865            unreachable!("expected CreateTrigger");
8866        }
8867    }
8868
8869    #[test]
8870    fn test_trigger_raise_abort() {
8871        let stmt = parse_one(
8872            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8873             SELECT RAISE(ABORT, 'not allowed'); \
8874             END",
8875        );
8876        if let Statement::CreateTrigger(ct) = stmt {
8877            assert_eq!(ct.body.len(), 1);
8878        } else {
8879            unreachable!("expected CreateTrigger");
8880        }
8881    }
8882
8883    #[test]
8884    fn test_trigger_raise_rollback() {
8885        let stmt = parse_one(
8886            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8887             SELECT RAISE(ROLLBACK, 'invalid'); \
8888             END",
8889        );
8890        if let Statement::CreateTrigger(ct) = stmt {
8891            assert_eq!(ct.body.len(), 1);
8892        } else {
8893            unreachable!("expected CreateTrigger");
8894        }
8895    }
8896
8897    #[test]
8898    fn test_trigger_raise_fail() {
8899        let stmt = parse_one(
8900            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8901             SELECT RAISE(FAIL, 'bad data'); \
8902             END",
8903        );
8904        if let Statement::CreateTrigger(ct) = stmt {
8905            assert_eq!(ct.body.len(), 1);
8906        } else {
8907            unreachable!("expected CreateTrigger");
8908        }
8909    }
8910
8911    #[test]
8912    fn test_trigger_raise_ignore() {
8913        let stmt = parse_one(
8914            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8915             SELECT RAISE(IGNORE); \
8916             END",
8917        );
8918        if let Statement::CreateTrigger(ct) = stmt {
8919            assert_eq!(ct.body.len(), 1);
8920        } else {
8921            unreachable!("expected CreateTrigger");
8922        }
8923    }
8924
8925    #[test]
8926    fn test_trigger_recursive() {
8927        // Parser-level: trigger referencing its own table parses normally.
8928        // Runtime recursive trigger behavior (PRAGMA recursive_triggers) is in the engine.
8929        let stmt = parse_one(
8930            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
8931             INSERT INTO t (val) VALUES (NEW.val + 1); \
8932             END",
8933        );
8934        if let Statement::CreateTrigger(ct) = stmt {
8935            assert_eq!(ct.timing, TriggerTiming::After);
8936            assert_eq!(ct.table, "t");
8937            assert_eq!(ct.body.len(), 1);
8938        } else {
8939            unreachable!("expected CreateTrigger");
8940        }
8941    }
8942
8943    #[test]
8944    fn test_trigger_max_recursion_depth() {
8945        // Parser-level: trigger with WHEN depth guard parses.
8946        // Runtime SQLITE_MAX_TRIGGER_DEPTH enforcement is in the VDBE.
8947        let stmt = parse_one(
8948            "CREATE TRIGGER tr AFTER INSERT ON t \
8949             WHEN NEW.depth < 1000 BEGIN \
8950             INSERT INTO t (depth) VALUES (NEW.depth + 1); \
8951             END",
8952        );
8953        if let Statement::CreateTrigger(ct) = stmt {
8954            assert!(ct.when.is_some());
8955            assert_eq!(ct.body.len(), 1);
8956        } else {
8957            unreachable!("expected CreateTrigger");
8958        }
8959    }
8960
8961    #[test]
8962    fn test_trigger_heap_frame_stack() {
8963        // Parser-level: trigger with UPDATE body parses correctly.
8964        // Runtime heap-allocated frame stack is in the VDBE.
8965        let stmt = parse_one(
8966            "CREATE TRIGGER tr AFTER UPDATE ON t BEGIN \
8967             UPDATE t SET counter = counter + 1 WHERE id = NEW.parent_id; \
8968             END",
8969        );
8970        if let Statement::CreateTrigger(ct) = stmt {
8971            assert_eq!(ct.body.len(), 1);
8972            assert!(matches!(ct.body[0], Statement::Update(_)));
8973        } else {
8974            unreachable!("expected CreateTrigger");
8975        }
8976    }
8977
8978    #[test]
8979    fn test_trigger_multiple_dml() {
8980        let stmt = parse_one(
8981            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
8982             INSERT INTO audit (action) VALUES ('insert'); \
8983             UPDATE stats SET count = count + 1; \
8984             END",
8985        );
8986        if let Statement::CreateTrigger(ct) = stmt {
8987            assert_eq!(ct.body.len(), 2);
8988            assert!(matches!(ct.body[0], Statement::Insert(_)));
8989            assert!(matches!(ct.body[1], Statement::Update(_)));
8990        } else {
8991            unreachable!("expected CreateTrigger");
8992        }
8993    }
8994
8995    #[test]
8996    fn test_alter_table_rename() {
8997        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
8998        if let Statement::AlterTable(at) = stmt {
8999            assert_eq!(at.table.name, "t");
9000            assert!(matches!(at.action, AlterTableAction::RenameTo(ref n) if n == "t2"));
9001        } else {
9002            unreachable!("expected AlterTable");
9003        }
9004    }
9005
9006    #[test]
9007    fn test_alter_table_rename_column() {
9008        let stmt = parse_one("ALTER TABLE t RENAME COLUMN old_name TO new_name");
9009        if let Statement::AlterTable(at) = stmt {
9010            if let AlterTableAction::RenameColumn { old, new } = &at.action {
9011                assert_eq!(old, "old_name");
9012                assert_eq!(new, "new_name");
9013            } else {
9014                unreachable!("expected RenameColumn action");
9015            }
9016        } else {
9017            unreachable!("expected AlterTable");
9018        }
9019    }
9020
9021    #[test]
9022    fn test_alter_table_add_column() {
9023        let stmt = parse_one("ALTER TABLE t ADD COLUMN email TEXT NOT NULL DEFAULT ''");
9024        if let Statement::AlterTable(at) = stmt {
9025            if let AlterTableAction::AddColumn(col) = &at.action {
9026                assert_eq!(col.name, "email");
9027                assert!(!col.constraints.is_empty());
9028            } else {
9029                unreachable!("expected AddColumn action");
9030            }
9031        } else {
9032            unreachable!("expected AlterTable");
9033        }
9034    }
9035
9036    #[test]
9037    fn test_alter_table_remove_column() {
9038        let stmt = parse_one("ALTER TABLE t DROP COLUMN old_col");
9039        if let Statement::AlterTable(at) = stmt {
9040            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "old_col"));
9041        } else {
9042            unreachable!("expected AlterTable");
9043        }
9044    }
9045
9046    #[test]
9047    fn test_alter_remove_column_pk_fails() {
9048        // Parser-level: DROP COLUMN on a PK column parses normally.
9049        // Runtime rejection is in schema validation.
9050        let stmt = parse_one("ALTER TABLE t DROP COLUMN id");
9051        if let Statement::AlterTable(at) = stmt {
9052            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "id"));
9053        } else {
9054            unreachable!("expected AlterTable");
9055        }
9056    }
9057
9058    #[test]
9059    fn test_alter_remove_column_unique_fails() {
9060        // Parser-level: DROP COLUMN on UNIQUE column parses normally.
9061        let stmt = parse_one("ALTER TABLE t DROP COLUMN email");
9062        if let Statement::AlterTable(at) = stmt {
9063            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "email"));
9064        } else {
9065            unreachable!("expected AlterTable");
9066        }
9067    }
9068
9069    #[test]
9070    fn test_alter_remove_column_index_fails() {
9071        // Parser-level: DROP COLUMN on indexed column parses normally.
9072        let stmt = parse_one("ALTER TABLE t DROP COLUMN indexed_col");
9073        if let Statement::AlterTable(at) = stmt {
9074            assert!(matches!(
9075                at.action,
9076                AlterTableAction::DropColumn(ref c) if c == "indexed_col"
9077            ));
9078        } else {
9079            unreachable!("expected AlterTable");
9080        }
9081    }
9082
9083    #[test]
9084    fn test_alter_remove_column_check_fails() {
9085        // Parser-level: DROP COLUMN on CHECK-constrained column parses normally.
9086        let stmt = parse_one("ALTER TABLE t DROP COLUMN checked_col");
9087        if let Statement::AlterTable(at) = stmt {
9088            assert!(matches!(
9089                at.action,
9090                AlterTableAction::DropColumn(ref c) if c == "checked_col"
9091            ));
9092        } else {
9093            unreachable!("expected AlterTable");
9094        }
9095    }
9096
9097    #[test]
9098    fn test_alter_remove_column_fk_fails() {
9099        // Parser-level: DROP COLUMN on FK-constrained column parses normally.
9100        let stmt = parse_one("ALTER TABLE t DROP COLUMN fk_col");
9101        if let Statement::AlterTable(at) = stmt {
9102            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "fk_col"));
9103        } else {
9104            unreachable!("expected AlterTable");
9105        }
9106    }
9107
9108    #[test]
9109    fn test_alter_remove_only_column_fails() {
9110        // Parser-level: DROP COLUMN on only column parses normally.
9111        let stmt = parse_one("ALTER TABLE t DROP COLUMN only_col");
9112        if let Statement::AlterTable(at) = stmt {
9113            assert!(matches!(
9114                at.action,
9115                AlterTableAction::DropColumn(ref c) if c == "only_col"
9116            ));
9117        } else {
9118            unreachable!("expected AlterTable");
9119        }
9120    }
9121
9122    #[test]
9123    fn test_ddl_remove_table() {
9124        let stmt = parse_one("DROP TABLE t");
9125        if let Statement::Drop(d) = stmt {
9126            assert_eq!(d.object_type, DropObjectType::Table);
9127            assert!(!d.if_exists);
9128            assert_eq!(d.name.name, "t");
9129        } else {
9130            unreachable!("expected Drop");
9131        }
9132    }
9133
9134    #[test]
9135    fn test_ddl_remove_table_if_exists() {
9136        let stmt = parse_one("DROP TABLE IF EXISTS t");
9137        if let Statement::Drop(d) = stmt {
9138            assert_eq!(d.object_type, DropObjectType::Table);
9139            assert!(d.if_exists);
9140        } else {
9141            unreachable!("expected Drop");
9142        }
9143    }
9144
9145    #[test]
9146    fn test_ddl_remove_index() {
9147        let stmt = parse_one("DROP INDEX idx");
9148        if let Statement::Drop(d) = stmt {
9149            assert_eq!(d.object_type, DropObjectType::Index);
9150            assert_eq!(d.name.name, "idx");
9151        } else {
9152            unreachable!("expected Drop");
9153        }
9154    }
9155
9156    #[test]
9157    fn test_ddl_remove_view() {
9158        let stmt = parse_one("DROP VIEW v");
9159        if let Statement::Drop(d) = stmt {
9160            assert_eq!(d.object_type, DropObjectType::View);
9161            assert_eq!(d.name.name, "v");
9162        } else {
9163            unreachable!("expected Drop");
9164        }
9165    }
9166
9167    #[test]
9168    fn test_ddl_remove_trigger() {
9169        let stmt = parse_one("DROP TRIGGER tr");
9170        if let Statement::Drop(d) = stmt {
9171            assert_eq!(d.object_type, DropObjectType::Trigger);
9172            assert_eq!(d.name.name, "tr");
9173        } else {
9174            unreachable!("expected Drop");
9175        }
9176    }
9177
9178    // -----------------------------------------------------------------------
9179    // bd-3kin §12.7-12.9 DDL gap-fill: REINDEX, ANALYZE, qualified names,
9180    //                                   IF NOT EXISTS/TEMP triggers
9181    // -----------------------------------------------------------------------
9182
9183    #[test]
9184    fn test_reindex_global() {
9185        let stmt = parse_one("REINDEX");
9186        assert!(matches!(stmt, Statement::Reindex(None)));
9187    }
9188
9189    #[test]
9190    fn test_reindex_table() {
9191        let stmt = parse_one("REINDEX t");
9192        if let Statement::Reindex(Some(name)) = stmt {
9193            assert_eq!(name.name, "t");
9194            assert!(name.schema.is_none());
9195        } else {
9196            unreachable!("expected Reindex(Some), got {stmt:?}");
9197        }
9198    }
9199
9200    #[test]
9201    fn test_reindex_qualified() {
9202        let stmt = parse_one("REINDEX main.idx");
9203        if let Statement::Reindex(Some(name)) = stmt {
9204            assert_eq!(name.schema.as_deref(), Some("main"));
9205            assert_eq!(name.name, "idx");
9206        } else {
9207            unreachable!("expected Reindex(Some), got {stmt:?}");
9208        }
9209    }
9210
9211    #[test]
9212    fn test_analyze_global() {
9213        let stmt = parse_one("ANALYZE");
9214        assert!(matches!(stmt, Statement::Analyze(None)));
9215    }
9216
9217    #[test]
9218    fn test_analyze_table() {
9219        let stmt = parse_one("ANALYZE t");
9220        if let Statement::Analyze(Some(name)) = stmt {
9221            assert_eq!(name.name, "t");
9222            assert!(name.schema.is_none());
9223        } else {
9224            unreachable!("expected Analyze(Some), got {stmt:?}");
9225        }
9226    }
9227
9228    #[test]
9229    fn test_analyze_qualified() {
9230        let stmt = parse_one("ANALYZE main.t");
9231        if let Statement::Analyze(Some(name)) = stmt {
9232            assert_eq!(name.schema.as_deref(), Some("main"));
9233            assert_eq!(name.name, "t");
9234        } else {
9235            unreachable!("expected Analyze(Some), got {stmt:?}");
9236        }
9237    }
9238
9239    #[test]
9240    fn test_drop_view_if_exists() {
9241        let stmt = parse_one("DROP VIEW IF EXISTS v");
9242        if let Statement::Drop(d) = stmt {
9243            assert_eq!(d.object_type, DropObjectType::View);
9244            assert!(d.if_exists);
9245            assert_eq!(d.name.name, "v");
9246        } else {
9247            unreachable!("expected Drop");
9248        }
9249    }
9250
9251    #[test]
9252    fn test_drop_index_if_exists() {
9253        let stmt = parse_one("DROP INDEX IF EXISTS idx");
9254        if let Statement::Drop(d) = stmt {
9255            assert_eq!(d.object_type, DropObjectType::Index);
9256            assert!(d.if_exists);
9257        } else {
9258            unreachable!("expected Drop");
9259        }
9260    }
9261
9262    #[test]
9263    fn test_drop_trigger_if_exists_qualified() {
9264        let stmt = parse_one("DROP TRIGGER IF EXISTS main.tr");
9265        if let Statement::Drop(d) = stmt {
9266            assert_eq!(d.object_type, DropObjectType::Trigger);
9267            assert!(d.if_exists);
9268            assert_eq!(d.name.schema.as_deref(), Some("main"));
9269            assert_eq!(d.name.name, "tr");
9270        } else {
9271            unreachable!("expected Drop");
9272        }
9273    }
9274
9275    #[test]
9276    fn test_drop_table_qualified() {
9277        let stmt = parse_one("DROP TABLE main.t");
9278        if let Statement::Drop(d) = stmt {
9279            assert_eq!(d.name.schema.as_deref(), Some("main"));
9280            assert_eq!(d.name.name, "t");
9281        } else {
9282            unreachable!("expected Drop");
9283        }
9284    }
9285
9286    #[test]
9287    fn test_create_trigger_if_not_exists() {
9288        let stmt =
9289            parse_one("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9290        if let Statement::CreateTrigger(ct) = stmt {
9291            assert!(ct.if_not_exists);
9292            assert_eq!(ct.name.name, "tr");
9293        } else {
9294            unreachable!("expected CreateTrigger");
9295        }
9296    }
9297
9298    #[test]
9299    fn test_create_temp_trigger() {
9300        let stmt = parse_one("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9301        if let Statement::CreateTrigger(ct) = stmt {
9302            assert!(ct.temporary);
9303            assert_eq!(ct.name.name, "tr");
9304        } else {
9305            unreachable!("expected CreateTrigger");
9306        }
9307    }
9308
9309    #[test]
9310    fn test_create_view_qualified_name() {
9311        let stmt = parse_one("CREATE VIEW main.v AS SELECT 1");
9312        if let Statement::CreateView(cv) = stmt {
9313            assert_eq!(cv.name.schema.as_deref(), Some("main"));
9314            assert_eq!(cv.name.name, "v");
9315        } else {
9316            unreachable!("expected CreateView");
9317        }
9318    }
9319
9320    #[test]
9321    fn test_alter_table_qualified() {
9322        let stmt = parse_one("ALTER TABLE main.t RENAME TO u");
9323        if let Statement::AlterTable(at) = stmt {
9324            assert_eq!(at.table.schema.as_deref(), Some("main"));
9325            assert_eq!(at.table.name, "t");
9326        } else {
9327            unreachable!("expected AlterTable");
9328        }
9329    }
9330
9331    #[test]
9332    fn test_roundtrip_reindex_all() {
9333        assert_roundtrip("REINDEX");
9334        assert_roundtrip("REINDEX t");
9335        assert_roundtrip("REINDEX main.idx");
9336    }
9337
9338    #[test]
9339    fn test_roundtrip_analyze_all() {
9340        assert_roundtrip("ANALYZE");
9341        assert_roundtrip("ANALYZE t");
9342        assert_roundtrip("ANALYZE main.t");
9343    }
9344
9345    #[test]
9346    fn test_roundtrip_drop_all_types_extended() {
9347        assert_roundtrip("DROP TABLE IF EXISTS main.t");
9348        assert_roundtrip("DROP VIEW IF EXISTS v");
9349        assert_roundtrip("DROP INDEX IF EXISTS idx");
9350        assert_roundtrip("DROP TRIGGER IF EXISTS main.tr");
9351    }
9352
9353    #[test]
9354    fn test_roundtrip_create_trigger_extended() {
9355        assert_roundtrip("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9356        assert_roundtrip("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9357        assert_roundtrip(
9358            "CREATE TRIGGER tr INSTEAD OF UPDATE ON v BEGIN INSERT INTO log VALUES (1); END",
9359        );
9360        assert_roundtrip("CREATE TRIGGER tr BEFORE UPDATE OF a, b ON t BEGIN SELECT 1; END");
9361        assert_roundtrip(
9362            "CREATE TRIGGER tr AFTER DELETE ON \"order\" BEGIN INSERT INTO log VALUES (OLD.id); END",
9363        );
9364    }
9365
9366    #[test]
9367    fn test_roundtrip_create_view_extended() {
9368        assert_roundtrip("CREATE VIEW main.v AS SELECT 1");
9369        assert_roundtrip("CREATE VIEW v(x, y, z) AS SELECT a, b, c FROM t");
9370    }
9371
9372    #[test]
9373    fn test_roundtrip_alter_table_extended() {
9374        assert_roundtrip("ALTER TABLE t RENAME COLUMN a TO b");
9375        assert_roundtrip("ALTER TABLE main.t RENAME TO u");
9376        assert_roundtrip("ALTER TABLE t ADD COLUMN c INTEGER NOT NULL DEFAULT 0");
9377    }
9378
9379    // -----------------------------------------------------------------------
9380    // bd-7pxb §12.10-12.12 Transaction Control + ATTACH/DETACH + EXPLAIN
9381    // -----------------------------------------------------------------------
9382
9383    #[test]
9384    fn test_begin_deferred() {
9385        let stmt = parse_one("BEGIN DEFERRED TRANSACTION");
9386        if let Statement::Begin(b) = stmt {
9387            assert_eq!(b.mode, Some(TransactionMode::Deferred));
9388        } else {
9389            unreachable!("expected Begin");
9390        }
9391    }
9392
9393    #[test]
9394    fn test_begin_immediate() {
9395        let stmt = parse_one("BEGIN IMMEDIATE");
9396        if let Statement::Begin(b) = stmt {
9397            assert_eq!(b.mode, Some(TransactionMode::Immediate));
9398        } else {
9399            unreachable!("expected Begin");
9400        }
9401    }
9402
9403    #[test]
9404    fn test_begin_exclusive() {
9405        let stmt = parse_one("BEGIN EXCLUSIVE TRANSACTION");
9406        if let Statement::Begin(b) = stmt {
9407            assert_eq!(b.mode, Some(TransactionMode::Exclusive));
9408        } else {
9409            unreachable!("expected Begin");
9410        }
9411    }
9412
9413    #[test]
9414    fn test_begin_concurrent() {
9415        let stmt = parse_one("BEGIN CONCURRENT");
9416        if let Statement::Begin(b) = stmt {
9417            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
9418        } else {
9419            unreachable!("expected Begin");
9420        }
9421    }
9422
9423    #[test]
9424    fn test_concurrent_no_conflict() {
9425        // Parser-level: BEGIN without mode (the concurrent entry point) parses.
9426        // Runtime concurrent writer conflict detection is in the MVCC/WAL layer.
9427        let stmt = parse_one("BEGIN");
9428        assert!(matches!(stmt, Statement::Begin(_)));
9429    }
9430
9431    #[test]
9432    fn test_concurrent_page_conflict() {
9433        // Parser-level: verify basic transaction and DML parse.
9434        // Runtime page-level conflict (SQLITE_BUSY_SNAPSHOT) is in the MVCC layer.
9435        let stmts = parse_ok("BEGIN; INSERT INTO t (a) VALUES (1)");
9436        assert_eq!(stmts.len(), 2);
9437        assert!(matches!(stmts[0], Statement::Begin(_)));
9438        assert!(matches!(stmts[1], Statement::Insert(_)));
9439    }
9440
9441    #[test]
9442    fn test_commit_end_synonym() {
9443        let stmt1 = parse_one("COMMIT");
9444        assert!(matches!(stmt1, Statement::Commit));
9445        let stmt2 = parse_one("END TRANSACTION");
9446        assert!(matches!(stmt2, Statement::Commit));
9447        let stmt3 = parse_one("COMMIT TRANSACTION");
9448        assert!(matches!(stmt3, Statement::Commit));
9449    }
9450
9451    #[test]
9452    fn test_rollback() {
9453        let stmt = parse_one("ROLLBACK");
9454        if let Statement::Rollback(r) = stmt {
9455            assert!(r.to_savepoint.is_none());
9456        } else {
9457            unreachable!("expected Rollback");
9458        }
9459    }
9460
9461    #[test]
9462    fn test_savepoint_basic() {
9463        let stmt = parse_one("SAVEPOINT sp1");
9464        assert!(matches!(stmt, Statement::Savepoint(ref name) if name == "sp1"));
9465    }
9466
9467    #[test]
9468    fn test_savepoint_release() {
9469        let stmt = parse_one("RELEASE SAVEPOINT sp1");
9470        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp1"));
9471    }
9472
9473    #[test]
9474    fn test_savepoint_release_removes_later() {
9475        // Parser-level: RELEASE without SAVEPOINT keyword also works.
9476        // Runtime savepoint stack semantics verified in engine tests.
9477        let stmt = parse_one("RELEASE sp2");
9478        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp2"));
9479    }
9480
9481    #[test]
9482    fn test_savepoint_rollback_to() {
9483        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
9484        if let Statement::Rollback(r) = stmt {
9485            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
9486        } else {
9487            unreachable!("expected Rollback");
9488        }
9489    }
9490
9491    #[test]
9492    fn test_savepoint_nested() {
9493        // Parser-level: multiple savepoints in sequence parse independently.
9494        // Runtime stack semantics verified in engine tests.
9495        let stmts = parse_ok("SAVEPOINT sp1; SAVEPOINT sp2; SAVEPOINT sp3");
9496        assert_eq!(stmts.len(), 3);
9497        assert!(matches!(stmts[0], Statement::Savepoint(ref n) if n == "sp1"));
9498        assert!(matches!(stmts[1], Statement::Savepoint(ref n) if n == "sp2"));
9499        assert!(matches!(stmts[2], Statement::Savepoint(ref n) if n == "sp3"));
9500    }
9501
9502    #[test]
9503    fn test_savepoint_rollback_then_continue() {
9504        // Parser-level: ROLLBACK TO followed by more DML parses.
9505        let stmts = parse_ok("ROLLBACK TO sp1; INSERT INTO t VALUES (1)");
9506        assert_eq!(stmts.len(), 2);
9507        assert!(matches!(stmts[0], Statement::Rollback(_)));
9508        assert!(matches!(stmts[1], Statement::Insert(_)));
9509    }
9510
9511    #[test]
9512    fn test_attach_database() {
9513        let stmt = parse_one("ATTACH DATABASE 'other.db' AS other");
9514        if let Statement::Attach(a) = stmt {
9515            assert_eq!(a.schema, "other");
9516        } else {
9517            unreachable!("expected Attach");
9518        }
9519    }
9520
9521    #[test]
9522    fn test_attach_schema_qualified_access() {
9523        // Parser-level: schema-qualified table reference parses correctly.
9524        let stmt = parse_one("SELECT * FROM other.t");
9525        if let Statement::Select(s) = stmt {
9526            if let SelectCore::Select { from, .. } = &s.body.select {
9527                let from = from.as_ref().expect("FROM clause");
9528                match &from.source {
9529                    TableOrSubquery::Table { name, .. } => {
9530                        assert_eq!(name.schema.as_deref(), Some("other"));
9531                        assert_eq!(name.name, "t");
9532                    }
9533                    other => unreachable!("expected Table source, got {other:?}"),
9534                }
9535            } else {
9536                unreachable!("expected Select core");
9537            }
9538        } else {
9539            unreachable!("expected Select");
9540        }
9541    }
9542
9543    #[test]
9544    fn test_detach_database() {
9545        let stmt = parse_one("DETACH DATABASE other");
9546        assert!(matches!(stmt, Statement::Detach(ref name) if name == "other"));
9547    }
9548
9549    #[test]
9550    fn test_attach_max_limit() {
9551        // Parser-level: ATTACH parses identically regardless of limit.
9552        // Runtime SQLITE_MAX_ATTACHED enforcement is in the engine.
9553        let stmt = parse_one("ATTACH 'db11.sqlite' AS db11");
9554        if let Statement::Attach(a) = stmt {
9555            assert_eq!(a.schema, "db11");
9556        } else {
9557            unreachable!("expected Attach");
9558        }
9559    }
9560
9561    #[test]
9562    fn test_cross_database_transaction() {
9563        // Parser-level: transaction with cross-database DML parses.
9564        // Runtime cross-database atomic commit is in WAL/MVCC layer.
9565        let stmts = parse_ok("BEGIN; INSERT INTO main.t SELECT * FROM other.t; COMMIT");
9566        assert_eq!(stmts.len(), 3);
9567        assert!(matches!(stmts[0], Statement::Begin(_)));
9568        assert!(matches!(stmts[1], Statement::Insert(_)));
9569        assert!(matches!(stmts[2], Statement::Commit));
9570    }
9571
9572    #[test]
9573    fn test_explain_returns_bytecode() {
9574        let stmt = parse_one("EXPLAIN SELECT 1");
9575        if let Statement::Explain { query_plan, stmt } = stmt {
9576            assert!(!query_plan);
9577            assert!(matches!(*stmt, Statement::Select(_)));
9578        } else {
9579            unreachable!("expected Explain");
9580        }
9581    }
9582
9583    #[test]
9584    fn test_explain_query_plan_columns() {
9585        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
9586        if let Statement::Explain { query_plan, stmt } = stmt {
9587            assert!(query_plan);
9588            assert!(matches!(*stmt, Statement::Select(_)));
9589        } else {
9590            unreachable!("expected Explain");
9591        }
9592    }
9593
9594    #[test]
9595    fn test_explain_query_plan_shows_index() {
9596        // Parser-level: EXPLAIN QUERY PLAN on indexed query parses.
9597        // Runtime index usage in EQP output is in the planner.
9598        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
9599        if let Statement::Explain { query_plan, .. } = stmt {
9600            assert!(query_plan);
9601        } else {
9602            unreachable!("expected Explain");
9603        }
9604    }
9605
9606    #[test]
9607    fn test_explain_query_plan_tree_structure() {
9608        // Parser-level: EXPLAIN QUERY PLAN on a join query parses.
9609        // Runtime tree structure in EQP output is in the planner.
9610        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id");
9611        if let Statement::Explain { query_plan, stmt } = stmt {
9612            assert!(query_plan);
9613            assert!(matches!(*stmt, Statement::Select(_)));
9614        } else {
9615            unreachable!("expected Explain");
9616        }
9617    }
9618
9619    // -----------------------------------------------------------------------
9620    // bd-2kvo Phase 3 acceptance: keywords as identifiers
9621    // -----------------------------------------------------------------------
9622
9623    #[test]
9624    fn test_parser_keyword_as_column_name() {
9625        // "order" is a keyword but valid as a column name in many contexts.
9626        let stmt = parse_one("SELECT \"order\" FROM t");
9627        assert!(matches!(stmt, Statement::Select(_)));
9628    }
9629
9630    #[test]
9631    fn test_parser_keyword_as_alias() {
9632        let stmt = parse_one("SELECT 1 AS \"limit\"");
9633        assert!(matches!(stmt, Statement::Select(_)));
9634    }
9635
9636    #[test]
9637    fn test_parser_keyword_as_table_name() {
9638        let stmt = parse_one("SELECT * FROM \"group\"");
9639        assert!(matches!(stmt, Statement::Select(_)));
9640    }
9641
9642    // -----------------------------------------------------------------------
9643    // bd-2kvo Phase 3 acceptance: all statement types (Section 12 coverage)
9644    // -----------------------------------------------------------------------
9645
9646    #[test]
9647    fn test_parser_all_statement_types() {
9648        // Each statement type from Section 12 must parse without error.
9649        let statements = [
9650            // DML
9651            "SELECT 1",
9652            "INSERT INTO t VALUES (1)",
9653            "INSERT OR REPLACE INTO t VALUES (1)",
9654            "UPDATE t SET a = 1",
9655            "DELETE FROM t WHERE id = 1",
9656            "REPLACE INTO t VALUES (1)",
9657            // DDL
9658            "CREATE TABLE t (id INTEGER PRIMARY KEY)",
9659            "CREATE TEMPORARY TABLE t (id INTEGER)",
9660            "CREATE TABLE IF NOT EXISTS t (id INTEGER)",
9661            "CREATE INDEX idx ON t (a)",
9662            "CREATE UNIQUE INDEX idx ON t (a)",
9663            "CREATE VIEW v AS SELECT 1",
9664            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END",
9665            "CREATE VIRTUAL TABLE t USING fts5(a, b)",
9666            "ALTER TABLE t RENAME TO t2",
9667            "ALTER TABLE t ADD COLUMN c TEXT",
9668            "ALTER TABLE t DROP COLUMN c",
9669            "ALTER TABLE t RENAME COLUMN a TO b",
9670            "DROP TABLE t",
9671            "DROP TABLE IF EXISTS t",
9672            "DROP INDEX idx",
9673            "DROP VIEW v",
9674            "DROP TRIGGER tr",
9675            // Transaction
9676            "BEGIN",
9677            "BEGIN DEFERRED",
9678            "BEGIN IMMEDIATE",
9679            "BEGIN EXCLUSIVE",
9680            "COMMIT",
9681            "END",
9682            "ROLLBACK",
9683            "SAVEPOINT sp1",
9684            "RELEASE sp1",
9685            "RELEASE SAVEPOINT sp1",
9686            "ROLLBACK TO sp1",
9687            "ROLLBACK TO SAVEPOINT sp1",
9688            // Utility
9689            "ATTACH DATABASE ':memory:' AS db2",
9690            "DETACH db2",
9691            "ANALYZE",
9692            "ANALYZE t",
9693            "VACUUM",
9694            "VACUUM INTO '/tmp/backup.db'",
9695            "REINDEX",
9696            "REINDEX t",
9697            "EXPLAIN SELECT 1",
9698            "EXPLAIN QUERY PLAN SELECT 1",
9699            // PRAGMA
9700            "PRAGMA journal_mode",
9701            "PRAGMA journal_mode = WAL",
9702            "PRAGMA table_info(t)",
9703        ];
9704
9705        for sql in &statements {
9706            let mut p = Parser::from_sql(sql);
9707            let (stmts, errs) = p.parse_all();
9708            assert!(errs.is_empty(), "failed to parse '{sql}': {errs:?}");
9709            assert_eq!(
9710                stmts.len(),
9711                1,
9712                "expected 1 statement for '{sql}', got {}",
9713                stmts.len()
9714            );
9715        }
9716    }
9717
9718    // -----------------------------------------------------------------------
9719    // bd-2kvo Phase 3 acceptance: expression precedence
9720    // -----------------------------------------------------------------------
9721
9722    #[test]
9723    fn test_parser_expression_precedence_mul_over_add() {
9724        // 1 + 2 * 3 should parse as 1 + (2 * 3)
9725        let stmt = parse_one("SELECT 1 + 2 * 3");
9726        if let Statement::Select(s) = stmt {
9727            if let SelectCore::Select { columns, .. } = &s.body.select {
9728                match &columns[0] {
9729                    ResultColumn::Expr { expr, .. } => {
9730                        // Outer expression should be Add, right side should be Multiply.
9731                        assert!(
9732                            matches!(expr, Expr::BinaryOp { .. }),
9733                            "expected BinaryOp, got {expr:?}"
9734                        );
9735                    }
9736                    other => unreachable!("expected Expr column, got {other:?}"),
9737                }
9738            } else {
9739                unreachable!("expected Select core");
9740            }
9741        } else {
9742            unreachable!("expected Select");
9743        }
9744    }
9745
9746    // -----------------------------------------------------------------------
9747    // bd-2kvo Phase 3 acceptance: INSERT with ON CONFLICT and RETURNING
9748    // -----------------------------------------------------------------------
9749
9750    #[test]
9751    fn test_parser_insert_on_conflict() {
9752        let stmt =
9753            parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a");
9754        if let Statement::Insert(i) = stmt {
9755            assert!(!i.upsert.is_empty());
9756        } else {
9757            unreachable!("expected Insert");
9758        }
9759    }
9760
9761    #[test]
9762    fn test_parser_insert_returning() {
9763        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING *");
9764        if let Statement::Insert(i) = stmt {
9765            assert!(!i.returning.is_empty());
9766        } else {
9767            unreachable!("expected Insert");
9768        }
9769    }
9770
9771    #[test]
9772    fn test_parser_delete_returning() {
9773        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
9774        if let Statement::Delete(d) = stmt {
9775            assert!(!d.returning.is_empty());
9776        } else {
9777            unreachable!("expected Delete");
9778        }
9779    }
9780
9781    #[test]
9782    fn test_parser_update_returning() {
9783        let stmt = parse_one("UPDATE t SET a = 1 RETURNING a, b");
9784        if let Statement::Update(u) = stmt {
9785            assert_eq!(u.returning.len(), 2);
9786        } else {
9787            unreachable!("expected Update");
9788        }
9789    }
9790
9791    // -----------------------------------------------------------------------
9792    // bd-2kvo Phase 3 acceptance: compound SELECT operators
9793    // -----------------------------------------------------------------------
9794
9795    #[test]
9796    fn test_parser_union() {
9797        let stmt = parse_one("SELECT 1 UNION SELECT 2");
9798        if let Statement::Select(s) = stmt {
9799            assert_eq!(s.body.compounds.len(), 1);
9800            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
9801        } else {
9802            unreachable!("expected Select");
9803        }
9804    }
9805
9806    #[test]
9807    fn test_parser_intersect() {
9808        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
9809        if let Statement::Select(s) = stmt {
9810            assert_eq!(s.body.compounds.len(), 1);
9811            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
9812        } else {
9813            unreachable!("expected Select");
9814        }
9815    }
9816
9817    #[test]
9818    fn test_parser_except() {
9819        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
9820        if let Statement::Select(s) = stmt {
9821            assert_eq!(s.body.compounds.len(), 1);
9822            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
9823        } else {
9824            unreachable!("expected Select");
9825        }
9826    }
9827
9828    // -----------------------------------------------------------------------
9829    // bd-2kvo Phase 3 acceptance: subquery in FROM
9830    // -----------------------------------------------------------------------
9831
9832    #[test]
9833    fn test_parser_subquery_in_from() {
9834        let stmt = parse_one("SELECT * FROM (SELECT 1 AS x) AS sub");
9835        assert!(matches!(stmt, Statement::Select(_)));
9836    }
9837
9838    // -----------------------------------------------------------------------
9839    // bd-2kvo Phase 3 acceptance: CREATE TABLE with constraints
9840    // -----------------------------------------------------------------------
9841
9842    #[test]
9843    fn test_parser_create_table_all_constraints() {
9844        let stmt = parse_one(
9845            "CREATE TABLE t (\
9846             id INTEGER PRIMARY KEY AUTOINCREMENT,\
9847             name TEXT NOT NULL DEFAULT '',\
9848             email TEXT UNIQUE,\
9849             age INTEGER CHECK(age >= 0),\
9850             dept_id INTEGER REFERENCES dept(id) ON DELETE CASCADE,\
9851             CONSTRAINT pk PRIMARY KEY (id),\
9852             UNIQUE (email),\
9853             CHECK (age < 200),\
9854             FOREIGN KEY (dept_id) REFERENCES dept(id)\
9855             )",
9856        );
9857        if let Statement::CreateTable(ct) = stmt {
9858            if let CreateTableBody::Columns {
9859                columns,
9860                constraints,
9861            } = ct.body
9862            {
9863                assert_eq!(columns.len(), 5);
9864                assert!(!constraints.is_empty());
9865            } else {
9866                unreachable!("expected column defs");
9867            }
9868        } else {
9869            unreachable!("expected CreateTable");
9870        }
9871    }
9872
9873    // -----------------------------------------------------------------------
9874    // bd-2kvo Phase 3 acceptance: CREATE TRIGGER with all timing/events
9875    // -----------------------------------------------------------------------
9876
9877    #[test]
9878    fn test_parser_create_trigger_before_delete() {
9879        let stmt = parse_one("CREATE TRIGGER tr BEFORE DELETE ON t BEGIN SELECT 1; END");
9880        if let Statement::CreateTrigger(tr) = stmt {
9881            assert_eq!(tr.timing, TriggerTiming::Before);
9882            assert!(matches!(tr.event, TriggerEvent::Delete));
9883        } else {
9884            unreachable!("expected CreateTrigger");
9885        }
9886    }
9887
9888    #[test]
9889    fn test_parser_create_trigger_instead_of_update() {
9890        let stmt =
9891            parse_one("CREATE TRIGGER tr INSTEAD OF UPDATE OF a, b ON v BEGIN SELECT 1; END");
9892        if let Statement::CreateTrigger(tr) = stmt {
9893            assert_eq!(tr.timing, TriggerTiming::InsteadOf);
9894            if let TriggerEvent::Update(cols) = &tr.event {
9895                assert_eq!(cols.len(), 2);
9896            } else {
9897                unreachable!("expected UpdateOf event");
9898            }
9899        } else {
9900            unreachable!("expected CreateTrigger");
9901        }
9902    }
9903
9904    // -----------------------------------------------------------------------
9905    // bd-2kvo Phase 3 acceptance: CREATE VIEW with columns
9906    // -----------------------------------------------------------------------
9907
9908    #[test]
9909    fn test_parser_create_view_with_columns() {
9910        let stmt = parse_one("CREATE VIEW v (a, b) AS SELECT 1, 2");
9911        if let Statement::CreateView(cv) = stmt {
9912            assert_eq!(cv.columns, vec!["a".to_owned(), "b".to_owned()]);
9913        } else {
9914            unreachable!("expected CreateView");
9915        }
9916    }
9917
9918    // -----------------------------------------------------------------------
9919    // bd-2kvo Phase 3 acceptance: multi-way join
9920    // -----------------------------------------------------------------------
9921
9922    #[test]
9923    fn test_parser_multi_join() {
9924        let stmt = parse_one(
9925            "SELECT a.x, b.y, c.z FROM a \
9926             JOIN b ON a.id = b.a_id \
9927             LEFT JOIN c ON b.id = c.b_id \
9928             CROSS JOIN d",
9929        );
9930        if let Statement::Select(s) = stmt {
9931            if let SelectCore::Select { from, .. } = &s.body.select {
9932                let from = from.as_ref().expect("FROM clause");
9933                assert_eq!(from.joins.len(), 3);
9934                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
9935                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
9936                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
9937            } else {
9938                unreachable!("expected Select core");
9939            }
9940        } else {
9941            unreachable!("expected Select");
9942        }
9943    }
9944
9945    // -----------------------------------------------------------------------
9946    // bd-2kvo Phase 3 acceptance: GROUP BY / HAVING
9947    // -----------------------------------------------------------------------
9948
9949    #[test]
9950    fn test_parser_group_by_having() {
9951        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
9952        if let Statement::Select(s) = stmt {
9953            if let SelectCore::Select {
9954                group_by, having, ..
9955            } = &s.body.select
9956            {
9957                assert!(!group_by.is_empty());
9958                assert!(having.is_some());
9959            } else {
9960                unreachable!("expected Select core");
9961            }
9962        } else {
9963            unreachable!("expected Select");
9964        }
9965    }
9966
9967    // -----------------------------------------------------------------------
9968    // bd-2kvo Phase 3 acceptance: Error recovery with line:column spans
9969    // -----------------------------------------------------------------------
9970
9971    #[test]
9972    fn test_parser_error_recovery_with_span() {
9973        // Multi-line input with an error on line 2.
9974        let sql = "SELECT 1;\nXYZZY 42;\nSELECT 3";
9975        let mut p = Parser::from_sql(sql);
9976        let (stmts, errs) = p.parse_all();
9977        assert_eq!(stmts.len(), 2, "should recover two valid statements");
9978        assert!(!errs.is_empty(), "should report at least one error");
9979
9980        let err = &errs[0];
9981        // XYZZY starts at line 2, column 1.
9982        assert_eq!(err.line, 2, "error should be on line 2");
9983        assert_eq!(err.col, 1, "error should be at column 1");
9984        // Span should be non-zero and point within the source.
9985        assert!(
9986            err.span.start < err.span.end,
9987            "error span should be non-empty"
9988        );
9989        let source_len = u32::try_from(sql.len()).unwrap();
9990        assert!(
9991            err.span.end <= source_len,
9992            "error span.end should be within source"
9993        );
9994    }
9995
9996    #[test]
9997    fn test_parser_error_span_mid_line() {
9998        // Incomplete CREATE should produce an error.
9999        let bad = Parser::from_sql("CREATE").parse_statement();
10000        assert!(bad.is_err());
10001        let err = bad.unwrap_err();
10002        assert_eq!(err.line, 1);
10003    }
10004
10005    // -----------------------------------------------------------------------
10006    // bd-2kvo Phase 3 acceptance: Keyword lookup covers 150+ keywords
10007    // -----------------------------------------------------------------------
10008
10009    #[test]
10010    #[allow(clippy::too_many_lines)]
10011    fn test_parser_keyword_lookup_all_150() {
10012        use crate::token::TokenKind;
10013
10014        // Exhaustive list of all SQL keywords in lookup_keyword.
10015        let keywords = [
10016            "ABORT",
10017            "ACTION",
10018            "ADD",
10019            "AFTER",
10020            "ALL",
10021            "ALTER",
10022            "ALWAYS",
10023            "ANALYZE",
10024            "AND",
10025            "AS",
10026            "ASC",
10027            "ATTACH",
10028            "AUTOINCREMENT",
10029            "BEFORE",
10030            "BEGIN",
10031            "BETWEEN",
10032            "BY",
10033            "CASCADE",
10034            "CASE",
10035            "CAST",
10036            "CHECK",
10037            "COLLATE",
10038            "COLUMN",
10039            "COMMIT",
10040            "CONCURRENT",
10041            "CONFLICT",
10042            "CONSTRAINT",
10043            "CREATE",
10044            "CROSS",
10045            "CURRENT_DATE",
10046            "CURRENT_TIME",
10047            "CURRENT_TIMESTAMP",
10048            "DATABASE",
10049            "DEFAULT",
10050            "DEFERRABLE",
10051            "DEFERRED",
10052            "DELETE",
10053            "DESC",
10054            "DETACH",
10055            "DISTINCT",
10056            "DO",
10057            "DROP",
10058            "EACH",
10059            "ELSE",
10060            "END",
10061            "ESCAPE",
10062            "EXCEPT",
10063            "EXCLUDE",
10064            "EXCLUSIVE",
10065            "EXISTS",
10066            "EXPLAIN",
10067            "FAIL",
10068            "FILTER",
10069            "FIRST",
10070            "FOLLOWING",
10071            "FOR",
10072            "FOREIGN",
10073            "FROM",
10074            "FULL",
10075            "GENERATED",
10076            "GLOB",
10077            "GROUP",
10078            "GROUPS",
10079            "HAVING",
10080            "IF",
10081            "IGNORE",
10082            "IMMEDIATE",
10083            "IN",
10084            "INDEX",
10085            "INDEXED",
10086            "INITIALLY",
10087            "INNER",
10088            "INSERT",
10089            "INSTEAD",
10090            "INTERSECT",
10091            "INTO",
10092            "IS",
10093            "ISNULL",
10094            "JOIN",
10095            "KEY",
10096            "LAST",
10097            "LEFT",
10098            "LIKE",
10099            "LIMIT",
10100            "MATCH",
10101            "MATERIALIZED",
10102            "NATURAL",
10103            "NO",
10104            "NOT",
10105            "NOTHING",
10106            "NOTNULL",
10107            "NULL",
10108            "NULLS",
10109            "OF",
10110            "OFFSET",
10111            "ON",
10112            "OR",
10113            "ORDER",
10114            "OTHERS",
10115            "OUTER",
10116            "OVER",
10117            "PARTITION",
10118            "PLAN",
10119            "PRAGMA",
10120            "PRECEDING",
10121            "PRIMARY",
10122            "QUERY",
10123            "RAISE",
10124            "RANGE",
10125            "RECURSIVE",
10126            "REFERENCES",
10127            "REGEXP",
10128            "REINDEX",
10129            "RELEASE",
10130            "RENAME",
10131            "REPLACE",
10132            "RESTRICT",
10133            "RETURNING",
10134            "RIGHT",
10135            "ROLLBACK",
10136            "ROW",
10137            "ROWS",
10138            "SAVEPOINT",
10139            "SELECT",
10140            "SET",
10141            "STORED",
10142            "STRICT",
10143            "TABLE",
10144            "TEMP",
10145            "TEMPORARY",
10146            "THEN",
10147            "TIES",
10148            "TO",
10149            "TRANSACTION",
10150            "TRIGGER",
10151            "TRUE",
10152            "FALSE",
10153            "UNBOUNDED",
10154            "UNION",
10155            "UNIQUE",
10156            "UPDATE",
10157            "USING",
10158            "VACUUM",
10159            "VALUES",
10160            "VIEW",
10161            "VIRTUAL",
10162            "WHEN",
10163            "WHERE",
10164            "WINDOW",
10165            "WITH",
10166            "WITHOUT",
10167        ];
10168
10169        assert!(
10170            keywords.len() >= 150,
10171            "expected 150+ keywords, got {}",
10172            keywords.len()
10173        );
10174
10175        for kw in &keywords {
10176            assert!(
10177                TokenKind::lookup_keyword(kw).is_some(),
10178                "keyword {kw} not recognized (uppercase)"
10179            );
10180            // Case-insensitive: lowercase must also work.
10181            let lower = kw.to_ascii_lowercase();
10182            assert!(
10183                TokenKind::lookup_keyword(&lower).is_some(),
10184                "keyword {kw} not recognized (lowercase)"
10185            );
10186            // Mixed case.
10187            let mixed: String = kw
10188                .chars()
10189                .enumerate()
10190                .map(|(i, c)| {
10191                    if i % 2 == 0 {
10192                        c.to_ascii_lowercase()
10193                    } else {
10194                        c.to_ascii_uppercase()
10195                    }
10196                })
10197                .collect();
10198            assert!(
10199                TokenKind::lookup_keyword(&mixed).is_some(),
10200                "keyword {kw} not recognized (mixed case: {mixed})"
10201            );
10202        }
10203
10204        // Non-keyword should return None.
10205        assert!(TokenKind::lookup_keyword("FOOBAR").is_none());
10206        assert!(TokenKind::lookup_keyword("").is_none());
10207    }
10208
10209    // -----------------------------------------------------------------------
10210    // Round-trip: parse → Display → re-parse → compare ASTs
10211    // -----------------------------------------------------------------------
10212
10213    /// Parse SQL, convert back to string via Display, re-parse, convert back
10214    /// again, and assert the two rendered strings are identical.  We compare
10215    /// rendered strings (not ASTs) because Display may normalise constructs
10216    /// (e.g. `INSERT OR REPLACE` → `REPLACE`) which changes SQL length and
10217    /// therefore Span positions, while the logical content is identical.
10218    fn assert_roundtrip(sql: &str) {
10219        let ast1 = parse_one(sql);
10220        let rendered1 = ast1.to_string();
10221        let ast2 = parse_one(&rendered1);
10222        let rendered2 = ast2.to_string();
10223        assert_eq!(
10224            rendered1, rendered2,
10225            "round-trip failed for:\n  input: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
10226        );
10227    }
10228
10229    #[test]
10230    fn test_roundtrip_select_simple() {
10231        assert_roundtrip("SELECT 1");
10232        assert_roundtrip("SELECT 1, 2, 3");
10233        assert_roundtrip("SELECT *");
10234        assert_roundtrip("SELECT * FROM t");
10235        assert_roundtrip("SELECT a, b FROM t WHERE a > 10");
10236        assert_roundtrip("SELECT a FROM t ORDER BY a DESC");
10237        assert_roundtrip("SELECT a FROM t LIMIT 10 OFFSET 5");
10238    }
10239
10240    #[test]
10241    fn test_roundtrip_select_distinct() {
10242        assert_roundtrip("SELECT DISTINCT a, b FROM t");
10243    }
10244
10245    #[test]
10246    fn test_roundtrip_select_alias() {
10247        assert_roundtrip("SELECT a AS x, b AS y FROM t AS u");
10248    }
10249
10250    #[test]
10251    fn test_roundtrip_select_join_types() {
10252        assert_roundtrip("SELECT * FROM a INNER JOIN b ON a.id = b.id");
10253        assert_roundtrip("SELECT * FROM a LEFT JOIN b ON a.id = b.id");
10254        assert_roundtrip("SELECT * FROM a RIGHT JOIN b ON a.id = b.id");
10255        assert_roundtrip("SELECT * FROM a FULL JOIN b ON a.id = b.id");
10256        assert_roundtrip("SELECT * FROM a CROSS JOIN b");
10257        assert_roundtrip("SELECT * FROM a NATURAL INNER JOIN b");
10258        assert_roundtrip("SELECT * FROM a LEFT JOIN b USING (id)");
10259    }
10260
10261    #[test]
10262    fn test_roundtrip_select_subquery() {
10263        assert_roundtrip("SELECT * FROM (SELECT 1 AS x) AS sub");
10264    }
10265
10266    #[test]
10267    fn test_roundtrip_select_group_by_having() {
10268        assert_roundtrip("SELECT a, count(*) FROM t GROUP BY a HAVING count(*) > 1");
10269    }
10270
10271    #[test]
10272    fn test_roundtrip_select_window() {
10273        assert_roundtrip("SELECT sum(x) OVER (PARTITION BY g ORDER BY x) FROM t");
10274    }
10275
10276    #[test]
10277    fn test_roundtrip_select_cte() {
10278        assert_roundtrip("WITH cte AS (SELECT 1 AS n) SELECT * FROM cte");
10279        assert_roundtrip(
10280            "WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x + 1 FROM cnt WHERE x < 10) SELECT * FROM cnt",
10281        );
10282    }
10283
10284    #[test]
10285    fn test_roundtrip_select_compound() {
10286        assert_roundtrip("SELECT 1 UNION SELECT 2");
10287        assert_roundtrip("SELECT 1 UNION ALL SELECT 2");
10288        assert_roundtrip("SELECT 1 INTERSECT SELECT 2");
10289        assert_roundtrip("SELECT 1 EXCEPT SELECT 2");
10290    }
10291
10292    #[test]
10293    fn test_roundtrip_insert() {
10294        assert_roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
10295        assert_roundtrip("INSERT INTO t DEFAULT VALUES");
10296        assert_roundtrip("INSERT INTO t SELECT * FROM u");
10297        assert_roundtrip("INSERT OR REPLACE INTO t (a) VALUES (1)");
10298        assert_roundtrip("REPLACE INTO t (a) VALUES (1)");
10299    }
10300
10301    #[test]
10302    fn test_roundtrip_insert_returning() {
10303        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING *");
10304        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING a, b");
10305    }
10306
10307    #[test]
10308    fn test_roundtrip_insert_on_conflict() {
10309        assert_roundtrip("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
10310        assert_roundtrip(
10311            "INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a",
10312        );
10313    }
10314
10315    #[test]
10316    fn test_roundtrip_update() {
10317        assert_roundtrip("UPDATE t SET a = 1");
10318        assert_roundtrip("UPDATE t SET a = 1, b = 2 WHERE c > 3");
10319        assert_roundtrip("UPDATE t SET a = 1 RETURNING *");
10320    }
10321
10322    #[test]
10323    fn test_roundtrip_delete() {
10324        assert_roundtrip("DELETE FROM t");
10325        assert_roundtrip("DELETE FROM t WHERE a = 1");
10326        assert_roundtrip("DELETE FROM t RETURNING *");
10327    }
10328
10329    #[test]
10330    fn test_roundtrip_create_table() {
10331        assert_roundtrip("CREATE TABLE t (a INTEGER, b TEXT)");
10332        assert_roundtrip("CREATE TABLE IF NOT EXISTS t (a INTEGER PRIMARY KEY)");
10333        assert_roundtrip("CREATE TEMP TABLE t (a TEXT NOT NULL, b REAL DEFAULT 0.0)");
10334    }
10335
10336    #[test]
10337    fn test_roundtrip_create_index() {
10338        assert_roundtrip("CREATE INDEX idx ON t (a)");
10339        assert_roundtrip("CREATE UNIQUE INDEX IF NOT EXISTS idx ON t (a, b DESC)");
10340        assert_roundtrip("CREATE INDEX idx ON t (a) WHERE a > 0");
10341    }
10342
10343    #[test]
10344    fn test_roundtrip_drop() {
10345        assert_roundtrip("DROP TABLE t");
10346        assert_roundtrip("DROP TABLE IF EXISTS t");
10347        assert_roundtrip("DROP INDEX idx");
10348        assert_roundtrip("DROP VIEW v");
10349    }
10350
10351    #[test]
10352    fn test_roundtrip_alter_table() {
10353        assert_roundtrip("ALTER TABLE t RENAME TO u");
10354        assert_roundtrip("ALTER TABLE t ADD COLUMN c TEXT");
10355        assert_roundtrip("ALTER TABLE t DROP COLUMN c");
10356    }
10357
10358    #[test]
10359    fn test_roundtrip_transaction() {
10360        assert_roundtrip("BEGIN");
10361        assert_roundtrip("BEGIN IMMEDIATE");
10362        assert_roundtrip("BEGIN EXCLUSIVE");
10363        assert_roundtrip("COMMIT");
10364        assert_roundtrip("ROLLBACK");
10365        assert_roundtrip("SAVEPOINT sp1");
10366        assert_roundtrip("RELEASE sp1");
10367    }
10368
10369    #[test]
10370    fn test_roundtrip_pragma() {
10371        assert_roundtrip("PRAGMA journal_mode");
10372        assert_roundtrip("PRAGMA journal_mode = wal");
10373    }
10374
10375    #[test]
10376    fn test_roundtrip_explain() {
10377        assert_roundtrip("EXPLAIN SELECT 1");
10378        assert_roundtrip("EXPLAIN QUERY PLAN SELECT * FROM t");
10379    }
10380
10381    #[test]
10382    fn test_roundtrip_expressions() {
10383        assert_roundtrip("SELECT 1 + 2 * 3");
10384        assert_roundtrip("SELECT NOT a");
10385        assert_roundtrip("SELECT -x");
10386        assert_roundtrip("SELECT ~x");
10387        assert_roundtrip("SELECT a BETWEEN 1 AND 10");
10388        assert_roundtrip("SELECT a NOT BETWEEN 1 AND 10");
10389        assert_roundtrip("SELECT a IN (1, 2, 3)");
10390        assert_roundtrip("SELECT a NOT IN (1, 2, 3)");
10391        assert_roundtrip("SELECT a LIKE '%foo%'");
10392        assert_roundtrip("SELECT a GLOB '*foo*'");
10393        assert_roundtrip("SELECT CASE WHEN a = 1 THEN 'one' ELSE 'other' END");
10394        assert_roundtrip("SELECT CASE x WHEN 1 THEN 'a' WHEN 2 THEN 'b' END");
10395        assert_roundtrip("SELECT CAST(a AS TEXT)");
10396        assert_roundtrip("SELECT EXISTS (SELECT 1)");
10397        assert_roundtrip("SELECT (SELECT 1)");
10398        assert_roundtrip("SELECT a COLLATE NOCASE");
10399    }
10400
10401    #[test]
10402    fn test_roundtrip_literals() {
10403        assert_roundtrip("SELECT NULL");
10404        assert_roundtrip("SELECT TRUE");
10405        assert_roundtrip("SELECT FALSE");
10406        assert_roundtrip("SELECT 42");
10407        assert_roundtrip("SELECT 3.14");
10408        assert_roundtrip("SELECT 'hello'");
10409        assert_roundtrip("SELECT X'DEADBEEF'");
10410        assert_roundtrip("SELECT CURRENT_TIME");
10411        assert_roundtrip("SELECT CURRENT_DATE");
10412        assert_roundtrip("SELECT CURRENT_TIMESTAMP");
10413    }
10414
10415    #[test]
10416    fn test_roundtrip_placeholders() {
10417        assert_roundtrip("SELECT ?");
10418        assert_roundtrip("SELECT ?1");
10419        assert_roundtrip("SELECT :name");
10420        assert_roundtrip("SELECT @name");
10421        assert_roundtrip("SELECT $name");
10422    }
10423
10424    #[test]
10425    fn test_roundtrip_json_arrows() {
10426        assert_roundtrip("SELECT a -> 'key'");
10427        assert_roundtrip("SELECT a ->> 'key'");
10428    }
10429
10430    #[test]
10431    fn test_roundtrip_function_calls() {
10432        assert_roundtrip("SELECT count(*)");
10433        assert_roundtrip("SELECT count(DISTINCT a)");
10434        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0)");
10435    }
10436
10437    #[test]
10438    fn test_roundtrip_isnull_notnull() {
10439        assert_roundtrip("SELECT a ISNULL");
10440        assert_roundtrip("SELECT a IS NOT NULL");
10441    }
10442
10443    #[test]
10444    fn test_roundtrip_create_view() {
10445        assert_roundtrip("CREATE VIEW v AS SELECT * FROM t");
10446        assert_roundtrip("CREATE VIEW IF NOT EXISTS v (a, b) AS SELECT 1, 2");
10447    }
10448
10449    #[test]
10450    fn test_roundtrip_create_trigger() {
10451        assert_roundtrip(
10452            "CREATE TRIGGER tr BEFORE DELETE ON t FOR EACH ROW BEGIN DELETE FROM log WHERE id = OLD.id; END",
10453        );
10454    }
10455
10456    #[test]
10457    fn test_roundtrip_attach_detach() {
10458        assert_roundtrip("ATTACH 'file.db' AS db2");
10459        assert_roundtrip("DETACH db2");
10460    }
10461
10462    #[test]
10463    fn test_roundtrip_vacuum() {
10464        assert_roundtrip("VACUUM");
10465    }
10466
10467    #[test]
10468    fn test_roundtrip_analyze_reindex() {
10469        assert_roundtrip("ANALYZE");
10470        assert_roundtrip("ANALYZE t");
10471        assert_roundtrip("REINDEX");
10472        assert_roundtrip("REINDEX t");
10473    }
10474
10475    #[test]
10476    fn test_roundtrip_cte_materialized() {
10477        assert_roundtrip("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
10478        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
10479    }
10480
10481    // -----------------------------------------------------------------------
10482    // Proptest: round-trip property test (bd-2kvo acceptance criterion #12)
10483    // -----------------------------------------------------------------------
10484
10485    mod proptest_roundtrip {
10486        use super::*;
10487        use proptest::prelude::*;
10488
10489        /// Returns `true` if the string is a SQL keyword.
10490        fn is_keyword(s: &str) -> bool {
10491            TokenKind::lookup_keyword(s).is_some()
10492        }
10493
10494        /// Generate a random identifier (simple alphanumeric, not a SQL keyword).
10495        fn arb_ident() -> BoxedStrategy<String> {
10496            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
10497                .expect("valid regex")
10498                .prop_filter("must not be keyword", |s| !is_keyword(s))
10499                .boxed()
10500        }
10501
10502        /// Generate a random literal value.
10503        fn arb_literal() -> BoxedStrategy<String> {
10504            prop_oneof![
10505                any::<i32>().prop_map(|n| n.to_string()),
10506                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
10507                arb_ident().prop_map(|s| format!("'{s}'")),
10508                Just("NULL".to_string()),
10509                Just("TRUE".to_string()),
10510                Just("FALSE".to_string()),
10511            ]
10512            .boxed()
10513        }
10514
10515        /// Generate a random expression of bounded depth.
10516        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
10517            if depth == 0 {
10518                prop_oneof![
10519                    arb_literal(),
10520                    arb_ident(),
10521                    (arb_ident(), arb_ident()).prop_map(|(t, c)| format!("{t}.{c}")),
10522                ]
10523                .boxed()
10524            } else {
10525                let leaf = arb_expr(0);
10526                prop_oneof![
10527                    4 => leaf,
10528                    // Binary ops (always parenthesized by display)
10529                    2 => (arb_expr(depth - 1), prop_oneof![
10530                        Just("+"), Just("-"), Just("*"), Just("/"),
10531                        Just("="), Just("!="), Just("<"), Just("<="),
10532                        Just(">"), Just(">="), Just("AND"), Just("OR"),
10533                        Just("||"),
10534                    ], arb_expr(depth - 1))
10535                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
10536                    // Unary ops
10537                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
10538                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
10539                    // IS NULL / IS NOT NULL
10540                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NULL")),
10541                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NOT NULL")),
10542                    // Single-token postfix null tests
10543                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} ISNULL")),
10544                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} NOTNULL")),
10545                    // COLLATE (postfix)
10546                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} COLLATE nocase")),
10547                    // UNPARENTHESIZED binary ops: exercise precedence and
10548                    // associativity through the display round-trip (issue
10549                    // #122: `a IS NULL = b IS NULL` must not regroup after
10550                    // parse → display → re-parse).
10551                    2 => (arb_expr(depth - 1), prop_oneof![
10552                        Just("+"), Just("*"), Just("="), Just("<"),
10553                        Just("AND"), Just("OR"), Just("||"), Just("IS"),
10554                        Just("IS NOT"),
10555                    ], arb_expr(depth - 1))
10556                        .prop_map(|(l, op, r)| format!("{l} {op} {r}")),
10557                    // BETWEEN
10558                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
10559                        .prop_map(|(e, lo, hi)| format!("{e} BETWEEN {lo} AND {hi}")),
10560                    // IN list
10561                    1 => (arb_expr(depth - 1), proptest::collection::vec(arb_expr(0), 1..4))
10562                        .prop_map(|(e, items)| format!("{e} IN ({})", items.join(", "))),
10563                    // LIKE
10564                    1 => (arb_expr(depth - 1), arb_ident())
10565                        .prop_map(|(e, p)| format!("{e} LIKE '{p}'")),
10566                    // CAST
10567                    1 => arb_expr(depth - 1).prop_map(|e| format!("CAST({e} AS TEXT)")),
10568                    // CASE
10569                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
10570                        .prop_map(|(c, t, el)| format!("CASE WHEN {c} THEN {t} ELSE {el} END")),
10571                    // Function call
10572                    1 => (arb_ident(), proptest::collection::vec(arb_expr(0), 0..3))
10573                        .prop_map(|(name, args)| format!("{name}({})", args.join(", "))),
10574                    // Subquery
10575                    1 => arb_expr(0).prop_map(|e| format!("(SELECT {e})")),
10576                ]
10577                .boxed()
10578            }
10579        }
10580
10581        /// Generate a random SELECT statement.
10582        fn arb_select() -> BoxedStrategy<String> {
10583            use std::fmt::Write as _;
10584
10585            let cols =
10586                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
10587            let table = arb_ident();
10588            let where_clause = prop::option::of(arb_expr(1));
10589            let order_by = prop::option::of(arb_ident());
10590            let limit = prop::option::of(1u32..100);
10591
10592            (cols, table, where_clause, order_by, limit)
10593                .prop_map(|(cols, tbl, wh, ord, lim)| {
10594                    let mut sql = format!("SELECT {cols} FROM {tbl}");
10595                    if let Some(w) = wh {
10596                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
10597                    }
10598                    if let Some(o) = ord {
10599                        write!(sql, " ORDER BY {o}").expect("writing to String should not fail");
10600                    }
10601                    if let Some(l) = lim {
10602                        write!(sql, " LIMIT {l}").expect("writing to String should not fail");
10603                    }
10604                    sql
10605                })
10606                .boxed()
10607        }
10608
10609        /// Generate a random INSERT statement.
10610        fn arb_insert() -> BoxedStrategy<String> {
10611            let ncols = 1usize..4;
10612            ncols
10613                .prop_flat_map(|n| {
10614                    let tbl = arb_ident();
10615                    let cols = proptest::collection::vec(arb_ident(), n..=n);
10616                    let vals = proptest::collection::vec(arb_literal(), n..=n);
10617                    (tbl, cols, vals).prop_map(|(t, cs, vs): (String, Vec<String>, Vec<String>)| {
10618                        format!(
10619                            "INSERT INTO {t} ({}) VALUES ({})",
10620                            cs.join(", "),
10621                            vs.join(", ")
10622                        )
10623                    })
10624                })
10625                .boxed()
10626        }
10627
10628        /// Generate a random statement.
10629        fn arb_statement() -> BoxedStrategy<String> {
10630            prop_oneof![
10631                6 => arb_select(),
10632                3 => arb_insert(),
10633                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
10634                1 => (arb_ident(), arb_expr(1))
10635                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
10636                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
10637                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
10638            ]
10639            .boxed()
10640        }
10641
10642        /// Try to parse SQL into a single statement; returns `None` if unparseable.
10643        fn try_parse_one(sql: &str) -> Option<Statement> {
10644            let mut p = Parser::from_sql(sql);
10645            let (stmts, errs) = p.parse_all();
10646            if errs.is_empty() && stmts.len() == 1 {
10647                Some(stmts.into_iter().next().unwrap())
10648            } else {
10649                None
10650            }
10651        }
10652
10653        proptest::proptest! {
10654            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
10655
10656            #[test]
10657            fn test_parser_roundtrip_proptest(sql in arb_statement()) {
10658                // Phase 1: parse the generated SQL.
10659                let Some(ast1) = try_parse_one(&sql) else {
10660                    return Ok(()); // skip unparseable inputs
10661                };
10662
10663                // Phase 2: display the AST back to SQL text.
10664                let rendered1 = ast1.to_string();
10665
10666                // Phase 3: re-parse the rendered SQL.
10667                let Some(ast2) = try_parse_one(&rendered1) else {
10668                    let msg = format!("re-parse failed for rendered SQL: {rendered1:?}");
10669                    prop_assert!(false, "{}", msg);
10670                    unreachable!()
10671                };
10672
10673                // Phase 4: display again and compare (idempotency check).
10674                let rendered2 = ast2.to_string();
10675                let msg = format!(
10676                    "round-trip not idempotent:\n  original: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
10677                );
10678                prop_assert_eq!(rendered1, rendered2, "{}", msg);
10679            }
10680        }
10681    }
10682
10683    // -----------------------------------------------------------------------
10684    // Proptest: additional property tests (bd-1lsfu.4)
10685    // -----------------------------------------------------------------------
10686
10687    mod proptest_properties {
10688        use super::*;
10689        use proptest::prelude::*;
10690
10691        /// Reuse the statement generator from the roundtrip module.
10692        fn arb_ident() -> BoxedStrategy<String> {
10693            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
10694                .expect("valid regex")
10695                .prop_filter("must not be keyword", |s| {
10696                    TokenKind::lookup_keyword(s).is_none()
10697                })
10698                .boxed()
10699        }
10700
10701        fn arb_literal() -> BoxedStrategy<String> {
10702            prop_oneof![
10703                any::<i32>().prop_map(|n| n.to_string()),
10704                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
10705                arb_ident().prop_map(|s| format!("'{s}'")),
10706                Just("NULL".to_string()),
10707                Just("TRUE".to_string()),
10708                Just("FALSE".to_string()),
10709            ]
10710            .boxed()
10711        }
10712
10713        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
10714            if depth == 0 {
10715                prop_oneof![arb_literal(), arb_ident(),].boxed()
10716            } else {
10717                let leaf = arb_expr(0);
10718                prop_oneof![
10719                    4 => leaf,
10720                    2 => (arb_expr(depth - 1), prop_oneof![
10721                        Just("+"), Just("-"), Just("*"), Just("/"),
10722                        Just("="), Just("!="), Just("<"), Just("<="),
10723                        Just(">"), Just(">="), Just("AND"), Just("OR"),
10724                    ], arb_expr(depth - 1))
10725                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
10726                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
10727                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
10728                ]
10729                .boxed()
10730            }
10731        }
10732
10733        fn arb_select() -> BoxedStrategy<String> {
10734            use std::fmt::Write as _;
10735            let cols =
10736                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
10737            let table = arb_ident();
10738            let where_clause = prop::option::of(arb_expr(1));
10739            (cols, table, where_clause)
10740                .prop_map(|(cols, tbl, wh)| {
10741                    let mut sql = format!("SELECT {cols} FROM {tbl}");
10742                    if let Some(w) = wh {
10743                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
10744                    }
10745                    sql
10746                })
10747                .boxed()
10748        }
10749
10750        fn arb_statement() -> BoxedStrategy<String> {
10751            prop_oneof![
10752                6 => arb_select(),
10753                3 => {
10754                    let ncols = 1usize..4;
10755                    ncols
10756                        .prop_flat_map(|n| {
10757                            let tbl = arb_ident();
10758                            let cols = proptest::collection::vec(arb_ident(), n..=n);
10759                            let vals = proptest::collection::vec(arb_literal(), n..=n);
10760                            (tbl, cols, vals).prop_map(
10761                                |(t, cs, vs): (String, Vec<String>, Vec<String>)| {
10762                                    format!(
10763                                        "INSERT INTO {t} ({}) VALUES ({})",
10764                                        cs.join(", "),
10765                                        vs.join(", ")
10766                                    )
10767                                },
10768                            )
10769                        })
10770                        .boxed()
10771                },
10772                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
10773                1 => (arb_ident(), arb_expr(1))
10774                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
10775                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
10776                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
10777            ]
10778            .boxed()
10779        }
10780
10781        // Property 2: Determinism — same input always produces the same AST.
10782        proptest::proptest! {
10783            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(500))]
10784
10785            #[test]
10786            fn test_parser_determinism(sql in arb_statement()) {
10787                let mut p1 = Parser::from_sql(&sql);
10788                let (stmts1, errs1) = p1.parse_all();
10789
10790                let mut p2 = Parser::from_sql(&sql);
10791                let (stmts2, errs2) = p2.parse_all();
10792
10793                // Both parses must produce the same number of statements and errors.
10794                let msg_stmt = format!("different statement counts for: {sql}");
10795                prop_assert_eq!(stmts1.len(), stmts2.len(), "{}", msg_stmt);
10796                let msg_err = format!("different error counts for: {sql}");
10797                prop_assert_eq!(errs1.len(), errs2.len(), "{}", msg_err);
10798
10799                // If successful, the rendered SQL must be identical.
10800                if errs1.is_empty() && !stmts1.is_empty() {
10801                    for (s1, s2) in stmts1.iter().zip(stmts2.iter()) {
10802                        let r1 = s1.to_string();
10803                        let r2 = s2.to_string();
10804                        let msg_det = format!("non-deterministic parse output for: {sql}");
10805                        prop_assert_eq!(r1, r2, "{}", msg_det);
10806                    }
10807                }
10808            }
10809        }
10810
10811        // Property 3: Fuzz safety — random byte strings never panic the parser.
10812        proptest::proptest! {
10813                #![proptest_config(proptest::prelude::ProptestConfig::with_cases(2000))]
10814
10815                #[test]
10816        fn test_parser_fuzz_no_panic(input in prop::collection::vec(any::<u8>(), 0..256)) {
10817            let sql = String::from_utf8_lossy(&input);
10818            // Must not panic — errors are fine, panics are not.
10819            let mut p = Parser::from_sql(&sql);
10820            let _ = p.parse_all();
10821                }
10822            }
10823
10824        // Property 3b: Fuzz safety with near-valid SQL (more likely to trigger edge cases).
10825        proptest::proptest! {
10826            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
10827
10828            #[test]
10829            fn test_parser_fuzz_near_valid(
10830                prefix in prop_oneof![
10831                    Just("SELECT "),
10832                    Just("INSERT INTO "),
10833                    Just("DELETE FROM "),
10834                    Just("UPDATE "),
10835                    Just("CREATE TABLE "),
10836                    Just("DROP TABLE "),
10837                    Just("BEGIN "),
10838                    Just("PRAGMA "),
10839                ],
10840                suffix in prop::string::string_regex("[a-zA-Z0-9_ ,.*=<>!()'\";+\\-/]{0,100}")
10841                    .expect("valid regex")
10842            ) {
10843                let sql = format!("{prefix}{suffix}");
10844                let mut p = Parser::from_sql(&sql);
10845                let _ = p.parse_all();
10846            }
10847        }
10848
10849        // Property 4: Unicode identifiers parse correctly.
10850        proptest::proptest! {
10851            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
10852
10853            #[test]
10854            fn test_parser_unicode_identifiers(
10855                name in prop::string::string_regex("[\\p{L}][\\p{L}\\p{N}_]{0,10}")
10856                    .expect("valid regex")
10857                    .prop_filter("must not be keyword", |s| {
10858                        TokenKind::lookup_keyword(s).is_none()
10859                    })
10860            ) {
10861                // Double-quoted identifiers with Unicode should parse.
10862                let sql = format!("SELECT \"{name}\" FROM \"{name}\"");
10863                let mut p = Parser::from_sql(&sql);
10864                let (stmts, errs) = p.parse_all();
10865                prop_assert!(
10866                    errs.is_empty(),
10867                    "Unicode identifier should parse: {sql}, errors: {errs:?}"
10868                );
10869                prop_assert_eq!(stmts.len(), 1);
10870            }
10871        }
10872
10873        // Property 5: Rejection — various forms of invalid SQL are rejected.
10874        proptest::proptest! {
10875            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(300))]
10876
10877            #[test]
10878            fn test_parser_rejects_incomplete_statements(
10879                kind in prop_oneof![
10880                    Just("SELECT"),
10881                    Just("SELECT FROM"),
10882                    Just("INSERT INTO"),
10883                    Just("DELETE"),
10884                    Just("UPDATE SET"),
10885                    Just("CREATE"),
10886                    Just("CREATE TABLE"),
10887                    Just("DROP"),
10888                ],
10889                trailing in prop::option::of(
10890                    prop::string::string_regex("[;, ]{0,3}").expect("valid regex")
10891                )
10892            ) {
10893                let sql = match trailing {
10894                    Some(t) => format!("{kind}{t}"),
10895                    None => kind.to_string(),
10896                };
10897                let mut p = Parser::from_sql(&sql);
10898                let (stmts, errs) = p.parse_all();
10899                // At least one of: parse errors, or no valid statements produced.
10900                // The parser should not silently produce a valid-looking AST from
10901                // these fundamentally incomplete inputs.
10902                prop_assert!(
10903                    !errs.is_empty() || stmts.is_empty(),
10904                    "Expected rejection of incomplete SQL: {sql}, got {stmts:?}"
10905                );
10906            }
10907        }
10908
10909        // Property 6: Statement count stability — concatenated statements produce
10910        // the right number of parsed statements.
10911        proptest::proptest! {
10912            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
10913
10914            #[test]
10915            fn test_parser_multi_statement_count(
10916                stmts in proptest::collection::vec(arb_statement(), 1..4)
10917            ) {
10918                let sql = stmts.join("; ");
10919                let mut p = Parser::from_sql(&sql);
10920                let (parsed, errors) = p.parse_all();
10921                // If no errors, we should get at least as many statements as we joined.
10922                if errors.is_empty() {
10923                    prop_assert!(
10924                        parsed.len() >= stmts.len(),
10925                        "Expected at least {} statements from: {sql}, got {}",
10926                        stmts.len(),
10927                        parsed.len()
10928                    );
10929                }
10930            }
10931        }
10932    }
10933
10934    #[test]
10935    fn test_parse_statements_with_scratch_reuses_token_and_error_capacity() {
10936        let mut scratch = StatementParseScratch::default();
10937        let err = parse_statements_with_scratch("SELECT FROM", &mut scratch)
10938            .expect_err("malformed SQL should surface a parse error");
10939        assert!(
10940            err.message.contains("expected"),
10941            "malformed parse should preserve its diagnostic detail",
10942        );
10943        let warmed_token_capacity = scratch.token_capacity();
10944        let warmed_error_capacity = scratch.error_capacity();
10945        assert!(
10946            warmed_token_capacity > 0,
10947            "parse scratch should warm token storage"
10948        );
10949        assert!(
10950            warmed_error_capacity > 0,
10951            "parse scratch should warm error storage"
10952        );
10953
10954        let statements = parse_statements_with_scratch("SELECT 1;", &mut scratch)
10955            .expect("follow-up parse should succeed");
10956        assert_eq!(statements.len(), 1);
10957        assert_eq!(
10958            scratch.token_capacity(),
10959            warmed_token_capacity,
10960            "successful parse should reuse token scratch capacity",
10961        );
10962        assert_eq!(
10963            scratch.error_capacity(),
10964            warmed_error_capacity,
10965            "successful parse should preserve error scratch capacity for the next recovery path",
10966        );
10967    }
10968
10969    #[test]
10970    fn test_parse_statements_with_scratch_enforces_top_level_separators() {
10971        let mut scratch = StatementParseScratch::default();
10972        let sql = "SELECT 1 SELECT 2";
10973        let error = parse_statements_with_scratch(sql, &mut scratch)
10974            .expect_err("scratch parser must reject adjacent statements");
10975        assert!(
10976            error.message.contains("expected ';' separator"),
10977            "unexpected diagnostic: {error:?}"
10978        );
10979        assert_eq!(
10980            &sql[error.span.start as usize..error.span.end as usize],
10981            "SELECT"
10982        );
10983
10984        let statements = parse_statements_with_scratch("SELECT 1; SELECT 2;", &mut scratch)
10985            .expect("semicolon-separated statements must remain valid");
10986        assert_eq!(statements.len(), 2);
10987
10988        let trigger_script = "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
10989                              INSERT INTO t VALUES (1); \
10990                              INSERT INTO t VALUES (2); \
10991                              END; SELECT 3;";
10992        let statements = parse_statements_with_scratch(trigger_script, &mut scratch)
10993            .expect("trigger-body terminators must not become top-level separator errors");
10994        assert_eq!(statements.len(), 2);
10995        assert!(matches!(statements[0], Statement::CreateTrigger(_)));
10996        assert!(matches!(statements[1], Statement::Select(_)));
10997    }
10998
10999    #[test]
11000    fn test_parse_statements_with_scratch_reuses_identifier_interns_across_parses() {
11001        let mut scratch = StatementParseScratch::default();
11002        let mut sql = String::from("SELECT ");
11003        for i in 0..32 {
11004            if i > 0 {
11005                sql.push_str(", ");
11006            }
11007            sql.push_str(&format!("unique_identifier_{i} AS unique_alias_{i}"));
11008        }
11009        sql.push(';');
11010
11011        let statements = parse_statements_with_scratch(&sql, &mut scratch)
11012            .expect("identifier-heavy statement should parse");
11013        assert_eq!(statements.len(), 1);
11014        let interner_len = scratch.identifier_interner_len();
11015        assert!(
11016            interner_len > 0,
11017            "scratch should retain identifier interns for the next parse",
11018        );
11019
11020        let statements = parse_statements_with_scratch(&sql, &mut scratch)
11021            .expect("repeat parse should also succeed");
11022        assert_eq!(statements.len(), 1);
11023        assert_eq!(
11024            scratch.identifier_interner_len(),
11025            interner_len,
11026            "repeated parse should reuse the retained interner set instead of growing it",
11027        );
11028
11029        scratch.reset();
11030        assert!(
11031            scratch.identifier_interner_is_empty(),
11032            "explicit scratch reset should also keep the interner logically empty",
11033        );
11034    }
11035
11036    #[test]
11037    fn test_parse_statements_with_scratch_drops_oversized_identifier_interner() {
11038        let mut scratch = StatementParseScratch::default();
11039        let mut sql = String::from("SELECT ");
11040        for i in 0..300 {
11041            if i > 0 {
11042                sql.push_str(", ");
11043            }
11044            sql.push_str(&format!(
11045                "very_long_unique_identifier_{i:03} AS alias_{i:03}"
11046            ));
11047        }
11048        sql.push(';');
11049
11050        let statements = parse_statements_with_scratch(&sql, &mut scratch)
11051            .expect("oversized identifier-heavy statement should parse");
11052        assert_eq!(statements.len(), 1);
11053        assert!(
11054            scratch.identifier_interner_is_empty(),
11055            "oversized identifier interners should be dropped instead of retained indefinitely",
11056        );
11057    }
11058
11059    // ── bd-1702 repro tests ─────────────────────────────────────────────
11060    // Reserved-word column names in CREATE TABLE (quoted and unquoted).
11061
11062    #[test]
11063    fn create_table_quoted_reserved_word_key() {
11064        // Double-quoted "key" should parse as identifier, not KwKey.
11065        parse_ok(r#"CREATE TABLE "meta" ("key" TEXT, "val" TEXT);"#);
11066    }
11067
11068    #[test]
11069    fn create_table_unquoted_key_column() {
11070        // KEY is a non-reserved keyword — should work unquoted.
11071        parse_ok("CREATE TABLE meta (key TEXT, val TEXT);");
11072    }
11073
11074    #[test]
11075    fn create_table_quoted_order_column() {
11076        // ORDER is reserved — must work when double-quoted.
11077        parse_ok(r#"CREATE TABLE t ("order" INTEGER);"#);
11078    }
11079
11080    #[test]
11081    fn create_table_quoted_select_column() {
11082        // SELECT is reserved — must work when double-quoted.
11083        parse_ok(r#"CREATE TABLE t ("select" TEXT);"#);
11084    }
11085
11086    #[test]
11087    fn select_with_reserved_word_column_key() {
11088        // SELECT using "key" as column name — unquoted.
11089        parse_ok("SELECT key FROM meta;");
11090    }
11091
11092    #[test]
11093    fn select_with_reserved_word_column_value() {
11094        // SELECT using "value" — check if it's a keyword.
11095        parse_ok("SELECT value FROM meta;");
11096    }
11097
11098    #[test]
11099    fn select_with_reserved_word_column_order() {
11100        // ORDER is reserved — quoted should work.
11101        parse_ok(r#"SELECT "order" FROM t;"#);
11102    }
11103
11104    #[test]
11105    fn where_clause_with_reserved_word_column() {
11106        // WHERE referencing a reserved-word column.
11107        parse_ok("UPDATE meta SET val = '2.0' WHERE key = 'version';");
11108    }
11109
11110    #[test]
11111    fn update_set_reserved_word_column() {
11112        // SET reserved-word column.
11113        parse_ok(r#"UPDATE meta SET "key" = 'newkey' WHERE "key" = 'oldkey';"#);
11114    }
11115
11116    #[test]
11117    fn delete_where_reserved_word_column() {
11118        parse_ok("DELETE FROM meta WHERE key = 'version';");
11119    }
11120
11121    #[test]
11122    fn persistence_dump_with_reserved_word_columns() {
11123        // Simulates the exact SQL that build_create_table_sql generates
11124        // for a table that was originally created with reserved-word columns.
11125        let sql = concat!(
11126            r#"CREATE TABLE "meta" ("key" TEXT, "value" TEXT);"#,
11127            "\n",
11128            r#"INSERT INTO "meta" VALUES ('version', '1.0');"#,
11129            "\n",
11130            r#"INSERT INTO "meta" VALUES ('author', 'test');"#,
11131        );
11132        let mut p = Parser::from_sql(sql);
11133        let (stmts, errs) = p.parse_all();
11134        assert!(
11135            errs.is_empty(),
11136            "persistence dump with reserved-word columns should parse cleanly: {errs:?}"
11137        );
11138        assert_eq!(stmts.len(), 3);
11139    }
11140
11141    #[test]
11142    fn create_table_with_single_quoted_name_parses_cleanly() {
11143        let sql = "CREATE TABLE 'fts_messages_data'(id INTEGER PRIMARY KEY, block BLOB);";
11144        let mut p = Parser::from_sql(sql);
11145        let (stmts, errs) = p.parse_all();
11146        assert!(
11147            errs.is_empty(),
11148            "single-quoted sqlite_master shadow-table SQL should parse cleanly: {errs:?}"
11149        );
11150        assert_eq!(stmts.len(), 1);
11151        match &stmts[0] {
11152            Statement::CreateTable(stmt) => {
11153                assert_eq!(stmt.name.name, "fts_messages_data");
11154            }
11155            other => panic!("expected CreateTable, got {other:?}"),
11156        }
11157    }
11158
11159    #[test]
11160    fn select_qualified_column_with_alias() {
11161        // Bug: "a.name as from_name" was being parsed with alias=None and
11162        // col_ref.column="name as" instead of alias=Some("from_name").
11163        let stmt = parse_one("SELECT a.name AS from_name FROM users a");
11164        if let Statement::Select(s) = stmt {
11165            if let SelectCore::Select { columns, .. } = &s.body.select {
11166                assert_eq!(columns.len(), 1);
11167                match &columns[0] {
11168                    ResultColumn::Expr { expr, alias } => {
11169                        // Alias should be captured as "from_name".
11170                        assert_eq!(
11171                            alias.as_deref(),
11172                            Some("from_name"),
11173                            "alias should be 'from_name', got {alias:?}"
11174                        );
11175                        // Expression should be a qualified column ref: a.name
11176                        if let Expr::Column(col_ref, _) = expr {
11177                            assert_eq!(col_ref.table.as_deref(), Some("a"));
11178                            assert_eq!(col_ref.column.as_ref(), "name");
11179                        } else {
11180                            panic!("expected Column expression, got {expr:?}");
11181                        }
11182                    }
11183                    other => panic!("expected Expr variant, got {other:?}"),
11184                }
11185            } else {
11186                panic!("expected Select core");
11187            }
11188        } else {
11189            panic!("expected Select statement");
11190        }
11191    }
11192
11193    #[test]
11194    fn select_qualified_column_with_implicit_alias() {
11195        // Test implicit alias syntax (without AS keyword).
11196        let stmt = parse_one("SELECT a.name from_name FROM users a");
11197        if let Statement::Select(s) = stmt {
11198            if let SelectCore::Select { columns, .. } = &s.body.select {
11199                assert_eq!(columns.len(), 1);
11200                match &columns[0] {
11201                    ResultColumn::Expr { expr, alias } => {
11202                        // Alias should be captured as "from_name" even without AS.
11203                        assert_eq!(
11204                            alias.as_deref(),
11205                            Some("from_name"),
11206                            "implicit alias should be 'from_name', got {alias:?}"
11207                        );
11208                        // Expression should be a qualified column ref: a.name
11209                        if let Expr::Column(col_ref, _) = expr {
11210                            assert_eq!(col_ref.table.as_deref(), Some("a"));
11211                            assert_eq!(col_ref.column.as_ref(), "name");
11212                        } else {
11213                            panic!("expected Column expression, got {expr:?}");
11214                        }
11215                    }
11216                    other => panic!("expected Expr variant, got {other:?}"),
11217                }
11218            } else {
11219                panic!("expected Select core");
11220            }
11221        } else {
11222            panic!("expected Select statement");
11223        }
11224    }
11225
11226    #[test]
11227    fn select_implicit_alias_non_reserved_keyword() {
11228        // 'action' is a non-reserved keyword (TokenKind::KwAction).
11229        // It should be accepted as an implicit alias: SELECT 1 action
11230        let stmt = parse_one("SELECT 1 action");
11231        if let Statement::Select(s) = stmt {
11232            if let SelectCore::Select { columns, .. } = &s.body.select {
11233                if let ResultColumn::Expr { alias, .. } = &columns[0] {
11234                    assert_eq!(
11235                        alias.as_deref(),
11236                        Some("action"),
11237                        "implicit alias 'action' (keyword) failed to parse"
11238                    );
11239                } else {
11240                    unreachable!("expected Expr result column");
11241                }
11242            } else {
11243                unreachable!("expected Select core");
11244            }
11245        } else {
11246            unreachable!("expected Select");
11247        }
11248    }
11249}