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