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