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