Skip to main content

fsqlite_parser/
parser.rs

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