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