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