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    /// Parse the next top-level statement and return its ending byte offset in
478    /// the original token source, including its terminating semicolon if any.
479    /// Empty statements and comments are skipped; exhausted input returns `None`.
480    ///
481    /// Reusing this cursor avoids tokenizing a batch's remaining suffix for each
482    /// statement. Callers must stop on an error; this method does not recover.
483    pub fn parse_next_statement_with_tail(
484        &mut self,
485    ) -> Result<Option<(Statement, usize)>, ParseError> {
486        while self.eat(&TokenKind::Semicolon) {}
487        if self.at_eof() {
488            return Ok(None);
489        }
490
491        let statement = self.parse_statement()?;
492        let tail_offset = if self.eat(&TokenKind::Semicolon) {
493            self.tokens[self.pos - 1].span.end as usize
494        } else if self.at_eof() {
495            self.tokens
496                .last()
497                .map_or(0, |token| token.span.end as usize)
498        } else {
499            return Err(self.err_unexpected(
500                "unexpected token after end of statement; expected ';' separator",
501            ));
502        };
503
504        Ok(Some((statement, tail_offset)))
505    }
506
507    #[must_use]
508    pub fn errors(&self) -> &[ParseError] {
509        &self.errors
510    }
511
512    // -----------------------------------------------------------------------
513    // Token navigation
514    // -----------------------------------------------------------------------
515
516    pub(crate) fn peek(&self) -> &TokenKind {
517        self.current().map_or(&TokenKind::Eof, |t| &t.kind)
518    }
519
520    pub(crate) fn current(&self) -> Option<&Token> {
521        self.tokens.get(self.pos)
522    }
523
524    pub(crate) fn peek_nth(&self, n: usize) -> &TokenKind {
525        self.tokens
526            .get(self.pos + n)
527            .map_or(&TokenKind::Eof, |t| &t.kind)
528    }
529
530    pub(crate) fn at_eof(&self) -> bool {
531        matches!(self.peek(), TokenKind::Eof)
532    }
533
534    pub(crate) fn advance(&mut self) -> Option<&Token> {
535        let t = self.tokens.get(self.pos);
536        if self.pos < self.tokens.len().saturating_sub(1) {
537            self.pos += 1;
538        }
539        t
540    }
541
542    pub(crate) fn check(&self, kind: &TokenKind) -> bool {
543        std::mem::discriminant(self.peek()) == std::mem::discriminant(kind)
544    }
545
546    pub(crate) fn check_kw(&self, kw: &TokenKind) -> bool {
547        self.peek() == kw
548    }
549
550    pub(crate) fn eat(&mut self, kind: &TokenKind) -> bool {
551        if self.check(kind) {
552            self.advance();
553            true
554        } else {
555            false
556        }
557    }
558
559    pub(crate) fn eat_kw(&mut self, kw: &TokenKind) -> bool {
560        if self.peek() == kw {
561            self.advance();
562            true
563        } else {
564            false
565        }
566    }
567
568    pub(crate) fn expect_kw(&mut self, kw: &TokenKind) -> Result<Span, ParseError> {
569        if self.peek() == kw {
570            let sp = self.current_span();
571            self.advance();
572            Ok(sp)
573        } else {
574            Err(self.err_expected(&format!("{kw:?}")))
575        }
576    }
577
578    pub(crate) fn expect_token(&mut self, kind: &TokenKind) -> Result<Span, ParseError> {
579        if self.check(kind) {
580            let sp = self.current_span();
581            self.advance();
582            Ok(sp)
583        } else {
584            Err(self.err_expected(&format!("{kind:?}")))
585        }
586    }
587
588    pub(crate) fn current_span(&self) -> Span {
589        self.current().map_or(Span::ZERO, |t| t.span)
590    }
591
592    pub(crate) fn err_expected(&self, what: &str) -> ParseError {
593        // Stock's grammar reports every "expected X" grammar failure as
594        // `near "<offending-token>": syntax error` (or `incomplete input` at
595        // EOF). Tag UnexpectedToken so the connection boundary renders it that
596        // way. bd-parser-syntax-error-format-6w6kp (Part B).
597        let mut error = ParseError::at(format!("expected {what}"), self.current());
598        error.kind = ParseErrorKind::UnexpectedToken;
599        error
600    }
601
602    pub(crate) fn err_msg(&self, msg: impl Into<String>) -> ParseError {
603        ParseError::at(msg, self.current())
604    }
605
606    /// A generic unexpected-token parse error anchored at the current token.
607    /// The connection boundary renders it as stock `near "<lexeme>": syntax
608    /// error` (or `incomplete input` at EOF), keyed on the tagged kind.
609    /// bd-parser-syntax-error-format-6w6kp (Part B).
610    pub(crate) fn err_unexpected(&self, msg: impl Into<String>) -> ParseError {
611        let mut error = ParseError::at(msg, self.current());
612        error.kind = ParseErrorKind::UnexpectedToken;
613        error
614    }
615
616    /// A semantic parse-error whose fixed message matches stock SQLite verbatim.
617    /// The connection boundary surfaces it with NO offset prefix.
618    /// bd-parser-syntax-error-format-6w6kp (Part C).
619    pub(crate) fn err_semantic(&self, msg: impl Into<String>) -> ParseError {
620        let mut error = ParseError::at(msg, self.current());
621        error.kind = ParseErrorKind::Semantic;
622        error
623    }
624
625    fn synchronize(&mut self) {
626        loop {
627            match self.peek() {
628                TokenKind::Eof => return,
629                TokenKind::Semicolon => {
630                    self.advance();
631                    return;
632                }
633                k if k.is_statement_start() => return,
634                _ => {
635                    self.advance();
636                }
637            }
638        }
639    }
640
641    fn starts_nested_trigger_definition(&self) -> bool {
642        if !self.check_kw(&TokenKind::KwCreate) {
643            return false;
644        }
645
646        let mut offset = 1_usize;
647        while offset <= 2
648            && matches!(
649                self.peek_nth(offset),
650                TokenKind::KwTemp | TokenKind::KwTemporary | TokenKind::KwUnique
651            )
652        {
653            offset += 1;
654        }
655        self.peek_nth(offset) == &TokenKind::KwTrigger
656    }
657
658    fn recover_trigger_body_after_error(&mut self, statement_start: usize) {
659        // Rewind to the rejected body statement's first token. The enclosing
660        // trigger END is then the first END observed at a statement boundary;
661        // END used as an alias or CASE terminator remains inside its statement.
662        self.pos = statement_start.min(self.tokens.len().saturating_sub(1));
663        let mut at_statement_boundary = true;
664        let mut nested_trigger_header = false;
665        let mut nested_trigger_depth = 0_usize;
666
667        loop {
668            match self.peek() {
669                TokenKind::Eof => return,
670                TokenKind::Semicolon => {
671                    self.advance();
672                    at_statement_boundary = true;
673                    nested_trigger_header = false;
674                }
675                TokenKind::KwBegin if nested_trigger_header => {
676                    nested_trigger_depth = nested_trigger_depth.saturating_add(1);
677                    self.advance();
678                    at_statement_boundary = true;
679                    nested_trigger_header = false;
680                }
681                TokenKind::KwEnd if at_statement_boundary && nested_trigger_depth > 0 => {
682                    nested_trigger_depth -= 1;
683                    self.advance();
684                    let _ = self.eat(&TokenKind::Semicolon);
685                    at_statement_boundary = true;
686                }
687                TokenKind::KwEnd if at_statement_boundary => {
688                    self.advance();
689                    let _ = self.eat(&TokenKind::Semicolon);
690                    return;
691                }
692                _ => {
693                    if at_statement_boundary {
694                        nested_trigger_header = self.starts_nested_trigger_definition();
695                    }
696                    self.advance();
697                    at_statement_boundary = false;
698                }
699            }
700        }
701    }
702
703    // -----------------------------------------------------------------------
704    // Identifiers and names
705    // -----------------------------------------------------------------------
706
707    pub(crate) fn parse_identifier(&mut self) -> Result<String, ParseError> {
708        match self.peek().clone() {
709            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
710                self.advance();
711                Ok(s.to_string())
712            }
713            TokenKind::String(s) => {
714                self.advance();
715                Ok(s)
716            }
717            ref k if starts_post_dot_identifier(k) => {
718                let s = kw_to_str(k);
719                self.advance();
720                Ok(s)
721            }
722            _ => Err(self.err_expected("identifier")),
723        }
724    }
725
726    pub(crate) fn parse_table_star_qualifier(&mut self) -> Result<String, ParseError> {
727        match self.peek().clone() {
728            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
729                self.advance();
730                Ok(s.to_string())
731            }
732            TokenKind::String(s) => {
733                self.advance();
734                Ok(s)
735            }
736            ref k if starts_table_star_qualifier(k) => {
737                let s = kw_to_str(k);
738                self.advance();
739                Ok(s)
740            }
741            _ => Err(self.err_expected("table-star qualifier")),
742        }
743    }
744
745    pub(crate) fn parse_window_name(&mut self) -> Result<String, ParseError> {
746        match self.peek().clone() {
747            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
748                self.advance();
749                Ok(s.to_string())
750            }
751            TokenKind::String(s) => {
752                self.advance();
753                Ok(s)
754            }
755            ref k if starts_bare_window_name(k) => {
756                let s = kw_to_str(k);
757                self.advance();
758                Ok(s)
759            }
760            _ => Err(self.err_expected("window name")),
761        }
762    }
763
764    pub(crate) fn parse_qualified_name(&mut self) -> Result<QualifiedName, ParseError> {
765        let first = self.parse_identifier()?;
766        if self.eat(&TokenKind::Dot) {
767            let second = self.parse_identifier()?;
768            Ok(QualifiedName::qualified(first, second))
769        } else {
770            Ok(QualifiedName::bare(first))
771        }
772    }
773
774    fn parse_dml_target_name(
775        &mut self,
776        context: DmlParseContext,
777    ) -> Result<QualifiedName, ParseError> {
778        let first = self.parse_identifier()?;
779        if self.check(&TokenKind::Dot) {
780            if context == DmlParseContext::TriggerBody {
781                return Err(self.err_semantic(
782                    "qualified table names are not allowed on INSERT, UPDATE, and DELETE \
783                     statements within triggers",
784                ));
785            }
786            self.advance();
787            let second = self.parse_identifier()?;
788            Ok(QualifiedName::qualified(first, second))
789        } else {
790            Ok(QualifiedName::bare(first))
791        }
792    }
793
794    fn parse_qualified_table_ref(
795        &mut self,
796        context: DmlParseContext,
797    ) -> Result<QualifiedTableRef, ParseError> {
798        let name = self.parse_dml_target_name(context)?;
799        let alias = if context == DmlParseContext::TriggerBody {
800            if self.eat_kw(&TokenKind::KwAs) {
801                Some(self.parse_identifier()?)
802            } else {
803                None
804            }
805        } else {
806            self.try_table_alias()?
807        };
808        if context == DmlParseContext::TriggerBody
809            && (self.check_kw(&TokenKind::KwIndexed)
810                || (self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwIndexed))
811        {
812            // Stock emits a distinct verbatim message per clause.
813            // bd-parser-syntax-error-format-6w6kp (Part C).
814            let clause = if self.check_kw(&TokenKind::KwIndexed) {
815                "INDEXED BY"
816            } else {
817                "NOT INDEXED"
818            };
819            return Err(self.err_semantic(format!(
820                "the {clause} clause is not allowed on UPDATE or DELETE statements within triggers"
821            )));
822        }
823        let index_hint = self.parse_index_hint()?;
824        let time_travel = self.parse_time_travel_clause()?;
825        Ok(QualifiedTableRef {
826            name,
827            alias,
828            index_hint,
829            time_travel,
830        })
831    }
832
833    fn parse_alias_name(&mut self) -> Result<String, ParseError> {
834        match self.peek().clone() {
835            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
836                self.advance();
837                Ok(s.to_string())
838            }
839            TokenKind::String(s) => {
840                self.advance();
841                Ok(s)
842            }
843            ref k if starts_explicit_alias_name(k) => {
844                let s = kw_to_str(k);
845                self.advance();
846                Ok(s)
847            }
848            _ => Err(self.err_expected("alias")),
849        }
850    }
851
852    fn starts_window_clause(&self) -> bool {
853        self.check_kw(&TokenKind::KwWindow)
854            && starts_bare_window_name(self.peek_nth(1))
855            && self.peek_nth(2) == &TokenKind::KwAs
856    }
857
858    fn starts_time_travel_clause(&self) -> bool {
859        self.check_kw(&TokenKind::KwFor)
860            && matches!(
861                self.peek_nth(1),
862                TokenKind::Id(name) if name.eq_ignore_ascii_case("SYSTEM_TIME")
863            )
864    }
865
866    pub(crate) fn try_result_alias(&mut self) -> Result<Option<String>, ParseError> {
867        if self.eat_kw(&TokenKind::KwAs) {
868            return Ok(Some(self.parse_alias_name()?));
869        }
870        if self.starts_window_clause() {
871            return Ok(None);
872        }
873        if starts_result_alias(self.peek()) {
874            return Ok(Some(self.parse_alias_name()?));
875        }
876        Ok(None)
877    }
878
879    pub(crate) fn try_table_alias(&mut self) -> Result<Option<String>, ParseError> {
880        if self.eat_kw(&TokenKind::KwAs) {
881            return Ok(Some(self.parse_alias_name()?));
882        }
883        if self.starts_window_clause() || self.starts_time_travel_clause() {
884            return Ok(None);
885        }
886        if starts_table_alias(self.peek()) {
887            return Ok(Some(self.parse_alias_name()?));
888        }
889        Ok(None)
890    }
891
892    pub(crate) fn parse_index_hint(&mut self) -> Result<Option<IndexHint>, ParseError> {
893        if self.eat_kw(&TokenKind::KwIndexed) {
894            self.expect_kw(&TokenKind::KwBy)?;
895            Ok(Some(IndexHint::IndexedBy(self.parse_identifier()?)))
896        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwIndexed {
897            self.advance();
898            self.advance();
899            Ok(Some(IndexHint::NotIndexed))
900        } else {
901            Ok(None)
902        }
903    }
904
905    /// Parse an optional `FOR SYSTEM_TIME AS OF ...` clause (SQL:2011 temporal query).
906    ///
907    /// Grammar:
908    /// ```text
909    /// time_travel_clause ::= FOR SYSTEM_TIME AS OF COMMITSEQ integer
910    ///                      | FOR SYSTEM_TIME AS OF string_literal
911    /// ```
912    pub(crate) fn parse_time_travel_clause(
913        &mut self,
914    ) -> Result<Option<TimeTravelClause>, ParseError> {
915        if !self.check_kw(&TokenKind::KwFor) {
916            return Ok(None);
917        }
918        // Lookahead: FOR must be followed by SYSTEM_TIME (contextual identifier).
919        if !matches!(self.peek_nth(1), TokenKind::Id(s) if s.eq_ignore_ascii_case("SYSTEM_TIME")) {
920            return Ok(None);
921        }
922        self.advance(); // consume FOR
923        self.advance(); // consume SYSTEM_TIME
924        self.expect_kw(&TokenKind::KwAs)?;
925        self.expect_kw(&TokenKind::KwOf)?;
926
927        let target = if self.eat_kw(&TokenKind::KwCommitseq) {
928            match self.peek().clone() {
929                TokenKind::Integer(n) if n >= 0 => {
930                    self.advance();
931                    TimeTravelTarget::CommitSequence(n as u64)
932                }
933                TokenKind::OversizedInt(s) => {
934                    if let Ok(n) = s.parse::<u64>() {
935                        self.advance();
936                        TimeTravelTarget::CommitSequence(n)
937                    } else {
938                        return Err(self.err_expected("non-negative integer after COMMITSEQ"));
939                    }
940                }
941                _ => return Err(self.err_expected("non-negative integer after COMMITSEQ")),
942            }
943        } else {
944            match self.peek().clone() {
945                TokenKind::String(s) => {
946                    self.advance();
947                    TimeTravelTarget::Timestamp(s)
948                }
949                _ => {
950                    return Err(self.err_expected(
951                        "COMMITSEQ <n> or '<timestamp>' after FOR SYSTEM_TIME AS OF",
952                    ));
953                }
954            }
955        };
956
957        Ok(Some(TimeTravelClause { target }))
958    }
959
960    pub(crate) fn parse_comma_sep<T>(
961        &mut self,
962        f: fn(&mut Self) -> Result<T, ParseError>,
963    ) -> Result<Vec<T>, ParseError> {
964        let mut v = Vec::with_capacity(4);
965        v.push(f(self)?);
966        while self.eat(&TokenKind::Comma) {
967            v.push(f(self)?);
968        }
969        Ok(v)
970    }
971
972    // -----------------------------------------------------------------------
973    // Statement dispatch
974    // -----------------------------------------------------------------------
975
976    fn parse_statement_inner(&mut self) -> Result<Statement, ParseError> {
977        self.with_recursion_guard(|parser| match parser.peek().clone() {
978            TokenKind::KwSelect | TokenKind::KwValues => {
979                Ok(Statement::Select(parser.parse_select_stmt(None)?))
980            }
981            TokenKind::KwWith => parser.parse_with_leading(),
982            TokenKind::KwInsert | TokenKind::KwReplace => {
983                parser.parse_insert_stmt(None, DmlParseContext::TopLevel)
984            }
985            TokenKind::KwUpdate => parser.parse_update_stmt(None, DmlParseContext::TopLevel),
986            TokenKind::KwDelete => parser.parse_delete_stmt(None, DmlParseContext::TopLevel),
987            TokenKind::KwCreate => parser.parse_create(),
988            TokenKind::KwDrop => parser.parse_drop(),
989            TokenKind::KwAlter => parser.parse_alter(),
990            TokenKind::KwBegin => parser.parse_begin(),
991            TokenKind::KwCommit | TokenKind::KwEnd => {
992                parser.advance();
993                let _ = parser.eat_kw(&TokenKind::KwTransaction);
994                Ok(Statement::Commit)
995            }
996            TokenKind::KwRollback => parser.parse_rollback(),
997            TokenKind::KwSavepoint => {
998                parser.advance();
999                Ok(Statement::Savepoint(parser.parse_identifier()?))
1000            }
1001            TokenKind::KwRelease => {
1002                parser.advance();
1003                let _ = parser.eat_kw(&TokenKind::KwSavepoint);
1004                Ok(Statement::Release(parser.parse_identifier()?))
1005            }
1006            TokenKind::KwAttach => parser.parse_attach(),
1007            TokenKind::KwDetach => {
1008                parser.advance();
1009                let _ = parser.eat_kw(&TokenKind::KwDatabase);
1010                Ok(Statement::Detach(parser.parse_identifier()?))
1011            }
1012            TokenKind::KwPragma => parser.parse_pragma(),
1013            TokenKind::KwVacuum => parser.parse_vacuum(),
1014            TokenKind::KwReindex => {
1015                parser.advance();
1016                let name = if !parser.at_eof() && !parser.check(&TokenKind::Semicolon) {
1017                    Some(parser.parse_qualified_name()?)
1018                } else {
1019                    None
1020                };
1021                Ok(Statement::Reindex(name))
1022            }
1023            TokenKind::KwAnalyze => {
1024                parser.advance();
1025                let name = if !parser.at_eof() && !parser.check(&TokenKind::Semicolon) {
1026                    Some(parser.parse_qualified_name()?)
1027                } else {
1028                    None
1029                };
1030                Ok(Statement::Analyze(name))
1031            }
1032            TokenKind::KwExplain => parser.parse_explain(),
1033            _ => Err(parser.err_unexpected("unexpected token at start of statement")),
1034        })
1035    }
1036
1037    // -----------------------------------------------------------------------
1038    // WITH ... (SELECT | INSERT | UPDATE | DELETE)
1039    // -----------------------------------------------------------------------
1040
1041    fn parse_with_leading(&mut self) -> Result<Statement, ParseError> {
1042        let with = self.parse_with_clause()?;
1043        match self.peek() {
1044            TokenKind::KwSelect | TokenKind::KwValues => {
1045                Ok(Statement::Select(self.parse_select_stmt(Some(with))?))
1046            }
1047            TokenKind::KwInsert | TokenKind::KwReplace => {
1048                self.parse_insert_stmt(Some(with), DmlParseContext::TopLevel)
1049            }
1050            TokenKind::KwUpdate => self.parse_update_stmt(Some(with), DmlParseContext::TopLevel),
1051            TokenKind::KwDelete => self.parse_delete_stmt(Some(with), DmlParseContext::TopLevel),
1052            _ => Err(self.err_expected("SELECT, INSERT, UPDATE, or DELETE after WITH")),
1053        }
1054    }
1055
1056    pub(crate) fn parse_with_clause(&mut self) -> Result<WithClause, ParseError> {
1057        self.parse_with_clause_machine()
1058    }
1059
1060    // -----------------------------------------------------------------------
1061    // SELECT
1062    // -----------------------------------------------------------------------
1063
1064    pub(crate) fn parse_select_stmt(
1065        &mut self,
1066        with: Option<WithClause>,
1067    ) -> Result<SelectStatement, ParseError> {
1068        self.parse_select_stmt_tracked(with)
1069            .map(|tracked| tracked.value)
1070    }
1071
1072    pub(crate) fn parse_select_stmt_tracked(
1073        &mut self,
1074        with: Option<WithClause>,
1075    ) -> Result<HeightTracked<SelectStatement>, ParseError> {
1076        self.parse_select_tracked_machine(with)
1077    }
1078
1079    #[cfg(test)]
1080    pub(crate) fn parse_select_stmt_inner_tracked(
1081        &mut self,
1082        with: Option<WithClause>,
1083    ) -> Result<HeightTracked<SelectStatement>, ParseError> {
1084        let body = self.parse_select_body_tracked()?;
1085        let mut height = body.height;
1086        let final_core = body
1087            .value
1088            .compounds
1089            .last()
1090            .map_or(&body.value.select, |(_, core)| core);
1091        if matches!(final_core, SelectCore::Values(_))
1092            && matches!(self.peek(), TokenKind::KwOrder | TokenKind::KwLimit)
1093        {
1094            return Err(
1095                self.err_unexpected("ORDER BY / LIMIT clause is not allowed after a VALUES term")
1096            );
1097        }
1098        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1099            self.expect_kw(&TokenKind::KwBy)?;
1100            let parsed = self.parse_comma_sep(Self::parse_ordering_term_tracked)?;
1101            let mut terms = Vec::with_capacity(parsed.len());
1102            for tracked in parsed {
1103                height = height.max(tracked.height);
1104                terms.push(tracked.value);
1105            }
1106            terms
1107        } else {
1108            vec![]
1109        };
1110        let limit = self.parse_limit_tracked()?;
1111        height = height.max(limit.height);
1112        // bd-tp6ia: SQLite's grammar attaches ORDER BY / LIMIT to a SELECT,
1113        // but a final VALUES term has no such slot. This rejects both standalone
1114        // `VALUES (1) ORDER BY 1` and compounds such as
1115        // `SELECT 1 UNION VALUES (2),(3) ORDER BY 1`.
1116        if matches!(final_core, SelectCore::Values(_))
1117            && (!order_by.is_empty() || limit.value.is_some())
1118        {
1119            return Err(
1120                self.err_unexpected("ORDER BY / LIMIT clause is not allowed after a VALUES term")
1121            );
1122        }
1123        Ok(HeightTracked {
1124            value: SelectStatement {
1125                with,
1126                body: body.value,
1127                order_by,
1128                limit: limit.value,
1129            },
1130            height,
1131        })
1132    }
1133
1134    #[cfg(test)]
1135    fn parse_select_body_tracked(&mut self) -> Result<HeightTracked<SelectBody>, ParseError> {
1136        let select = self.parse_select_core_tracked()?;
1137        let mut height = select.height;
1138        let mut compounds = Vec::new();
1139        loop {
1140            let op = if self.eat_kw(&TokenKind::KwUnion) {
1141                if self.eat_kw(&TokenKind::KwAll) {
1142                    CompoundOp::UnionAll
1143                } else {
1144                    CompoundOp::Union
1145                }
1146            } else if self.eat_kw(&TokenKind::KwIntersect) {
1147                CompoundOp::Intersect
1148            } else if self.eat_kw(&TokenKind::KwExcept) {
1149                CompoundOp::Except
1150            } else {
1151                break;
1152            };
1153            let core = self.parse_select_core_tracked()?;
1154            height = height.max(core.height);
1155            compounds.push((op, core.value));
1156        }
1157        Ok(HeightTracked {
1158            value: SelectBody {
1159                select: select.value,
1160                compounds,
1161            },
1162            height,
1163        })
1164    }
1165
1166    #[cfg(test)]
1167    fn parse_select_core_tracked(&mut self) -> Result<HeightTracked<SelectCore>, ParseError> {
1168        if self.eat_kw(&TokenKind::KwValues) {
1169            return self.parse_values_core_tracked();
1170        }
1171        self.expect_kw(&TokenKind::KwSelect)?;
1172        let distinct = if self.eat_kw(&TokenKind::KwDistinct) {
1173            Distinctness::Distinct
1174        } else {
1175            let _ = self.eat_kw(&TokenKind::KwAll);
1176            Distinctness::All
1177        };
1178        let parsed_columns = self.parse_comma_sep(Self::parse_result_column_tracked)?;
1179        let mut height = 0;
1180        let mut columns = Vec::with_capacity(parsed_columns.len());
1181        for tracked in parsed_columns {
1182            height = height.max(tracked.height);
1183            columns.push(tracked.value);
1184        }
1185        let from = if self.eat_kw(&TokenKind::KwFrom) {
1186            Some(self.parse_from_clause()?)
1187        } else {
1188            None
1189        };
1190        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1191            let parsed = self.parse_expr_tracked()?;
1192            height = height.max(parsed.height);
1193            Some(Box::new(parsed.expr))
1194        } else {
1195            None
1196        };
1197        let group_by = if self.eat_kw(&TokenKind::KwGroup) {
1198            self.expect_kw(&TokenKind::KwBy)?;
1199            let parsed = self.parse_comma_sep(Self::parse_expr_tracked)?;
1200            let mut expressions = Vec::with_capacity(parsed.len());
1201            for tracked in parsed {
1202                height = height.max(tracked.height);
1203                expressions.push(tracked.expr);
1204            }
1205            expressions
1206        } else {
1207            vec![]
1208        };
1209        let having = if self.eat_kw(&TokenKind::KwHaving) {
1210            let parsed = self.parse_expr_tracked()?;
1211            height = height.max(parsed.height);
1212            Some(Box::new(parsed.expr))
1213        } else {
1214            None
1215        };
1216        let windows = if self.eat_kw(&TokenKind::KwWindow) {
1217            self.parse_comma_sep(Self::parse_window_def)?
1218        } else {
1219            vec![]
1220        };
1221        Ok(HeightTracked {
1222            value: SelectCore::Select {
1223                distinct,
1224                columns,
1225                from,
1226                where_clause,
1227                group_by,
1228                having,
1229                windows,
1230            },
1231            height,
1232        })
1233    }
1234
1235    fn parse_values_core(&mut self) -> Result<SelectCore, ParseError> {
1236        self.parse_values_core_tracked()
1237            .map(|tracked| tracked.value)
1238    }
1239
1240    fn parse_values_core_tracked(&mut self) -> Result<HeightTracked<SelectCore>, ParseError> {
1241        let mut rows = Vec::new();
1242        let mut height = 0;
1243        let mut force_union_all_from = None;
1244        loop {
1245            self.expect_token(&TokenKind::LeftParen)?;
1246            let parsed = self.parse_comma_sep(Self::parse_expr_tracked)?;
1247            let mut row = Vec::with_capacity(parsed.len());
1248            for tracked in parsed {
1249                height = height.max(tracked.height);
1250                row.push(tracked.expr);
1251            }
1252            self.expect_token(&TokenKind::RightParen)?;
1253            if force_union_all_from.is_none() && self.has_with {
1254                force_union_all_from = Some(rows.len());
1255            }
1256            rows.push(row);
1257            if !self.eat(&TokenKind::Comma) {
1258                break;
1259            }
1260        }
1261        Ok(HeightTracked {
1262            value: SelectCore::Values(ValuesClause::parsed(rows, force_union_all_from)),
1263            height,
1264        })
1265    }
1266
1267    fn parse_result_column_tracked(&mut self) -> Result<HeightTracked<ResultColumn>, ParseError> {
1268        if self.eat(&TokenKind::Star) {
1269            return Ok(HeightTracked {
1270                value: ResultColumn::Star,
1271                height: 0,
1272            });
1273        }
1274        if starts_table_star_qualifier(self.peek()) && self.peek_nth(1) == &TokenKind::Dot {
1275            if self.peek_nth(2) == &TokenKind::Star {
1276                let table = self.parse_table_star_qualifier()?;
1277                self.expect_token(&TokenKind::Dot)?;
1278                self.expect_token(&TokenKind::Star)?;
1279                return Ok(HeightTracked {
1280                    value: ResultColumn::TableStar(QualifiedName::bare(table)),
1281                    height: 0,
1282                });
1283            }
1284            if starts_table_star_qualifier(self.peek_nth(2))
1285                && self.peek_nth(3) == &TokenKind::Dot
1286                && self.peek_nth(4) == &TokenKind::Star
1287            {
1288                let schema = self.parse_table_star_qualifier()?;
1289                self.expect_token(&TokenKind::Dot)?;
1290                let table = self.parse_table_star_qualifier()?;
1291                self.expect_token(&TokenKind::Dot)?;
1292                self.expect_token(&TokenKind::Star)?;
1293                return Ok(HeightTracked {
1294                    value: ResultColumn::TableStar(QualifiedName::qualified(schema, table)),
1295                    height: 0,
1296                });
1297            }
1298        }
1299        let parsed = self.parse_expr_tracked()?;
1300        let alias = self.try_result_alias()?;
1301        Ok(HeightTracked {
1302            value: ResultColumn::Expr {
1303                expr: parsed.expr,
1304                alias,
1305            },
1306            height: parsed.height,
1307        })
1308    }
1309
1310    fn parse_result_column(&mut self) -> Result<ResultColumn, ParseError> {
1311        self.parse_result_column_tracked()
1312            .map(|tracked| tracked.value)
1313    }
1314
1315    // -----------------------------------------------------------------------
1316    // FROM clause & JOINs
1317    // -----------------------------------------------------------------------
1318
1319    #[cfg(test)]
1320    fn parse_from_clause(&mut self) -> Result<FromClause, ParseError> {
1321        let source = self.parse_table_or_subquery()?;
1322        let mut joins = Vec::new();
1323        loop {
1324            if let Some(jt) = self.try_join_type()? {
1325                let table = self.parse_table_or_subquery()?;
1326                let constraint = self.parse_join_constraint()?;
1327                if jt.natural && constraint.is_some() {
1328                    return Err(self.err_msg("a NATURAL join may not have an ON or USING clause"));
1329                }
1330                joins.push(JoinClause {
1331                    join_type: jt,
1332                    table,
1333                    constraint,
1334                });
1335            } else if self.eat(&TokenKind::Comma) {
1336                let table = self.parse_table_or_subquery()?;
1337                let constraint = self.parse_join_constraint()?;
1338                joins.push(JoinClause {
1339                    join_type: JoinType {
1340                        natural: false,
1341                        kind: JoinKind::Cross,
1342                    },
1343                    table,
1344                    constraint,
1345                });
1346            } else {
1347                break;
1348            }
1349        }
1350        Ok(FromClause { source, joins })
1351    }
1352
1353    #[cfg(test)]
1354    fn parse_table_or_subquery(&mut self) -> Result<TableOrSubquery, ParseError> {
1355        self.with_recursion_guard(|parser| parser.parse_table_or_subquery_inner())
1356    }
1357
1358    #[cfg(test)]
1359    fn parse_table_or_subquery_inner(&mut self) -> Result<TableOrSubquery, ParseError> {
1360        if self.check(&TokenKind::LeftParen) {
1361            self.advance();
1362            if matches!(
1363                self.peek(),
1364                TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
1365            ) {
1366                let with = if self.check_kw(&TokenKind::KwWith) {
1367                    Some(self.parse_with_clause()?)
1368                } else {
1369                    None
1370                };
1371                let q = self.parse_select_stmt(with)?;
1372                self.expect_token(&TokenKind::RightParen)?;
1373                let alias = self.try_table_alias()?;
1374                return Ok(TableOrSubquery::Subquery {
1375                    query: Box::new(q),
1376                    alias,
1377                });
1378            }
1379            // Parenthesized join
1380            let fc = self.parse_from_clause()?;
1381            self.expect_token(&TokenKind::RightParen)?;
1382            return Ok(TableOrSubquery::ParenJoin(Box::new(fc)));
1383        }
1384
1385        let name = self.parse_qualified_name()?;
1386
1387        // Table-valued function: name(args)
1388        if self.check(&TokenKind::LeftParen) && name.schema.is_none() {
1389            self.advance();
1390            let args = if self.check(&TokenKind::RightParen) {
1391                vec![]
1392            } else {
1393                self.parse_comma_sep(Self::parse_expr)?
1394            };
1395            self.expect_token(&TokenKind::RightParen)?;
1396            let alias = self.try_table_alias()?;
1397            return Ok(TableOrSubquery::TableFunction {
1398                name: name.name,
1399                args,
1400                alias,
1401            });
1402        }
1403
1404        let alias = self.try_table_alias()?;
1405        let index_hint = self.parse_index_hint()?;
1406        let time_travel = self.parse_time_travel_clause()?;
1407        Ok(TableOrSubquery::Table {
1408            name,
1409            alias,
1410            index_hint,
1411            time_travel,
1412        })
1413    }
1414
1415    pub(crate) fn try_join_type(&mut self) -> Result<Option<JoinType>, ParseError> {
1416        let natural = self.eat_kw(&TokenKind::KwNatural);
1417        let kind = if self.eat_kw(&TokenKind::KwJoin) {
1418            Some(JoinKind::Inner)
1419        } else if self.eat_kw(&TokenKind::KwInner) {
1420            self.expect_kw(&TokenKind::KwJoin)?;
1421            Some(JoinKind::Inner)
1422        } else if self.eat_kw(&TokenKind::KwCross) {
1423            self.expect_kw(&TokenKind::KwJoin)?;
1424            Some(JoinKind::Cross)
1425        } else if self.eat_kw(&TokenKind::KwLeft) {
1426            let _ = self.eat_kw(&TokenKind::KwOuter);
1427            self.expect_kw(&TokenKind::KwJoin)?;
1428            Some(JoinKind::Left)
1429        } else if self.eat_kw(&TokenKind::KwRight) {
1430            let _ = self.eat_kw(&TokenKind::KwOuter);
1431            self.expect_kw(&TokenKind::KwJoin)?;
1432            Some(JoinKind::Right)
1433        } else if self.eat_kw(&TokenKind::KwFull) {
1434            let _ = self.eat_kw(&TokenKind::KwOuter);
1435            self.expect_kw(&TokenKind::KwJoin)?;
1436            Some(JoinKind::Full)
1437        } else {
1438            None
1439        };
1440        match kind {
1441            Some(k) => Ok(Some(JoinType { natural, kind: k })),
1442            None if natural => Err(self.err_expected("JOIN after NATURAL")),
1443            None => Ok(None),
1444        }
1445    }
1446
1447    #[cfg(test)]
1448    fn parse_join_constraint(&mut self) -> Result<Option<JoinConstraint>, ParseError> {
1449        if self.eat_kw(&TokenKind::KwOn) {
1450            Ok(Some(JoinConstraint::On(self.parse_expr()?)))
1451        } else if self.eat_kw(&TokenKind::KwUsing) {
1452            self.expect_token(&TokenKind::LeftParen)?;
1453            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1454            self.expect_token(&TokenKind::RightParen)?;
1455            Ok(Some(JoinConstraint::Using(cols)))
1456        } else {
1457            Ok(None)
1458        }
1459    }
1460
1461    // -----------------------------------------------------------------------
1462    // ORDER BY / LIMIT
1463    // -----------------------------------------------------------------------
1464
1465    pub(crate) fn parse_ordering_term(&mut self) -> Result<OrderingTerm, ParseError> {
1466        self.parse_ordering_term_tracked()
1467            .map(|tracked| tracked.value)
1468    }
1469
1470    fn parse_ordering_term_tracked(&mut self) -> Result<HeightTracked<OrderingTerm>, ParseError> {
1471        let parsed = self.parse_expr_tracked()?;
1472        let direction = if self.eat_kw(&TokenKind::KwAsc) {
1473            Some(SortDirection::Asc)
1474        } else if self.eat_kw(&TokenKind::KwDesc) {
1475            Some(SortDirection::Desc)
1476        } else {
1477            None
1478        };
1479        let nulls = if self.eat_kw(&TokenKind::KwNulls) {
1480            if self.eat_kw(&TokenKind::KwFirst) {
1481                Some(NullsOrder::First)
1482            } else {
1483                self.expect_kw(&TokenKind::KwLast)?;
1484                Some(NullsOrder::Last)
1485            }
1486        } else {
1487            None
1488        };
1489        Ok(HeightTracked {
1490            value: OrderingTerm {
1491                expr: parsed.expr,
1492                direction,
1493                nulls,
1494            },
1495            height: parsed.height,
1496        })
1497    }
1498
1499    pub(crate) fn parse_limit(&mut self) -> Result<Option<LimitClause>, ParseError> {
1500        self.parse_limit_tracked().map(|tracked| tracked.value)
1501    }
1502
1503    fn parse_limit_tracked(&mut self) -> Result<HeightTracked<Option<LimitClause>>, ParseError> {
1504        if !self.eat_kw(&TokenKind::KwLimit) {
1505            return Ok(HeightTracked {
1506                value: None,
1507                height: 0,
1508            });
1509        }
1510        let first = self.parse_expr_tracked()?;
1511        if self.eat_kw(&TokenKind::KwOffset) {
1512            let offset = self.parse_expr_tracked()?;
1513            let height = self.checked_cached_parent_height(first.height.max(offset.height))?;
1514            return Ok(HeightTracked {
1515                value: Some(LimitClause {
1516                    limit: first.expr,
1517                    offset: Some(offset.expr),
1518                }),
1519                height,
1520            });
1521        }
1522
1523        if self.eat(&TokenKind::Comma) {
1524            // LIMIT offset, count — SQLite/MySQL compatibility form.
1525            let second = self.parse_expr_tracked()?;
1526            let height = self.checked_cached_parent_height(first.height.max(second.height))?;
1527            return Ok(HeightTracked {
1528                value: Some(LimitClause {
1529                    limit: second.expr,
1530                    offset: Some(first.expr),
1531                }),
1532                height,
1533            });
1534        }
1535
1536        let height = self.checked_cached_parent_height(first.height)?;
1537        Ok(HeightTracked {
1538            value: Some(LimitClause {
1539                limit: first.expr,
1540                offset: None,
1541            }),
1542            height,
1543        })
1544    }
1545
1546    // -----------------------------------------------------------------------
1547    // RETURNING clause
1548    // -----------------------------------------------------------------------
1549
1550    fn parse_returning(
1551        &mut self,
1552        context: DmlParseContext,
1553        dml_is_insert: bool,
1554    ) -> Result<Vec<ResultColumn>, ParseError> {
1555        if self.check_kw(&TokenKind::KwReturning) && context == DmlParseContext::TriggerBody {
1556            // Stock diverges by statement: a trigger-body INSERT parses RETURNING
1557            // grammatically and then rejects it with a fixed semantic message,
1558            // while the trigger UPDATE/DELETE grammar has no RETURNING slot at
1559            // all, so RETURNING is just an unexpected token there.
1560            // bd-parser-syntax-error-format-6w6kp (Part C).
1561            return Err(if dml_is_insert {
1562                self.err_semantic("cannot use RETURNING in a trigger")
1563            } else {
1564                self.err_unexpected("RETURNING is not allowed in trigger body statements")
1565            });
1566        }
1567        if self.eat_kw(&TokenKind::KwReturning) {
1568            self.parse_comma_sep(Self::parse_result_column)
1569        } else {
1570            Ok(vec![])
1571        }
1572    }
1573
1574    // -----------------------------------------------------------------------
1575    // INSERT
1576    // -----------------------------------------------------------------------
1577
1578    fn parse_insert_stmt(
1579        &mut self,
1580        with: Option<WithClause>,
1581        context: DmlParseContext,
1582    ) -> Result<Statement, ParseError> {
1583        let or_conflict = if self.eat_kw(&TokenKind::KwReplace) {
1584            Some(ConflictAction::Replace)
1585        } else {
1586            self.expect_kw(&TokenKind::KwInsert)?;
1587            if self.eat_kw(&TokenKind::KwOr) {
1588                Some(self.parse_conflict_action()?)
1589            } else {
1590                None
1591            }
1592        };
1593        self.eat_kw(&TokenKind::KwInto);
1594        let table = self.parse_dml_target_name(context)?;
1595        let alias = if self.eat_kw(&TokenKind::KwAs) {
1596            Some(self.parse_identifier()?)
1597        } else {
1598            None
1599        };
1600        let columns = if self.check(&TokenKind::LeftParen)
1601            && !matches!(
1602                self.peek_nth(1),
1603                TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
1604            ) {
1605            self.advance();
1606            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1607            self.expect_token(&TokenKind::RightParen)?;
1608            cols
1609        } else {
1610            vec![]
1611        };
1612        let source = if self.check_kw(&TokenKind::KwDefault)
1613            && context == DmlParseContext::TriggerBody
1614        {
1615            return Err(
1616                self.err_unexpected("DEFAULT VALUES is not allowed in trigger body statements")
1617            );
1618        } else if self.eat_kw(&TokenKind::KwDefault) {
1619            self.expect_kw(&TokenKind::KwValues)?;
1620            InsertSource::DefaultValues
1621        } else if self.eat_kw(&TokenKind::KwValues) {
1622            match self.parse_values_core()? {
1623                SelectCore::Values(rows) => InsertSource::Values(rows.into_rows()),
1624                SelectCore::Select { .. } => unreachable!("parse_values_core must return VALUES"),
1625            }
1626        } else {
1627            let inner_with = if self.check_kw(&TokenKind::KwWith) {
1628                Some(self.parse_with_clause()?)
1629            } else {
1630                None
1631            };
1632            InsertSource::Select(Box::new(self.parse_select_stmt(inner_with)?))
1633        };
1634        let upsert = self.parse_upsert_clauses()?;
1635        let returning = self.parse_returning(context, true)?;
1636        Ok(Statement::Insert(InsertStatement {
1637            with,
1638            or_conflict,
1639            table,
1640            alias,
1641            columns,
1642            source,
1643            upsert,
1644            returning,
1645        }))
1646    }
1647
1648    fn parse_conflict_action(&mut self) -> Result<ConflictAction, ParseError> {
1649        if self.eat_kw(&TokenKind::KwRollback) {
1650            Ok(ConflictAction::Rollback)
1651        } else if self.eat_kw(&TokenKind::KwAbort) {
1652            Ok(ConflictAction::Abort)
1653        } else if self.eat_kw(&TokenKind::KwFail) {
1654            Ok(ConflictAction::Fail)
1655        } else if self.eat_kw(&TokenKind::KwIgnore) {
1656            Ok(ConflictAction::Ignore)
1657        } else if self.eat_kw(&TokenKind::KwReplace) {
1658            Ok(ConflictAction::Replace)
1659        } else {
1660            Err(self.err_expected("conflict action"))
1661        }
1662    }
1663
1664    fn parse_upsert_clauses(&mut self) -> Result<Vec<UpsertClause>, ParseError> {
1665        let mut clauses = Vec::new();
1666        while self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
1667            // SQLite 3.35+: a clause without a conflict target is only valid as
1668            // the final ON CONFLICT clause of the INSERT.
1669            if clauses
1670                .last()
1671                .is_some_and(|clause: &UpsertClause| clause.target.is_none())
1672            {
1673                return Err(self.err_msg(
1674                    "ON CONFLICT clause without a conflict target must be the last ON CONFLICT clause",
1675                ));
1676            }
1677            self.advance(); // ON
1678            self.advance(); // CONFLICT
1679            let target = if self.check(&TokenKind::LeftParen) {
1680                self.advance();
1681                let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1682                self.expect_token(&TokenKind::RightParen)?;
1683                let wh = if self.eat_kw(&TokenKind::KwWhere) {
1684                    Some(self.parse_expr()?)
1685                } else {
1686                    None
1687                };
1688                Some(UpsertTarget {
1689                    columns,
1690                    where_clause: wh,
1691                })
1692            } else {
1693                None
1694            };
1695            self.expect_kw(&TokenKind::KwDo)?;
1696            let action = if self.eat_kw(&TokenKind::KwNothing) {
1697                UpsertAction::Nothing
1698            } else {
1699                self.expect_kw(&TokenKind::KwUpdate)?;
1700                self.expect_kw(&TokenKind::KwSet)?;
1701                let assignments = self.parse_comma_sep(Self::parse_assignment)?;
1702                let wh = if self.eat_kw(&TokenKind::KwWhere) {
1703                    Some(Box::new(self.parse_expr()?))
1704                } else {
1705                    None
1706                };
1707                UpsertAction::Update {
1708                    assignments,
1709                    where_clause: wh,
1710                }
1711            };
1712            clauses.push(UpsertClause { target, action });
1713        }
1714        Ok(clauses)
1715    }
1716
1717    // -----------------------------------------------------------------------
1718    // UPDATE
1719    // -----------------------------------------------------------------------
1720
1721    fn parse_update_stmt(
1722        &mut self,
1723        with: Option<WithClause>,
1724        context: DmlParseContext,
1725    ) -> Result<Statement, ParseError> {
1726        self.expect_kw(&TokenKind::KwUpdate)?;
1727        let or_conflict = if self.eat_kw(&TokenKind::KwOr) {
1728            Some(self.parse_conflict_action()?)
1729        } else {
1730            None
1731        };
1732        let table = self.parse_qualified_table_ref(context)?;
1733        self.expect_kw(&TokenKind::KwSet)?;
1734        let assignments = self.parse_comma_sep(Self::parse_assignment)?;
1735        let from = if self.eat_kw(&TokenKind::KwFrom) {
1736            Some(self.parse_from_clause_machine()?)
1737        } else {
1738            None
1739        };
1740        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1741            Some(self.parse_expr()?)
1742        } else {
1743            None
1744        };
1745        let returning = self.parse_returning(context, false)?;
1746        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwOrder) {
1747            return Err(
1748                self.err_unexpected("ORDER BY is not allowed in trigger body UPDATE statements")
1749            );
1750        }
1751        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1752            self.expect_kw(&TokenKind::KwBy)?;
1753            self.parse_comma_sep(Self::parse_ordering_term)?
1754        } else {
1755            vec![]
1756        };
1757        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwLimit) {
1758            return Err(
1759                self.err_unexpected("LIMIT is not allowed in trigger body UPDATE statements")
1760            );
1761        }
1762        let limit = self.parse_limit()?;
1763        Ok(Statement::Update(UpdateStatement {
1764            with,
1765            or_conflict,
1766            table,
1767            assignments,
1768            from,
1769            where_clause,
1770            returning,
1771            order_by,
1772            limit,
1773        }))
1774    }
1775
1776    fn parse_assignment(&mut self) -> Result<Assignment, ParseError> {
1777        let target = if self.check(&TokenKind::LeftParen) {
1778            self.advance();
1779            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1780            self.expect_token(&TokenKind::RightParen)?;
1781            AssignmentTarget::ColumnList(cols)
1782        } else {
1783            AssignmentTarget::Column(self.parse_identifier()?)
1784        };
1785        self.expect_token(&TokenKind::Eq)?;
1786        let value = self.parse_expr()?;
1787        Ok(Assignment { target, value })
1788    }
1789
1790    // -----------------------------------------------------------------------
1791    // DELETE
1792    // -----------------------------------------------------------------------
1793
1794    fn parse_delete_stmt(
1795        &mut self,
1796        with: Option<WithClause>,
1797        context: DmlParseContext,
1798    ) -> Result<Statement, ParseError> {
1799        self.expect_kw(&TokenKind::KwDelete)?;
1800        self.expect_kw(&TokenKind::KwFrom)?;
1801        let table = self.parse_qualified_table_ref(context)?;
1802        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1803            Some(self.parse_expr()?)
1804        } else {
1805            None
1806        };
1807        let returning = self.parse_returning(context, false)?;
1808        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwOrder) {
1809            return Err(
1810                self.err_unexpected("ORDER BY is not allowed in trigger body DELETE statements")
1811            );
1812        }
1813        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1814            self.expect_kw(&TokenKind::KwBy)?;
1815            self.parse_comma_sep(Self::parse_ordering_term)?
1816        } else {
1817            vec![]
1818        };
1819        if context == DmlParseContext::TriggerBody && self.check_kw(&TokenKind::KwLimit) {
1820            return Err(
1821                self.err_unexpected("LIMIT is not allowed in trigger body DELETE statements")
1822            );
1823        }
1824        let limit = self.parse_limit()?;
1825        Ok(Statement::Delete(DeleteStatement {
1826            with,
1827            table,
1828            where_clause,
1829            returning,
1830            order_by,
1831            limit,
1832        }))
1833    }
1834
1835    // -----------------------------------------------------------------------
1836    // CREATE
1837    // -----------------------------------------------------------------------
1838
1839    fn parse_create(&mut self) -> Result<Statement, ParseError> {
1840        self.expect_kw(&TokenKind::KwCreate)?;
1841        let temporary = self.eat_kw(&TokenKind::KwTemp) || self.eat_kw(&TokenKind::KwTemporary);
1842        let unique = self.eat_kw(&TokenKind::KwUnique);
1843
1844        if self.eat_kw(&TokenKind::KwTable) {
1845            if unique {
1846                return Err(self.err_expected("INDEX after UNIQUE"));
1847            }
1848            return self.parse_create_table(temporary);
1849        }
1850        if self.eat_kw(&TokenKind::KwIndex) {
1851            if temporary {
1852                return Err(self.err_expected("TABLE, VIEW, or TRIGGER after TEMP"));
1853            }
1854            return self.parse_create_index(unique);
1855        }
1856        if self.eat_kw(&TokenKind::KwView) {
1857            if unique {
1858                return Err(self.err_expected("INDEX after UNIQUE"));
1859            }
1860            return self.parse_create_view(temporary);
1861        }
1862        if self.eat_kw(&TokenKind::KwTrigger) {
1863            if unique {
1864                return Err(self.err_expected("INDEX after UNIQUE"));
1865            }
1866            return self.parse_create_trigger(temporary);
1867        }
1868        if self.eat_kw(&TokenKind::KwVirtual) {
1869            if temporary || unique {
1870                return Err(self.err_expected("TABLE, INDEX, VIEW, or TRIGGER"));
1871            }
1872            self.expect_kw(&TokenKind::KwTable)?;
1873            return self.parse_create_virtual_table();
1874        }
1875        Err(self.err_expected("TABLE, INDEX, VIEW, TRIGGER, or VIRTUAL"))
1876    }
1877
1878    fn parse_if_not_exists(&mut self) -> bool {
1879        if self.check_kw(&TokenKind::KwIf)
1880            && self.peek_nth(1) == &TokenKind::KwNot
1881            && self.peek_nth(2) == &TokenKind::KwExists
1882        {
1883            self.advance();
1884            self.advance();
1885            self.advance();
1886            true
1887        } else {
1888            false
1889        }
1890    }
1891
1892    fn parse_create_table(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1893        let if_not_exists = self.parse_if_not_exists();
1894        let name = self.parse_qualified_name()?;
1895        let body = if self.eat_kw(&TokenKind::KwAs) {
1896            let with = if self.check_kw(&TokenKind::KwWith) {
1897                Some(self.parse_with_clause()?)
1898            } else {
1899                None
1900            };
1901            CreateTableBody::AsSelect(Box::new(self.parse_select_stmt(with)?))
1902        } else {
1903            self.expect_token(&TokenKind::LeftParen)?;
1904            let mut columns = Vec::new();
1905            let mut constraints = Vec::new();
1906            loop {
1907                if self.is_table_constraint_start() {
1908                    constraints.push(self.parse_table_constraint()?);
1909                } else {
1910                    columns.push(self.parse_column_def()?);
1911                }
1912                if !self.eat(&TokenKind::Comma) {
1913                    break;
1914                }
1915            }
1916            self.expect_token(&TokenKind::RightParen)?;
1917            CreateTableBody::Columns {
1918                columns,
1919                constraints,
1920            }
1921        };
1922        let mut without_rowid = false;
1923        let mut strict = false;
1924        // Table options after the closing paren.
1925        if self.check_kw(&TokenKind::KwWithout) || self.check_kw(&TokenKind::KwStrict) {
1926            loop {
1927                if self.check_kw(&TokenKind::KwWithout) {
1928                    self.advance();
1929                    // Expect "ROWID" as an identifier.
1930                    let id = self.parse_identifier()?;
1931                    if !id.eq_ignore_ascii_case("ROWID") {
1932                        return Err(self.err_expected("ROWID after WITHOUT"));
1933                    }
1934                    without_rowid = true;
1935                } else if self.eat_kw(&TokenKind::KwStrict) {
1936                    strict = true;
1937                } else {
1938                    return Err(self.err_expected("table option"));
1939                }
1940                if !self.eat(&TokenKind::Comma) {
1941                    break;
1942                }
1943            }
1944        }
1945        Ok(Statement::CreateTable(CreateTableStatement {
1946            if_not_exists,
1947            temporary,
1948            name,
1949            body,
1950            without_rowid,
1951            strict,
1952        }))
1953    }
1954
1955    fn is_table_constraint_start(&self) -> bool {
1956        matches!(
1957            self.peek(),
1958            TokenKind::KwPrimary | TokenKind::KwUnique | TokenKind::KwCheck | TokenKind::KwForeign
1959        ) || (self.check_kw(&TokenKind::KwConstraint))
1960    }
1961
1962    fn parse_column_def(&mut self) -> Result<ColumnDef, ParseError> {
1963        let name = self.parse_identifier()?;
1964        let type_name = self.try_type_name()?;
1965        let mut constraints = Vec::new();
1966        while let Some(c) = self.try_column_constraint()? {
1967            constraints.push(c);
1968        }
1969        Ok(ColumnDef {
1970            name,
1971            type_name,
1972            constraints,
1973        })
1974    }
1975
1976    fn try_type_name(&mut self) -> Result<Option<TypeName>, ParseError> {
1977        // Type name is one or more identifiers, stopping at known boundaries.
1978        if self.is_column_constraint_start()
1979            || matches!(
1980                self.peek(),
1981                TokenKind::Comma | TokenKind::RightParen | TokenKind::Eof
1982            )
1983        {
1984            return Ok(None);
1985        }
1986        // Collect type name words.
1987        let mut words = Vec::new();
1988        loop {
1989            match self.peek() {
1990                TokenKind::Id(_) | TokenKind::QuotedId(_, _) => {
1991                    words.push(self.parse_identifier()?);
1992                }
1993                k if is_nonreserved_kw(k) => {
1994                    words.push(self.parse_identifier()?);
1995                }
1996                _ => break,
1997            }
1998            if self.is_column_constraint_start()
1999                || matches!(
2000                    self.peek(),
2001                    TokenKind::Comma | TokenKind::RightParen | TokenKind::LeftParen
2002                )
2003            {
2004                break;
2005            }
2006        }
2007        if words.is_empty() {
2008            return Ok(None);
2009        }
2010        let type_name = words.join(" ");
2011        let (arg1, arg2) = if self.eat(&TokenKind::LeftParen) {
2012            let a1 = self.parse_signed_number_str()?;
2013            let a2 = if self.eat(&TokenKind::Comma) {
2014                Some(self.parse_signed_number_str()?)
2015            } else {
2016                None
2017            };
2018            self.expect_token(&TokenKind::RightParen)?;
2019            (Some(a1), a2)
2020        } else {
2021            (None, None)
2022        };
2023        Ok(Some(TypeName {
2024            name: type_name,
2025            arg1,
2026            arg2,
2027        }))
2028    }
2029
2030    fn parse_signed_number_str(&mut self) -> Result<String, ParseError> {
2031        let neg = self.eat(&TokenKind::Minus);
2032        let plus = if neg {
2033            false
2034        } else {
2035            self.eat(&TokenKind::Plus)
2036        };
2037        let _ = plus; // just consume
2038        match self.peek().clone() {
2039            TokenKind::Integer(n) => {
2040                self.advance();
2041                Ok(if neg { format!("-{n}") } else { n.to_string() })
2042            }
2043            TokenKind::OversizedInt(s) => {
2044                self.advance();
2045                Ok(if neg { format!("-{s}") } else { s.clone() })
2046            }
2047            TokenKind::Float(f) => {
2048                self.advance();
2049                Ok(if neg { format!("-{f}") } else { f.to_string() })
2050            }
2051            _ => Err(self.err_expected("number")),
2052        }
2053    }
2054
2055    fn is_column_constraint_start(&self) -> bool {
2056        matches!(
2057            self.peek(),
2058            TokenKind::KwPrimary
2059                | TokenKind::KwNot
2060                | TokenKind::KwNull
2061                | TokenKind::KwUnique
2062                | TokenKind::KwCheck
2063                | TokenKind::KwDefault
2064                | TokenKind::KwCollate
2065                | TokenKind::KwReferences
2066                | TokenKind::KwGenerated
2067                | TokenKind::KwConstraint
2068                | TokenKind::KwAs
2069        )
2070    }
2071
2072    fn try_column_constraint(&mut self) -> Result<Option<ColumnConstraint>, ParseError> {
2073        let name = if self.eat_kw(&TokenKind::KwConstraint) {
2074            Some(self.parse_identifier()?)
2075        } else {
2076            None
2077        };
2078        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
2079            self.expect_kw(&TokenKind::KwKey)?;
2080            let direction = if self.eat_kw(&TokenKind::KwAsc) {
2081                Some(SortDirection::Asc)
2082            } else if self.eat_kw(&TokenKind::KwDesc) {
2083                Some(SortDirection::Desc)
2084            } else {
2085                None
2086            };
2087            let conflict = self.parse_on_conflict()?;
2088            let autoincrement = self.eat_kw(&TokenKind::KwAutoincrement);
2089            ColumnConstraintKind::PrimaryKey {
2090                direction,
2091                conflict,
2092                autoincrement,
2093            }
2094        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwNull {
2095            self.advance();
2096            self.advance();
2097            let conflict = self.parse_on_conflict()?;
2098            ColumnConstraintKind::NotNull { conflict }
2099        } else if self.eat_kw(&TokenKind::KwNull) {
2100            ColumnConstraintKind::Null
2101        } else if self.eat_kw(&TokenKind::KwUnique) {
2102            let conflict = self.parse_on_conflict()?;
2103            ColumnConstraintKind::Unique { conflict }
2104        } else if self.eat_kw(&TokenKind::KwCheck) {
2105            self.expect_token(&TokenKind::LeftParen)?;
2106            let expr = self.parse_expr()?;
2107            self.expect_token(&TokenKind::RightParen)?;
2108            ColumnConstraintKind::Check(expr)
2109        } else if self.eat_kw(&TokenKind::KwDefault) {
2110            if self.eat(&TokenKind::LeftParen) {
2111                let expr = self.parse_expr()?;
2112                self.expect_token(&TokenKind::RightParen)?;
2113                ColumnConstraintKind::Default(DefaultValue::ParenExpr(expr))
2114            } else if let TokenKind::Id(name) | TokenKind::QuotedId(name, _) = self.peek().clone() {
2115                // SQLite quirk: a BARE (unparenthesized) identifier after DEFAULT is
2116                // treated as a STRING LITERAL, not a column reference — a column
2117                // DEFAULT cannot reference other columns. Stock's grammar
2118                // (`ccons ::= DEFAULT scantok id`) takes any identifier token, so
2119                // `DEFAULT abc`, `DEFAULT "abc"`, `DEFAULT [abc]` and
2120                // ``DEFAULT `abc` `` all yield the string 'abc'; `DEFAULT (abc)`
2121                // stays an expression and is later rejected as non-constant.
2122                // Keyword constants (TRUE/FALSE/NULL/CURRENT_*) lex as keywords
2123                // (not Id), so they fall through to the normal expression parse
2124                // below and keep their literal meaning.
2125                let span = self.current_span();
2126                self.advance();
2127                ColumnConstraintKind::Default(DefaultValue::Expr(Expr::Literal(
2128                    Literal::String(name.to_string()),
2129                    span,
2130                )))
2131            } else {
2132                let expr = self.parse_expr()?;
2133                ColumnConstraintKind::Default(DefaultValue::Expr(expr))
2134            }
2135        } else if self.eat_kw(&TokenKind::KwCollate) {
2136            ColumnConstraintKind::Collate(self.parse_identifier()?)
2137        } else if self.eat_kw(&TokenKind::KwReferences) {
2138            ColumnConstraintKind::ForeignKey(self.parse_fk_clause()?)
2139        } else if self.eat_kw(&TokenKind::KwGenerated) || self.eat_kw(&TokenKind::KwAs) {
2140            if self.tokens[self.pos.saturating_sub(1)].kind == TokenKind::KwGenerated {
2141                let _ = self.eat_kw(&TokenKind::KwAlways);
2142                let _ = self.eat_kw(&TokenKind::KwAs);
2143            }
2144            self.expect_token(&TokenKind::LeftParen)?;
2145            let expr = self.parse_expr()?;
2146            self.expect_token(&TokenKind::RightParen)?;
2147            let storage = if self.eat_kw(&TokenKind::KwStored) {
2148                Some(GeneratedStorage::Stored)
2149            } else if self.eat_kw(&TokenKind::KwVirtual) {
2150                Some(GeneratedStorage::Virtual)
2151            } else {
2152                None
2153            };
2154            ColumnConstraintKind::Generated { expr, storage }
2155        } else if name.is_some() {
2156            return Err(self.err_expected("constraint kind after CONSTRAINT name"));
2157        } else {
2158            return Ok(None);
2159        };
2160        Ok(Some(ColumnConstraint { name, kind }))
2161    }
2162
2163    fn parse_on_conflict(&mut self) -> Result<Option<ConflictAction>, ParseError> {
2164        if self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
2165            self.advance();
2166            self.advance();
2167            Ok(Some(self.parse_conflict_action()?))
2168        } else {
2169            Ok(None)
2170        }
2171    }
2172
2173    fn parse_fk_clause(&mut self) -> Result<ForeignKeyClause, ParseError> {
2174        let table = self.parse_identifier()?;
2175        let columns = if self.eat(&TokenKind::LeftParen) {
2176            let cols = self.parse_comma_sep(Self::parse_identifier)?;
2177            self.expect_token(&TokenKind::RightParen)?;
2178            cols
2179        } else {
2180            vec![]
2181        };
2182        let mut actions = Vec::new();
2183        let mut deferrable = None;
2184        loop {
2185            if self.check_kw(&TokenKind::KwOn) {
2186                self.advance();
2187                let trigger = if self.eat_kw(&TokenKind::KwDelete) {
2188                    ForeignKeyTrigger::OnDelete
2189                } else {
2190                    self.expect_kw(&TokenKind::KwUpdate)?;
2191                    ForeignKeyTrigger::OnUpdate
2192                };
2193                let action = self.parse_fk_action_type()?;
2194                actions.push(ForeignKeyAction { trigger, action });
2195            } else if self.check_kw(&TokenKind::KwNot) || self.check_kw(&TokenKind::KwDeferrable) {
2196                let not = self.eat_kw(&TokenKind::KwNot);
2197                self.expect_kw(&TokenKind::KwDeferrable)?;
2198                let initially = if self.eat_kw(&TokenKind::KwInitially) {
2199                    if self.eat_kw(&TokenKind::KwDeferred) {
2200                        Some(DeferrableInitially::Deferred)
2201                    } else {
2202                        self.expect_kw(&TokenKind::KwImmediate)?;
2203                        Some(DeferrableInitially::Immediate)
2204                    }
2205                } else {
2206                    None
2207                };
2208                deferrable = Some(Deferrable { not, initially });
2209            } else if self.eat_kw(&TokenKind::KwMatch) {
2210                // MATCH name — parsed but ignored per SQLite behavior.
2211                self.parse_identifier()?;
2212            } else {
2213                break;
2214            }
2215        }
2216        Ok(ForeignKeyClause {
2217            table,
2218            columns,
2219            actions,
2220            deferrable,
2221        })
2222    }
2223
2224    fn parse_fk_action_type(&mut self) -> Result<ForeignKeyActionType, ParseError> {
2225        if self.eat_kw(&TokenKind::KwSet) {
2226            if self.eat_kw(&TokenKind::KwNull) {
2227                Ok(ForeignKeyActionType::SetNull)
2228            } else {
2229                self.expect_kw(&TokenKind::KwDefault)?;
2230                Ok(ForeignKeyActionType::SetDefault)
2231            }
2232        } else if self.eat_kw(&TokenKind::KwCascade) {
2233            Ok(ForeignKeyActionType::Cascade)
2234        } else if self.eat_kw(&TokenKind::KwRestrict) {
2235            Ok(ForeignKeyActionType::Restrict)
2236        } else if self.check_kw(&TokenKind::KwNo) {
2237            self.advance();
2238            let id = self.parse_identifier()?;
2239            if !id.eq_ignore_ascii_case("ACTION") {
2240                return Err(self.err_expected("ACTION after NO"));
2241            }
2242            Ok(ForeignKeyActionType::NoAction)
2243        } else {
2244            Err(self.err_expected("foreign key action"))
2245        }
2246    }
2247
2248    fn parse_table_constraint(&mut self) -> Result<TableConstraint, ParseError> {
2249        let name = if self.eat_kw(&TokenKind::KwConstraint) {
2250            Some(self.parse_identifier()?)
2251        } else {
2252            None
2253        };
2254        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
2255            self.expect_kw(&TokenKind::KwKey)?;
2256            self.expect_token(&TokenKind::LeftParen)?;
2257            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
2258            self.expect_token(&TokenKind::RightParen)?;
2259            let conflict = self.parse_on_conflict()?;
2260            TableConstraintKind::PrimaryKey { columns, conflict }
2261        } else if self.eat_kw(&TokenKind::KwUnique) {
2262            self.expect_token(&TokenKind::LeftParen)?;
2263            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
2264            self.expect_token(&TokenKind::RightParen)?;
2265            let conflict = self.parse_on_conflict()?;
2266            TableConstraintKind::Unique { columns, conflict }
2267        } else if self.eat_kw(&TokenKind::KwCheck) {
2268            self.expect_token(&TokenKind::LeftParen)?;
2269            let expr = self.parse_expr()?;
2270            self.expect_token(&TokenKind::RightParen)?;
2271            // A table CHECK constraint may carry a trailing conflict clause
2272            // (`CHECK(expr) ON CONFLICT <action>`), accepted by stock sqlite3.
2273            // Consume it for parse parity so a stock-created schema stays
2274            // loadable on reopen (mirrors PRIMARY KEY / UNIQUE above).
2275            let _conflict = self.parse_on_conflict()?;
2276            TableConstraintKind::Check(expr)
2277        } else if self.eat_kw(&TokenKind::KwForeign) {
2278            self.expect_kw(&TokenKind::KwKey)?;
2279            self.expect_token(&TokenKind::LeftParen)?;
2280            let columns = self.parse_comma_sep(Self::parse_identifier)?;
2281            self.expect_token(&TokenKind::RightParen)?;
2282            self.expect_kw(&TokenKind::KwReferences)?;
2283            let clause = self.parse_fk_clause()?;
2284            TableConstraintKind::ForeignKey { columns, clause }
2285        } else {
2286            return Err(self.err_expected("table constraint"));
2287        };
2288        Ok(TableConstraint { name, kind })
2289    }
2290
2291    fn parse_indexed_column(&mut self) -> Result<IndexedColumn, ParseError> {
2292        let expr = self.parse_expr()?;
2293        let collation = if self.eat_kw(&TokenKind::KwCollate) {
2294            Some(self.parse_identifier()?)
2295        } else {
2296            None
2297        };
2298        let direction = if self.eat_kw(&TokenKind::KwAsc) {
2299            Some(SortDirection::Asc)
2300        } else if self.eat_kw(&TokenKind::KwDesc) {
2301            Some(SortDirection::Desc)
2302        } else {
2303            None
2304        };
2305        Ok(IndexedColumn {
2306            expr,
2307            collation,
2308            direction,
2309        })
2310    }
2311
2312    fn parse_create_index(&mut self, unique: bool) -> Result<Statement, ParseError> {
2313        let if_not_exists = self.parse_if_not_exists();
2314        let name = self.parse_qualified_name()?;
2315        self.expect_kw(&TokenKind::KwOn)?;
2316        let table = self.parse_identifier()?;
2317        self.expect_token(&TokenKind::LeftParen)?;
2318        let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
2319        self.expect_token(&TokenKind::RightParen)?;
2320        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
2321            Some(self.parse_expr()?)
2322        } else {
2323            None
2324        };
2325        Ok(Statement::CreateIndex(CreateIndexStatement {
2326            unique,
2327            if_not_exists,
2328            name,
2329            table,
2330            columns,
2331            where_clause,
2332        }))
2333    }
2334
2335    fn parse_create_view(&mut self, temporary: bool) -> Result<Statement, ParseError> {
2336        let if_not_exists = self.parse_if_not_exists();
2337        let name = self.parse_qualified_name()?;
2338        let columns = if self.check(&TokenKind::LeftParen) {
2339            self.advance();
2340            let cols = self.parse_comma_sep(Self::parse_identifier)?;
2341            self.expect_token(&TokenKind::RightParen)?;
2342            cols
2343        } else {
2344            vec![]
2345        };
2346        self.expect_kw(&TokenKind::KwAs)?;
2347        let with = if self.check_kw(&TokenKind::KwWith) {
2348            Some(self.parse_with_clause()?)
2349        } else {
2350            None
2351        };
2352        let query = self.parse_select_stmt(with)?;
2353        Ok(Statement::CreateView(CreateViewStatement {
2354            if_not_exists,
2355            temporary,
2356            name,
2357            columns,
2358            query,
2359        }))
2360    }
2361
2362    fn parse_trigger_body_statement_inner(&mut self) -> Result<Statement, ParseError> {
2363        match self.peek().clone() {
2364            TokenKind::KwSelect | TokenKind::KwValues => {
2365                Ok(Statement::Select(self.parse_select_stmt(None)?))
2366            }
2367            TokenKind::KwWith => {
2368                // Stock SQLite (3.46.1, verified) only allows a WITH (CTE) clause
2369                // ahead of SELECT/VALUES in a trigger body; `WITH ... INSERT/
2370                // UPDATE/DELETE` is a syntax error ("near INSERT"). Keep parity.
2371                let with = self.parse_with_clause()?;
2372                if matches!(self.peek(), TokenKind::KwSelect | TokenKind::KwValues) {
2373                    Ok(Statement::Select(self.parse_select_stmt(Some(with))?))
2374                } else {
2375                    Err(self.err_expected("SELECT or VALUES after WITH in a trigger body"))
2376                }
2377            }
2378            TokenKind::KwInsert | TokenKind::KwReplace => {
2379                self.parse_insert_stmt(None, DmlParseContext::TriggerBody)
2380            }
2381            TokenKind::KwUpdate => self.parse_update_stmt(None, DmlParseContext::TriggerBody),
2382            TokenKind::KwDelete => self.parse_delete_stmt(None, DmlParseContext::TriggerBody),
2383            _ => Err(self.err_unexpected(
2384                "trigger body statement must be SELECT, INSERT, UPDATE, or DELETE",
2385            )),
2386        }
2387    }
2388
2389    fn parse_trigger_body_statement(&mut self) -> Result<Statement, ParseError> {
2390        self.parse_trigger_body_statement_inner()
2391    }
2392
2393    fn parse_create_trigger(&mut self, temporary: bool) -> Result<Statement, ParseError> {
2394        let if_not_exists = self.parse_if_not_exists();
2395        let name = self.parse_qualified_name()?;
2396        let timing = if self.eat_kw(&TokenKind::KwBefore) {
2397            TriggerTiming::Before
2398        } else if self.eat_kw(&TokenKind::KwAfter) {
2399            TriggerTiming::After
2400        } else if self.eat_kw(&TokenKind::KwInstead) {
2401            self.expect_kw(&TokenKind::KwOf)?;
2402            TriggerTiming::InsteadOf
2403        } else {
2404            TriggerTiming::Before // default
2405        };
2406        let event = if self.eat_kw(&TokenKind::KwInsert) {
2407            TriggerEvent::Insert
2408        } else if self.eat_kw(&TokenKind::KwDelete) {
2409            TriggerEvent::Delete
2410        } else {
2411            self.expect_kw(&TokenKind::KwUpdate)?;
2412            let cols = if self.eat_kw(&TokenKind::KwOf) {
2413                self.parse_comma_sep(Self::parse_identifier)?
2414            } else {
2415                vec![]
2416            };
2417            TriggerEvent::Update(cols)
2418        };
2419        self.expect_kw(&TokenKind::KwOn)?;
2420        let table = self.parse_identifier()?;
2421        let for_each_row = if self.eat_kw(&TokenKind::KwFor) {
2422            self.expect_kw(&TokenKind::KwEach)?;
2423            self.expect_kw(&TokenKind::KwRow)?;
2424            true
2425        } else {
2426            false
2427        };
2428        let when = if self.eat_kw(&TokenKind::KwWhen) {
2429            Some(self.parse_expr()?)
2430        } else {
2431            None
2432        };
2433        self.expect_kw(&TokenKind::KwBegin)?;
2434        let mut body = Vec::new();
2435        if self.check_kw(&TokenKind::KwEnd) {
2436            let error = self.err_unexpected("trigger body must contain at least one statement");
2437            self.recover_trigger_body_after_error(self.pos);
2438            return Err(error);
2439        }
2440        loop {
2441            if self.check_kw(&TokenKind::KwEnd) {
2442                break;
2443            }
2444            let statement_start = self.pos;
2445            let stmt = match self.parse_trigger_body_statement() {
2446                Ok(stmt) => stmt,
2447                Err(err) => {
2448                    self.recover_trigger_body_after_error(statement_start);
2449                    return Err(err);
2450                }
2451            };
2452            body.push(stmt);
2453            if !self.eat(&TokenKind::Semicolon) {
2454                let error = self.err_expected("';' after trigger body statement");
2455                // The statement parsed successfully, so the current token is
2456                // already a statement boundary. In particular, UPDATE/INSERT/
2457                // DELETE leave the enclosing trigger END current here.
2458                self.recover_trigger_body_after_error(self.pos);
2459                return Err(error);
2460            }
2461        }
2462        self.expect_kw(&TokenKind::KwEnd)?;
2463        Ok(Statement::CreateTrigger(CreateTriggerStatement {
2464            if_not_exists,
2465            temporary,
2466            name,
2467            timing,
2468            event,
2469            table,
2470            for_each_row,
2471            when,
2472            body,
2473        }))
2474    }
2475
2476    fn parse_create_virtual_table(&mut self) -> Result<Statement, ParseError> {
2477        let if_not_exists = self.parse_if_not_exists();
2478        let name = self.parse_qualified_name()?;
2479        self.expect_kw(&TokenKind::KwUsing)?;
2480        let module = self.parse_identifier()?;
2481        let args = if self.eat(&TokenKind::LeftParen) {
2482            if self.check(&TokenKind::RightParen) {
2483                self.advance();
2484                vec![]
2485            } else {
2486                // Virtual table args are opaque; collect tokens as strings until matching rparen.
2487                let mut args = Vec::new();
2488                let mut depth = 0i32;
2489                let mut current_arg = String::new();
2490                loop {
2491                    match self.peek() {
2492                        TokenKind::RightParen if depth == 0 => {
2493                            self.advance();
2494                            args.push(current_arg.trim().to_owned());
2495                            break;
2496                        }
2497                        TokenKind::LeftParen => {
2498                            depth += 1;
2499                            current_arg.push('(');
2500                            self.advance();
2501                        }
2502                        TokenKind::RightParen => {
2503                            depth -= 1;
2504                            current_arg.push(')');
2505                            self.advance();
2506                        }
2507                        TokenKind::Comma if depth == 0 => {
2508                            args.push(current_arg.trim().to_owned());
2509                            current_arg = String::new();
2510                            self.advance();
2511                        }
2512                        TokenKind::Eof => {
2513                            return Err(self.err_expected("closing parenthesis"));
2514                        }
2515                        _ => {
2516                            // Reconstruct token text from token kind.
2517                            let t = self
2518                                .current()
2519                                .ok_or_else(|| self.err_expected("virtual table argument token"))?;
2520                            let text = t.kind.to_sql();
2521                            if !current_arg.is_empty()
2522                                && !current_arg.ends_with(' ')
2523                                && !text.is_empty()
2524                            {
2525                                current_arg.push(' ');
2526                            }
2527                            current_arg.push_str(&text);
2528                            self.advance();
2529                        }
2530                    }
2531                }
2532                args
2533            }
2534        } else {
2535            vec![]
2536        };
2537        Ok(Statement::CreateVirtualTable(CreateVirtualTableStatement {
2538            if_not_exists,
2539            name,
2540            module,
2541            args,
2542        }))
2543    }
2544
2545    // -----------------------------------------------------------------------
2546    // DROP
2547    // -----------------------------------------------------------------------
2548
2549    fn parse_drop(&mut self) -> Result<Statement, ParseError> {
2550        self.expect_kw(&TokenKind::KwDrop)?;
2551        let object_type = if self.eat_kw(&TokenKind::KwTable) {
2552            DropObjectType::Table
2553        } else if self.eat_kw(&TokenKind::KwView) {
2554            DropObjectType::View
2555        } else if self.eat_kw(&TokenKind::KwIndex) {
2556            DropObjectType::Index
2557        } else if self.eat_kw(&TokenKind::KwTrigger) {
2558            DropObjectType::Trigger
2559        } else {
2560            return Err(self.err_expected("TABLE, VIEW, INDEX, or TRIGGER"));
2561        };
2562        let if_exists =
2563            if self.check_kw(&TokenKind::KwIf) && self.peek_nth(1) == &TokenKind::KwExists {
2564                self.advance();
2565                self.advance();
2566                true
2567            } else {
2568                false
2569            };
2570        let name = self.parse_qualified_name()?;
2571        Ok(Statement::Drop(DropStatement {
2572            object_type,
2573            if_exists,
2574            name,
2575        }))
2576    }
2577
2578    // -----------------------------------------------------------------------
2579    // ALTER TABLE
2580    // -----------------------------------------------------------------------
2581
2582    fn parse_alter(&mut self) -> Result<Statement, ParseError> {
2583        self.expect_kw(&TokenKind::KwAlter)?;
2584        self.expect_kw(&TokenKind::KwTable)?;
2585        let table = self.parse_qualified_name()?;
2586        let action = if self.eat_kw(&TokenKind::KwRename) {
2587            if self.eat_kw(&TokenKind::KwTo) {
2588                AlterTableAction::RenameTo(self.parse_identifier()?)
2589            } else {
2590                let _ = self.eat_kw(&TokenKind::KwColumn);
2591                let old = self.parse_identifier()?;
2592                self.expect_kw(&TokenKind::KwTo)?;
2593                let new = self.parse_identifier()?;
2594                AlterTableAction::RenameColumn { old, new }
2595            }
2596        } else if self.eat_kw(&TokenKind::KwAdd) {
2597            let _ = self.eat_kw(&TokenKind::KwColumn);
2598            AlterTableAction::AddColumn(self.parse_column_def()?)
2599        } else if self.eat_kw(&TokenKind::KwDrop) {
2600            let _ = self.eat_kw(&TokenKind::KwColumn);
2601            AlterTableAction::DropColumn(self.parse_identifier()?)
2602        } else {
2603            return Err(self.err_expected("RENAME, ADD, or DROP"));
2604        };
2605        Ok(Statement::AlterTable(AlterTableStatement { table, action }))
2606    }
2607
2608    // -----------------------------------------------------------------------
2609    // Transaction control
2610    // -----------------------------------------------------------------------
2611
2612    fn parse_begin(&mut self) -> Result<Statement, ParseError> {
2613        self.expect_kw(&TokenKind::KwBegin)?;
2614        let mode = if self.eat_kw(&TokenKind::KwDeferred) {
2615            Some(TransactionMode::Deferred)
2616        } else if self.eat_kw(&TokenKind::KwImmediate) {
2617            Some(TransactionMode::Immediate)
2618        } else if self.eat_kw(&TokenKind::KwExclusive) {
2619            Some(TransactionMode::Exclusive)
2620        } else if self.eat_kw(&TokenKind::KwConcurrent) {
2621            Some(TransactionMode::Concurrent)
2622        } else {
2623            None
2624        };
2625        // Optional TRANSACTION keyword.
2626        let _ = self.eat_kw(&TokenKind::KwTransaction);
2627        Ok(Statement::Begin(BeginStatement { mode }))
2628    }
2629
2630    fn parse_rollback(&mut self) -> Result<Statement, ParseError> {
2631        self.expect_kw(&TokenKind::KwRollback)?;
2632        let _ = self.eat_kw(&TokenKind::KwTransaction);
2633        let to_savepoint = if self.eat_kw(&TokenKind::KwTo) {
2634            let _ = self.eat_kw(&TokenKind::KwSavepoint);
2635            Some(self.parse_identifier()?)
2636        } else {
2637            None
2638        };
2639        Ok(Statement::Rollback(RollbackStatement { to_savepoint }))
2640    }
2641
2642    // -----------------------------------------------------------------------
2643    // ATTACH / PRAGMA / VACUUM / EXPLAIN
2644    // -----------------------------------------------------------------------
2645
2646    fn parse_attach(&mut self) -> Result<Statement, ParseError> {
2647        self.expect_kw(&TokenKind::KwAttach)?;
2648        let _ = self.eat_kw(&TokenKind::KwDatabase);
2649        let expr = self.parse_expr()?;
2650        self.expect_kw(&TokenKind::KwAs)?;
2651        let schema = self.parse_identifier()?;
2652        Ok(Statement::Attach(AttachStatement { expr, schema }))
2653    }
2654
2655    fn parse_pragma_value_expr(
2656        &mut self,
2657        preserve_numeric_spelling: bool,
2658    ) -> Result<Expr, ParseError> {
2659        if preserve_numeric_spelling && let Some(sql) = &self.pragma_source_sql {
2660            let signed = matches!(self.peek(), TokenKind::Plus | TokenKind::Minus);
2661            let offset = usize::from(signed);
2662            if matches!(
2663                self.peek_nth(offset),
2664                TokenKind::Integer(_) | TokenKind::OversizedInt(_) | TokenKind::Float(_)
2665            ) {
2666                let token = &self.tokens[self.pos + offset];
2667                let raw = &sql[token.span.start as usize..token.span.end as usize];
2668                if raw.contains('_') {
2669                    let mut error = ParseError::at(
2670                        "numeric separators are not valid PRAGMA arguments",
2671                        Some(token),
2672                    );
2673                    error.kind = ParseErrorKind::UnexpectedToken;
2674                    return Err(error);
2675                }
2676                // SQLite's plus_num grammar drops unary '+', but its minus_num
2677                // grammar prepends '-'. Whitespace/comments between sign and
2678                // number are not part of the numeric argument.
2679                let value = if matches!(self.peek(), TokenKind::Minus) {
2680                    format!("-{raw}")
2681                } else {
2682                    raw.to_owned()
2683                };
2684                let span = Span::new(self.current_span().start, token.span.end);
2685                if signed {
2686                    self.advance();
2687                }
2688                self.advance();
2689                return Ok(Expr::Literal(Literal::String(value), span));
2690            }
2691        }
2692        // SQLite allows ON/OFF for many boolean pragmas. Treat `ON` as `TRUE`
2693        // in PRAGMA value position (OFF is tokenized as an identifier, so the
2694        // regular expression parser handles it).
2695        if self.check_kw(&TokenKind::KwOn) {
2696            let sp = self.current_span();
2697            self.advance();
2698            return Ok(Expr::Literal(Literal::True, sp));
2699        }
2700        // SQLite's pragma value grammar (nmnum ::= plus_num | nm | ON | DELETE |
2701        // DEFAULT) treats the reserved keywords DELETE and DEFAULT as names in
2702        // value position — e.g. `PRAGMA journal_mode=DELETE`, `PRAGMA
2703        // temp_store=DEFAULT`. The general expression parser rejects reserved
2704        // keyword tokens, so accept them here as identifier-valued pragma
2705        // arguments (matching how WAL/TRUNCATE/PERSIST/MEMORY/OFF lex as plain
2706        // identifiers and already work).
2707        let pragma_value_keyword = match self.peek() {
2708            TokenKind::KwDelete => Some("delete"),
2709            TokenKind::KwDefault => Some("default"),
2710            _ => None,
2711        };
2712        if let Some(name) = pragma_value_keyword {
2713            let sp = self.current_span();
2714            self.advance();
2715            return Ok(Expr::Column(ColumnRef::bare(name), sp));
2716        }
2717        self.parse_expr()
2718    }
2719
2720    fn parse_pragma(&mut self) -> Result<Statement, ParseError> {
2721        self.expect_kw(&TokenKind::KwPragma)?;
2722        let name = self.parse_qualified_name()?;
2723        let preserve_numeric_spelling = name.name.eq_ignore_ascii_case("cache_size")
2724            || name.name.eq_ignore_ascii_case("default_cache_size");
2725        let value = if self.eat(&TokenKind::Eq) || self.eat(&TokenKind::EqEq) {
2726            Some(PragmaValue::Assign(
2727                self.parse_pragma_value_expr(preserve_numeric_spelling)?,
2728            ))
2729        } else if self.eat(&TokenKind::LeftParen) {
2730            let v = self.parse_pragma_value_expr(preserve_numeric_spelling)?;
2731            self.expect_token(&TokenKind::RightParen)?;
2732            Some(PragmaValue::Call(v))
2733        } else {
2734            None
2735        };
2736        Ok(Statement::Pragma(PragmaStatement { name, value }))
2737    }
2738
2739    fn parse_vacuum(&mut self) -> Result<Statement, ParseError> {
2740        self.expect_kw(&TokenKind::KwVacuum)?;
2741        let schema = if !self.at_eof()
2742            && !self.check(&TokenKind::Semicolon)
2743            && !self.check_kw(&TokenKind::KwInto)
2744        {
2745            Some(self.parse_identifier()?)
2746        } else {
2747            None
2748        };
2749        let into = if self.eat_kw(&TokenKind::KwInto) {
2750            Some(self.parse_expr()?)
2751        } else {
2752            None
2753        };
2754        Ok(Statement::Vacuum(VacuumStatement { schema, into }))
2755    }
2756
2757    fn parse_explain(&mut self) -> Result<Statement, ParseError> {
2758        self.expect_kw(&TokenKind::KwExplain)?;
2759        let query_plan = if self.eat_kw(&TokenKind::KwQuery) {
2760            self.expect_kw(&TokenKind::KwPlan)?;
2761            true
2762        } else {
2763            false
2764        };
2765        if self.check_kw(&TokenKind::KwExplain) {
2766            return Err(self.err_unexpected("nested EXPLAIN is not allowed"));
2767        }
2768        let stmt = self.parse_statement_inner()?;
2769        Ok(Statement::Explain {
2770            query_plan,
2771            stmt: Box::new(stmt),
2772        })
2773    }
2774
2775    // -----------------------------------------------------------------------
2776    // Window definitions (used in SELECT ... WINDOW clause and OVER)
2777    // -----------------------------------------------------------------------
2778
2779    #[cfg(test)]
2780    fn parse_window_def(&mut self) -> Result<WindowDef, ParseError> {
2781        let name = self.parse_window_name()?;
2782        self.expect_kw(&TokenKind::KwAs)?;
2783        self.expect_token(&TokenKind::LeftParen)?;
2784        let spec = self.parse_window_spec()?;
2785        self.expect_token(&TokenKind::RightParen)?;
2786        Ok(WindowDef { name, spec })
2787    }
2788
2789    #[cfg(test)]
2790    pub(crate) fn parse_window_spec(&mut self) -> Result<WindowSpec, ParseError> {
2791        // Optional base window name.
2792        let has_base_window = starts_window_base_name(self.peek());
2793        let base_window = if has_base_window {
2794            Some(self.parse_window_name()?)
2795        } else {
2796            None
2797        };
2798        let partition_by = if self.eat_kw(&TokenKind::KwPartition) {
2799            self.expect_kw(&TokenKind::KwBy)?;
2800            self.parse_comma_sep(Self::parse_expr)?
2801        } else {
2802            vec![]
2803        };
2804        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
2805            self.expect_kw(&TokenKind::KwBy)?;
2806            self.parse_comma_sep(Self::parse_ordering_term)?
2807        } else {
2808            vec![]
2809        };
2810        let frame = self.try_frame_spec()?;
2811        Ok(WindowSpec {
2812            window_ref: base_window.map(WindowReference::Base),
2813            partition_by,
2814            order_by,
2815            frame,
2816        })
2817    }
2818
2819    #[cfg(test)]
2820    fn try_frame_spec(&mut self) -> Result<Option<FrameSpec>, ParseError> {
2821        let frame_type = if self.eat_kw(&TokenKind::KwRows) {
2822            FrameType::Rows
2823        } else if self.eat_kw(&TokenKind::KwRange) {
2824            FrameType::Range
2825        } else if self.eat_kw(&TokenKind::KwGroups) {
2826            FrameType::Groups
2827        } else {
2828            return Ok(None);
2829        };
2830        let (start, end) = if self.eat_kw(&TokenKind::KwBetween) {
2831            let start = self.parse_frame_bound()?;
2832            validate_frame_start(&start, true)?;
2833            self.expect_kw(&TokenKind::KwAnd)?;
2834            let end = self.parse_frame_bound()?;
2835            validate_frame_end(&start, &end)?;
2836            (start, Some(end))
2837        } else {
2838            let start = self.parse_frame_bound()?;
2839            validate_frame_start(&start, false)?;
2840            (start, None)
2841        };
2842        let exclude = if self.eat_kw(&TokenKind::KwExclude) {
2843            if self.check_kw(&TokenKind::KwNo) {
2844                self.advance();
2845                // "NO OTHERS"
2846                let id = self.parse_identifier()?;
2847                if !id.eq_ignore_ascii_case("OTHERS") {
2848                    return Err(self.err_expected("OTHERS"));
2849                }
2850                Some(FrameExclude::NoOthers)
2851            } else if self.eat_kw(&TokenKind::KwTies) {
2852                Some(FrameExclude::Ties)
2853            } else if self.eat_kw(&TokenKind::KwGroup) {
2854                Some(FrameExclude::Group)
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                Some(FrameExclude::CurrentRow)
2859            } else {
2860                return Err(
2861                    self.err_expected("NO OTHERS, TIES, GROUP, or CURRENT ROW after EXCLUDE")
2862                );
2863            }
2864        } else {
2865            None
2866        };
2867        Ok(Some(FrameSpec {
2868            frame_type,
2869            start: start.value,
2870            end: end.map(|bound| bound.value),
2871            exclude,
2872        }))
2873    }
2874
2875    #[cfg(test)]
2876    fn parse_frame_bound(&mut self) -> Result<ParsedFrameBound, ParseError> {
2877        let origin = self
2878            .current()
2879            .cloned()
2880            .ok_or_else(|| self.err_expected("window frame bound"))?;
2881        let value = if self.eat_kw(&TokenKind::KwUnbounded) {
2882            if self.eat_kw(&TokenKind::KwPreceding) {
2883                FrameBound::UnboundedPreceding
2884            } else {
2885                self.expect_kw(&TokenKind::KwFollowing)?;
2886                FrameBound::UnboundedFollowing
2887            }
2888        } else if matches!(self.peek(), TokenKind::Id(s) if s.eq_ignore_ascii_case("CURRENT")) {
2889            self.advance();
2890            self.expect_kw(&TokenKind::KwRow)?;
2891            FrameBound::CurrentRow
2892        } else {
2893            let expr = self.parse_expr()?;
2894            if self.eat_kw(&TokenKind::KwPreceding) {
2895                FrameBound::Preceding(Box::new(expr))
2896            } else {
2897                self.expect_kw(&TokenKind::KwFollowing)?;
2898                FrameBound::Following(Box::new(expr))
2899            }
2900        };
2901        Ok(ParsedFrameBound { value, origin })
2902    }
2903}
2904
2905fn parse_statements_with_scratch_inner(
2906    sql: &str,
2907    scratch: &mut StatementParseScratch,
2908) -> (Vec<Statement>, Option<ParseError>) {
2909    Lexer::tokenize_into_with_interner(sql, &mut scratch.tokens, &mut scratch.identifier_interner);
2910    let mut parser = Parser {
2911        tokens: std::mem::take(&mut scratch.tokens),
2912        pragma_source_sql: None,
2913        pos: 0,
2914        errors: std::mem::take(&mut scratch.errors),
2915        depth: 0,
2916        has_with: false,
2917    };
2918    parser.preserve_pragma_source(sql);
2919    let (statements, errors) = parser.parse_all();
2920    scratch.tokens = parser.tokens;
2921    scratch.tokens.clear();
2922    scratch.identifier_interner.prepare_for_next_parse();
2923    scratch.errors = errors;
2924    let first_error = scratch.errors.first().cloned();
2925    scratch.errors.clear();
2926    (statements, first_error)
2927}
2928
2929/// Parse all statements from `sql` using caller-owned token/error lookaside.
2930///
2931/// Semantically-empty input (empty string, whitespace, comments, or bare
2932/// `;` separators) parses to an empty statement list, matching stock SQLite:
2933/// `sqlite3_prepare_v2` of such input returns `SQLITE_OK` with a NULL
2934/// statement — a no-op, never a parse error. The single-statement callers
2935/// below still reject an empty list on their own where exactly one statement
2936/// is required. bd-parser-errfmt-residuals-3giu1.
2937pub fn parse_statements_with_scratch(
2938    sql: &str,
2939    scratch: &mut StatementParseScratch,
2940) -> Result<Vec<Statement>, ParseError> {
2941    let (statements, first_error) = parse_statements_with_scratch_inner(sql, scratch);
2942    if let Some(error) = first_error {
2943        return Err(error);
2944    }
2945    Ok(statements)
2946}
2947
2948/// Parse exactly one statement from `sql` using caller-owned lookaside.
2949pub fn parse_single_statement_with_scratch(
2950    sql: &str,
2951    scratch: &mut StatementParseScratch,
2952) -> Result<Statement, ParseError> {
2953    let statements = parse_statements_with_scratch(sql, scratch)?;
2954    let mut iter = statements.into_iter();
2955    let statement = iter
2956        .next()
2957        .ok_or_else(|| ParseError::at("no SQL statement provided", None))?;
2958    if iter.next().is_some() {
2959        return Err(ParseError::at(
2960            "multiple statements are not supported in this API path",
2961            None,
2962        ));
2963    }
2964    Ok(statement)
2965}
2966
2967/// Parse only the first top-level SQL statement from `sql` and report the byte
2968/// offset immediately after the consumed statement text.
2969///
2970/// Returns `Ok(None)` when the input contains no statement text (for example
2971/// whitespace, comments, or empty statements only). The returned tail offset is
2972/// suitable for `sqlite3_prepare_v2`-style APIs that must leave any remaining
2973/// SQL untouched.
2974pub fn parse_first_statement_with_tail(
2975    sql: &str,
2976) -> Result<Option<(Statement, usize)>, ParseError> {
2977    Parser::from_sql(sql).parse_next_statement_with_tail()
2978}
2979
2980// ---------------------------------------------------------------------------
2981// Keyword classification helper
2982// ---------------------------------------------------------------------------
2983
2984/// Whether `OVER` can consume the next token as a bare window name.
2985///
2986/// SQLite's fallback-name grammar is contextual here: it accepts several hard
2987/// keywords that the generic identifier parser rejects, while `FILTER`,
2988/// `NOTHING`, and `TRANSACTION` remain structural/reserved in this position.
2989pub(crate) fn starts_bare_window_name(k: &TokenKind) -> bool {
2990    matches!(
2991        k,
2992        TokenKind::Id(_) | TokenKind::QuotedId(_, _) | TokenKind::String(_)
2993    ) || (is_nonreserved_kw(k) && !matches!(k, TokenKind::KwFilter))
2994        || matches!(
2995            k,
2996            TokenKind::KwAttach
2997                | TokenKind::KwBegin
2998                | TokenKind::KwBy
2999                | TokenKind::KwCast
3000                | TokenKind::KwCurrentDate
3001                | TokenKind::KwCurrentTime
3002                | TokenKind::KwCurrentTimestamp
3003                | TokenKind::KwCross
3004                | TokenKind::KwDetach
3005                | TokenKind::KwExplain
3006                | TokenKind::KwFalse
3007                | TokenKind::KwFor
3008                | TokenKind::KwGlob
3009                | TokenKind::KwInner
3010                | TokenKind::KwLeft
3011                | TokenKind::KwLike
3012                | TokenKind::KwNatural
3013                | TokenKind::KwOuter
3014                | TokenKind::KwRaise
3015                | TokenKind::KwRegexp
3016                | TokenKind::KwRight
3017                | TokenKind::KwRollback
3018                | TokenKind::KwTrue
3019                | TokenKind::KwWith
3020        )
3021}
3022
3023/// Whether a token can begin the optional base name inside `OVER (...)`.
3024///
3025/// This uses the same contextual fallback set as a bare name, minus the five
3026/// structural window-spec delimiters.
3027pub(crate) fn starts_window_base_name(k: &TokenKind) -> bool {
3028    starts_bare_window_name(k)
3029        && !matches!(
3030            k,
3031            TokenKind::KwPartition
3032                | TokenKind::KwOrder
3033                | TokenKind::KwRange
3034                | TokenKind::KwRows
3035                | TokenKind::KwGroups
3036        )
3037}
3038
3039pub(crate) fn starts_post_dot_identifier(k: &TokenKind) -> bool {
3040    matches!(
3041        k,
3042        TokenKind::Id(_) | TokenKind::QuotedId(_, _) | TokenKind::String(_)
3043    ) || (k.keyword_str().is_some()
3044        && !matches!(
3045            k,
3046            TokenKind::KwAdd
3047                | TokenKind::KwAll
3048                | TokenKind::KwAlter
3049                | TokenKind::KwAnd
3050                | TokenKind::KwAs
3051                | TokenKind::KwAutoincrement
3052                | TokenKind::KwBetween
3053                | TokenKind::KwCase
3054                | TokenKind::KwCheck
3055                | TokenKind::KwCollate
3056                | TokenKind::KwCommit
3057                | TokenKind::KwConstraint
3058                | TokenKind::KwCreate
3059                | TokenKind::KwDefault
3060                | TokenKind::KwDeferrable
3061                | TokenKind::KwDelete
3062                | TokenKind::KwDistinct
3063                | TokenKind::KwDrop
3064                | TokenKind::KwElse
3065                | TokenKind::KwEscape
3066                | TokenKind::KwExcept
3067                | TokenKind::KwExists
3068                | TokenKind::KwForeign
3069                | TokenKind::KwFrom
3070                | TokenKind::KwGroup
3071                | TokenKind::KwHaving
3072                | TokenKind::KwIn
3073                | TokenKind::KwIndex
3074                | TokenKind::KwInsert
3075                | TokenKind::KwIntersect
3076                | TokenKind::KwInto
3077                | TokenKind::KwIs
3078                | TokenKind::KwIsnull
3079                | TokenKind::KwJoin
3080                | TokenKind::KwLimit
3081                | TokenKind::KwNot
3082                | TokenKind::KwNothing
3083                | TokenKind::KwNotnull
3084                | TokenKind::KwNull
3085                | TokenKind::KwOn
3086                | TokenKind::KwOr
3087                | TokenKind::KwOrder
3088                | TokenKind::KwPrimary
3089                | TokenKind::KwReferences
3090                | TokenKind::KwReturning
3091                | TokenKind::KwSelect
3092                | TokenKind::KwSet
3093                | TokenKind::KwTable
3094                | TokenKind::KwThen
3095                | TokenKind::KwTo
3096                | TokenKind::KwTransaction
3097                | TokenKind::KwUnion
3098                | TokenKind::KwUnique
3099                | TokenKind::KwUpdate
3100                | TokenKind::KwUsing
3101                | TokenKind::KwValues
3102                | TokenKind::KwWhen
3103                | TokenKind::KwWhere
3104        ))
3105}
3106
3107pub(crate) fn starts_table_star_qualifier(k: &TokenKind) -> bool {
3108    matches!(
3109        k,
3110        TokenKind::Id(_)
3111            | TokenKind::QuotedId(_, _)
3112            | TokenKind::String(_)
3113            | TokenKind::KwAbort
3114            | TokenKind::KwAction
3115            | TokenKind::KwAfter
3116            | TokenKind::KwAlways
3117            | TokenKind::KwAnalyze
3118            | TokenKind::KwAsc
3119            | TokenKind::KwAttach
3120            | TokenKind::KwBefore
3121            | TokenKind::KwBegin
3122            | TokenKind::KwBy
3123            | TokenKind::KwCascade
3124            | TokenKind::KwColumn
3125            | TokenKind::KwCommitseq
3126            | TokenKind::KwConcurrent
3127            | TokenKind::KwConflict
3128            | TokenKind::KwCross
3129            | TokenKind::KwDatabase
3130            | TokenKind::KwDeferred
3131            | TokenKind::KwDesc
3132            | TokenKind::KwDetach
3133            | TokenKind::KwDo
3134            | TokenKind::KwEach
3135            | TokenKind::KwEnd
3136            | TokenKind::KwExclude
3137            | TokenKind::KwExclusive
3138            | TokenKind::KwExplain
3139            | TokenKind::KwFail
3140            | TokenKind::KwFalse
3141            | TokenKind::KwFilter
3142            | TokenKind::KwFirst
3143            | TokenKind::KwFollowing
3144            | TokenKind::KwFor
3145            | TokenKind::KwFull
3146            | TokenKind::KwGenerated
3147            | TokenKind::KwGlob
3148            | TokenKind::KwGroups
3149            | TokenKind::KwIf
3150            | TokenKind::KwIgnore
3151            | TokenKind::KwImmediate
3152            | TokenKind::KwIndexed
3153            | TokenKind::KwInitially
3154            | TokenKind::KwInner
3155            | TokenKind::KwInstead
3156            | TokenKind::KwKey
3157            | TokenKind::KwLast
3158            | TokenKind::KwLeft
3159            | TokenKind::KwLike
3160            | TokenKind::KwMatch
3161            | TokenKind::KwMaterialized
3162            | TokenKind::KwNatural
3163            | TokenKind::KwNo
3164            | TokenKind::KwNulls
3165            | TokenKind::KwOf
3166            | TokenKind::KwOffset
3167            | TokenKind::KwOthers
3168            | TokenKind::KwOuter
3169            | TokenKind::KwOver
3170            | TokenKind::KwPartition
3171            | TokenKind::KwPlan
3172            | TokenKind::KwPragma
3173            | TokenKind::KwPreceding
3174            | TokenKind::KwQuery
3175            | TokenKind::KwRange
3176            | TokenKind::KwRecursive
3177            | TokenKind::KwRegexp
3178            | TokenKind::KwReindex
3179            | TokenKind::KwRelease
3180            | TokenKind::KwRename
3181            | TokenKind::KwReplace
3182            | TokenKind::KwRestrict
3183            | TokenKind::KwRight
3184            | TokenKind::KwRollback
3185            | TokenKind::KwRow
3186            | TokenKind::KwRows
3187            | TokenKind::KwSavepoint
3188            | TokenKind::KwStored
3189            | TokenKind::KwStrict
3190            | TokenKind::KwTemp
3191            | TokenKind::KwTemporary
3192            | TokenKind::KwTies
3193            | TokenKind::KwTrigger
3194            | TokenKind::KwTrue
3195            | TokenKind::KwUnbounded
3196            | TokenKind::KwVacuum
3197            | TokenKind::KwView
3198            | TokenKind::KwVirtual
3199            | TokenKind::KwWindow
3200            | TokenKind::KwWith
3201            | TokenKind::KwWithout
3202    )
3203}
3204
3205pub(crate) fn is_nonreserved_kw(k: &TokenKind) -> bool {
3206    matches!(
3207        k,
3208        TokenKind::KwAbort
3209            | TokenKind::KwAction
3210            | TokenKind::KwAfter
3211            | TokenKind::KwAlways
3212            | TokenKind::KwAnalyze
3213            | TokenKind::KwAsc
3214            | TokenKind::KwBefore
3215            | TokenKind::KwCascade
3216            | TokenKind::KwColumn
3217            | TokenKind::KwConcurrent
3218            | TokenKind::KwConflict
3219            | TokenKind::KwDatabase
3220            | TokenKind::KwDeferred
3221            | TokenKind::KwDesc
3222            | TokenKind::KwDo
3223            | TokenKind::KwEach
3224            | TokenKind::KwEnd
3225            | TokenKind::KwExclude
3226            | TokenKind::KwExclusive
3227            | TokenKind::KwFail
3228            | TokenKind::KwFilter
3229            | TokenKind::KwFirst
3230            | TokenKind::KwFollowing
3231            | TokenKind::KwFull
3232            | TokenKind::KwGenerated
3233            | TokenKind::KwGroups
3234            | TokenKind::KwIf
3235            | TokenKind::KwIgnore
3236            | TokenKind::KwImmediate
3237            | TokenKind::KwInitially
3238            | TokenKind::KwInstead
3239            | TokenKind::KwKey
3240            | TokenKind::KwLast
3241            | TokenKind::KwMatch
3242            | TokenKind::KwMaterialized
3243            | TokenKind::KwNo
3244            | TokenKind::KwNulls
3245            | TokenKind::KwOf
3246            | TokenKind::KwOffset
3247            | TokenKind::KwOthers
3248            | TokenKind::KwOver
3249            | TokenKind::KwPartition
3250            | TokenKind::KwPlan
3251            | TokenKind::KwPragma
3252            | TokenKind::KwPreceding
3253            | TokenKind::KwQuery
3254            | TokenKind::KwRange
3255            | TokenKind::KwRecursive
3256            | TokenKind::KwReindex
3257            | TokenKind::KwRelease
3258            | TokenKind::KwRename
3259            | TokenKind::KwReplace
3260            | TokenKind::KwRestrict
3261            | TokenKind::KwRow
3262            | TokenKind::KwRows
3263            | TokenKind::KwSavepoint
3264            | TokenKind::KwStored
3265            | TokenKind::KwStrict
3266            | TokenKind::KwTemp
3267            | TokenKind::KwTemporary
3268            | TokenKind::KwTies
3269            | TokenKind::KwTrigger
3270            | TokenKind::KwUnbounded
3271            | TokenKind::KwVacuum
3272            | TokenKind::KwView
3273            | TokenKind::KwVirtual
3274            | TokenKind::KwWindow
3275            | TokenKind::KwWithout
3276    )
3277}
3278
3279/// Whether a token can be used as an alias after an explicit `AS`.
3280///
3281/// SQLite's fallback-name grammar accepts more hard keywords here than the
3282/// old generic identifier grammar, while operator-shaped and structural
3283/// keywords remain reserved.
3284fn starts_explicit_alias_name(k: &TokenKind) -> bool {
3285    starts_post_dot_identifier(k)
3286}
3287
3288/// Whether a token can be consumed as an implicit result-column alias.
3289///
3290/// Pattern-matching tokens remain expression operators in this position.
3291/// `WINDOW` is admitted here only after `Parser::starts_window_clause` has
3292/// ruled out an actual named-window clause.
3293fn starts_result_alias(k: &TokenKind) -> bool {
3294    starts_explicit_alias_name(k)
3295        && !matches!(
3296            k,
3297            TokenKind::KwCross
3298                | TokenKind::KwFull
3299                | TokenKind::KwGlob
3300                | TokenKind::KwIndexed
3301                | TokenKind::KwInner
3302                | TokenKind::KwLeft
3303                | TokenKind::KwLike
3304                | TokenKind::KwMatch
3305                | TokenKind::KwNatural
3306                | TokenKind::KwOuter
3307                | TokenKind::KwRegexp
3308                | TokenKind::KwRight
3309        )
3310}
3311
3312/// Whether a token can be consumed as an implicit table/source alias.
3313///
3314/// Join prefixes, `INDEXED`, and FrankenSQLite's `FOR SYSTEM_TIME` introducer
3315/// must remain available to the surrounding FROM grammar. Unlike a result
3316/// alias, MATCH-family tokens are unambiguous source aliases here.
3317fn starts_table_alias(k: &TokenKind) -> bool {
3318    starts_explicit_alias_name(k)
3319        && !matches!(
3320            k,
3321            TokenKind::KwCross
3322                | TokenKind::KwFull
3323                | TokenKind::KwIndexed
3324                | TokenKind::KwInner
3325                | TokenKind::KwIsnull
3326                | TokenKind::KwLeft
3327                | TokenKind::KwNatural
3328                | TokenKind::KwNotnull
3329                | TokenKind::KwOuter
3330                | TokenKind::KwRight
3331        )
3332}
3333
3334pub(crate) fn kw_to_str(k: &TokenKind) -> String {
3335    k.keyword_str()
3336        .map(|s| s.to_ascii_lowercase())
3337        .unwrap_or_else(|| {
3338            let dbg = format!("{k:?}");
3339            dbg.strip_prefix("Kw").unwrap_or(&dbg).to_ascii_lowercase()
3340        })
3341}
3342
3343// ---------------------------------------------------------------------------
3344// Tests
3345// ---------------------------------------------------------------------------
3346
3347#[cfg(test)]
3348mod tests {
3349    use super::*;
3350
3351    static PARSE_OBSERVABILITY_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
3352
3353    fn parse_ok(sql: &str) -> Vec<Statement> {
3354        let mut p = Parser::from_sql(sql);
3355        let (stmts, errs) = p.parse_all();
3356        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
3357        stmts
3358    }
3359
3360    fn parse_one(sql: &str) -> Statement {
3361        let stmts = parse_ok(sql);
3362        assert_eq!(stmts.len(), 1, "expected 1 statement, got {}", stmts.len());
3363        stmts.into_iter().next().unwrap()
3364    }
3365
3366    fn parse_full_select(sql: &str) -> SelectStatement {
3367        let Some((statement, tail_offset)) =
3368            parse_first_statement_with_tail(sql).expect("full SELECT statement must parse")
3369        else {
3370            panic!("expected one SELECT statement");
3371        };
3372        assert_eq!(
3373            tail_offset,
3374            sql.len(),
3375            "public parser must consume the full statement"
3376        );
3377        let Statement::Select(select) = statement else {
3378            panic!("expected SELECT AST");
3379        };
3380        select
3381    }
3382
3383    fn top_level_values(statement: &Statement) -> &ValuesClause {
3384        let Statement::Select(select) = statement else {
3385            panic!("expected SELECT statement");
3386        };
3387        let SelectCore::Values(values) = &select.body.select else {
3388            panic!("expected top-level VALUES core");
3389        };
3390        values
3391    }
3392
3393    fn scalar_subquery_values(expr: &Expr) -> &ValuesClause {
3394        let Expr::Subquery(select, _) = expr else {
3395            panic!("expected scalar subquery");
3396        };
3397        let SelectCore::Values(values) = &select.body.select else {
3398            panic!("expected VALUES scalar subquery");
3399        };
3400        values
3401    }
3402
3403    fn only_join(select: &SelectStatement) -> &JoinClause {
3404        let SelectCore::Select {
3405            from: Some(from), ..
3406        } = &select.body.select
3407        else {
3408            panic!("expected SELECT with FROM clause");
3409        };
3410        let [join] = from.joins.as_slice() else {
3411            panic!("expected exactly one join");
3412        };
3413        assert_eq!(join.join_type.kind, JoinKind::Cross);
3414        assert!(!join.join_type.natural);
3415        join
3416    }
3417
3418    #[test]
3419    fn test_comma_join_accepts_on_constraint_and_consumes_full_statement() {
3420        let select = parse_full_select("SELECT * FROM a, b ON a.id = b.id");
3421        assert!(matches!(
3422            &only_join(&select).constraint,
3423            Some(JoinConstraint::On(Expr::BinaryOp {
3424                op: fsqlite_ast::BinaryOp::Eq,
3425                ..
3426            }))
3427        ));
3428    }
3429
3430    #[test]
3431    fn test_comma_join_accepts_using_constraint_and_consumes_full_statement() {
3432        let select = parse_full_select("SELECT * FROM a, b USING(id)");
3433        assert!(matches!(
3434            &only_join(&select).constraint,
3435            Some(JoinConstraint::Using(columns))
3436                if columns.len() == 1 && columns[0] == "id"
3437        ));
3438    }
3439
3440    #[test]
3441    fn test_explicit_cross_join_accepts_on_constraint_and_consumes_full_statement() {
3442        let select = parse_full_select("SELECT * FROM a CROSS JOIN b ON a.id = b.id");
3443        assert!(matches!(
3444            &only_join(&select).constraint,
3445            Some(JoinConstraint::On(Expr::BinaryOp {
3446                op: fsqlite_ast::BinaryOp::Eq,
3447                ..
3448            }))
3449        ));
3450    }
3451
3452    #[test]
3453    fn test_nonreserved_keyword_table_star_uses_wildcard_ast() {
3454        let select = parse_full_select("SELECT filter.* FROM t AS filter");
3455        let SelectCore::Select { columns, .. } = &select.body.select else {
3456            panic!("expected SELECT core");
3457        };
3458        assert!(matches!(
3459            columns.as_slice(),
3460            [ResultColumn::TableStar(name)] if name == &QualifiedName::bare("filter")
3461        ));
3462        assert_eq!(
3463            select.to_string(),
3464            "SELECT \"filter\".* FROM t AS \"filter\""
3465        );
3466    }
3467
3468    #[test]
3469    fn test_table_star_qualifier_uses_sqlite_contextual_keyword_matrix() {
3470        for name in [
3471            "abort",
3472            "action",
3473            "after",
3474            "always",
3475            "analyze",
3476            "asc",
3477            "attach",
3478            "before",
3479            "begin",
3480            "by",
3481            "cascade",
3482            "column",
3483            "commitseq",
3484            "concurrent",
3485            "conflict",
3486            "cross",
3487            "database",
3488            "deferred",
3489            "desc",
3490            "detach",
3491            "do",
3492            "each",
3493            "end",
3494            "exclude",
3495            "exclusive",
3496            "explain",
3497            "fail",
3498            "false",
3499            "filter",
3500            "first",
3501            "following",
3502            "for",
3503            "full",
3504            "generated",
3505            "glob",
3506            "groups",
3507            "if",
3508            "ignore",
3509            "immediate",
3510            "indexed",
3511            "initially",
3512            "inner",
3513            "instead",
3514            "key",
3515            "last",
3516            "left",
3517            "like",
3518            "match",
3519            "materialized",
3520            "natural",
3521            "no",
3522            "nulls",
3523            "of",
3524            "offset",
3525            "others",
3526            "outer",
3527            "over",
3528            "partition",
3529            "plan",
3530            "pragma",
3531            "preceding",
3532            "query",
3533            "range",
3534            "recursive",
3535            "regexp",
3536            "reindex",
3537            "release",
3538            "rename",
3539            "replace",
3540            "restrict",
3541            "right",
3542            "rollback",
3543            "row",
3544            "rows",
3545            "savepoint",
3546            "stored",
3547            "strict",
3548            "temp",
3549            "temporary",
3550            "ties",
3551            "trigger",
3552            "true",
3553            "unbounded",
3554            "vacuum",
3555            "view",
3556            "virtual",
3557            "window",
3558            "with",
3559            "without",
3560        ] {
3561            let sql = format!("SELECT {name}.* FROM t AS \"{name}\"");
3562            let select = parse_full_select(&sql);
3563            let SelectCore::Select { columns, .. } = &select.body.select else {
3564                panic!("expected SELECT core for `{sql}`");
3565            };
3566            assert!(matches!(
3567                columns.as_slice(),
3568                [ResultColumn::TableStar(qualifier)] if qualifier == &QualifiedName::bare(name)
3569            ));
3570        }
3571
3572        for name in ["nothing", "transaction", "select", "table"] {
3573            let sql = format!("SELECT {name}.* FROM t AS \"{name}\"");
3574            parse_first_statement_with_tail(&sql)
3575                .expect_err("non-fallback table-star keywords must be rejected");
3576        }
3577    }
3578
3579    #[test]
3580    fn test_qualified_star_is_rejected_outside_result_columns() {
3581        for sql in ["SELECT 1 WHERE t.*", "SELECT abs(t.*) FROM t"] {
3582            let error = parse_first_statement_with_tail(sql)
3583                .expect_err("qualified star must not become an ordinary column expression");
3584            // Stock reports the illegal `table.*` outside result columns as
3585            // `near "*": syntax error`. bd-parser-syntax-error-format-6w6kp.
3586            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3587            assert_eq!(
3588                &sql[error.span.start as usize..error.span.end as usize],
3589                "*",
3590                "the diagnostic for `{sql}` must point at the illegal wildcard"
3591            );
3592            assert!(
3593                error.message.contains("expected column name after '.'"),
3594                "unexpected diagnostic for `{sql}`: {error:?}"
3595            );
3596        }
3597
3598        let valid = parse_full_select("SELECT t.*, filter.* FROM t AS filter");
3599        let SelectCore::Select { columns, .. } = &valid.body.select else {
3600            panic!("expected SELECT core");
3601        };
3602        assert!(matches!(
3603            columns.as_slice(),
3604            [ResultColumn::TableStar(first), ResultColumn::TableStar(second)]
3605                if first == &QualifiedName::bare("t")
3606                    && second == &QualifiedName::bare("filter")
3607        ));
3608    }
3609
3610    #[test]
3611    fn test_single_quoted_qualified_identifiers_follow_identifier_context() {
3612        for (sql, expected) in [
3613            ("SELECT 't'.x FROM t", "SELECT t.x FROM t"),
3614            ("SELECT t.'x' FROM t", "SELECT t.x FROM t"),
3615            ("SELECT 't'.'x' FROM t", "SELECT t.x FROM t"),
3616            ("SELECT t.'select' FROM t", "SELECT t.\"select\" FROM t"),
3617            ("SELECT 't'.* FROM t", "SELECT t.* FROM t"),
3618        ] {
3619            let select = parse_full_select(sql);
3620            assert_eq!(
3621                select.to_string(),
3622                expected,
3623                "round-trip mismatch for `{sql}`"
3624            );
3625        }
3626    }
3627
3628    #[test]
3629    fn test_post_dot_identifier_classification_matches_unquoted_names() {
3630        for name in [
3631            "key",
3632            "window",
3633            "filter",
3634            "range",
3635            "rows",
3636            "groups",
3637            "match",
3638            "replace",
3639            "abort",
3640            "column",
3641            "strict",
3642            "true",
3643            "false",
3644            "current_date",
3645            "current_time",
3646            "current_timestamp",
3647            "like",
3648            "glob",
3649            "regexp",
3650        ] {
3651            parse_full_select(&format!("SELECT t.{name} FROM t"));
3652        }
3653
3654        for name in [
3655            "add",
3656            "all",
3657            "alter",
3658            "and",
3659            "as",
3660            "autoincrement",
3661            "between",
3662            "case",
3663            "check",
3664            "collate",
3665            "commit",
3666            "constraint",
3667            "create",
3668            "default",
3669            "deferrable",
3670            "delete",
3671            "distinct",
3672            "drop",
3673            "else",
3674            "escape",
3675            "except",
3676            "exists",
3677            "foreign",
3678            "select",
3679            "from",
3680            "group",
3681            "having",
3682            "in",
3683            "index",
3684            "insert",
3685            "intersect",
3686            "into",
3687            "is",
3688            "isnull",
3689            "join",
3690            "limit",
3691            "not",
3692            "nothing",
3693            "notnull",
3694            "null",
3695            "on",
3696            "or",
3697            "order",
3698            "primary",
3699            "references",
3700            "returning",
3701            "set",
3702            "table",
3703            "then",
3704            "to",
3705            "transaction",
3706            "union",
3707            "unique",
3708            "update",
3709            "using",
3710            "values",
3711            "when",
3712            "where",
3713        ] {
3714            let sql = format!("SELECT t.{name} FROM t");
3715            let error = parse_first_statement_with_tail(&sql)
3716                .expect_err("hard reserved names after a dot must require quoting");
3717            assert_eq!(
3718                &sql[error.span.start as usize..error.span.end as usize],
3719                name,
3720                "the diagnostic for `{sql}` must point at the rejected name"
3721            );
3722        }
3723
3724        for (sql, rejected) in [("SELECT t.1 FROM t", ".1"), ("SELECT t. 1 FROM t", "1")] {
3725            let error = parse_first_statement_with_tail(sql)
3726                .expect_err("numeric tokens after a dot must not become identifiers");
3727            assert_eq!(
3728                &sql[error.span.start as usize..error.span.end as usize],
3729                rejected
3730            );
3731        }
3732    }
3733
3734    #[test]
3735    fn test_leading_qualified_keyword_uses_dot_lookahead_only() {
3736        for name in [
3737            "attach", "begin", "by", "false", "filter", "glob", "inner", "left", "like", "natural",
3738            "outer", "regexp", "right", "rollback", "true", "with",
3739        ] {
3740            let sql = format!("SELECT {name}.x FROM (SELECT 1 AS x) AS \"{name}\"");
3741            let select = parse_full_select(&sql);
3742            let SelectCore::Select { columns, .. } = &select.body.select else {
3743                panic!("expected SELECT core for `{sql}`");
3744            };
3745            assert!(matches!(
3746                columns.as_slice(),
3747                [ResultColumn::Expr {
3748                    expr: Expr::Column(column, _),
3749                    alias: None,
3750                }] if column.table.as_deref() == Some(name) && column.column.as_ref() == "x"
3751            ));
3752        }
3753
3754        for name in ["cast", "current_date", "nothing", "raise", "transaction"] {
3755            let sql = format!("SELECT {name}.x FROM (SELECT 1 AS x) AS \"{name}\"");
3756            parse_first_statement_with_tail(&sql)
3757                .expect_err("non-fallback leading qualifiers must remain expressions or syntax");
3758        }
3759    }
3760
3761    #[test]
3762    fn test_result_alias_uses_sqlite_contextual_name_policy() {
3763        for (source, expected) in [
3764            ("'single quoted'", "single quoted"),
3765            ("attach", "attach"),
3766            ("cast", "cast"),
3767            ("current_date", "current_date"),
3768            ("false", "false"),
3769            ("raise", "raise"),
3770            ("rollback", "rollback"),
3771            ("true", "true"),
3772            ("with", "with"),
3773            ("window", "window"),
3774            ("offset", "offset"),
3775        ] {
3776            let sql = format!("SELECT 1 {source}");
3777            let select = parse_full_select(&sql);
3778            let SelectCore::Select { columns, .. } = &select.body.select else {
3779                panic!("expected SELECT core for `{sql}`");
3780            };
3781            assert!(matches!(
3782                columns.as_slice(),
3783                [ResultColumn::Expr {
3784                    alias: Some(alias),
3785                    ..
3786                }] if alias == expected
3787            ));
3788        }
3789
3790        for sql in [
3791            "SELECT 1 indexed",
3792            "SELECT 1 left",
3793            "SELECT 1 match",
3794            "SELECT 1 nothing",
3795            "SELECT 1 transaction",
3796            "SELECT 1 AS isnull",
3797            "SELECT 1 AS notnull",
3798        ] {
3799            parse_first_statement_with_tail(sql)
3800                .expect_err("operators and non-fallback names must not become result aliases");
3801        }
3802    }
3803
3804    #[test]
3805    fn test_table_alias_uses_sqlite_contextual_name_policy() {
3806        for (source, expected) in [
3807            ("'single quoted'", "single quoted"),
3808            ("attach", "attach"),
3809            ("cast", "cast"),
3810            ("current_date", "current_date"),
3811            ("false", "false"),
3812            ("for", "for"),
3813            ("match", "match"),
3814            ("raise", "raise"),
3815            ("rollback", "rollback"),
3816            ("true", "true"),
3817            ("with", "with"),
3818            ("window", "window"),
3819            ("offset", "offset"),
3820        ] {
3821            let sql = format!("SELECT * FROM (SELECT 1) {source}");
3822            let select = parse_full_select(&sql);
3823            let SelectCore::Select {
3824                from:
3825                    Some(FromClause {
3826                        source: TableOrSubquery::Subquery { alias, .. },
3827                        ..
3828                    }),
3829                ..
3830            } = &select.body.select
3831            else {
3832                panic!("expected aliased subquery for `{sql}`");
3833            };
3834            assert_eq!(
3835                alias.as_deref(),
3836                Some(expected),
3837                "alias mismatch for `{sql}`"
3838            );
3839        }
3840
3841        for sql in [
3842            "SELECT * FROM (SELECT 1) isnull",
3843            "SELECT * FROM (SELECT 1) notnull",
3844            "SELECT * FROM (SELECT 1) nothing",
3845            "SELECT * FROM (SELECT 1) transaction",
3846        ] {
3847            parse_first_statement_with_tail(sql)
3848                .expect_err("non-table-alias tokens must remain rejected");
3849        }
3850
3851        let select = parse_full_select("SELECT * FROM t WINDOW w AS ()");
3852        let SelectCore::Select { from, windows, .. } = &select.body.select else {
3853            panic!("expected SELECT core");
3854        };
3855        assert!(matches!(
3856            from,
3857            Some(FromClause {
3858                source: TableOrSubquery::Table { alias: None, .. },
3859                ..
3860            })
3861        ));
3862        assert_eq!(windows.len(), 1);
3863        assert_eq!(windows[0].name, "w");
3864
3865        let select = parse_full_select("SELECT * FROM t FOR SYSTEM_TIME AS OF COMMITSEQ 1");
3866        let SelectCore::Select { from, .. } = &select.body.select else {
3867            panic!("expected SELECT core");
3868        };
3869        assert!(matches!(
3870            from,
3871            Some(FromClause {
3872                source: TableOrSubquery::Table {
3873                    alias: None,
3874                    time_travel: Some(TimeTravelClause {
3875                        target: TimeTravelTarget::CommitSequence(1),
3876                    }),
3877                    ..
3878                },
3879                ..
3880            })
3881        ));
3882    }
3883
3884    #[test]
3885    fn test_indexed_is_explicit_name_but_not_implicit_alias() {
3886        for sql in [
3887            "SELECT 1 AS indexed",
3888            "SELECT * FROM (SELECT 1) AS indexed",
3889            "CREATE TABLE t(indexed); SELECT t.indexed FROM t",
3890        ] {
3891            let mut parser = Parser::from_sql(sql);
3892            let (statements, errors) = parser.parse_all();
3893            assert!(
3894                errors.is_empty(),
3895                "unexpected errors for `{sql}`: {errors:?}"
3896            );
3897            assert!(
3898                !statements.is_empty(),
3899                "explicit INDEXED name context must produce an AST for `{sql}`"
3900            );
3901        }
3902
3903        for sql in ["SELECT 1 indexed", "SELECT * FROM (SELECT 1) indexed"] {
3904            parse_first_statement_with_tail(sql)
3905                .expect_err("INDEXED must remain unavailable as an implicit alias");
3906        }
3907    }
3908
3909    #[test]
3910    fn test_nested_explain_is_rejected_at_the_second_explain() {
3911        for sql in [
3912            "EXPLAIN EXPLAIN SELECT 1",
3913            "EXPLAIN QUERY PLAN EXPLAIN SELECT 1",
3914        ] {
3915            let error = parse_first_statement_with_tail(sql)
3916                .expect_err("SQLite does not permit nested EXPLAIN statements");
3917            // Stock: `near "EXPLAIN": syntax error`. bd-parser-syntax-error-format-6w6kp.
3918            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3919            assert!(
3920                error.message.contains("nested EXPLAIN"),
3921                "unexpected diagnostic for `{sql}`: {error:?}"
3922            );
3923            let second_explain = sql
3924                .match_indices("EXPLAIN")
3925                .nth(1)
3926                .map(|(offset, _)| offset)
3927                .expect("test SQL must contain a second EXPLAIN");
3928            assert_eq!(
3929                &sql[error.span.start as usize..error.span.end as usize],
3930                &sql[second_explain..second_explain + "EXPLAIN".len()],
3931                "the nested-EXPLAIN diagnostic must point at the rejected keyword"
3932            );
3933        }
3934    }
3935
3936    #[test]
3937    fn test_generic_name_slots_use_sqlite_fallback_name_policy() {
3938        for sql in [
3939            "CREATE TABLE begin(x)",
3940            "SELECT * FROM begin",
3941            "DROP TABLE main.begin",
3942        ] {
3943            parse_first_statement_with_tail(sql)
3944                .expect("fallback-name keywords must parse in an established name slot");
3945        }
3946    }
3947
3948    #[test]
3949    fn test_hard_reserved_column_names_require_quoting_in_ddl() {
3950        for name in ["index", "nothing", "returning", "table", "transaction"] {
3951            let sql = format!("CREATE TABLE t({name} INTEGER)");
3952            let error = parse_first_statement_with_tail(&sql)
3953                .expect_err("hard reserved column names must require quoting");
3954            assert_eq!(
3955                &sql[error.span.start as usize..error.span.end as usize],
3956                name
3957            );
3958        }
3959    }
3960
3961    #[test]
3962    fn test_final_values_rejects_order_by_and_limit() {
3963        for (sql, clause) in [
3964            ("VALUES (1) ORDER BY 1", "ORDER"),
3965            ("VALUES (1) LIMIT 1", "LIMIT"),
3966            ("SELECT 1 UNION VALUES (2) ORDER BY 1", "ORDER"),
3967            ("SELECT 1 UNION VALUES (2) LIMIT 1", "LIMIT"),
3968        ] {
3969            let error = parse_first_statement_with_tail(sql)
3970                .expect_err("a trailing clause on a final VALUES term must be rejected");
3971            // Stock reports a trailing ORDER BY / LIMIT on a final VALUES term as
3972            // `near "ORDER"/"LIMIT": syntax error` (there is no such grammar slot),
3973            // so this is an UnexpectedToken. bd-parser-syntax-error-format-6w6kp.
3974            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3975            assert!(
3976                error.message.contains("not allowed after a VALUES term"),
3977                "unexpected diagnostic for `{sql}`: {error:?}"
3978            );
3979            assert_eq!(
3980                &sql[error.span.start as usize..error.span.end as usize],
3981                clause,
3982                "the primary error for `{sql}` must point at the forbidden clause"
3983            );
3984        }
3985
3986        let deeply_nested = format!(
3987            "VALUES (1) ORDER BY {}1{}",
3988            "(".repeat(1_200),
3989            ")".repeat(1_200)
3990        );
3991        let error = parse_first_statement_with_tail(&deeply_nested)
3992            .expect_err("the forbidden final-VALUES clause must win over expression depth");
3993        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
3994        assert_eq!(
3995            &deeply_nested[error.span.start as usize..error.span.end as usize],
3996            "ORDER"
3997        );
3998
3999        let final_select = parse_full_select("VALUES (1) UNION SELECT 2 ORDER BY 1 LIMIT 1");
4000        assert_eq!(
4001            final_select.to_string(),
4002            "VALUES (1) UNION SELECT 2 ORDER BY 1 LIMIT 1"
4003        );
4004        let wrapped = parse_full_select("SELECT * FROM (VALUES (1)) ORDER BY 1 LIMIT 1");
4005        assert_eq!(
4006            wrapped.to_string(),
4007            "SELECT * FROM (VALUES (1)) ORDER BY 1 LIMIT 1"
4008        );
4009    }
4010
4011    #[test]
4012    fn test_count_star_rejects_aggregate_order_by() {
4013        let error = parse_first_statement_with_tail("SELECT count(* ORDER BY x) FROM t")
4014            .expect_err("aggregate ORDER BY after count(*) must be rejected");
4015        // Stock: `near "ORDER": syntax error` (the aggregate arg list has no
4016        // ORDER BY slot). bd-parser-syntax-error-format-6w6kp.
4017        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4018        assert!(
4019            error.message.contains("RightParen"),
4020            "unexpected diagnostic: {error:?}"
4021        );
4022
4023        let valid = parse_full_select("SELECT count(*) FILTER (WHERE x > 0) OVER () FROM t");
4024        assert_eq!(
4025            valid.to_string(),
4026            "SELECT count(*) FILTER (WHERE x > 0) OVER () FROM t"
4027        );
4028    }
4029
4030    #[test]
4031    fn test_parse_metrics_emitted_when_enabled() {
4032        let _guard = PARSE_OBSERVABILITY_LOCK
4033            .lock()
4034            .unwrap_or_else(|poisoned| poisoned.into_inner());
4035        let prev_metrics_enabled = parse_metrics_enabled();
4036        reset_parse_metrics();
4037        set_parse_metrics_enabled(true);
4038
4039        let mut parser = Parser::from_sql("SELECT 1; SELECT 2;");
4040        let (stmts, errs) = parser.parse_all();
4041        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
4042        assert_eq!(stmts.len(), 2);
4043
4044        let snapshot = parse_metrics_snapshot();
4045        assert!(snapshot.fsqlite_parse_statements_total >= 2);
4046
4047        set_parse_metrics_enabled(prev_metrics_enabled);
4048        reset_parse_metrics();
4049    }
4050
4051    #[test]
4052    fn test_public_parser_new_normalizes_empty_and_missing_eof_streams() {
4053        let error = Parser::new(Vec::new())
4054            .parse_expr()
4055            .expect_err("an empty public token stream must return an error, not panic");
4056        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4057        assert_eq!(error.span, Span::ZERO);
4058
4059        let integer = Token {
4060            kind: TokenKind::Integer(1),
4061            span: Span::new(0, 1),
4062            line: 1,
4063            col: 1,
4064        };
4065        let mut expression_parser = Parser::new(vec![integer]);
4066        assert!(matches!(
4067            expression_parser
4068                .parse_expr()
4069                .expect("a token stream without explicit EOF must be normalized"),
4070            Expr::Literal(Literal::Integer(1), _)
4071        ));
4072        assert!(expression_parser.at_eof());
4073
4074        let mut tokens = Lexer::tokenize("SELECT 1");
4075        assert!(matches!(
4076            tokens.pop(),
4077            Some(Token {
4078                kind: TokenKind::Eof,
4079                ..
4080            })
4081        ));
4082        let (statements, errors) = Parser::new(tokens).parse_all();
4083        assert!(errors.is_empty(), "unexpected errors: {errors:?}");
4084        assert_eq!(statements.len(), 1);
4085        assert_eq!(statements[0].to_string(), "SELECT 1");
4086
4087        let mut tokens = Lexer::tokenize("SELECT 1; SELECT 2");
4088        tokens.insert(
4089            2,
4090            Token {
4091                kind: TokenKind::Eof,
4092                span: Span::new(8, 8),
4093                line: 1,
4094                col: 9,
4095            },
4096        );
4097        let mut parser = Parser::new(tokens);
4098        assert_eq!(
4099            parser
4100                .tokens
4101                .iter()
4102                .filter(|token| token.kind == TokenKind::Eof)
4103                .count(),
4104            1
4105        );
4106        assert!(matches!(
4107            parser.tokens.last(),
4108            Some(Token {
4109                kind: TokenKind::Eof,
4110                ..
4111            })
4112        ));
4113        let (statements, errors) = parser.parse_all();
4114        assert!(
4115            errors.is_empty(),
4116            "embedded EOF normalization must not hide later tokens: {errors:?}"
4117        );
4118        assert_eq!(statements.len(), 2);
4119        assert_eq!(statements[1].to_string(), "SELECT 2");
4120
4121        let tokens = Lexer::tokenize("SELECT CASE WHEN 1 THEN 'a\nb'");
4122        let expected_eof = tokens
4123            .last()
4124            .cloned()
4125            .expect("the lexer must supply a terminal EOF");
4126        let mut parser = Parser::new(tokens);
4127        let normalized_eof = parser
4128            .tokens
4129            .last()
4130            .expect("the normalized stream must retain a terminal EOF");
4131        assert_eq!(normalized_eof, &expected_eof);
4132        assert_eq!(normalized_eof.line, 2);
4133        let (_, errors) = parser.parse_all();
4134        assert!(
4135            errors
4136                .iter()
4137                .any(|error| error.span == expected_eof.span && error.line == expected_eof.line),
4138            "the missing END diagnostic must retain the lexer's multiline EOF coordinates: \
4139             {errors:?}"
4140        );
4141    }
4142
4143    #[test]
4144    fn test_parse_metrics_can_be_disabled_off_hot_path() {
4145        let _guard = PARSE_OBSERVABILITY_LOCK
4146            .lock()
4147            .unwrap_or_else(|poisoned| poisoned.into_inner());
4148        let prev_metrics_enabled = parse_metrics_enabled();
4149        reset_parse_metrics();
4150        set_parse_metrics_enabled(false);
4151
4152        let mut parser = Parser::from_sql("SELECT 1; SELECT 2;");
4153        let (stmts, errs) = parser.parse_all();
4154        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
4155        assert_eq!(stmts.len(), 2);
4156
4157        let snapshot = parse_metrics_snapshot();
4158        assert_eq!(snapshot.fsqlite_parse_statements_total, 0);
4159
4160        set_parse_metrics_enabled(prev_metrics_enabled);
4161        reset_parse_metrics();
4162    }
4163
4164    #[test]
4165    fn test_parse_depth_overflow_does_not_poison_following_statement() {
4166        const OVER_LIMIT: usize = MAX_PARSE_DEPTH as usize + 1;
4167        let expression = std::iter::repeat_n("1", OVER_LIMIT)
4168            .collect::<Vec<_>>()
4169            .join(" + ");
4170        let sql = format!("SELECT {expression}; SELECT 42;");
4171        let mut parser = Parser::from_sql(&sql);
4172        let (statements, errors) = parser.parse_all();
4173
4174        assert_eq!(
4175            errors.len(),
4176            1,
4177            "only the height-1001 statement should be rejected: {errors:?}"
4178        );
4179        assert_eq!(
4180            errors[0].kind,
4181            ParseErrorKind::ExpressionTooDeep {
4182                max: MAX_PARSE_DEPTH
4183            }
4184        );
4185        assert_eq!(statements.len(), 1, "the valid statement must survive");
4186        assert_eq!(statements[0].to_string(), "SELECT 42");
4187        assert_eq!(
4188            parser.depth, 0,
4189            "expression-height recovery must not poison native parser depth"
4190        );
4191    }
4192
4193    #[test]
4194    fn test_parse_first_statement_with_tail_consumes_full_trigger_body() {
4195        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN INSERT INTO audit VALUES('first'); INSERT INTO audit VALUES('second'); END; SELECT 1;";
4196        let Some((statement, tail_offset)) =
4197            parse_first_statement_with_tail(sql).expect("trigger statement should parse")
4198        else {
4199            panic!("expected a trigger statement");
4200        };
4201
4202        assert!(matches!(statement, Statement::CreateTrigger(_)));
4203        assert_eq!(&sql[tail_offset..], " SELECT 1;");
4204    }
4205
4206    #[test]
4207    fn test_trigger_body_accepts_only_sqlite_trigger_commands() {
4208        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4209                   SELECT 1; \
4210                   VALUES (2); \
4211                   INSERT INTO audit VALUES (3); \
4212                   REPLACE INTO audit VALUES (4); \
4213                   UPDATE audit SET value = 5; \
4214                   DELETE FROM audit WHERE value = 6; \
4215                   WITH seed(value) AS (VALUES (7)) SELECT value FROM seed; \
4216                   SELECT 8 end; \
4217                   SELECT * FROM audit end; \
4218                   SELECT * FROM (SELECT 9) end; \
4219                   UPDATE audit SET value = source.value FROM source \
4220                     WHERE audit.id = source.id; \
4221                   END";
4222        let Statement::CreateTrigger(trigger) = parse_one(sql) else {
4223            panic!("expected CREATE TRIGGER");
4224        };
4225        assert_eq!(trigger.body.len(), 11);
4226        assert!(matches!(trigger.body[0], Statement::Select(_)));
4227        assert!(matches!(trigger.body[1], Statement::Select(_)));
4228        assert!(matches!(trigger.body[2], Statement::Insert(_)));
4229        assert!(matches!(trigger.body[3], Statement::Insert(_)));
4230        assert!(matches!(trigger.body[4], Statement::Update(_)));
4231        assert!(matches!(trigger.body[5], Statement::Delete(_)));
4232        assert!(matches!(trigger.body[6], Statement::Select(_)));
4233        assert!(matches!(trigger.body[7], Statement::Select(_)));
4234        assert!(matches!(trigger.body[8], Statement::Select(_)));
4235        assert!(matches!(trigger.body[9], Statement::Select(_)));
4236        assert!(matches!(trigger.body[10], Statement::Update(_)));
4237    }
4238
4239    #[test]
4240    fn test_trigger_body_accepts_explicit_table_aliases_only() {
4241        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4242                   UPDATE audit AS update_target SET value = 1; \
4243                   INSERT INTO audit AS insert_target(value) VALUES (2); \
4244                   DELETE FROM audit AS delete_target WHERE delete_target.value = 3; \
4245                   END";
4246        let Statement::CreateTrigger(trigger) = parse_one(sql) else {
4247            panic!("expected CREATE TRIGGER");
4248        };
4249
4250        assert!(matches!(
4251            &trigger.body[0],
4252            Statement::Update(UpdateStatement {
4253                table: QualifiedTableRef {
4254                    alias: Some(alias),
4255                    ..
4256                },
4257                ..
4258            }) if alias == "update_target"
4259        ));
4260        assert!(matches!(
4261            &trigger.body[1],
4262            Statement::Insert(InsertStatement {
4263                alias: Some(alias),
4264                ..
4265            }) if alias == "insert_target"
4266        ));
4267        assert!(matches!(
4268            &trigger.body[2],
4269            Statement::Delete(DeleteStatement {
4270                table: QualifiedTableRef {
4271                    alias: Some(alias),
4272                    ..
4273                },
4274                ..
4275            }) if alias == "delete_target"
4276        ));
4277
4278        for (body_statement, rejected) in [
4279            ("UPDATE audit target SET value = 1", "target"),
4280            ("INSERT INTO audit target(value) VALUES (1)", "target"),
4281            ("DELETE FROM audit target", "target"),
4282        ] {
4283            let sql = format!("CREATE TRIGGER trg AFTER INSERT ON t BEGIN {body_statement}; END");
4284            let error = parse_first_statement_with_tail(&sql)
4285                .expect_err("trigger-body table aliases without AS must remain rejected");
4286            assert_eq!(
4287                &sql[error.span.start as usize..error.span.end as usize],
4288                rejected,
4289                "the diagnostic for `{sql}` must identify the bare alias"
4290            );
4291        }
4292    }
4293
4294    #[test]
4295    fn test_trigger_body_rejects_empty_missing_semicolon_and_non_dml_commands() {
4296        for (sql, rejected) in [
4297            ("CREATE TRIGGER trg AFTER INSERT ON t BEGIN END", "END"),
4298            (
4299                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN CREATE TABLE bad(x); END",
4300                "CREATE",
4301            ),
4302            (
4303                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN BEGIN; END",
4304                "BEGIN",
4305            ),
4306            (
4307                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN PRAGMA foreign_keys; END",
4308                "PRAGMA",
4309            ),
4310            (
4311                "CREATE TRIGGER outer_trg AFTER INSERT ON t BEGIN \
4312                 CREATE TRIGGER inner_trg AFTER INSERT ON t BEGIN SELECT 1; END; END",
4313                "CREATE",
4314            ),
4315        ] {
4316            let error = parse_first_statement_with_tail(sql)
4317                .expect_err("invalid trigger-body grammar must fail closed");
4318            // Empty body and non-DML statements are reported as stock near-X
4319            // syntax errors at the offending token. bd-parser-syntax-error-format-6w6kp.
4320            assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4321            assert_eq!(
4322                &sql[error.span.start as usize..error.span.end as usize],
4323                rejected,
4324                "the diagnostic for `{sql}` must identify the rejected token"
4325            );
4326        }
4327
4328        let missing_separator = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN SELECT 1 END";
4329        let error = parse_first_statement_with_tail(missing_separator)
4330            .expect_err("a trigger body statement still requires a semicolon");
4331        // An UnexpectedToken with an empty (EOF) span; the connection boundary
4332        // renders it as stock's `incomplete input`.
4333        // bd-parser-syntax-error-format-6w6kp (Part B).
4334        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
4335        assert!(
4336            error
4337                .message
4338                .contains("expected ';' after trigger body statement"),
4339            "unexpected missing-separator diagnostic: {error:?}"
4340        );
4341        assert_eq!(
4342            error.span.start, error.span.end,
4343            "like stock SQLite's incomplete-input result, the parser must not reinterpret \
4344             the implicit END alias as the trigger terminator"
4345        );
4346    }
4347
4348    #[test]
4349    fn test_trigger_missing_separator_recovery_preserves_trailing_top_level_sql() {
4350        for body_statement in [
4351            "INSERT INTO audit VALUES (1)",
4352            "UPDATE audit SET value = 1",
4353            "DELETE FROM audit",
4354        ] {
4355            let sql = format!(
4356                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4357                 {body_statement} END; SELECT 42;"
4358            );
4359            let (statements, errors) = Parser::from_sql(&sql).parse_all();
4360
4361            assert_eq!(
4362                errors.len(),
4363                1,
4364                "the missing trigger-body separator must be reported: {sql}"
4365            );
4366            assert_eq!(
4367                statements.len(),
4368                1,
4369                "recovery must preserve the trailing top-level statement: {sql}"
4370            );
4371            assert_eq!(statements[0].to_string(), "SELECT 42");
4372        }
4373    }
4374
4375    #[test]
4376    fn test_trigger_body_rejects_stock_forbidden_dml_forms_at_exact_tokens() {
4377        for (sql, rejected) in [
4378            (
4379                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4380                 INSERT INTO main.audit VALUES (1); END",
4381                ".",
4382            ),
4383            (
4384                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4385                 UPDATE main.audit SET value = 1; END",
4386                ".",
4387            ),
4388            (
4389                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4390                 DELETE FROM main.audit; END",
4391                ".",
4392            ),
4393            (
4394                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4395                 INSERT INTO audit DEFAULT VALUES; END",
4396                "DEFAULT",
4397            ),
4398            (
4399                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4400                 UPDATE audit INDEXED BY audit_idx SET value = 1; END",
4401                "INDEXED",
4402            ),
4403            (
4404                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4405                 UPDATE audit NOT INDEXED SET value = 1; END",
4406                "NOT",
4407            ),
4408            (
4409                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4410                 DELETE FROM audit INDEXED BY audit_idx; END",
4411                "INDEXED",
4412            ),
4413            (
4414                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4415                 DELETE FROM audit NOT INDEXED; END",
4416                "NOT",
4417            ),
4418            (
4419                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4420                 UPDATE audit SET value = 1 ORDER BY value; END",
4421                "ORDER",
4422            ),
4423            (
4424                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4425                 UPDATE audit SET value = 1 LIMIT 1; END",
4426                "LIMIT",
4427            ),
4428            (
4429                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4430                 DELETE FROM audit ORDER BY value; END",
4431                "ORDER",
4432            ),
4433            (
4434                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4435                 DELETE FROM audit LIMIT 1; END",
4436                "LIMIT",
4437            ),
4438            (
4439                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4440                 INSERT INTO audit VALUES (1) RETURNING value; END",
4441                "RETURNING",
4442            ),
4443            (
4444                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4445                 UPDATE audit SET value = 1 RETURNING value; END",
4446                "RETURNING",
4447            ),
4448            (
4449                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4450                 DELETE FROM audit RETURNING value; END",
4451                "RETURNING",
4452            ),
4453            (
4454                "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4455                 WITH seed(value) AS (VALUES (1)) \
4456                 INSERT INTO audit SELECT value FROM seed; END",
4457                "INSERT",
4458            ),
4459        ] {
4460            let error = parse_first_statement_with_tail(sql)
4461                .expect_err("stock-forbidden trigger DML must fail closed");
4462            // This test guards the exact-token SPAN. The kind varies by stock's
4463            // own behavior: near-X grammar errors (UnexpectedToken: DEFAULT
4464            // VALUES, ORDER BY/LIMIT, WITH..INSERT, UPDATE/DELETE RETURNING),
4465            // fixed verbatim messages (Semantic: qualified names, INDEXED BY /
4466            // NOT INDEXED, INSERT RETURNING). The exact user-facing messages
4467            // are pinned in the end-to-end keeper
4468            // (bd_parser_syntax_error_near_x). bd-parser-syntax-error-format-6w6kp.
4469            assert!(
4470                matches!(
4471                    error.kind,
4472                    ParseErrorKind::Syntax
4473                        | ParseErrorKind::UnexpectedToken
4474                        | ParseErrorKind::Semantic
4475                ),
4476                "unexpected kind for `{sql}`: {error:?}"
4477            );
4478            assert_eq!(
4479                &sql[error.span.start as usize..error.span.end as usize],
4480                rejected,
4481                "the diagnostic for `{sql}` must identify the forbidden token"
4482            );
4483        }
4484    }
4485
4486    #[test]
4487    fn test_trigger_dml_restrictions_do_not_leak_to_top_level_statements() {
4488        for sql in [
4489            "INSERT INTO main.audit DEFAULT VALUES RETURNING rowid",
4490            "UPDATE main.audit INDEXED BY audit_idx SET value = 1 \
4491             RETURNING value ORDER BY value LIMIT 1",
4492            "DELETE FROM main.audit NOT INDEXED RETURNING value ORDER BY value LIMIT 1",
4493        ] {
4494            parse_first_statement_with_tail(sql)
4495                .unwrap_or_else(|error| panic!("top-level DML must remain accepted: {error}"));
4496        }
4497    }
4498
4499    #[test]
4500    fn test_trigger_body_recovery_preserves_following_top_level_statement() {
4501        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4502                   CREATE TABLE bad(x); END; SELECT 42;";
4503        let (statements, errors) = Parser::from_sql(sql).parse_all();
4504        assert_eq!(errors.len(), 1, "expected one trigger-body grammar error");
4505        assert_eq!(
4506            statements.len(),
4507            1,
4508            "the malformed trigger must be discarded"
4509        );
4510        assert_eq!(statements[0].to_string(), "SELECT 42");
4511        assert_eq!(
4512            &sql[errors[0].span.start as usize..errors[0].span.end as usize],
4513            "CREATE"
4514        );
4515    }
4516
4517    #[test]
4518    fn test_parse_first_statement_with_tail_rejects_adjacent_statements_without_separator() {
4519        let error = parse_first_statement_with_tail("SELECT 1 SELECT 2")
4520            .expect_err("adjacent statements without a semicolon must be rejected");
4521
4522        assert!(
4523            error.message.contains("expected ';' separator"),
4524            "unexpected error: {error:?}"
4525        );
4526    }
4527
4528    #[test]
4529    fn test_parse_next_statement_with_tail_preserves_batch_boundaries() {
4530        let pieces = [
4531            "; /* before; */ SELECT 'it''s; 雪';",
4532            " CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
4533             INSERT INTO audit VALUES('body;'); INSERT INTO audit VALUES(NEW.x); END;",
4534            " -- after;\n SELECT 3 /* trailing comment */",
4535        ];
4536        let sql = pieces.concat();
4537        let mut parser = Parser::from_sql(&sql);
4538        let mut start = 0;
4539        for piece in pieces {
4540            let (_, tail) = parser
4541                .parse_next_statement_with_tail()
4542                .expect("next complete statement")
4543                .expect("statement remains");
4544            assert_eq!(&sql[start..tail], piece);
4545            start = tail;
4546        }
4547        assert_eq!(start, sql.len());
4548        assert_eq!(parser.parse_next_statement_with_tail(), Ok(None));
4549        assert_eq!(parser.parse_next_statement_with_tail(), Ok(None));
4550        assert_eq!(
4551            Parser::from_sql("; /* empty; */ ; -- done;").parse_next_statement_with_tail(),
4552            Ok(None)
4553        );
4554    }
4555
4556    #[test]
4557    fn test_parse_next_statement_with_tail_reports_later_error_at_original_offset() {
4558        let sql = "SELECT '雪'; /* prefix; */ SELEC 2; SELECT 3;";
4559        let mut parser = Parser::from_sql(sql);
4560        let (_, tail) = parser
4561            .parse_next_statement_with_tail()
4562            .expect("valid first statement")
4563            .expect("first statement");
4564        assert_eq!(&sql[..tail], "SELECT '雪';");
4565        let error = parser
4566            .parse_next_statement_with_tail()
4567            .expect_err("later malformed statement must fail");
4568        assert_eq!(
4569            &sql[error.span.start as usize..error.span.end as usize],
4570            "SELEC"
4571        );
4572        assert!(error.span.start as usize >= tail);
4573        assert!(
4574            Parser::from_sql("SELECT 1 SELECT 2")
4575                .parse_next_statement_with_tail()
4576                .is_err(),
4577            "do not execute a prefix missing its required separator"
4578        );
4579    }
4580
4581    #[test]
4582    fn test_parse_all_reports_and_recovers_from_missing_statement_separator() {
4583        let sql = "SELECT 1 SELECT 2";
4584        let mut parser = Parser::from_sql(sql);
4585        let (statements, errors) = parser.parse_all();
4586
4587        assert_eq!(
4588            statements.len(),
4589            2,
4590            "both independently valid statements should remain available for diagnostics"
4591        );
4592        assert_eq!(statements[0].to_string(), "SELECT 1");
4593        assert_eq!(statements[1].to_string(), "SELECT 2");
4594        assert_eq!(errors.len(), 1, "the missing separator must be reported");
4595        assert!(
4596            errors[0].message.contains("expected ';' separator"),
4597            "unexpected diagnostic: {:?}",
4598            errors[0]
4599        );
4600        assert_eq!(
4601            &sql[errors[0].span.start as usize..errors[0].span.end as usize],
4602            "SELECT",
4603            "the separator diagnostic must point at the second statement"
4604        );
4605    }
4606
4607    #[test]
4608    fn test_create_table_without_rowid_and_strict_round_trips_display() {
4609        let sql = "CREATE TABLE s (id INTEGER PRIMARY KEY) WITHOUT ROWID, STRICT";
4610        let Some((statement, _)) =
4611            parse_first_statement_with_tail(sql).expect("statement should parse")
4612        else {
4613            panic!("expected CREATE TABLE statement");
4614        };
4615
4616        assert_eq!(statement.to_string(), sql);
4617    }
4618
4619    #[test]
4620    fn test_error_recovery_does_not_fabricate_top_level_statements_from_trigger_body() {
4621        let mut parser = Parser::from_sql(
4622            "CREATE TRIGGER trg AFTER INSERT ON t BEGIN \
4623             XYZZY; SELECT CASE WHEN 1 THEN 2 END AS end; SELECT 2; END; SELECT 3;",
4624        );
4625        let (stmts, errs) = parser.parse_all();
4626
4627        assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
4628        assert_eq!(
4629            stmts.len(),
4630            1,
4631            "only the trailing top-level SELECT should remain"
4632        );
4633        assert!(
4634            matches!(
4635                &stmts[0],
4636                Statement::Select(select)
4637                    if matches!(
4638                        &select.body.select,
4639                        SelectCore::Select { columns, .. }
4640                            if matches!(
4641                                columns.as_slice(),
4642                                [ResultColumn::Expr {
4643                                    expr: Expr::Literal(Literal::Integer(3), _),
4644                                    alias: None,
4645                                }]
4646                            )
4647                    )
4648            ),
4649            "parser must skip the malformed trigger instead of reinterpreting body tokens as top-level SQL: {stmts:?}"
4650        );
4651    }
4652
4653    #[test]
4654    fn test_error_recovery_skips_a_rejected_nested_trigger_before_outer_end() {
4655        for nested_prefix in ["CREATE TRIGGER", "CREATE UNIQUE TRIGGER"] {
4656            let sql = format!(
4657                "CREATE TRIGGER outer_trg AFTER INSERT ON t BEGIN \
4658                 {nested_prefix} inner_trg AFTER INSERT ON t BEGIN SELECT 1; END; \
4659                 END; SELECT 7;"
4660            );
4661            let (stmts, errs) = Parser::from_sql(&sql).parse_all();
4662
4663            assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
4664            assert_eq!(
4665                stmts.len(),
4666                1,
4667                "neither nested-trigger tokens nor the outer END may escape as top-level SQL: {sql}"
4668            );
4669            assert_eq!(stmts[0].to_string(), "SELECT 7");
4670        }
4671    }
4672
4673    #[test]
4674    fn test_error_recovery_recovers_values_statement_after_garbage() {
4675        let mut parser = Parser::from_sql("XYZZY VALUES (1);");
4676        let (stmts, errs) = parser.parse_all();
4677
4678        assert_eq!(errs.len(), 1, "expected one error for leading garbage");
4679        assert_eq!(stmts.len(), 1, "VALUES statement should still be recovered");
4680        assert!(matches!(stmts[0], Statement::Select(_)));
4681    }
4682
4683    #[test]
4684    fn test_error_recovery_does_not_swallow_top_level_sql_after_unbalanced_trigger_paren() {
4685        let mut parser = Parser::from_sql(
4686            "CREATE TRIGGER trg AFTER INSERT ON t BEGIN SELECT (1; END; SELECT 2;",
4687        );
4688        let (stmts, errs) = parser.parse_all();
4689
4690        assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
4691        assert_eq!(
4692            stmts.len(),
4693            1,
4694            "malformed trigger recovery must still preserve the trailing top-level SELECT"
4695        );
4696        assert!(
4697            matches!(
4698                &stmts[0],
4699                Statement::Select(select)
4700                    if matches!(
4701                        &select.body.select,
4702                        SelectCore::Select { columns, .. }
4703                            if matches!(
4704                                columns.as_slice(),
4705                                [ResultColumn::Expr {
4706                                    expr: Expr::Literal(Literal::Integer(2), _),
4707                                    alias: None,
4708                                }]
4709                            )
4710                    )
4711            ),
4712            "parser must stop at the trigger END even when parentheses are left unbalanced: {stmts:?}"
4713        );
4714    }
4715
4716    #[test]
4717    fn select_literal() {
4718        let stmt = parse_one("SELECT 1");
4719        assert!(matches!(stmt, Statement::Select(_)));
4720    }
4721
4722    #[test]
4723    fn select_star_from() {
4724        let stmt = parse_one("SELECT * FROM t");
4725        if let Statement::Select(s) = stmt {
4726            if let SelectCore::Select { columns, from, .. } = &s.body.select {
4727                assert!(matches!(columns[0], ResultColumn::Star));
4728                assert!(from.is_some());
4729            } else {
4730                unreachable!("expected Select core");
4731            }
4732        } else {
4733            unreachable!("expected Select");
4734        }
4735    }
4736
4737    #[test]
4738    fn select_where_order_limit() {
4739        let stmt = parse_one("SELECT a FROM t WHERE a > 1 ORDER BY a LIMIT 10 OFFSET 5");
4740        if let Statement::Select(s) = stmt {
4741            assert!(s.limit.is_some());
4742            assert_eq!(s.order_by.len(), 1);
4743        } else {
4744            unreachable!("expected Select");
4745        }
4746    }
4747
4748    #[test]
4749    fn select_limit_comma_syntax_uses_offset_then_count() {
4750        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
4751        if let Statement::Select(s) = stmt {
4752            let limit = s.limit.expect("LIMIT clause");
4753            assert!(matches!(
4754                limit.limit,
4755                Expr::Literal(Literal::Integer(10), _)
4756            ));
4757            assert!(matches!(
4758                limit.offset,
4759                Some(Expr::Literal(Literal::Integer(5), _))
4760            ));
4761        } else {
4762            unreachable!("expected Select");
4763        }
4764    }
4765
4766    #[test]
4767    fn select_order_by_nulls_first_last() {
4768        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST, b DESC NULLS LAST");
4769        if let Statement::Select(s) = stmt {
4770            assert_eq!(s.order_by.len(), 2);
4771            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
4772            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
4773            assert_eq!(s.order_by[1].direction, Some(SortDirection::Desc));
4774            assert_eq!(s.order_by[1].nulls, Some(NullsOrder::Last));
4775        } else {
4776            unreachable!("expected Select");
4777        }
4778    }
4779
4780    #[test]
4781    fn select_from_indexed_by_hint() {
4782        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
4783        if let Statement::Select(s) = stmt {
4784            if let SelectCore::Select { from, .. } = &s.body.select {
4785                let from = from.as_ref().expect("FROM clause");
4786                match &from.source {
4787                    TableOrSubquery::Table {
4788                        index_hint: Some(IndexHint::IndexedBy(name)),
4789                        ..
4790                    } => assert_eq!(name, "idx_t"),
4791                    other => unreachable!("expected indexed table source, got {other:?}"),
4792                }
4793            } else {
4794                unreachable!("expected Select core");
4795            }
4796        } else {
4797            unreachable!("expected Select");
4798        }
4799    }
4800
4801    #[test]
4802    fn select_from_not_indexed_hint() {
4803        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
4804        if let Statement::Select(s) = stmt {
4805            if let SelectCore::Select { from, .. } = &s.body.select {
4806                let from = from.as_ref().expect("FROM clause");
4807                match &from.source {
4808                    TableOrSubquery::Table {
4809                        index_hint: Some(IndexHint::NotIndexed),
4810                        ..
4811                    } => {}
4812                    other => unreachable!("expected not-indexed table source, got {other:?}"),
4813                }
4814            } else {
4815                unreachable!("expected Select core");
4816            }
4817        } else {
4818            unreachable!("expected Select");
4819        }
4820    }
4821
4822    #[test]
4823    fn select_from_table_valued_function() {
4824        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
4825        if let Statement::Select(s) = stmt {
4826            if let SelectCore::Select { from, .. } = &s.body.select {
4827                let from = from.as_ref().expect("FROM clause");
4828                match &from.source {
4829                    TableOrSubquery::TableFunction { name, args, alias } => {
4830                        assert_eq!(name, "generate_series");
4831                        assert_eq!(args.len(), 2);
4832                        assert_eq!(alias.as_deref(), Some("gs"));
4833                    }
4834                    other => unreachable!("expected table-valued function source, got {other:?}"),
4835                }
4836            } else {
4837                unreachable!("expected Select core");
4838            }
4839        } else {
4840            unreachable!("expected Select");
4841        }
4842    }
4843
4844    #[test]
4845    fn select_window_function_over_clause() {
4846        let stmt = parse_one(
4847            "SELECT sum(x) OVER (PARTITION BY y ORDER BY z \
4848             ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
4849        );
4850        if let Statement::Select(s) = stmt {
4851            if let SelectCore::Select { columns, .. } = &s.body.select {
4852                match &columns[0] {
4853                    ResultColumn::Expr {
4854                        expr:
4855                            Expr::FunctionCall {
4856                                over: Some(over), ..
4857                            },
4858                        ..
4859                    } => {
4860                        assert_eq!(over.partition_by.len(), 1);
4861                        assert_eq!(over.order_by.len(), 1);
4862                        assert!(matches!(
4863                            over.frame,
4864                            Some(FrameSpec {
4865                                frame_type: FrameType::Rows,
4866                                ..
4867                            })
4868                        ));
4869                    }
4870                    other => unreachable!("expected window function result column, got {other:?}"),
4871                }
4872            } else {
4873                unreachable!("expected Select core");
4874            }
4875        } else {
4876            unreachable!("expected Select");
4877        }
4878    }
4879
4880    #[test]
4881    fn select_named_window_definition_and_reference() {
4882        let stmt = parse_one(
4883            "SELECT sum(x) OVER win FROM t \
4884             WINDOW win AS (PARTITION BY y ORDER BY z)",
4885        );
4886        if let Statement::Select(s) = stmt {
4887            if let SelectCore::Select {
4888                columns, windows, ..
4889            } = &s.body.select
4890            {
4891                assert_eq!(windows.len(), 1);
4892                assert_eq!(windows[0].name, "win");
4893                assert_eq!(windows[0].spec.partition_by.len(), 1);
4894                assert_eq!(windows[0].spec.order_by.len(), 1);
4895                match &columns[0] {
4896                    ResultColumn::Expr {
4897                        expr:
4898                            Expr::FunctionCall {
4899                                over: Some(over), ..
4900                            },
4901                        ..
4902                    } => assert_eq!(
4903                        over.window_ref,
4904                        Some(WindowReference::Direct("win".to_owned()))
4905                    ),
4906                    other => unreachable!("expected named window function, got {other:?}"),
4907                }
4908            } else {
4909                unreachable!("expected Select core");
4910            }
4911        } else {
4912            unreachable!("expected Select");
4913        }
4914    }
4915
4916    #[test]
4917    fn select_named_window_reference_uses_sqlite_contextual_keyword_matrix() {
4918        let assert_reference = |reference: &str, name: &str| {
4919            let sql = format!("SELECT sum(1) OVER {reference} WINDOW {name} AS ()");
4920            let select = parse_full_select(&sql);
4921            let SelectCore::Select {
4922                columns, windows, ..
4923            } = &select.body.select
4924            else {
4925                panic!("expected SELECT core for `{sql}`");
4926            };
4927            assert_eq!(windows.len(), 1, "missing WINDOW definition for `{sql}`");
4928            assert_eq!(windows[0].name, name);
4929            let [
4930                ResultColumn::Expr {
4931                    expr:
4932                        Expr::FunctionCall {
4933                            over: Some(window), ..
4934                        },
4935                    alias: None,
4936                },
4937            ] = columns.as_slice()
4938            else {
4939                panic!("expected one named window call for `{sql}`");
4940            };
4941            let expected = if reference.starts_with('(') {
4942                WindowReference::Base(name.to_owned())
4943            } else {
4944                WindowReference::Direct(name.to_owned())
4945            };
4946            assert_eq!(
4947                window.window_ref,
4948                Some(expected),
4949                "wrong OVER form for `{sql}`"
4950            );
4951        };
4952
4953        for name in [
4954            "attach",
4955            "begin",
4956            "by",
4957            "cast",
4958            "current_date",
4959            "current_time",
4960            "current_timestamp",
4961            "cross",
4962            "detach",
4963            "explain",
4964            "false",
4965            "for",
4966            "glob",
4967            "inner",
4968            "left",
4969            "like",
4970            "natural",
4971            "outer",
4972            "over",
4973            "raise",
4974            "regexp",
4975            "right",
4976            "rollback",
4977            "key",
4978            "true",
4979            "window",
4980            "with",
4981        ] {
4982            assert_reference(name, name);
4983            assert_reference(&format!("({name})"), name);
4984        }
4985
4986        for name in ["partition", "range", "rows", "groups"] {
4987            assert_reference(name, name);
4988            let sql = format!("SELECT sum(1) OVER ({name})");
4989            parse_first_statement_with_tail(&sql)
4990                .expect_err("window-spec delimiters cannot be parenthesized base names");
4991        }
4992    }
4993
4994    #[test]
4995    fn select_named_window_reference_rejects_non_fallback_keywords() {
4996        for name in ["filter", "nothing", "transaction"] {
4997            for reference in [name.to_owned(), format!("({name})")] {
4998                let sql = format!("SELECT sum(1) OVER {reference}");
4999                parse_first_statement_with_tail(&sql)
5000                    .expect_err("reserved window-name tokens must not be consumed as names");
5001            }
5002            let sql = format!("SELECT sum(1) WINDOW {name} AS ()");
5003            parse_first_statement_with_tail(&sql)
5004                .expect_err("reserved WINDOW definition names must be rejected");
5005        }
5006    }
5007
5008    #[test]
5009    fn select_named_window_reference_accepts_string_and_parenthesized_names() {
5010        for (sql, expected_name) in [
5011            ("SELECT sum(1) OVER 'w' WINDOW 'w' AS ()", "w"),
5012            ("SELECT sum(1) OVER ('w') WINDOW 'w' AS ()", "w"),
5013            ("SELECT sum(1) OVER (window) WINDOW window AS ()", "window"),
5014        ] {
5015            let select = parse_full_select(sql);
5016            let SelectCore::Select {
5017                columns, windows, ..
5018            } = &select.body.select
5019            else {
5020                panic!("expected SELECT core for `{sql}`");
5021            };
5022            assert_eq!(windows.len(), 1, "missing WINDOW definition for `{sql}`");
5023            assert_eq!(windows[0].name, expected_name);
5024            let [
5025                ResultColumn::Expr {
5026                    expr:
5027                        Expr::FunctionCall {
5028                            over: Some(window), ..
5029                        },
5030                    alias: None,
5031                },
5032            ] = columns.as_slice()
5033            else {
5034                panic!("expected one named window call for `{sql}`");
5035            };
5036            let expected = if sql.contains("OVER (") {
5037                WindowReference::Base(expected_name.to_owned())
5038            } else {
5039                WindowReference::Direct(expected_name.to_owned())
5040            };
5041            assert_eq!(
5042                window.window_ref,
5043                Some(expected),
5044                "wrong OVER form for `{sql}`"
5045            );
5046        }
5047    }
5048
5049    #[test]
5050    fn over_window_prefers_named_window_reference_over_implicit_alias() {
5051        let sql = "WITH t(x) AS (VALUES (1), (2)) \
5052                   SELECT sum(x) OVER window FROM t WINDOW window AS ()";
5053        let select = parse_full_select(sql);
5054        let SelectCore::Select {
5055            columns, windows, ..
5056        } = &select.body.select
5057        else {
5058            panic!("expected SELECT core");
5059        };
5060        assert_eq!(windows.len(), 1);
5061        assert_eq!(windows[0].name, "window");
5062        assert!(matches!(
5063            columns.as_slice(),
5064            [ResultColumn::Expr {
5065                expr: Expr::FunctionCall {
5066                    over: Some(WindowSpec {
5067                        window_ref: Some(WindowReference::Direct(base_window)),
5068                        ..
5069                    }),
5070                    ..
5071                },
5072                alias: None,
5073            }] if base_window == "window"
5074        ));
5075
5076        let error = parse_first_statement_with_tail("SELECT sum(1) OVER WINDOW w AS ()")
5077            .expect_err("WINDOW after OVER is a window name, not an implicit OVER alias");
5078        // Stock: `near "WINDOW": syntax error`. bd-parser-syntax-error-format-6w6kp.
5079        assert_eq!(error.kind, ParseErrorKind::UnexpectedToken);
5080    }
5081
5082    #[test]
5083    fn over_implicit_alias_disambiguation_is_preserved_at_real_boundaries() {
5084        for sql in ["SELECT sum(1) OVER, 2", "SELECT sum(1) OVER FROM t"] {
5085            let select = parse_full_select(sql);
5086            let SelectCore::Select { columns, .. } = &select.body.select else {
5087                panic!("expected SELECT core for `{sql}`");
5088            };
5089            assert!(matches!(
5090                columns.first(),
5091                Some(ResultColumn::Expr {
5092                    expr: Expr::FunctionCall { over: None, .. },
5093                    alias: Some(alias),
5094                }) if alias == "over"
5095            ));
5096        }
5097    }
5098
5099    #[test]
5100    fn overflowing_float_literals_round_trip_as_infinite_numbers() {
5101        fn assert_infinite_columns(select: &SelectStatement) {
5102            let SelectCore::Select { columns, .. } = &select.body.select else {
5103                panic!("expected SELECT core");
5104            };
5105            let [positive, negative] = columns.as_slice() else {
5106                panic!("expected positive and negative infinity columns");
5107            };
5108            assert!(matches!(
5109                positive,
5110                ResultColumn::Expr {
5111                    expr: Expr::Literal(Literal::Float(value), _),
5112                    ..
5113                } if value.is_infinite() && value.is_sign_positive()
5114            ));
5115            let ResultColumn::Expr {
5116                expr:
5117                    Expr::UnaryOp {
5118                        op: UnaryOp::Negate,
5119                        expr,
5120                        ..
5121                    },
5122                ..
5123            } = negative
5124            else {
5125                panic!("expected negative infinity to retain unary negation");
5126            };
5127            assert!(matches!(
5128                expr.as_ref(),
5129                Expr::Literal(Literal::Float(value), _)
5130                    if value.is_infinite() && value.is_sign_positive()
5131            ));
5132        }
5133
5134        let parsed = parse_full_select("SELECT 9e999, -9e999");
5135        assert_infinite_columns(&parsed);
5136        let rendered = parsed.to_string();
5137        assert_eq!(rendered, "SELECT 9e999, -9e999");
5138        let reparsed = parse_full_select(&rendered);
5139        assert_infinite_columns(&reparsed);
5140        assert_eq!(reparsed.to_string(), rendered);
5141    }
5142
5143    #[test]
5144    fn insert_values() {
5145        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2), (3, 4)");
5146        assert!(matches!(stmt, Statement::Insert(_)));
5147    }
5148
5149    #[test]
5150    fn update_set() {
5151        let stmt = parse_one("UPDATE t SET a = 1, b = 2 WHERE id = 3");
5152        assert!(matches!(stmt, Statement::Update(_)));
5153    }
5154
5155    #[test]
5156    fn delete_from() {
5157        let stmt = parse_one("DELETE FROM t WHERE id = 1");
5158        assert!(matches!(stmt, Statement::Delete(_)));
5159    }
5160
5161    #[test]
5162    fn create_table_basic() {
5163        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
5164        if let Statement::CreateTable(ct) = stmt {
5165            assert_eq!(ct.name.name, "t");
5166            if let CreateTableBody::Columns { columns, .. } = ct.body {
5167                assert_eq!(columns.len(), 2);
5168            } else {
5169                unreachable!("expected column defs");
5170            }
5171        } else {
5172            unreachable!("expected CreateTable");
5173        }
5174    }
5175
5176    #[test]
5177    fn create_index() {
5178        let stmt = parse_one("CREATE UNIQUE INDEX idx ON t (a, b DESC)");
5179        if let Statement::CreateIndex(ci) = stmt {
5180            assert!(ci.unique);
5181            assert_eq!(ci.columns.len(), 2);
5182        } else {
5183            unreachable!("expected CreateIndex");
5184        }
5185    }
5186
5187    #[test]
5188    fn drop_table_if_exists() {
5189        let stmt = parse_one("DROP TABLE IF EXISTS t");
5190        if let Statement::Drop(d) = stmt {
5191            assert!(d.if_exists);
5192            assert_eq!(d.object_type, DropObjectType::Table);
5193        } else {
5194            unreachable!("expected Drop");
5195        }
5196    }
5197
5198    #[test]
5199    fn begin_commit() {
5200        let stmts = parse_ok("BEGIN IMMEDIATE; COMMIT");
5201        assert_eq!(stmts.len(), 2);
5202        if let Statement::Begin(b) = &stmts[0] {
5203            assert_eq!(b.mode, Some(TransactionMode::Immediate));
5204        } else {
5205            unreachable!("expected Begin");
5206        }
5207        assert!(matches!(stmts[1], Statement::Commit));
5208    }
5209
5210    #[test]
5211    fn begin_concurrent() {
5212        let stmt = parse_one("BEGIN CONCURRENT");
5213        if let Statement::Begin(b) = stmt {
5214            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
5215        } else {
5216            unreachable!("expected Begin");
5217        }
5218    }
5219
5220    #[test]
5221    fn rollback_to_savepoint() {
5222        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
5223        if let Statement::Rollback(r) = stmt {
5224            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
5225        } else {
5226            unreachable!("expected Rollback");
5227        }
5228    }
5229
5230    #[test]
5231    fn explain_query_plan() {
5232        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT 1");
5233        assert!(matches!(
5234            stmt,
5235            Statement::Explain {
5236                query_plan: true,
5237                ..
5238            }
5239        ));
5240    }
5241
5242    #[test]
5243    fn pragma() {
5244        let stmt = parse_one("PRAGMA journal_mode = WAL");
5245        assert!(matches!(stmt, Statement::Pragma(_)));
5246    }
5247
5248    #[test]
5249    fn pragma_cache_size_preserves_numeric_spelling_across_sql_entrypoints() {
5250        fn argument(statement: Statement) -> String {
5251            let Statement::Pragma(pragma) = statement else {
5252                panic!("expected PRAGMA");
5253            };
5254            let Some(
5255                PragmaValue::Assign(Expr::Literal(Literal::String(value), _))
5256                | PragmaValue::Call(Expr::Literal(Literal::String(value), _)),
5257            ) = pragma.value
5258            else {
5259                panic!("expected preserved cache argument: {pragma:?}");
5260            };
5261            value
5262        }
5263
5264        let mut scratch = StatementParseScratch::default();
5265        for (sql, expected) in [
5266            ("PRAGMA cache_size=1e3", "1e3"),
5267            ("PRAGMA main.cache_size=-1E-3", "-1E-3"),
5268            ("PRAGMA temp.CACHE_SIZE(+ /* sign */ 0x10)", "0x10"),
5269            ("PRAGMA aux.default_cache_size=- 0x10", "-0x10"),
5270            (
5271                "PRAGMA cache_size=9223372036854775808",
5272                "9223372036854775808",
5273            ),
5274            (
5275                "PRAGMA cache_size=-9223372036854775809",
5276                "-9223372036854775809",
5277            ),
5278            ("PRAGMA cache_size='123abc'", "123abc"),
5279        ] {
5280            assert_eq!(argument(parse_one(sql)), expected, "ordinary: {sql}");
5281            let mut statements = parse_statements_with_scratch(sql, &mut scratch).expect(sql);
5282            assert_eq!(statements.len(), 1, "scratch: {sql}");
5283            assert_eq!(argument(statements.remove(0)), expected, "scratch: {sql}");
5284            let (statement, tail) = parse_first_statement_with_tail(sql).expect(sql).expect(sql);
5285            assert_eq!(tail, sql.len(), "tail: {sql}");
5286            assert_eq!(argument(statement), expected, "tail: {sql}");
5287        }
5288    }
5289
5290    #[test]
5291    fn pragma_cache_size_numeric_spelling_does_not_change_other_parsing() {
5292        let Statement::Pragma(pragma) = parse_one("PRAGMA busy_timeout=1e3") else {
5293            panic!("expected PRAGMA");
5294        };
5295        assert!(matches!(pragma.value,
5296            Some(PragmaValue::Assign(Expr::Literal(Literal::Float(value), _))) if value == 1000.0
5297        ));
5298
5299        // Public token-only callers cannot recover text the lexer discarded.
5300        let mut parser = Parser::new(Lexer::tokenize("PRAGMA cache_size=1e3"));
5301        let Statement::Pragma(pragma) = parser.parse_statement().expect("token-only parse") else {
5302            panic!("expected PRAGMA");
5303        };
5304        assert!(matches!(pragma.value,
5305            Some(PragmaValue::Assign(Expr::Literal(Literal::Float(value), _))) if value == 1000.0
5306        ));
5307
5308        for sql in ["PRAGMA cache_size=1_0", "PRAGMA default_cache_size=0x1_0"] {
5309            assert!(parse_first_statement_with_tail(sql).is_err(), "{sql}");
5310        }
5311        assert!(Parser::from_sql("SELECT 1e3").pragma_source_sql.is_none());
5312    }
5313
5314    #[test]
5315    fn pragma_allows_on_value() {
5316        let stmt = parse_one("PRAGMA fsqlite.serializable = ON");
5317        assert!(matches!(stmt, Statement::Pragma(_)));
5318    }
5319
5320    #[test]
5321    fn pragma_allows_delete_and_default_keyword_values() {
5322        // GH #276: DELETE and DEFAULT are reserved statement keywords but are
5323        // valid pragma values (SQLite's nmnum grammar). They must parse in value
5324        // position and resolve to their identifier name.
5325        for (sql, expected) in [
5326            ("PRAGMA journal_mode = DELETE", "delete"),
5327            ("PRAGMA temp_store = DEFAULT", "default"),
5328        ] {
5329            let Statement::Pragma(p) = parse_one(sql) else {
5330                unreachable!("expected Pragma for {sql}");
5331            };
5332            match p.value {
5333                Some(PragmaValue::Assign(Expr::Column(col, _))) => {
5334                    assert!(col.table.is_none(), "sql={sql}");
5335                    assert_eq!(&*col.column, expected, "sql={sql}");
5336                }
5337                other => unreachable!("expected Assign(Column) for {sql}, got {other:?}"),
5338            }
5339        }
5340    }
5341
5342    #[test]
5343    fn error_recovery_multiple_statements() {
5344        let mut p = Parser::from_sql("SELECT 1; XYZZY; SELECT 2");
5345        let (stmts, errs) = p.parse_all();
5346        assert_eq!(stmts.len(), 2, "should recover: stmts={stmts:?}");
5347        assert!(!errs.is_empty());
5348    }
5349
5350    #[test]
5351    fn compound_union() {
5352        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
5353        if let Statement::Select(s) = stmt {
5354            assert_eq!(s.body.compounds.len(), 1);
5355        } else {
5356            unreachable!("expected Select");
5357        }
5358    }
5359
5360    #[test]
5361    fn alter_table_rename() {
5362        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
5363        assert!(matches!(
5364            stmt,
5365            Statement::AlterTable(AlterTableStatement {
5366                action: AlterTableAction::RenameTo(_),
5367                ..
5368            })
5369        ));
5370    }
5371
5372    // -----------------------------------------------------------------------
5373    // bd-2kvo Phase 3 acceptance: parser join types
5374    // -----------------------------------------------------------------------
5375
5376    #[test]
5377    fn test_parser_join_inner() {
5378        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
5379        if let Statement::Select(s) = stmt {
5380            if let SelectCore::Select { from, .. } = &s.body.select {
5381                let from = from.as_ref().expect("FROM clause");
5382                assert!(!from.joins.is_empty());
5383                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
5384            } else {
5385                unreachable!("expected Select core");
5386            }
5387        } else {
5388            unreachable!("expected Select");
5389        }
5390    }
5391
5392    #[test]
5393    fn test_parser_join_left() {
5394        let stmt = parse_one("SELECT * FROM a LEFT JOIN b ON a.id = b.a_id");
5395        if let Statement::Select(s) = stmt {
5396            if let SelectCore::Select { from, .. } = &s.body.select {
5397                let from = from.as_ref().expect("FROM clause");
5398                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
5399            } else {
5400                unreachable!("expected Select core");
5401            }
5402        } else {
5403            unreachable!("expected Select");
5404        }
5405    }
5406
5407    #[test]
5408    fn test_parser_join_left_outer() {
5409        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
5410        if let Statement::Select(s) = stmt {
5411            if let SelectCore::Select { from, .. } = &s.body.select {
5412                let from = from.as_ref().expect("FROM clause");
5413                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
5414            } else {
5415                unreachable!("expected Select core");
5416            }
5417        } else {
5418            unreachable!("expected Select");
5419        }
5420    }
5421
5422    #[test]
5423    fn test_parser_join_right() {
5424        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
5425        if let Statement::Select(s) = stmt {
5426            if let SelectCore::Select { from, .. } = &s.body.select {
5427                let from = from.as_ref().expect("FROM clause");
5428                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
5429            } else {
5430                unreachable!("expected Select core");
5431            }
5432        } else {
5433            unreachable!("expected Select");
5434        }
5435    }
5436
5437    #[test]
5438    fn test_parser_join_full() {
5439        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
5440        if let Statement::Select(s) = stmt {
5441            if let SelectCore::Select { from, .. } = &s.body.select {
5442                let from = from.as_ref().expect("FROM clause");
5443                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
5444            } else {
5445                unreachable!("expected Select core");
5446            }
5447        } else {
5448            unreachable!("expected Select");
5449        }
5450    }
5451
5452    #[test]
5453    fn test_parser_join_full_outer_with_semicolon() {
5454        let stmt = parse_one("SELECT l.name, r.tag FROM l FULL OUTER JOIN r ON l.id = r.l_id;");
5455        if let Statement::Select(s) = stmt {
5456            if let SelectCore::Select { from, .. } = &s.body.select {
5457                let from = from.as_ref().expect("FROM clause");
5458                assert_eq!(from.joins.len(), 1);
5459                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
5460            } else {
5461                unreachable!("expected Select core");
5462            }
5463        } else {
5464            unreachable!("expected Select");
5465        }
5466    }
5467
5468    #[test]
5469    fn test_parser_join_cross() {
5470        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
5471        if let Statement::Select(s) = stmt {
5472            if let SelectCore::Select { from, .. } = &s.body.select {
5473                let from = from.as_ref().expect("FROM clause");
5474                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
5475            } else {
5476                unreachable!("expected Select core");
5477            }
5478        } else {
5479            unreachable!("expected Select");
5480        }
5481    }
5482
5483    #[test]
5484    fn test_parser_join_natural() {
5485        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
5486        if let Statement::Select(s) = stmt {
5487            if let SelectCore::Select { from, .. } = &s.body.select {
5488                let from = from.as_ref().expect("FROM clause");
5489                assert!(from.joins[0].join_type.natural);
5490            } else {
5491                unreachable!("expected Select core");
5492            }
5493        } else {
5494            unreachable!("expected Select");
5495        }
5496    }
5497
5498    #[test]
5499    fn test_parser_join_using() {
5500        let stmt = parse_one("SELECT * FROM a JOIN b USING (id)");
5501        if let Statement::Select(s) = stmt {
5502            if let SelectCore::Select { from, .. } = &s.body.select {
5503                let from = from.as_ref().expect("FROM clause");
5504                assert!(matches!(
5505                    from.joins[0].constraint,
5506                    Some(JoinConstraint::Using(_))
5507                ));
5508            } else {
5509                unreachable!("expected Select core");
5510            }
5511        } else {
5512            unreachable!("expected Select");
5513        }
5514    }
5515
5516    #[test]
5517    fn test_parser_join_comma() {
5518        // Comma-join is an implicit cross join.
5519        let stmt = parse_one("SELECT * FROM a, b WHERE a.id = b.a_id");
5520        if let Statement::Select(s) = stmt {
5521            if let SelectCore::Select { from, .. } = &s.body.select {
5522                let from = from.as_ref().expect("FROM clause");
5523                assert!(!from.joins.is_empty());
5524                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
5525            } else {
5526                unreachable!("expected Select core");
5527            }
5528        } else {
5529            unreachable!("expected Select");
5530        }
5531    }
5532
5533    // -----------------------------------------------------------------------
5534    // bd-2kvo Phase 3 acceptance: CTE syntax
5535    // -----------------------------------------------------------------------
5536
5537    #[test]
5538    fn test_parser_cte_basic() {
5539        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
5540        if let Statement::Select(s) = stmt {
5541            let with = s.with.as_ref().expect("WITH clause");
5542            assert!(!with.recursive);
5543            assert_eq!(with.ctes.len(), 1);
5544            assert_eq!(with.ctes[0].name, "cte");
5545        } else {
5546            unreachable!("expected Select");
5547        }
5548    }
5549
5550    #[test]
5551    fn test_parser_cte_multiple() {
5552        let stmt = parse_one("WITH a AS (SELECT 1), b AS (SELECT 2) SELECT * FROM a, b");
5553        if let Statement::Select(s) = stmt {
5554            let with = s.with.as_ref().expect("WITH clause");
5555            assert_eq!(with.ctes.len(), 2);
5556            assert_eq!(with.ctes[0].name, "a");
5557            assert_eq!(with.ctes[1].name, "b");
5558        } else {
5559            unreachable!("expected Select");
5560        }
5561    }
5562
5563    #[test]
5564    fn test_parser_cte_recursive() {
5565        let stmt = parse_one(
5566            "WITH RECURSIVE cnt(x) AS (\
5567             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
5568             ) SELECT x FROM cnt",
5569        );
5570        if let Statement::Select(s) = stmt {
5571            let with = s.with.as_ref().expect("WITH clause");
5572            assert!(with.recursive);
5573            assert_eq!(with.ctes[0].name, "cnt");
5574            assert_eq!(with.ctes[0].columns, vec!["x".to_owned()]);
5575        } else {
5576            unreachable!("expected Select");
5577        }
5578    }
5579
5580    #[test]
5581    fn test_parser_cte_materialized() {
5582        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
5583        if let Statement::Select(s) = stmt {
5584            let with = s.with.as_ref().expect("WITH clause");
5585            assert_eq!(
5586                with.ctes[0].materialized,
5587                Some(CteMaterialized::Materialized)
5588            );
5589        } else {
5590            unreachable!("expected Select");
5591        }
5592    }
5593
5594    // -----------------------------------------------------------------------
5595    // bd-2d6i §12.1 SELECT full syntax acceptance tests
5596    // -----------------------------------------------------------------------
5597
5598    #[test]
5599    fn test_select_table_star() {
5600        let stmt = parse_one("SELECT t1.* FROM t1, t2");
5601        if let Statement::Select(s) = stmt {
5602            if let SelectCore::Select { columns, .. } = &s.body.select {
5603                assert!(
5604                    matches!(&columns[0], ResultColumn::TableStar(t) if t == &QualifiedName::bare("t1")),
5605                    "expected TableStar(t1), got {:?}",
5606                    columns[0]
5607                );
5608            } else {
5609                unreachable!("expected Select core");
5610            }
5611        } else {
5612            unreachable!("expected Select");
5613        }
5614    }
5615
5616    #[test]
5617    fn test_select_schema_table_star() {
5618        let stmt = parse_one("SELECT aux.t1.* FROM aux.t1");
5619        if let Statement::Select(s) = stmt {
5620            if let SelectCore::Select { columns, .. } = &s.body.select {
5621                assert!(
5622                    matches!(&columns[0], ResultColumn::TableStar(t) if t == &QualifiedName::qualified("aux", "t1")),
5623                    "expected TableStar(aux.t1), got {:?}",
5624                    columns[0]
5625                );
5626            } else {
5627                unreachable!("expected Select core");
5628            }
5629        } else {
5630            unreachable!("expected Select");
5631        }
5632    }
5633
5634    #[test]
5635    fn test_select_expr_alias() {
5636        let stmt = parse_one("SELECT x + 1 AS result FROM t");
5637        if let Statement::Select(s) = stmt {
5638            if let SelectCore::Select { columns, .. } = &s.body.select {
5639                match &columns[0] {
5640                    ResultColumn::Expr {
5641                        alias: Some(alias), ..
5642                    } => assert_eq!(alias, "result"),
5643                    other => unreachable!("expected aliased expr column, got {other:?}"),
5644                }
5645            } else {
5646                unreachable!("expected Select core");
5647            }
5648        } else {
5649            unreachable!("expected Select");
5650        }
5651    }
5652
5653    #[test]
5654    fn test_select_distinct_keyword() {
5655        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
5656        if let Statement::Select(s) = stmt {
5657            if let SelectCore::Select {
5658                distinct, columns, ..
5659            } = &s.body.select
5660            {
5661                assert_eq!(*distinct, Distinctness::Distinct);
5662                assert_eq!(columns.len(), 2);
5663            } else {
5664                unreachable!("expected Select core");
5665            }
5666        } else {
5667            unreachable!("expected Select");
5668        }
5669    }
5670
5671    #[test]
5672    fn test_select_values_clause() {
5673        let stmt = parse_one("VALUES (1, 2), (3, 4)");
5674        if let Statement::Select(s) = stmt {
5675            if let SelectCore::Values(rows) = &s.body.select {
5676                assert_eq!(rows.len(), 2);
5677                assert_eq!(rows[0].len(), 2);
5678                assert_eq!(rows[1].len(), 2);
5679            } else {
5680                unreachable!("expected Values core");
5681            }
5682        } else {
5683            unreachable!("expected Select");
5684        }
5685    }
5686
5687    #[test]
5688    fn test_values_representation_captures_leading_and_nested_with_timing() {
5689        let plain = parse_one("VALUES (1), (2), (3)");
5690        assert_eq!(
5691            top_level_values(&plain).representation(),
5692            ValuesRepresentation::Deferred {
5693                force_union_all_from: None,
5694            }
5695        );
5696
5697        let leading = parse_one("WITH c(x) AS (SELECT 1) VALUES (2), (3)");
5698        assert_eq!(top_level_values(&leading).force_union_all_from(), Some(0));
5699
5700        let nested_first =
5701            parse_one("VALUES ((WITH c(x) AS (SELECT 1) SELECT x FROM c)), (2), (3)");
5702        assert_eq!(
5703            top_level_values(&nested_first).force_union_all_from(),
5704            Some(0)
5705        );
5706
5707        let nested_second =
5708            parse_one("VALUES (1), ((WITH c(x) AS (SELECT 2) SELECT x FROM c)), (3)");
5709        assert_eq!(
5710            top_level_values(&nested_second).force_union_all_from(),
5711            Some(1)
5712        );
5713    }
5714
5715    #[test]
5716    fn test_values_representation_is_sticky_but_not_retroactive_within_a_statement() {
5717        let later_with = parse_full_select(
5718            "SELECT (VALUES (1), (2)), (WITH c(x) AS (SELECT 3) SELECT x FROM c)",
5719        );
5720        let SelectCore::Select { columns, .. } = &later_with.body.select else {
5721            panic!("expected SELECT core");
5722        };
5723        let ResultColumn::Expr { expr, .. } = &columns[0] else {
5724            panic!("expected expression result column");
5725        };
5726        assert_eq!(scalar_subquery_values(expr).force_union_all_from(), None);
5727
5728        let earlier_with = parse_full_select(
5729            "SELECT (WITH c(x) AS (SELECT 3) SELECT x FROM c), (VALUES (1), (2))",
5730        );
5731        let SelectCore::Select { columns, .. } = &earlier_with.body.select else {
5732            panic!("expected SELECT core");
5733        };
5734        let ResultColumn::Expr { expr, .. } = &columns[1] else {
5735            panic!("expected expression result column");
5736        };
5737        assert_eq!(scalar_subquery_values(expr).force_union_all_from(), Some(0));
5738    }
5739
5740    #[test]
5741    fn test_values_with_state_resets_between_parse_all_statements() {
5742        let statements = parse_ok("WITH c(x) AS (SELECT 1) VALUES (2), (3); VALUES (4), (5);");
5743        assert_eq!(statements.len(), 2);
5744        assert_eq!(
5745            top_level_values(&statements[0]).force_union_all_from(),
5746            Some(0)
5747        );
5748        assert_eq!(
5749            top_level_values(&statements[1]).force_union_all_from(),
5750            None
5751        );
5752    }
5753
5754    #[test]
5755    fn test_direct_values_parser_captures_nested_with_row_boundary() {
5756        let mut parser =
5757            Parser::from_sql("VALUES (1), ((WITH c(x) AS (SELECT 2) SELECT x FROM c)), (3)");
5758        let parsed = parser
5759            .parse_select_core_tracked()
5760            .expect("direct VALUES parser must succeed");
5761        let SelectCore::Values(values) = parsed.value else {
5762            panic!("direct parser must return VALUES");
5763        };
5764
5765        assert_eq!(values.force_union_all_from(), Some(1));
5766        assert_eq!(values.len(), 3);
5767    }
5768
5769    #[test]
5770    fn test_insert_values_extraction_retains_nested_values_representation() {
5771        let statement =
5772            parse_one("WITH c(x) AS (SELECT 1) INSERT INTO t VALUES ((VALUES (2), (3)))");
5773        let Statement::Insert(insert) = statement else {
5774            panic!("expected INSERT statement");
5775        };
5776        let InsertSource::Values(rows) = insert.source else {
5777            panic!("expected INSERT VALUES source");
5778        };
5779        let [row] = rows.as_slice() else {
5780            panic!("expected one INSERT row");
5781        };
5782        let [expr] = row.as_slice() else {
5783            panic!("expected one INSERT column");
5784        };
5785
5786        assert_eq!(scalar_subquery_values(expr).force_union_all_from(), Some(0));
5787    }
5788
5789    #[test]
5790    fn test_select_group_by_having() {
5791        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
5792        if let Statement::Select(s) = stmt {
5793            if let SelectCore::Select {
5794                group_by, having, ..
5795            } = &s.body.select
5796            {
5797                assert_eq!(group_by.len(), 1);
5798                assert!(having.is_some(), "HAVING clause must be present");
5799            } else {
5800                unreachable!("expected Select core");
5801            }
5802        } else {
5803            unreachable!("expected Select");
5804        }
5805    }
5806
5807    #[test]
5808    fn test_compound_union() {
5809        let stmt = parse_one("SELECT 1 UNION SELECT 2");
5810        if let Statement::Select(s) = stmt {
5811            assert_eq!(s.body.compounds.len(), 1);
5812            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
5813        } else {
5814            unreachable!("expected Select");
5815        }
5816    }
5817
5818    #[test]
5819    fn test_compound_union_all() {
5820        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
5821        if let Statement::Select(s) = stmt {
5822            assert_eq!(s.body.compounds.len(), 1);
5823            assert_eq!(s.body.compounds[0].0, CompoundOp::UnionAll);
5824        } else {
5825            unreachable!("expected Select");
5826        }
5827    }
5828
5829    #[test]
5830    fn test_compound_intersect() {
5831        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
5832        if let Statement::Select(s) = stmt {
5833            assert_eq!(s.body.compounds.len(), 1);
5834            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
5835        } else {
5836            unreachable!("expected Select");
5837        }
5838    }
5839
5840    #[test]
5841    fn test_compound_except() {
5842        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
5843        if let Statement::Select(s) = stmt {
5844            assert_eq!(s.body.compounds.len(), 1);
5845            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
5846        } else {
5847            unreachable!("expected Select");
5848        }
5849    }
5850
5851    #[test]
5852    fn test_compound_order_applies_to_whole() {
5853        // ORDER BY and LIMIT apply to the entire compound result per SQL spec.
5854        let stmt = parse_one("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
5855        if let Statement::Select(s) = stmt {
5856            assert_eq!(s.body.compounds.len(), 1);
5857            assert_eq!(s.order_by.len(), 1, "ORDER BY must be on compound");
5858            assert!(s.limit.is_some(), "LIMIT must be on compound");
5859        } else {
5860            unreachable!("expected Select");
5861        }
5862    }
5863
5864    #[test]
5865    fn test_compound_three_way() {
5866        let stmt = parse_one("SELECT 1 UNION SELECT 2 INTERSECT SELECT 3");
5867        if let Statement::Select(s) = stmt {
5868            assert_eq!(s.body.compounds.len(), 2);
5869            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
5870            assert_eq!(s.body.compounds[1].0, CompoundOp::Intersect);
5871        } else {
5872            unreachable!("expected Select");
5873        }
5874    }
5875
5876    #[test]
5877    fn test_cte_not_materialized() {
5878        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
5879        if let Statement::Select(s) = stmt {
5880            let with = s.with.as_ref().expect("WITH clause");
5881            assert_eq!(
5882                with.ctes[0].materialized,
5883                Some(CteMaterialized::NotMaterialized)
5884            );
5885        } else {
5886            unreachable!("expected Select");
5887        }
5888    }
5889
5890    #[test]
5891    fn test_cte_with_explicit_columns() {
5892        let stmt = parse_one("WITH cte(a, b, c) AS (SELECT 1, 2, 3) SELECT * FROM cte");
5893        if let Statement::Select(s) = stmt {
5894            let with = s.with.as_ref().expect("WITH clause");
5895            assert_eq!(with.ctes[0].columns, vec!["a", "b", "c"]);
5896        } else {
5897            unreachable!("expected Select");
5898        }
5899    }
5900
5901    #[test]
5902    fn test_window_frame_range() {
5903        let stmt = parse_one(
5904            "SELECT sum(x) OVER (ORDER BY y RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t",
5905        );
5906        if let Statement::Select(s) = stmt {
5907            if let SelectCore::Select { columns, .. } = &s.body.select {
5908                match &columns[0] {
5909                    ResultColumn::Expr {
5910                        expr:
5911                            Expr::FunctionCall {
5912                                over: Some(over), ..
5913                            },
5914                        ..
5915                    } => {
5916                        let frame = over.frame.as_ref().expect("frame spec");
5917                        assert_eq!(frame.frame_type, FrameType::Range);
5918                        assert!(matches!(frame.start, FrameBound::UnboundedPreceding));
5919                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
5920                    }
5921                    other => unreachable!("expected window function, got {other:?}"),
5922                }
5923            } else {
5924                unreachable!("expected Select core");
5925            }
5926        } else {
5927            unreachable!("expected Select");
5928        }
5929    }
5930
5931    #[test]
5932    fn test_window_frame_groups() {
5933        let stmt = parse_one(
5934            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
5935        );
5936        if let Statement::Select(s) = stmt {
5937            if let SelectCore::Select { columns, .. } = &s.body.select {
5938                match &columns[0] {
5939                    ResultColumn::Expr {
5940                        expr:
5941                            Expr::FunctionCall {
5942                                over: Some(over), ..
5943                            },
5944                        ..
5945                    } => {
5946                        let frame = over.frame.as_ref().expect("frame spec");
5947                        assert_eq!(frame.frame_type, FrameType::Groups);
5948                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
5949                        assert!(matches!(frame.end, Some(FrameBound::Following(_))));
5950                    }
5951                    other => unreachable!("expected window function, got {other:?}"),
5952                }
5953            } else {
5954                unreachable!("expected Select core");
5955            }
5956        } else {
5957            unreachable!("expected Select");
5958        }
5959    }
5960
5961    #[test]
5962    fn test_window_frame_exclude_current_row() {
5963        let stmt = parse_one(
5964            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
5965             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
5966        );
5967        if let Statement::Select(s) = stmt {
5968            if let SelectCore::Select { columns, .. } = &s.body.select {
5969                match &columns[0] {
5970                    ResultColumn::Expr {
5971                        expr:
5972                            Expr::FunctionCall {
5973                                over: Some(over), ..
5974                            },
5975                        ..
5976                    } => {
5977                        let frame = over.frame.as_ref().expect("frame spec");
5978                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
5979                    }
5980                    other => unreachable!("expected window function, got {other:?}"),
5981                }
5982            } else {
5983                unreachable!("expected Select core");
5984            }
5985        } else {
5986            unreachable!("expected Select");
5987        }
5988    }
5989
5990    #[test]
5991    fn test_window_frame_exclude_ties() {
5992        let stmt = parse_one(
5993            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
5994             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
5995        );
5996        if let Statement::Select(s) = stmt {
5997            if let SelectCore::Select { columns, .. } = &s.body.select {
5998                match &columns[0] {
5999                    ResultColumn::Expr {
6000                        expr:
6001                            Expr::FunctionCall {
6002                                over: Some(over), ..
6003                            },
6004                        ..
6005                    } => {
6006                        let frame = over.frame.as_ref().expect("frame spec");
6007                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
6008                    }
6009                    other => unreachable!("expected window function, got {other:?}"),
6010                }
6011            } else {
6012                unreachable!("expected Select core");
6013            }
6014        } else {
6015            unreachable!("expected Select");
6016        }
6017    }
6018
6019    #[test]
6020    fn test_window_frame_exclude_group() {
6021        let stmt =
6022            parse_one("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
6023        if let Statement::Select(s) = stmt {
6024            if let SelectCore::Select { columns, .. } = &s.body.select {
6025                match &columns[0] {
6026                    ResultColumn::Expr {
6027                        expr:
6028                            Expr::FunctionCall {
6029                                over: Some(over), ..
6030                            },
6031                        ..
6032                    } => {
6033                        let frame = over.frame.as_ref().expect("frame spec");
6034                        assert_eq!(frame.frame_type, FrameType::Groups);
6035                        assert_eq!(frame.exclude, Some(FrameExclude::Group));
6036                    }
6037                    other => unreachable!("expected window function, got {other:?}"),
6038                }
6039            } else {
6040                unreachable!("expected Select core");
6041            }
6042        } else {
6043            unreachable!("expected Select");
6044        }
6045    }
6046
6047    #[test]
6048    fn test_window_frame_unbounded_following() {
6049        let stmt = parse_one(
6050            "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM t",
6051        );
6052        if let Statement::Select(s) = stmt {
6053            if let SelectCore::Select { columns, .. } = &s.body.select {
6054                match &columns[0] {
6055                    ResultColumn::Expr {
6056                        expr:
6057                            Expr::FunctionCall {
6058                                over: Some(over), ..
6059                            },
6060                        ..
6061                    } => {
6062                        let frame = over.frame.as_ref().expect("frame spec");
6063                        assert!(matches!(frame.start, FrameBound::CurrentRow));
6064                        assert!(matches!(frame.end, Some(FrameBound::UnboundedFollowing)));
6065                    }
6066                    other => unreachable!("expected window function, got {other:?}"),
6067                }
6068            } else {
6069                unreachable!("expected Select core");
6070            }
6071        } else {
6072            unreachable!("expected Select");
6073        }
6074    }
6075
6076    #[test]
6077    fn test_window_frame_rejects_illegal_bound_order_with_exact_span() {
6078        for (sql, rejected, kind) in [
6079            (
6080                "SELECT sum(x) OVER (ROWS UNBOUNDED FOLLOWING) FROM t",
6081                "FOLLOWING",
6082                ParseErrorKind::UnexpectedToken,
6083            ),
6084            (
6085                "SELECT sum(x) OVER (ROWS 1 FOLLOWING) FROM t",
6086                "1",
6087                ParseErrorKind::Semantic,
6088            ),
6089            (
6090                "SELECT sum(x) OVER (ROWS BETWEEN UNBOUNDED FOLLOWING AND UNBOUNDED FOLLOWING) FROM t",
6091                "FOLLOWING",
6092                ParseErrorKind::UnexpectedToken,
6093            ),
6094            (
6095                "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING) FROM t",
6096                "PRECEDING",
6097                ParseErrorKind::UnexpectedToken,
6098            ),
6099            (
6100                "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND 1 PRECEDING) FROM t",
6101                "1",
6102                ParseErrorKind::Semantic,
6103            ),
6104            (
6105                "SELECT sum(x) OVER (ROWS BETWEEN 1 FOLLOWING AND CURRENT ROW) FROM t",
6106                "CURRENT",
6107                ParseErrorKind::Semantic,
6108            ),
6109        ] {
6110            let error = parse_first_statement_with_tail(sql)
6111                .expect_err("illegal window-frame boundaries must be rejected");
6112            assert_eq!(error.kind, kind, "diagnostic kind for `{sql}`");
6113            if error.kind == ParseErrorKind::Semantic {
6114                assert_eq!(error.message, "unsupported frame specification");
6115            }
6116            assert_eq!(
6117                &sql[error.span.start as usize..error.span.end as usize],
6118                rejected,
6119                "the diagnostic for `{sql}` must point at the illegal boundary"
6120            );
6121        }
6122    }
6123
6124    #[test]
6125    fn test_window_frame_accepts_legal_categorical_order_without_offset_comparison() {
6126        for sql in [
6127            "SELECT sum(x) OVER (ROWS 1 PRECEDING) FROM t",
6128            "SELECT sum(x) OVER (ROWS BETWEEN 1 PRECEDING AND 2 PRECEDING) FROM t",
6129            "SELECT sum(x) OVER (ROWS BETWEEN 2 FOLLOWING AND UNBOUNDED FOLLOWING) FROM t",
6130            "SELECT sum(x) OVER (RANGE BETWEEN CURRENT ROW AND 1 FOLLOWING) FROM t",
6131            "SELECT sum(x) OVER (GROUPS BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM t",
6132        ] {
6133            parse_full_select(sql);
6134        }
6135    }
6136
6137    #[test]
6138    fn test_filter_clause_aggregate() {
6139        let stmt = parse_one("SELECT count(*) FILTER (WHERE x > 0) FROM t");
6140        if let Statement::Select(s) = stmt {
6141            if let SelectCore::Select { columns, .. } = &s.body.select {
6142                match &columns[0] {
6143                    ResultColumn::Expr {
6144                        expr: Expr::FunctionCall { filter, .. },
6145                        ..
6146                    } => {
6147                        assert!(
6148                            filter.is_some(),
6149                            "FILTER clause must be present on aggregate"
6150                        );
6151                    }
6152                    other => unreachable!("expected function call with filter, got {other:?}"),
6153                }
6154            } else {
6155                unreachable!("expected Select core");
6156            }
6157        } else {
6158            unreachable!("expected Select");
6159        }
6160    }
6161
6162    #[test]
6163    fn test_filter_clause_window() {
6164        let stmt = parse_one("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
6165        if let Statement::Select(s) = stmt {
6166            if let SelectCore::Select { columns, .. } = &s.body.select {
6167                match &columns[0] {
6168                    ResultColumn::Expr {
6169                        expr:
6170                            Expr::FunctionCall {
6171                                filter,
6172                                over: Some(_),
6173                                ..
6174                            },
6175                        ..
6176                    } => {
6177                        assert!(
6178                            filter.is_some(),
6179                            "FILTER clause must be present on window function"
6180                        );
6181                    }
6182                    other => unreachable!("expected window function with filter, got {other:?}"),
6183                }
6184            } else {
6185                unreachable!("expected Select core");
6186            }
6187        } else {
6188            unreachable!("expected Select");
6189        }
6190    }
6191
6192    #[test]
6193    fn test_subquery_in_from() {
6194        let stmt = parse_one("SELECT sub.x FROM (SELECT 1 AS x) AS sub");
6195        if let Statement::Select(s) = stmt {
6196            if let SelectCore::Select { from, .. } = &s.body.select {
6197                let from = from.as_ref().expect("FROM clause");
6198                match &from.source {
6199                    TableOrSubquery::Subquery { alias, .. } => {
6200                        assert_eq!(alias.as_deref(), Some("sub"));
6201                    }
6202                    other => unreachable!("expected subquery source, got {other:?}"),
6203                }
6204            } else {
6205                unreachable!("expected Select core");
6206            }
6207        } else {
6208            unreachable!("expected Select");
6209        }
6210    }
6211
6212    #[test]
6213    fn test_multiple_joins_chain() {
6214        let stmt = parse_one(
6215            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id \
6216             LEFT JOIN c ON b.id = c.b_id \
6217             CROSS JOIN d",
6218        );
6219        if let Statement::Select(s) = stmt {
6220            if let SelectCore::Select { from, .. } = &s.body.select {
6221                let from = from.as_ref().expect("FROM clause");
6222                assert_eq!(from.joins.len(), 3);
6223                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
6224                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
6225                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
6226            } else {
6227                unreachable!("expected Select core");
6228            }
6229        } else {
6230            unreachable!("expected Select");
6231        }
6232    }
6233
6234    #[test]
6235    fn test_natural_left_join() {
6236        let stmt = parse_one("SELECT * FROM a NATURAL LEFT JOIN b");
6237        if let Statement::Select(s) = stmt {
6238            if let SelectCore::Select { from, .. } = &s.body.select {
6239                let from = from.as_ref().expect("FROM clause");
6240                let jt = &from.joins[0].join_type;
6241                assert!(jt.natural, "must be NATURAL");
6242                assert_eq!(jt.kind, JoinKind::Left);
6243            } else {
6244                unreachable!("expected Select core");
6245            }
6246        } else {
6247            unreachable!("expected Select");
6248        }
6249    }
6250
6251    #[test]
6252    fn test_select_nulls_first_default_asc() {
6253        // Verify NULLS FIRST with explicit ASC direction.
6254        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
6255        if let Statement::Select(s) = stmt {
6256            assert_eq!(s.order_by.len(), 1);
6257            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
6258            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
6259        } else {
6260            unreachable!("expected Select");
6261        }
6262    }
6263
6264    #[test]
6265    fn test_select_nulls_last_desc() {
6266        // Verify NULLS LAST with explicit DESC direction.
6267        let stmt = parse_one("SELECT a FROM t ORDER BY a DESC NULLS LAST");
6268        if let Statement::Select(s) = stmt {
6269            assert_eq!(s.order_by.len(), 1);
6270            assert_eq!(s.order_by[0].direction, Some(SortDirection::Desc));
6271            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
6272        } else {
6273            unreachable!("expected Select");
6274        }
6275    }
6276
6277    // -----------------------------------------------------------------------
6278    // bd-2d6i §12.1 roundtrip coverage for advanced SELECT forms
6279    // -----------------------------------------------------------------------
6280
6281    #[test]
6282    fn test_roundtrip_select_filter_clause() {
6283        assert_roundtrip("SELECT count(*) FILTER (WHERE x > 0) FROM t");
6284    }
6285
6286    #[test]
6287    fn test_roundtrip_select_window_frame_groups() {
6288        assert_roundtrip(
6289            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
6290        );
6291    }
6292
6293    #[test]
6294    fn test_roundtrip_select_window_frame_exclude() {
6295        assert_roundtrip(
6296            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6297             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
6298        );
6299        assert_roundtrip(
6300            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6301             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
6302        );
6303        assert_roundtrip("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
6304    }
6305
6306    #[test]
6307    fn test_roundtrip_select_nulls_order() {
6308        assert_roundtrip("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
6309        assert_roundtrip("SELECT a FROM t ORDER BY a DESC NULLS LAST");
6310    }
6311
6312    #[test]
6313    fn test_roundtrip_select_values() {
6314        assert_roundtrip("VALUES (1, 2), (3, 4)");
6315    }
6316
6317    #[test]
6318    fn test_roundtrip_select_compound_order_limit() {
6319        assert_roundtrip("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
6320    }
6321
6322    #[test]
6323    fn test_roundtrip_select_cte_not_materialized() {
6324        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
6325    }
6326
6327    #[test]
6328    fn test_roundtrip_select_natural_left_join() {
6329        assert_roundtrip("SELECT * FROM a NATURAL LEFT JOIN b");
6330    }
6331
6332    #[test]
6333    fn test_roundtrip_select_indexed_by() {
6334        assert_roundtrip("SELECT * FROM t INDEXED BY idx_t WHERE x = 1");
6335    }
6336
6337    #[test]
6338    fn test_roundtrip_select_filter_window_combined() {
6339        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
6340    }
6341
6342    #[test]
6343    fn test_serializer_regression_window_base_name_and_extensions_roundtrip() {
6344        assert_roundtrip("SELECT sum(x) OVER base FROM t");
6345        assert_roundtrip(
6346            "SELECT sum(x) OVER (base PARTITION BY p ORDER BY y \
6347             ROWS BETWEEN z PRECEDING AND CURRENT ROW) FROM t",
6348        );
6349    }
6350
6351    #[test]
6352    fn test_roundtrip_select_three_way_compound() {
6353        assert_roundtrip("SELECT 1 UNION SELECT 2 EXCEPT SELECT 3");
6354    }
6355
6356    #[test]
6357    fn test_roundtrip_select_multiple_joins() {
6358        assert_roundtrip(
6359            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id LEFT JOIN c ON b.id = c.b_id",
6360        );
6361    }
6362
6363    // -----------------------------------------------------------------------
6364    // bd-2d6i §12.1 — remaining required tests (exact names per bead spec)
6365    // -----------------------------------------------------------------------
6366
6367    #[test]
6368    fn test_select_star() {
6369        // SELECT * returns all columns from all tables.
6370        let stmt = parse_one("SELECT * FROM t");
6371        if let Statement::Select(s) = stmt {
6372            if let SelectCore::Select { columns, .. } = &s.body.select {
6373                assert!(matches!(columns[0], ResultColumn::Star));
6374            } else {
6375                unreachable!("expected Select core");
6376            }
6377        } else {
6378            unreachable!("expected Select");
6379        }
6380    }
6381
6382    #[test]
6383    fn test_inner_join_on() {
6384        // INNER JOIN ON produces correct intersection.
6385        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
6386        if let Statement::Select(s) = stmt {
6387            if let SelectCore::Select { from, .. } = &s.body.select {
6388                let from = from.as_ref().expect("FROM clause");
6389                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
6390                assert!(matches!(
6391                    from.joins[0].constraint,
6392                    Some(JoinConstraint::On(_))
6393                ));
6394            } else {
6395                unreachable!("expected Select core");
6396            }
6397        } else {
6398            unreachable!("expected Select");
6399        }
6400    }
6401
6402    #[test]
6403    fn test_left_outer_join() {
6404        // LEFT JOIN returns all left rows with NULLs for non-matching right.
6405        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
6406        if let Statement::Select(s) = stmt {
6407            if let SelectCore::Select { from, .. } = &s.body.select {
6408                let from = from.as_ref().expect("FROM clause");
6409                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
6410            } else {
6411                unreachable!("expected Select core");
6412            }
6413        } else {
6414            unreachable!("expected Select");
6415        }
6416    }
6417
6418    #[test]
6419    fn test_right_outer_join() {
6420        // RIGHT JOIN returns all right rows (3.39+ feature).
6421        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
6422        if let Statement::Select(s) = stmt {
6423            if let SelectCore::Select { from, .. } = &s.body.select {
6424                let from = from.as_ref().expect("FROM clause");
6425                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
6426            } else {
6427                unreachable!("expected Select core");
6428            }
6429        } else {
6430            unreachable!("expected Select");
6431        }
6432    }
6433
6434    #[test]
6435    fn test_full_outer_join() {
6436        // FULL OUTER JOIN returns rows from both tables.
6437        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
6438        if let Statement::Select(s) = stmt {
6439            if let SelectCore::Select { from, .. } = &s.body.select {
6440                let from = from.as_ref().expect("FROM clause");
6441                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
6442            } else {
6443                unreachable!("expected Select core");
6444            }
6445        } else {
6446            unreachable!("expected Select");
6447        }
6448    }
6449
6450    #[test]
6451    fn test_cross_join_no_reorder() {
6452        // CROSS JOIN prevents optimizer reordering; parser must produce JoinKind::Cross.
6453        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
6454        if let Statement::Select(s) = stmt {
6455            if let SelectCore::Select { from, .. } = &s.body.select {
6456                let from = from.as_ref().expect("FROM clause");
6457                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
6458                // Cross joins must NOT have an ON or USING constraint.
6459                assert!(from.joins[0].constraint.is_none());
6460            } else {
6461                unreachable!("expected Select core");
6462            }
6463        } else {
6464            unreachable!("expected Select");
6465        }
6466    }
6467
6468    #[test]
6469    fn test_natural_join() {
6470        // NATURAL JOIN uses shared column names for implicit ON.
6471        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
6472        if let Statement::Select(s) = stmt {
6473            if let SelectCore::Select { from, .. } = &s.body.select {
6474                let from = from.as_ref().expect("FROM clause");
6475                assert!(from.joins[0].join_type.natural);
6476            } else {
6477                unreachable!("expected Select core");
6478            }
6479        } else {
6480            unreachable!("expected Select");
6481        }
6482    }
6483
6484    #[test]
6485    fn test_using_clause() {
6486        // JOIN USING joins on specified shared columns.
6487        let stmt = parse_one("SELECT * FROM a JOIN b USING (id, name)");
6488        if let Statement::Select(s) = stmt {
6489            if let SelectCore::Select { from, .. } = &s.body.select {
6490                let from = from.as_ref().expect("FROM clause");
6491                match &from.joins[0].constraint {
6492                    Some(JoinConstraint::Using(cols)) => {
6493                        assert_eq!(cols.len(), 2);
6494                        assert_eq!(cols[0], "id");
6495                        assert_eq!(cols[1], "name");
6496                    }
6497                    other => unreachable!("expected USING constraint, got {other:?}"),
6498                }
6499            } else {
6500                unreachable!("expected Select core");
6501            }
6502        } else {
6503            unreachable!("expected Select");
6504        }
6505    }
6506
6507    #[test]
6508    fn test_cte_basic() {
6509        // WITH clause defines reusable named subquery.
6510        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
6511        if let Statement::Select(s) = stmt {
6512            let with = s.with.as_ref().expect("WITH clause");
6513            assert!(!with.recursive);
6514            assert_eq!(with.ctes.len(), 1);
6515            assert_eq!(with.ctes[0].name, "cte");
6516        } else {
6517            unreachable!("expected Select");
6518        }
6519    }
6520
6521    #[test]
6522    fn test_cte_recursive_union_all() {
6523        // Recursive CTE with UNION ALL generates rows.
6524        let stmt = parse_one(
6525            "WITH RECURSIVE cnt(x) AS (\
6526             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
6527             ) SELECT x FROM cnt",
6528        );
6529        if let Statement::Select(s) = stmt {
6530            let with = s.with.as_ref().expect("WITH clause");
6531            assert!(with.recursive);
6532            assert_eq!(with.ctes[0].name, "cnt");
6533            // Verify the CTE body contains a UNION ALL compound.
6534            let cte_body = &with.ctes[0].query;
6535            assert_eq!(cte_body.body.compounds.len(), 1);
6536            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::UnionAll);
6537        } else {
6538            unreachable!("expected Select");
6539        }
6540    }
6541
6542    #[test]
6543    fn test_cte_recursive_union_cycle_detection() {
6544        // Recursive CTE with UNION (not UNION ALL) detects cycles via dedup.
6545        let stmt = parse_one(
6546            "WITH RECURSIVE paths(a, b) AS (\
6547             SELECT src, dst FROM edges \
6548             UNION \
6549             SELECT p.a, e.dst FROM paths p JOIN edges e ON p.b = e.src\
6550             ) SELECT * FROM paths",
6551        );
6552        if let Statement::Select(s) = stmt {
6553            let with = s.with.as_ref().expect("WITH clause");
6554            assert!(with.recursive);
6555            // UNION (not UNION ALL) provides implicit cycle detection.
6556            let cte_body = &with.ctes[0].query;
6557            assert_eq!(cte_body.body.compounds.len(), 1);
6558            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::Union);
6559        } else {
6560            unreachable!("expected Select");
6561        }
6562    }
6563
6564    #[test]
6565    fn test_cte_materialized_hint() {
6566        // MATERIALIZED forces single evaluation.
6567        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
6568        if let Statement::Select(s) = stmt {
6569            let with = s.with.as_ref().expect("WITH clause");
6570            assert_eq!(
6571                with.ctes[0].materialized,
6572                Some(CteMaterialized::Materialized)
6573            );
6574        } else {
6575            unreachable!("expected Select");
6576        }
6577    }
6578
6579    #[test]
6580    fn test_cte_not_materialized_hint() {
6581        // NOT MATERIALIZED allows inlining.
6582        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
6583        if let Statement::Select(s) = stmt {
6584            let with = s.with.as_ref().expect("WITH clause");
6585            assert_eq!(
6586                with.ctes[0].materialized,
6587                Some(CteMaterialized::NotMaterialized)
6588            );
6589        } else {
6590            unreachable!("expected Select");
6591        }
6592    }
6593
6594    #[test]
6595    fn test_window_partition_by() {
6596        // PARTITION BY correctly groups window function output.
6597        let stmt = parse_one("SELECT sum(x) OVER (PARTITION BY dept) FROM emp");
6598        if let Statement::Select(s) = stmt {
6599            if let SelectCore::Select { columns, .. } = &s.body.select {
6600                match &columns[0] {
6601                    ResultColumn::Expr {
6602                        expr:
6603                            Expr::FunctionCall {
6604                                over: Some(over), ..
6605                            },
6606                        ..
6607                    } => {
6608                        assert_eq!(over.partition_by.len(), 1);
6609                    }
6610                    other => unreachable!("expected window function, got {other:?}"),
6611                }
6612            } else {
6613                unreachable!("expected Select core");
6614            }
6615        } else {
6616            unreachable!("expected Select");
6617        }
6618    }
6619
6620    #[test]
6621    fn test_window_order_by() {
6622        // ORDER BY within window function controls row ordering.
6623        let stmt = parse_one("SELECT row_number() OVER (ORDER BY salary DESC) FROM emp");
6624        if let Statement::Select(s) = stmt {
6625            if let SelectCore::Select { columns, .. } = &s.body.select {
6626                match &columns[0] {
6627                    ResultColumn::Expr {
6628                        expr:
6629                            Expr::FunctionCall {
6630                                over: Some(over), ..
6631                            },
6632                        ..
6633                    } => {
6634                        assert_eq!(over.order_by.len(), 1);
6635                        assert_eq!(over.order_by[0].direction, Some(SortDirection::Desc));
6636                    }
6637                    other => unreachable!("expected window function, got {other:?}"),
6638                }
6639            } else {
6640                unreachable!("expected Select core");
6641            }
6642        } else {
6643            unreachable!("expected Select");
6644        }
6645    }
6646
6647    #[test]
6648    fn test_window_frame_rows() {
6649        // ROWS frame spec limits window to specified row range.
6650        let stmt = parse_one(
6651            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
6652        );
6653        if let Statement::Select(s) = stmt {
6654            if let SelectCore::Select { columns, .. } = &s.body.select {
6655                match &columns[0] {
6656                    ResultColumn::Expr {
6657                        expr:
6658                            Expr::FunctionCall {
6659                                over: Some(over), ..
6660                            },
6661                        ..
6662                    } => {
6663                        let frame = over.frame.as_ref().expect("frame spec");
6664                        assert_eq!(frame.frame_type, FrameType::Rows);
6665                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
6666                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
6667                    }
6668                    other => unreachable!("expected window function, got {other:?}"),
6669                }
6670            } else {
6671                unreachable!("expected Select core");
6672            }
6673        } else {
6674            unreachable!("expected Select");
6675        }
6676    }
6677
6678    #[test]
6679    fn test_window_exclude_current_row() {
6680        // EXCLUDE CURRENT ROW omits current row from frame.
6681        let stmt = parse_one(
6682            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6683             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
6684        );
6685        if let Statement::Select(s) = stmt {
6686            if let SelectCore::Select { columns, .. } = &s.body.select {
6687                match &columns[0] {
6688                    ResultColumn::Expr {
6689                        expr:
6690                            Expr::FunctionCall {
6691                                over: Some(over), ..
6692                            },
6693                        ..
6694                    } => {
6695                        let frame = over.frame.as_ref().expect("frame spec");
6696                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
6697                    }
6698                    other => unreachable!("expected window function, got {other:?}"),
6699                }
6700            } else {
6701                unreachable!("expected Select core");
6702            }
6703        } else {
6704            unreachable!("expected Select");
6705        }
6706    }
6707
6708    #[test]
6709    fn test_window_exclude_ties() {
6710        // EXCLUDE TIES omits peers of current row.
6711        let stmt = parse_one(
6712            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
6713             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
6714        );
6715        if let Statement::Select(s) = stmt {
6716            if let SelectCore::Select { columns, .. } = &s.body.select {
6717                match &columns[0] {
6718                    ResultColumn::Expr {
6719                        expr:
6720                            Expr::FunctionCall {
6721                                over: Some(over), ..
6722                            },
6723                        ..
6724                    } => {
6725                        let frame = over.frame.as_ref().expect("frame spec");
6726                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
6727                    }
6728                    other => unreachable!("expected window function, got {other:?}"),
6729                }
6730            } else {
6731                unreachable!("expected Select core");
6732            }
6733        } else {
6734            unreachable!("expected Select");
6735        }
6736    }
6737
6738    #[test]
6739    fn test_nulls_first_asc() {
6740        // NULLS FIRST with ASC puts NULLs before non-NULL values.
6741        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
6742        if let Statement::Select(s) = stmt {
6743            assert_eq!(s.order_by.len(), 1);
6744            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
6745            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
6746        } else {
6747            unreachable!("expected Select");
6748        }
6749    }
6750
6751    #[test]
6752    fn test_nulls_last_asc() {
6753        // NULLS LAST with ASC puts NULLs after non-NULL values.
6754        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS LAST");
6755        if let Statement::Select(s) = stmt {
6756            assert_eq!(s.order_by.len(), 1);
6757            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
6758            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
6759        } else {
6760            unreachable!("expected Select");
6761        }
6762    }
6763
6764    #[test]
6765    fn test_distinct_deduplicates() {
6766        // SELECT DISTINCT removes duplicate rows (parser-level: keyword present).
6767        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
6768        if let Statement::Select(s) = stmt {
6769            if let SelectCore::Select { distinct, .. } = &s.body.select {
6770                assert_eq!(*distinct, Distinctness::Distinct);
6771            } else {
6772                unreachable!("expected Select core");
6773            }
6774        } else {
6775            unreachable!("expected Select");
6776        }
6777    }
6778
6779    #[test]
6780    fn test_limit_offset() {
6781        // LIMIT N OFFSET M skips M rows and returns N.
6782        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET 20");
6783        if let Statement::Select(s) = stmt {
6784            let limit = s.limit.expect("LIMIT clause");
6785            assert!(matches!(
6786                limit.limit,
6787                Expr::Literal(Literal::Integer(10), _)
6788            ));
6789            assert!(matches!(
6790                limit.offset,
6791                Some(Expr::Literal(Literal::Integer(20), _))
6792            ));
6793        } else {
6794            unreachable!("expected Select");
6795        }
6796    }
6797
6798    #[test]
6799    fn test_limit_comma_syntax() {
6800        // LIMIT offset,count (MySQL syntax) — offset first, count second.
6801        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
6802        if let Statement::Select(s) = stmt {
6803            let limit = s.limit.expect("LIMIT clause");
6804            // In MySQL syntax, LIMIT 5, 10 means offset=5, count=10.
6805            assert!(matches!(
6806                limit.limit,
6807                Expr::Literal(Literal::Integer(10), _)
6808            ));
6809            assert!(matches!(
6810                limit.offset,
6811                Some(Expr::Literal(Literal::Integer(5), _))
6812            ));
6813        } else {
6814            unreachable!("expected Select");
6815        }
6816    }
6817
6818    #[test]
6819    fn test_negative_limit_unlimited() {
6820        // Negative LIMIT means unlimited (parser accepts negative literal).
6821        let stmt = parse_one("SELECT a FROM t LIMIT -1");
6822        if let Statement::Select(s) = stmt {
6823            let limit = s.limit.expect("LIMIT clause");
6824            // Parser may represent -1 as UnaryOp::Negate on Integer(1),
6825            // or as Integer(-1). Either is valid.
6826            match &limit.limit {
6827                Expr::UnaryOp {
6828                    op: fsqlite_ast::UnaryOp::Negate,
6829                    ..
6830                } => {}
6831                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
6832                other => unreachable!("expected negative limit expression, got {other:?}"),
6833            }
6834        } else {
6835            unreachable!("expected Select");
6836        }
6837    }
6838
6839    #[test]
6840    fn test_negative_offset_zero() {
6841        // Negative OFFSET treated as zero (parser accepts negative literal).
6842        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET -5");
6843        if let Statement::Select(s) = stmt {
6844            let limit = s.limit.expect("LIMIT clause");
6845            assert!(limit.offset.is_some());
6846            match limit.offset.as_ref().unwrap() {
6847                Expr::UnaryOp {
6848                    op: fsqlite_ast::UnaryOp::Negate,
6849                    ..
6850                } => {}
6851                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
6852                other => unreachable!("expected negative offset expression, got {other:?}"),
6853            }
6854        } else {
6855            unreachable!("expected Select");
6856        }
6857    }
6858
6859    #[test]
6860    fn test_current_date_constant() {
6861        // current_date is parsed as a literal keyword.
6862        let stmt = parse_one("SELECT CURRENT_DATE");
6863        if let Statement::Select(s) = stmt {
6864            if let SelectCore::Select { columns, .. } = &s.body.select {
6865                match &columns[0] {
6866                    ResultColumn::Expr {
6867                        expr: Expr::Literal(Literal::CurrentDate, _),
6868                        ..
6869                    } => {}
6870                    other => unreachable!("expected CURRENT_DATE literal, got {other:?}"),
6871                }
6872            } else {
6873                unreachable!("expected Select core");
6874            }
6875        } else {
6876            unreachable!("expected Select");
6877        }
6878    }
6879
6880    #[test]
6881    fn test_current_time_constant() {
6882        // current_time is parsed as a literal keyword.
6883        let stmt = parse_one("SELECT CURRENT_TIME");
6884        if let Statement::Select(s) = stmt {
6885            if let SelectCore::Select { columns, .. } = &s.body.select {
6886                match &columns[0] {
6887                    ResultColumn::Expr {
6888                        expr: Expr::Literal(Literal::CurrentTime, _),
6889                        ..
6890                    } => {}
6891                    other => unreachable!("expected CURRENT_TIME literal, got {other:?}"),
6892                }
6893            } else {
6894                unreachable!("expected Select core");
6895            }
6896        } else {
6897            unreachable!("expected Select");
6898        }
6899    }
6900
6901    #[test]
6902    fn test_current_timestamp_constant() {
6903        // current_timestamp is parsed as a literal keyword.
6904        let stmt = parse_one("SELECT CURRENT_TIMESTAMP");
6905        if let Statement::Select(s) = stmt {
6906            if let SelectCore::Select { columns, .. } = &s.body.select {
6907                match &columns[0] {
6908                    ResultColumn::Expr {
6909                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
6910                        ..
6911                    } => {}
6912                    other => unreachable!("expected CURRENT_TIMESTAMP literal, got {other:?}"),
6913                }
6914            } else {
6915                unreachable!("expected Select core");
6916            }
6917        } else {
6918            unreachable!("expected Select");
6919        }
6920    }
6921
6922    #[test]
6923    fn test_date_constants_evaluated_once_per_statement() {
6924        // Parser-level: all three date/time constants parse as distinct Literal variants.
6925        // Runtime guarantee (evaluated once per stmt, not per row) is verified at VDBE level.
6926        let stmt = parse_one("SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP FROM t");
6927        if let Statement::Select(s) = stmt {
6928            if let SelectCore::Select { columns, .. } = &s.body.select {
6929                assert_eq!(columns.len(), 3);
6930                assert!(matches!(
6931                    &columns[0],
6932                    ResultColumn::Expr {
6933                        expr: Expr::Literal(Literal::CurrentDate, _),
6934                        ..
6935                    }
6936                ));
6937                assert!(matches!(
6938                    &columns[1],
6939                    ResultColumn::Expr {
6940                        expr: Expr::Literal(Literal::CurrentTime, _),
6941                        ..
6942                    }
6943                ));
6944                assert!(matches!(
6945                    &columns[2],
6946                    ResultColumn::Expr {
6947                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
6948                        ..
6949                    }
6950                ));
6951            } else {
6952                unreachable!("expected Select core");
6953            }
6954        } else {
6955            unreachable!("expected Select");
6956        }
6957    }
6958
6959    #[test]
6960    fn test_indexed_by_hint() {
6961        // FROM t1 INDEXED BY idx forces specified index.
6962        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
6963        if let Statement::Select(s) = stmt {
6964            if let SelectCore::Select { from, .. } = &s.body.select {
6965                let from = from.as_ref().expect("FROM clause");
6966                match &from.source {
6967                    TableOrSubquery::Table {
6968                        index_hint: Some(IndexHint::IndexedBy(name)),
6969                        ..
6970                    } => assert_eq!(name, "idx_t"),
6971                    other => unreachable!("expected indexed table source, got {other:?}"),
6972                }
6973            } else {
6974                unreachable!("expected Select core");
6975            }
6976        } else {
6977            unreachable!("expected Select");
6978        }
6979    }
6980
6981    #[test]
6982    fn test_not_indexed_hint() {
6983        // FROM t1 NOT INDEXED prevents index use.
6984        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
6985        if let Statement::Select(s) = stmt {
6986            if let SelectCore::Select { from, .. } = &s.body.select {
6987                let from = from.as_ref().expect("FROM clause");
6988                match &from.source {
6989                    TableOrSubquery::Table {
6990                        index_hint: Some(IndexHint::NotIndexed),
6991                        ..
6992                    } => {}
6993                    other => unreachable!("expected not-indexed table source, got {other:?}"),
6994                }
6995            } else {
6996                unreachable!("expected Select core");
6997            }
6998        } else {
6999            unreachable!("expected Select");
7000        }
7001    }
7002
7003    #[test]
7004    fn test_table_valued_function_in_from() {
7005        // FROM generate_series(1,100) works as table source.
7006        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
7007        if let Statement::Select(s) = stmt {
7008            if let SelectCore::Select { from, .. } = &s.body.select {
7009                let from = from.as_ref().expect("FROM clause");
7010                match &from.source {
7011                    TableOrSubquery::TableFunction { name, args, alias } => {
7012                        assert_eq!(name, "generate_series");
7013                        assert_eq!(args.len(), 2);
7014                        assert_eq!(alias.as_deref(), Some("gs"));
7015                    }
7016                    other => unreachable!("expected table-valued function source, got {other:?}"),
7017                }
7018            } else {
7019                unreachable!("expected Select core");
7020            }
7021        } else {
7022            unreachable!("expected Select");
7023        }
7024    }
7025
7026    // -----------------------------------------------------------------------
7027    // bd-1llo §12.2-12.4 INSERT + UPDATE + DELETE DML parsing tests
7028    // -----------------------------------------------------------------------
7029
7030    #[test]
7031    fn test_insert_values_single() {
7032        let stmt = parse_one("INSERT INTO t (a, b, c) VALUES (1, 'hello', 3.14)");
7033        if let Statement::Insert(i) = stmt {
7034            assert_eq!(i.columns, vec!["a", "b", "c"]);
7035            if let InsertSource::Values(rows) = &i.source {
7036                assert_eq!(rows.len(), 1);
7037                assert_eq!(rows[0].len(), 3);
7038            } else {
7039                unreachable!("expected Values source");
7040            }
7041        } else {
7042            unreachable!("expected Insert");
7043        }
7044    }
7045
7046    #[test]
7047    fn test_insert_values_multi() {
7048        let stmt = parse_one("INSERT INTO t (x, y) VALUES (1, 2), (3, 4), (5, 6)");
7049        if let Statement::Insert(i) = stmt {
7050            if let InsertSource::Values(rows) = &i.source {
7051                assert_eq!(rows.len(), 3);
7052                for row in rows {
7053                    assert_eq!(row.len(), 2);
7054                }
7055            } else {
7056                unreachable!("expected Values source");
7057            }
7058        } else {
7059            unreachable!("expected Insert");
7060        }
7061    }
7062
7063    #[test]
7064    fn test_insert_from_select() {
7065        let stmt = parse_one("INSERT INTO t2 (a, b) SELECT x, y FROM t1 WHERE x > 0");
7066        if let Statement::Insert(i) = stmt {
7067            assert!(matches!(i.source, InsertSource::Select(_)));
7068            assert_eq!(i.columns, vec!["a", "b"]);
7069        } else {
7070            unreachable!("expected Insert");
7071        }
7072    }
7073
7074    #[test]
7075    fn test_insert_from_select_without_from_clause() {
7076        let stmt = parse_one("INSERT INTO t (a) SELECT 1");
7077        if let Statement::Insert(i) = stmt {
7078            if let InsertSource::Select(select) = &i.source {
7079                if let SelectCore::Select { from, columns, .. } = &select.body.select {
7080                    assert!(from.is_none(), "SELECT 1 should parse without FROM");
7081                    assert_eq!(columns.len(), 1);
7082                } else {
7083                    unreachable!("expected Select core");
7084                }
7085            } else {
7086                unreachable!("expected Select source");
7087            }
7088        } else {
7089            unreachable!("expected Insert");
7090        }
7091    }
7092
7093    #[test]
7094    fn test_insert_from_select_subquery_source() {
7095        let stmt = parse_one("INSERT INTO t (a) SELECT sub.x FROM (SELECT 1 AS x) AS sub");
7096        if let Statement::Insert(i) = stmt {
7097            if let InsertSource::Select(select) = &i.source {
7098                if let SelectCore::Select { from, .. } = &select.body.select {
7099                    let from = from.as_ref().expect("FROM clause");
7100                    match &from.source {
7101                        TableOrSubquery::Subquery { alias, .. } => {
7102                            assert_eq!(alias.as_deref(), Some("sub"));
7103                        }
7104                        other => unreachable!("expected subquery source, got {other:?}"),
7105                    }
7106                } else {
7107                    unreachable!("expected Select core");
7108                }
7109            } else {
7110                unreachable!("expected Select source");
7111            }
7112        } else {
7113            unreachable!("expected Insert");
7114        }
7115    }
7116
7117    #[test]
7118    fn test_insert_from_select_table_function_source() {
7119        let stmt = parse_one("INSERT INTO t (a) SELECT gs.value FROM generate_series(1, 3) AS gs");
7120        if let Statement::Insert(i) = stmt {
7121            if let InsertSource::Select(select) = &i.source {
7122                if let SelectCore::Select { from, .. } = &select.body.select {
7123                    let from = from.as_ref().expect("FROM clause");
7124                    match &from.source {
7125                        TableOrSubquery::TableFunction { name, args, alias } => {
7126                            assert_eq!(name, "generate_series");
7127                            assert_eq!(args.len(), 2);
7128                            assert_eq!(alias.as_deref(), Some("gs"));
7129                        }
7130                        other => unreachable!("expected table function source, got {other:?}"),
7131                    }
7132                } else {
7133                    unreachable!("expected Select core");
7134                }
7135            } else {
7136                unreachable!("expected Select source");
7137            }
7138        } else {
7139            unreachable!("expected Insert");
7140        }
7141    }
7142
7143    #[test]
7144    fn test_insert_default_values() {
7145        let stmt = parse_one("INSERT INTO t DEFAULT VALUES");
7146        if let Statement::Insert(i) = stmt {
7147            assert!(matches!(i.source, InsertSource::DefaultValues));
7148            assert!(i.columns.is_empty());
7149        } else {
7150            unreachable!("expected Insert");
7151        }
7152    }
7153
7154    #[test]
7155    fn test_insert_or_abort() {
7156        let stmt = parse_one("INSERT OR ABORT INTO t (a) VALUES (1)");
7157        if let Statement::Insert(i) = stmt {
7158            assert_eq!(i.or_conflict, Some(ConflictAction::Abort));
7159        } else {
7160            unreachable!("expected Insert");
7161        }
7162    }
7163
7164    #[test]
7165    fn test_insert_or_rollback() {
7166        let stmt = parse_one("INSERT OR ROLLBACK INTO t (a) VALUES (1)");
7167        if let Statement::Insert(i) = stmt {
7168            assert_eq!(i.or_conflict, Some(ConflictAction::Rollback));
7169        } else {
7170            unreachable!("expected Insert");
7171        }
7172    }
7173
7174    #[test]
7175    fn test_insert_or_fail() {
7176        let stmt = parse_one("INSERT OR FAIL INTO t (a) VALUES (1)");
7177        if let Statement::Insert(i) = stmt {
7178            assert_eq!(i.or_conflict, Some(ConflictAction::Fail));
7179        } else {
7180            unreachable!("expected Insert");
7181        }
7182    }
7183
7184    #[test]
7185    fn test_insert_or_ignore() {
7186        let stmt = parse_one("INSERT OR IGNORE INTO t (a) VALUES (1)");
7187        if let Statement::Insert(i) = stmt {
7188            assert_eq!(i.or_conflict, Some(ConflictAction::Ignore));
7189        } else {
7190            unreachable!("expected Insert");
7191        }
7192    }
7193
7194    #[test]
7195    fn test_insert_or_replace() {
7196        // Both INSERT OR REPLACE and REPLACE INTO forms
7197        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1)");
7198        if let Statement::Insert(i) = stmt {
7199            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
7200        } else {
7201            unreachable!("expected Insert");
7202        }
7203    }
7204
7205    #[test]
7206    fn test_upsert_do_update() {
7207        let stmt = parse_one(
7208            "INSERT INTO t (a, b) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET b = excluded.b",
7209        );
7210        if let Statement::Insert(i) = stmt {
7211            assert_eq!(i.upsert.len(), 1);
7212            assert!(i.upsert[0].target.is_some());
7213            match &i.upsert[0].action {
7214                UpsertAction::Update {
7215                    assignments,
7216                    where_clause,
7217                } => {
7218                    assert_eq!(assignments.len(), 1);
7219                    assert!(where_clause.is_none());
7220                }
7221                UpsertAction::Nothing => unreachable!("expected Update action"),
7222            }
7223        } else {
7224            unreachable!("expected Insert");
7225        }
7226    }
7227
7228    #[test]
7229    fn test_upsert_do_nothing() {
7230        let stmt = parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
7231        if let Statement::Insert(i) = stmt {
7232            assert_eq!(i.upsert.len(), 1);
7233            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
7234        } else {
7235            unreachable!("expected Insert");
7236        }
7237    }
7238
7239    #[test]
7240    fn test_upsert_excluded_pseudo_table() {
7241        let stmt = parse_one(
7242            "INSERT INTO t (a, b) VALUES (1, 2) \
7243             ON CONFLICT (a) DO UPDATE SET b = excluded.b, a = excluded.a + 1",
7244        );
7245        if let Statement::Insert(i) = stmt {
7246            assert_eq!(i.upsert.len(), 1);
7247            if let UpsertAction::Update { assignments, .. } = &i.upsert[0].action {
7248                assert_eq!(assignments.len(), 2);
7249                // Verify excluded.b reference in first assignment
7250                match &assignments[0].value {
7251                    Expr::Column(col, _) => {
7252                        assert_eq!(col.table.as_deref(), Some("excluded"));
7253                        assert_eq!(col.column.as_ref(), "b");
7254                    }
7255                    other => unreachable!("expected Column ref to excluded.b, got {other:?}"),
7256                }
7257            } else {
7258                unreachable!("expected Update action");
7259            }
7260        } else {
7261            unreachable!("expected Insert");
7262        }
7263    }
7264
7265    #[test]
7266    fn test_upsert_multiple_on_conflict() {
7267        let stmt = parse_one(
7268            "INSERT INTO t (a, b) VALUES (1, 2) \
7269             ON CONFLICT (a) DO NOTHING \
7270             ON CONFLICT (b) DO UPDATE SET a = excluded.a",
7271        );
7272        if let Statement::Insert(i) = stmt {
7273            assert_eq!(i.upsert.len(), 2);
7274            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
7275            assert!(matches!(i.upsert[1].action, UpsertAction::Update { .. }));
7276        } else {
7277            unreachable!("expected Insert");
7278        }
7279    }
7280
7281    #[test]
7282    fn test_upsert_where_on_conflict_target() {
7283        let stmt = parse_one(
7284            "INSERT INTO t (a, b) VALUES (1, 2) \
7285             ON CONFLICT (a) WHERE a > 0 DO UPDATE SET b = excluded.b WHERE b < 100",
7286        );
7287        if let Statement::Insert(i) = stmt {
7288            assert_eq!(i.upsert.len(), 1);
7289            let target = i.upsert[0].target.as_ref().expect("conflict target");
7290            assert!(target.where_clause.is_some(), "target WHERE missing");
7291            if let UpsertAction::Update { where_clause, .. } = &i.upsert[0].action {
7292                assert!(where_clause.is_some(), "action WHERE missing");
7293            } else {
7294                unreachable!("expected Update action");
7295            }
7296        } else {
7297            unreachable!("expected Insert");
7298        }
7299    }
7300
7301    #[test]
7302    fn test_returning_insert() {
7303        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, rowid");
7304        if let Statement::Insert(i) = stmt {
7305            assert_eq!(i.returning.len(), 3);
7306        } else {
7307            unreachable!("expected Insert");
7308        }
7309    }
7310
7311    #[test]
7312    fn test_returning_insert_select_with_semicolon() {
7313        let stmt = parse_one("INSERT INTO t2 SELECT * FROM t RETURNING *;");
7314        if let Statement::Insert(i) = stmt {
7315            assert!(matches!(i.source, InsertSource::Select(_)));
7316            assert_eq!(i.returning.len(), 1);
7317            assert!(matches!(i.returning[0], ResultColumn::Star));
7318        } else {
7319            unreachable!("expected Insert");
7320        }
7321    }
7322
7323    #[test]
7324    fn test_returning_reflects_before_triggers() {
7325        // Parser-level: verify RETURNING clause parses alongside trigger-affected DML
7326        // Runtime verification that RETURNING reflects BEFORE-trigger modifications
7327        // is deferred to VDBE/engine tests
7328        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING a AS modified_a");
7329        if let Statement::Insert(i) = stmt {
7330            assert_eq!(i.returning.len(), 1);
7331            match &i.returning[0] {
7332                ResultColumn::Expr { alias, .. } => {
7333                    assert_eq!(alias.as_deref(), Some("modified_a"));
7334                }
7335                other => unreachable!("expected Expr result column, got {other:?}"),
7336            }
7337        } else {
7338            unreachable!("expected Insert");
7339        }
7340    }
7341
7342    #[test]
7343    fn test_returning_ignores_after_triggers() {
7344        // Parser-level: verify RETURNING * parses on INSERT with conflict clause
7345        // Runtime verification that RETURNING ignores AFTER-trigger modifications
7346        // is deferred to VDBE/engine tests
7347        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1) RETURNING *");
7348        if let Statement::Insert(i) = stmt {
7349            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
7350            assert_eq!(i.returning.len(), 1);
7351            assert!(matches!(i.returning[0], ResultColumn::Star));
7352        } else {
7353            unreachable!("expected Insert");
7354        }
7355    }
7356
7357    #[test]
7358    fn test_returning_after_before_trigger_modify() {
7359        // Parser-level: verify RETURNING with multiple column expressions
7360        // Runtime verification of BEFORE trigger modifying returned values
7361        // is deferred to VDBE/engine tests
7362        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, a + b AS total");
7363        if let Statement::Insert(i) = stmt {
7364            assert_eq!(i.returning.len(), 3);
7365            match &i.returning[2] {
7366                ResultColumn::Expr {
7367                    alias: Some(alias), ..
7368                } => assert_eq!(alias, "total"),
7369                other => unreachable!("expected aliased expression, got {other:?}"),
7370            }
7371        } else {
7372            unreachable!("expected Insert");
7373        }
7374    }
7375
7376    #[test]
7377    fn test_returning_before_trigger_raise_abort() {
7378        // Parser-level: RAISE(ABORT, ...) is a valid expression in trigger bodies;
7379        // here we verify RETURNING parses on multi-row INSERT (runtime abort
7380        // behavior verified in VDBE/engine tests)
7381        let stmt = parse_one("INSERT INTO t (a) VALUES (1), (2), (3) RETURNING a");
7382        if let Statement::Insert(i) = stmt {
7383            if let InsertSource::Values(rows) = &i.source {
7384                assert_eq!(rows.len(), 3);
7385            } else {
7386                unreachable!("expected Values source");
7387            }
7388            assert_eq!(i.returning.len(), 1);
7389        } else {
7390            unreachable!("expected Insert");
7391        }
7392    }
7393
7394    #[test]
7395    fn test_returning_instead_of_view() {
7396        // Parser-level: INSERT into a view name parses the same as INSERT into a table
7397        // Runtime INSTEAD OF trigger behavior is verified in VDBE/engine tests
7398        let stmt = parse_one("INSERT INTO v (a, b) VALUES (1, 2) RETURNING *");
7399        if let Statement::Insert(i) = stmt {
7400            assert_eq!(i.table.name, "v");
7401            assert!(!i.returning.is_empty());
7402        } else {
7403            unreachable!("expected Insert");
7404        }
7405    }
7406
7407    #[test]
7408    fn test_returning_autoincrement_with_trigger() {
7409        // Parser-level: verify RETURNING can reference rowid on INSERT
7410        // Runtime autoincrement + trigger interaction is verified in VDBE/engine tests
7411        let stmt = parse_one("INSERT INTO t (name) VALUES ('test') RETURNING rowid, name");
7412        if let Statement::Insert(i) = stmt {
7413            assert_eq!(i.returning.len(), 2);
7414        } else {
7415            unreachable!("expected Insert");
7416        }
7417    }
7418
7419    #[test]
7420    fn test_update_set_where() {
7421        let stmt = parse_one("UPDATE t SET a = 1, b = 'hello' WHERE id = 42");
7422        if let Statement::Update(u) = stmt {
7423            assert_eq!(u.assignments.len(), 2);
7424            assert!(u.where_clause.is_some());
7425            assert!(u.from.is_none());
7426        } else {
7427            unreachable!("expected Update");
7428        }
7429    }
7430
7431    #[test]
7432    fn test_update_from_join() {
7433        let stmt = parse_one("UPDATE t1 SET a = t2.x FROM t2 WHERE t1.id = t2.id");
7434        if let Statement::Update(u) = stmt {
7435            assert_eq!(u.assignments.len(), 1);
7436            assert!(u.from.is_some());
7437            assert!(u.where_clause.is_some());
7438        } else {
7439            unreachable!("expected Update");
7440        }
7441    }
7442
7443    #[test]
7444    fn test_update_from_multi_match() {
7445        // Parser-level: UPDATE FROM with a join that could produce multiple matches
7446        // Runtime behavior (arbitrary row chosen) verified in VDBE/engine tests
7447        let stmt = parse_one(
7448            "UPDATE t1 SET val = src.val FROM src \
7449             INNER JOIN mapping ON mapping.src_id = src.id \
7450             WHERE t1.id = mapping.dst_id",
7451        );
7452        if let Statement::Update(u) = stmt {
7453            assert!(u.from.is_some());
7454            let from = u.from.as_ref().unwrap();
7455            assert!(!from.joins.is_empty(), "expected JOIN in FROM clause");
7456        } else {
7457            unreachable!("expected Update");
7458        }
7459    }
7460
7461    #[test]
7462    fn test_update_from_parentheses_are_stack_safe_at_1000_and_1001() {
7463        fn drop_update_from_iteratively(statement: Statement) {
7464            let Statement::Update(mut update) = statement else {
7465                panic!("expected UPDATE statement");
7466            };
7467            let Some(mut from) = update.from.take() else {
7468                panic!("expected UPDATE FROM clause");
7469            };
7470            drop(update);
7471            loop {
7472                let FromClause { source, joins } = from;
7473                assert!(joins.is_empty());
7474                match source {
7475                    TableOrSubquery::ParenJoin(inner) => from = *inner,
7476                    leaf => {
7477                        drop(leaf);
7478                        break;
7479                    }
7480                }
7481            }
7482        }
7483
7484        for height in [1000, 1001] {
7485            let sql = format!(
7486                "UPDATE target SET value = 1 FROM {}source{} WHERE target.id = source.id",
7487                "(".repeat(height),
7488                ")".repeat(height)
7489            );
7490            let (rendered, statement) = std::thread::Builder::new()
7491                .stack_size(1024 * 1024)
7492                .spawn(move || {
7493                    let statement = Parser::from_sql(&sql)
7494                        .parse_statement()
7495                        .expect("deep UPDATE FROM must parse");
7496                    let rendered = statement.to_string();
7497                    (rendered, statement)
7498                })
7499                .expect("1 MiB parser thread must spawn")
7500                .join()
7501                .expect("deep UPDATE FROM parsing and formatting must not overflow");
7502            assert!(rendered.starts_with("UPDATE target SET value = 1 FROM "));
7503            assert!(rendered.ends_with(" WHERE target.id = source.id"));
7504            assert_eq!(rendered.matches('(').count(), height);
7505            assert_eq!(rendered.matches(')').count(), height);
7506            drop_update_from_iteratively(statement);
7507        }
7508    }
7509
7510    #[test]
7511    fn test_malformed_deep_update_from_recovers_following_statement() {
7512        let sql = format!(
7513            "UPDATE target SET value = 1 FROM {}source{}; SELECT 42;",
7514            "(".repeat(1001),
7515            ")".repeat(1000)
7516        );
7517        let (statements, errors) = std::thread::Builder::new()
7518            .stack_size(1024 * 1024)
7519            .spawn(move || Parser::from_sql(&sql).parse_all())
7520            .expect("1 MiB parser thread must spawn")
7521            .join()
7522            .expect("malformed deep UPDATE FROM recovery must not overflow");
7523
7524        assert_eq!(errors.len(), 1, "expected one unbalanced-FROM error");
7525        assert_eq!(
7526            statements.len(),
7527            1,
7528            "the malformed UPDATE must be discarded"
7529        );
7530        assert_eq!(statements[0].to_string(), "SELECT 42");
7531    }
7532
7533    #[test]
7534    fn recursion_limit_error_uses_stock_parser_stack_overflow_text() {
7535        // bd-parser-errfmt-residuals-3giu1: the native parser-recursion guard
7536        // must surface stock SQLite's verbatim `parser stack overflow`
7537        // (sqlite3_errmsg text), not a frank-specific "recursion limit
7538        // exceeded" phrasing. Drive the depth counter straight to the cap so
7539        // the guard fires without any real stack recursion.
7540        let mut parser = Parser::from_sql("SELECT 1");
7541        for _ in 0..MAX_NATIVE_PARSE_DEPTH {
7542            parser
7543                .enter_recursion()
7544                .expect("depth below the cap must be accepted");
7545        }
7546        let error = parser
7547            .enter_recursion()
7548            .expect_err("exceeding the native recursion cap must be rejected");
7549        assert_eq!(error.kind, ParseErrorKind::RecursionLimit);
7550        assert_eq!(error.message, "parser stack overflow");
7551    }
7552
7553    #[test]
7554    fn semantically_empty_input_parses_to_no_statements() {
7555        // bd-parser-errfmt-residuals-3giu1: stock's sqlite3_prepare_v2 treats
7556        // empty / whitespace / comment / bare-`;` input as a no-op (SQLITE_OK,
7557        // NULL statement) rather than a parse error. The multi-statement parse
7558        // entry point mirrors that with an empty statement list and no error.
7559        let mut scratch = StatementParseScratch::default();
7560        for sql in [
7561            "",
7562            "   ",
7563            "\n\t ",
7564            ";",
7565            " ; ; ",
7566            "-- just a comment",
7567            "/* block */",
7568        ] {
7569            let statements = parse_statements_with_scratch(sql, &mut scratch)
7570                .unwrap_or_else(|error| panic!("empty input {sql:?} must not error: {error}"));
7571            assert!(
7572                statements.is_empty(),
7573                "empty input {sql:?} must parse to zero statements"
7574            );
7575        }
7576    }
7577
7578    #[test]
7579    fn test_update_order_by_limit() {
7580        let stmt = parse_one("UPDATE t SET a = a + 1 ORDER BY b DESC LIMIT 10");
7581        if let Statement::Update(u) = stmt {
7582            assert_eq!(u.order_by.len(), 1);
7583            assert_eq!(u.order_by[0].direction, Some(SortDirection::Desc));
7584            assert!(u.limit.is_some());
7585        } else {
7586            unreachable!("expected Update");
7587        }
7588    }
7589
7590    #[test]
7591    fn test_update_returning() {
7592        let stmt = parse_one("UPDATE t SET a = 1 WHERE id = 5 RETURNING id, a AS new_a");
7593        if let Statement::Update(u) = stmt {
7594            assert_eq!(u.returning.len(), 2);
7595            match &u.returning[1] {
7596                ResultColumn::Expr {
7597                    alias: Some(alias), ..
7598                } => assert_eq!(alias, "new_a"),
7599                other => unreachable!("expected aliased result column, got {other:?}"),
7600            }
7601        } else {
7602            unreachable!("expected Update");
7603        }
7604    }
7605
7606    #[test]
7607    fn test_update_or_ignore() {
7608        let stmt = parse_one("UPDATE OR IGNORE t SET a = 1 WHERE id = 5");
7609        if let Statement::Update(u) = stmt {
7610            assert_eq!(u.or_conflict, Some(ConflictAction::Ignore));
7611            assert!(u.where_clause.is_some());
7612        } else {
7613            unreachable!("expected Update");
7614        }
7615    }
7616
7617    #[test]
7618    fn test_delete_where() {
7619        let stmt = parse_one("DELETE FROM t WHERE id = 42 AND active = 0");
7620        if let Statement::Delete(d) = stmt {
7621            assert!(d.where_clause.is_some());
7622            assert!(d.returning.is_empty());
7623        } else {
7624            unreachable!("expected Delete");
7625        }
7626    }
7627
7628    #[test]
7629    fn test_delete_order_by_limit() {
7630        let stmt = parse_one("DELETE FROM t ORDER BY created_at ASC LIMIT 100");
7631        if let Statement::Delete(d) = stmt {
7632            assert_eq!(d.order_by.len(), 1);
7633            assert_eq!(d.order_by[0].direction, Some(SortDirection::Asc));
7634            let limit = d.limit.as_ref().expect("LIMIT clause");
7635            assert!(matches!(
7636                limit.limit,
7637                Expr::Literal(Literal::Integer(100), _)
7638            ));
7639        } else {
7640            unreachable!("expected Delete");
7641        }
7642    }
7643
7644    #[test]
7645    fn test_delete_returning() {
7646        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
7647        if let Statement::Delete(d) = stmt {
7648            assert!(d.where_clause.is_some());
7649            assert_eq!(d.returning.len(), 1);
7650            assert!(matches!(d.returning[0], ResultColumn::Star));
7651        } else {
7652            unreachable!("expected Delete");
7653        }
7654    }
7655
7656    #[test]
7657    fn test_delete_bulk_optimization() {
7658        // Parser-level: DELETE without WHERE produces AST with no where_clause
7659        // Runtime bulk-delete optimization (OP_Clear) is verified in VDBE/engine tests
7660        let stmt = parse_one("DELETE FROM t");
7661        if let Statement::Delete(d) = stmt {
7662            assert!(d.where_clause.is_none());
7663            assert!(d.order_by.is_empty());
7664            assert!(d.limit.is_none());
7665            assert!(d.returning.is_empty());
7666        } else {
7667            unreachable!("expected Delete");
7668        }
7669    }
7670
7671    #[test]
7672    fn test_delete_bulk_no_where_fast() {
7673        // Parser-level: confirms DELETE without WHERE parses to minimal AST
7674        // Runtime OP_Clear vs OP_Delete selection is verified in VDBE/engine tests
7675        let stmt = parse_one("DELETE FROM main.t");
7676        if let Statement::Delete(d) = stmt {
7677            assert_eq!(d.table.name.schema.as_deref(), Some("main"));
7678            assert_eq!(d.table.name.name, "t");
7679            assert!(d.where_clause.is_none());
7680        } else {
7681            unreachable!("expected Delete");
7682        }
7683    }
7684
7685    #[test]
7686    fn test_delete_bulk_blocked_by_trigger() {
7687        // Parser-level: DELETE without WHERE from a table that might have triggers
7688        // has the same AST shape (no WHERE). Runtime trigger detection is in the engine.
7689        let stmt = parse_one("DELETE FROM orders");
7690        if let Statement::Delete(d) = stmt {
7691            assert!(d.where_clause.is_none());
7692            assert!(d.returning.is_empty());
7693        } else {
7694            unreachable!("expected Delete");
7695        }
7696    }
7697
7698    #[test]
7699    fn test_delete_bulk_blocked_by_fk() {
7700        // Parser-level: DELETE without WHERE is the same AST regardless of FK constraints.
7701        // Runtime FK-based fallback to row-by-row is verified in VDBE/engine tests.
7702        let stmt = parse_one("DELETE FROM parent_table");
7703        if let Statement::Delete(d) = stmt {
7704            assert!(d.where_clause.is_none());
7705        } else {
7706            unreachable!("expected Delete");
7707        }
7708    }
7709
7710    #[test]
7711    fn test_delete_bulk_changes_count() {
7712        // Parser-level: DELETE without WHERE returning count via changes()
7713        // is the same AST as any unconditional delete. Runtime changes()
7714        // reporting is verified in VDBE/engine tests.
7715        let stmt = parse_one("DELETE FROM t");
7716        if let Statement::Delete(d) = stmt {
7717            assert!(d.where_clause.is_none());
7718        } else {
7719            unreachable!("expected Delete");
7720        }
7721    }
7722
7723    #[test]
7724    fn test_delete_bulk_autoincrement_preserved() {
7725        // Parser-level: DELETE without WHERE on an autoincrement table has
7726        // identical AST to any unconditional delete. Runtime autoincrement
7727        // sequence preservation is verified in VDBE/engine tests.
7728        let stmt = parse_one("DELETE FROM t");
7729        if let Statement::Delete(d) = stmt {
7730            assert!(d.where_clause.is_none());
7731            assert!(d.limit.is_none());
7732        } else {
7733            unreachable!("expected Delete");
7734        }
7735    }
7736
7737    #[test]
7738    fn test_delete_bulk_where_1_not_optimized() {
7739        // Parser-level: DELETE WHERE 1 has a where_clause (unlike bare DELETE),
7740        // so the optimizer cannot use bulk-delete. Verify WHERE is present.
7741        let stmt = parse_one("DELETE FROM t WHERE 1");
7742        if let Statement::Delete(d) = stmt {
7743            assert!(
7744                d.where_clause.is_some(),
7745                "WHERE 1 must produce a where_clause"
7746            );
7747            assert!(matches!(
7748                d.where_clause.as_ref().unwrap(),
7749                Expr::Literal(Literal::Integer(1), _)
7750            ));
7751        } else {
7752            unreachable!("expected Delete");
7753        }
7754    }
7755
7756    // -----------------------------------------------------------------------
7757    // bd-34de §12.5-12.6 DDL: CREATE TABLE + CREATE INDEX parsing tests
7758    // -----------------------------------------------------------------------
7759
7760    #[test]
7761    fn test_create_table_basic() {
7762        let stmt = parse_one("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
7763        if let Statement::CreateTable(ct) = stmt {
7764            assert_eq!(ct.name.name, "users");
7765            assert!(!ct.if_not_exists);
7766            assert!(!ct.temporary);
7767            assert!(!ct.without_rowid);
7768            assert!(!ct.strict);
7769            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7770                assert_eq!(columns.len(), 3);
7771                assert_eq!(columns[0].name, "id");
7772                assert_eq!(columns[1].name, "name");
7773                assert_eq!(columns[2].name, "age");
7774            } else {
7775                unreachable!("expected Columns body");
7776            }
7777        } else {
7778            unreachable!("expected CreateTable");
7779        }
7780    }
7781
7782    #[test]
7783    fn test_create_table_if_not_exists() {
7784        let stmt = parse_one("CREATE TABLE IF NOT EXISTS t (id INTEGER)");
7785        if let Statement::CreateTable(ct) = stmt {
7786            assert!(ct.if_not_exists);
7787        } else {
7788            unreachable!("expected CreateTable");
7789        }
7790    }
7791
7792    #[test]
7793    fn test_create_temp_table() {
7794        let stmt = parse_one("CREATE TEMP TABLE session_data (key TEXT, val BLOB)");
7795        if let Statement::CreateTable(ct) = stmt {
7796            assert!(ct.temporary);
7797        } else {
7798            unreachable!("expected CreateTable");
7799        }
7800    }
7801
7802    #[test]
7803    fn test_create_table_as_select() {
7804        let stmt = parse_one("CREATE TABLE t2 AS SELECT id, name FROM t1 WHERE active = 1");
7805        if let Statement::CreateTable(ct) = stmt {
7806            assert!(matches!(ct.body, CreateTableBody::AsSelect(_)));
7807        } else {
7808            unreachable!("expected CreateTable");
7809        }
7810    }
7811
7812    #[test]
7813    fn test_column_primary_key() {
7814        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY ASC)");
7815        if let Statement::CreateTable(ct) = stmt {
7816            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7817                let pk = columns[0]
7818                    .constraints
7819                    .iter()
7820                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
7821                assert!(pk.is_some(), "PK constraint missing");
7822                if let ColumnConstraintKind::PrimaryKey { direction, .. } = &pk.unwrap().kind {
7823                    assert_eq!(*direction, Some(SortDirection::Asc));
7824                }
7825            } else {
7826                unreachable!("expected Columns body");
7827            }
7828        } else {
7829            unreachable!("expected CreateTable");
7830        }
7831    }
7832
7833    #[test]
7834    fn test_column_primary_key_autoincrement() {
7835        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)");
7836        if let Statement::CreateTable(ct) = stmt {
7837            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7838                let pk = columns[0]
7839                    .constraints
7840                    .iter()
7841                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
7842                if let ColumnConstraintKind::PrimaryKey { autoincrement, .. } = &pk.unwrap().kind {
7843                    assert!(autoincrement, "AUTOINCREMENT flag not set");
7844                }
7845            } else {
7846                unreachable!("expected Columns body");
7847            }
7848        } else {
7849            unreachable!("expected CreateTable");
7850        }
7851    }
7852
7853    #[test]
7854    fn test_autoincrement_uses_sqlite_sequence() {
7855        // Parser-level: verify AUTOINCREMENT syntax parses correctly.
7856        // Runtime sqlite_sequence tracking is verified in VDBE/engine tests.
7857        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)");
7858        if let Statement::CreateTable(ct) = stmt {
7859            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7860                assert_eq!(columns.len(), 2);
7861                let pk = columns[0].constraints.iter().find(|c| {
7862                    matches!(
7863                        c.kind,
7864                        ColumnConstraintKind::PrimaryKey {
7865                            autoincrement: true,
7866                            ..
7867                        }
7868                    )
7869                });
7870                assert!(pk.is_some(), "AUTOINCREMENT constraint missing");
7871            } else {
7872                unreachable!("expected Columns body");
7873            }
7874        } else {
7875            unreachable!("expected CreateTable");
7876        }
7877    }
7878
7879    #[test]
7880    fn test_column_not_null() {
7881        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL)");
7882        if let Statement::CreateTable(ct) = stmt {
7883            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7884                let nn = columns[0]
7885                    .constraints
7886                    .iter()
7887                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
7888                assert!(nn.is_some(), "NOT NULL constraint missing");
7889            } else {
7890                unreachable!("expected Columns body");
7891            }
7892        } else {
7893            unreachable!("expected CreateTable");
7894        }
7895    }
7896
7897    #[test]
7898    fn test_column_unique() {
7899        let stmt = parse_one("CREATE TABLE t (email TEXT UNIQUE)");
7900        if let Statement::CreateTable(ct) = stmt {
7901            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7902                let uq = columns[0]
7903                    .constraints
7904                    .iter()
7905                    .find(|c| matches!(c.kind, ColumnConstraintKind::Unique { .. }));
7906                assert!(uq.is_some(), "UNIQUE constraint missing");
7907            } else {
7908                unreachable!("expected Columns body");
7909            }
7910        } else {
7911            unreachable!("expected CreateTable");
7912        }
7913    }
7914
7915    #[test]
7916    fn test_column_check() {
7917        let stmt = parse_one("CREATE TABLE t (age INTEGER CHECK(age >= 0 AND age < 200))");
7918        if let Statement::CreateTable(ct) = stmt {
7919            if let CreateTableBody::Columns { columns, .. } = &ct.body {
7920                let chk = columns[0]
7921                    .constraints
7922                    .iter()
7923                    .find(|c| matches!(c.kind, ColumnConstraintKind::Check(_)));
7924                assert!(chk.is_some(), "CHECK constraint missing");
7925            } else {
7926                unreachable!("expected Columns body");
7927            }
7928        } else {
7929            unreachable!("expected CreateTable");
7930        }
7931    }
7932
7933    #[test]
7934    fn test_check_constraint_expression_height_fails_closed_at_1001() {
7935        const LIMIT: usize = MAX_PARSE_DEPTH as usize;
7936        let at_limit = std::iter::repeat_n("1", LIMIT)
7937            .collect::<Vec<_>>()
7938            .join(" + ");
7939        let statement = format!("CREATE TABLE t (value INTEGER CHECK({at_limit}))");
7940        assert!(
7941            matches!(parse_one(&statement), Statement::CreateTable(_)),
7942            "height-1000 CHECK must remain attached to its CREATE TABLE"
7943        );
7944
7945        let over_limit = std::iter::repeat_n("1", LIMIT + 1)
7946            .collect::<Vec<_>>()
7947            .join(" + ");
7948        let statement = format!("CREATE TABLE t (value INTEGER CHECK({over_limit}))");
7949        let mut parser = Parser::from_sql(&statement);
7950        let error = parser
7951            .parse_statement()
7952            .expect_err("height-1001 CHECK must reject the entire schema statement");
7953        assert_eq!(
7954            error.kind,
7955            ParseErrorKind::ExpressionTooDeep {
7956                max: MAX_PARSE_DEPTH
7957            }
7958        );
7959        assert_eq!(
7960            parser.depth, 0,
7961            "expression-height rejection must unwind parser recursion state"
7962        );
7963    }
7964
7965    #[test]
7966    fn test_expression_height_boundary_is_context_independent_on_one_mib_stack() {
7967        const LIMIT: usize = MAX_PARSE_DEPTH as usize;
7968        fn right_deep_expression(height: usize) -> String {
7969            format!("{}1{}", "1 + (".repeat(height - 1), ")".repeat(height - 1))
7970        }
7971        fn parse_on_one_mib_stack(sql: String) -> Result<(), ParseError> {
7972            std::thread::Builder::new()
7973                .stack_size(1024 * 1024)
7974                .spawn(move || {
7975                    let statement = Parser::from_sql(&sql).parse_statement()?;
7976                    // Destruction is part of the constrained-stack contract:
7977                    // a successful parse must not merely move a deep AST onto
7978                    // the caller's larger stack before dropping it.
7979                    drop(statement);
7980                    Ok(())
7981                })
7982                .expect("1 MiB parser thread must spawn")
7983                .join()
7984                .expect("schema-context parse must not overflow or panic")
7985        }
7986
7987        let at_limit = right_deep_expression(LIMIT);
7988        let over_limit = right_deep_expression(LIMIT + 1);
7989        let contexts = [
7990            (
7991                "SELECT",
7992                format!("SELECT {at_limit}"),
7993                format!("SELECT {over_limit}"),
7994            ),
7995            (
7996                "column CHECK",
7997                format!("CREATE TABLE t (value INTEGER CHECK({at_limit}))"),
7998                format!("CREATE TABLE t (value INTEGER CHECK({over_limit}))"),
7999            ),
8000            (
8001                "table CHECK",
8002                format!("CREATE TABLE t (value INTEGER, CHECK({at_limit}))"),
8003                format!("CREATE TABLE t (value INTEGER, CHECK({over_limit}))"),
8004            ),
8005            (
8006                "view",
8007                format!("CREATE VIEW v AS SELECT {at_limit}"),
8008                format!("CREATE VIEW v AS SELECT {over_limit}"),
8009            ),
8010            (
8011                "trigger",
8012                format!(
8013                    "CREATE TRIGGER tr BEFORE INSERT ON t WHEN {at_limit} \
8014                     BEGIN SELECT 1; END"
8015                ),
8016                format!(
8017                    "CREATE TRIGGER tr BEFORE INSERT ON t WHEN {over_limit} \
8018                     BEGIN SELECT 1; END"
8019                ),
8020            ),
8021        ];
8022
8023        for (context, accepted, rejected) in contexts {
8024            parse_on_one_mib_stack(accepted)
8025                .unwrap_or_else(|error| panic!("{context} height 1000 rejected: {error}"));
8026
8027            let error = parse_on_one_mib_stack(rejected)
8028                .expect_err("height 1001 must reject every expression-bearing context");
8029            assert_eq!(
8030                error.kind,
8031                ParseErrorKind::ExpressionTooDeep {
8032                    max: MAX_PARSE_DEPTH
8033                },
8034                "wrong error classification for {context}: {error}"
8035            );
8036        }
8037    }
8038
8039    #[test]
8040    fn test_column_default_literal() {
8041        let stmt = parse_one("CREATE TABLE t (status TEXT DEFAULT 'active')");
8042        if let Statement::CreateTable(ct) = stmt {
8043            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8044                let def = columns[0]
8045                    .constraints
8046                    .iter()
8047                    .find(|c| matches!(c.kind, ColumnConstraintKind::Default(_)));
8048                assert!(def.is_some(), "DEFAULT constraint missing");
8049            } else {
8050                unreachable!("expected Columns body");
8051            }
8052        } else {
8053            unreachable!("expected CreateTable");
8054        }
8055    }
8056
8057    #[test]
8058    fn test_column_default_expr() {
8059        let stmt = parse_one("CREATE TABLE t (created_at TEXT DEFAULT (datetime('now')))");
8060        if let Statement::CreateTable(ct) = stmt {
8061            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8062                let def = columns[0].constraints.iter().find(|c| {
8063                    matches!(
8064                        c.kind,
8065                        ColumnConstraintKind::Default(DefaultValue::ParenExpr(_))
8066                    )
8067                });
8068                assert!(def.is_some(), "DEFAULT (expr) missing");
8069            } else {
8070                unreachable!("expected Columns body");
8071            }
8072        } else {
8073            unreachable!("expected CreateTable");
8074        }
8075    }
8076
8077    #[test]
8078    fn test_column_collate() {
8079        let stmt = parse_one("CREATE TABLE t (name TEXT COLLATE NOCASE)");
8080        if let Statement::CreateTable(ct) = stmt {
8081            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8082                let coll = columns[0]
8083                    .constraints
8084                    .iter()
8085                    .find(|c| matches!(c.kind, ColumnConstraintKind::Collate(_)));
8086                assert!(coll.is_some(), "COLLATE constraint missing");
8087                if let ColumnConstraintKind::Collate(name) = &coll.unwrap().kind {
8088                    assert_eq!(name, "NOCASE");
8089                }
8090            } else {
8091                unreachable!("expected Columns body");
8092            }
8093        } else {
8094            unreachable!("expected CreateTable");
8095        }
8096    }
8097
8098    #[test]
8099    fn test_table_constraint_composite_pk() {
8100        let stmt = parse_one("CREATE TABLE t (a INTEGER, b INTEGER, PRIMARY KEY (a, b))");
8101        if let Statement::CreateTable(ct) = stmt {
8102            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
8103                let pk = constraints
8104                    .iter()
8105                    .find(|c| matches!(c.kind, TableConstraintKind::PrimaryKey { .. }));
8106                assert!(pk.is_some(), "composite PK missing");
8107                if let TableConstraintKind::PrimaryKey { columns, .. } = &pk.unwrap().kind {
8108                    assert_eq!(columns.len(), 2);
8109                }
8110            } else {
8111                unreachable!("expected Columns body");
8112            }
8113        } else {
8114            unreachable!("expected CreateTable");
8115        }
8116    }
8117
8118    #[test]
8119    fn test_table_constraint_composite_unique() {
8120        let stmt = parse_one("CREATE TABLE t (a TEXT, b TEXT, UNIQUE (a, b))");
8121        if let Statement::CreateTable(ct) = stmt {
8122            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
8123                let uq = constraints
8124                    .iter()
8125                    .find(|c| matches!(c.kind, TableConstraintKind::Unique { .. }));
8126                assert!(uq.is_some(), "composite UNIQUE missing");
8127                if let TableConstraintKind::Unique { columns, .. } = &uq.unwrap().kind {
8128                    assert_eq!(columns.len(), 2);
8129                }
8130            } else {
8131                unreachable!("expected Columns body");
8132            }
8133        } else {
8134            unreachable!("expected CreateTable");
8135        }
8136    }
8137
8138    #[test]
8139    fn test_table_constraint_check() {
8140        let stmt = parse_one(
8141            "CREATE TABLE t (start_date TEXT, end_date TEXT, CHECK (start_date < end_date))",
8142        );
8143        if let Statement::CreateTable(ct) = stmt {
8144            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
8145                let chk = constraints
8146                    .iter()
8147                    .find(|c| matches!(c.kind, TableConstraintKind::Check(_)));
8148                assert!(chk.is_some(), "table CHECK constraint missing");
8149            } else {
8150                unreachable!("expected Columns body");
8151            }
8152        } else {
8153            unreachable!("expected CreateTable");
8154        }
8155    }
8156
8157    #[test]
8158    fn test_create_rejects_modifiers_for_incompatible_object_kinds() {
8159        for sql in [
8160            "CREATE UNIQUE TABLE t(value INTEGER)",
8161            "CREATE UNIQUE VIEW v AS SELECT 1",
8162            "CREATE UNIQUE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END",
8163            "CREATE UNIQUE VIRTUAL TABLE vt USING fts5(content)",
8164            "CREATE TEMP INDEX i ON t(value)",
8165            "CREATE TEMP VIRTUAL TABLE vt USING fts5(content)",
8166        ] {
8167            Parser::from_sql(sql)
8168                .parse_statement()
8169                .expect_err("CREATE modifiers must not be discarded for incompatible objects");
8170        }
8171    }
8172
8173    #[test]
8174    fn test_invalid_create_modifier_recovers_next_statement() {
8175        let (statements, errors) =
8176            Parser::from_sql("CREATE UNIQUE TABLE t(value INTEGER); SELECT 42;").parse_all();
8177
8178        assert_eq!(errors.len(), 1);
8179        assert_eq!(statements.len(), 1);
8180        assert!(matches!(statements[0], Statement::Select(_)));
8181    }
8182
8183    #[test]
8184    fn test_foreign_key_on_delete_cascade() {
8185        let stmt = parse_one(
8186            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8187             REFERENCES parent(id) ON DELETE CASCADE)",
8188        );
8189        if let Statement::CreateTable(ct) = stmt {
8190            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8191                let fk = columns[1]
8192                    .constraints
8193                    .iter()
8194                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)));
8195                assert!(fk.is_some(), "FK constraint missing");
8196                if let ColumnConstraintKind::ForeignKey(clause) = &fk.unwrap().kind {
8197                    assert_eq!(clause.table, "parent");
8198                    let del = clause
8199                        .actions
8200                        .iter()
8201                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
8202                    assert!(del.is_some());
8203                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Cascade);
8204                }
8205            } else {
8206                unreachable!("expected Columns body");
8207            }
8208        } else {
8209            unreachable!("expected CreateTable");
8210        }
8211    }
8212
8213    #[test]
8214    fn test_foreign_key_on_delete_set_null() {
8215        let stmt = parse_one(
8216            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8217             REFERENCES parent(id) ON DELETE SET NULL)",
8218        );
8219        if let Statement::CreateTable(ct) = stmt {
8220            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8221                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8222                    .constraints
8223                    .iter()
8224                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8225                    .unwrap()
8226                    .kind
8227                {
8228                    let del = clause
8229                        .actions
8230                        .iter()
8231                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
8232                    assert_eq!(del.unwrap().action, ForeignKeyActionType::SetNull);
8233                }
8234            } else {
8235                unreachable!("expected Columns body");
8236            }
8237        } else {
8238            unreachable!("expected CreateTable");
8239        }
8240    }
8241
8242    #[test]
8243    fn test_foreign_key_on_update_cascade() {
8244        let stmt = parse_one(
8245            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8246             REFERENCES parent(id) ON UPDATE CASCADE)",
8247        );
8248        if let Statement::CreateTable(ct) = stmt {
8249            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8250                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8251                    .constraints
8252                    .iter()
8253                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8254                    .unwrap()
8255                    .kind
8256                {
8257                    let upd = clause
8258                        .actions
8259                        .iter()
8260                        .find(|a| a.trigger == ForeignKeyTrigger::OnUpdate);
8261                    assert!(upd.is_some());
8262                    assert_eq!(upd.unwrap().action, ForeignKeyActionType::Cascade);
8263                }
8264            } else {
8265                unreachable!("expected Columns body");
8266            }
8267        } else {
8268            unreachable!("expected CreateTable");
8269        }
8270    }
8271
8272    #[test]
8273    fn test_foreign_key_restrict() {
8274        let stmt = parse_one(
8275            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8276             REFERENCES parent(id) ON DELETE RESTRICT)",
8277        );
8278        if let Statement::CreateTable(ct) = stmt {
8279            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8280                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8281                    .constraints
8282                    .iter()
8283                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8284                    .unwrap()
8285                    .kind
8286                {
8287                    let del = clause
8288                        .actions
8289                        .iter()
8290                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
8291                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Restrict);
8292                }
8293            } else {
8294                unreachable!("expected Columns body");
8295            }
8296        } else {
8297            unreachable!("expected CreateTable");
8298        }
8299    }
8300
8301    #[test]
8302    fn test_foreign_key_deferred() {
8303        let stmt = parse_one(
8304            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8305             REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED)",
8306        );
8307        if let Statement::CreateTable(ct) = stmt {
8308            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8309                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8310                    .constraints
8311                    .iter()
8312                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8313                    .unwrap()
8314                    .kind
8315                {
8316                    let def = clause.deferrable.as_ref().expect("DEFERRABLE missing");
8317                    assert!(!def.not, "should be DEFERRABLE, not NOT DEFERRABLE");
8318                    assert_eq!(def.initially, Some(DeferrableInitially::Deferred));
8319                }
8320            } else {
8321                unreachable!("expected Columns body");
8322            }
8323        } else {
8324            unreachable!("expected CreateTable");
8325        }
8326    }
8327
8328    #[test]
8329    fn test_foreign_key_pragma_required() {
8330        // Parser-level: FK syntax parses identically regardless of PRAGMA state.
8331        // Runtime enforcement requiring PRAGMA foreign_keys = ON is in VDBE/engine.
8332        let stmt = parse_one(
8333            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
8334             REFERENCES parent(id) ON DELETE CASCADE ON UPDATE SET NULL)",
8335        );
8336        if let Statement::CreateTable(ct) = stmt {
8337            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8338                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
8339                    .constraints
8340                    .iter()
8341                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
8342                    .unwrap()
8343                    .kind
8344                {
8345                    assert_eq!(clause.actions.len(), 2);
8346                }
8347            } else {
8348                unreachable!("expected Columns body");
8349            }
8350        } else {
8351            unreachable!("expected CreateTable");
8352        }
8353    }
8354
8355    #[test]
8356    fn test_conflict_clause_on_not_null() {
8357        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL ON CONFLICT IGNORE)");
8358        if let Statement::CreateTable(ct) = stmt {
8359            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8360                let nn = columns[0]
8361                    .constraints
8362                    .iter()
8363                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
8364                if let ColumnConstraintKind::NotNull { conflict } = &nn.unwrap().kind {
8365                    assert_eq!(*conflict, Some(ConflictAction::Ignore));
8366                }
8367            } else {
8368                unreachable!("expected Columns body");
8369            }
8370        } else {
8371            unreachable!("expected CreateTable");
8372        }
8373    }
8374
8375    #[test]
8376    fn test_without_rowid_table() {
8377        let stmt = parse_one("CREATE TABLE t (k TEXT PRIMARY KEY, v BLOB) WITHOUT ROWID");
8378        if let Statement::CreateTable(ct) = stmt {
8379            assert!(ct.without_rowid);
8380        } else {
8381            unreachable!("expected CreateTable");
8382        }
8383    }
8384
8385    #[test]
8386    fn test_without_rowid_no_autoincrement() {
8387        // Parser-level: verify WITHOUT ROWID and AUTOINCREMENT can both parse.
8388        // Runtime rejection of AUTOINCREMENT on WITHOUT ROWID is in schema validation.
8389        let stmt = parse_one(
8390            "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT) WITHOUT ROWID",
8391        );
8392        if let Statement::CreateTable(ct) = stmt {
8393            assert!(ct.without_rowid);
8394            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8395                let pk = columns[0].constraints.iter().find(|c| {
8396                    matches!(
8397                        c.kind,
8398                        ColumnConstraintKind::PrimaryKey {
8399                            autoincrement: true,
8400                            ..
8401                        }
8402                    )
8403                });
8404                assert!(pk.is_some());
8405            } else {
8406                unreachable!("expected Columns body");
8407            }
8408        } else {
8409            unreachable!("expected CreateTable");
8410        }
8411    }
8412
8413    #[test]
8414    fn test_without_rowid_integer_pk_not_alias() {
8415        // Parser-level: INTEGER PRIMARY KEY in WITHOUT ROWID parses as normal PK.
8416        // Runtime non-aliasing of rowid is in the B-tree layer.
8417        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT) WITHOUT ROWID");
8418        if let Statement::CreateTable(ct) = stmt {
8419            assert!(ct.without_rowid);
8420            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8421                assert_eq!(columns[0].name, "id");
8422                assert!(columns[0].type_name.is_some());
8423            } else {
8424                unreachable!("expected Columns body");
8425            }
8426        } else {
8427            unreachable!("expected CreateTable");
8428        }
8429    }
8430
8431    #[test]
8432    fn test_strict_table_type_enforcement() {
8433        // Parser-level: STRICT keyword parses on CREATE TABLE.
8434        // Runtime type enforcement on INSERT/UPDATE is in VDBE/engine.
8435        let stmt = parse_one("CREATE TABLE t (id INTEGER, name TEXT, score REAL) STRICT");
8436        if let Statement::CreateTable(ct) = stmt {
8437            assert!(ct.strict);
8438            assert!(!ct.without_rowid);
8439        } else {
8440            unreachable!("expected CreateTable");
8441        }
8442    }
8443
8444    #[test]
8445    fn test_strict_table_any_column() {
8446        // Parser-level: ANY type name parses in STRICT table context.
8447        let stmt = parse_one("CREATE TABLE t (id INTEGER, data ANY) STRICT");
8448        if let Statement::CreateTable(ct) = stmt {
8449            assert!(ct.strict);
8450            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8451                let tn = columns[1].type_name.as_ref().expect("type name");
8452                assert_eq!(tn.name, "ANY");
8453            } else {
8454                unreachable!("expected Columns body");
8455            }
8456        } else {
8457            unreachable!("expected CreateTable");
8458        }
8459    }
8460
8461    #[test]
8462    fn test_strict_allowed_types() {
8463        // Parser-level: STRICT table with all allowed type names parses.
8464        let stmt =
8465            parse_one("CREATE TABLE t (a INT, b INTEGER, c REAL, d TEXT, e BLOB, f ANY) STRICT");
8466        if let Statement::CreateTable(ct) = stmt {
8467            assert!(ct.strict);
8468            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8469                assert_eq!(columns.len(), 6);
8470                let types: Vec<&str> = columns
8471                    .iter()
8472                    .map(|c| c.type_name.as_ref().unwrap().name.as_str())
8473                    .collect();
8474                assert_eq!(types, vec!["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"]);
8475            } else {
8476                unreachable!("expected Columns body");
8477            }
8478        } else {
8479            unreachable!("expected CreateTable");
8480        }
8481    }
8482
8483    #[test]
8484    fn test_generated_col_virtual() {
8485        let stmt = parse_one(
8486            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) VIRTUAL)",
8487        );
8488        if let Statement::CreateTable(ct) = stmt {
8489            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8490                let generated = columns[2]
8491                    .constraints
8492                    .iter()
8493                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8494                assert!(generated.is_some(), "Generated constraint missing");
8495                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
8496                    assert_eq!(*storage, Some(GeneratedStorage::Virtual));
8497                }
8498            } else {
8499                unreachable!("expected Columns body");
8500            }
8501        } else {
8502            unreachable!("expected CreateTable");
8503        }
8504    }
8505
8506    #[test]
8507    fn test_generated_col_stored() {
8508        let stmt = parse_one(
8509            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a * b) STORED)",
8510        );
8511        if let Statement::CreateTable(ct) = stmt {
8512            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8513                let generated = columns[2]
8514                    .constraints
8515                    .iter()
8516                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8517                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
8518                    assert_eq!(*storage, Some(GeneratedStorage::Stored));
8519                }
8520            } else {
8521                unreachable!("expected Columns body");
8522            }
8523        } else {
8524            unreachable!("expected CreateTable");
8525        }
8526    }
8527
8528    #[test]
8529    fn test_generated_col_ordering() {
8530        // Parser-level: generated columns with forward references parse correctly.
8531        // Runtime rejection of forward references is in schema validation.
8532        let stmt = parse_one(
8533            "CREATE TABLE t (\
8534             a INTEGER, \
8535             b INTEGER GENERATED ALWAYS AS (a + 1) STORED, \
8536             c INTEGER GENERATED ALWAYS AS (b * 2) VIRTUAL)",
8537        );
8538        if let Statement::CreateTable(ct) = stmt {
8539            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8540                assert_eq!(columns.len(), 3);
8541                // Both b and c have Generated constraints
8542                let gen_b = columns[1]
8543                    .constraints
8544                    .iter()
8545                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8546                let gen_c = columns[2]
8547                    .constraints
8548                    .iter()
8549                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
8550                assert!(gen_b, "column b should be generated");
8551                assert!(gen_c, "column c should be generated");
8552            } else {
8553                unreachable!("expected Columns body");
8554            }
8555        } else {
8556            unreachable!("expected CreateTable");
8557        }
8558    }
8559
8560    #[test]
8561    fn test_generated_col_stored_indexable() {
8562        // Parser-level: STORED generated column can appear alongside indexes.
8563        // Runtime indexability verified in B-tree/engine tests.
8564        let stmts = parse_ok(
8565            "CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED); \
8566             CREATE INDEX idx_b ON t (b)",
8567        );
8568        assert_eq!(stmts.len(), 2);
8569        assert!(matches!(stmts[0], Statement::CreateTable(_)));
8570        assert!(matches!(stmts[1], Statement::CreateIndex(_)));
8571    }
8572
8573    #[test]
8574    fn test_type_affinity_int() {
8575        // Parser-level: type names containing "INT" parse as valid type names.
8576        // Runtime affinity determination is in the type system.
8577        let stmt = parse_one("CREATE TABLE t (a INTEGER, b BIGINT, c SMALLINT, d MEDIUMINT)");
8578        if let Statement::CreateTable(ct) = stmt {
8579            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8580                assert_eq!(columns.len(), 4);
8581                for col in columns {
8582                    let tn = col.type_name.as_ref().unwrap();
8583                    assert!(tn.name.contains("INT"), "{} should contain INT", tn.name);
8584                }
8585            } else {
8586                unreachable!("expected Columns body");
8587            }
8588        } else {
8589            unreachable!("expected CreateTable");
8590        }
8591    }
8592
8593    #[test]
8594    fn test_type_affinity_text() {
8595        let stmt = parse_one("CREATE TABLE t (a TEXT, b VARCHAR, c CLOB, d CHARACTER)");
8596        if let Statement::CreateTable(ct) = stmt {
8597            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8598                assert_eq!(columns.len(), 4);
8599                for col in columns {
8600                    assert!(col.type_name.is_some());
8601                }
8602            } else {
8603                unreachable!("expected Columns body");
8604            }
8605        } else {
8606            unreachable!("expected CreateTable");
8607        }
8608    }
8609
8610    #[test]
8611    fn test_type_affinity_blob() {
8612        let stmt = parse_one("CREATE TABLE t (a BLOB, b)");
8613        if let Statement::CreateTable(ct) = stmt {
8614            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8615                assert_eq!(columns.len(), 2);
8616                assert_eq!(columns[0].type_name.as_ref().unwrap().name, "BLOB");
8617                // Column b has no type name -> BLOB affinity
8618                assert!(columns[1].type_name.is_none());
8619            } else {
8620                unreachable!("expected Columns body");
8621            }
8622        } else {
8623            unreachable!("expected CreateTable");
8624        }
8625    }
8626
8627    #[test]
8628    fn test_type_affinity_real() {
8629        let stmt = parse_one("CREATE TABLE t (a REAL, b DOUBLE, c FLOAT)");
8630        if let Statement::CreateTable(ct) = stmt {
8631            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8632                assert_eq!(columns.len(), 3);
8633                for col in columns {
8634                    assert!(col.type_name.is_some());
8635                }
8636            } else {
8637                unreachable!("expected Columns body");
8638            }
8639        } else {
8640            unreachable!("expected CreateTable");
8641        }
8642    }
8643
8644    #[test]
8645    fn test_type_affinity_numeric() {
8646        let stmt = parse_one("CREATE TABLE t (a NUMERIC, b DECIMAL, c BOOLEAN)");
8647        if let Statement::CreateTable(ct) = stmt {
8648            if let CreateTableBody::Columns { columns, .. } = &ct.body {
8649                assert_eq!(columns.len(), 3);
8650                for col in columns {
8651                    assert!(col.type_name.is_some());
8652                }
8653            } else {
8654                unreachable!("expected Columns body");
8655            }
8656        } else {
8657            unreachable!("expected CreateTable");
8658        }
8659    }
8660
8661    #[test]
8662    fn test_create_unique_index() {
8663        let stmt = parse_one("CREATE UNIQUE INDEX idx_email ON users (email)");
8664        if let Statement::CreateIndex(ci) = stmt {
8665            assert!(ci.unique);
8666            assert_eq!(ci.name.name, "idx_email");
8667            assert_eq!(ci.table, "users");
8668            assert_eq!(ci.columns.len(), 1);
8669            assert!(ci.where_clause.is_none());
8670        } else {
8671            unreachable!("expected CreateIndex");
8672        }
8673    }
8674
8675    #[test]
8676    fn test_partial_index() {
8677        let stmt = parse_one("CREATE INDEX idx_active ON users (name) WHERE active = 1");
8678        if let Statement::CreateIndex(ci) = stmt {
8679            assert!(!ci.unique);
8680            assert_eq!(ci.name.name, "idx_active");
8681            assert!(ci.where_clause.is_some(), "partial index WHERE missing");
8682        } else {
8683            unreachable!("expected CreateIndex");
8684        }
8685    }
8686
8687    #[test]
8688    fn test_partial_index_planner_usage() {
8689        // Parser-level: partial index with complex WHERE parses correctly.
8690        // Runtime planner usage (query WHERE implies index WHERE) is in planner tests.
8691        let stmt =
8692            parse_one("CREATE INDEX idx_recent ON orders (created_at) WHERE status != 'archived'");
8693        if let Statement::CreateIndex(ci) = stmt {
8694            assert!(ci.where_clause.is_some());
8695            assert_eq!(ci.columns.len(), 1);
8696        } else {
8697            unreachable!("expected CreateIndex");
8698        }
8699    }
8700
8701    #[test]
8702    fn test_expression_index() {
8703        let stmt = parse_one("CREATE INDEX idx_lower_name ON users (lower(name))");
8704        if let Statement::CreateIndex(ci) = stmt {
8705            assert_eq!(ci.columns.len(), 1);
8706            // The indexed expression should be a function call, not a plain column
8707            assert!(
8708                matches!(ci.columns[0].expr, Expr::FunctionCall { .. }),
8709                "expected function call expression in index"
8710            );
8711        } else {
8712            unreachable!("expected CreateIndex");
8713        }
8714    }
8715
8716    #[test]
8717    fn test_expression_index_planner_match() {
8718        // Parser-level: expression index with arithmetic parses correctly.
8719        // Runtime structural equality matching is in planner tests.
8720        let stmt = parse_one("CREATE INDEX idx_calc ON t (a + b * 2)");
8721        if let Statement::CreateIndex(ci) = stmt {
8722            assert_eq!(ci.columns.len(), 1);
8723            assert!(
8724                matches!(ci.columns[0].expr, Expr::BinaryOp { .. }),
8725                "expected binary op in expression index"
8726            );
8727        } else {
8728            unreachable!("expected CreateIndex");
8729        }
8730    }
8731
8732    #[test]
8733    fn test_index_collate_asc_desc() {
8734        let stmt = parse_one("CREATE INDEX idx_multi ON t (a COLLATE NOCASE ASC, b DESC, c)");
8735        if let Statement::CreateIndex(ci) = stmt {
8736            assert_eq!(ci.columns.len(), 3);
8737            // COLLATE is consumed by the expression parser as Expr::Collate
8738            assert!(
8739                matches!(
8740                    &ci.columns[0].expr,
8741                    Expr::Collate { collation, .. } if collation == "NOCASE"
8742                ),
8743                "expected Collate expr with NOCASE"
8744            );
8745            assert_eq!(ci.columns[0].direction, Some(SortDirection::Asc));
8746            assert_eq!(ci.columns[1].direction, Some(SortDirection::Desc));
8747            assert!(ci.columns[2].direction.is_none());
8748        } else {
8749            unreachable!("expected CreateIndex");
8750        }
8751    }
8752
8753    // -----------------------------------------------------------------------
8754    // bd-3kin §12.7-12.9 CREATE VIEW + CREATE TRIGGER + ALTER/DROP parsing tests
8755    // -----------------------------------------------------------------------
8756
8757    #[test]
8758    fn test_create_view_basic() {
8759        let stmt = parse_one("CREATE VIEW v AS SELECT id, name FROM users");
8760        if let Statement::CreateView(cv) = stmt {
8761            assert_eq!(cv.name.name, "v");
8762            assert!(!cv.if_not_exists);
8763            assert!(!cv.temporary);
8764            assert!(cv.columns.is_empty());
8765        } else {
8766            unreachable!("expected CreateView");
8767        }
8768    }
8769
8770    #[test]
8771    fn test_create_view_column_aliases() {
8772        let stmt = parse_one("CREATE VIEW v (user_id, user_name) AS SELECT id, name FROM users");
8773        if let Statement::CreateView(cv) = stmt {
8774            assert_eq!(cv.columns, vec!["user_id", "user_name"]);
8775        } else {
8776            unreachable!("expected CreateView");
8777        }
8778    }
8779
8780    #[test]
8781    fn test_create_view_if_not_exists() {
8782        let stmt = parse_one("CREATE VIEW IF NOT EXISTS v AS SELECT 1");
8783        if let Statement::CreateView(cv) = stmt {
8784            assert!(cv.if_not_exists);
8785        } else {
8786            unreachable!("expected CreateView");
8787        }
8788    }
8789
8790    #[test]
8791    fn test_create_temp_view() {
8792        let stmt = parse_one("CREATE TEMP VIEW tv AS SELECT 1");
8793        if let Statement::CreateView(cv) = stmt {
8794            assert!(cv.temporary);
8795        } else {
8796            unreachable!("expected CreateView");
8797        }
8798    }
8799
8800    #[test]
8801    fn test_view_inline_expansion() {
8802        // Parser-level: view defined with WHERE is captured in AST.
8803        // Runtime inline expansion (not materialization) is in the planner.
8804        let stmt =
8805            parse_one("CREATE VIEW active_users AS SELECT id, name FROM users WHERE active = 1");
8806        if let Statement::CreateView(cv) = stmt {
8807            assert_eq!(cv.name.name, "active_users");
8808        } else {
8809            unreachable!("expected CreateView");
8810        }
8811    }
8812
8813    #[test]
8814    fn test_view_read_only() {
8815        // Parser-level: views parse as SELECT-only definitions.
8816        // Runtime read-only enforcement (rejecting DML without INSTEAD OF) is in the engine.
8817        let stmt = parse_one("CREATE VIEW v AS SELECT * FROM t");
8818        assert!(matches!(stmt, Statement::CreateView(_)));
8819    }
8820
8821    #[test]
8822    fn test_view_with_recursive_cte() {
8823        // View referencing a subquery (parser does not yet support WITH directly
8824        // in CREATE VIEW ... AS context; CTE-in-view support is a planner concern).
8825        let stmt = parse_one(
8826            "CREATE VIEW tree AS \
8827             SELECT n.id, n.parent FROM nodes n \
8828             WHERE n.parent IS NULL \
8829             UNION ALL \
8830             SELECT c.id, c.parent FROM nodes c JOIN nodes p ON c.parent = p.id",
8831        );
8832        if let Statement::CreateView(cv) = stmt {
8833            assert_eq!(cv.name.name, "tree");
8834            // Compound UNION ALL captured
8835            assert!(
8836                !cv.query.body.compounds.is_empty(),
8837                "expected compound SELECT in view"
8838            );
8839        } else {
8840            unreachable!("expected CreateView");
8841        }
8842    }
8843
8844    #[test]
8845    fn test_instead_of_trigger_on_view() {
8846        let stmt = parse_one(
8847            "CREATE TRIGGER tr INSTEAD OF INSERT ON v BEGIN \
8848             INSERT INTO t (a) VALUES (NEW.a); \
8849             END",
8850        );
8851        if let Statement::CreateTrigger(ct) = stmt {
8852            assert_eq!(ct.timing, TriggerTiming::InsteadOf);
8853            assert!(matches!(ct.event, TriggerEvent::Insert));
8854            assert_eq!(ct.table, "v");
8855            assert!(!ct.body.is_empty());
8856        } else {
8857            unreachable!("expected CreateTrigger");
8858        }
8859    }
8860
8861    #[test]
8862    fn test_trigger_before_insert() {
8863        let stmt = parse_one("CREATE TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
8864        if let Statement::CreateTrigger(ct) = stmt {
8865            assert_eq!(ct.timing, TriggerTiming::Before);
8866            assert!(matches!(ct.event, TriggerEvent::Insert));
8867        } else {
8868            unreachable!("expected CreateTrigger");
8869        }
8870    }
8871
8872    #[test]
8873    fn test_trigger_after_insert() {
8874        let stmt = parse_one("CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END");
8875        if let Statement::CreateTrigger(ct) = stmt {
8876            assert_eq!(ct.timing, TriggerTiming::After);
8877            assert!(matches!(ct.event, TriggerEvent::Insert));
8878        } else {
8879            unreachable!("expected CreateTrigger");
8880        }
8881    }
8882
8883    #[test]
8884    fn test_trigger_before_update() {
8885        let stmt = parse_one("CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN SELECT OLD.a, NEW.a; END");
8886        if let Statement::CreateTrigger(ct) = stmt {
8887            assert_eq!(ct.timing, TriggerTiming::Before);
8888            assert!(matches!(ct.event, TriggerEvent::Update(_)));
8889        } else {
8890            unreachable!("expected CreateTrigger");
8891        }
8892    }
8893
8894    #[test]
8895    fn test_trigger_after_delete() {
8896        let stmt = parse_one("CREATE TRIGGER tr AFTER DELETE ON t BEGIN SELECT OLD.id; END");
8897        if let Statement::CreateTrigger(ct) = stmt {
8898            assert_eq!(ct.timing, TriggerTiming::After);
8899            assert!(matches!(ct.event, TriggerEvent::Delete));
8900        } else {
8901            unreachable!("expected CreateTrigger");
8902        }
8903    }
8904
8905    #[test]
8906    fn test_trigger_update_of_column() {
8907        let stmt =
8908            parse_one("CREATE TRIGGER tr BEFORE UPDATE OF name, email ON t BEGIN SELECT 1; END");
8909        if let Statement::CreateTrigger(ct) = stmt {
8910            if let TriggerEvent::Update(cols) = &ct.event {
8911                assert_eq!(cols, &["name", "email"]);
8912            } else {
8913                unreachable!("expected Update event with columns");
8914            }
8915        } else {
8916            unreachable!("expected CreateTrigger");
8917        }
8918    }
8919
8920    #[test]
8921    fn test_trigger_when_clause() {
8922        let stmt = parse_one(
8923            "CREATE TRIGGER tr BEFORE INSERT ON t WHEN NEW.active = 1 BEGIN SELECT 1; END",
8924        );
8925        if let Statement::CreateTrigger(ct) = stmt {
8926            assert!(ct.when.is_some(), "WHEN clause missing");
8927        } else {
8928            unreachable!("expected CreateTrigger");
8929        }
8930    }
8931
8932    #[test]
8933    fn test_trigger_old_new_pseudo_tables() {
8934        let stmt = parse_one(
8935            "CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN \
8936             INSERT INTO log (old_val, new_val) VALUES (OLD.a, NEW.a); \
8937             END",
8938        );
8939        if let Statement::CreateTrigger(ct) = stmt {
8940            assert_eq!(ct.body.len(), 1);
8941            assert!(matches!(ct.body[0], Statement::Insert(_)));
8942        } else {
8943            unreachable!("expected CreateTrigger");
8944        }
8945    }
8946
8947    #[test]
8948    fn test_trigger_raise_abort() {
8949        let stmt = parse_one(
8950            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8951             SELECT RAISE(ABORT, 'not allowed'); \
8952             END",
8953        );
8954        if let Statement::CreateTrigger(ct) = stmt {
8955            assert_eq!(ct.body.len(), 1);
8956        } else {
8957            unreachable!("expected CreateTrigger");
8958        }
8959    }
8960
8961    #[test]
8962    fn test_trigger_raise_rollback() {
8963        let stmt = parse_one(
8964            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8965             SELECT RAISE(ROLLBACK, 'invalid'); \
8966             END",
8967        );
8968        if let Statement::CreateTrigger(ct) = stmt {
8969            assert_eq!(ct.body.len(), 1);
8970        } else {
8971            unreachable!("expected CreateTrigger");
8972        }
8973    }
8974
8975    #[test]
8976    fn test_trigger_raise_fail() {
8977        let stmt = parse_one(
8978            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8979             SELECT RAISE(FAIL, 'bad data'); \
8980             END",
8981        );
8982        if let Statement::CreateTrigger(ct) = stmt {
8983            assert_eq!(ct.body.len(), 1);
8984        } else {
8985            unreachable!("expected CreateTrigger");
8986        }
8987    }
8988
8989    #[test]
8990    fn test_trigger_raise_ignore() {
8991        let stmt = parse_one(
8992            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
8993             SELECT RAISE(IGNORE); \
8994             END",
8995        );
8996        if let Statement::CreateTrigger(ct) = stmt {
8997            assert_eq!(ct.body.len(), 1);
8998        } else {
8999            unreachable!("expected CreateTrigger");
9000        }
9001    }
9002
9003    #[test]
9004    fn test_trigger_recursive() {
9005        // Parser-level: trigger referencing its own table parses normally.
9006        // Runtime recursive trigger behavior (PRAGMA recursive_triggers) is in the engine.
9007        let stmt = parse_one(
9008            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
9009             INSERT INTO t (val) VALUES (NEW.val + 1); \
9010             END",
9011        );
9012        if let Statement::CreateTrigger(ct) = stmt {
9013            assert_eq!(ct.timing, TriggerTiming::After);
9014            assert_eq!(ct.table, "t");
9015            assert_eq!(ct.body.len(), 1);
9016        } else {
9017            unreachable!("expected CreateTrigger");
9018        }
9019    }
9020
9021    #[test]
9022    fn test_trigger_max_recursion_depth() {
9023        // Parser-level: trigger with WHEN depth guard parses.
9024        // Runtime SQLITE_MAX_TRIGGER_DEPTH enforcement is in the VDBE.
9025        let stmt = parse_one(
9026            "CREATE TRIGGER tr AFTER INSERT ON t \
9027             WHEN NEW.depth < 1000 BEGIN \
9028             INSERT INTO t (depth) VALUES (NEW.depth + 1); \
9029             END",
9030        );
9031        if let Statement::CreateTrigger(ct) = stmt {
9032            assert!(ct.when.is_some());
9033            assert_eq!(ct.body.len(), 1);
9034        } else {
9035            unreachable!("expected CreateTrigger");
9036        }
9037    }
9038
9039    #[test]
9040    fn test_trigger_heap_frame_stack() {
9041        // Parser-level: trigger with UPDATE body parses correctly.
9042        // Runtime heap-allocated frame stack is in the VDBE.
9043        let stmt = parse_one(
9044            "CREATE TRIGGER tr AFTER UPDATE ON t BEGIN \
9045             UPDATE t SET counter = counter + 1 WHERE id = NEW.parent_id; \
9046             END",
9047        );
9048        if let Statement::CreateTrigger(ct) = stmt {
9049            assert_eq!(ct.body.len(), 1);
9050            assert!(matches!(ct.body[0], Statement::Update(_)));
9051        } else {
9052            unreachable!("expected CreateTrigger");
9053        }
9054    }
9055
9056    #[test]
9057    fn test_trigger_multiple_dml() {
9058        let stmt = parse_one(
9059            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
9060             INSERT INTO audit (action) VALUES ('insert'); \
9061             UPDATE stats SET count = count + 1; \
9062             END",
9063        );
9064        if let Statement::CreateTrigger(ct) = stmt {
9065            assert_eq!(ct.body.len(), 2);
9066            assert!(matches!(ct.body[0], Statement::Insert(_)));
9067            assert!(matches!(ct.body[1], Statement::Update(_)));
9068        } else {
9069            unreachable!("expected CreateTrigger");
9070        }
9071    }
9072
9073    #[test]
9074    fn test_alter_table_rename() {
9075        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
9076        if let Statement::AlterTable(at) = stmt {
9077            assert_eq!(at.table.name, "t");
9078            assert!(matches!(at.action, AlterTableAction::RenameTo(ref n) if n == "t2"));
9079        } else {
9080            unreachable!("expected AlterTable");
9081        }
9082    }
9083
9084    #[test]
9085    fn test_alter_table_rename_column() {
9086        let stmt = parse_one("ALTER TABLE t RENAME COLUMN old_name TO new_name");
9087        if let Statement::AlterTable(at) = stmt {
9088            if let AlterTableAction::RenameColumn { old, new } = &at.action {
9089                assert_eq!(old, "old_name");
9090                assert_eq!(new, "new_name");
9091            } else {
9092                unreachable!("expected RenameColumn action");
9093            }
9094        } else {
9095            unreachable!("expected AlterTable");
9096        }
9097    }
9098
9099    #[test]
9100    fn test_alter_table_add_column() {
9101        let stmt = parse_one("ALTER TABLE t ADD COLUMN email TEXT NOT NULL DEFAULT ''");
9102        if let Statement::AlterTable(at) = stmt {
9103            if let AlterTableAction::AddColumn(col) = &at.action {
9104                assert_eq!(col.name, "email");
9105                assert!(!col.constraints.is_empty());
9106            } else {
9107                unreachable!("expected AddColumn action");
9108            }
9109        } else {
9110            unreachable!("expected AlterTable");
9111        }
9112    }
9113
9114    #[test]
9115    fn test_alter_table_remove_column() {
9116        let stmt = parse_one("ALTER TABLE t DROP COLUMN old_col");
9117        if let Statement::AlterTable(at) = stmt {
9118            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "old_col"));
9119        } else {
9120            unreachable!("expected AlterTable");
9121        }
9122    }
9123
9124    #[test]
9125    fn test_alter_remove_column_pk_fails() {
9126        // Parser-level: DROP COLUMN on a PK column parses normally.
9127        // Runtime rejection is in schema validation.
9128        let stmt = parse_one("ALTER TABLE t DROP COLUMN id");
9129        if let Statement::AlterTable(at) = stmt {
9130            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "id"));
9131        } else {
9132            unreachable!("expected AlterTable");
9133        }
9134    }
9135
9136    #[test]
9137    fn test_alter_remove_column_unique_fails() {
9138        // Parser-level: DROP COLUMN on UNIQUE column parses normally.
9139        let stmt = parse_one("ALTER TABLE t DROP COLUMN email");
9140        if let Statement::AlterTable(at) = stmt {
9141            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "email"));
9142        } else {
9143            unreachable!("expected AlterTable");
9144        }
9145    }
9146
9147    #[test]
9148    fn test_alter_remove_column_index_fails() {
9149        // Parser-level: DROP COLUMN on indexed column parses normally.
9150        let stmt = parse_one("ALTER TABLE t DROP COLUMN indexed_col");
9151        if let Statement::AlterTable(at) = stmt {
9152            assert!(matches!(
9153                at.action,
9154                AlterTableAction::DropColumn(ref c) if c == "indexed_col"
9155            ));
9156        } else {
9157            unreachable!("expected AlterTable");
9158        }
9159    }
9160
9161    #[test]
9162    fn test_alter_remove_column_check_fails() {
9163        // Parser-level: DROP COLUMN on CHECK-constrained column parses normally.
9164        let stmt = parse_one("ALTER TABLE t DROP COLUMN checked_col");
9165        if let Statement::AlterTable(at) = stmt {
9166            assert!(matches!(
9167                at.action,
9168                AlterTableAction::DropColumn(ref c) if c == "checked_col"
9169            ));
9170        } else {
9171            unreachable!("expected AlterTable");
9172        }
9173    }
9174
9175    #[test]
9176    fn test_alter_remove_column_fk_fails() {
9177        // Parser-level: DROP COLUMN on FK-constrained column parses normally.
9178        let stmt = parse_one("ALTER TABLE t DROP COLUMN fk_col");
9179        if let Statement::AlterTable(at) = stmt {
9180            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "fk_col"));
9181        } else {
9182            unreachable!("expected AlterTable");
9183        }
9184    }
9185
9186    #[test]
9187    fn test_alter_remove_only_column_fails() {
9188        // Parser-level: DROP COLUMN on only column parses normally.
9189        let stmt = parse_one("ALTER TABLE t DROP COLUMN only_col");
9190        if let Statement::AlterTable(at) = stmt {
9191            assert!(matches!(
9192                at.action,
9193                AlterTableAction::DropColumn(ref c) if c == "only_col"
9194            ));
9195        } else {
9196            unreachable!("expected AlterTable");
9197        }
9198    }
9199
9200    #[test]
9201    fn test_ddl_remove_table() {
9202        let stmt = parse_one("DROP TABLE t");
9203        if let Statement::Drop(d) = stmt {
9204            assert_eq!(d.object_type, DropObjectType::Table);
9205            assert!(!d.if_exists);
9206            assert_eq!(d.name.name, "t");
9207        } else {
9208            unreachable!("expected Drop");
9209        }
9210    }
9211
9212    #[test]
9213    fn test_ddl_remove_table_if_exists() {
9214        let stmt = parse_one("DROP TABLE IF EXISTS t");
9215        if let Statement::Drop(d) = stmt {
9216            assert_eq!(d.object_type, DropObjectType::Table);
9217            assert!(d.if_exists);
9218        } else {
9219            unreachable!("expected Drop");
9220        }
9221    }
9222
9223    #[test]
9224    fn test_ddl_remove_index() {
9225        let stmt = parse_one("DROP INDEX idx");
9226        if let Statement::Drop(d) = stmt {
9227            assert_eq!(d.object_type, DropObjectType::Index);
9228            assert_eq!(d.name.name, "idx");
9229        } else {
9230            unreachable!("expected Drop");
9231        }
9232    }
9233
9234    #[test]
9235    fn test_ddl_remove_view() {
9236        let stmt = parse_one("DROP VIEW v");
9237        if let Statement::Drop(d) = stmt {
9238            assert_eq!(d.object_type, DropObjectType::View);
9239            assert_eq!(d.name.name, "v");
9240        } else {
9241            unreachable!("expected Drop");
9242        }
9243    }
9244
9245    #[test]
9246    fn test_ddl_remove_trigger() {
9247        let stmt = parse_one("DROP TRIGGER tr");
9248        if let Statement::Drop(d) = stmt {
9249            assert_eq!(d.object_type, DropObjectType::Trigger);
9250            assert_eq!(d.name.name, "tr");
9251        } else {
9252            unreachable!("expected Drop");
9253        }
9254    }
9255
9256    // -----------------------------------------------------------------------
9257    // bd-3kin §12.7-12.9 DDL gap-fill: REINDEX, ANALYZE, qualified names,
9258    //                                   IF NOT EXISTS/TEMP triggers
9259    // -----------------------------------------------------------------------
9260
9261    #[test]
9262    fn test_reindex_global() {
9263        let stmt = parse_one("REINDEX");
9264        assert!(matches!(stmt, Statement::Reindex(None)));
9265    }
9266
9267    #[test]
9268    fn test_reindex_table() {
9269        let stmt = parse_one("REINDEX t");
9270        if let Statement::Reindex(Some(name)) = stmt {
9271            assert_eq!(name.name, "t");
9272            assert!(name.schema.is_none());
9273        } else {
9274            unreachable!("expected Reindex(Some), got {stmt:?}");
9275        }
9276    }
9277
9278    #[test]
9279    fn test_reindex_qualified() {
9280        let stmt = parse_one("REINDEX main.idx");
9281        if let Statement::Reindex(Some(name)) = stmt {
9282            assert_eq!(name.schema.as_deref(), Some("main"));
9283            assert_eq!(name.name, "idx");
9284        } else {
9285            unreachable!("expected Reindex(Some), got {stmt:?}");
9286        }
9287    }
9288
9289    #[test]
9290    fn test_analyze_global() {
9291        let stmt = parse_one("ANALYZE");
9292        assert!(matches!(stmt, Statement::Analyze(None)));
9293    }
9294
9295    #[test]
9296    fn test_analyze_table() {
9297        let stmt = parse_one("ANALYZE t");
9298        if let Statement::Analyze(Some(name)) = stmt {
9299            assert_eq!(name.name, "t");
9300            assert!(name.schema.is_none());
9301        } else {
9302            unreachable!("expected Analyze(Some), got {stmt:?}");
9303        }
9304    }
9305
9306    #[test]
9307    fn test_analyze_qualified() {
9308        let stmt = parse_one("ANALYZE main.t");
9309        if let Statement::Analyze(Some(name)) = stmt {
9310            assert_eq!(name.schema.as_deref(), Some("main"));
9311            assert_eq!(name.name, "t");
9312        } else {
9313            unreachable!("expected Analyze(Some), got {stmt:?}");
9314        }
9315    }
9316
9317    #[test]
9318    fn test_drop_view_if_exists() {
9319        let stmt = parse_one("DROP VIEW IF EXISTS v");
9320        if let Statement::Drop(d) = stmt {
9321            assert_eq!(d.object_type, DropObjectType::View);
9322            assert!(d.if_exists);
9323            assert_eq!(d.name.name, "v");
9324        } else {
9325            unreachable!("expected Drop");
9326        }
9327    }
9328
9329    #[test]
9330    fn test_drop_index_if_exists() {
9331        let stmt = parse_one("DROP INDEX IF EXISTS idx");
9332        if let Statement::Drop(d) = stmt {
9333            assert_eq!(d.object_type, DropObjectType::Index);
9334            assert!(d.if_exists);
9335        } else {
9336            unreachable!("expected Drop");
9337        }
9338    }
9339
9340    #[test]
9341    fn test_drop_trigger_if_exists_qualified() {
9342        let stmt = parse_one("DROP TRIGGER IF EXISTS main.tr");
9343        if let Statement::Drop(d) = stmt {
9344            assert_eq!(d.object_type, DropObjectType::Trigger);
9345            assert!(d.if_exists);
9346            assert_eq!(d.name.schema.as_deref(), Some("main"));
9347            assert_eq!(d.name.name, "tr");
9348        } else {
9349            unreachable!("expected Drop");
9350        }
9351    }
9352
9353    #[test]
9354    fn test_drop_table_qualified() {
9355        let stmt = parse_one("DROP TABLE main.t");
9356        if let Statement::Drop(d) = stmt {
9357            assert_eq!(d.name.schema.as_deref(), Some("main"));
9358            assert_eq!(d.name.name, "t");
9359        } else {
9360            unreachable!("expected Drop");
9361        }
9362    }
9363
9364    #[test]
9365    fn test_create_trigger_if_not_exists() {
9366        let stmt =
9367            parse_one("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9368        if let Statement::CreateTrigger(ct) = stmt {
9369            assert!(ct.if_not_exists);
9370            assert_eq!(ct.name.name, "tr");
9371        } else {
9372            unreachable!("expected CreateTrigger");
9373        }
9374    }
9375
9376    #[test]
9377    fn test_create_temp_trigger() {
9378        let stmt = parse_one("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9379        if let Statement::CreateTrigger(ct) = stmt {
9380            assert!(ct.temporary);
9381            assert_eq!(ct.name.name, "tr");
9382        } else {
9383            unreachable!("expected CreateTrigger");
9384        }
9385    }
9386
9387    #[test]
9388    fn test_create_view_qualified_name() {
9389        let stmt = parse_one("CREATE VIEW main.v AS SELECT 1");
9390        if let Statement::CreateView(cv) = stmt {
9391            assert_eq!(cv.name.schema.as_deref(), Some("main"));
9392            assert_eq!(cv.name.name, "v");
9393        } else {
9394            unreachable!("expected CreateView");
9395        }
9396    }
9397
9398    #[test]
9399    fn test_alter_table_qualified() {
9400        let stmt = parse_one("ALTER TABLE main.t RENAME TO u");
9401        if let Statement::AlterTable(at) = stmt {
9402            assert_eq!(at.table.schema.as_deref(), Some("main"));
9403            assert_eq!(at.table.name, "t");
9404        } else {
9405            unreachable!("expected AlterTable");
9406        }
9407    }
9408
9409    #[test]
9410    fn test_roundtrip_reindex_all() {
9411        assert_roundtrip("REINDEX");
9412        assert_roundtrip("REINDEX t");
9413        assert_roundtrip("REINDEX main.idx");
9414    }
9415
9416    #[test]
9417    fn test_roundtrip_analyze_all() {
9418        assert_roundtrip("ANALYZE");
9419        assert_roundtrip("ANALYZE t");
9420        assert_roundtrip("ANALYZE main.t");
9421    }
9422
9423    #[test]
9424    fn test_roundtrip_drop_all_types_extended() {
9425        assert_roundtrip("DROP TABLE IF EXISTS main.t");
9426        assert_roundtrip("DROP VIEW IF EXISTS v");
9427        assert_roundtrip("DROP INDEX IF EXISTS idx");
9428        assert_roundtrip("DROP TRIGGER IF EXISTS main.tr");
9429    }
9430
9431    #[test]
9432    fn test_roundtrip_create_trigger_extended() {
9433        assert_roundtrip("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9434        assert_roundtrip("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
9435        assert_roundtrip(
9436            "CREATE TRIGGER tr INSTEAD OF UPDATE ON v BEGIN INSERT INTO log VALUES (1); END",
9437        );
9438        assert_roundtrip("CREATE TRIGGER tr BEFORE UPDATE OF a, b ON t BEGIN SELECT 1; END");
9439        assert_roundtrip(
9440            "CREATE TRIGGER tr AFTER DELETE ON \"order\" BEGIN INSERT INTO log VALUES (OLD.id); END",
9441        );
9442    }
9443
9444    #[test]
9445    fn test_roundtrip_create_view_extended() {
9446        assert_roundtrip("CREATE VIEW main.v AS SELECT 1");
9447        assert_roundtrip("CREATE VIEW v(x, y, z) AS SELECT a, b, c FROM t");
9448    }
9449
9450    #[test]
9451    fn test_roundtrip_alter_table_extended() {
9452        assert_roundtrip("ALTER TABLE t RENAME COLUMN a TO b");
9453        assert_roundtrip("ALTER TABLE main.t RENAME TO u");
9454        assert_roundtrip("ALTER TABLE t ADD COLUMN c INTEGER NOT NULL DEFAULT 0");
9455    }
9456
9457    // -----------------------------------------------------------------------
9458    // bd-7pxb §12.10-12.12 Transaction Control + ATTACH/DETACH + EXPLAIN
9459    // -----------------------------------------------------------------------
9460
9461    #[test]
9462    fn test_begin_deferred() {
9463        let stmt = parse_one("BEGIN DEFERRED TRANSACTION");
9464        if let Statement::Begin(b) = stmt {
9465            assert_eq!(b.mode, Some(TransactionMode::Deferred));
9466        } else {
9467            unreachable!("expected Begin");
9468        }
9469    }
9470
9471    #[test]
9472    fn test_begin_immediate() {
9473        let stmt = parse_one("BEGIN IMMEDIATE");
9474        if let Statement::Begin(b) = stmt {
9475            assert_eq!(b.mode, Some(TransactionMode::Immediate));
9476        } else {
9477            unreachable!("expected Begin");
9478        }
9479    }
9480
9481    #[test]
9482    fn test_begin_exclusive() {
9483        let stmt = parse_one("BEGIN EXCLUSIVE TRANSACTION");
9484        if let Statement::Begin(b) = stmt {
9485            assert_eq!(b.mode, Some(TransactionMode::Exclusive));
9486        } else {
9487            unreachable!("expected Begin");
9488        }
9489    }
9490
9491    #[test]
9492    fn test_begin_concurrent() {
9493        let stmt = parse_one("BEGIN CONCURRENT");
9494        if let Statement::Begin(b) = stmt {
9495            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
9496        } else {
9497            unreachable!("expected Begin");
9498        }
9499    }
9500
9501    #[test]
9502    fn test_concurrent_no_conflict() {
9503        // Parser-level: BEGIN without mode (the concurrent entry point) parses.
9504        // Runtime concurrent writer conflict detection is in the MVCC/WAL layer.
9505        let stmt = parse_one("BEGIN");
9506        assert!(matches!(stmt, Statement::Begin(_)));
9507    }
9508
9509    #[test]
9510    fn test_concurrent_page_conflict() {
9511        // Parser-level: verify basic transaction and DML parse.
9512        // Runtime page-level conflict (SQLITE_BUSY_SNAPSHOT) is in the MVCC layer.
9513        let stmts = parse_ok("BEGIN; INSERT INTO t (a) VALUES (1)");
9514        assert_eq!(stmts.len(), 2);
9515        assert!(matches!(stmts[0], Statement::Begin(_)));
9516        assert!(matches!(stmts[1], Statement::Insert(_)));
9517    }
9518
9519    #[test]
9520    fn test_commit_end_synonym() {
9521        let stmt1 = parse_one("COMMIT");
9522        assert!(matches!(stmt1, Statement::Commit));
9523        let stmt2 = parse_one("END TRANSACTION");
9524        assert!(matches!(stmt2, Statement::Commit));
9525        let stmt3 = parse_one("COMMIT TRANSACTION");
9526        assert!(matches!(stmt3, Statement::Commit));
9527    }
9528
9529    #[test]
9530    fn test_rollback() {
9531        let stmt = parse_one("ROLLBACK");
9532        if let Statement::Rollback(r) = stmt {
9533            assert!(r.to_savepoint.is_none());
9534        } else {
9535            unreachable!("expected Rollback");
9536        }
9537    }
9538
9539    #[test]
9540    fn test_savepoint_basic() {
9541        let stmt = parse_one("SAVEPOINT sp1");
9542        assert!(matches!(stmt, Statement::Savepoint(ref name) if name == "sp1"));
9543    }
9544
9545    #[test]
9546    fn test_savepoint_release() {
9547        let stmt = parse_one("RELEASE SAVEPOINT sp1");
9548        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp1"));
9549    }
9550
9551    #[test]
9552    fn test_savepoint_release_removes_later() {
9553        // Parser-level: RELEASE without SAVEPOINT keyword also works.
9554        // Runtime savepoint stack semantics verified in engine tests.
9555        let stmt = parse_one("RELEASE sp2");
9556        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp2"));
9557    }
9558
9559    #[test]
9560    fn test_savepoint_rollback_to() {
9561        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
9562        if let Statement::Rollback(r) = stmt {
9563            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
9564        } else {
9565            unreachable!("expected Rollback");
9566        }
9567    }
9568
9569    #[test]
9570    fn test_savepoint_nested() {
9571        // Parser-level: multiple savepoints in sequence parse independently.
9572        // Runtime stack semantics verified in engine tests.
9573        let stmts = parse_ok("SAVEPOINT sp1; SAVEPOINT sp2; SAVEPOINT sp3");
9574        assert_eq!(stmts.len(), 3);
9575        assert!(matches!(stmts[0], Statement::Savepoint(ref n) if n == "sp1"));
9576        assert!(matches!(stmts[1], Statement::Savepoint(ref n) if n == "sp2"));
9577        assert!(matches!(stmts[2], Statement::Savepoint(ref n) if n == "sp3"));
9578    }
9579
9580    #[test]
9581    fn test_savepoint_rollback_then_continue() {
9582        // Parser-level: ROLLBACK TO followed by more DML parses.
9583        let stmts = parse_ok("ROLLBACK TO sp1; INSERT INTO t VALUES (1)");
9584        assert_eq!(stmts.len(), 2);
9585        assert!(matches!(stmts[0], Statement::Rollback(_)));
9586        assert!(matches!(stmts[1], Statement::Insert(_)));
9587    }
9588
9589    #[test]
9590    fn test_attach_database() {
9591        let stmt = parse_one("ATTACH DATABASE 'other.db' AS other");
9592        if let Statement::Attach(a) = stmt {
9593            assert_eq!(a.schema, "other");
9594        } else {
9595            unreachable!("expected Attach");
9596        }
9597    }
9598
9599    #[test]
9600    fn test_attach_schema_qualified_access() {
9601        // Parser-level: schema-qualified table reference parses correctly.
9602        let stmt = parse_one("SELECT * FROM other.t");
9603        if let Statement::Select(s) = stmt {
9604            if let SelectCore::Select { from, .. } = &s.body.select {
9605                let from = from.as_ref().expect("FROM clause");
9606                match &from.source {
9607                    TableOrSubquery::Table { name, .. } => {
9608                        assert_eq!(name.schema.as_deref(), Some("other"));
9609                        assert_eq!(name.name, "t");
9610                    }
9611                    other => unreachable!("expected Table source, got {other:?}"),
9612                }
9613            } else {
9614                unreachable!("expected Select core");
9615            }
9616        } else {
9617            unreachable!("expected Select");
9618        }
9619    }
9620
9621    #[test]
9622    fn test_detach_database() {
9623        let stmt = parse_one("DETACH DATABASE other");
9624        assert!(matches!(stmt, Statement::Detach(ref name) if name == "other"));
9625    }
9626
9627    #[test]
9628    fn test_attach_max_limit() {
9629        // Parser-level: ATTACH parses identically regardless of limit.
9630        // Runtime SQLITE_MAX_ATTACHED enforcement is in the engine.
9631        let stmt = parse_one("ATTACH 'db11.sqlite' AS db11");
9632        if let Statement::Attach(a) = stmt {
9633            assert_eq!(a.schema, "db11");
9634        } else {
9635            unreachable!("expected Attach");
9636        }
9637    }
9638
9639    #[test]
9640    fn test_cross_database_transaction() {
9641        // Parser-level: transaction with cross-database DML parses.
9642        // Runtime cross-database atomic commit is in WAL/MVCC layer.
9643        let stmts = parse_ok("BEGIN; INSERT INTO main.t SELECT * FROM other.t; COMMIT");
9644        assert_eq!(stmts.len(), 3);
9645        assert!(matches!(stmts[0], Statement::Begin(_)));
9646        assert!(matches!(stmts[1], Statement::Insert(_)));
9647        assert!(matches!(stmts[2], Statement::Commit));
9648    }
9649
9650    #[test]
9651    fn test_explain_returns_bytecode() {
9652        let stmt = parse_one("EXPLAIN SELECT 1");
9653        if let Statement::Explain { query_plan, stmt } = stmt {
9654            assert!(!query_plan);
9655            assert!(matches!(*stmt, Statement::Select(_)));
9656        } else {
9657            unreachable!("expected Explain");
9658        }
9659    }
9660
9661    #[test]
9662    fn test_explain_query_plan_columns() {
9663        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
9664        if let Statement::Explain { query_plan, stmt } = stmt {
9665            assert!(query_plan);
9666            assert!(matches!(*stmt, Statement::Select(_)));
9667        } else {
9668            unreachable!("expected Explain");
9669        }
9670    }
9671
9672    #[test]
9673    fn test_explain_query_plan_shows_index() {
9674        // Parser-level: EXPLAIN QUERY PLAN on indexed query parses.
9675        // Runtime index usage in EQP output is in the planner.
9676        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
9677        if let Statement::Explain { query_plan, .. } = stmt {
9678            assert!(query_plan);
9679        } else {
9680            unreachable!("expected Explain");
9681        }
9682    }
9683
9684    #[test]
9685    fn test_explain_query_plan_tree_structure() {
9686        // Parser-level: EXPLAIN QUERY PLAN on a join query parses.
9687        // Runtime tree structure in EQP output is in the planner.
9688        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id");
9689        if let Statement::Explain { query_plan, stmt } = stmt {
9690            assert!(query_plan);
9691            assert!(matches!(*stmt, Statement::Select(_)));
9692        } else {
9693            unreachable!("expected Explain");
9694        }
9695    }
9696
9697    // -----------------------------------------------------------------------
9698    // bd-2kvo Phase 3 acceptance: keywords as identifiers
9699    // -----------------------------------------------------------------------
9700
9701    #[test]
9702    fn test_parser_keyword_as_column_name() {
9703        // "order" is a keyword but valid as a column name in many contexts.
9704        let stmt = parse_one("SELECT \"order\" FROM t");
9705        assert!(matches!(stmt, Statement::Select(_)));
9706    }
9707
9708    #[test]
9709    fn test_parser_keyword_as_alias() {
9710        let stmt = parse_one("SELECT 1 AS \"limit\"");
9711        assert!(matches!(stmt, Statement::Select(_)));
9712    }
9713
9714    #[test]
9715    fn test_parser_keyword_as_table_name() {
9716        let stmt = parse_one("SELECT * FROM \"group\"");
9717        assert!(matches!(stmt, Statement::Select(_)));
9718    }
9719
9720    // -----------------------------------------------------------------------
9721    // bd-2kvo Phase 3 acceptance: all statement types (Section 12 coverage)
9722    // -----------------------------------------------------------------------
9723
9724    #[test]
9725    fn test_parser_all_statement_types() {
9726        // Each statement type from Section 12 must parse without error.
9727        let statements = [
9728            // DML
9729            "SELECT 1",
9730            "INSERT INTO t VALUES (1)",
9731            "INSERT OR REPLACE INTO t VALUES (1)",
9732            "UPDATE t SET a = 1",
9733            "DELETE FROM t WHERE id = 1",
9734            "REPLACE INTO t VALUES (1)",
9735            // DDL
9736            "CREATE TABLE t (id INTEGER PRIMARY KEY)",
9737            "CREATE TEMPORARY TABLE t (id INTEGER)",
9738            "CREATE TABLE IF NOT EXISTS t (id INTEGER)",
9739            "CREATE INDEX idx ON t (a)",
9740            "CREATE UNIQUE INDEX idx ON t (a)",
9741            "CREATE VIEW v AS SELECT 1",
9742            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END",
9743            "CREATE VIRTUAL TABLE t USING fts5(a, b)",
9744            "ALTER TABLE t RENAME TO t2",
9745            "ALTER TABLE t ADD COLUMN c TEXT",
9746            "ALTER TABLE t DROP COLUMN c",
9747            "ALTER TABLE t RENAME COLUMN a TO b",
9748            "DROP TABLE t",
9749            "DROP TABLE IF EXISTS t",
9750            "DROP INDEX idx",
9751            "DROP VIEW v",
9752            "DROP TRIGGER tr",
9753            // Transaction
9754            "BEGIN",
9755            "BEGIN DEFERRED",
9756            "BEGIN IMMEDIATE",
9757            "BEGIN EXCLUSIVE",
9758            "COMMIT",
9759            "END",
9760            "ROLLBACK",
9761            "SAVEPOINT sp1",
9762            "RELEASE sp1",
9763            "RELEASE SAVEPOINT sp1",
9764            "ROLLBACK TO sp1",
9765            "ROLLBACK TO SAVEPOINT sp1",
9766            // Utility
9767            "ATTACH DATABASE ':memory:' AS db2",
9768            "DETACH db2",
9769            "ANALYZE",
9770            "ANALYZE t",
9771            "VACUUM",
9772            "VACUUM INTO '/tmp/backup.db'",
9773            "REINDEX",
9774            "REINDEX t",
9775            "EXPLAIN SELECT 1",
9776            "EXPLAIN QUERY PLAN SELECT 1",
9777            // PRAGMA
9778            "PRAGMA journal_mode",
9779            "PRAGMA journal_mode = WAL",
9780            "PRAGMA table_info(t)",
9781        ];
9782
9783        for sql in &statements {
9784            let mut p = Parser::from_sql(sql);
9785            let (stmts, errs) = p.parse_all();
9786            assert!(errs.is_empty(), "failed to parse '{sql}': {errs:?}");
9787            assert_eq!(
9788                stmts.len(),
9789                1,
9790                "expected 1 statement for '{sql}', got {}",
9791                stmts.len()
9792            );
9793        }
9794    }
9795
9796    // -----------------------------------------------------------------------
9797    // bd-2kvo Phase 3 acceptance: expression precedence
9798    // -----------------------------------------------------------------------
9799
9800    #[test]
9801    fn test_parser_expression_precedence_mul_over_add() {
9802        // 1 + 2 * 3 should parse as 1 + (2 * 3)
9803        let stmt = parse_one("SELECT 1 + 2 * 3");
9804        if let Statement::Select(s) = stmt {
9805            if let SelectCore::Select { columns, .. } = &s.body.select {
9806                match &columns[0] {
9807                    ResultColumn::Expr { expr, .. } => {
9808                        // Outer expression should be Add, right side should be Multiply.
9809                        assert!(
9810                            matches!(expr, Expr::BinaryOp { .. }),
9811                            "expected BinaryOp, got {expr:?}"
9812                        );
9813                    }
9814                    other => unreachable!("expected Expr column, got {other:?}"),
9815                }
9816            } else {
9817                unreachable!("expected Select core");
9818            }
9819        } else {
9820            unreachable!("expected Select");
9821        }
9822    }
9823
9824    // -----------------------------------------------------------------------
9825    // bd-2kvo Phase 3 acceptance: INSERT with ON CONFLICT and RETURNING
9826    // -----------------------------------------------------------------------
9827
9828    #[test]
9829    fn test_parser_insert_on_conflict() {
9830        let stmt =
9831            parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a");
9832        if let Statement::Insert(i) = stmt {
9833            assert!(!i.upsert.is_empty());
9834        } else {
9835            unreachable!("expected Insert");
9836        }
9837    }
9838
9839    #[test]
9840    fn test_parser_insert_returning() {
9841        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING *");
9842        if let Statement::Insert(i) = stmt {
9843            assert!(!i.returning.is_empty());
9844        } else {
9845            unreachable!("expected Insert");
9846        }
9847    }
9848
9849    #[test]
9850    fn test_parser_delete_returning() {
9851        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
9852        if let Statement::Delete(d) = stmt {
9853            assert!(!d.returning.is_empty());
9854        } else {
9855            unreachable!("expected Delete");
9856        }
9857    }
9858
9859    #[test]
9860    fn test_parser_update_returning() {
9861        let stmt = parse_one("UPDATE t SET a = 1 RETURNING a, b");
9862        if let Statement::Update(u) = stmt {
9863            assert_eq!(u.returning.len(), 2);
9864        } else {
9865            unreachable!("expected Update");
9866        }
9867    }
9868
9869    // -----------------------------------------------------------------------
9870    // bd-2kvo Phase 3 acceptance: compound SELECT operators
9871    // -----------------------------------------------------------------------
9872
9873    #[test]
9874    fn test_parser_union() {
9875        let stmt = parse_one("SELECT 1 UNION SELECT 2");
9876        if let Statement::Select(s) = stmt {
9877            assert_eq!(s.body.compounds.len(), 1);
9878            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
9879        } else {
9880            unreachable!("expected Select");
9881        }
9882    }
9883
9884    #[test]
9885    fn test_parser_intersect() {
9886        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
9887        if let Statement::Select(s) = stmt {
9888            assert_eq!(s.body.compounds.len(), 1);
9889            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
9890        } else {
9891            unreachable!("expected Select");
9892        }
9893    }
9894
9895    #[test]
9896    fn test_parser_except() {
9897        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
9898        if let Statement::Select(s) = stmt {
9899            assert_eq!(s.body.compounds.len(), 1);
9900            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
9901        } else {
9902            unreachable!("expected Select");
9903        }
9904    }
9905
9906    // -----------------------------------------------------------------------
9907    // bd-2kvo Phase 3 acceptance: subquery in FROM
9908    // -----------------------------------------------------------------------
9909
9910    #[test]
9911    fn test_parser_subquery_in_from() {
9912        let stmt = parse_one("SELECT * FROM (SELECT 1 AS x) AS sub");
9913        assert!(matches!(stmt, Statement::Select(_)));
9914    }
9915
9916    // -----------------------------------------------------------------------
9917    // bd-2kvo Phase 3 acceptance: CREATE TABLE with constraints
9918    // -----------------------------------------------------------------------
9919
9920    #[test]
9921    fn test_parser_create_table_all_constraints() {
9922        let stmt = parse_one(
9923            "CREATE TABLE t (\
9924             id INTEGER PRIMARY KEY AUTOINCREMENT,\
9925             name TEXT NOT NULL DEFAULT '',\
9926             email TEXT UNIQUE,\
9927             age INTEGER CHECK(age >= 0),\
9928             dept_id INTEGER REFERENCES dept(id) ON DELETE CASCADE,\
9929             CONSTRAINT pk PRIMARY KEY (id),\
9930             UNIQUE (email),\
9931             CHECK (age < 200),\
9932             FOREIGN KEY (dept_id) REFERENCES dept(id)\
9933             )",
9934        );
9935        if let Statement::CreateTable(ct) = stmt {
9936            if let CreateTableBody::Columns {
9937                columns,
9938                constraints,
9939            } = ct.body
9940            {
9941                assert_eq!(columns.len(), 5);
9942                assert!(!constraints.is_empty());
9943            } else {
9944                unreachable!("expected column defs");
9945            }
9946        } else {
9947            unreachable!("expected CreateTable");
9948        }
9949    }
9950
9951    // -----------------------------------------------------------------------
9952    // bd-2kvo Phase 3 acceptance: CREATE TRIGGER with all timing/events
9953    // -----------------------------------------------------------------------
9954
9955    #[test]
9956    fn test_parser_create_trigger_before_delete() {
9957        let stmt = parse_one("CREATE TRIGGER tr BEFORE DELETE ON t BEGIN SELECT 1; END");
9958        if let Statement::CreateTrigger(tr) = stmt {
9959            assert_eq!(tr.timing, TriggerTiming::Before);
9960            assert!(matches!(tr.event, TriggerEvent::Delete));
9961        } else {
9962            unreachable!("expected CreateTrigger");
9963        }
9964    }
9965
9966    #[test]
9967    fn test_parser_create_trigger_instead_of_update() {
9968        let stmt =
9969            parse_one("CREATE TRIGGER tr INSTEAD OF UPDATE OF a, b ON v BEGIN SELECT 1; END");
9970        if let Statement::CreateTrigger(tr) = stmt {
9971            assert_eq!(tr.timing, TriggerTiming::InsteadOf);
9972            if let TriggerEvent::Update(cols) = &tr.event {
9973                assert_eq!(cols.len(), 2);
9974            } else {
9975                unreachable!("expected UpdateOf event");
9976            }
9977        } else {
9978            unreachable!("expected CreateTrigger");
9979        }
9980    }
9981
9982    // -----------------------------------------------------------------------
9983    // bd-2kvo Phase 3 acceptance: CREATE VIEW with columns
9984    // -----------------------------------------------------------------------
9985
9986    #[test]
9987    fn test_parser_create_view_with_columns() {
9988        let stmt = parse_one("CREATE VIEW v (a, b) AS SELECT 1, 2");
9989        if let Statement::CreateView(cv) = stmt {
9990            assert_eq!(cv.columns, vec!["a".to_owned(), "b".to_owned()]);
9991        } else {
9992            unreachable!("expected CreateView");
9993        }
9994    }
9995
9996    // -----------------------------------------------------------------------
9997    // bd-2kvo Phase 3 acceptance: multi-way join
9998    // -----------------------------------------------------------------------
9999
10000    #[test]
10001    fn test_parser_multi_join() {
10002        let stmt = parse_one(
10003            "SELECT a.x, b.y, c.z FROM a \
10004             JOIN b ON a.id = b.a_id \
10005             LEFT JOIN c ON b.id = c.b_id \
10006             CROSS JOIN d",
10007        );
10008        if let Statement::Select(s) = stmt {
10009            if let SelectCore::Select { from, .. } = &s.body.select {
10010                let from = from.as_ref().expect("FROM clause");
10011                assert_eq!(from.joins.len(), 3);
10012                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
10013                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
10014                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
10015            } else {
10016                unreachable!("expected Select core");
10017            }
10018        } else {
10019            unreachable!("expected Select");
10020        }
10021    }
10022
10023    // -----------------------------------------------------------------------
10024    // bd-2kvo Phase 3 acceptance: GROUP BY / HAVING
10025    // -----------------------------------------------------------------------
10026
10027    #[test]
10028    fn test_parser_group_by_having() {
10029        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
10030        if let Statement::Select(s) = stmt {
10031            if let SelectCore::Select {
10032                group_by, having, ..
10033            } = &s.body.select
10034            {
10035                assert!(!group_by.is_empty());
10036                assert!(having.is_some());
10037            } else {
10038                unreachable!("expected Select core");
10039            }
10040        } else {
10041            unreachable!("expected Select");
10042        }
10043    }
10044
10045    // -----------------------------------------------------------------------
10046    // bd-2kvo Phase 3 acceptance: Error recovery with line:column spans
10047    // -----------------------------------------------------------------------
10048
10049    #[test]
10050    fn test_parser_error_recovery_with_span() {
10051        // Multi-line input with an error on line 2.
10052        let sql = "SELECT 1;\nXYZZY 42;\nSELECT 3";
10053        let mut p = Parser::from_sql(sql);
10054        let (stmts, errs) = p.parse_all();
10055        assert_eq!(stmts.len(), 2, "should recover two valid statements");
10056        assert!(!errs.is_empty(), "should report at least one error");
10057
10058        let err = &errs[0];
10059        // XYZZY starts at line 2, column 1.
10060        assert_eq!(err.line, 2, "error should be on line 2");
10061        assert_eq!(err.col, 1, "error should be at column 1");
10062        // Span should be non-zero and point within the source.
10063        assert!(
10064            err.span.start < err.span.end,
10065            "error span should be non-empty"
10066        );
10067        let source_len = u32::try_from(sql.len()).unwrap();
10068        assert!(
10069            err.span.end <= source_len,
10070            "error span.end should be within source"
10071        );
10072    }
10073
10074    #[test]
10075    fn test_parser_error_span_mid_line() {
10076        // Incomplete CREATE should produce an error.
10077        let bad = Parser::from_sql("CREATE").parse_statement();
10078        assert!(bad.is_err());
10079        let err = bad.unwrap_err();
10080        assert_eq!(err.line, 1);
10081    }
10082
10083    // -----------------------------------------------------------------------
10084    // bd-2kvo Phase 3 acceptance: Keyword lookup covers 150+ keywords
10085    // -----------------------------------------------------------------------
10086
10087    #[test]
10088    #[allow(clippy::too_many_lines)]
10089    fn test_parser_keyword_lookup_all_150() {
10090        use crate::token::TokenKind;
10091
10092        // Exhaustive list of all SQL keywords in lookup_keyword.
10093        let keywords = [
10094            "ABORT",
10095            "ACTION",
10096            "ADD",
10097            "AFTER",
10098            "ALL",
10099            "ALTER",
10100            "ALWAYS",
10101            "ANALYZE",
10102            "AND",
10103            "AS",
10104            "ASC",
10105            "ATTACH",
10106            "AUTOINCREMENT",
10107            "BEFORE",
10108            "BEGIN",
10109            "BETWEEN",
10110            "BY",
10111            "CASCADE",
10112            "CASE",
10113            "CAST",
10114            "CHECK",
10115            "COLLATE",
10116            "COLUMN",
10117            "COMMIT",
10118            "CONCURRENT",
10119            "CONFLICT",
10120            "CONSTRAINT",
10121            "CREATE",
10122            "CROSS",
10123            "CURRENT_DATE",
10124            "CURRENT_TIME",
10125            "CURRENT_TIMESTAMP",
10126            "DATABASE",
10127            "DEFAULT",
10128            "DEFERRABLE",
10129            "DEFERRED",
10130            "DELETE",
10131            "DESC",
10132            "DETACH",
10133            "DISTINCT",
10134            "DO",
10135            "DROP",
10136            "EACH",
10137            "ELSE",
10138            "END",
10139            "ESCAPE",
10140            "EXCEPT",
10141            "EXCLUDE",
10142            "EXCLUSIVE",
10143            "EXISTS",
10144            "EXPLAIN",
10145            "FAIL",
10146            "FILTER",
10147            "FIRST",
10148            "FOLLOWING",
10149            "FOR",
10150            "FOREIGN",
10151            "FROM",
10152            "FULL",
10153            "GENERATED",
10154            "GLOB",
10155            "GROUP",
10156            "GROUPS",
10157            "HAVING",
10158            "IF",
10159            "IGNORE",
10160            "IMMEDIATE",
10161            "IN",
10162            "INDEX",
10163            "INDEXED",
10164            "INITIALLY",
10165            "INNER",
10166            "INSERT",
10167            "INSTEAD",
10168            "INTERSECT",
10169            "INTO",
10170            "IS",
10171            "ISNULL",
10172            "JOIN",
10173            "KEY",
10174            "LAST",
10175            "LEFT",
10176            "LIKE",
10177            "LIMIT",
10178            "MATCH",
10179            "MATERIALIZED",
10180            "NATURAL",
10181            "NO",
10182            "NOT",
10183            "NOTHING",
10184            "NOTNULL",
10185            "NULL",
10186            "NULLS",
10187            "OF",
10188            "OFFSET",
10189            "ON",
10190            "OR",
10191            "ORDER",
10192            "OTHERS",
10193            "OUTER",
10194            "OVER",
10195            "PARTITION",
10196            "PLAN",
10197            "PRAGMA",
10198            "PRECEDING",
10199            "PRIMARY",
10200            "QUERY",
10201            "RAISE",
10202            "RANGE",
10203            "RECURSIVE",
10204            "REFERENCES",
10205            "REGEXP",
10206            "REINDEX",
10207            "RELEASE",
10208            "RENAME",
10209            "REPLACE",
10210            "RESTRICT",
10211            "RETURNING",
10212            "RIGHT",
10213            "ROLLBACK",
10214            "ROW",
10215            "ROWS",
10216            "SAVEPOINT",
10217            "SELECT",
10218            "SET",
10219            "STORED",
10220            "STRICT",
10221            "TABLE",
10222            "TEMP",
10223            "TEMPORARY",
10224            "THEN",
10225            "TIES",
10226            "TO",
10227            "TRANSACTION",
10228            "TRIGGER",
10229            "TRUE",
10230            "FALSE",
10231            "UNBOUNDED",
10232            "UNION",
10233            "UNIQUE",
10234            "UPDATE",
10235            "USING",
10236            "VACUUM",
10237            "VALUES",
10238            "VIEW",
10239            "VIRTUAL",
10240            "WHEN",
10241            "WHERE",
10242            "WINDOW",
10243            "WITH",
10244            "WITHOUT",
10245        ];
10246
10247        assert!(
10248            keywords.len() >= 150,
10249            "expected 150+ keywords, got {}",
10250            keywords.len()
10251        );
10252
10253        for kw in &keywords {
10254            assert!(
10255                TokenKind::lookup_keyword(kw).is_some(),
10256                "keyword {kw} not recognized (uppercase)"
10257            );
10258            // Case-insensitive: lowercase must also work.
10259            let lower = kw.to_ascii_lowercase();
10260            assert!(
10261                TokenKind::lookup_keyword(&lower).is_some(),
10262                "keyword {kw} not recognized (lowercase)"
10263            );
10264            // Mixed case.
10265            let mixed: String = kw
10266                .chars()
10267                .enumerate()
10268                .map(|(i, c)| {
10269                    if i % 2 == 0 {
10270                        c.to_ascii_lowercase()
10271                    } else {
10272                        c.to_ascii_uppercase()
10273                    }
10274                })
10275                .collect();
10276            assert!(
10277                TokenKind::lookup_keyword(&mixed).is_some(),
10278                "keyword {kw} not recognized (mixed case: {mixed})"
10279            );
10280        }
10281
10282        // Non-keyword should return None.
10283        assert!(TokenKind::lookup_keyword("FOOBAR").is_none());
10284        assert!(TokenKind::lookup_keyword("").is_none());
10285    }
10286
10287    // -----------------------------------------------------------------------
10288    // Round-trip: parse → Display → re-parse → compare ASTs
10289    // -----------------------------------------------------------------------
10290
10291    /// Parse SQL, convert back to string via Display, re-parse, convert back
10292    /// again, and assert the two rendered strings are identical.  We compare
10293    /// rendered strings (not ASTs) because Display may normalise constructs
10294    /// (e.g. `INSERT OR REPLACE` → `REPLACE`) which changes SQL length and
10295    /// therefore Span positions, while the logical content is identical.
10296    fn assert_roundtrip(sql: &str) {
10297        let ast1 = parse_one(sql);
10298        let rendered1 = ast1.to_string();
10299        let ast2 = parse_one(&rendered1);
10300        let rendered2 = ast2.to_string();
10301        assert_eq!(
10302            rendered1, rendered2,
10303            "round-trip failed for:\n  input: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
10304        );
10305    }
10306
10307    #[test]
10308    fn test_roundtrip_select_simple() {
10309        assert_roundtrip("SELECT 1");
10310        assert_roundtrip("SELECT 1, 2, 3");
10311        assert_roundtrip("SELECT *");
10312        assert_roundtrip("SELECT * FROM t");
10313        assert_roundtrip("SELECT a, b FROM t WHERE a > 10");
10314        assert_roundtrip("SELECT a FROM t ORDER BY a DESC");
10315        assert_roundtrip("SELECT a FROM t LIMIT 10 OFFSET 5");
10316    }
10317
10318    #[test]
10319    fn test_roundtrip_select_distinct() {
10320        assert_roundtrip("SELECT DISTINCT a, b FROM t");
10321    }
10322
10323    #[test]
10324    fn test_roundtrip_select_alias() {
10325        assert_roundtrip("SELECT a AS x, b AS y FROM t AS u");
10326    }
10327
10328    #[test]
10329    fn test_roundtrip_select_join_types() {
10330        assert_roundtrip("SELECT * FROM a INNER JOIN b ON a.id = b.id");
10331        assert_roundtrip("SELECT * FROM a LEFT JOIN b ON a.id = b.id");
10332        assert_roundtrip("SELECT * FROM a RIGHT JOIN b ON a.id = b.id");
10333        assert_roundtrip("SELECT * FROM a FULL JOIN b ON a.id = b.id");
10334        assert_roundtrip("SELECT * FROM a CROSS JOIN b");
10335        assert_roundtrip("SELECT * FROM a NATURAL INNER JOIN b");
10336        assert_roundtrip("SELECT * FROM a LEFT JOIN b USING (id)");
10337    }
10338
10339    #[test]
10340    fn test_roundtrip_select_subquery() {
10341        assert_roundtrip("SELECT * FROM (SELECT 1 AS x) AS sub");
10342    }
10343
10344    #[test]
10345    fn test_roundtrip_select_group_by_having() {
10346        assert_roundtrip("SELECT a, count(*) FROM t GROUP BY a HAVING count(*) > 1");
10347    }
10348
10349    #[test]
10350    fn test_roundtrip_select_window() {
10351        assert_roundtrip("SELECT sum(x) OVER (PARTITION BY g ORDER BY x) FROM t");
10352    }
10353
10354    #[test]
10355    fn test_roundtrip_select_cte() {
10356        assert_roundtrip("WITH cte AS (SELECT 1 AS n) SELECT * FROM cte");
10357        assert_roundtrip(
10358            "WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x + 1 FROM cnt WHERE x < 10) SELECT * FROM cnt",
10359        );
10360    }
10361
10362    #[test]
10363    fn test_roundtrip_select_compound() {
10364        assert_roundtrip("SELECT 1 UNION SELECT 2");
10365        assert_roundtrip("SELECT 1 UNION ALL SELECT 2");
10366        assert_roundtrip("SELECT 1 INTERSECT SELECT 2");
10367        assert_roundtrip("SELECT 1 EXCEPT SELECT 2");
10368    }
10369
10370    #[test]
10371    fn test_roundtrip_insert() {
10372        assert_roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
10373        assert_roundtrip("INSERT INTO t DEFAULT VALUES");
10374        assert_roundtrip("INSERT INTO t SELECT * FROM u");
10375        assert_roundtrip("INSERT OR REPLACE INTO t (a) VALUES (1)");
10376        assert_roundtrip("REPLACE INTO t (a) VALUES (1)");
10377    }
10378
10379    #[test]
10380    fn test_roundtrip_insert_returning() {
10381        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING *");
10382        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING a, b");
10383    }
10384
10385    #[test]
10386    fn test_roundtrip_insert_on_conflict() {
10387        assert_roundtrip("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
10388        assert_roundtrip(
10389            "INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a",
10390        );
10391    }
10392
10393    #[test]
10394    fn test_roundtrip_update() {
10395        assert_roundtrip("UPDATE t SET a = 1");
10396        assert_roundtrip("UPDATE t SET a = 1, b = 2 WHERE c > 3");
10397        assert_roundtrip("UPDATE t SET a = 1 RETURNING *");
10398    }
10399
10400    #[test]
10401    fn test_roundtrip_delete() {
10402        assert_roundtrip("DELETE FROM t");
10403        assert_roundtrip("DELETE FROM t WHERE a = 1");
10404        assert_roundtrip("DELETE FROM t RETURNING *");
10405    }
10406
10407    #[test]
10408    fn test_roundtrip_create_table() {
10409        assert_roundtrip("CREATE TABLE t (a INTEGER, b TEXT)");
10410        assert_roundtrip("CREATE TABLE IF NOT EXISTS t (a INTEGER PRIMARY KEY)");
10411        assert_roundtrip("CREATE TEMP TABLE t (a TEXT NOT NULL, b REAL DEFAULT 0.0)");
10412    }
10413
10414    #[test]
10415    fn test_roundtrip_create_index() {
10416        assert_roundtrip("CREATE INDEX idx ON t (a)");
10417        assert_roundtrip("CREATE UNIQUE INDEX IF NOT EXISTS idx ON t (a, b DESC)");
10418        assert_roundtrip("CREATE INDEX idx ON t (a) WHERE a > 0");
10419    }
10420
10421    #[test]
10422    fn test_roundtrip_drop() {
10423        assert_roundtrip("DROP TABLE t");
10424        assert_roundtrip("DROP TABLE IF EXISTS t");
10425        assert_roundtrip("DROP INDEX idx");
10426        assert_roundtrip("DROP VIEW v");
10427    }
10428
10429    #[test]
10430    fn test_roundtrip_alter_table() {
10431        assert_roundtrip("ALTER TABLE t RENAME TO u");
10432        assert_roundtrip("ALTER TABLE t ADD COLUMN c TEXT");
10433        assert_roundtrip("ALTER TABLE t DROP COLUMN c");
10434    }
10435
10436    #[test]
10437    fn test_roundtrip_transaction() {
10438        assert_roundtrip("BEGIN");
10439        assert_roundtrip("BEGIN IMMEDIATE");
10440        assert_roundtrip("BEGIN EXCLUSIVE");
10441        assert_roundtrip("COMMIT");
10442        assert_roundtrip("ROLLBACK");
10443        assert_roundtrip("SAVEPOINT sp1");
10444        assert_roundtrip("RELEASE sp1");
10445    }
10446
10447    #[test]
10448    fn test_roundtrip_pragma() {
10449        assert_roundtrip("PRAGMA journal_mode");
10450        assert_roundtrip("PRAGMA journal_mode = wal");
10451    }
10452
10453    #[test]
10454    fn test_roundtrip_explain() {
10455        assert_roundtrip("EXPLAIN SELECT 1");
10456        assert_roundtrip("EXPLAIN QUERY PLAN SELECT * FROM t");
10457    }
10458
10459    #[test]
10460    fn test_roundtrip_expressions() {
10461        assert_roundtrip("SELECT 1 + 2 * 3");
10462        assert_roundtrip("SELECT NOT a");
10463        assert_roundtrip("SELECT -x");
10464        assert_roundtrip("SELECT ~x");
10465        assert_roundtrip("SELECT a BETWEEN 1 AND 10");
10466        assert_roundtrip("SELECT a NOT BETWEEN 1 AND 10");
10467        assert_roundtrip("SELECT a IN (1, 2, 3)");
10468        assert_roundtrip("SELECT a NOT IN (1, 2, 3)");
10469        assert_roundtrip("SELECT a LIKE '%foo%'");
10470        assert_roundtrip("SELECT a GLOB '*foo*'");
10471        assert_roundtrip("SELECT CASE WHEN a = 1 THEN 'one' ELSE 'other' END");
10472        assert_roundtrip("SELECT CASE x WHEN 1 THEN 'a' WHEN 2 THEN 'b' END");
10473        assert_roundtrip("SELECT CAST(a AS TEXT)");
10474        assert_roundtrip("SELECT EXISTS (SELECT 1)");
10475        assert_roundtrip("SELECT (SELECT 1)");
10476        assert_roundtrip("SELECT a COLLATE NOCASE");
10477    }
10478
10479    #[test]
10480    fn test_roundtrip_literals() {
10481        assert_roundtrip("SELECT NULL");
10482        assert_roundtrip("SELECT TRUE");
10483        assert_roundtrip("SELECT FALSE");
10484        assert_roundtrip("SELECT 42");
10485        assert_roundtrip("SELECT 3.14");
10486        assert_roundtrip("SELECT 'hello'");
10487        assert_roundtrip("SELECT X'DEADBEEF'");
10488        assert_roundtrip("SELECT CURRENT_TIME");
10489        assert_roundtrip("SELECT CURRENT_DATE");
10490        assert_roundtrip("SELECT CURRENT_TIMESTAMP");
10491    }
10492
10493    #[test]
10494    fn test_roundtrip_placeholders() {
10495        assert_roundtrip("SELECT ?");
10496        assert_roundtrip("SELECT ?1");
10497        assert_roundtrip("SELECT :name");
10498        assert_roundtrip("SELECT @name");
10499        assert_roundtrip("SELECT $name");
10500    }
10501
10502    #[test]
10503    fn test_roundtrip_json_arrows() {
10504        assert_roundtrip("SELECT a -> 'key'");
10505        assert_roundtrip("SELECT a ->> 'key'");
10506    }
10507
10508    #[test]
10509    fn test_roundtrip_function_calls() {
10510        assert_roundtrip("SELECT count(*)");
10511        assert_roundtrip("SELECT count(DISTINCT a)");
10512        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0)");
10513    }
10514
10515    #[test]
10516    fn test_roundtrip_isnull_notnull() {
10517        assert_roundtrip("SELECT a ISNULL");
10518        assert_roundtrip("SELECT a IS NOT NULL");
10519    }
10520
10521    #[test]
10522    fn test_roundtrip_create_view() {
10523        assert_roundtrip("CREATE VIEW v AS SELECT * FROM t");
10524        assert_roundtrip("CREATE VIEW IF NOT EXISTS v (a, b) AS SELECT 1, 2");
10525    }
10526
10527    #[test]
10528    fn test_roundtrip_create_trigger() {
10529        assert_roundtrip(
10530            "CREATE TRIGGER tr BEFORE DELETE ON t FOR EACH ROW BEGIN DELETE FROM log WHERE id = OLD.id; END",
10531        );
10532    }
10533
10534    #[test]
10535    fn test_roundtrip_attach_detach() {
10536        assert_roundtrip("ATTACH 'file.db' AS db2");
10537        assert_roundtrip("DETACH db2");
10538    }
10539
10540    #[test]
10541    fn test_roundtrip_vacuum() {
10542        assert_roundtrip("VACUUM");
10543    }
10544
10545    #[test]
10546    fn test_roundtrip_analyze_reindex() {
10547        assert_roundtrip("ANALYZE");
10548        assert_roundtrip("ANALYZE t");
10549        assert_roundtrip("REINDEX");
10550        assert_roundtrip("REINDEX t");
10551    }
10552
10553    #[test]
10554    fn test_roundtrip_cte_materialized() {
10555        assert_roundtrip("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
10556        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
10557    }
10558
10559    // -----------------------------------------------------------------------
10560    // Proptest: round-trip property test (bd-2kvo acceptance criterion #12)
10561    // -----------------------------------------------------------------------
10562
10563    mod proptest_roundtrip {
10564        use super::*;
10565        use proptest::prelude::*;
10566
10567        /// Returns `true` if the string is a SQL keyword.
10568        fn is_keyword(s: &str) -> bool {
10569            TokenKind::lookup_keyword(s).is_some()
10570        }
10571
10572        /// Generate a random identifier (simple alphanumeric, not a SQL keyword).
10573        fn arb_ident() -> BoxedStrategy<String> {
10574            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
10575                .expect("valid regex")
10576                .prop_filter("must not be keyword", |s| !is_keyword(s))
10577                .boxed()
10578        }
10579
10580        /// Generate a random literal value.
10581        fn arb_literal() -> BoxedStrategy<String> {
10582            prop_oneof![
10583                any::<i32>().prop_map(|n| n.to_string()),
10584                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
10585                arb_ident().prop_map(|s| format!("'{s}'")),
10586                Just("NULL".to_string()),
10587                Just("TRUE".to_string()),
10588                Just("FALSE".to_string()),
10589            ]
10590            .boxed()
10591        }
10592
10593        /// Generate a random expression of bounded depth.
10594        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
10595            if depth == 0 {
10596                prop_oneof![
10597                    arb_literal(),
10598                    arb_ident(),
10599                    (arb_ident(), arb_ident()).prop_map(|(t, c)| format!("{t}.{c}")),
10600                ]
10601                .boxed()
10602            } else {
10603                let leaf = arb_expr(0);
10604                prop_oneof![
10605                    4 => leaf,
10606                    // Binary ops (always parenthesized by display)
10607                    2 => (arb_expr(depth - 1), prop_oneof![
10608                        Just("+"), Just("-"), Just("*"), Just("/"),
10609                        Just("="), Just("!="), Just("<"), Just("<="),
10610                        Just(">"), Just(">="), Just("AND"), Just("OR"),
10611                        Just("||"),
10612                    ], arb_expr(depth - 1))
10613                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
10614                    // Unary ops
10615                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
10616                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
10617                    // IS NULL / IS NOT NULL
10618                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NULL")),
10619                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NOT NULL")),
10620                    // Single-token postfix null tests
10621                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} ISNULL")),
10622                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} NOTNULL")),
10623                    // COLLATE (postfix)
10624                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} COLLATE nocase")),
10625                    // UNPARENTHESIZED binary ops: exercise precedence and
10626                    // associativity through the display round-trip (issue
10627                    // #122: `a IS NULL = b IS NULL` must not regroup after
10628                    // parse → display → re-parse).
10629                    2 => (arb_expr(depth - 1), prop_oneof![
10630                        Just("+"), Just("*"), Just("="), Just("<"),
10631                        Just("AND"), Just("OR"), Just("||"), Just("IS"),
10632                        Just("IS NOT"),
10633                    ], arb_expr(depth - 1))
10634                        .prop_map(|(l, op, r)| format!("{l} {op} {r}")),
10635                    // BETWEEN
10636                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
10637                        .prop_map(|(e, lo, hi)| format!("{e} BETWEEN {lo} AND {hi}")),
10638                    // IN list
10639                    1 => (arb_expr(depth - 1), proptest::collection::vec(arb_expr(0), 1..4))
10640                        .prop_map(|(e, items)| format!("{e} IN ({})", items.join(", "))),
10641                    // LIKE
10642                    1 => (arb_expr(depth - 1), arb_ident())
10643                        .prop_map(|(e, p)| format!("{e} LIKE '{p}'")),
10644                    // CAST
10645                    1 => arb_expr(depth - 1).prop_map(|e| format!("CAST({e} AS TEXT)")),
10646                    // CASE
10647                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
10648                        .prop_map(|(c, t, el)| format!("CASE WHEN {c} THEN {t} ELSE {el} END")),
10649                    // Function call
10650                    1 => (arb_ident(), proptest::collection::vec(arb_expr(0), 0..3))
10651                        .prop_map(|(name, args)| format!("{name}({})", args.join(", "))),
10652                    // Subquery
10653                    1 => arb_expr(0).prop_map(|e| format!("(SELECT {e})")),
10654                ]
10655                .boxed()
10656            }
10657        }
10658
10659        /// Generate a random SELECT statement.
10660        fn arb_select() -> BoxedStrategy<String> {
10661            use std::fmt::Write as _;
10662
10663            let cols =
10664                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
10665            let table = arb_ident();
10666            let where_clause = prop::option::of(arb_expr(1));
10667            let order_by = prop::option::of(arb_ident());
10668            let limit = prop::option::of(1u32..100);
10669
10670            (cols, table, where_clause, order_by, limit)
10671                .prop_map(|(cols, tbl, wh, ord, lim)| {
10672                    let mut sql = format!("SELECT {cols} FROM {tbl}");
10673                    if let Some(w) = wh {
10674                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
10675                    }
10676                    if let Some(o) = ord {
10677                        write!(sql, " ORDER BY {o}").expect("writing to String should not fail");
10678                    }
10679                    if let Some(l) = lim {
10680                        write!(sql, " LIMIT {l}").expect("writing to String should not fail");
10681                    }
10682                    sql
10683                })
10684                .boxed()
10685        }
10686
10687        /// Generate a random INSERT statement.
10688        fn arb_insert() -> BoxedStrategy<String> {
10689            let ncols = 1usize..4;
10690            ncols
10691                .prop_flat_map(|n| {
10692                    let tbl = arb_ident();
10693                    let cols = proptest::collection::vec(arb_ident(), n..=n);
10694                    let vals = proptest::collection::vec(arb_literal(), n..=n);
10695                    (tbl, cols, vals).prop_map(|(t, cs, vs): (String, Vec<String>, Vec<String>)| {
10696                        format!(
10697                            "INSERT INTO {t} ({}) VALUES ({})",
10698                            cs.join(", "),
10699                            vs.join(", ")
10700                        )
10701                    })
10702                })
10703                .boxed()
10704        }
10705
10706        /// Generate a random statement.
10707        fn arb_statement() -> BoxedStrategy<String> {
10708            prop_oneof![
10709                6 => arb_select(),
10710                3 => arb_insert(),
10711                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
10712                1 => (arb_ident(), arb_expr(1))
10713                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
10714                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
10715                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
10716            ]
10717            .boxed()
10718        }
10719
10720        /// Try to parse SQL into a single statement; returns `None` if unparseable.
10721        fn try_parse_one(sql: &str) -> Option<Statement> {
10722            let mut p = Parser::from_sql(sql);
10723            let (stmts, errs) = p.parse_all();
10724            if errs.is_empty() && stmts.len() == 1 {
10725                Some(stmts.into_iter().next().unwrap())
10726            } else {
10727                None
10728            }
10729        }
10730
10731        proptest::proptest! {
10732            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
10733
10734            #[test]
10735            fn test_parser_roundtrip_proptest(sql in arb_statement()) {
10736                // Phase 1: parse the generated SQL.
10737                let Some(ast1) = try_parse_one(&sql) else {
10738                    return Ok(()); // skip unparseable inputs
10739                };
10740
10741                // Phase 2: display the AST back to SQL text.
10742                let rendered1 = ast1.to_string();
10743
10744                // Phase 3: re-parse the rendered SQL.
10745                let Some(ast2) = try_parse_one(&rendered1) else {
10746                    let msg = format!("re-parse failed for rendered SQL: {rendered1:?}");
10747                    prop_assert!(false, "{}", msg);
10748                    unreachable!()
10749                };
10750
10751                // Phase 4: display again and compare (idempotency check).
10752                let rendered2 = ast2.to_string();
10753                let msg = format!(
10754                    "round-trip not idempotent:\n  original: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
10755                );
10756                prop_assert_eq!(rendered1, rendered2, "{}", msg);
10757            }
10758        }
10759    }
10760
10761    // -----------------------------------------------------------------------
10762    // Proptest: additional property tests (bd-1lsfu.4)
10763    // -----------------------------------------------------------------------
10764
10765    mod proptest_properties {
10766        use super::*;
10767        use proptest::prelude::*;
10768
10769        /// Reuse the statement generator from the roundtrip module.
10770        fn arb_ident() -> BoxedStrategy<String> {
10771            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
10772                .expect("valid regex")
10773                .prop_filter("must not be keyword", |s| {
10774                    TokenKind::lookup_keyword(s).is_none()
10775                })
10776                .boxed()
10777        }
10778
10779        fn arb_literal() -> BoxedStrategy<String> {
10780            prop_oneof![
10781                any::<i32>().prop_map(|n| n.to_string()),
10782                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
10783                arb_ident().prop_map(|s| format!("'{s}'")),
10784                Just("NULL".to_string()),
10785                Just("TRUE".to_string()),
10786                Just("FALSE".to_string()),
10787            ]
10788            .boxed()
10789        }
10790
10791        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
10792            if depth == 0 {
10793                prop_oneof![arb_literal(), arb_ident(),].boxed()
10794            } else {
10795                let leaf = arb_expr(0);
10796                prop_oneof![
10797                    4 => leaf,
10798                    2 => (arb_expr(depth - 1), prop_oneof![
10799                        Just("+"), Just("-"), Just("*"), Just("/"),
10800                        Just("="), Just("!="), Just("<"), Just("<="),
10801                        Just(">"), Just(">="), Just("AND"), Just("OR"),
10802                    ], arb_expr(depth - 1))
10803                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
10804                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
10805                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
10806                ]
10807                .boxed()
10808            }
10809        }
10810
10811        fn arb_select() -> BoxedStrategy<String> {
10812            use std::fmt::Write as _;
10813            let cols =
10814                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
10815            let table = arb_ident();
10816            let where_clause = prop::option::of(arb_expr(1));
10817            (cols, table, where_clause)
10818                .prop_map(|(cols, tbl, wh)| {
10819                    let mut sql = format!("SELECT {cols} FROM {tbl}");
10820                    if let Some(w) = wh {
10821                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
10822                    }
10823                    sql
10824                })
10825                .boxed()
10826        }
10827
10828        fn arb_statement() -> BoxedStrategy<String> {
10829            prop_oneof![
10830                6 => arb_select(),
10831                3 => {
10832                    let ncols = 1usize..4;
10833                    ncols
10834                        .prop_flat_map(|n| {
10835                            let tbl = arb_ident();
10836                            let cols = proptest::collection::vec(arb_ident(), n..=n);
10837                            let vals = proptest::collection::vec(arb_literal(), n..=n);
10838                            (tbl, cols, vals).prop_map(
10839                                |(t, cs, vs): (String, Vec<String>, Vec<String>)| {
10840                                    format!(
10841                                        "INSERT INTO {t} ({}) VALUES ({})",
10842                                        cs.join(", "),
10843                                        vs.join(", ")
10844                                    )
10845                                },
10846                            )
10847                        })
10848                        .boxed()
10849                },
10850                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
10851                1 => (arb_ident(), arb_expr(1))
10852                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
10853                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
10854                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
10855            ]
10856            .boxed()
10857        }
10858
10859        // Property 2: Determinism — same input always produces the same AST.
10860        proptest::proptest! {
10861            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(500))]
10862
10863            #[test]
10864            fn test_parser_determinism(sql in arb_statement()) {
10865                let mut p1 = Parser::from_sql(&sql);
10866                let (stmts1, errs1) = p1.parse_all();
10867
10868                let mut p2 = Parser::from_sql(&sql);
10869                let (stmts2, errs2) = p2.parse_all();
10870
10871                // Both parses must produce the same number of statements and errors.
10872                let msg_stmt = format!("different statement counts for: {sql}");
10873                prop_assert_eq!(stmts1.len(), stmts2.len(), "{}", msg_stmt);
10874                let msg_err = format!("different error counts for: {sql}");
10875                prop_assert_eq!(errs1.len(), errs2.len(), "{}", msg_err);
10876
10877                // If successful, the rendered SQL must be identical.
10878                if errs1.is_empty() && !stmts1.is_empty() {
10879                    for (s1, s2) in stmts1.iter().zip(stmts2.iter()) {
10880                        let r1 = s1.to_string();
10881                        let r2 = s2.to_string();
10882                        let msg_det = format!("non-deterministic parse output for: {sql}");
10883                        prop_assert_eq!(r1, r2, "{}", msg_det);
10884                    }
10885                }
10886            }
10887        }
10888
10889        // Property 3: Fuzz safety — random byte strings never panic the parser.
10890        proptest::proptest! {
10891                #![proptest_config(proptest::prelude::ProptestConfig::with_cases(2000))]
10892
10893                #[test]
10894        fn test_parser_fuzz_no_panic(input in prop::collection::vec(any::<u8>(), 0..256)) {
10895            let sql = String::from_utf8_lossy(&input);
10896            // Must not panic — errors are fine, panics are not.
10897            let mut p = Parser::from_sql(&sql);
10898            let _ = p.parse_all();
10899                }
10900            }
10901
10902        // Property 3b: Fuzz safety with near-valid SQL (more likely to trigger edge cases).
10903        proptest::proptest! {
10904            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
10905
10906            #[test]
10907            fn test_parser_fuzz_near_valid(
10908                prefix in prop_oneof![
10909                    Just("SELECT "),
10910                    Just("INSERT INTO "),
10911                    Just("DELETE FROM "),
10912                    Just("UPDATE "),
10913                    Just("CREATE TABLE "),
10914                    Just("DROP TABLE "),
10915                    Just("BEGIN "),
10916                    Just("PRAGMA "),
10917                ],
10918                suffix in prop::string::string_regex("[a-zA-Z0-9_ ,.*=<>!()'\";+\\-/]{0,100}")
10919                    .expect("valid regex")
10920            ) {
10921                let sql = format!("{prefix}{suffix}");
10922                let mut p = Parser::from_sql(&sql);
10923                let _ = p.parse_all();
10924            }
10925        }
10926
10927        // Property 4: Unicode identifiers parse correctly.
10928        proptest::proptest! {
10929            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
10930
10931            #[test]
10932            fn test_parser_unicode_identifiers(
10933                name in prop::string::string_regex("[\\p{L}][\\p{L}\\p{N}_]{0,10}")
10934                    .expect("valid regex")
10935                    .prop_filter("must not be keyword", |s| {
10936                        TokenKind::lookup_keyword(s).is_none()
10937                    })
10938            ) {
10939                // Double-quoted identifiers with Unicode should parse.
10940                let sql = format!("SELECT \"{name}\" FROM \"{name}\"");
10941                let mut p = Parser::from_sql(&sql);
10942                let (stmts, errs) = p.parse_all();
10943                prop_assert!(
10944                    errs.is_empty(),
10945                    "Unicode identifier should parse: {sql}, errors: {errs:?}"
10946                );
10947                prop_assert_eq!(stmts.len(), 1);
10948            }
10949        }
10950
10951        // Property 5: Rejection — various forms of invalid SQL are rejected.
10952        proptest::proptest! {
10953            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(300))]
10954
10955            #[test]
10956            fn test_parser_rejects_incomplete_statements(
10957                kind in prop_oneof![
10958                    Just("SELECT"),
10959                    Just("SELECT FROM"),
10960                    Just("INSERT INTO"),
10961                    Just("DELETE"),
10962                    Just("UPDATE SET"),
10963                    Just("CREATE"),
10964                    Just("CREATE TABLE"),
10965                    Just("DROP"),
10966                ],
10967                trailing in prop::option::of(
10968                    prop::string::string_regex("[;, ]{0,3}").expect("valid regex")
10969                )
10970            ) {
10971                let sql = match trailing {
10972                    Some(t) => format!("{kind}{t}"),
10973                    None => kind.to_string(),
10974                };
10975                let mut p = Parser::from_sql(&sql);
10976                let (stmts, errs) = p.parse_all();
10977                // At least one of: parse errors, or no valid statements produced.
10978                // The parser should not silently produce a valid-looking AST from
10979                // these fundamentally incomplete inputs.
10980                prop_assert!(
10981                    !errs.is_empty() || stmts.is_empty(),
10982                    "Expected rejection of incomplete SQL: {sql}, got {stmts:?}"
10983                );
10984            }
10985        }
10986
10987        // Property 6: Statement count stability — concatenated statements produce
10988        // the right number of parsed statements.
10989        proptest::proptest! {
10990            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
10991
10992            #[test]
10993            fn test_parser_multi_statement_count(
10994                stmts in proptest::collection::vec(arb_statement(), 1..4)
10995            ) {
10996                let sql = stmts.join("; ");
10997                let mut p = Parser::from_sql(&sql);
10998                let (parsed, errors) = p.parse_all();
10999                // If no errors, we should get at least as many statements as we joined.
11000                if errors.is_empty() {
11001                    prop_assert!(
11002                        parsed.len() >= stmts.len(),
11003                        "Expected at least {} statements from: {sql}, got {}",
11004                        stmts.len(),
11005                        parsed.len()
11006                    );
11007                }
11008            }
11009        }
11010    }
11011
11012    #[test]
11013    fn test_parse_statements_with_scratch_reuses_token_and_error_capacity() {
11014        let mut scratch = StatementParseScratch::default();
11015        let err = parse_statements_with_scratch("SELECT FROM", &mut scratch)
11016            .expect_err("malformed SQL should surface a parse error");
11017        assert!(
11018            err.message.contains("expected"),
11019            "malformed parse should preserve its diagnostic detail",
11020        );
11021        let warmed_token_capacity = scratch.token_capacity();
11022        let warmed_error_capacity = scratch.error_capacity();
11023        assert!(
11024            warmed_token_capacity > 0,
11025            "parse scratch should warm token storage"
11026        );
11027        assert!(
11028            warmed_error_capacity > 0,
11029            "parse scratch should warm error storage"
11030        );
11031
11032        let statements = parse_statements_with_scratch("SELECT 1;", &mut scratch)
11033            .expect("follow-up parse should succeed");
11034        assert_eq!(statements.len(), 1);
11035        assert_eq!(
11036            scratch.token_capacity(),
11037            warmed_token_capacity,
11038            "successful parse should reuse token scratch capacity",
11039        );
11040        assert_eq!(
11041            scratch.error_capacity(),
11042            warmed_error_capacity,
11043            "successful parse should preserve error scratch capacity for the next recovery path",
11044        );
11045    }
11046
11047    #[test]
11048    fn test_parse_statements_with_scratch_enforces_top_level_separators() {
11049        let mut scratch = StatementParseScratch::default();
11050        let sql = "SELECT 1 SELECT 2";
11051        let error = parse_statements_with_scratch(sql, &mut scratch)
11052            .expect_err("scratch parser must reject adjacent statements");
11053        assert!(
11054            error.message.contains("expected ';' separator"),
11055            "unexpected diagnostic: {error:?}"
11056        );
11057        assert_eq!(
11058            &sql[error.span.start as usize..error.span.end as usize],
11059            "SELECT"
11060        );
11061
11062        let statements = parse_statements_with_scratch("SELECT 1; SELECT 2;", &mut scratch)
11063            .expect("semicolon-separated statements must remain valid");
11064        assert_eq!(statements.len(), 2);
11065
11066        let trigger_script = "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
11067                              INSERT INTO t VALUES (1); \
11068                              INSERT INTO t VALUES (2); \
11069                              END; SELECT 3;";
11070        let statements = parse_statements_with_scratch(trigger_script, &mut scratch)
11071            .expect("trigger-body terminators must not become top-level separator errors");
11072        assert_eq!(statements.len(), 2);
11073        assert!(matches!(statements[0], Statement::CreateTrigger(_)));
11074        assert!(matches!(statements[1], Statement::Select(_)));
11075    }
11076
11077    #[test]
11078    fn test_parse_statements_with_scratch_reuses_identifier_interns_across_parses() {
11079        let mut scratch = StatementParseScratch::default();
11080        let mut sql = String::from("SELECT ");
11081        for i in 0..32 {
11082            if i > 0 {
11083                sql.push_str(", ");
11084            }
11085            sql.push_str(&format!("unique_identifier_{i} AS unique_alias_{i}"));
11086        }
11087        sql.push(';');
11088
11089        let statements = parse_statements_with_scratch(&sql, &mut scratch)
11090            .expect("identifier-heavy statement should parse");
11091        assert_eq!(statements.len(), 1);
11092        let interner_len = scratch.identifier_interner_len();
11093        assert!(
11094            interner_len > 0,
11095            "scratch should retain identifier interns for the next parse",
11096        );
11097
11098        let statements = parse_statements_with_scratch(&sql, &mut scratch)
11099            .expect("repeat parse should also succeed");
11100        assert_eq!(statements.len(), 1);
11101        assert_eq!(
11102            scratch.identifier_interner_len(),
11103            interner_len,
11104            "repeated parse should reuse the retained interner set instead of growing it",
11105        );
11106
11107        scratch.reset();
11108        assert!(
11109            scratch.identifier_interner_is_empty(),
11110            "explicit scratch reset should also keep the interner logically empty",
11111        );
11112    }
11113
11114    #[test]
11115    fn test_parse_statements_with_scratch_drops_oversized_identifier_interner() {
11116        let mut scratch = StatementParseScratch::default();
11117        let mut sql = String::from("SELECT ");
11118        for i in 0..300 {
11119            if i > 0 {
11120                sql.push_str(", ");
11121            }
11122            sql.push_str(&format!(
11123                "very_long_unique_identifier_{i:03} AS alias_{i:03}"
11124            ));
11125        }
11126        sql.push(';');
11127
11128        let statements = parse_statements_with_scratch(&sql, &mut scratch)
11129            .expect("oversized identifier-heavy statement should parse");
11130        assert_eq!(statements.len(), 1);
11131        assert!(
11132            scratch.identifier_interner_is_empty(),
11133            "oversized identifier interners should be dropped instead of retained indefinitely",
11134        );
11135    }
11136
11137    // ── bd-1702 repro tests ─────────────────────────────────────────────
11138    // Reserved-word column names in CREATE TABLE (quoted and unquoted).
11139
11140    #[test]
11141    fn create_table_quoted_reserved_word_key() {
11142        // Double-quoted "key" should parse as identifier, not KwKey.
11143        parse_ok(r#"CREATE TABLE "meta" ("key" TEXT, "val" TEXT);"#);
11144    }
11145
11146    #[test]
11147    fn create_table_unquoted_key_column() {
11148        // KEY is a non-reserved keyword — should work unquoted.
11149        parse_ok("CREATE TABLE meta (key TEXT, val TEXT);");
11150    }
11151
11152    #[test]
11153    fn create_table_quoted_order_column() {
11154        // ORDER is reserved — must work when double-quoted.
11155        parse_ok(r#"CREATE TABLE t ("order" INTEGER);"#);
11156    }
11157
11158    #[test]
11159    fn create_table_quoted_select_column() {
11160        // SELECT is reserved — must work when double-quoted.
11161        parse_ok(r#"CREATE TABLE t ("select" TEXT);"#);
11162    }
11163
11164    #[test]
11165    fn select_with_reserved_word_column_key() {
11166        // SELECT using "key" as column name — unquoted.
11167        parse_ok("SELECT key FROM meta;");
11168    }
11169
11170    #[test]
11171    fn select_with_reserved_word_column_value() {
11172        // SELECT using "value" — check if it's a keyword.
11173        parse_ok("SELECT value FROM meta;");
11174    }
11175
11176    #[test]
11177    fn select_with_reserved_word_column_order() {
11178        // ORDER is reserved — quoted should work.
11179        parse_ok(r#"SELECT "order" FROM t;"#);
11180    }
11181
11182    #[test]
11183    fn where_clause_with_reserved_word_column() {
11184        // WHERE referencing a reserved-word column.
11185        parse_ok("UPDATE meta SET val = '2.0' WHERE key = 'version';");
11186    }
11187
11188    #[test]
11189    fn update_set_reserved_word_column() {
11190        // SET reserved-word column.
11191        parse_ok(r#"UPDATE meta SET "key" = 'newkey' WHERE "key" = 'oldkey';"#);
11192    }
11193
11194    #[test]
11195    fn delete_where_reserved_word_column() {
11196        parse_ok("DELETE FROM meta WHERE key = 'version';");
11197    }
11198
11199    #[test]
11200    fn persistence_dump_with_reserved_word_columns() {
11201        // Simulates the exact SQL that build_create_table_sql generates
11202        // for a table that was originally created with reserved-word columns.
11203        let sql = concat!(
11204            r#"CREATE TABLE "meta" ("key" TEXT, "value" TEXT);"#,
11205            "\n",
11206            r#"INSERT INTO "meta" VALUES ('version', '1.0');"#,
11207            "\n",
11208            r#"INSERT INTO "meta" VALUES ('author', 'test');"#,
11209        );
11210        let mut p = Parser::from_sql(sql);
11211        let (stmts, errs) = p.parse_all();
11212        assert!(
11213            errs.is_empty(),
11214            "persistence dump with reserved-word columns should parse cleanly: {errs:?}"
11215        );
11216        assert_eq!(stmts.len(), 3);
11217    }
11218
11219    #[test]
11220    fn create_table_with_single_quoted_name_parses_cleanly() {
11221        let sql = "CREATE TABLE 'fts_messages_data'(id INTEGER PRIMARY KEY, block BLOB);";
11222        let mut p = Parser::from_sql(sql);
11223        let (stmts, errs) = p.parse_all();
11224        assert!(
11225            errs.is_empty(),
11226            "single-quoted sqlite_master shadow-table SQL should parse cleanly: {errs:?}"
11227        );
11228        assert_eq!(stmts.len(), 1);
11229        match &stmts[0] {
11230            Statement::CreateTable(stmt) => {
11231                assert_eq!(stmt.name.name, "fts_messages_data");
11232            }
11233            other => panic!("expected CreateTable, got {other:?}"),
11234        }
11235    }
11236
11237    #[test]
11238    fn select_qualified_column_with_alias() {
11239        // Bug: "a.name as from_name" was being parsed with alias=None and
11240        // col_ref.column="name as" instead of alias=Some("from_name").
11241        let stmt = parse_one("SELECT a.name AS from_name FROM users a");
11242        if let Statement::Select(s) = stmt {
11243            if let SelectCore::Select { columns, .. } = &s.body.select {
11244                assert_eq!(columns.len(), 1);
11245                match &columns[0] {
11246                    ResultColumn::Expr { expr, alias } => {
11247                        // Alias should be captured as "from_name".
11248                        assert_eq!(
11249                            alias.as_deref(),
11250                            Some("from_name"),
11251                            "alias should be 'from_name', got {alias:?}"
11252                        );
11253                        // Expression should be a qualified column ref: a.name
11254                        if let Expr::Column(col_ref, _) = expr {
11255                            assert_eq!(col_ref.table.as_deref(), Some("a"));
11256                            assert_eq!(col_ref.column.as_ref(), "name");
11257                        } else {
11258                            panic!("expected Column expression, got {expr:?}");
11259                        }
11260                    }
11261                    other => panic!("expected Expr variant, got {other:?}"),
11262                }
11263            } else {
11264                panic!("expected Select core");
11265            }
11266        } else {
11267            panic!("expected Select statement");
11268        }
11269    }
11270
11271    #[test]
11272    fn select_qualified_column_with_implicit_alias() {
11273        // Test implicit alias syntax (without AS keyword).
11274        let stmt = parse_one("SELECT a.name from_name FROM users a");
11275        if let Statement::Select(s) = stmt {
11276            if let SelectCore::Select { columns, .. } = &s.body.select {
11277                assert_eq!(columns.len(), 1);
11278                match &columns[0] {
11279                    ResultColumn::Expr { expr, alias } => {
11280                        // Alias should be captured as "from_name" even without AS.
11281                        assert_eq!(
11282                            alias.as_deref(),
11283                            Some("from_name"),
11284                            "implicit alias should be 'from_name', got {alias:?}"
11285                        );
11286                        // Expression should be a qualified column ref: a.name
11287                        if let Expr::Column(col_ref, _) = expr {
11288                            assert_eq!(col_ref.table.as_deref(), Some("a"));
11289                            assert_eq!(col_ref.column.as_ref(), "name");
11290                        } else {
11291                            panic!("expected Column expression, got {expr:?}");
11292                        }
11293                    }
11294                    other => panic!("expected Expr variant, got {other:?}"),
11295                }
11296            } else {
11297                panic!("expected Select core");
11298            }
11299        } else {
11300            panic!("expected Select statement");
11301        }
11302    }
11303
11304    #[test]
11305    fn select_implicit_alias_non_reserved_keyword() {
11306        // 'action' is a non-reserved keyword (TokenKind::KwAction).
11307        // It should be accepted as an implicit alias: SELECT 1 action
11308        let stmt = parse_one("SELECT 1 action");
11309        if let Statement::Select(s) = stmt {
11310            if let SelectCore::Select { columns, .. } = &s.body.select {
11311                if let ResultColumn::Expr { alias, .. } = &columns[0] {
11312                    assert_eq!(
11313                        alias.as_deref(),
11314                        Some("action"),
11315                        "implicit alias 'action' (keyword) failed to parse"
11316                    );
11317                } else {
11318                    unreachable!("expected Expr result column");
11319                }
11320            } else {
11321                unreachable!("expected Select core");
11322            }
11323        } else {
11324            unreachable!("expected Select");
11325        }
11326    }
11327}