Skip to main content

fsqlite_parser/
parser.rs

1// bd-2tu6: §10.2 SQL Parser
2//
3// Hand-written recursive descent parser. Expression parsing lives in expr.rs.
4
5use std::error::Error;
6use std::fmt;
7use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
8
9use fsqlite_ast::{
10    AlterTableAction, AlterTableStatement, Assignment, AssignmentTarget, AttachStatement,
11    BeginStatement, ColumnConstraint, ColumnConstraintKind, ColumnDef, CompoundOp, ConflictAction,
12    CreateIndexStatement, CreateTableBody, CreateTableStatement, CreateTriggerStatement,
13    CreateViewStatement, CreateVirtualTableStatement, Cte, CteMaterialized, DefaultValue,
14    Deferrable, DeferrableInitially, DeleteStatement, Distinctness, DropObjectType, DropStatement,
15    Expr, ForeignKeyAction, ForeignKeyActionType, ForeignKeyClause, ForeignKeyTrigger, FrameBound,
16    FrameExclude, FrameSpec, FrameType, FromClause, GeneratedStorage, IndexHint, IndexedColumn,
17    InsertSource, InsertStatement, JoinClause, JoinConstraint, JoinKind, JoinType, LimitClause,
18    Literal, NullsOrder, OrderingTerm, PragmaStatement, PragmaValue, QualifiedName,
19    QualifiedTableRef, ResultColumn, RollbackStatement, SelectBody, SelectCore, SelectStatement,
20    SortDirection, Span, Statement, TableConstraint, TableConstraintKind, TableOrSubquery,
21    TimeTravelClause, TimeTravelTarget, TransactionMode, TriggerEvent, TriggerTiming, TypeName,
22    UpdateStatement, UpsertAction, UpsertClause, UpsertTarget, VacuumStatement, WindowDef,
23    WindowSpec, WithClause,
24};
25
26use crate::lexer::Lexer;
27use crate::token::{Token, TokenKind};
28
29// ---------------------------------------------------------------------------
30// Parse metrics
31// ---------------------------------------------------------------------------
32
33/// Monotonic counter of successfully parsed statements.
34static FSQLITE_PARSE_STATEMENTS_TOTAL: AtomicU64 = AtomicU64::new(0);
35/// Monotonic counter of tokens consumed by successful parse_all() calls.
36static FSQLITE_PARSE_TOKENS_TOTAL: AtomicU64 = AtomicU64::new(0);
37/// Monotonic counter of parse errors encountered by parse_all().
38static FSQLITE_PARSE_ERRORS_TOTAL: AtomicU64 = AtomicU64::new(0);
39/// Whether parse metrics should be collected on the hot path.
40///
41/// These counters are currently used only for diagnostics/tests, so leave
42/// them disabled by default to avoid shared-state bookkeeping on ordinary
43/// parse calls.
44static FSQLITE_PARSE_METRICS_ENABLED: AtomicBool = AtomicBool::new(false);
45
46/// Point-in-time parse metrics snapshot.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
48pub struct ParseMetricsSnapshot {
49    /// Total statements successfully parsed.
50    pub fsqlite_parse_statements_total: u64,
51    /// Total tokens observed by successful parse_all() calls.
52    pub fsqlite_parse_tokens_total: u64,
53    /// Total parse errors observed by parse_all() calls.
54    pub fsqlite_parse_errors_total: u64,
55}
56
57/// Take a point-in-time snapshot of parse metrics.
58#[must_use]
59pub fn parse_metrics_snapshot() -> ParseMetricsSnapshot {
60    ParseMetricsSnapshot {
61        fsqlite_parse_statements_total: FSQLITE_PARSE_STATEMENTS_TOTAL.load(Ordering::Relaxed),
62        fsqlite_parse_tokens_total: FSQLITE_PARSE_TOKENS_TOTAL.load(Ordering::Relaxed),
63        fsqlite_parse_errors_total: FSQLITE_PARSE_ERRORS_TOTAL.load(Ordering::Relaxed),
64    }
65}
66
67/// Enable or disable parse metrics collection on the hot path.
68pub fn set_parse_metrics_enabled(enabled: bool) {
69    FSQLITE_PARSE_METRICS_ENABLED.store(enabled, Ordering::Relaxed);
70}
71
72/// Return whether parse metrics collection is enabled.
73#[must_use]
74pub fn parse_metrics_enabled() -> bool {
75    FSQLITE_PARSE_METRICS_ENABLED.load(Ordering::Relaxed)
76}
77
78/// Reset parse metrics (used by tests/diagnostics).
79pub fn reset_parse_metrics() {
80    FSQLITE_PARSE_STATEMENTS_TOTAL.store(0, Ordering::Relaxed);
81    FSQLITE_PARSE_TOKENS_TOTAL.store(0, Ordering::Relaxed);
82    FSQLITE_PARSE_ERRORS_TOTAL.store(0, Ordering::Relaxed);
83}
84
85// ---------------------------------------------------------------------------
86// Error type
87// ---------------------------------------------------------------------------
88
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct ParseError {
91    pub message: String,
92    pub span: Span,
93    pub line: u32,
94    pub col: u32,
95}
96
97impl ParseError {
98    #[must_use]
99    pub(crate) fn at(message: impl Into<String>, token: Option<&Token>) -> Self {
100        if let Some(t) = token {
101            Self {
102                message: message.into(),
103                span: t.span,
104                line: t.line,
105                col: t.col,
106            }
107        } else {
108            Self {
109                message: message.into(),
110                span: Span::ZERO,
111                line: 0,
112                col: 0,
113            }
114        }
115    }
116}
117
118impl fmt::Display for ParseError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        write!(f, "{}:{}: {}", self.line, self.col, self.message)
121    }
122}
123
124impl Error for ParseError {}
125
126// ---------------------------------------------------------------------------
127// Parser
128// ---------------------------------------------------------------------------
129
130pub const MAX_PARSE_DEPTH: u32 = 1000;
131
132pub struct Parser {
133    pub(crate) tokens: Vec<Token>,
134    pub(crate) pos: usize,
135    pub(crate) errors: Vec<ParseError>,
136    pub(crate) depth: u32,
137}
138
139impl Parser {
140    #[must_use]
141    pub fn new(tokens: Vec<Token>) -> Self {
142        Self {
143            tokens,
144            pos: 0,
145            errors: Vec::new(),
146            depth: 0,
147        }
148    }
149
150    pub(crate) fn enter_recursion(&mut self) -> Result<(), ParseError> {
151        if self.depth >= MAX_PARSE_DEPTH {
152            return Err(self.err_msg(format!(
153                "expression tree is too deep (maximum depth {MAX_PARSE_DEPTH})"
154            )));
155        }
156        self.depth += 1;
157        Ok(())
158    }
159
160    pub(crate) fn leave_recursion(&mut self) {
161        self.depth = self.depth.saturating_sub(1);
162    }
163
164    pub(crate) fn with_recursion_guard<T>(
165        &mut self,
166        f: impl FnOnce(&mut Self) -> Result<T, ParseError>,
167    ) -> Result<T, ParseError> {
168        self.enter_recursion()?;
169        let result = f(self);
170        self.leave_recursion();
171        result
172    }
173
174    #[must_use]
175    pub fn from_sql(sql: &str) -> Self {
176        Self::new(Lexer::tokenize(sql))
177    }
178
179    pub fn parse_all(&mut self) -> (Vec<Statement>, Vec<ParseError>) {
180        let parse_debug_enabled = tracing::enabled!(target: "fsqlite.parse", tracing::Level::DEBUG);
181        let collect_parse_metrics = parse_metrics_enabled();
182        if collect_parse_metrics {
183            let token_count = u64::try_from(self.tokens.len()).unwrap_or(u64::MAX);
184            FSQLITE_PARSE_TOKENS_TOTAL.fetch_add(token_count, Ordering::Relaxed);
185        }
186        let span = parse_debug_enabled.then(|| {
187            tracing::debug_span!(
188                target: "fsqlite.parse",
189                "parse",
190                ast_node_count = tracing::field::Empty,
191                parse_errors = tracing::field::Empty,
192            )
193        });
194        let _guard = span.as_ref().map(|span| span.enter());
195
196        let mut stmts = Vec::new();
197        while !self.at_eof() {
198            if self.check(&TokenKind::Semicolon) {
199                self.advance();
200                continue;
201            }
202            match self.parse_statement() {
203                Ok(s) => {
204                    if collect_parse_metrics {
205                        FSQLITE_PARSE_STATEMENTS_TOTAL.fetch_add(1, Ordering::Relaxed);
206                    }
207                    stmts.push(s);
208                    let _ = self.eat(&TokenKind::Semicolon);
209                }
210                Err(e) => {
211                    if collect_parse_metrics {
212                        FSQLITE_PARSE_ERRORS_TOTAL.fetch_add(1, Ordering::Relaxed);
213                    }
214                    tracing::warn!(
215                        target: "fsqlite.parse",
216                        error = %e,
217                        "parse recovery: skipping malformed statement"
218                    );
219                    self.errors.push(e);
220                    self.synchronize();
221                }
222            }
223        }
224
225        let errors = std::mem::take(&mut self.errors);
226        if let Some(span) = span.as_ref() {
227            span.record("ast_node_count", stmts.len() as u64);
228            span.record("parse_errors", errors.len() as u64);
229        }
230
231        (stmts, errors)
232    }
233
234    pub fn parse_statement(&mut self) -> Result<Statement, ParseError> {
235        self.parse_statement_inner()
236    }
237
238    #[must_use]
239    pub fn errors(&self) -> &[ParseError] {
240        &self.errors
241    }
242
243    // -----------------------------------------------------------------------
244    // Token navigation
245    // -----------------------------------------------------------------------
246
247    pub(crate) fn peek(&self) -> &TokenKind {
248        self.current().map_or(&TokenKind::Eof, |t| &t.kind)
249    }
250
251    pub(crate) fn current(&self) -> Option<&Token> {
252        self.tokens.get(self.pos)
253    }
254
255    pub(crate) fn peek_nth(&self, n: usize) -> &TokenKind {
256        self.tokens
257            .get(self.pos + n)
258            .map_or(&TokenKind::Eof, |t| &t.kind)
259    }
260
261    pub(crate) fn at_eof(&self) -> bool {
262        matches!(self.peek(), TokenKind::Eof)
263    }
264
265    pub(crate) fn advance(&mut self) -> Option<&Token> {
266        let t = self.tokens.get(self.pos);
267        if self.pos < self.tokens.len().saturating_sub(1) {
268            self.pos += 1;
269        }
270        t
271    }
272
273    pub(crate) fn check(&self, kind: &TokenKind) -> bool {
274        std::mem::discriminant(self.peek()) == std::mem::discriminant(kind)
275    }
276
277    pub(crate) fn check_kw(&self, kw: &TokenKind) -> bool {
278        self.peek() == kw
279    }
280
281    pub(crate) fn eat(&mut self, kind: &TokenKind) -> bool {
282        if self.check(kind) {
283            self.advance();
284            true
285        } else {
286            false
287        }
288    }
289
290    pub(crate) fn eat_kw(&mut self, kw: &TokenKind) -> bool {
291        if self.peek() == kw {
292            self.advance();
293            true
294        } else {
295            false
296        }
297    }
298
299    pub(crate) fn expect_kw(&mut self, kw: &TokenKind) -> Result<Span, ParseError> {
300        if self.peek() == kw {
301            let sp = self.current_span();
302            self.advance();
303            Ok(sp)
304        } else {
305            Err(self.err_expected(&format!("{kw:?}")))
306        }
307    }
308
309    pub(crate) fn expect_token(&mut self, kind: &TokenKind) -> Result<Span, ParseError> {
310        if self.check(kind) {
311            let sp = self.current_span();
312            self.advance();
313            Ok(sp)
314        } else {
315            Err(self.err_expected(&format!("{kind:?}")))
316        }
317    }
318
319    pub(crate) fn current_span(&self) -> Span {
320        self.current().map_or(Span::ZERO, |t| t.span)
321    }
322
323    pub(crate) fn err_expected(&self, what: &str) -> ParseError {
324        ParseError::at(format!("expected {what}"), self.current())
325    }
326
327    pub(crate) fn err_msg(&self, msg: impl Into<String>) -> ParseError {
328        ParseError::at(msg, self.current())
329    }
330
331    fn synchronize(&mut self) {
332        loop {
333            match self.peek() {
334                TokenKind::Eof => return,
335                TokenKind::Semicolon => {
336                    self.advance();
337                    return;
338                }
339                k if k.is_statement_start() => return,
340                _ => {
341                    self.advance();
342                }
343            }
344        }
345    }
346
347    fn recover_trigger_body_after_error(&mut self) {
348        let mut case_depth = 0_usize;
349
350        loop {
351            match self.peek() {
352                TokenKind::Eof => return,
353                TokenKind::KwCase => {
354                    case_depth = case_depth.saturating_add(1);
355                    self.advance();
356                }
357                TokenKind::KwEnd if case_depth > 0 => {
358                    case_depth = case_depth.saturating_sub(1);
359                    self.advance();
360                }
361                // Once CASE nesting is balanced, treat END as the trigger-body
362                // terminator even if the malformed statement left parentheses
363                // unbalanced. Recovery should prefer the enclosing trigger
364                // boundary over swallowing subsequent top-level SQL.
365                TokenKind::KwEnd => {
366                    self.advance();
367                    let _ = self.eat(&TokenKind::Semicolon);
368                    return;
369                }
370                _ => {
371                    self.advance();
372                }
373            }
374        }
375    }
376
377    // -----------------------------------------------------------------------
378    // Identifiers and names
379    // -----------------------------------------------------------------------
380
381    pub(crate) fn parse_identifier(&mut self) -> Result<String, ParseError> {
382        match self.peek().clone() {
383            TokenKind::Id(s) | TokenKind::QuotedId(s, _) => {
384                self.advance();
385                Ok(s)
386            }
387            ref k if is_nonreserved_kw(k) => {
388                let s = kw_to_str(k);
389                self.advance();
390                Ok(s)
391            }
392            _ => Err(self.err_expected("identifier")),
393        }
394    }
395
396    pub(crate) fn parse_qualified_name(&mut self) -> Result<QualifiedName, ParseError> {
397        let first = self.parse_identifier()?;
398        if self.eat(&TokenKind::Dot) {
399            let second = self.parse_identifier()?;
400            Ok(QualifiedName::qualified(first, second))
401        } else {
402            Ok(QualifiedName::bare(first))
403        }
404    }
405
406    fn parse_qualified_table_ref(&mut self) -> Result<QualifiedTableRef, ParseError> {
407        let name = self.parse_qualified_name()?;
408        let alias = self.try_alias()?;
409        let index_hint = self.parse_index_hint()?;
410        let time_travel = self.parse_time_travel_clause()?;
411        Ok(QualifiedTableRef {
412            name,
413            alias,
414            index_hint,
415            time_travel,
416        })
417    }
418
419    fn try_alias(&mut self) -> Result<Option<String>, ParseError> {
420        if self.eat_kw(&TokenKind::KwAs) {
421            return Ok(Some(self.parse_identifier()?));
422        }
423        // Peek for an identifier that isn't a keyword starting the next clause.
424        // We also accept non-reserved keywords as implicit aliases.
425        match self.peek() {
426            TokenKind::Id(_) | TokenKind::QuotedId(_, _) => {
427                return Ok(Some(self.parse_identifier()?));
428            }
429            k if is_nonreserved_kw(k) && !is_alias_terminator_kw(k) => {
430                return Ok(Some(self.parse_identifier()?));
431            }
432            _ => {}
433        }
434        Ok(None)
435    }
436
437    fn parse_index_hint(&mut self) -> Result<Option<IndexHint>, ParseError> {
438        if self.eat_kw(&TokenKind::KwIndexed) {
439            self.expect_kw(&TokenKind::KwBy)?;
440            Ok(Some(IndexHint::IndexedBy(self.parse_identifier()?)))
441        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwIndexed {
442            self.advance();
443            self.advance();
444            Ok(Some(IndexHint::NotIndexed))
445        } else {
446            Ok(None)
447        }
448    }
449
450    /// Parse an optional `FOR SYSTEM_TIME AS OF ...` clause (SQL:2011 temporal query).
451    ///
452    /// Grammar:
453    /// ```text
454    /// time_travel_clause ::= FOR SYSTEM_TIME AS OF COMMITSEQ integer
455    ///                      | FOR SYSTEM_TIME AS OF string_literal
456    /// ```
457    fn parse_time_travel_clause(&mut self) -> Result<Option<TimeTravelClause>, ParseError> {
458        if !self.check_kw(&TokenKind::KwFor) {
459            return Ok(None);
460        }
461        // Lookahead: FOR must be followed by SYSTEM_TIME (contextual identifier).
462        if !matches!(self.peek_nth(1), TokenKind::Id(s) if s.eq_ignore_ascii_case("SYSTEM_TIME")) {
463            return Ok(None);
464        }
465        self.advance(); // consume FOR
466        self.advance(); // consume SYSTEM_TIME
467        self.expect_kw(&TokenKind::KwAs)?;
468        self.expect_kw(&TokenKind::KwOf)?;
469
470        let target = if self.eat_kw(&TokenKind::KwCommitseq) {
471            match self.peek().clone() {
472                TokenKind::Integer(n) if n >= 0 => {
473                    self.advance();
474                    TimeTravelTarget::CommitSequence(n as u64)
475                }
476                TokenKind::OversizedInt(s) => {
477                    if let Ok(n) = s.parse::<u64>() {
478                        self.advance();
479                        TimeTravelTarget::CommitSequence(n)
480                    } else {
481                        return Err(self.err_expected("non-negative integer after COMMITSEQ"));
482                    }
483                }
484                _ => return Err(self.err_expected("non-negative integer after COMMITSEQ")),
485            }
486        } else {
487            match self.peek().clone() {
488                TokenKind::String(s) => {
489                    self.advance();
490                    TimeTravelTarget::Timestamp(s)
491                }
492                _ => {
493                    return Err(self.err_expected(
494                        "COMMITSEQ <n> or '<timestamp>' after FOR SYSTEM_TIME AS OF",
495                    ));
496                }
497            }
498        };
499
500        Ok(Some(TimeTravelClause { target }))
501    }
502
503    pub(crate) fn parse_comma_sep<T>(
504        &mut self,
505        f: fn(&mut Self) -> Result<T, ParseError>,
506    ) -> Result<Vec<T>, ParseError> {
507        let mut v = vec![f(self)?];
508        while self.eat(&TokenKind::Comma) {
509            v.push(f(self)?);
510        }
511        Ok(v)
512    }
513
514    // -----------------------------------------------------------------------
515    // Statement dispatch
516    // -----------------------------------------------------------------------
517
518    fn parse_statement_inner(&mut self) -> Result<Statement, ParseError> {
519        self.with_recursion_guard(|parser| match parser.peek().clone() {
520            TokenKind::KwSelect | TokenKind::KwValues => {
521                Ok(Statement::Select(parser.parse_select_stmt(None)?))
522            }
523            TokenKind::KwWith => parser.parse_with_leading(),
524            TokenKind::KwInsert | TokenKind::KwReplace => parser.parse_insert_stmt(None),
525            TokenKind::KwUpdate => parser.parse_update_stmt(None),
526            TokenKind::KwDelete => parser.parse_delete_stmt(None),
527            TokenKind::KwCreate => parser.parse_create(),
528            TokenKind::KwDrop => parser.parse_drop(),
529            TokenKind::KwAlter => parser.parse_alter(),
530            TokenKind::KwBegin => parser.parse_begin(),
531            TokenKind::KwCommit | TokenKind::KwEnd => {
532                parser.advance();
533                let _ = parser.eat_kw(&TokenKind::KwTransaction);
534                Ok(Statement::Commit)
535            }
536            TokenKind::KwRollback => parser.parse_rollback(),
537            TokenKind::KwSavepoint => {
538                parser.advance();
539                Ok(Statement::Savepoint(parser.parse_identifier()?))
540            }
541            TokenKind::KwRelease => {
542                parser.advance();
543                let _ = parser.eat_kw(&TokenKind::KwSavepoint);
544                Ok(Statement::Release(parser.parse_identifier()?))
545            }
546            TokenKind::KwAttach => parser.parse_attach(),
547            TokenKind::KwDetach => {
548                parser.advance();
549                let _ = parser.eat_kw(&TokenKind::KwDatabase);
550                Ok(Statement::Detach(parser.parse_identifier()?))
551            }
552            TokenKind::KwPragma => parser.parse_pragma(),
553            TokenKind::KwVacuum => parser.parse_vacuum(),
554            TokenKind::KwReindex => {
555                parser.advance();
556                let name = if !parser.at_eof() && !parser.check(&TokenKind::Semicolon) {
557                    Some(parser.parse_qualified_name()?)
558                } else {
559                    None
560                };
561                Ok(Statement::Reindex(name))
562            }
563            TokenKind::KwAnalyze => {
564                parser.advance();
565                let name = if !parser.at_eof() && !parser.check(&TokenKind::Semicolon) {
566                    Some(parser.parse_qualified_name()?)
567                } else {
568                    None
569                };
570                Ok(Statement::Analyze(name))
571            }
572            TokenKind::KwExplain => parser.parse_explain(),
573            _ => Err(parser.err_msg("unexpected token at start of statement")),
574        })
575    }
576
577    // -----------------------------------------------------------------------
578    // WITH ... (SELECT | INSERT | UPDATE | DELETE)
579    // -----------------------------------------------------------------------
580
581    fn parse_with_leading(&mut self) -> Result<Statement, ParseError> {
582        let with = self.parse_with_clause()?;
583        match self.peek() {
584            TokenKind::KwSelect | TokenKind::KwValues => {
585                Ok(Statement::Select(self.parse_select_stmt(Some(with))?))
586            }
587            TokenKind::KwInsert | TokenKind::KwReplace => self.parse_insert_stmt(Some(with)),
588            TokenKind::KwUpdate => self.parse_update_stmt(Some(with)),
589            TokenKind::KwDelete => self.parse_delete_stmt(Some(with)),
590            _ => Err(self.err_expected("SELECT, INSERT, UPDATE, or DELETE after WITH")),
591        }
592    }
593
594    pub(crate) fn parse_with_clause(&mut self) -> Result<WithClause, ParseError> {
595        self.expect_kw(&TokenKind::KwWith)?;
596        let recursive = self.eat_kw(&TokenKind::KwRecursive);
597        let ctes = self.parse_comma_sep(Self::parse_cte)?;
598        Ok(WithClause { recursive, ctes })
599    }
600
601    fn parse_cte(&mut self) -> Result<Cte, ParseError> {
602        let name = self.parse_identifier()?;
603        let columns = if self.eat(&TokenKind::LeftParen) {
604            let cols = self.parse_comma_sep(Self::parse_identifier)?;
605            self.expect_token(&TokenKind::RightParen)?;
606            cols
607        } else {
608            vec![]
609        };
610        // SQL syntax: name AS [NOT] MATERIALIZED (subquery)
611        self.expect_kw(&TokenKind::KwAs)?;
612        let materialized = if self.check_kw(&TokenKind::KwNot) {
613            self.advance();
614            self.expect_kw(&TokenKind::KwMaterialized)?;
615            Some(CteMaterialized::NotMaterialized)
616        } else if self.eat_kw(&TokenKind::KwMaterialized) {
617            Some(CteMaterialized::Materialized)
618        } else {
619            None
620        };
621        self.expect_token(&TokenKind::LeftParen)?;
622        let query = self.parse_select_stmt(None)?;
623        self.expect_token(&TokenKind::RightParen)?;
624        Ok(Cte {
625            name,
626            columns,
627            materialized,
628            query,
629        })
630    }
631
632    // -----------------------------------------------------------------------
633    // SELECT
634    // -----------------------------------------------------------------------
635
636    pub(crate) fn parse_select_stmt(
637        &mut self,
638        with: Option<WithClause>,
639    ) -> Result<SelectStatement, ParseError> {
640        self.with_recursion_guard(|parser| parser.parse_select_stmt_inner(with))
641    }
642
643    fn parse_select_stmt_inner(
644        &mut self,
645        with: Option<WithClause>,
646    ) -> Result<SelectStatement, ParseError> {
647        let body = self.parse_select_body()?;
648        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
649            self.expect_kw(&TokenKind::KwBy)?;
650            self.parse_comma_sep(Self::parse_ordering_term)?
651        } else {
652            vec![]
653        };
654        let limit = self.parse_limit()?;
655        Ok(SelectStatement {
656            with,
657            body,
658            order_by,
659            limit,
660        })
661    }
662
663    fn parse_select_body(&mut self) -> Result<SelectBody, ParseError> {
664        let select = self.parse_select_core()?;
665        let mut compounds = Vec::new();
666        loop {
667            let op = if self.eat_kw(&TokenKind::KwUnion) {
668                if self.eat_kw(&TokenKind::KwAll) {
669                    CompoundOp::UnionAll
670                } else {
671                    CompoundOp::Union
672                }
673            } else if self.eat_kw(&TokenKind::KwIntersect) {
674                CompoundOp::Intersect
675            } else if self.eat_kw(&TokenKind::KwExcept) {
676                CompoundOp::Except
677            } else {
678                break;
679            };
680            compounds.push((op, self.parse_select_core()?));
681        }
682        Ok(SelectBody { select, compounds })
683    }
684
685    fn parse_select_core(&mut self) -> Result<SelectCore, ParseError> {
686        if self.eat_kw(&TokenKind::KwValues) {
687            return self.parse_values_core();
688        }
689        self.expect_kw(&TokenKind::KwSelect)?;
690        let distinct = if self.eat_kw(&TokenKind::KwDistinct) {
691            Distinctness::Distinct
692        } else {
693            let _ = self.eat_kw(&TokenKind::KwAll);
694            Distinctness::All
695        };
696        let columns = self.parse_comma_sep(Self::parse_result_column)?;
697        let from = if self.eat_kw(&TokenKind::KwFrom) {
698            Some(self.parse_from_clause()?)
699        } else {
700            None
701        };
702        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
703            Some(Box::new(self.parse_expr()?))
704        } else {
705            None
706        };
707        let group_by = if self.eat_kw(&TokenKind::KwGroup) {
708            self.expect_kw(&TokenKind::KwBy)?;
709            self.parse_comma_sep(Self::parse_expr)?
710        } else {
711            vec![]
712        };
713        let having = if self.eat_kw(&TokenKind::KwHaving) {
714            Some(Box::new(self.parse_expr()?))
715        } else {
716            None
717        };
718        let windows = if self.eat_kw(&TokenKind::KwWindow) {
719            self.parse_comma_sep(Self::parse_window_def)?
720        } else {
721            vec![]
722        };
723        Ok(SelectCore::Select {
724            distinct,
725            columns,
726            from,
727            where_clause,
728            group_by,
729            having,
730            windows,
731        })
732    }
733
734    fn parse_values_core(&mut self) -> Result<SelectCore, ParseError> {
735        let mut rows = Vec::new();
736        loop {
737            self.expect_token(&TokenKind::LeftParen)?;
738            let row = self.parse_comma_sep(Self::parse_expr)?;
739            self.expect_token(&TokenKind::RightParen)?;
740            rows.push(row);
741            if !self.eat(&TokenKind::Comma) {
742                break;
743            }
744        }
745        Ok(SelectCore::Values(rows))
746    }
747
748    fn parse_result_column(&mut self) -> Result<ResultColumn, ParseError> {
749        if self.eat(&TokenKind::Star) {
750            return Ok(ResultColumn::Star);
751        }
752        // table.* check: identifier followed by dot-star.
753        if matches!(self.peek(), TokenKind::Id(_) | TokenKind::QuotedId(_, _))
754            && self.peek_nth(1) == &TokenKind::Dot
755            && self.peek_nth(2) == &TokenKind::Star
756        {
757            let tbl = self.parse_identifier()?;
758            self.advance(); // dot
759            self.advance(); // star
760            return Ok(ResultColumn::TableStar(tbl));
761        }
762        let expr = self.parse_expr()?;
763        let alias = self.try_alias()?;
764        Ok(ResultColumn::Expr { expr, alias })
765    }
766
767    // -----------------------------------------------------------------------
768    // FROM clause & JOINs
769    // -----------------------------------------------------------------------
770
771    fn parse_from_clause(&mut self) -> Result<FromClause, ParseError> {
772        let source = self.parse_table_or_subquery()?;
773        let mut joins = Vec::new();
774        loop {
775            if let Some(jt) = self.try_join_type()? {
776                let table = self.parse_table_or_subquery()?;
777                let constraint = self.parse_join_constraint()?;
778                if jt.natural && constraint.is_some() {
779                    return Err(self.err_msg("a NATURAL join may not have an ON or USING clause"));
780                }
781                joins.push(JoinClause {
782                    join_type: jt,
783                    table,
784                    constraint,
785                });
786            } else if self.eat(&TokenKind::Comma) {
787                let table = self.parse_table_or_subquery()?;
788                joins.push(JoinClause {
789                    join_type: JoinType {
790                        natural: false,
791                        kind: JoinKind::Cross,
792                    },
793                    table,
794                    constraint: None,
795                });
796            } else {
797                break;
798            }
799        }
800        Ok(FromClause { source, joins })
801    }
802
803    fn parse_table_or_subquery(&mut self) -> Result<TableOrSubquery, ParseError> {
804        if self.check(&TokenKind::LeftParen) {
805            self.advance();
806            if matches!(
807                self.peek(),
808                TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
809            ) {
810                let with = if self.check_kw(&TokenKind::KwWith) {
811                    Some(self.parse_with_clause()?)
812                } else {
813                    None
814                };
815                let q = self.parse_select_stmt(with)?;
816                self.expect_token(&TokenKind::RightParen)?;
817                let alias = self.try_alias()?;
818                return Ok(TableOrSubquery::Subquery {
819                    query: Box::new(q),
820                    alias,
821                });
822            }
823            // Parenthesized join
824            let fc = self.parse_from_clause()?;
825            self.expect_token(&TokenKind::RightParen)?;
826            return Ok(TableOrSubquery::ParenJoin(Box::new(fc)));
827        }
828
829        let name = self.parse_qualified_name()?;
830
831        // Table-valued function: name(args)
832        if self.check(&TokenKind::LeftParen) && name.schema.is_none() {
833            self.advance();
834            let args = if self.check(&TokenKind::RightParen) {
835                vec![]
836            } else {
837                self.parse_comma_sep(Self::parse_expr)?
838            };
839            self.expect_token(&TokenKind::RightParen)?;
840            let alias = self.try_alias()?;
841            return Ok(TableOrSubquery::TableFunction {
842                name: name.name,
843                args,
844                alias,
845            });
846        }
847
848        let alias = self.try_alias()?;
849        let index_hint = self.parse_index_hint()?;
850        let time_travel = self.parse_time_travel_clause()?;
851        Ok(TableOrSubquery::Table {
852            name,
853            alias,
854            index_hint,
855            time_travel,
856        })
857    }
858
859    fn try_join_type(&mut self) -> Result<Option<JoinType>, ParseError> {
860        let natural = self.eat_kw(&TokenKind::KwNatural);
861        let kind = if self.eat_kw(&TokenKind::KwJoin) {
862            Some(JoinKind::Inner)
863        } else if self.eat_kw(&TokenKind::KwInner) {
864            self.expect_kw(&TokenKind::KwJoin)?;
865            Some(JoinKind::Inner)
866        } else if self.eat_kw(&TokenKind::KwCross) {
867            self.expect_kw(&TokenKind::KwJoin)?;
868            Some(JoinKind::Cross)
869        } else if self.eat_kw(&TokenKind::KwLeft) {
870            let _ = self.eat_kw(&TokenKind::KwOuter);
871            self.expect_kw(&TokenKind::KwJoin)?;
872            Some(JoinKind::Left)
873        } else if self.eat_kw(&TokenKind::KwRight) {
874            let _ = self.eat_kw(&TokenKind::KwOuter);
875            self.expect_kw(&TokenKind::KwJoin)?;
876            Some(JoinKind::Right)
877        } else if self.eat_kw(&TokenKind::KwFull) {
878            let _ = self.eat_kw(&TokenKind::KwOuter);
879            self.expect_kw(&TokenKind::KwJoin)?;
880            Some(JoinKind::Full)
881        } else {
882            None
883        };
884        match kind {
885            Some(k) => Ok(Some(JoinType { natural, kind: k })),
886            None if natural => Err(self.err_expected("JOIN after NATURAL")),
887            None => Ok(None),
888        }
889    }
890
891    fn parse_join_constraint(&mut self) -> Result<Option<JoinConstraint>, ParseError> {
892        if self.eat_kw(&TokenKind::KwOn) {
893            Ok(Some(JoinConstraint::On(self.parse_expr()?)))
894        } else if self.eat_kw(&TokenKind::KwUsing) {
895            self.expect_token(&TokenKind::LeftParen)?;
896            let cols = self.parse_comma_sep(Self::parse_identifier)?;
897            self.expect_token(&TokenKind::RightParen)?;
898            Ok(Some(JoinConstraint::Using(cols)))
899        } else {
900            Ok(None)
901        }
902    }
903
904    // -----------------------------------------------------------------------
905    // ORDER BY / LIMIT
906    // -----------------------------------------------------------------------
907
908    pub(crate) fn parse_ordering_term(&mut self) -> Result<OrderingTerm, ParseError> {
909        let expr = self.parse_expr()?;
910        let direction = if self.eat_kw(&TokenKind::KwAsc) {
911            Some(SortDirection::Asc)
912        } else if self.eat_kw(&TokenKind::KwDesc) {
913            Some(SortDirection::Desc)
914        } else {
915            None
916        };
917        let nulls = if self.eat_kw(&TokenKind::KwNulls) {
918            if self.eat_kw(&TokenKind::KwFirst) {
919                Some(NullsOrder::First)
920            } else {
921                self.expect_kw(&TokenKind::KwLast)?;
922                Some(NullsOrder::Last)
923            }
924        } else {
925            None
926        };
927        Ok(OrderingTerm {
928            expr,
929            direction,
930            nulls,
931        })
932    }
933
934    pub(crate) fn parse_limit(&mut self) -> Result<Option<LimitClause>, ParseError> {
935        if !self.eat_kw(&TokenKind::KwLimit) {
936            return Ok(None);
937        }
938        let first = self.parse_expr()?;
939        if self.eat_kw(&TokenKind::KwOffset) {
940            return Ok(Some(LimitClause {
941                limit: first,
942                offset: Some(self.parse_expr()?),
943            }));
944        }
945
946        if self.eat(&TokenKind::Comma) {
947            // LIMIT offset, count — SQLite/MySQL compatibility form.
948            let second = self.parse_expr()?;
949            return Ok(Some(LimitClause {
950                limit: second,
951                offset: Some(first),
952            }));
953        }
954
955        Ok(Some(LimitClause {
956            limit: first,
957            offset: None,
958        }))
959    }
960
961    // -----------------------------------------------------------------------
962    // RETURNING clause
963    // -----------------------------------------------------------------------
964
965    fn parse_returning(&mut self) -> Result<Vec<ResultColumn>, ParseError> {
966        if self.eat_kw(&TokenKind::KwReturning) {
967            self.parse_comma_sep(Self::parse_result_column)
968        } else {
969            Ok(vec![])
970        }
971    }
972
973    // -----------------------------------------------------------------------
974    // INSERT
975    // -----------------------------------------------------------------------
976
977    fn parse_insert_stmt(&mut self, with: Option<WithClause>) -> Result<Statement, ParseError> {
978        let or_conflict = if self.eat_kw(&TokenKind::KwReplace) {
979            Some(ConflictAction::Replace)
980        } else {
981            self.expect_kw(&TokenKind::KwInsert)?;
982            if self.eat_kw(&TokenKind::KwOr) {
983                Some(self.parse_conflict_action()?)
984            } else {
985                None
986            }
987        };
988        self.eat_kw(&TokenKind::KwInto);
989        let table = self.parse_qualified_name()?;
990        let alias = if self.eat_kw(&TokenKind::KwAs) {
991            Some(self.parse_identifier()?)
992        } else {
993            None
994        };
995        let columns = if self.check(&TokenKind::LeftParen)
996            && !matches!(
997                self.peek_nth(1),
998                TokenKind::KwSelect | TokenKind::KwWith | TokenKind::KwValues
999            ) {
1000            self.advance();
1001            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1002            self.expect_token(&TokenKind::RightParen)?;
1003            cols
1004        } else {
1005            vec![]
1006        };
1007        let source = if self.eat_kw(&TokenKind::KwDefault) {
1008            self.expect_kw(&TokenKind::KwValues)?;
1009            InsertSource::DefaultValues
1010        } else if self.eat_kw(&TokenKind::KwValues) {
1011            match self.parse_values_core()? {
1012                SelectCore::Values(rows) => InsertSource::Values(rows),
1013                SelectCore::Select { .. } => unreachable!("parse_values_core must return VALUES"),
1014            }
1015        } else {
1016            let inner_with = if self.check_kw(&TokenKind::KwWith) {
1017                Some(self.parse_with_clause()?)
1018            } else {
1019                None
1020            };
1021            InsertSource::Select(Box::new(self.parse_select_stmt(inner_with)?))
1022        };
1023        let upsert = self.parse_upsert_clauses()?;
1024        let returning = self.parse_returning()?;
1025        Ok(Statement::Insert(InsertStatement {
1026            with,
1027            or_conflict,
1028            table,
1029            alias,
1030            columns,
1031            source,
1032            upsert,
1033            returning,
1034        }))
1035    }
1036
1037    fn parse_conflict_action(&mut self) -> Result<ConflictAction, ParseError> {
1038        if self.eat_kw(&TokenKind::KwRollback) {
1039            Ok(ConflictAction::Rollback)
1040        } else if self.eat_kw(&TokenKind::KwAbort) {
1041            Ok(ConflictAction::Abort)
1042        } else if self.eat_kw(&TokenKind::KwFail) {
1043            Ok(ConflictAction::Fail)
1044        } else if self.eat_kw(&TokenKind::KwIgnore) {
1045            Ok(ConflictAction::Ignore)
1046        } else if self.eat_kw(&TokenKind::KwReplace) {
1047            Ok(ConflictAction::Replace)
1048        } else {
1049            Err(self.err_expected("conflict action"))
1050        }
1051    }
1052
1053    fn parse_upsert_clauses(&mut self) -> Result<Vec<UpsertClause>, ParseError> {
1054        let mut clauses = Vec::new();
1055        while self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
1056            self.advance(); // ON
1057            self.advance(); // CONFLICT
1058            let target = if self.check(&TokenKind::LeftParen) {
1059                self.advance();
1060                let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1061                self.expect_token(&TokenKind::RightParen)?;
1062                let wh = if self.eat_kw(&TokenKind::KwWhere) {
1063                    Some(self.parse_expr()?)
1064                } else {
1065                    None
1066                };
1067                Some(UpsertTarget {
1068                    columns,
1069                    where_clause: wh,
1070                })
1071            } else {
1072                None
1073            };
1074            self.expect_kw(&TokenKind::KwDo)?;
1075            let action = if self.eat_kw(&TokenKind::KwNothing) {
1076                UpsertAction::Nothing
1077            } else {
1078                self.expect_kw(&TokenKind::KwUpdate)?;
1079                self.expect_kw(&TokenKind::KwSet)?;
1080                let assignments = self.parse_comma_sep(Self::parse_assignment)?;
1081                let wh = if self.eat_kw(&TokenKind::KwWhere) {
1082                    Some(Box::new(self.parse_expr()?))
1083                } else {
1084                    None
1085                };
1086                UpsertAction::Update {
1087                    assignments,
1088                    where_clause: wh,
1089                }
1090            };
1091            if target.is_none() && matches!(action, UpsertAction::Update { .. }) {
1092                return Err(self.err_msg("ON CONFLICT DO UPDATE requires a conflict target"));
1093            }
1094            clauses.push(UpsertClause { target, action });
1095        }
1096        Ok(clauses)
1097    }
1098
1099    // -----------------------------------------------------------------------
1100    // UPDATE
1101    // -----------------------------------------------------------------------
1102
1103    fn parse_update_stmt(&mut self, with: Option<WithClause>) -> Result<Statement, ParseError> {
1104        self.expect_kw(&TokenKind::KwUpdate)?;
1105        let or_conflict = if self.eat_kw(&TokenKind::KwOr) {
1106            Some(self.parse_conflict_action()?)
1107        } else {
1108            None
1109        };
1110        let table = self.parse_qualified_table_ref()?;
1111        self.expect_kw(&TokenKind::KwSet)?;
1112        let assignments = self.parse_comma_sep(Self::parse_assignment)?;
1113        let from = if self.eat_kw(&TokenKind::KwFrom) {
1114            Some(self.parse_from_clause()?)
1115        } else {
1116            None
1117        };
1118        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1119            Some(self.parse_expr()?)
1120        } else {
1121            None
1122        };
1123        let returning = self.parse_returning()?;
1124        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1125            self.expect_kw(&TokenKind::KwBy)?;
1126            self.parse_comma_sep(Self::parse_ordering_term)?
1127        } else {
1128            vec![]
1129        };
1130        let limit = self.parse_limit()?;
1131        Ok(Statement::Update(UpdateStatement {
1132            with,
1133            or_conflict,
1134            table,
1135            assignments,
1136            from,
1137            where_clause,
1138            returning,
1139            order_by,
1140            limit,
1141        }))
1142    }
1143
1144    fn parse_assignment(&mut self) -> Result<Assignment, ParseError> {
1145        let target = if self.check(&TokenKind::LeftParen) {
1146            self.advance();
1147            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1148            self.expect_token(&TokenKind::RightParen)?;
1149            AssignmentTarget::ColumnList(cols)
1150        } else {
1151            AssignmentTarget::Column(self.parse_identifier()?)
1152        };
1153        self.expect_token(&TokenKind::Eq)?;
1154        let value = self.parse_expr()?;
1155        Ok(Assignment { target, value })
1156    }
1157
1158    // -----------------------------------------------------------------------
1159    // DELETE
1160    // -----------------------------------------------------------------------
1161
1162    fn parse_delete_stmt(&mut self, with: Option<WithClause>) -> Result<Statement, ParseError> {
1163        self.expect_kw(&TokenKind::KwDelete)?;
1164        self.expect_kw(&TokenKind::KwFrom)?;
1165        let table = self.parse_qualified_table_ref()?;
1166        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1167            Some(self.parse_expr()?)
1168        } else {
1169            None
1170        };
1171        let returning = self.parse_returning()?;
1172        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
1173            self.expect_kw(&TokenKind::KwBy)?;
1174            self.parse_comma_sep(Self::parse_ordering_term)?
1175        } else {
1176            vec![]
1177        };
1178        let limit = self.parse_limit()?;
1179        Ok(Statement::Delete(DeleteStatement {
1180            with,
1181            table,
1182            where_clause,
1183            returning,
1184            order_by,
1185            limit,
1186        }))
1187    }
1188
1189    // -----------------------------------------------------------------------
1190    // CREATE
1191    // -----------------------------------------------------------------------
1192
1193    fn parse_create(&mut self) -> Result<Statement, ParseError> {
1194        self.expect_kw(&TokenKind::KwCreate)?;
1195        let temporary = self.eat_kw(&TokenKind::KwTemp) || self.eat_kw(&TokenKind::KwTemporary);
1196        let unique = self.eat_kw(&TokenKind::KwUnique);
1197
1198        if self.eat_kw(&TokenKind::KwTable) {
1199            return self.parse_create_table(temporary);
1200        }
1201        if self.eat_kw(&TokenKind::KwIndex) {
1202            return self.parse_create_index(unique);
1203        }
1204        if self.eat_kw(&TokenKind::KwView) {
1205            return self.parse_create_view(temporary);
1206        }
1207        if self.eat_kw(&TokenKind::KwTrigger) {
1208            return self.parse_create_trigger(temporary);
1209        }
1210        if self.eat_kw(&TokenKind::KwVirtual) {
1211            self.expect_kw(&TokenKind::KwTable)?;
1212            return self.parse_create_virtual_table();
1213        }
1214        Err(self.err_expected("TABLE, INDEX, VIEW, TRIGGER, or VIRTUAL"))
1215    }
1216
1217    fn parse_if_not_exists(&mut self) -> bool {
1218        if self.check_kw(&TokenKind::KwIf)
1219            && self.peek_nth(1) == &TokenKind::KwNot
1220            && self.peek_nth(2) == &TokenKind::KwExists
1221        {
1222            self.advance();
1223            self.advance();
1224            self.advance();
1225            true
1226        } else {
1227            false
1228        }
1229    }
1230
1231    fn parse_create_table(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1232        let if_not_exists = self.parse_if_not_exists();
1233        let name = self.parse_qualified_name()?;
1234        let body = if self.eat_kw(&TokenKind::KwAs) {
1235            let with = if self.check_kw(&TokenKind::KwWith) {
1236                Some(self.parse_with_clause()?)
1237            } else {
1238                None
1239            };
1240            CreateTableBody::AsSelect(Box::new(self.parse_select_stmt(with)?))
1241        } else {
1242            self.expect_token(&TokenKind::LeftParen)?;
1243            let mut columns = Vec::new();
1244            let mut constraints = Vec::new();
1245            loop {
1246                if self.is_table_constraint_start() {
1247                    constraints.push(self.parse_table_constraint()?);
1248                } else {
1249                    columns.push(self.parse_column_def()?);
1250                }
1251                if !self.eat(&TokenKind::Comma) {
1252                    break;
1253                }
1254            }
1255            self.expect_token(&TokenKind::RightParen)?;
1256            CreateTableBody::Columns {
1257                columns,
1258                constraints,
1259            }
1260        };
1261        let mut without_rowid = false;
1262        let mut strict = false;
1263        // Table options after the closing paren.
1264        if self.check_kw(&TokenKind::KwWithout) || self.check_kw(&TokenKind::KwStrict) {
1265            loop {
1266                if self.check_kw(&TokenKind::KwWithout) {
1267                    self.advance();
1268                    // Expect "ROWID" as an identifier.
1269                    let id = self.parse_identifier()?;
1270                    if !id.eq_ignore_ascii_case("ROWID") {
1271                        return Err(self.err_expected("ROWID after WITHOUT"));
1272                    }
1273                    without_rowid = true;
1274                } else if self.eat_kw(&TokenKind::KwStrict) {
1275                    strict = true;
1276                } else {
1277                    return Err(self.err_expected("table option"));
1278                }
1279                if !self.eat(&TokenKind::Comma) {
1280                    break;
1281                }
1282            }
1283        }
1284        Ok(Statement::CreateTable(CreateTableStatement {
1285            if_not_exists,
1286            temporary,
1287            name,
1288            body,
1289            without_rowid,
1290            strict,
1291        }))
1292    }
1293
1294    fn is_table_constraint_start(&self) -> bool {
1295        matches!(
1296            self.peek(),
1297            TokenKind::KwPrimary | TokenKind::KwUnique | TokenKind::KwCheck | TokenKind::KwForeign
1298        ) || (self.check_kw(&TokenKind::KwConstraint))
1299    }
1300
1301    fn parse_column_def(&mut self) -> Result<ColumnDef, ParseError> {
1302        let name = self.parse_identifier()?;
1303        let type_name = self.try_type_name()?;
1304        let mut constraints = Vec::new();
1305        while let Some(c) = self.try_column_constraint()? {
1306            constraints.push(c);
1307        }
1308        Ok(ColumnDef {
1309            name,
1310            type_name,
1311            constraints,
1312        })
1313    }
1314
1315    fn try_type_name(&mut self) -> Result<Option<TypeName>, ParseError> {
1316        // Type name is one or more identifiers, stopping at known boundaries.
1317        if self.is_column_constraint_start()
1318            || matches!(
1319                self.peek(),
1320                TokenKind::Comma | TokenKind::RightParen | TokenKind::Eof
1321            )
1322        {
1323            return Ok(None);
1324        }
1325        // Collect type name words.
1326        let mut words = Vec::new();
1327        loop {
1328            match self.peek() {
1329                TokenKind::Id(_) | TokenKind::QuotedId(_, _) => {
1330                    words.push(self.parse_identifier()?);
1331                }
1332                k if is_nonreserved_kw(k) => {
1333                    words.push(self.parse_identifier()?);
1334                }
1335                _ => break,
1336            }
1337            if self.is_column_constraint_start()
1338                || matches!(
1339                    self.peek(),
1340                    TokenKind::Comma | TokenKind::RightParen | TokenKind::LeftParen
1341                )
1342            {
1343                break;
1344            }
1345        }
1346        if words.is_empty() {
1347            return Ok(None);
1348        }
1349        let type_name = words.join(" ");
1350        let (arg1, arg2) = if self.eat(&TokenKind::LeftParen) {
1351            let a1 = self.parse_signed_number_str()?;
1352            let a2 = if self.eat(&TokenKind::Comma) {
1353                Some(self.parse_signed_number_str()?)
1354            } else {
1355                None
1356            };
1357            self.expect_token(&TokenKind::RightParen)?;
1358            (Some(a1), a2)
1359        } else {
1360            (None, None)
1361        };
1362        Ok(Some(TypeName {
1363            name: type_name,
1364            arg1,
1365            arg2,
1366        }))
1367    }
1368
1369    fn parse_signed_number_str(&mut self) -> Result<String, ParseError> {
1370        let neg = self.eat(&TokenKind::Minus);
1371        let plus = if neg {
1372            false
1373        } else {
1374            self.eat(&TokenKind::Plus)
1375        };
1376        let _ = plus; // just consume
1377        match self.peek().clone() {
1378            TokenKind::Integer(n) => {
1379                self.advance();
1380                Ok(if neg { format!("-{n}") } else { n.to_string() })
1381            }
1382            TokenKind::OversizedInt(s) => {
1383                self.advance();
1384                Ok(if neg { format!("-{s}") } else { s.clone() })
1385            }
1386            TokenKind::Float(f) => {
1387                self.advance();
1388                Ok(if neg { format!("-{f}") } else { f.to_string() })
1389            }
1390            _ => Err(self.err_expected("number")),
1391        }
1392    }
1393
1394    fn is_column_constraint_start(&self) -> bool {
1395        matches!(
1396            self.peek(),
1397            TokenKind::KwPrimary
1398                | TokenKind::KwNot
1399                | TokenKind::KwNull
1400                | TokenKind::KwUnique
1401                | TokenKind::KwCheck
1402                | TokenKind::KwDefault
1403                | TokenKind::KwCollate
1404                | TokenKind::KwReferences
1405                | TokenKind::KwGenerated
1406                | TokenKind::KwConstraint
1407                | TokenKind::KwAs
1408        )
1409    }
1410
1411    fn try_column_constraint(&mut self) -> Result<Option<ColumnConstraint>, ParseError> {
1412        let name = if self.eat_kw(&TokenKind::KwConstraint) {
1413            Some(self.parse_identifier()?)
1414        } else {
1415            None
1416        };
1417        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
1418            self.expect_kw(&TokenKind::KwKey)?;
1419            let direction = if self.eat_kw(&TokenKind::KwAsc) {
1420                Some(SortDirection::Asc)
1421            } else if self.eat_kw(&TokenKind::KwDesc) {
1422                Some(SortDirection::Desc)
1423            } else {
1424                None
1425            };
1426            let conflict = self.parse_on_conflict()?;
1427            let autoincrement = self.eat_kw(&TokenKind::KwAutoincrement);
1428            ColumnConstraintKind::PrimaryKey {
1429                direction,
1430                conflict,
1431                autoincrement,
1432            }
1433        } else if self.check_kw(&TokenKind::KwNot) && self.peek_nth(1) == &TokenKind::KwNull {
1434            self.advance();
1435            self.advance();
1436            let conflict = self.parse_on_conflict()?;
1437            ColumnConstraintKind::NotNull { conflict }
1438        } else if self.eat_kw(&TokenKind::KwNull) {
1439            ColumnConstraintKind::Null
1440        } else if self.eat_kw(&TokenKind::KwUnique) {
1441            let conflict = self.parse_on_conflict()?;
1442            ColumnConstraintKind::Unique { conflict }
1443        } else if self.eat_kw(&TokenKind::KwCheck) {
1444            self.expect_token(&TokenKind::LeftParen)?;
1445            let expr = self.parse_expr()?;
1446            self.expect_token(&TokenKind::RightParen)?;
1447            ColumnConstraintKind::Check(expr)
1448        } else if self.eat_kw(&TokenKind::KwDefault) {
1449            if self.eat(&TokenKind::LeftParen) {
1450                let expr = self.parse_expr()?;
1451                self.expect_token(&TokenKind::RightParen)?;
1452                ColumnConstraintKind::Default(DefaultValue::ParenExpr(expr))
1453            } else {
1454                let expr = self.parse_expr()?;
1455                ColumnConstraintKind::Default(DefaultValue::Expr(expr))
1456            }
1457        } else if self.eat_kw(&TokenKind::KwCollate) {
1458            ColumnConstraintKind::Collate(self.parse_identifier()?)
1459        } else if self.eat_kw(&TokenKind::KwReferences) {
1460            ColumnConstraintKind::ForeignKey(self.parse_fk_clause()?)
1461        } else if self.eat_kw(&TokenKind::KwGenerated) || self.eat_kw(&TokenKind::KwAs) {
1462            if self.tokens[self.pos.saturating_sub(1)].kind == TokenKind::KwGenerated {
1463                let _ = self.eat_kw(&TokenKind::KwAlways);
1464                let _ = self.eat_kw(&TokenKind::KwAs);
1465            }
1466            self.expect_token(&TokenKind::LeftParen)?;
1467            let expr = self.parse_expr()?;
1468            self.expect_token(&TokenKind::RightParen)?;
1469            let storage = if self.eat_kw(&TokenKind::KwStored) {
1470                Some(GeneratedStorage::Stored)
1471            } else if self.eat_kw(&TokenKind::KwVirtual) {
1472                Some(GeneratedStorage::Virtual)
1473            } else {
1474                None
1475            };
1476            ColumnConstraintKind::Generated { expr, storage }
1477        } else if name.is_some() {
1478            return Err(self.err_expected("constraint kind after CONSTRAINT name"));
1479        } else {
1480            return Ok(None);
1481        };
1482        Ok(Some(ColumnConstraint { name, kind }))
1483    }
1484
1485    fn parse_on_conflict(&mut self) -> Result<Option<ConflictAction>, ParseError> {
1486        if self.check_kw(&TokenKind::KwOn) && self.peek_nth(1) == &TokenKind::KwConflict {
1487            self.advance();
1488            self.advance();
1489            Ok(Some(self.parse_conflict_action()?))
1490        } else {
1491            Ok(None)
1492        }
1493    }
1494
1495    fn parse_fk_clause(&mut self) -> Result<ForeignKeyClause, ParseError> {
1496        let table = self.parse_identifier()?;
1497        let columns = if self.eat(&TokenKind::LeftParen) {
1498            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1499            self.expect_token(&TokenKind::RightParen)?;
1500            cols
1501        } else {
1502            vec![]
1503        };
1504        let mut actions = Vec::new();
1505        let mut deferrable = None;
1506        loop {
1507            if self.check_kw(&TokenKind::KwOn) {
1508                self.advance();
1509                let trigger = if self.eat_kw(&TokenKind::KwDelete) {
1510                    ForeignKeyTrigger::OnDelete
1511                } else {
1512                    self.expect_kw(&TokenKind::KwUpdate)?;
1513                    ForeignKeyTrigger::OnUpdate
1514                };
1515                let action = self.parse_fk_action_type()?;
1516                actions.push(ForeignKeyAction { trigger, action });
1517            } else if self.check_kw(&TokenKind::KwNot) || self.check_kw(&TokenKind::KwDeferrable) {
1518                let not = self.eat_kw(&TokenKind::KwNot);
1519                self.expect_kw(&TokenKind::KwDeferrable)?;
1520                let initially = if self.eat_kw(&TokenKind::KwInitially) {
1521                    if self.eat_kw(&TokenKind::KwDeferred) {
1522                        Some(DeferrableInitially::Deferred)
1523                    } else {
1524                        self.expect_kw(&TokenKind::KwImmediate)?;
1525                        Some(DeferrableInitially::Immediate)
1526                    }
1527                } else {
1528                    None
1529                };
1530                deferrable = Some(Deferrable { not, initially });
1531            } else if self.eat_kw(&TokenKind::KwMatch) {
1532                // MATCH name — parsed but ignored per SQLite behavior.
1533                self.parse_identifier()?;
1534            } else {
1535                break;
1536            }
1537        }
1538        Ok(ForeignKeyClause {
1539            table,
1540            columns,
1541            actions,
1542            deferrable,
1543        })
1544    }
1545
1546    fn parse_fk_action_type(&mut self) -> Result<ForeignKeyActionType, ParseError> {
1547        if self.eat_kw(&TokenKind::KwSet) {
1548            if self.eat_kw(&TokenKind::KwNull) {
1549                Ok(ForeignKeyActionType::SetNull)
1550            } else {
1551                self.expect_kw(&TokenKind::KwDefault)?;
1552                Ok(ForeignKeyActionType::SetDefault)
1553            }
1554        } else if self.eat_kw(&TokenKind::KwCascade) {
1555            Ok(ForeignKeyActionType::Cascade)
1556        } else if self.eat_kw(&TokenKind::KwRestrict) {
1557            Ok(ForeignKeyActionType::Restrict)
1558        } else if self.check_kw(&TokenKind::KwNo) {
1559            self.advance();
1560            let id = self.parse_identifier()?;
1561            if !id.eq_ignore_ascii_case("ACTION") {
1562                return Err(self.err_expected("ACTION after NO"));
1563            }
1564            Ok(ForeignKeyActionType::NoAction)
1565        } else {
1566            Err(self.err_expected("foreign key action"))
1567        }
1568    }
1569
1570    fn parse_table_constraint(&mut self) -> Result<TableConstraint, ParseError> {
1571        let name = if self.eat_kw(&TokenKind::KwConstraint) {
1572            Some(self.parse_identifier()?)
1573        } else {
1574            None
1575        };
1576        let kind = if self.eat_kw(&TokenKind::KwPrimary) {
1577            self.expect_kw(&TokenKind::KwKey)?;
1578            self.expect_token(&TokenKind::LeftParen)?;
1579            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1580            self.expect_token(&TokenKind::RightParen)?;
1581            let conflict = self.parse_on_conflict()?;
1582            TableConstraintKind::PrimaryKey { columns, conflict }
1583        } else if self.eat_kw(&TokenKind::KwUnique) {
1584            self.expect_token(&TokenKind::LeftParen)?;
1585            let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1586            self.expect_token(&TokenKind::RightParen)?;
1587            let conflict = self.parse_on_conflict()?;
1588            TableConstraintKind::Unique { columns, conflict }
1589        } else if self.eat_kw(&TokenKind::KwCheck) {
1590            self.expect_token(&TokenKind::LeftParen)?;
1591            let expr = self.parse_expr()?;
1592            self.expect_token(&TokenKind::RightParen)?;
1593            TableConstraintKind::Check(expr)
1594        } else if self.eat_kw(&TokenKind::KwForeign) {
1595            self.expect_kw(&TokenKind::KwKey)?;
1596            self.expect_token(&TokenKind::LeftParen)?;
1597            let columns = self.parse_comma_sep(Self::parse_identifier)?;
1598            self.expect_token(&TokenKind::RightParen)?;
1599            self.expect_kw(&TokenKind::KwReferences)?;
1600            let clause = self.parse_fk_clause()?;
1601            TableConstraintKind::ForeignKey { columns, clause }
1602        } else {
1603            return Err(self.err_expected("table constraint"));
1604        };
1605        Ok(TableConstraint { name, kind })
1606    }
1607
1608    fn parse_indexed_column(&mut self) -> Result<IndexedColumn, ParseError> {
1609        let expr = self.parse_expr()?;
1610        let collation = if self.eat_kw(&TokenKind::KwCollate) {
1611            Some(self.parse_identifier()?)
1612        } else {
1613            None
1614        };
1615        let direction = if self.eat_kw(&TokenKind::KwAsc) {
1616            Some(SortDirection::Asc)
1617        } else if self.eat_kw(&TokenKind::KwDesc) {
1618            Some(SortDirection::Desc)
1619        } else {
1620            None
1621        };
1622        Ok(IndexedColumn {
1623            expr,
1624            collation,
1625            direction,
1626        })
1627    }
1628
1629    fn parse_create_index(&mut self, unique: bool) -> Result<Statement, ParseError> {
1630        let if_not_exists = self.parse_if_not_exists();
1631        let name = self.parse_qualified_name()?;
1632        self.expect_kw(&TokenKind::KwOn)?;
1633        let table = self.parse_identifier()?;
1634        self.expect_token(&TokenKind::LeftParen)?;
1635        let columns = self.parse_comma_sep(Self::parse_indexed_column)?;
1636        self.expect_token(&TokenKind::RightParen)?;
1637        let where_clause = if self.eat_kw(&TokenKind::KwWhere) {
1638            Some(self.parse_expr()?)
1639        } else {
1640            None
1641        };
1642        Ok(Statement::CreateIndex(CreateIndexStatement {
1643            unique,
1644            if_not_exists,
1645            name,
1646            table,
1647            columns,
1648            where_clause,
1649        }))
1650    }
1651
1652    fn parse_create_view(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1653        let if_not_exists = self.parse_if_not_exists();
1654        let name = self.parse_qualified_name()?;
1655        let columns = if self.check(&TokenKind::LeftParen) {
1656            self.advance();
1657            let cols = self.parse_comma_sep(Self::parse_identifier)?;
1658            self.expect_token(&TokenKind::RightParen)?;
1659            cols
1660        } else {
1661            vec![]
1662        };
1663        self.expect_kw(&TokenKind::KwAs)?;
1664        let with = if self.check_kw(&TokenKind::KwWith) {
1665            Some(self.parse_with_clause()?)
1666        } else {
1667            None
1668        };
1669        let query = self.parse_select_stmt(with)?;
1670        Ok(Statement::CreateView(CreateViewStatement {
1671            if_not_exists,
1672            temporary,
1673            name,
1674            columns,
1675            query,
1676        }))
1677    }
1678
1679    fn parse_create_trigger(&mut self, temporary: bool) -> Result<Statement, ParseError> {
1680        let if_not_exists = self.parse_if_not_exists();
1681        let name = self.parse_qualified_name()?;
1682        let timing = if self.eat_kw(&TokenKind::KwBefore) {
1683            TriggerTiming::Before
1684        } else if self.eat_kw(&TokenKind::KwAfter) {
1685            TriggerTiming::After
1686        } else if self.eat_kw(&TokenKind::KwInstead) {
1687            self.expect_kw(&TokenKind::KwOf)?;
1688            TriggerTiming::InsteadOf
1689        } else {
1690            TriggerTiming::Before // default
1691        };
1692        let event = if self.eat_kw(&TokenKind::KwInsert) {
1693            TriggerEvent::Insert
1694        } else if self.eat_kw(&TokenKind::KwDelete) {
1695            TriggerEvent::Delete
1696        } else {
1697            self.expect_kw(&TokenKind::KwUpdate)?;
1698            let cols = if self.eat_kw(&TokenKind::KwOf) {
1699                self.parse_comma_sep(Self::parse_identifier)?
1700            } else {
1701                vec![]
1702            };
1703            TriggerEvent::Update(cols)
1704        };
1705        self.expect_kw(&TokenKind::KwOn)?;
1706        let table = self.parse_identifier()?;
1707        let for_each_row = if self.eat_kw(&TokenKind::KwFor) {
1708            self.expect_kw(&TokenKind::KwEach)?;
1709            self.expect_kw(&TokenKind::KwRow)?;
1710            true
1711        } else {
1712            false
1713        };
1714        let when = if self.eat_kw(&TokenKind::KwWhen) {
1715            Some(self.parse_expr()?)
1716        } else {
1717            None
1718        };
1719        self.expect_kw(&TokenKind::KwBegin)?;
1720        let mut body = Vec::new();
1721        loop {
1722            if self.check_kw(&TokenKind::KwEnd) {
1723                break;
1724            }
1725            let stmt = match self.parse_statement_inner() {
1726                Ok(stmt) => stmt,
1727                Err(err) => {
1728                    self.recover_trigger_body_after_error();
1729                    return Err(err);
1730                }
1731            };
1732            body.push(stmt);
1733            let _ = self.eat(&TokenKind::Semicolon);
1734        }
1735        self.expect_kw(&TokenKind::KwEnd)?;
1736        Ok(Statement::CreateTrigger(CreateTriggerStatement {
1737            if_not_exists,
1738            temporary,
1739            name,
1740            timing,
1741            event,
1742            table,
1743            for_each_row,
1744            when,
1745            body,
1746        }))
1747    }
1748
1749    fn parse_create_virtual_table(&mut self) -> Result<Statement, ParseError> {
1750        let if_not_exists = self.parse_if_not_exists();
1751        let name = self.parse_qualified_name()?;
1752        self.expect_kw(&TokenKind::KwUsing)?;
1753        let module = self.parse_identifier()?;
1754        let args = if self.eat(&TokenKind::LeftParen) {
1755            if self.check(&TokenKind::RightParen) {
1756                self.advance();
1757                vec![]
1758            } else {
1759                // Virtual table args are opaque; collect tokens as strings until matching rparen.
1760                let mut args = Vec::new();
1761                let mut depth = 0i32;
1762                let mut current_arg = String::new();
1763                loop {
1764                    match self.peek() {
1765                        TokenKind::RightParen if depth == 0 => {
1766                            self.advance();
1767                            args.push(current_arg.trim().to_owned());
1768                            break;
1769                        }
1770                        TokenKind::LeftParen => {
1771                            depth += 1;
1772                            current_arg.push('(');
1773                            self.advance();
1774                        }
1775                        TokenKind::RightParen => {
1776                            depth -= 1;
1777                            current_arg.push(')');
1778                            self.advance();
1779                        }
1780                        TokenKind::Comma if depth == 0 => {
1781                            args.push(current_arg.trim().to_owned());
1782                            current_arg = String::new();
1783                            self.advance();
1784                        }
1785                        TokenKind::Eof => {
1786                            return Err(self.err_expected("closing parenthesis"));
1787                        }
1788                        _ => {
1789                            // Reconstruct token text from token kind.
1790                            let t = self
1791                                .current()
1792                                .ok_or_else(|| self.err_expected("virtual table argument token"))?;
1793                            let text = t.kind.to_sql();
1794                            if !current_arg.is_empty()
1795                                && !current_arg.ends_with(' ')
1796                                && !text.is_empty()
1797                            {
1798                                current_arg.push(' ');
1799                            }
1800                            current_arg.push_str(&text);
1801                            self.advance();
1802                        }
1803                    }
1804                }
1805                args
1806            }
1807        } else {
1808            vec![]
1809        };
1810        Ok(Statement::CreateVirtualTable(CreateVirtualTableStatement {
1811            if_not_exists,
1812            name,
1813            module,
1814            args,
1815        }))
1816    }
1817
1818    // -----------------------------------------------------------------------
1819    // DROP
1820    // -----------------------------------------------------------------------
1821
1822    fn parse_drop(&mut self) -> Result<Statement, ParseError> {
1823        self.expect_kw(&TokenKind::KwDrop)?;
1824        let object_type = if self.eat_kw(&TokenKind::KwTable) {
1825            DropObjectType::Table
1826        } else if self.eat_kw(&TokenKind::KwView) {
1827            DropObjectType::View
1828        } else if self.eat_kw(&TokenKind::KwIndex) {
1829            DropObjectType::Index
1830        } else if self.eat_kw(&TokenKind::KwTrigger) {
1831            DropObjectType::Trigger
1832        } else {
1833            return Err(self.err_expected("TABLE, VIEW, INDEX, or TRIGGER"));
1834        };
1835        let if_exists =
1836            if self.check_kw(&TokenKind::KwIf) && self.peek_nth(1) == &TokenKind::KwExists {
1837                self.advance();
1838                self.advance();
1839                true
1840            } else {
1841                false
1842            };
1843        let name = self.parse_qualified_name()?;
1844        Ok(Statement::Drop(DropStatement {
1845            object_type,
1846            if_exists,
1847            name,
1848        }))
1849    }
1850
1851    // -----------------------------------------------------------------------
1852    // ALTER TABLE
1853    // -----------------------------------------------------------------------
1854
1855    fn parse_alter(&mut self) -> Result<Statement, ParseError> {
1856        self.expect_kw(&TokenKind::KwAlter)?;
1857        self.expect_kw(&TokenKind::KwTable)?;
1858        let table = self.parse_qualified_name()?;
1859        let action = if self.eat_kw(&TokenKind::KwRename) {
1860            if self.eat_kw(&TokenKind::KwTo) {
1861                AlterTableAction::RenameTo(self.parse_identifier()?)
1862            } else {
1863                let _ = self.eat_kw(&TokenKind::KwColumn);
1864                let old = self.parse_identifier()?;
1865                self.expect_kw(&TokenKind::KwTo)?;
1866                let new = self.parse_identifier()?;
1867                AlterTableAction::RenameColumn { old, new }
1868            }
1869        } else if self.eat_kw(&TokenKind::KwAdd) {
1870            let _ = self.eat_kw(&TokenKind::KwColumn);
1871            AlterTableAction::AddColumn(self.parse_column_def()?)
1872        } else if self.eat_kw(&TokenKind::KwDrop) {
1873            let _ = self.eat_kw(&TokenKind::KwColumn);
1874            AlterTableAction::DropColumn(self.parse_identifier()?)
1875        } else {
1876            return Err(self.err_expected("RENAME, ADD, or DROP"));
1877        };
1878        Ok(Statement::AlterTable(AlterTableStatement { table, action }))
1879    }
1880
1881    // -----------------------------------------------------------------------
1882    // Transaction control
1883    // -----------------------------------------------------------------------
1884
1885    fn parse_begin(&mut self) -> Result<Statement, ParseError> {
1886        self.expect_kw(&TokenKind::KwBegin)?;
1887        let mode = if self.eat_kw(&TokenKind::KwDeferred) {
1888            Some(TransactionMode::Deferred)
1889        } else if self.eat_kw(&TokenKind::KwImmediate) {
1890            Some(TransactionMode::Immediate)
1891        } else if self.eat_kw(&TokenKind::KwExclusive) {
1892            Some(TransactionMode::Exclusive)
1893        } else if self.eat_kw(&TokenKind::KwConcurrent) {
1894            Some(TransactionMode::Concurrent)
1895        } else {
1896            None
1897        };
1898        // Optional TRANSACTION keyword.
1899        let _ = self.eat_kw(&TokenKind::KwTransaction);
1900        Ok(Statement::Begin(BeginStatement { mode }))
1901    }
1902
1903    fn parse_rollback(&mut self) -> Result<Statement, ParseError> {
1904        self.expect_kw(&TokenKind::KwRollback)?;
1905        let _ = self.eat_kw(&TokenKind::KwTransaction);
1906        let to_savepoint = if self.eat_kw(&TokenKind::KwTo) {
1907            let _ = self.eat_kw(&TokenKind::KwSavepoint);
1908            Some(self.parse_identifier()?)
1909        } else {
1910            None
1911        };
1912        Ok(Statement::Rollback(RollbackStatement { to_savepoint }))
1913    }
1914
1915    // -----------------------------------------------------------------------
1916    // ATTACH / PRAGMA / VACUUM / EXPLAIN
1917    // -----------------------------------------------------------------------
1918
1919    fn parse_attach(&mut self) -> Result<Statement, ParseError> {
1920        self.expect_kw(&TokenKind::KwAttach)?;
1921        let _ = self.eat_kw(&TokenKind::KwDatabase);
1922        let expr = self.parse_expr()?;
1923        self.expect_kw(&TokenKind::KwAs)?;
1924        let schema = self.parse_identifier()?;
1925        Ok(Statement::Attach(AttachStatement { expr, schema }))
1926    }
1927
1928    fn parse_pragma_value_expr(&mut self) -> Result<Expr, ParseError> {
1929        // SQLite allows ON/OFF for many boolean pragmas. Treat `ON` as `TRUE`
1930        // in PRAGMA value position (OFF is tokenized as an identifier, so the
1931        // regular expression parser handles it).
1932        if self.check_kw(&TokenKind::KwOn) {
1933            let sp = self.current_span();
1934            self.advance();
1935            return Ok(Expr::Literal(Literal::True, sp));
1936        }
1937        self.parse_expr()
1938    }
1939
1940    fn parse_pragma(&mut self) -> Result<Statement, ParseError> {
1941        self.expect_kw(&TokenKind::KwPragma)?;
1942        let name = self.parse_qualified_name()?;
1943        let value = if self.eat(&TokenKind::Eq) || self.eat(&TokenKind::EqEq) {
1944            Some(PragmaValue::Assign(self.parse_pragma_value_expr()?))
1945        } else if self.eat(&TokenKind::LeftParen) {
1946            let v = self.parse_pragma_value_expr()?;
1947            self.expect_token(&TokenKind::RightParen)?;
1948            Some(PragmaValue::Call(v))
1949        } else {
1950            None
1951        };
1952        Ok(Statement::Pragma(PragmaStatement { name, value }))
1953    }
1954
1955    fn parse_vacuum(&mut self) -> Result<Statement, ParseError> {
1956        self.expect_kw(&TokenKind::KwVacuum)?;
1957        let schema = if !self.at_eof()
1958            && !self.check(&TokenKind::Semicolon)
1959            && !self.check_kw(&TokenKind::KwInto)
1960        {
1961            Some(self.parse_identifier()?)
1962        } else {
1963            None
1964        };
1965        let into = if self.eat_kw(&TokenKind::KwInto) {
1966            Some(self.parse_expr()?)
1967        } else {
1968            None
1969        };
1970        Ok(Statement::Vacuum(VacuumStatement { schema, into }))
1971    }
1972
1973    fn parse_explain(&mut self) -> Result<Statement, ParseError> {
1974        self.expect_kw(&TokenKind::KwExplain)?;
1975        let query_plan = if self.eat_kw(&TokenKind::KwQuery) {
1976            self.expect_kw(&TokenKind::KwPlan)?;
1977            true
1978        } else {
1979            false
1980        };
1981        let stmt = self.parse_statement_inner()?;
1982        Ok(Statement::Explain {
1983            query_plan,
1984            stmt: Box::new(stmt),
1985        })
1986    }
1987
1988    // -----------------------------------------------------------------------
1989    // Window definitions (used in SELECT ... WINDOW clause and OVER)
1990    // -----------------------------------------------------------------------
1991
1992    fn parse_window_def(&mut self) -> Result<WindowDef, ParseError> {
1993        let name = self.parse_identifier()?;
1994        self.expect_kw(&TokenKind::KwAs)?;
1995        self.expect_token(&TokenKind::LeftParen)?;
1996        let spec = self.parse_window_spec()?;
1997        self.expect_token(&TokenKind::RightParen)?;
1998        Ok(WindowDef { name, spec })
1999    }
2000
2001    pub(crate) fn parse_window_spec(&mut self) -> Result<WindowSpec, ParseError> {
2002        // Optional base window name.
2003        let has_base_window = match self.peek() {
2004            TokenKind::Id(_) | TokenKind::QuotedId(_, _) => true,
2005            k if is_nonreserved_kw(k) => !matches!(
2006                k,
2007                TokenKind::KwPartition
2008                    | TokenKind::KwOrder
2009                    | TokenKind::KwRange
2010                    | TokenKind::KwRows
2011                    | TokenKind::KwGroups
2012            ),
2013            _ => false,
2014        };
2015        let base_window = if has_base_window {
2016            Some(self.parse_identifier()?)
2017        } else {
2018            None
2019        };
2020        let partition_by = if self.eat_kw(&TokenKind::KwPartition) {
2021            self.expect_kw(&TokenKind::KwBy)?;
2022            self.parse_comma_sep(Self::parse_expr)?
2023        } else {
2024            vec![]
2025        };
2026        let order_by = if self.eat_kw(&TokenKind::KwOrder) {
2027            self.expect_kw(&TokenKind::KwBy)?;
2028            self.parse_comma_sep(Self::parse_ordering_term)?
2029        } else {
2030            vec![]
2031        };
2032        let frame = self.try_frame_spec()?;
2033        Ok(WindowSpec {
2034            base_window,
2035            partition_by,
2036            order_by,
2037            frame,
2038        })
2039    }
2040
2041    fn try_frame_spec(&mut self) -> Result<Option<FrameSpec>, ParseError> {
2042        let frame_type = if self.eat_kw(&TokenKind::KwRows) {
2043            FrameType::Rows
2044        } else if self.eat_kw(&TokenKind::KwRange) {
2045            FrameType::Range
2046        } else if self.eat_kw(&TokenKind::KwGroups) {
2047            FrameType::Groups
2048        } else {
2049            return Ok(None);
2050        };
2051        let (start, end) = if self.eat_kw(&TokenKind::KwBetween) {
2052            let s = self.parse_frame_bound()?;
2053            self.expect_kw(&TokenKind::KwAnd)?;
2054            let e = self.parse_frame_bound()?;
2055            (s, Some(e))
2056        } else {
2057            (self.parse_frame_bound()?, None)
2058        };
2059        let exclude = if self.eat_kw(&TokenKind::KwExclude) {
2060            if self.check_kw(&TokenKind::KwNo) {
2061                self.advance();
2062                // "NO OTHERS"
2063                let id = self.parse_identifier()?;
2064                if !id.eq_ignore_ascii_case("OTHERS") {
2065                    return Err(self.err_expected("OTHERS"));
2066                }
2067                Some(FrameExclude::NoOthers)
2068            } else if self.eat_kw(&TokenKind::KwTies) {
2069                Some(FrameExclude::Ties)
2070            } else if self.eat_kw(&TokenKind::KwGroup) {
2071                Some(FrameExclude::Group)
2072            } else if matches!(self.peek(), TokenKind::Id(s) if s.eq_ignore_ascii_case("CURRENT")) {
2073                self.advance();
2074                self.expect_kw(&TokenKind::KwRow)?;
2075                Some(FrameExclude::CurrentRow)
2076            } else {
2077                return Err(
2078                    self.err_expected("NO OTHERS, TIES, GROUP, or CURRENT ROW after EXCLUDE")
2079                );
2080            }
2081        } else {
2082            None
2083        };
2084        Ok(Some(FrameSpec {
2085            frame_type,
2086            start,
2087            end,
2088            exclude,
2089        }))
2090    }
2091
2092    fn parse_frame_bound(&mut self) -> Result<FrameBound, ParseError> {
2093        if self.eat_kw(&TokenKind::KwUnbounded) {
2094            if self.eat_kw(&TokenKind::KwPreceding) {
2095                Ok(FrameBound::UnboundedPreceding)
2096            } else {
2097                self.expect_kw(&TokenKind::KwFollowing)?;
2098                Ok(FrameBound::UnboundedFollowing)
2099            }
2100        } else if matches!(self.peek(), TokenKind::Id(s) if s.eq_ignore_ascii_case("CURRENT")) {
2101            self.advance();
2102            self.expect_kw(&TokenKind::KwRow)?;
2103            Ok(FrameBound::CurrentRow)
2104        } else {
2105            let expr = self.parse_expr()?;
2106            if self.eat_kw(&TokenKind::KwPreceding) {
2107                Ok(FrameBound::Preceding(Box::new(expr)))
2108            } else {
2109                self.expect_kw(&TokenKind::KwFollowing)?;
2110                Ok(FrameBound::Following(Box::new(expr)))
2111            }
2112        }
2113    }
2114}
2115
2116/// Parse only the first top-level SQL statement from `sql` and report the byte
2117/// offset immediately after the consumed statement text.
2118///
2119/// Returns `Ok(None)` when the input contains no statement text (for example
2120/// whitespace, comments, or empty statements only). The returned tail offset is
2121/// suitable for `sqlite3_prepare_v2`-style APIs that must leave any remaining
2122/// SQL untouched.
2123pub fn parse_first_statement_with_tail(
2124    sql: &str,
2125) -> Result<Option<(Statement, usize)>, ParseError> {
2126    let mut parser = Parser::from_sql(sql);
2127
2128    while parser.eat(&TokenKind::Semicolon) {}
2129    if parser.at_eof() {
2130        return Ok(None);
2131    }
2132
2133    let statement = parser.parse_statement()?;
2134    let tail_offset = if parser.eat(&TokenKind::Semicolon) {
2135        parser
2136            .tokens
2137            .get(parser.pos.saturating_sub(1))
2138            .map_or(sql.len(), |token| token.span.end as usize)
2139    } else if parser.at_eof() {
2140        sql.len()
2141    } else {
2142        return Err(ParseError::at(
2143            "unexpected token after end of statement; expected ';' separator",
2144            parser.current(),
2145        ));
2146    };
2147
2148    Ok(Some((statement, tail_offset)))
2149}
2150
2151// ---------------------------------------------------------------------------
2152// Keyword classification helper
2153// ---------------------------------------------------------------------------
2154
2155pub(crate) fn is_nonreserved_kw(k: &TokenKind) -> bool {
2156    matches!(
2157        k,
2158        TokenKind::KwAbort
2159            | TokenKind::KwAction
2160            | TokenKind::KwAfter
2161            | TokenKind::KwAlways
2162            | TokenKind::KwAnalyze
2163            | TokenKind::KwAsc
2164            | TokenKind::KwBefore
2165            | TokenKind::KwCascade
2166            | TokenKind::KwColumn
2167            | TokenKind::KwConcurrent
2168            | TokenKind::KwConflict
2169            | TokenKind::KwDatabase
2170            | TokenKind::KwDeferred
2171            | TokenKind::KwDesc
2172            | TokenKind::KwDo
2173            | TokenKind::KwEach
2174            | TokenKind::KwEnd
2175            | TokenKind::KwExclude
2176            | TokenKind::KwExclusive
2177            | TokenKind::KwFail
2178            | TokenKind::KwFilter
2179            | TokenKind::KwFirst
2180            | TokenKind::KwFollowing
2181            | TokenKind::KwFull
2182            | TokenKind::KwGenerated
2183            | TokenKind::KwGroups
2184            | TokenKind::KwIf
2185            | TokenKind::KwIgnore
2186            | TokenKind::KwImmediate
2187            | TokenKind::KwIndex
2188            | TokenKind::KwInitially
2189            | TokenKind::KwInstead
2190            | TokenKind::KwKey
2191            | TokenKind::KwLast
2192            | TokenKind::KwMatch
2193            | TokenKind::KwMaterialized
2194            | TokenKind::KwNo
2195            | TokenKind::KwNothing
2196            | TokenKind::KwNulls
2197            | TokenKind::KwOf
2198            | TokenKind::KwOffset
2199            | TokenKind::KwOthers
2200            | TokenKind::KwOver
2201            | TokenKind::KwPartition
2202            | TokenKind::KwPlan
2203            | TokenKind::KwPragma
2204            | TokenKind::KwPreceding
2205            | TokenKind::KwQuery
2206            | TokenKind::KwRange
2207            | TokenKind::KwRecursive
2208            | TokenKind::KwReindex
2209            | TokenKind::KwRelease
2210            | TokenKind::KwRename
2211            | TokenKind::KwReplace
2212            | TokenKind::KwRestrict
2213            | TokenKind::KwReturning
2214            | TokenKind::KwRow
2215            | TokenKind::KwRows
2216            | TokenKind::KwSavepoint
2217            | TokenKind::KwStored
2218            | TokenKind::KwStrict
2219            | TokenKind::KwTable
2220            | TokenKind::KwTemp
2221            | TokenKind::KwTemporary
2222            | TokenKind::KwTies
2223            | TokenKind::KwTransaction
2224            | TokenKind::KwTrigger
2225            | TokenKind::KwUnbounded
2226            | TokenKind::KwVacuum
2227            | TokenKind::KwView
2228            | TokenKind::KwVirtual
2229            | TokenKind::KwWindow
2230            | TokenKind::KwWithout
2231    )
2232}
2233
2234/// Keywords that should never be consumed as implicit aliases because they
2235/// begin/continue the next clause in this grammar position.
2236fn is_alias_terminator_kw(k: &TokenKind) -> bool {
2237    matches!(
2238        k,
2239        TokenKind::KwCross
2240            | TokenKind::KwExcept
2241            | TokenKind::KwFull
2242            | TokenKind::KwGroup
2243            | TokenKind::KwHaving
2244            | TokenKind::KwInner
2245            | TokenKind::KwIntersect
2246            | TokenKind::KwJoin
2247            | TokenKind::KwLeft
2248            | TokenKind::KwLimit
2249            | TokenKind::KwNatural
2250            | TokenKind::KwOffset
2251            | TokenKind::KwOn
2252            | TokenKind::KwOrder
2253            | TokenKind::KwOuter
2254            | TokenKind::KwReturning
2255            | TokenKind::KwRight
2256            | TokenKind::KwUnion
2257            | TokenKind::KwUsing
2258            | TokenKind::KwWhere
2259            | TokenKind::KwWindow
2260    )
2261}
2262
2263pub(crate) fn kw_to_str(k: &TokenKind) -> String {
2264    k.keyword_str()
2265        .map(|s| s.to_ascii_lowercase())
2266        .unwrap_or_else(|| {
2267            let dbg = format!("{k:?}");
2268            dbg.strip_prefix("Kw").unwrap_or(&dbg).to_ascii_lowercase()
2269        })
2270}
2271
2272// ---------------------------------------------------------------------------
2273// Tests
2274// ---------------------------------------------------------------------------
2275
2276#[cfg(test)]
2277mod tests {
2278    use super::*;
2279
2280    static PARSE_OBSERVABILITY_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
2281
2282    fn parse_ok(sql: &str) -> Vec<Statement> {
2283        let mut p = Parser::from_sql(sql);
2284        let (stmts, errs) = p.parse_all();
2285        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
2286        stmts
2287    }
2288
2289    fn parse_one(sql: &str) -> Statement {
2290        let stmts = parse_ok(sql);
2291        assert_eq!(stmts.len(), 1, "expected 1 statement, got {}", stmts.len());
2292        stmts.into_iter().next().unwrap()
2293    }
2294
2295    #[test]
2296    fn test_parse_metrics_emitted_when_enabled() {
2297        let _guard = PARSE_OBSERVABILITY_LOCK
2298            .lock()
2299            .unwrap_or_else(|poisoned| poisoned.into_inner());
2300        let prev_metrics_enabled = parse_metrics_enabled();
2301        reset_parse_metrics();
2302        set_parse_metrics_enabled(true);
2303
2304        let mut parser = Parser::from_sql("SELECT 1; SELECT 2;");
2305        let (stmts, errs) = parser.parse_all();
2306        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
2307        assert_eq!(stmts.len(), 2);
2308
2309        let snapshot = parse_metrics_snapshot();
2310        assert!(snapshot.fsqlite_parse_statements_total >= 2);
2311
2312        set_parse_metrics_enabled(prev_metrics_enabled);
2313        reset_parse_metrics();
2314    }
2315
2316    #[test]
2317    fn test_parse_metrics_can_be_disabled_off_hot_path() {
2318        let _guard = PARSE_OBSERVABILITY_LOCK
2319            .lock()
2320            .unwrap_or_else(|poisoned| poisoned.into_inner());
2321        let prev_metrics_enabled = parse_metrics_enabled();
2322        reset_parse_metrics();
2323        set_parse_metrics_enabled(false);
2324
2325        let mut parser = Parser::from_sql("SELECT 1; SELECT 2;");
2326        let (stmts, errs) = parser.parse_all();
2327        assert!(errs.is_empty(), "unexpected errors: {errs:?}");
2328        assert_eq!(stmts.len(), 2);
2329
2330        let snapshot = parse_metrics_snapshot();
2331        assert_eq!(snapshot.fsqlite_parse_statements_total, 0);
2332
2333        set_parse_metrics_enabled(prev_metrics_enabled);
2334        reset_parse_metrics();
2335    }
2336
2337    #[test]
2338    fn test_parse_depth_overflow_does_not_poison_following_statement() {
2339        let mut parser = Parser::from_sql("SELECT 1; SELECT 42;");
2340        parser.depth = MAX_PARSE_DEPTH - 1;
2341
2342        // First statement overflows in nested parse entry (statement -> select)
2343        // and should unwind depth cleanly back to MAX_PARSE_DEPTH - 1.
2344        let first = parser.parse_statement();
2345        assert!(first.is_err(), "first statement should hit depth guard");
2346        assert_eq!(
2347            parser.depth,
2348            MAX_PARSE_DEPTH - 1,
2349            "depth must not leak upward on recursion-limit error"
2350        );
2351
2352        // Consume separator and parse the next statement; it should still be
2353        // reachable and fail for the same controlled reason (not because depth
2354        // was poisoned by the prior attempt).
2355        let _ = parser.eat(&TokenKind::Semicolon);
2356        let second = parser.parse_statement();
2357        assert!(
2358            second.is_err(),
2359            "second statement should still be parseable"
2360        );
2361        assert_eq!(
2362            parser.depth,
2363            MAX_PARSE_DEPTH - 1,
2364            "depth must remain stable across repeated recursion-limit errors"
2365        );
2366    }
2367
2368    #[test]
2369    fn test_parse_first_statement_with_tail_consumes_full_trigger_body() {
2370        let sql = "CREATE TRIGGER trg AFTER INSERT ON t BEGIN INSERT INTO audit VALUES('first'); INSERT INTO audit VALUES('second'); END; SELECT 1;";
2371        let Some((statement, tail_offset)) =
2372            parse_first_statement_with_tail(sql).expect("trigger statement should parse")
2373        else {
2374            panic!("expected a trigger statement");
2375        };
2376
2377        assert!(matches!(statement, Statement::CreateTrigger(_)));
2378        assert_eq!(&sql[tail_offset..], " SELECT 1;");
2379    }
2380
2381    #[test]
2382    fn test_parse_first_statement_with_tail_rejects_adjacent_statements_without_separator() {
2383        let error = parse_first_statement_with_tail("SELECT 1 SELECT 2")
2384            .expect_err("adjacent statements without a semicolon must be rejected");
2385
2386        assert!(
2387            error.message.contains("expected ';' separator"),
2388            "unexpected error: {error:?}"
2389        );
2390    }
2391
2392    #[test]
2393    fn test_create_table_without_rowid_and_strict_round_trips_display() {
2394        let sql = "CREATE TABLE s (id INTEGER PRIMARY KEY) WITHOUT ROWID, STRICT";
2395        let Some((statement, _)) =
2396            parse_first_statement_with_tail(sql).expect("statement should parse")
2397        else {
2398            panic!("expected CREATE TABLE statement");
2399        };
2400
2401        assert_eq!(statement.to_string(), sql);
2402    }
2403
2404    #[test]
2405    fn test_error_recovery_does_not_fabricate_top_level_statements_from_trigger_body() {
2406        let mut parser = Parser::from_sql(
2407            "CREATE TRIGGER trg AFTER INSERT ON t BEGIN XYZZY; SELECT 1; END; SELECT 2;",
2408        );
2409        let (stmts, errs) = parser.parse_all();
2410
2411        assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
2412        assert_eq!(
2413            stmts.len(),
2414            1,
2415            "only the trailing top-level SELECT should remain"
2416        );
2417        assert!(
2418            matches!(
2419                &stmts[0],
2420                Statement::Select(select)
2421                    if matches!(
2422                        &select.body.select,
2423                        SelectCore::Select { columns, .. }
2424                            if matches!(
2425                                columns.as_slice(),
2426                                [ResultColumn::Expr {
2427                                    expr: Expr::Literal(Literal::Integer(2), _),
2428                                    alias: None,
2429                                }]
2430                            )
2431                    )
2432            ),
2433            "parser must skip the malformed trigger instead of reinterpreting body tokens as top-level SQL: {stmts:?}"
2434        );
2435    }
2436
2437    #[test]
2438    fn test_error_recovery_recovers_values_statement_after_garbage() {
2439        let mut parser = Parser::from_sql("XYZZY VALUES (1);");
2440        let (stmts, errs) = parser.parse_all();
2441
2442        assert_eq!(errs.len(), 1, "expected one error for leading garbage");
2443        assert_eq!(stmts.len(), 1, "VALUES statement should still be recovered");
2444        assert!(matches!(stmts[0], Statement::Select(_)));
2445    }
2446
2447    #[test]
2448    fn test_error_recovery_does_not_swallow_top_level_sql_after_unbalanced_trigger_paren() {
2449        let mut parser = Parser::from_sql(
2450            "CREATE TRIGGER trg AFTER INSERT ON t BEGIN SELECT (1; END; SELECT 2;",
2451        );
2452        let (stmts, errs) = parser.parse_all();
2453
2454        assert_eq!(errs.len(), 1, "expected one trigger-body parse error");
2455        assert_eq!(
2456            stmts.len(),
2457            1,
2458            "malformed trigger recovery must still preserve the trailing top-level SELECT"
2459        );
2460        assert!(
2461            matches!(
2462                &stmts[0],
2463                Statement::Select(select)
2464                    if matches!(
2465                        &select.body.select,
2466                        SelectCore::Select { columns, .. }
2467                            if matches!(
2468                                columns.as_slice(),
2469                                [ResultColumn::Expr {
2470                                    expr: Expr::Literal(Literal::Integer(2), _),
2471                                    alias: None,
2472                                }]
2473                            )
2474                    )
2475            ),
2476            "parser must stop at the trigger END even when parentheses are left unbalanced: {stmts:?}"
2477        );
2478    }
2479
2480    #[test]
2481    fn select_literal() {
2482        let stmt = parse_one("SELECT 1");
2483        assert!(matches!(stmt, Statement::Select(_)));
2484    }
2485
2486    #[test]
2487    fn select_star_from() {
2488        let stmt = parse_one("SELECT * FROM t");
2489        if let Statement::Select(s) = stmt {
2490            if let SelectCore::Select { columns, from, .. } = &s.body.select {
2491                assert!(matches!(columns[0], ResultColumn::Star));
2492                assert!(from.is_some());
2493            } else {
2494                unreachable!("expected Select core");
2495            }
2496        } else {
2497            unreachable!("expected Select");
2498        }
2499    }
2500
2501    #[test]
2502    fn select_where_order_limit() {
2503        let stmt = parse_one("SELECT a FROM t WHERE a > 1 ORDER BY a LIMIT 10 OFFSET 5");
2504        if let Statement::Select(s) = stmt {
2505            assert!(s.limit.is_some());
2506            assert_eq!(s.order_by.len(), 1);
2507        } else {
2508            unreachable!("expected Select");
2509        }
2510    }
2511
2512    #[test]
2513    fn select_limit_comma_syntax_uses_offset_then_count() {
2514        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
2515        if let Statement::Select(s) = stmt {
2516            let limit = s.limit.expect("LIMIT clause");
2517            assert!(matches!(
2518                limit.limit,
2519                Expr::Literal(Literal::Integer(10), _)
2520            ));
2521            assert!(matches!(
2522                limit.offset,
2523                Some(Expr::Literal(Literal::Integer(5), _))
2524            ));
2525        } else {
2526            unreachable!("expected Select");
2527        }
2528    }
2529
2530    #[test]
2531    fn select_order_by_nulls_first_last() {
2532        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST, b DESC NULLS LAST");
2533        if let Statement::Select(s) = stmt {
2534            assert_eq!(s.order_by.len(), 2);
2535            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
2536            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
2537            assert_eq!(s.order_by[1].direction, Some(SortDirection::Desc));
2538            assert_eq!(s.order_by[1].nulls, Some(NullsOrder::Last));
2539        } else {
2540            unreachable!("expected Select");
2541        }
2542    }
2543
2544    #[test]
2545    fn select_from_indexed_by_hint() {
2546        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
2547        if let Statement::Select(s) = stmt {
2548            if let SelectCore::Select { from, .. } = &s.body.select {
2549                let from = from.as_ref().expect("FROM clause");
2550                match &from.source {
2551                    TableOrSubquery::Table {
2552                        index_hint: Some(IndexHint::IndexedBy(name)),
2553                        ..
2554                    } => assert_eq!(name, "idx_t"),
2555                    other => unreachable!("expected indexed table source, got {other:?}"),
2556                }
2557            } else {
2558                unreachable!("expected Select core");
2559            }
2560        } else {
2561            unreachable!("expected Select");
2562        }
2563    }
2564
2565    #[test]
2566    fn select_from_not_indexed_hint() {
2567        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
2568        if let Statement::Select(s) = stmt {
2569            if let SelectCore::Select { from, .. } = &s.body.select {
2570                let from = from.as_ref().expect("FROM clause");
2571                match &from.source {
2572                    TableOrSubquery::Table {
2573                        index_hint: Some(IndexHint::NotIndexed),
2574                        ..
2575                    } => {}
2576                    other => unreachable!("expected not-indexed table source, got {other:?}"),
2577                }
2578            } else {
2579                unreachable!("expected Select core");
2580            }
2581        } else {
2582            unreachable!("expected Select");
2583        }
2584    }
2585
2586    #[test]
2587    fn select_from_table_valued_function() {
2588        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
2589        if let Statement::Select(s) = stmt {
2590            if let SelectCore::Select { from, .. } = &s.body.select {
2591                let from = from.as_ref().expect("FROM clause");
2592                match &from.source {
2593                    TableOrSubquery::TableFunction { name, args, alias } => {
2594                        assert_eq!(name, "generate_series");
2595                        assert_eq!(args.len(), 2);
2596                        assert_eq!(alias.as_deref(), Some("gs"));
2597                    }
2598                    other => unreachable!("expected table-valued function source, got {other:?}"),
2599                }
2600            } else {
2601                unreachable!("expected Select core");
2602            }
2603        } else {
2604            unreachable!("expected Select");
2605        }
2606    }
2607
2608    #[test]
2609    fn select_window_function_over_clause() {
2610        let stmt = parse_one(
2611            "SELECT sum(x) OVER (PARTITION BY y ORDER BY z \
2612             ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
2613        );
2614        if let Statement::Select(s) = stmt {
2615            if let SelectCore::Select { columns, .. } = &s.body.select {
2616                match &columns[0] {
2617                    ResultColumn::Expr {
2618                        expr:
2619                            Expr::FunctionCall {
2620                                over: Some(over), ..
2621                            },
2622                        ..
2623                    } => {
2624                        assert_eq!(over.partition_by.len(), 1);
2625                        assert_eq!(over.order_by.len(), 1);
2626                        assert!(matches!(
2627                            over.frame,
2628                            Some(FrameSpec {
2629                                frame_type: FrameType::Rows,
2630                                ..
2631                            })
2632                        ));
2633                    }
2634                    other => unreachable!("expected window function result column, got {other:?}"),
2635                }
2636            } else {
2637                unreachable!("expected Select core");
2638            }
2639        } else {
2640            unreachable!("expected Select");
2641        }
2642    }
2643
2644    #[test]
2645    fn select_named_window_definition_and_reference() {
2646        let stmt = parse_one(
2647            "SELECT sum(x) OVER win FROM t \
2648             WINDOW win AS (PARTITION BY y ORDER BY z)",
2649        );
2650        if let Statement::Select(s) = stmt {
2651            if let SelectCore::Select {
2652                columns, windows, ..
2653            } = &s.body.select
2654            {
2655                assert_eq!(windows.len(), 1);
2656                assert_eq!(windows[0].name, "win");
2657                assert_eq!(windows[0].spec.partition_by.len(), 1);
2658                assert_eq!(windows[0].spec.order_by.len(), 1);
2659                match &columns[0] {
2660                    ResultColumn::Expr {
2661                        expr:
2662                            Expr::FunctionCall {
2663                                over: Some(over), ..
2664                            },
2665                        ..
2666                    } => assert_eq!(over.base_window.as_deref(), Some("win")),
2667                    other => unreachable!("expected named window function, got {other:?}"),
2668                }
2669            } else {
2670                unreachable!("expected Select core");
2671            }
2672        } else {
2673            unreachable!("expected Select");
2674        }
2675    }
2676
2677    #[test]
2678    fn insert_values() {
2679        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2), (3, 4)");
2680        assert!(matches!(stmt, Statement::Insert(_)));
2681    }
2682
2683    #[test]
2684    fn update_set() {
2685        let stmt = parse_one("UPDATE t SET a = 1, b = 2 WHERE id = 3");
2686        assert!(matches!(stmt, Statement::Update(_)));
2687    }
2688
2689    #[test]
2690    fn delete_from() {
2691        let stmt = parse_one("DELETE FROM t WHERE id = 1");
2692        assert!(matches!(stmt, Statement::Delete(_)));
2693    }
2694
2695    #[test]
2696    fn create_table_basic() {
2697        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT NOT NULL)");
2698        if let Statement::CreateTable(ct) = stmt {
2699            assert_eq!(ct.name.name, "t");
2700            if let CreateTableBody::Columns { columns, .. } = ct.body {
2701                assert_eq!(columns.len(), 2);
2702            } else {
2703                unreachable!("expected column defs");
2704            }
2705        } else {
2706            unreachable!("expected CreateTable");
2707        }
2708    }
2709
2710    #[test]
2711    fn create_index() {
2712        let stmt = parse_one("CREATE UNIQUE INDEX idx ON t (a, b DESC)");
2713        if let Statement::CreateIndex(ci) = stmt {
2714            assert!(ci.unique);
2715            assert_eq!(ci.columns.len(), 2);
2716        } else {
2717            unreachable!("expected CreateIndex");
2718        }
2719    }
2720
2721    #[test]
2722    fn drop_table_if_exists() {
2723        let stmt = parse_one("DROP TABLE IF EXISTS t");
2724        if let Statement::Drop(d) = stmt {
2725            assert!(d.if_exists);
2726            assert_eq!(d.object_type, DropObjectType::Table);
2727        } else {
2728            unreachable!("expected Drop");
2729        }
2730    }
2731
2732    #[test]
2733    fn begin_commit() {
2734        let stmts = parse_ok("BEGIN IMMEDIATE; COMMIT");
2735        assert_eq!(stmts.len(), 2);
2736        if let Statement::Begin(b) = &stmts[0] {
2737            assert_eq!(b.mode, Some(TransactionMode::Immediate));
2738        } else {
2739            unreachable!("expected Begin");
2740        }
2741        assert!(matches!(stmts[1], Statement::Commit));
2742    }
2743
2744    #[test]
2745    fn begin_concurrent() {
2746        let stmt = parse_one("BEGIN CONCURRENT");
2747        if let Statement::Begin(b) = stmt {
2748            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
2749        } else {
2750            unreachable!("expected Begin");
2751        }
2752    }
2753
2754    #[test]
2755    fn rollback_to_savepoint() {
2756        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
2757        if let Statement::Rollback(r) = stmt {
2758            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
2759        } else {
2760            unreachable!("expected Rollback");
2761        }
2762    }
2763
2764    #[test]
2765    fn explain_query_plan() {
2766        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT 1");
2767        assert!(matches!(
2768            stmt,
2769            Statement::Explain {
2770                query_plan: true,
2771                ..
2772            }
2773        ));
2774    }
2775
2776    #[test]
2777    fn pragma() {
2778        let stmt = parse_one("PRAGMA journal_mode = WAL");
2779        assert!(matches!(stmt, Statement::Pragma(_)));
2780    }
2781
2782    #[test]
2783    fn pragma_allows_on_value() {
2784        let stmt = parse_one("PRAGMA fsqlite.serializable = ON");
2785        assert!(matches!(stmt, Statement::Pragma(_)));
2786    }
2787
2788    #[test]
2789    fn error_recovery_multiple_statements() {
2790        let mut p = Parser::from_sql("SELECT 1; XYZZY; SELECT 2");
2791        let (stmts, errs) = p.parse_all();
2792        assert_eq!(stmts.len(), 2, "should recover: stmts={stmts:?}");
2793        assert!(!errs.is_empty());
2794    }
2795
2796    #[test]
2797    fn compound_union() {
2798        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
2799        if let Statement::Select(s) = stmt {
2800            assert_eq!(s.body.compounds.len(), 1);
2801        } else {
2802            unreachable!("expected Select");
2803        }
2804    }
2805
2806    #[test]
2807    fn alter_table_rename() {
2808        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
2809        assert!(matches!(
2810            stmt,
2811            Statement::AlterTable(AlterTableStatement {
2812                action: AlterTableAction::RenameTo(_),
2813                ..
2814            })
2815        ));
2816    }
2817
2818    // -----------------------------------------------------------------------
2819    // bd-2kvo Phase 3 acceptance: parser join types
2820    // -----------------------------------------------------------------------
2821
2822    #[test]
2823    fn test_parser_join_inner() {
2824        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
2825        if let Statement::Select(s) = stmt {
2826            if let SelectCore::Select { from, .. } = &s.body.select {
2827                let from = from.as_ref().expect("FROM clause");
2828                assert!(!from.joins.is_empty());
2829                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
2830            } else {
2831                unreachable!("expected Select core");
2832            }
2833        } else {
2834            unreachable!("expected Select");
2835        }
2836    }
2837
2838    #[test]
2839    fn test_parser_join_left() {
2840        let stmt = parse_one("SELECT * FROM a LEFT JOIN b ON a.id = b.a_id");
2841        if let Statement::Select(s) = stmt {
2842            if let SelectCore::Select { from, .. } = &s.body.select {
2843                let from = from.as_ref().expect("FROM clause");
2844                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
2845            } else {
2846                unreachable!("expected Select core");
2847            }
2848        } else {
2849            unreachable!("expected Select");
2850        }
2851    }
2852
2853    #[test]
2854    fn test_parser_join_left_outer() {
2855        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
2856        if let Statement::Select(s) = stmt {
2857            if let SelectCore::Select { from, .. } = &s.body.select {
2858                let from = from.as_ref().expect("FROM clause");
2859                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
2860            } else {
2861                unreachable!("expected Select core");
2862            }
2863        } else {
2864            unreachable!("expected Select");
2865        }
2866    }
2867
2868    #[test]
2869    fn test_parser_join_right() {
2870        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
2871        if let Statement::Select(s) = stmt {
2872            if let SelectCore::Select { from, .. } = &s.body.select {
2873                let from = from.as_ref().expect("FROM clause");
2874                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
2875            } else {
2876                unreachable!("expected Select core");
2877            }
2878        } else {
2879            unreachable!("expected Select");
2880        }
2881    }
2882
2883    #[test]
2884    fn test_parser_join_full() {
2885        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
2886        if let Statement::Select(s) = stmt {
2887            if let SelectCore::Select { from, .. } = &s.body.select {
2888                let from = from.as_ref().expect("FROM clause");
2889                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
2890            } else {
2891                unreachable!("expected Select core");
2892            }
2893        } else {
2894            unreachable!("expected Select");
2895        }
2896    }
2897
2898    #[test]
2899    fn test_parser_join_full_outer_with_semicolon() {
2900        let stmt = parse_one("SELECT l.name, r.tag FROM l FULL OUTER JOIN r ON l.id = r.l_id;");
2901        if let Statement::Select(s) = stmt {
2902            if let SelectCore::Select { from, .. } = &s.body.select {
2903                let from = from.as_ref().expect("FROM clause");
2904                assert_eq!(from.joins.len(), 1);
2905                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
2906            } else {
2907                unreachable!("expected Select core");
2908            }
2909        } else {
2910            unreachable!("expected Select");
2911        }
2912    }
2913
2914    #[test]
2915    fn test_parser_join_cross() {
2916        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
2917        if let Statement::Select(s) = stmt {
2918            if let SelectCore::Select { from, .. } = &s.body.select {
2919                let from = from.as_ref().expect("FROM clause");
2920                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
2921            } else {
2922                unreachable!("expected Select core");
2923            }
2924        } else {
2925            unreachable!("expected Select");
2926        }
2927    }
2928
2929    #[test]
2930    fn test_parser_join_natural() {
2931        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
2932        if let Statement::Select(s) = stmt {
2933            if let SelectCore::Select { from, .. } = &s.body.select {
2934                let from = from.as_ref().expect("FROM clause");
2935                assert!(from.joins[0].join_type.natural);
2936            } else {
2937                unreachable!("expected Select core");
2938            }
2939        } else {
2940            unreachable!("expected Select");
2941        }
2942    }
2943
2944    #[test]
2945    fn test_parser_join_using() {
2946        let stmt = parse_one("SELECT * FROM a JOIN b USING (id)");
2947        if let Statement::Select(s) = stmt {
2948            if let SelectCore::Select { from, .. } = &s.body.select {
2949                let from = from.as_ref().expect("FROM clause");
2950                assert!(matches!(
2951                    from.joins[0].constraint,
2952                    Some(JoinConstraint::Using(_))
2953                ));
2954            } else {
2955                unreachable!("expected Select core");
2956            }
2957        } else {
2958            unreachable!("expected Select");
2959        }
2960    }
2961
2962    #[test]
2963    fn test_parser_join_comma() {
2964        // Comma-join is an implicit cross join.
2965        let stmt = parse_one("SELECT * FROM a, b WHERE a.id = b.a_id");
2966        if let Statement::Select(s) = stmt {
2967            if let SelectCore::Select { from, .. } = &s.body.select {
2968                let from = from.as_ref().expect("FROM clause");
2969                assert!(!from.joins.is_empty());
2970                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
2971            } else {
2972                unreachable!("expected Select core");
2973            }
2974        } else {
2975            unreachable!("expected Select");
2976        }
2977    }
2978
2979    // -----------------------------------------------------------------------
2980    // bd-2kvo Phase 3 acceptance: CTE syntax
2981    // -----------------------------------------------------------------------
2982
2983    #[test]
2984    fn test_parser_cte_basic() {
2985        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
2986        if let Statement::Select(s) = stmt {
2987            let with = s.with.as_ref().expect("WITH clause");
2988            assert!(!with.recursive);
2989            assert_eq!(with.ctes.len(), 1);
2990            assert_eq!(with.ctes[0].name, "cte");
2991        } else {
2992            unreachable!("expected Select");
2993        }
2994    }
2995
2996    #[test]
2997    fn test_parser_cte_multiple() {
2998        let stmt = parse_one("WITH a AS (SELECT 1), b AS (SELECT 2) SELECT * FROM a, b");
2999        if let Statement::Select(s) = stmt {
3000            let with = s.with.as_ref().expect("WITH clause");
3001            assert_eq!(with.ctes.len(), 2);
3002            assert_eq!(with.ctes[0].name, "a");
3003            assert_eq!(with.ctes[1].name, "b");
3004        } else {
3005            unreachable!("expected Select");
3006        }
3007    }
3008
3009    #[test]
3010    fn test_parser_cte_recursive() {
3011        let stmt = parse_one(
3012            "WITH RECURSIVE cnt(x) AS (\
3013             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
3014             ) SELECT x FROM cnt",
3015        );
3016        if let Statement::Select(s) = stmt {
3017            let with = s.with.as_ref().expect("WITH clause");
3018            assert!(with.recursive);
3019            assert_eq!(with.ctes[0].name, "cnt");
3020            assert_eq!(with.ctes[0].columns, vec!["x".to_owned()]);
3021        } else {
3022            unreachable!("expected Select");
3023        }
3024    }
3025
3026    #[test]
3027    fn test_parser_cte_materialized() {
3028        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
3029        if let Statement::Select(s) = stmt {
3030            let with = s.with.as_ref().expect("WITH clause");
3031            assert_eq!(
3032                with.ctes[0].materialized,
3033                Some(CteMaterialized::Materialized)
3034            );
3035        } else {
3036            unreachable!("expected Select");
3037        }
3038    }
3039
3040    // -----------------------------------------------------------------------
3041    // bd-2d6i §12.1 SELECT full syntax acceptance tests
3042    // -----------------------------------------------------------------------
3043
3044    #[test]
3045    fn test_select_table_star() {
3046        let stmt = parse_one("SELECT t1.* FROM t1, t2");
3047        if let Statement::Select(s) = stmt {
3048            if let SelectCore::Select { columns, .. } = &s.body.select {
3049                assert!(
3050                    matches!(&columns[0], ResultColumn::TableStar(t) if t == "t1"),
3051                    "expected TableStar(t1), got {:?}",
3052                    columns[0]
3053                );
3054            } else {
3055                unreachable!("expected Select core");
3056            }
3057        } else {
3058            unreachable!("expected Select");
3059        }
3060    }
3061
3062    #[test]
3063    fn test_select_expr_alias() {
3064        let stmt = parse_one("SELECT x + 1 AS result FROM t");
3065        if let Statement::Select(s) = stmt {
3066            if let SelectCore::Select { columns, .. } = &s.body.select {
3067                match &columns[0] {
3068                    ResultColumn::Expr {
3069                        alias: Some(alias), ..
3070                    } => assert_eq!(alias, "result"),
3071                    other => unreachable!("expected aliased expr column, got {other:?}"),
3072                }
3073            } else {
3074                unreachable!("expected Select core");
3075            }
3076        } else {
3077            unreachable!("expected Select");
3078        }
3079    }
3080
3081    #[test]
3082    fn test_select_distinct_keyword() {
3083        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
3084        if let Statement::Select(s) = stmt {
3085            if let SelectCore::Select {
3086                distinct, columns, ..
3087            } = &s.body.select
3088            {
3089                assert_eq!(*distinct, Distinctness::Distinct);
3090                assert_eq!(columns.len(), 2);
3091            } else {
3092                unreachable!("expected Select core");
3093            }
3094        } else {
3095            unreachable!("expected Select");
3096        }
3097    }
3098
3099    #[test]
3100    fn test_select_values_clause() {
3101        let stmt = parse_one("VALUES (1, 2), (3, 4)");
3102        if let Statement::Select(s) = stmt {
3103            if let SelectCore::Values(rows) = &s.body.select {
3104                assert_eq!(rows.len(), 2);
3105                assert_eq!(rows[0].len(), 2);
3106                assert_eq!(rows[1].len(), 2);
3107            } else {
3108                unreachable!("expected Values core");
3109            }
3110        } else {
3111            unreachable!("expected Select");
3112        }
3113    }
3114
3115    #[test]
3116    fn test_select_group_by_having() {
3117        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
3118        if let Statement::Select(s) = stmt {
3119            if let SelectCore::Select {
3120                group_by, having, ..
3121            } = &s.body.select
3122            {
3123                assert_eq!(group_by.len(), 1);
3124                assert!(having.is_some(), "HAVING clause must be present");
3125            } else {
3126                unreachable!("expected Select core");
3127            }
3128        } else {
3129            unreachable!("expected Select");
3130        }
3131    }
3132
3133    #[test]
3134    fn test_compound_union() {
3135        let stmt = parse_one("SELECT 1 UNION SELECT 2");
3136        if let Statement::Select(s) = stmt {
3137            assert_eq!(s.body.compounds.len(), 1);
3138            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
3139        } else {
3140            unreachable!("expected Select");
3141        }
3142    }
3143
3144    #[test]
3145    fn test_compound_union_all() {
3146        let stmt = parse_one("SELECT 1 UNION ALL SELECT 2");
3147        if let Statement::Select(s) = stmt {
3148            assert_eq!(s.body.compounds.len(), 1);
3149            assert_eq!(s.body.compounds[0].0, CompoundOp::UnionAll);
3150        } else {
3151            unreachable!("expected Select");
3152        }
3153    }
3154
3155    #[test]
3156    fn test_compound_intersect() {
3157        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
3158        if let Statement::Select(s) = stmt {
3159            assert_eq!(s.body.compounds.len(), 1);
3160            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
3161        } else {
3162            unreachable!("expected Select");
3163        }
3164    }
3165
3166    #[test]
3167    fn test_compound_except() {
3168        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
3169        if let Statement::Select(s) = stmt {
3170            assert_eq!(s.body.compounds.len(), 1);
3171            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
3172        } else {
3173            unreachable!("expected Select");
3174        }
3175    }
3176
3177    #[test]
3178    fn test_compound_order_applies_to_whole() {
3179        // ORDER BY and LIMIT apply to the entire compound result per SQL spec.
3180        let stmt = parse_one("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
3181        if let Statement::Select(s) = stmt {
3182            assert_eq!(s.body.compounds.len(), 1);
3183            assert_eq!(s.order_by.len(), 1, "ORDER BY must be on compound");
3184            assert!(s.limit.is_some(), "LIMIT must be on compound");
3185        } else {
3186            unreachable!("expected Select");
3187        }
3188    }
3189
3190    #[test]
3191    fn test_compound_three_way() {
3192        let stmt = parse_one("SELECT 1 UNION SELECT 2 INTERSECT SELECT 3");
3193        if let Statement::Select(s) = stmt {
3194            assert_eq!(s.body.compounds.len(), 2);
3195            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
3196            assert_eq!(s.body.compounds[1].0, CompoundOp::Intersect);
3197        } else {
3198            unreachable!("expected Select");
3199        }
3200    }
3201
3202    #[test]
3203    fn test_cte_not_materialized() {
3204        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
3205        if let Statement::Select(s) = stmt {
3206            let with = s.with.as_ref().expect("WITH clause");
3207            assert_eq!(
3208                with.ctes[0].materialized,
3209                Some(CteMaterialized::NotMaterialized)
3210            );
3211        } else {
3212            unreachable!("expected Select");
3213        }
3214    }
3215
3216    #[test]
3217    fn test_cte_with_explicit_columns() {
3218        let stmt = parse_one("WITH cte(a, b, c) AS (SELECT 1, 2, 3) SELECT * FROM cte");
3219        if let Statement::Select(s) = stmt {
3220            let with = s.with.as_ref().expect("WITH clause");
3221            assert_eq!(with.ctes[0].columns, vec!["a", "b", "c"]);
3222        } else {
3223            unreachable!("expected Select");
3224        }
3225    }
3226
3227    #[test]
3228    fn test_window_frame_range() {
3229        let stmt = parse_one(
3230            "SELECT sum(x) OVER (ORDER BY y RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t",
3231        );
3232        if let Statement::Select(s) = stmt {
3233            if let SelectCore::Select { columns, .. } = &s.body.select {
3234                match &columns[0] {
3235                    ResultColumn::Expr {
3236                        expr:
3237                            Expr::FunctionCall {
3238                                over: Some(over), ..
3239                            },
3240                        ..
3241                    } => {
3242                        let frame = over.frame.as_ref().expect("frame spec");
3243                        assert_eq!(frame.frame_type, FrameType::Range);
3244                        assert!(matches!(frame.start, FrameBound::UnboundedPreceding));
3245                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
3246                    }
3247                    other => unreachable!("expected window function, got {other:?}"),
3248                }
3249            } else {
3250                unreachable!("expected Select core");
3251            }
3252        } else {
3253            unreachable!("expected Select");
3254        }
3255    }
3256
3257    #[test]
3258    fn test_window_frame_groups() {
3259        let stmt = parse_one(
3260            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
3261        );
3262        if let Statement::Select(s) = stmt {
3263            if let SelectCore::Select { columns, .. } = &s.body.select {
3264                match &columns[0] {
3265                    ResultColumn::Expr {
3266                        expr:
3267                            Expr::FunctionCall {
3268                                over: Some(over), ..
3269                            },
3270                        ..
3271                    } => {
3272                        let frame = over.frame.as_ref().expect("frame spec");
3273                        assert_eq!(frame.frame_type, FrameType::Groups);
3274                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
3275                        assert!(matches!(frame.end, Some(FrameBound::Following(_))));
3276                    }
3277                    other => unreachable!("expected window function, got {other:?}"),
3278                }
3279            } else {
3280                unreachable!("expected Select core");
3281            }
3282        } else {
3283            unreachable!("expected Select");
3284        }
3285    }
3286
3287    #[test]
3288    fn test_window_frame_exclude_current_row() {
3289        let stmt = parse_one(
3290            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3291             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
3292        );
3293        if let Statement::Select(s) = stmt {
3294            if let SelectCore::Select { columns, .. } = &s.body.select {
3295                match &columns[0] {
3296                    ResultColumn::Expr {
3297                        expr:
3298                            Expr::FunctionCall {
3299                                over: Some(over), ..
3300                            },
3301                        ..
3302                    } => {
3303                        let frame = over.frame.as_ref().expect("frame spec");
3304                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
3305                    }
3306                    other => unreachable!("expected window function, got {other:?}"),
3307                }
3308            } else {
3309                unreachable!("expected Select core");
3310            }
3311        } else {
3312            unreachable!("expected Select");
3313        }
3314    }
3315
3316    #[test]
3317    fn test_window_frame_exclude_ties() {
3318        let stmt = parse_one(
3319            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3320             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
3321        );
3322        if let Statement::Select(s) = stmt {
3323            if let SelectCore::Select { columns, .. } = &s.body.select {
3324                match &columns[0] {
3325                    ResultColumn::Expr {
3326                        expr:
3327                            Expr::FunctionCall {
3328                                over: Some(over), ..
3329                            },
3330                        ..
3331                    } => {
3332                        let frame = over.frame.as_ref().expect("frame spec");
3333                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
3334                    }
3335                    other => unreachable!("expected window function, got {other:?}"),
3336                }
3337            } else {
3338                unreachable!("expected Select core");
3339            }
3340        } else {
3341            unreachable!("expected Select");
3342        }
3343    }
3344
3345    #[test]
3346    fn test_window_frame_exclude_group() {
3347        let stmt =
3348            parse_one("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
3349        if let Statement::Select(s) = stmt {
3350            if let SelectCore::Select { columns, .. } = &s.body.select {
3351                match &columns[0] {
3352                    ResultColumn::Expr {
3353                        expr:
3354                            Expr::FunctionCall {
3355                                over: Some(over), ..
3356                            },
3357                        ..
3358                    } => {
3359                        let frame = over.frame.as_ref().expect("frame spec");
3360                        assert_eq!(frame.frame_type, FrameType::Groups);
3361                        assert_eq!(frame.exclude, Some(FrameExclude::Group));
3362                    }
3363                    other => unreachable!("expected window function, got {other:?}"),
3364                }
3365            } else {
3366                unreachable!("expected Select core");
3367            }
3368        } else {
3369            unreachable!("expected Select");
3370        }
3371    }
3372
3373    #[test]
3374    fn test_window_frame_unbounded_following() {
3375        let stmt = parse_one(
3376            "SELECT sum(x) OVER (ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) FROM t",
3377        );
3378        if let Statement::Select(s) = stmt {
3379            if let SelectCore::Select { columns, .. } = &s.body.select {
3380                match &columns[0] {
3381                    ResultColumn::Expr {
3382                        expr:
3383                            Expr::FunctionCall {
3384                                over: Some(over), ..
3385                            },
3386                        ..
3387                    } => {
3388                        let frame = over.frame.as_ref().expect("frame spec");
3389                        assert!(matches!(frame.start, FrameBound::CurrentRow));
3390                        assert!(matches!(frame.end, Some(FrameBound::UnboundedFollowing)));
3391                    }
3392                    other => unreachable!("expected window function, got {other:?}"),
3393                }
3394            } else {
3395                unreachable!("expected Select core");
3396            }
3397        } else {
3398            unreachable!("expected Select");
3399        }
3400    }
3401
3402    #[test]
3403    fn test_filter_clause_aggregate() {
3404        let stmt = parse_one("SELECT count(*) FILTER (WHERE x > 0) FROM t");
3405        if let Statement::Select(s) = stmt {
3406            if let SelectCore::Select { columns, .. } = &s.body.select {
3407                match &columns[0] {
3408                    ResultColumn::Expr {
3409                        expr: Expr::FunctionCall { filter, .. },
3410                        ..
3411                    } => {
3412                        assert!(
3413                            filter.is_some(),
3414                            "FILTER clause must be present on aggregate"
3415                        );
3416                    }
3417                    other => unreachable!("expected function call with filter, got {other:?}"),
3418                }
3419            } else {
3420                unreachable!("expected Select core");
3421            }
3422        } else {
3423            unreachable!("expected Select");
3424        }
3425    }
3426
3427    #[test]
3428    fn test_filter_clause_window() {
3429        let stmt = parse_one("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
3430        if let Statement::Select(s) = stmt {
3431            if let SelectCore::Select { columns, .. } = &s.body.select {
3432                match &columns[0] {
3433                    ResultColumn::Expr {
3434                        expr:
3435                            Expr::FunctionCall {
3436                                filter,
3437                                over: Some(_),
3438                                ..
3439                            },
3440                        ..
3441                    } => {
3442                        assert!(
3443                            filter.is_some(),
3444                            "FILTER clause must be present on window function"
3445                        );
3446                    }
3447                    other => unreachable!("expected window function with filter, got {other:?}"),
3448                }
3449            } else {
3450                unreachable!("expected Select core");
3451            }
3452        } else {
3453            unreachable!("expected Select");
3454        }
3455    }
3456
3457    #[test]
3458    fn test_subquery_in_from() {
3459        let stmt = parse_one("SELECT sub.x FROM (SELECT 1 AS x) AS sub");
3460        if let Statement::Select(s) = stmt {
3461            if let SelectCore::Select { from, .. } = &s.body.select {
3462                let from = from.as_ref().expect("FROM clause");
3463                match &from.source {
3464                    TableOrSubquery::Subquery { alias, .. } => {
3465                        assert_eq!(alias.as_deref(), Some("sub"));
3466                    }
3467                    other => unreachable!("expected subquery source, got {other:?}"),
3468                }
3469            } else {
3470                unreachable!("expected Select core");
3471            }
3472        } else {
3473            unreachable!("expected Select");
3474        }
3475    }
3476
3477    #[test]
3478    fn test_multiple_joins_chain() {
3479        let stmt = parse_one(
3480            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id \
3481             LEFT JOIN c ON b.id = c.b_id \
3482             CROSS JOIN d",
3483        );
3484        if let Statement::Select(s) = stmt {
3485            if let SelectCore::Select { from, .. } = &s.body.select {
3486                let from = from.as_ref().expect("FROM clause");
3487                assert_eq!(from.joins.len(), 3);
3488                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
3489                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
3490                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
3491            } else {
3492                unreachable!("expected Select core");
3493            }
3494        } else {
3495            unreachable!("expected Select");
3496        }
3497    }
3498
3499    #[test]
3500    fn test_natural_left_join() {
3501        let stmt = parse_one("SELECT * FROM a NATURAL LEFT JOIN b");
3502        if let Statement::Select(s) = stmt {
3503            if let SelectCore::Select { from, .. } = &s.body.select {
3504                let from = from.as_ref().expect("FROM clause");
3505                let jt = &from.joins[0].join_type;
3506                assert!(jt.natural, "must be NATURAL");
3507                assert_eq!(jt.kind, JoinKind::Left);
3508            } else {
3509                unreachable!("expected Select core");
3510            }
3511        } else {
3512            unreachable!("expected Select");
3513        }
3514    }
3515
3516    #[test]
3517    fn test_select_nulls_first_default_asc() {
3518        // Verify NULLS FIRST with explicit ASC direction.
3519        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
3520        if let Statement::Select(s) = stmt {
3521            assert_eq!(s.order_by.len(), 1);
3522            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
3523            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
3524        } else {
3525            unreachable!("expected Select");
3526        }
3527    }
3528
3529    #[test]
3530    fn test_select_nulls_last_desc() {
3531        // Verify NULLS LAST with explicit DESC direction.
3532        let stmt = parse_one("SELECT a FROM t ORDER BY a DESC NULLS LAST");
3533        if let Statement::Select(s) = stmt {
3534            assert_eq!(s.order_by.len(), 1);
3535            assert_eq!(s.order_by[0].direction, Some(SortDirection::Desc));
3536            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
3537        } else {
3538            unreachable!("expected Select");
3539        }
3540    }
3541
3542    // -----------------------------------------------------------------------
3543    // bd-2d6i §12.1 roundtrip coverage for advanced SELECT forms
3544    // -----------------------------------------------------------------------
3545
3546    #[test]
3547    fn test_roundtrip_select_filter_clause() {
3548        assert_roundtrip("SELECT count(*) FILTER (WHERE x > 0) FROM t");
3549    }
3550
3551    #[test]
3552    fn test_roundtrip_select_window_frame_groups() {
3553        assert_roundtrip(
3554            "SELECT sum(x) OVER (ORDER BY y GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t",
3555        );
3556    }
3557
3558    #[test]
3559    fn test_roundtrip_select_window_frame_exclude() {
3560        assert_roundtrip(
3561            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3562             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
3563        );
3564        assert_roundtrip(
3565            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3566             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
3567        );
3568        assert_roundtrip("SELECT sum(x) OVER (ORDER BY y GROUPS CURRENT ROW EXCLUDE GROUP) FROM t");
3569    }
3570
3571    #[test]
3572    fn test_roundtrip_select_nulls_order() {
3573        assert_roundtrip("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
3574        assert_roundtrip("SELECT a FROM t ORDER BY a DESC NULLS LAST");
3575    }
3576
3577    #[test]
3578    fn test_roundtrip_select_values() {
3579        assert_roundtrip("VALUES (1, 2), (3, 4)");
3580    }
3581
3582    #[test]
3583    fn test_roundtrip_select_compound_order_limit() {
3584        assert_roundtrip("SELECT a FROM t1 UNION ALL SELECT b FROM t2 ORDER BY 1 LIMIT 10");
3585    }
3586
3587    #[test]
3588    fn test_roundtrip_select_cte_not_materialized() {
3589        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
3590    }
3591
3592    #[test]
3593    fn test_roundtrip_select_natural_left_join() {
3594        assert_roundtrip("SELECT * FROM a NATURAL LEFT JOIN b");
3595    }
3596
3597    #[test]
3598    fn test_roundtrip_select_indexed_by() {
3599        assert_roundtrip("SELECT * FROM t INDEXED BY idx_t WHERE x = 1");
3600    }
3601
3602    #[test]
3603    fn test_roundtrip_select_filter_window_combined() {
3604        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0) OVER (ORDER BY y) FROM t");
3605    }
3606
3607    #[test]
3608    fn test_roundtrip_select_three_way_compound() {
3609        assert_roundtrip("SELECT 1 UNION SELECT 2 EXCEPT SELECT 3");
3610    }
3611
3612    #[test]
3613    fn test_roundtrip_select_multiple_joins() {
3614        assert_roundtrip(
3615            "SELECT * FROM a INNER JOIN b ON a.id = b.a_id LEFT JOIN c ON b.id = c.b_id",
3616        );
3617    }
3618
3619    // -----------------------------------------------------------------------
3620    // bd-2d6i §12.1 — remaining required tests (exact names per bead spec)
3621    // -----------------------------------------------------------------------
3622
3623    #[test]
3624    fn test_select_star() {
3625        // SELECT * returns all columns from all tables.
3626        let stmt = parse_one("SELECT * FROM t");
3627        if let Statement::Select(s) = stmt {
3628            if let SelectCore::Select { columns, .. } = &s.body.select {
3629                assert!(matches!(columns[0], ResultColumn::Star));
3630            } else {
3631                unreachable!("expected Select core");
3632            }
3633        } else {
3634            unreachable!("expected Select");
3635        }
3636    }
3637
3638    #[test]
3639    fn test_inner_join_on() {
3640        // INNER JOIN ON produces correct intersection.
3641        let stmt = parse_one("SELECT * FROM a INNER JOIN b ON a.id = b.a_id");
3642        if let Statement::Select(s) = stmt {
3643            if let SelectCore::Select { from, .. } = &s.body.select {
3644                let from = from.as_ref().expect("FROM clause");
3645                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
3646                assert!(matches!(
3647                    from.joins[0].constraint,
3648                    Some(JoinConstraint::On(_))
3649                ));
3650            } else {
3651                unreachable!("expected Select core");
3652            }
3653        } else {
3654            unreachable!("expected Select");
3655        }
3656    }
3657
3658    #[test]
3659    fn test_left_outer_join() {
3660        // LEFT JOIN returns all left rows with NULLs for non-matching right.
3661        let stmt = parse_one("SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.a_id");
3662        if let Statement::Select(s) = stmt {
3663            if let SelectCore::Select { from, .. } = &s.body.select {
3664                let from = from.as_ref().expect("FROM clause");
3665                assert_eq!(from.joins[0].join_type.kind, JoinKind::Left);
3666            } else {
3667                unreachable!("expected Select core");
3668            }
3669        } else {
3670            unreachable!("expected Select");
3671        }
3672    }
3673
3674    #[test]
3675    fn test_right_outer_join() {
3676        // RIGHT JOIN returns all right rows (3.39+ feature).
3677        let stmt = parse_one("SELECT * FROM a RIGHT JOIN b ON a.id = b.a_id");
3678        if let Statement::Select(s) = stmt {
3679            if let SelectCore::Select { from, .. } = &s.body.select {
3680                let from = from.as_ref().expect("FROM clause");
3681                assert_eq!(from.joins[0].join_type.kind, JoinKind::Right);
3682            } else {
3683                unreachable!("expected Select core");
3684            }
3685        } else {
3686            unreachable!("expected Select");
3687        }
3688    }
3689
3690    #[test]
3691    fn test_full_outer_join() {
3692        // FULL OUTER JOIN returns rows from both tables.
3693        let stmt = parse_one("SELECT * FROM a FULL OUTER JOIN b ON a.id = b.a_id");
3694        if let Statement::Select(s) = stmt {
3695            if let SelectCore::Select { from, .. } = &s.body.select {
3696                let from = from.as_ref().expect("FROM clause");
3697                assert_eq!(from.joins[0].join_type.kind, JoinKind::Full);
3698            } else {
3699                unreachable!("expected Select core");
3700            }
3701        } else {
3702            unreachable!("expected Select");
3703        }
3704    }
3705
3706    #[test]
3707    fn test_cross_join_no_reorder() {
3708        // CROSS JOIN prevents optimizer reordering; parser must produce JoinKind::Cross.
3709        let stmt = parse_one("SELECT * FROM a CROSS JOIN b");
3710        if let Statement::Select(s) = stmt {
3711            if let SelectCore::Select { from, .. } = &s.body.select {
3712                let from = from.as_ref().expect("FROM clause");
3713                assert_eq!(from.joins[0].join_type.kind, JoinKind::Cross);
3714                // Cross joins must NOT have an ON or USING constraint.
3715                assert!(from.joins[0].constraint.is_none());
3716            } else {
3717                unreachable!("expected Select core");
3718            }
3719        } else {
3720            unreachable!("expected Select");
3721        }
3722    }
3723
3724    #[test]
3725    fn test_natural_join() {
3726        // NATURAL JOIN uses shared column names for implicit ON.
3727        let stmt = parse_one("SELECT * FROM a NATURAL JOIN b");
3728        if let Statement::Select(s) = stmt {
3729            if let SelectCore::Select { from, .. } = &s.body.select {
3730                let from = from.as_ref().expect("FROM clause");
3731                assert!(from.joins[0].join_type.natural);
3732            } else {
3733                unreachable!("expected Select core");
3734            }
3735        } else {
3736            unreachable!("expected Select");
3737        }
3738    }
3739
3740    #[test]
3741    fn test_using_clause() {
3742        // JOIN USING joins on specified shared columns.
3743        let stmt = parse_one("SELECT * FROM a JOIN b USING (id, name)");
3744        if let Statement::Select(s) = stmt {
3745            if let SelectCore::Select { from, .. } = &s.body.select {
3746                let from = from.as_ref().expect("FROM clause");
3747                match &from.joins[0].constraint {
3748                    Some(JoinConstraint::Using(cols)) => {
3749                        assert_eq!(cols.len(), 2);
3750                        assert_eq!(cols[0], "id");
3751                        assert_eq!(cols[1], "name");
3752                    }
3753                    other => unreachable!("expected USING constraint, got {other:?}"),
3754                }
3755            } else {
3756                unreachable!("expected Select core");
3757            }
3758        } else {
3759            unreachable!("expected Select");
3760        }
3761    }
3762
3763    #[test]
3764    fn test_cte_basic() {
3765        // WITH clause defines reusable named subquery.
3766        let stmt = parse_one("WITH cte AS (SELECT 1 AS x) SELECT * FROM cte");
3767        if let Statement::Select(s) = stmt {
3768            let with = s.with.as_ref().expect("WITH clause");
3769            assert!(!with.recursive);
3770            assert_eq!(with.ctes.len(), 1);
3771            assert_eq!(with.ctes[0].name, "cte");
3772        } else {
3773            unreachable!("expected Select");
3774        }
3775    }
3776
3777    #[test]
3778    fn test_cte_recursive_union_all() {
3779        // Recursive CTE with UNION ALL generates rows.
3780        let stmt = parse_one(
3781            "WITH RECURSIVE cnt(x) AS (\
3782             SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x<10\
3783             ) SELECT x FROM cnt",
3784        );
3785        if let Statement::Select(s) = stmt {
3786            let with = s.with.as_ref().expect("WITH clause");
3787            assert!(with.recursive);
3788            assert_eq!(with.ctes[0].name, "cnt");
3789            // Verify the CTE body contains a UNION ALL compound.
3790            let cte_body = &with.ctes[0].query;
3791            assert_eq!(cte_body.body.compounds.len(), 1);
3792            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::UnionAll);
3793        } else {
3794            unreachable!("expected Select");
3795        }
3796    }
3797
3798    #[test]
3799    fn test_cte_recursive_union_cycle_detection() {
3800        // Recursive CTE with UNION (not UNION ALL) detects cycles via dedup.
3801        let stmt = parse_one(
3802            "WITH RECURSIVE paths(a, b) AS (\
3803             SELECT src, dst FROM edges \
3804             UNION \
3805             SELECT p.a, e.dst FROM paths p JOIN edges e ON p.b = e.src\
3806             ) SELECT * FROM paths",
3807        );
3808        if let Statement::Select(s) = stmt {
3809            let with = s.with.as_ref().expect("WITH clause");
3810            assert!(with.recursive);
3811            // UNION (not UNION ALL) provides implicit cycle detection.
3812            let cte_body = &with.ctes[0].query;
3813            assert_eq!(cte_body.body.compounds.len(), 1);
3814            assert_eq!(cte_body.body.compounds[0].0, CompoundOp::Union);
3815        } else {
3816            unreachable!("expected Select");
3817        }
3818    }
3819
3820    #[test]
3821    fn test_cte_materialized_hint() {
3822        // MATERIALIZED forces single evaluation.
3823        let stmt = parse_one("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
3824        if let Statement::Select(s) = stmt {
3825            let with = s.with.as_ref().expect("WITH clause");
3826            assert_eq!(
3827                with.ctes[0].materialized,
3828                Some(CteMaterialized::Materialized)
3829            );
3830        } else {
3831            unreachable!("expected Select");
3832        }
3833    }
3834
3835    #[test]
3836    fn test_cte_not_materialized_hint() {
3837        // NOT MATERIALIZED allows inlining.
3838        let stmt = parse_one("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
3839        if let Statement::Select(s) = stmt {
3840            let with = s.with.as_ref().expect("WITH clause");
3841            assert_eq!(
3842                with.ctes[0].materialized,
3843                Some(CteMaterialized::NotMaterialized)
3844            );
3845        } else {
3846            unreachable!("expected Select");
3847        }
3848    }
3849
3850    #[test]
3851    fn test_window_partition_by() {
3852        // PARTITION BY correctly groups window function output.
3853        let stmt = parse_one("SELECT sum(x) OVER (PARTITION BY dept) FROM emp");
3854        if let Statement::Select(s) = stmt {
3855            if let SelectCore::Select { columns, .. } = &s.body.select {
3856                match &columns[0] {
3857                    ResultColumn::Expr {
3858                        expr:
3859                            Expr::FunctionCall {
3860                                over: Some(over), ..
3861                            },
3862                        ..
3863                    } => {
3864                        assert_eq!(over.partition_by.len(), 1);
3865                    }
3866                    other => unreachable!("expected window function, got {other:?}"),
3867                }
3868            } else {
3869                unreachable!("expected Select core");
3870            }
3871        } else {
3872            unreachable!("expected Select");
3873        }
3874    }
3875
3876    #[test]
3877    fn test_window_order_by() {
3878        // ORDER BY within window function controls row ordering.
3879        let stmt = parse_one("SELECT row_number() OVER (ORDER BY salary DESC) FROM emp");
3880        if let Statement::Select(s) = stmt {
3881            if let SelectCore::Select { columns, .. } = &s.body.select {
3882                match &columns[0] {
3883                    ResultColumn::Expr {
3884                        expr:
3885                            Expr::FunctionCall {
3886                                over: Some(over), ..
3887                            },
3888                        ..
3889                    } => {
3890                        assert_eq!(over.order_by.len(), 1);
3891                        assert_eq!(over.order_by[0].direction, Some(SortDirection::Desc));
3892                    }
3893                    other => unreachable!("expected window function, got {other:?}"),
3894                }
3895            } else {
3896                unreachable!("expected Select core");
3897            }
3898        } else {
3899            unreachable!("expected Select");
3900        }
3901    }
3902
3903    #[test]
3904    fn test_window_frame_rows() {
3905        // ROWS frame spec limits window to specified row range.
3906        let stmt = parse_one(
3907            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t",
3908        );
3909        if let Statement::Select(s) = stmt {
3910            if let SelectCore::Select { columns, .. } = &s.body.select {
3911                match &columns[0] {
3912                    ResultColumn::Expr {
3913                        expr:
3914                            Expr::FunctionCall {
3915                                over: Some(over), ..
3916                            },
3917                        ..
3918                    } => {
3919                        let frame = over.frame.as_ref().expect("frame spec");
3920                        assert_eq!(frame.frame_type, FrameType::Rows);
3921                        assert!(matches!(frame.start, FrameBound::Preceding(_)));
3922                        assert!(matches!(frame.end, Some(FrameBound::CurrentRow)));
3923                    }
3924                    other => unreachable!("expected window function, got {other:?}"),
3925                }
3926            } else {
3927                unreachable!("expected Select core");
3928            }
3929        } else {
3930            unreachable!("expected Select");
3931        }
3932    }
3933
3934    #[test]
3935    fn test_window_exclude_current_row() {
3936        // EXCLUDE CURRENT ROW omits current row from frame.
3937        let stmt = parse_one(
3938            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3939             UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) FROM t",
3940        );
3941        if let Statement::Select(s) = stmt {
3942            if let SelectCore::Select { columns, .. } = &s.body.select {
3943                match &columns[0] {
3944                    ResultColumn::Expr {
3945                        expr:
3946                            Expr::FunctionCall {
3947                                over: Some(over), ..
3948                            },
3949                        ..
3950                    } => {
3951                        let frame = over.frame.as_ref().expect("frame spec");
3952                        assert_eq!(frame.exclude, Some(FrameExclude::CurrentRow));
3953                    }
3954                    other => unreachable!("expected window function, got {other:?}"),
3955                }
3956            } else {
3957                unreachable!("expected Select core");
3958            }
3959        } else {
3960            unreachable!("expected Select");
3961        }
3962    }
3963
3964    #[test]
3965    fn test_window_exclude_ties() {
3966        // EXCLUDE TIES omits peers of current row.
3967        let stmt = parse_one(
3968            "SELECT sum(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND \
3969             UNBOUNDED FOLLOWING EXCLUDE TIES) FROM t",
3970        );
3971        if let Statement::Select(s) = stmt {
3972            if let SelectCore::Select { columns, .. } = &s.body.select {
3973                match &columns[0] {
3974                    ResultColumn::Expr {
3975                        expr:
3976                            Expr::FunctionCall {
3977                                over: Some(over), ..
3978                            },
3979                        ..
3980                    } => {
3981                        let frame = over.frame.as_ref().expect("frame spec");
3982                        assert_eq!(frame.exclude, Some(FrameExclude::Ties));
3983                    }
3984                    other => unreachable!("expected window function, got {other:?}"),
3985                }
3986            } else {
3987                unreachable!("expected Select core");
3988            }
3989        } else {
3990            unreachable!("expected Select");
3991        }
3992    }
3993
3994    #[test]
3995    fn test_nulls_first_asc() {
3996        // NULLS FIRST with ASC puts NULLs before non-NULL values.
3997        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS FIRST");
3998        if let Statement::Select(s) = stmt {
3999            assert_eq!(s.order_by.len(), 1);
4000            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
4001            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::First));
4002        } else {
4003            unreachable!("expected Select");
4004        }
4005    }
4006
4007    #[test]
4008    fn test_nulls_last_asc() {
4009        // NULLS LAST with ASC puts NULLs after non-NULL values.
4010        let stmt = parse_one("SELECT a FROM t ORDER BY a ASC NULLS LAST");
4011        if let Statement::Select(s) = stmt {
4012            assert_eq!(s.order_by.len(), 1);
4013            assert_eq!(s.order_by[0].direction, Some(SortDirection::Asc));
4014            assert_eq!(s.order_by[0].nulls, Some(NullsOrder::Last));
4015        } else {
4016            unreachable!("expected Select");
4017        }
4018    }
4019
4020    #[test]
4021    fn test_distinct_deduplicates() {
4022        // SELECT DISTINCT removes duplicate rows (parser-level: keyword present).
4023        let stmt = parse_one("SELECT DISTINCT a, b FROM t");
4024        if let Statement::Select(s) = stmt {
4025            if let SelectCore::Select { distinct, .. } = &s.body.select {
4026                assert_eq!(*distinct, Distinctness::Distinct);
4027            } else {
4028                unreachable!("expected Select core");
4029            }
4030        } else {
4031            unreachable!("expected Select");
4032        }
4033    }
4034
4035    #[test]
4036    fn test_limit_offset() {
4037        // LIMIT N OFFSET M skips M rows and returns N.
4038        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET 20");
4039        if let Statement::Select(s) = stmt {
4040            let limit = s.limit.expect("LIMIT clause");
4041            assert!(matches!(
4042                limit.limit,
4043                Expr::Literal(Literal::Integer(10), _)
4044            ));
4045            assert!(matches!(
4046                limit.offset,
4047                Some(Expr::Literal(Literal::Integer(20), _))
4048            ));
4049        } else {
4050            unreachable!("expected Select");
4051        }
4052    }
4053
4054    #[test]
4055    fn test_limit_comma_syntax() {
4056        // LIMIT offset,count (MySQL syntax) — offset first, count second.
4057        let stmt = parse_one("SELECT a FROM t LIMIT 5, 10");
4058        if let Statement::Select(s) = stmt {
4059            let limit = s.limit.expect("LIMIT clause");
4060            // In MySQL syntax, LIMIT 5, 10 means offset=5, count=10.
4061            assert!(matches!(
4062                limit.limit,
4063                Expr::Literal(Literal::Integer(10), _)
4064            ));
4065            assert!(matches!(
4066                limit.offset,
4067                Some(Expr::Literal(Literal::Integer(5), _))
4068            ));
4069        } else {
4070            unreachable!("expected Select");
4071        }
4072    }
4073
4074    #[test]
4075    fn test_negative_limit_unlimited() {
4076        // Negative LIMIT means unlimited (parser accepts negative literal).
4077        let stmt = parse_one("SELECT a FROM t LIMIT -1");
4078        if let Statement::Select(s) = stmt {
4079            let limit = s.limit.expect("LIMIT clause");
4080            // Parser may represent -1 as UnaryOp::Negate on Integer(1),
4081            // or as Integer(-1). Either is valid.
4082            match &limit.limit {
4083                Expr::UnaryOp {
4084                    op: fsqlite_ast::UnaryOp::Negate,
4085                    ..
4086                } => {}
4087                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
4088                other => unreachable!("expected negative limit expression, got {other:?}"),
4089            }
4090        } else {
4091            unreachable!("expected Select");
4092        }
4093    }
4094
4095    #[test]
4096    fn test_negative_offset_zero() {
4097        // Negative OFFSET treated as zero (parser accepts negative literal).
4098        let stmt = parse_one("SELECT a FROM t LIMIT 10 OFFSET -5");
4099        if let Statement::Select(s) = stmt {
4100            let limit = s.limit.expect("LIMIT clause");
4101            assert!(limit.offset.is_some());
4102            match limit.offset.as_ref().unwrap() {
4103                Expr::UnaryOp {
4104                    op: fsqlite_ast::UnaryOp::Negate,
4105                    ..
4106                } => {}
4107                Expr::Literal(Literal::Integer(n), _) if *n < 0 => {}
4108                other => unreachable!("expected negative offset expression, got {other:?}"),
4109            }
4110        } else {
4111            unreachable!("expected Select");
4112        }
4113    }
4114
4115    #[test]
4116    fn test_current_date_constant() {
4117        // current_date is parsed as a literal keyword.
4118        let stmt = parse_one("SELECT CURRENT_DATE");
4119        if let Statement::Select(s) = stmt {
4120            if let SelectCore::Select { columns, .. } = &s.body.select {
4121                match &columns[0] {
4122                    ResultColumn::Expr {
4123                        expr: Expr::Literal(Literal::CurrentDate, _),
4124                        ..
4125                    } => {}
4126                    other => unreachable!("expected CURRENT_DATE literal, got {other:?}"),
4127                }
4128            } else {
4129                unreachable!("expected Select core");
4130            }
4131        } else {
4132            unreachable!("expected Select");
4133        }
4134    }
4135
4136    #[test]
4137    fn test_current_time_constant() {
4138        // current_time is parsed as a literal keyword.
4139        let stmt = parse_one("SELECT CURRENT_TIME");
4140        if let Statement::Select(s) = stmt {
4141            if let SelectCore::Select { columns, .. } = &s.body.select {
4142                match &columns[0] {
4143                    ResultColumn::Expr {
4144                        expr: Expr::Literal(Literal::CurrentTime, _),
4145                        ..
4146                    } => {}
4147                    other => unreachable!("expected CURRENT_TIME literal, got {other:?}"),
4148                }
4149            } else {
4150                unreachable!("expected Select core");
4151            }
4152        } else {
4153            unreachable!("expected Select");
4154        }
4155    }
4156
4157    #[test]
4158    fn test_current_timestamp_constant() {
4159        // current_timestamp is parsed as a literal keyword.
4160        let stmt = parse_one("SELECT CURRENT_TIMESTAMP");
4161        if let Statement::Select(s) = stmt {
4162            if let SelectCore::Select { columns, .. } = &s.body.select {
4163                match &columns[0] {
4164                    ResultColumn::Expr {
4165                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
4166                        ..
4167                    } => {}
4168                    other => unreachable!("expected CURRENT_TIMESTAMP literal, got {other:?}"),
4169                }
4170            } else {
4171                unreachable!("expected Select core");
4172            }
4173        } else {
4174            unreachable!("expected Select");
4175        }
4176    }
4177
4178    #[test]
4179    fn test_date_constants_evaluated_once_per_statement() {
4180        // Parser-level: all three date/time constants parse as distinct Literal variants.
4181        // Runtime guarantee (evaluated once per stmt, not per row) is verified at VDBE level.
4182        let stmt = parse_one("SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP FROM t");
4183        if let Statement::Select(s) = stmt {
4184            if let SelectCore::Select { columns, .. } = &s.body.select {
4185                assert_eq!(columns.len(), 3);
4186                assert!(matches!(
4187                    &columns[0],
4188                    ResultColumn::Expr {
4189                        expr: Expr::Literal(Literal::CurrentDate, _),
4190                        ..
4191                    }
4192                ));
4193                assert!(matches!(
4194                    &columns[1],
4195                    ResultColumn::Expr {
4196                        expr: Expr::Literal(Literal::CurrentTime, _),
4197                        ..
4198                    }
4199                ));
4200                assert!(matches!(
4201                    &columns[2],
4202                    ResultColumn::Expr {
4203                        expr: Expr::Literal(Literal::CurrentTimestamp, _),
4204                        ..
4205                    }
4206                ));
4207            } else {
4208                unreachable!("expected Select core");
4209            }
4210        } else {
4211            unreachable!("expected Select");
4212        }
4213    }
4214
4215    #[test]
4216    fn test_indexed_by_hint() {
4217        // FROM t1 INDEXED BY idx forces specified index.
4218        let stmt = parse_one("SELECT * FROM t INDEXED BY idx_t");
4219        if let Statement::Select(s) = stmt {
4220            if let SelectCore::Select { from, .. } = &s.body.select {
4221                let from = from.as_ref().expect("FROM clause");
4222                match &from.source {
4223                    TableOrSubquery::Table {
4224                        index_hint: Some(IndexHint::IndexedBy(name)),
4225                        ..
4226                    } => assert_eq!(name, "idx_t"),
4227                    other => unreachable!("expected indexed table source, got {other:?}"),
4228                }
4229            } else {
4230                unreachable!("expected Select core");
4231            }
4232        } else {
4233            unreachable!("expected Select");
4234        }
4235    }
4236
4237    #[test]
4238    fn test_not_indexed_hint() {
4239        // FROM t1 NOT INDEXED prevents index use.
4240        let stmt = parse_one("SELECT * FROM t NOT INDEXED");
4241        if let Statement::Select(s) = stmt {
4242            if let SelectCore::Select { from, .. } = &s.body.select {
4243                let from = from.as_ref().expect("FROM clause");
4244                match &from.source {
4245                    TableOrSubquery::Table {
4246                        index_hint: Some(IndexHint::NotIndexed),
4247                        ..
4248                    } => {}
4249                    other => unreachable!("expected not-indexed table source, got {other:?}"),
4250                }
4251            } else {
4252                unreachable!("expected Select core");
4253            }
4254        } else {
4255            unreachable!("expected Select");
4256        }
4257    }
4258
4259    #[test]
4260    fn test_table_valued_function_in_from() {
4261        // FROM generate_series(1,100) works as table source.
4262        let stmt = parse_one("SELECT * FROM generate_series(1, 100) AS gs");
4263        if let Statement::Select(s) = stmt {
4264            if let SelectCore::Select { from, .. } = &s.body.select {
4265                let from = from.as_ref().expect("FROM clause");
4266                match &from.source {
4267                    TableOrSubquery::TableFunction { name, args, alias } => {
4268                        assert_eq!(name, "generate_series");
4269                        assert_eq!(args.len(), 2);
4270                        assert_eq!(alias.as_deref(), Some("gs"));
4271                    }
4272                    other => unreachable!("expected table-valued function source, got {other:?}"),
4273                }
4274            } else {
4275                unreachable!("expected Select core");
4276            }
4277        } else {
4278            unreachable!("expected Select");
4279        }
4280    }
4281
4282    // -----------------------------------------------------------------------
4283    // bd-1llo §12.2-12.4 INSERT + UPDATE + DELETE DML parsing tests
4284    // -----------------------------------------------------------------------
4285
4286    #[test]
4287    fn test_insert_values_single() {
4288        let stmt = parse_one("INSERT INTO t (a, b, c) VALUES (1, 'hello', 3.14)");
4289        if let Statement::Insert(i) = stmt {
4290            assert_eq!(i.columns, vec!["a", "b", "c"]);
4291            if let InsertSource::Values(rows) = &i.source {
4292                assert_eq!(rows.len(), 1);
4293                assert_eq!(rows[0].len(), 3);
4294            } else {
4295                unreachable!("expected Values source");
4296            }
4297        } else {
4298            unreachable!("expected Insert");
4299        }
4300    }
4301
4302    #[test]
4303    fn test_insert_values_multi() {
4304        let stmt = parse_one("INSERT INTO t (x, y) VALUES (1, 2), (3, 4), (5, 6)");
4305        if let Statement::Insert(i) = stmt {
4306            if let InsertSource::Values(rows) = &i.source {
4307                assert_eq!(rows.len(), 3);
4308                for row in rows {
4309                    assert_eq!(row.len(), 2);
4310                }
4311            } else {
4312                unreachable!("expected Values source");
4313            }
4314        } else {
4315            unreachable!("expected Insert");
4316        }
4317    }
4318
4319    #[test]
4320    fn test_insert_from_select() {
4321        let stmt = parse_one("INSERT INTO t2 (a, b) SELECT x, y FROM t1 WHERE x > 0");
4322        if let Statement::Insert(i) = stmt {
4323            assert!(matches!(i.source, InsertSource::Select(_)));
4324            assert_eq!(i.columns, vec!["a", "b"]);
4325        } else {
4326            unreachable!("expected Insert");
4327        }
4328    }
4329
4330    #[test]
4331    fn test_insert_from_select_without_from_clause() {
4332        let stmt = parse_one("INSERT INTO t (a) SELECT 1");
4333        if let Statement::Insert(i) = stmt {
4334            if let InsertSource::Select(select) = &i.source {
4335                if let SelectCore::Select { from, columns, .. } = &select.body.select {
4336                    assert!(from.is_none(), "SELECT 1 should parse without FROM");
4337                    assert_eq!(columns.len(), 1);
4338                } else {
4339                    unreachable!("expected Select core");
4340                }
4341            } else {
4342                unreachable!("expected Select source");
4343            }
4344        } else {
4345            unreachable!("expected Insert");
4346        }
4347    }
4348
4349    #[test]
4350    fn test_insert_from_select_subquery_source() {
4351        let stmt = parse_one("INSERT INTO t (a) SELECT sub.x FROM (SELECT 1 AS x) AS sub");
4352        if let Statement::Insert(i) = stmt {
4353            if let InsertSource::Select(select) = &i.source {
4354                if let SelectCore::Select { from, .. } = &select.body.select {
4355                    let from = from.as_ref().expect("FROM clause");
4356                    match &from.source {
4357                        TableOrSubquery::Subquery { alias, .. } => {
4358                            assert_eq!(alias.as_deref(), Some("sub"));
4359                        }
4360                        other => unreachable!("expected subquery source, got {other:?}"),
4361                    }
4362                } else {
4363                    unreachable!("expected Select core");
4364                }
4365            } else {
4366                unreachable!("expected Select source");
4367            }
4368        } else {
4369            unreachable!("expected Insert");
4370        }
4371    }
4372
4373    #[test]
4374    fn test_insert_from_select_table_function_source() {
4375        let stmt = parse_one("INSERT INTO t (a) SELECT gs.value FROM generate_series(1, 3) AS gs");
4376        if let Statement::Insert(i) = stmt {
4377            if let InsertSource::Select(select) = &i.source {
4378                if let SelectCore::Select { from, .. } = &select.body.select {
4379                    let from = from.as_ref().expect("FROM clause");
4380                    match &from.source {
4381                        TableOrSubquery::TableFunction { name, args, alias } => {
4382                            assert_eq!(name, "generate_series");
4383                            assert_eq!(args.len(), 2);
4384                            assert_eq!(alias.as_deref(), Some("gs"));
4385                        }
4386                        other => unreachable!("expected table function source, got {other:?}"),
4387                    }
4388                } else {
4389                    unreachable!("expected Select core");
4390                }
4391            } else {
4392                unreachable!("expected Select source");
4393            }
4394        } else {
4395            unreachable!("expected Insert");
4396        }
4397    }
4398
4399    #[test]
4400    fn test_insert_default_values() {
4401        let stmt = parse_one("INSERT INTO t DEFAULT VALUES");
4402        if let Statement::Insert(i) = stmt {
4403            assert!(matches!(i.source, InsertSource::DefaultValues));
4404            assert!(i.columns.is_empty());
4405        } else {
4406            unreachable!("expected Insert");
4407        }
4408    }
4409
4410    #[test]
4411    fn test_insert_or_abort() {
4412        let stmt = parse_one("INSERT OR ABORT INTO t (a) VALUES (1)");
4413        if let Statement::Insert(i) = stmt {
4414            assert_eq!(i.or_conflict, Some(ConflictAction::Abort));
4415        } else {
4416            unreachable!("expected Insert");
4417        }
4418    }
4419
4420    #[test]
4421    fn test_insert_or_rollback() {
4422        let stmt = parse_one("INSERT OR ROLLBACK INTO t (a) VALUES (1)");
4423        if let Statement::Insert(i) = stmt {
4424            assert_eq!(i.or_conflict, Some(ConflictAction::Rollback));
4425        } else {
4426            unreachable!("expected Insert");
4427        }
4428    }
4429
4430    #[test]
4431    fn test_insert_or_fail() {
4432        let stmt = parse_one("INSERT OR FAIL INTO t (a) VALUES (1)");
4433        if let Statement::Insert(i) = stmt {
4434            assert_eq!(i.or_conflict, Some(ConflictAction::Fail));
4435        } else {
4436            unreachable!("expected Insert");
4437        }
4438    }
4439
4440    #[test]
4441    fn test_insert_or_ignore() {
4442        let stmt = parse_one("INSERT OR IGNORE INTO t (a) VALUES (1)");
4443        if let Statement::Insert(i) = stmt {
4444            assert_eq!(i.or_conflict, Some(ConflictAction::Ignore));
4445        } else {
4446            unreachable!("expected Insert");
4447        }
4448    }
4449
4450    #[test]
4451    fn test_insert_or_replace() {
4452        // Both INSERT OR REPLACE and REPLACE INTO forms
4453        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1)");
4454        if let Statement::Insert(i) = stmt {
4455            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
4456        } else {
4457            unreachable!("expected Insert");
4458        }
4459    }
4460
4461    #[test]
4462    fn test_upsert_do_update() {
4463        let stmt = parse_one(
4464            "INSERT INTO t (a, b) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET b = excluded.b",
4465        );
4466        if let Statement::Insert(i) = stmt {
4467            assert_eq!(i.upsert.len(), 1);
4468            assert!(i.upsert[0].target.is_some());
4469            match &i.upsert[0].action {
4470                UpsertAction::Update {
4471                    assignments,
4472                    where_clause,
4473                } => {
4474                    assert_eq!(assignments.len(), 1);
4475                    assert!(where_clause.is_none());
4476                }
4477                UpsertAction::Nothing => unreachable!("expected Update action"),
4478            }
4479        } else {
4480            unreachable!("expected Insert");
4481        }
4482    }
4483
4484    #[test]
4485    fn test_upsert_do_nothing() {
4486        let stmt = parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
4487        if let Statement::Insert(i) = stmt {
4488            assert_eq!(i.upsert.len(), 1);
4489            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
4490        } else {
4491            unreachable!("expected Insert");
4492        }
4493    }
4494
4495    #[test]
4496    fn test_upsert_excluded_pseudo_table() {
4497        let stmt = parse_one(
4498            "INSERT INTO t (a, b) VALUES (1, 2) \
4499             ON CONFLICT (a) DO UPDATE SET b = excluded.b, a = excluded.a + 1",
4500        );
4501        if let Statement::Insert(i) = stmt {
4502            assert_eq!(i.upsert.len(), 1);
4503            if let UpsertAction::Update { assignments, .. } = &i.upsert[0].action {
4504                assert_eq!(assignments.len(), 2);
4505                // Verify excluded.b reference in first assignment
4506                match &assignments[0].value {
4507                    Expr::Column(col, _) => {
4508                        assert_eq!(col.table.as_deref(), Some("excluded"));
4509                        assert_eq!(col.column, "b");
4510                    }
4511                    other => unreachable!("expected Column ref to excluded.b, got {other:?}"),
4512                }
4513            } else {
4514                unreachable!("expected Update action");
4515            }
4516        } else {
4517            unreachable!("expected Insert");
4518        }
4519    }
4520
4521    #[test]
4522    fn test_upsert_multiple_on_conflict() {
4523        let stmt = parse_one(
4524            "INSERT INTO t (a, b) VALUES (1, 2) \
4525             ON CONFLICT (a) DO NOTHING \
4526             ON CONFLICT (b) DO UPDATE SET a = excluded.a",
4527        );
4528        if let Statement::Insert(i) = stmt {
4529            assert_eq!(i.upsert.len(), 2);
4530            assert!(matches!(i.upsert[0].action, UpsertAction::Nothing));
4531            assert!(matches!(i.upsert[1].action, UpsertAction::Update { .. }));
4532        } else {
4533            unreachable!("expected Insert");
4534        }
4535    }
4536
4537    #[test]
4538    fn test_upsert_where_on_conflict_target() {
4539        let stmt = parse_one(
4540            "INSERT INTO t (a, b) VALUES (1, 2) \
4541             ON CONFLICT (a) WHERE a > 0 DO UPDATE SET b = excluded.b WHERE b < 100",
4542        );
4543        if let Statement::Insert(i) = stmt {
4544            assert_eq!(i.upsert.len(), 1);
4545            let target = i.upsert[0].target.as_ref().expect("conflict target");
4546            assert!(target.where_clause.is_some(), "target WHERE missing");
4547            if let UpsertAction::Update { where_clause, .. } = &i.upsert[0].action {
4548                assert!(where_clause.is_some(), "action WHERE missing");
4549            } else {
4550                unreachable!("expected Update action");
4551            }
4552        } else {
4553            unreachable!("expected Insert");
4554        }
4555    }
4556
4557    #[test]
4558    fn test_returning_insert() {
4559        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, rowid");
4560        if let Statement::Insert(i) = stmt {
4561            assert_eq!(i.returning.len(), 3);
4562        } else {
4563            unreachable!("expected Insert");
4564        }
4565    }
4566
4567    #[test]
4568    fn test_returning_insert_select_with_semicolon() {
4569        let stmt = parse_one("INSERT INTO t2 SELECT * FROM t RETURNING *;");
4570        if let Statement::Insert(i) = stmt {
4571            assert!(matches!(i.source, InsertSource::Select(_)));
4572            assert_eq!(i.returning.len(), 1);
4573            assert!(matches!(i.returning[0], ResultColumn::Star));
4574        } else {
4575            unreachable!("expected Insert");
4576        }
4577    }
4578
4579    #[test]
4580    fn test_returning_reflects_before_triggers() {
4581        // Parser-level: verify RETURNING clause parses alongside trigger-affected DML
4582        // Runtime verification that RETURNING reflects BEFORE-trigger modifications
4583        // is deferred to VDBE/engine tests
4584        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING a AS modified_a");
4585        if let Statement::Insert(i) = stmt {
4586            assert_eq!(i.returning.len(), 1);
4587            match &i.returning[0] {
4588                ResultColumn::Expr { alias, .. } => {
4589                    assert_eq!(alias.as_deref(), Some("modified_a"));
4590                }
4591                other => unreachable!("expected Expr result column, got {other:?}"),
4592            }
4593        } else {
4594            unreachable!("expected Insert");
4595        }
4596    }
4597
4598    #[test]
4599    fn test_returning_ignores_after_triggers() {
4600        // Parser-level: verify RETURNING * parses on INSERT with conflict clause
4601        // Runtime verification that RETURNING ignores AFTER-trigger modifications
4602        // is deferred to VDBE/engine tests
4603        let stmt = parse_one("INSERT OR REPLACE INTO t (a) VALUES (1) RETURNING *");
4604        if let Statement::Insert(i) = stmt {
4605            assert_eq!(i.or_conflict, Some(ConflictAction::Replace));
4606            assert_eq!(i.returning.len(), 1);
4607            assert!(matches!(i.returning[0], ResultColumn::Star));
4608        } else {
4609            unreachable!("expected Insert");
4610        }
4611    }
4612
4613    #[test]
4614    fn test_returning_after_before_trigger_modify() {
4615        // Parser-level: verify RETURNING with multiple column expressions
4616        // Runtime verification of BEFORE trigger modifying returned values
4617        // is deferred to VDBE/engine tests
4618        let stmt = parse_one("INSERT INTO t (a, b) VALUES (1, 2) RETURNING a, b, a + b AS total");
4619        if let Statement::Insert(i) = stmt {
4620            assert_eq!(i.returning.len(), 3);
4621            match &i.returning[2] {
4622                ResultColumn::Expr {
4623                    alias: Some(alias), ..
4624                } => assert_eq!(alias, "total"),
4625                other => unreachable!("expected aliased expression, got {other:?}"),
4626            }
4627        } else {
4628            unreachable!("expected Insert");
4629        }
4630    }
4631
4632    #[test]
4633    fn test_returning_before_trigger_raise_abort() {
4634        // Parser-level: RAISE(ABORT, ...) is a valid expression in trigger bodies;
4635        // here we verify RETURNING parses on multi-row INSERT (runtime abort
4636        // behavior verified in VDBE/engine tests)
4637        let stmt = parse_one("INSERT INTO t (a) VALUES (1), (2), (3) RETURNING a");
4638        if let Statement::Insert(i) = stmt {
4639            if let InsertSource::Values(rows) = &i.source {
4640                assert_eq!(rows.len(), 3);
4641            } else {
4642                unreachable!("expected Values source");
4643            }
4644            assert_eq!(i.returning.len(), 1);
4645        } else {
4646            unreachable!("expected Insert");
4647        }
4648    }
4649
4650    #[test]
4651    fn test_returning_instead_of_view() {
4652        // Parser-level: INSERT into a view name parses the same as INSERT into a table
4653        // Runtime INSTEAD OF trigger behavior is verified in VDBE/engine tests
4654        let stmt = parse_one("INSERT INTO v (a, b) VALUES (1, 2) RETURNING *");
4655        if let Statement::Insert(i) = stmt {
4656            assert_eq!(i.table.name, "v");
4657            assert!(!i.returning.is_empty());
4658        } else {
4659            unreachable!("expected Insert");
4660        }
4661    }
4662
4663    #[test]
4664    fn test_returning_autoincrement_with_trigger() {
4665        // Parser-level: verify RETURNING can reference rowid on INSERT
4666        // Runtime autoincrement + trigger interaction is verified in VDBE/engine tests
4667        let stmt = parse_one("INSERT INTO t (name) VALUES ('test') RETURNING rowid, name");
4668        if let Statement::Insert(i) = stmt {
4669            assert_eq!(i.returning.len(), 2);
4670        } else {
4671            unreachable!("expected Insert");
4672        }
4673    }
4674
4675    #[test]
4676    fn test_update_set_where() {
4677        let stmt = parse_one("UPDATE t SET a = 1, b = 'hello' WHERE id = 42");
4678        if let Statement::Update(u) = stmt {
4679            assert_eq!(u.assignments.len(), 2);
4680            assert!(u.where_clause.is_some());
4681            assert!(u.from.is_none());
4682        } else {
4683            unreachable!("expected Update");
4684        }
4685    }
4686
4687    #[test]
4688    fn test_update_from_join() {
4689        let stmt = parse_one("UPDATE t1 SET a = t2.x FROM t2 WHERE t1.id = t2.id");
4690        if let Statement::Update(u) = stmt {
4691            assert_eq!(u.assignments.len(), 1);
4692            assert!(u.from.is_some());
4693            assert!(u.where_clause.is_some());
4694        } else {
4695            unreachable!("expected Update");
4696        }
4697    }
4698
4699    #[test]
4700    fn test_update_from_multi_match() {
4701        // Parser-level: UPDATE FROM with a join that could produce multiple matches
4702        // Runtime behavior (arbitrary row chosen) verified in VDBE/engine tests
4703        let stmt = parse_one(
4704            "UPDATE t1 SET val = src.val FROM src \
4705             INNER JOIN mapping ON mapping.src_id = src.id \
4706             WHERE t1.id = mapping.dst_id",
4707        );
4708        if let Statement::Update(u) = stmt {
4709            assert!(u.from.is_some());
4710            let from = u.from.as_ref().unwrap();
4711            assert!(!from.joins.is_empty(), "expected JOIN in FROM clause");
4712        } else {
4713            unreachable!("expected Update");
4714        }
4715    }
4716
4717    #[test]
4718    fn test_update_order_by_limit() {
4719        let stmt = parse_one("UPDATE t SET a = a + 1 ORDER BY b DESC LIMIT 10");
4720        if let Statement::Update(u) = stmt {
4721            assert_eq!(u.order_by.len(), 1);
4722            assert_eq!(u.order_by[0].direction, Some(SortDirection::Desc));
4723            assert!(u.limit.is_some());
4724        } else {
4725            unreachable!("expected Update");
4726        }
4727    }
4728
4729    #[test]
4730    fn test_update_returning() {
4731        let stmt = parse_one("UPDATE t SET a = 1 WHERE id = 5 RETURNING id, a AS new_a");
4732        if let Statement::Update(u) = stmt {
4733            assert_eq!(u.returning.len(), 2);
4734            match &u.returning[1] {
4735                ResultColumn::Expr {
4736                    alias: Some(alias), ..
4737                } => assert_eq!(alias, "new_a"),
4738                other => unreachable!("expected aliased result column, got {other:?}"),
4739            }
4740        } else {
4741            unreachable!("expected Update");
4742        }
4743    }
4744
4745    #[test]
4746    fn test_update_or_ignore() {
4747        let stmt = parse_one("UPDATE OR IGNORE t SET a = 1 WHERE id = 5");
4748        if let Statement::Update(u) = stmt {
4749            assert_eq!(u.or_conflict, Some(ConflictAction::Ignore));
4750            assert!(u.where_clause.is_some());
4751        } else {
4752            unreachable!("expected Update");
4753        }
4754    }
4755
4756    #[test]
4757    fn test_delete_where() {
4758        let stmt = parse_one("DELETE FROM t WHERE id = 42 AND active = 0");
4759        if let Statement::Delete(d) = stmt {
4760            assert!(d.where_clause.is_some());
4761            assert!(d.returning.is_empty());
4762        } else {
4763            unreachable!("expected Delete");
4764        }
4765    }
4766
4767    #[test]
4768    fn test_delete_order_by_limit() {
4769        let stmt = parse_one("DELETE FROM t ORDER BY created_at ASC LIMIT 100");
4770        if let Statement::Delete(d) = stmt {
4771            assert_eq!(d.order_by.len(), 1);
4772            assert_eq!(d.order_by[0].direction, Some(SortDirection::Asc));
4773            let limit = d.limit.as_ref().expect("LIMIT clause");
4774            assert!(matches!(
4775                limit.limit,
4776                Expr::Literal(Literal::Integer(100), _)
4777            ));
4778        } else {
4779            unreachable!("expected Delete");
4780        }
4781    }
4782
4783    #[test]
4784    fn test_delete_returning() {
4785        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
4786        if let Statement::Delete(d) = stmt {
4787            assert!(d.where_clause.is_some());
4788            assert_eq!(d.returning.len(), 1);
4789            assert!(matches!(d.returning[0], ResultColumn::Star));
4790        } else {
4791            unreachable!("expected Delete");
4792        }
4793    }
4794
4795    #[test]
4796    fn test_delete_bulk_optimization() {
4797        // Parser-level: DELETE without WHERE produces AST with no where_clause
4798        // Runtime bulk-delete optimization (OP_Clear) is verified in VDBE/engine tests
4799        let stmt = parse_one("DELETE FROM t");
4800        if let Statement::Delete(d) = stmt {
4801            assert!(d.where_clause.is_none());
4802            assert!(d.order_by.is_empty());
4803            assert!(d.limit.is_none());
4804            assert!(d.returning.is_empty());
4805        } else {
4806            unreachable!("expected Delete");
4807        }
4808    }
4809
4810    #[test]
4811    fn test_delete_bulk_no_where_fast() {
4812        // Parser-level: confirms DELETE without WHERE parses to minimal AST
4813        // Runtime OP_Clear vs OP_Delete selection is verified in VDBE/engine tests
4814        let stmt = parse_one("DELETE FROM main.t");
4815        if let Statement::Delete(d) = stmt {
4816            assert_eq!(d.table.name.schema.as_deref(), Some("main"));
4817            assert_eq!(d.table.name.name, "t");
4818            assert!(d.where_clause.is_none());
4819        } else {
4820            unreachable!("expected Delete");
4821        }
4822    }
4823
4824    #[test]
4825    fn test_delete_bulk_blocked_by_trigger() {
4826        // Parser-level: DELETE without WHERE from a table that might have triggers
4827        // has the same AST shape (no WHERE). Runtime trigger detection is in the engine.
4828        let stmt = parse_one("DELETE FROM orders");
4829        if let Statement::Delete(d) = stmt {
4830            assert!(d.where_clause.is_none());
4831            assert!(d.returning.is_empty());
4832        } else {
4833            unreachable!("expected Delete");
4834        }
4835    }
4836
4837    #[test]
4838    fn test_delete_bulk_blocked_by_fk() {
4839        // Parser-level: DELETE without WHERE is the same AST regardless of FK constraints.
4840        // Runtime FK-based fallback to row-by-row is verified in VDBE/engine tests.
4841        let stmt = parse_one("DELETE FROM parent_table");
4842        if let Statement::Delete(d) = stmt {
4843            assert!(d.where_clause.is_none());
4844        } else {
4845            unreachable!("expected Delete");
4846        }
4847    }
4848
4849    #[test]
4850    fn test_delete_bulk_changes_count() {
4851        // Parser-level: DELETE without WHERE returning count via changes()
4852        // is the same AST as any unconditional delete. Runtime changes()
4853        // reporting is verified in VDBE/engine tests.
4854        let stmt = parse_one("DELETE FROM t");
4855        if let Statement::Delete(d) = stmt {
4856            assert!(d.where_clause.is_none());
4857        } else {
4858            unreachable!("expected Delete");
4859        }
4860    }
4861
4862    #[test]
4863    fn test_delete_bulk_autoincrement_preserved() {
4864        // Parser-level: DELETE without WHERE on an autoincrement table has
4865        // identical AST to any unconditional delete. Runtime autoincrement
4866        // sequence preservation is verified in VDBE/engine tests.
4867        let stmt = parse_one("DELETE FROM t");
4868        if let Statement::Delete(d) = stmt {
4869            assert!(d.where_clause.is_none());
4870            assert!(d.limit.is_none());
4871        } else {
4872            unreachable!("expected Delete");
4873        }
4874    }
4875
4876    #[test]
4877    fn test_delete_bulk_where_1_not_optimized() {
4878        // Parser-level: DELETE WHERE 1 has a where_clause (unlike bare DELETE),
4879        // so the optimizer cannot use bulk-delete. Verify WHERE is present.
4880        let stmt = parse_one("DELETE FROM t WHERE 1");
4881        if let Statement::Delete(d) = stmt {
4882            assert!(
4883                d.where_clause.is_some(),
4884                "WHERE 1 must produce a where_clause"
4885            );
4886            assert!(matches!(
4887                d.where_clause.as_ref().unwrap(),
4888                Expr::Literal(Literal::Integer(1), _)
4889            ));
4890        } else {
4891            unreachable!("expected Delete");
4892        }
4893    }
4894
4895    // -----------------------------------------------------------------------
4896    // bd-34de §12.5-12.6 DDL: CREATE TABLE + CREATE INDEX parsing tests
4897    // -----------------------------------------------------------------------
4898
4899    #[test]
4900    fn test_create_table_basic() {
4901        let stmt = parse_one("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
4902        if let Statement::CreateTable(ct) = stmt {
4903            assert_eq!(ct.name.name, "users");
4904            assert!(!ct.if_not_exists);
4905            assert!(!ct.temporary);
4906            assert!(!ct.without_rowid);
4907            assert!(!ct.strict);
4908            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4909                assert_eq!(columns.len(), 3);
4910                assert_eq!(columns[0].name, "id");
4911                assert_eq!(columns[1].name, "name");
4912                assert_eq!(columns[2].name, "age");
4913            } else {
4914                unreachable!("expected Columns body");
4915            }
4916        } else {
4917            unreachable!("expected CreateTable");
4918        }
4919    }
4920
4921    #[test]
4922    fn test_create_table_if_not_exists() {
4923        let stmt = parse_one("CREATE TABLE IF NOT EXISTS t (id INTEGER)");
4924        if let Statement::CreateTable(ct) = stmt {
4925            assert!(ct.if_not_exists);
4926        } else {
4927            unreachable!("expected CreateTable");
4928        }
4929    }
4930
4931    #[test]
4932    fn test_create_temp_table() {
4933        let stmt = parse_one("CREATE TEMP TABLE session_data (key TEXT, val BLOB)");
4934        if let Statement::CreateTable(ct) = stmt {
4935            assert!(ct.temporary);
4936        } else {
4937            unreachable!("expected CreateTable");
4938        }
4939    }
4940
4941    #[test]
4942    fn test_create_table_as_select() {
4943        let stmt = parse_one("CREATE TABLE t2 AS SELECT id, name FROM t1 WHERE active = 1");
4944        if let Statement::CreateTable(ct) = stmt {
4945            assert!(matches!(ct.body, CreateTableBody::AsSelect(_)));
4946        } else {
4947            unreachable!("expected CreateTable");
4948        }
4949    }
4950
4951    #[test]
4952    fn test_column_primary_key() {
4953        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY ASC)");
4954        if let Statement::CreateTable(ct) = stmt {
4955            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4956                let pk = columns[0]
4957                    .constraints
4958                    .iter()
4959                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
4960                assert!(pk.is_some(), "PK constraint missing");
4961                if let ColumnConstraintKind::PrimaryKey { direction, .. } = &pk.unwrap().kind {
4962                    assert_eq!(*direction, Some(SortDirection::Asc));
4963                }
4964            } else {
4965                unreachable!("expected Columns body");
4966            }
4967        } else {
4968            unreachable!("expected CreateTable");
4969        }
4970    }
4971
4972    #[test]
4973    fn test_column_primary_key_autoincrement() {
4974        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)");
4975        if let Statement::CreateTable(ct) = stmt {
4976            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4977                let pk = columns[0]
4978                    .constraints
4979                    .iter()
4980                    .find(|c| matches!(c.kind, ColumnConstraintKind::PrimaryKey { .. }));
4981                if let ColumnConstraintKind::PrimaryKey { autoincrement, .. } = &pk.unwrap().kind {
4982                    assert!(autoincrement, "AUTOINCREMENT flag not set");
4983                }
4984            } else {
4985                unreachable!("expected Columns body");
4986            }
4987        } else {
4988            unreachable!("expected CreateTable");
4989        }
4990    }
4991
4992    #[test]
4993    fn test_autoincrement_uses_sqlite_sequence() {
4994        // Parser-level: verify AUTOINCREMENT syntax parses correctly.
4995        // Runtime sqlite_sequence tracking is verified in VDBE/engine tests.
4996        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, val TEXT)");
4997        if let Statement::CreateTable(ct) = stmt {
4998            if let CreateTableBody::Columns { columns, .. } = &ct.body {
4999                assert_eq!(columns.len(), 2);
5000                let pk = columns[0].constraints.iter().find(|c| {
5001                    matches!(
5002                        c.kind,
5003                        ColumnConstraintKind::PrimaryKey {
5004                            autoincrement: true,
5005                            ..
5006                        }
5007                    )
5008                });
5009                assert!(pk.is_some(), "AUTOINCREMENT constraint missing");
5010            } else {
5011                unreachable!("expected Columns body");
5012            }
5013        } else {
5014            unreachable!("expected CreateTable");
5015        }
5016    }
5017
5018    #[test]
5019    fn test_column_not_null() {
5020        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL)");
5021        if let Statement::CreateTable(ct) = stmt {
5022            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5023                let nn = columns[0]
5024                    .constraints
5025                    .iter()
5026                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
5027                assert!(nn.is_some(), "NOT NULL constraint missing");
5028            } else {
5029                unreachable!("expected Columns body");
5030            }
5031        } else {
5032            unreachable!("expected CreateTable");
5033        }
5034    }
5035
5036    #[test]
5037    fn test_column_unique() {
5038        let stmt = parse_one("CREATE TABLE t (email TEXT UNIQUE)");
5039        if let Statement::CreateTable(ct) = stmt {
5040            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5041                let uq = columns[0]
5042                    .constraints
5043                    .iter()
5044                    .find(|c| matches!(c.kind, ColumnConstraintKind::Unique { .. }));
5045                assert!(uq.is_some(), "UNIQUE constraint missing");
5046            } else {
5047                unreachable!("expected Columns body");
5048            }
5049        } else {
5050            unreachable!("expected CreateTable");
5051        }
5052    }
5053
5054    #[test]
5055    fn test_column_check() {
5056        let stmt = parse_one("CREATE TABLE t (age INTEGER CHECK(age >= 0 AND age < 200))");
5057        if let Statement::CreateTable(ct) = stmt {
5058            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5059                let chk = columns[0]
5060                    .constraints
5061                    .iter()
5062                    .find(|c| matches!(c.kind, ColumnConstraintKind::Check(_)));
5063                assert!(chk.is_some(), "CHECK constraint missing");
5064            } else {
5065                unreachable!("expected Columns body");
5066            }
5067        } else {
5068            unreachable!("expected CreateTable");
5069        }
5070    }
5071
5072    #[test]
5073    fn test_column_default_literal() {
5074        let stmt = parse_one("CREATE TABLE t (status TEXT DEFAULT 'active')");
5075        if let Statement::CreateTable(ct) = stmt {
5076            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5077                let def = columns[0]
5078                    .constraints
5079                    .iter()
5080                    .find(|c| matches!(c.kind, ColumnConstraintKind::Default(_)));
5081                assert!(def.is_some(), "DEFAULT constraint missing");
5082            } else {
5083                unreachable!("expected Columns body");
5084            }
5085        } else {
5086            unreachable!("expected CreateTable");
5087        }
5088    }
5089
5090    #[test]
5091    fn test_column_default_expr() {
5092        let stmt = parse_one("CREATE TABLE t (created_at TEXT DEFAULT (datetime('now')))");
5093        if let Statement::CreateTable(ct) = stmt {
5094            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5095                let def = columns[0].constraints.iter().find(|c| {
5096                    matches!(
5097                        c.kind,
5098                        ColumnConstraintKind::Default(DefaultValue::ParenExpr(_))
5099                    )
5100                });
5101                assert!(def.is_some(), "DEFAULT (expr) missing");
5102            } else {
5103                unreachable!("expected Columns body");
5104            }
5105        } else {
5106            unreachable!("expected CreateTable");
5107        }
5108    }
5109
5110    #[test]
5111    fn test_column_collate() {
5112        let stmt = parse_one("CREATE TABLE t (name TEXT COLLATE NOCASE)");
5113        if let Statement::CreateTable(ct) = stmt {
5114            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5115                let coll = columns[0]
5116                    .constraints
5117                    .iter()
5118                    .find(|c| matches!(c.kind, ColumnConstraintKind::Collate(_)));
5119                assert!(coll.is_some(), "COLLATE constraint missing");
5120                if let ColumnConstraintKind::Collate(name) = &coll.unwrap().kind {
5121                    assert_eq!(name, "NOCASE");
5122                }
5123            } else {
5124                unreachable!("expected Columns body");
5125            }
5126        } else {
5127            unreachable!("expected CreateTable");
5128        }
5129    }
5130
5131    #[test]
5132    fn test_table_constraint_composite_pk() {
5133        let stmt = parse_one("CREATE TABLE t (a INTEGER, b INTEGER, PRIMARY KEY (a, b))");
5134        if let Statement::CreateTable(ct) = stmt {
5135            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
5136                let pk = constraints
5137                    .iter()
5138                    .find(|c| matches!(c.kind, TableConstraintKind::PrimaryKey { .. }));
5139                assert!(pk.is_some(), "composite PK missing");
5140                if let TableConstraintKind::PrimaryKey { columns, .. } = &pk.unwrap().kind {
5141                    assert_eq!(columns.len(), 2);
5142                }
5143            } else {
5144                unreachable!("expected Columns body");
5145            }
5146        } else {
5147            unreachable!("expected CreateTable");
5148        }
5149    }
5150
5151    #[test]
5152    fn test_table_constraint_composite_unique() {
5153        let stmt = parse_one("CREATE TABLE t (a TEXT, b TEXT, UNIQUE (a, b))");
5154        if let Statement::CreateTable(ct) = stmt {
5155            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
5156                let uq = constraints
5157                    .iter()
5158                    .find(|c| matches!(c.kind, TableConstraintKind::Unique { .. }));
5159                assert!(uq.is_some(), "composite UNIQUE missing");
5160                if let TableConstraintKind::Unique { columns, .. } = &uq.unwrap().kind {
5161                    assert_eq!(columns.len(), 2);
5162                }
5163            } else {
5164                unreachable!("expected Columns body");
5165            }
5166        } else {
5167            unreachable!("expected CreateTable");
5168        }
5169    }
5170
5171    #[test]
5172    fn test_table_constraint_check() {
5173        let stmt = parse_one(
5174            "CREATE TABLE t (start_date TEXT, end_date TEXT, CHECK (start_date < end_date))",
5175        );
5176        if let Statement::CreateTable(ct) = stmt {
5177            if let CreateTableBody::Columns { constraints, .. } = &ct.body {
5178                let chk = constraints
5179                    .iter()
5180                    .find(|c| matches!(c.kind, TableConstraintKind::Check(_)));
5181                assert!(chk.is_some(), "table CHECK constraint missing");
5182            } else {
5183                unreachable!("expected Columns body");
5184            }
5185        } else {
5186            unreachable!("expected CreateTable");
5187        }
5188    }
5189
5190    #[test]
5191    fn test_foreign_key_on_delete_cascade() {
5192        let stmt = parse_one(
5193            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
5194             REFERENCES parent(id) ON DELETE CASCADE)",
5195        );
5196        if let Statement::CreateTable(ct) = stmt {
5197            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5198                let fk = columns[1]
5199                    .constraints
5200                    .iter()
5201                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)));
5202                assert!(fk.is_some(), "FK constraint missing");
5203                if let ColumnConstraintKind::ForeignKey(clause) = &fk.unwrap().kind {
5204                    assert_eq!(clause.table, "parent");
5205                    let del = clause
5206                        .actions
5207                        .iter()
5208                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
5209                    assert!(del.is_some());
5210                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Cascade);
5211                }
5212            } else {
5213                unreachable!("expected Columns body");
5214            }
5215        } else {
5216            unreachable!("expected CreateTable");
5217        }
5218    }
5219
5220    #[test]
5221    fn test_foreign_key_on_delete_set_null() {
5222        let stmt = parse_one(
5223            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
5224             REFERENCES parent(id) ON DELETE SET NULL)",
5225        );
5226        if let Statement::CreateTable(ct) = stmt {
5227            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5228                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
5229                    .constraints
5230                    .iter()
5231                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
5232                    .unwrap()
5233                    .kind
5234                {
5235                    let del = clause
5236                        .actions
5237                        .iter()
5238                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
5239                    assert_eq!(del.unwrap().action, ForeignKeyActionType::SetNull);
5240                }
5241            } else {
5242                unreachable!("expected Columns body");
5243            }
5244        } else {
5245            unreachable!("expected CreateTable");
5246        }
5247    }
5248
5249    #[test]
5250    fn test_foreign_key_on_update_cascade() {
5251        let stmt = parse_one(
5252            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
5253             REFERENCES parent(id) ON UPDATE CASCADE)",
5254        );
5255        if let Statement::CreateTable(ct) = stmt {
5256            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5257                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
5258                    .constraints
5259                    .iter()
5260                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
5261                    .unwrap()
5262                    .kind
5263                {
5264                    let upd = clause
5265                        .actions
5266                        .iter()
5267                        .find(|a| a.trigger == ForeignKeyTrigger::OnUpdate);
5268                    assert!(upd.is_some());
5269                    assert_eq!(upd.unwrap().action, ForeignKeyActionType::Cascade);
5270                }
5271            } else {
5272                unreachable!("expected Columns body");
5273            }
5274        } else {
5275            unreachable!("expected CreateTable");
5276        }
5277    }
5278
5279    #[test]
5280    fn test_foreign_key_restrict() {
5281        let stmt = parse_one(
5282            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
5283             REFERENCES parent(id) ON DELETE RESTRICT)",
5284        );
5285        if let Statement::CreateTable(ct) = stmt {
5286            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5287                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
5288                    .constraints
5289                    .iter()
5290                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
5291                    .unwrap()
5292                    .kind
5293                {
5294                    let del = clause
5295                        .actions
5296                        .iter()
5297                        .find(|a| a.trigger == ForeignKeyTrigger::OnDelete);
5298                    assert_eq!(del.unwrap().action, ForeignKeyActionType::Restrict);
5299                }
5300            } else {
5301                unreachable!("expected Columns body");
5302            }
5303        } else {
5304            unreachable!("expected CreateTable");
5305        }
5306    }
5307
5308    #[test]
5309    fn test_foreign_key_deferred() {
5310        let stmt = parse_one(
5311            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
5312             REFERENCES parent(id) DEFERRABLE INITIALLY DEFERRED)",
5313        );
5314        if let Statement::CreateTable(ct) = stmt {
5315            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5316                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
5317                    .constraints
5318                    .iter()
5319                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
5320                    .unwrap()
5321                    .kind
5322                {
5323                    let def = clause.deferrable.as_ref().expect("DEFERRABLE missing");
5324                    assert!(!def.not, "should be DEFERRABLE, not NOT DEFERRABLE");
5325                    assert_eq!(def.initially, Some(DeferrableInitially::Deferred));
5326                }
5327            } else {
5328                unreachable!("expected Columns body");
5329            }
5330        } else {
5331            unreachable!("expected CreateTable");
5332        }
5333    }
5334
5335    #[test]
5336    fn test_foreign_key_pragma_required() {
5337        // Parser-level: FK syntax parses identically regardless of PRAGMA state.
5338        // Runtime enforcement requiring PRAGMA foreign_keys = ON is in VDBE/engine.
5339        let stmt = parse_one(
5340            "CREATE TABLE child (id INTEGER, parent_id INTEGER \
5341             REFERENCES parent(id) ON DELETE CASCADE ON UPDATE SET NULL)",
5342        );
5343        if let Statement::CreateTable(ct) = stmt {
5344            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5345                if let ColumnConstraintKind::ForeignKey(clause) = &columns[1]
5346                    .constraints
5347                    .iter()
5348                    .find(|c| matches!(c.kind, ColumnConstraintKind::ForeignKey(_)))
5349                    .unwrap()
5350                    .kind
5351                {
5352                    assert_eq!(clause.actions.len(), 2);
5353                }
5354            } else {
5355                unreachable!("expected Columns body");
5356            }
5357        } else {
5358            unreachable!("expected CreateTable");
5359        }
5360    }
5361
5362    #[test]
5363    fn test_conflict_clause_on_not_null() {
5364        let stmt = parse_one("CREATE TABLE t (name TEXT NOT NULL ON CONFLICT IGNORE)");
5365        if let Statement::CreateTable(ct) = stmt {
5366            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5367                let nn = columns[0]
5368                    .constraints
5369                    .iter()
5370                    .find(|c| matches!(c.kind, ColumnConstraintKind::NotNull { .. }));
5371                if let ColumnConstraintKind::NotNull { conflict } = &nn.unwrap().kind {
5372                    assert_eq!(*conflict, Some(ConflictAction::Ignore));
5373                }
5374            } else {
5375                unreachable!("expected Columns body");
5376            }
5377        } else {
5378            unreachable!("expected CreateTable");
5379        }
5380    }
5381
5382    #[test]
5383    fn test_without_rowid_table() {
5384        let stmt = parse_one("CREATE TABLE t (k TEXT PRIMARY KEY, v BLOB) WITHOUT ROWID");
5385        if let Statement::CreateTable(ct) = stmt {
5386            assert!(ct.without_rowid);
5387        } else {
5388            unreachable!("expected CreateTable");
5389        }
5390    }
5391
5392    #[test]
5393    fn test_without_rowid_no_autoincrement() {
5394        // Parser-level: verify WITHOUT ROWID and AUTOINCREMENT can both parse.
5395        // Runtime rejection of AUTOINCREMENT on WITHOUT ROWID is in schema validation.
5396        let stmt = parse_one(
5397            "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT, v TEXT) WITHOUT ROWID",
5398        );
5399        if let Statement::CreateTable(ct) = stmt {
5400            assert!(ct.without_rowid);
5401            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5402                let pk = columns[0].constraints.iter().find(|c| {
5403                    matches!(
5404                        c.kind,
5405                        ColumnConstraintKind::PrimaryKey {
5406                            autoincrement: true,
5407                            ..
5408                        }
5409                    )
5410                });
5411                assert!(pk.is_some());
5412            } else {
5413                unreachable!("expected Columns body");
5414            }
5415        } else {
5416            unreachable!("expected CreateTable");
5417        }
5418    }
5419
5420    #[test]
5421    fn test_without_rowid_integer_pk_not_alias() {
5422        // Parser-level: INTEGER PRIMARY KEY in WITHOUT ROWID parses as normal PK.
5423        // Runtime non-aliasing of rowid is in the B-tree layer.
5424        let stmt = parse_one("CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT) WITHOUT ROWID");
5425        if let Statement::CreateTable(ct) = stmt {
5426            assert!(ct.without_rowid);
5427            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5428                assert_eq!(columns[0].name, "id");
5429                assert!(columns[0].type_name.is_some());
5430            } else {
5431                unreachable!("expected Columns body");
5432            }
5433        } else {
5434            unreachable!("expected CreateTable");
5435        }
5436    }
5437
5438    #[test]
5439    fn test_strict_table_type_enforcement() {
5440        // Parser-level: STRICT keyword parses on CREATE TABLE.
5441        // Runtime type enforcement on INSERT/UPDATE is in VDBE/engine.
5442        let stmt = parse_one("CREATE TABLE t (id INTEGER, name TEXT, score REAL) STRICT");
5443        if let Statement::CreateTable(ct) = stmt {
5444            assert!(ct.strict);
5445            assert!(!ct.without_rowid);
5446        } else {
5447            unreachable!("expected CreateTable");
5448        }
5449    }
5450
5451    #[test]
5452    fn test_strict_table_any_column() {
5453        // Parser-level: ANY type name parses in STRICT table context.
5454        let stmt = parse_one("CREATE TABLE t (id INTEGER, data ANY) STRICT");
5455        if let Statement::CreateTable(ct) = stmt {
5456            assert!(ct.strict);
5457            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5458                let tn = columns[1].type_name.as_ref().expect("type name");
5459                assert_eq!(tn.name, "ANY");
5460            } else {
5461                unreachable!("expected Columns body");
5462            }
5463        } else {
5464            unreachable!("expected CreateTable");
5465        }
5466    }
5467
5468    #[test]
5469    fn test_strict_allowed_types() {
5470        // Parser-level: STRICT table with all allowed type names parses.
5471        let stmt =
5472            parse_one("CREATE TABLE t (a INT, b INTEGER, c REAL, d TEXT, e BLOB, f ANY) STRICT");
5473        if let Statement::CreateTable(ct) = stmt {
5474            assert!(ct.strict);
5475            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5476                assert_eq!(columns.len(), 6);
5477                let types: Vec<&str> = columns
5478                    .iter()
5479                    .map(|c| c.type_name.as_ref().unwrap().name.as_str())
5480                    .collect();
5481                assert_eq!(types, vec!["INT", "INTEGER", "REAL", "TEXT", "BLOB", "ANY"]);
5482            } else {
5483                unreachable!("expected Columns body");
5484            }
5485        } else {
5486            unreachable!("expected CreateTable");
5487        }
5488    }
5489
5490    #[test]
5491    fn test_generated_col_virtual() {
5492        let stmt = parse_one(
5493            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a + b) VIRTUAL)",
5494        );
5495        if let Statement::CreateTable(ct) = stmt {
5496            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5497                let generated = columns[2]
5498                    .constraints
5499                    .iter()
5500                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5501                assert!(generated.is_some(), "Generated constraint missing");
5502                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
5503                    assert_eq!(*storage, Some(GeneratedStorage::Virtual));
5504                }
5505            } else {
5506                unreachable!("expected Columns body");
5507            }
5508        } else {
5509            unreachable!("expected CreateTable");
5510        }
5511    }
5512
5513    #[test]
5514    fn test_generated_col_stored() {
5515        let stmt = parse_one(
5516            "CREATE TABLE t (a INTEGER, b INTEGER, c INTEGER GENERATED ALWAYS AS (a * b) STORED)",
5517        );
5518        if let Statement::CreateTable(ct) = stmt {
5519            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5520                let generated = columns[2]
5521                    .constraints
5522                    .iter()
5523                    .find(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5524                if let ColumnConstraintKind::Generated { storage, .. } = &generated.unwrap().kind {
5525                    assert_eq!(*storage, Some(GeneratedStorage::Stored));
5526                }
5527            } else {
5528                unreachable!("expected Columns body");
5529            }
5530        } else {
5531            unreachable!("expected CreateTable");
5532        }
5533    }
5534
5535    #[test]
5536    fn test_generated_col_ordering() {
5537        // Parser-level: generated columns with forward references parse correctly.
5538        // Runtime rejection of forward references is in schema validation.
5539        let stmt = parse_one(
5540            "CREATE TABLE t (\
5541             a INTEGER, \
5542             b INTEGER GENERATED ALWAYS AS (a + 1) STORED, \
5543             c INTEGER GENERATED ALWAYS AS (b * 2) VIRTUAL)",
5544        );
5545        if let Statement::CreateTable(ct) = stmt {
5546            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5547                assert_eq!(columns.len(), 3);
5548                // Both b and c have Generated constraints
5549                let gen_b = columns[1]
5550                    .constraints
5551                    .iter()
5552                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5553                let gen_c = columns[2]
5554                    .constraints
5555                    .iter()
5556                    .any(|c| matches!(c.kind, ColumnConstraintKind::Generated { .. }));
5557                assert!(gen_b, "column b should be generated");
5558                assert!(gen_c, "column c should be generated");
5559            } else {
5560                unreachable!("expected Columns body");
5561            }
5562        } else {
5563            unreachable!("expected CreateTable");
5564        }
5565    }
5566
5567    #[test]
5568    fn test_generated_col_stored_indexable() {
5569        // Parser-level: STORED generated column can appear alongside indexes.
5570        // Runtime indexability verified in B-tree/engine tests.
5571        let stmts = parse_ok(
5572            "CREATE TABLE t (a INTEGER, b INTEGER GENERATED ALWAYS AS (a * 2) STORED); \
5573             CREATE INDEX idx_b ON t (b)",
5574        );
5575        assert_eq!(stmts.len(), 2);
5576        assert!(matches!(stmts[0], Statement::CreateTable(_)));
5577        assert!(matches!(stmts[1], Statement::CreateIndex(_)));
5578    }
5579
5580    #[test]
5581    fn test_type_affinity_int() {
5582        // Parser-level: type names containing "INT" parse as valid type names.
5583        // Runtime affinity determination is in the type system.
5584        let stmt = parse_one("CREATE TABLE t (a INTEGER, b BIGINT, c SMALLINT, d MEDIUMINT)");
5585        if let Statement::CreateTable(ct) = stmt {
5586            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5587                assert_eq!(columns.len(), 4);
5588                for col in columns {
5589                    let tn = col.type_name.as_ref().unwrap();
5590                    assert!(tn.name.contains("INT"), "{} should contain INT", tn.name);
5591                }
5592            } else {
5593                unreachable!("expected Columns body");
5594            }
5595        } else {
5596            unreachable!("expected CreateTable");
5597        }
5598    }
5599
5600    #[test]
5601    fn test_type_affinity_text() {
5602        let stmt = parse_one("CREATE TABLE t (a TEXT, b VARCHAR, c CLOB, d CHARACTER)");
5603        if let Statement::CreateTable(ct) = stmt {
5604            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5605                assert_eq!(columns.len(), 4);
5606                for col in columns {
5607                    assert!(col.type_name.is_some());
5608                }
5609            } else {
5610                unreachable!("expected Columns body");
5611            }
5612        } else {
5613            unreachable!("expected CreateTable");
5614        }
5615    }
5616
5617    #[test]
5618    fn test_type_affinity_blob() {
5619        let stmt = parse_one("CREATE TABLE t (a BLOB, b)");
5620        if let Statement::CreateTable(ct) = stmt {
5621            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5622                assert_eq!(columns.len(), 2);
5623                assert_eq!(columns[0].type_name.as_ref().unwrap().name, "BLOB");
5624                // Column b has no type name -> BLOB affinity
5625                assert!(columns[1].type_name.is_none());
5626            } else {
5627                unreachable!("expected Columns body");
5628            }
5629        } else {
5630            unreachable!("expected CreateTable");
5631        }
5632    }
5633
5634    #[test]
5635    fn test_type_affinity_real() {
5636        let stmt = parse_one("CREATE TABLE t (a REAL, b DOUBLE, c FLOAT)");
5637        if let Statement::CreateTable(ct) = stmt {
5638            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5639                assert_eq!(columns.len(), 3);
5640                for col in columns {
5641                    assert!(col.type_name.is_some());
5642                }
5643            } else {
5644                unreachable!("expected Columns body");
5645            }
5646        } else {
5647            unreachable!("expected CreateTable");
5648        }
5649    }
5650
5651    #[test]
5652    fn test_type_affinity_numeric() {
5653        let stmt = parse_one("CREATE TABLE t (a NUMERIC, b DECIMAL, c BOOLEAN)");
5654        if let Statement::CreateTable(ct) = stmt {
5655            if let CreateTableBody::Columns { columns, .. } = &ct.body {
5656                assert_eq!(columns.len(), 3);
5657                for col in columns {
5658                    assert!(col.type_name.is_some());
5659                }
5660            } else {
5661                unreachable!("expected Columns body");
5662            }
5663        } else {
5664            unreachable!("expected CreateTable");
5665        }
5666    }
5667
5668    #[test]
5669    fn test_create_unique_index() {
5670        let stmt = parse_one("CREATE UNIQUE INDEX idx_email ON users (email)");
5671        if let Statement::CreateIndex(ci) = stmt {
5672            assert!(ci.unique);
5673            assert_eq!(ci.name.name, "idx_email");
5674            assert_eq!(ci.table, "users");
5675            assert_eq!(ci.columns.len(), 1);
5676            assert!(ci.where_clause.is_none());
5677        } else {
5678            unreachable!("expected CreateIndex");
5679        }
5680    }
5681
5682    #[test]
5683    fn test_partial_index() {
5684        let stmt = parse_one("CREATE INDEX idx_active ON users (name) WHERE active = 1");
5685        if let Statement::CreateIndex(ci) = stmt {
5686            assert!(!ci.unique);
5687            assert_eq!(ci.name.name, "idx_active");
5688            assert!(ci.where_clause.is_some(), "partial index WHERE missing");
5689        } else {
5690            unreachable!("expected CreateIndex");
5691        }
5692    }
5693
5694    #[test]
5695    fn test_partial_index_planner_usage() {
5696        // Parser-level: partial index with complex WHERE parses correctly.
5697        // Runtime planner usage (query WHERE implies index WHERE) is in planner tests.
5698        let stmt =
5699            parse_one("CREATE INDEX idx_recent ON orders (created_at) WHERE status != 'archived'");
5700        if let Statement::CreateIndex(ci) = stmt {
5701            assert!(ci.where_clause.is_some());
5702            assert_eq!(ci.columns.len(), 1);
5703        } else {
5704            unreachable!("expected CreateIndex");
5705        }
5706    }
5707
5708    #[test]
5709    fn test_expression_index() {
5710        let stmt = parse_one("CREATE INDEX idx_lower_name ON users (lower(name))");
5711        if let Statement::CreateIndex(ci) = stmt {
5712            assert_eq!(ci.columns.len(), 1);
5713            // The indexed expression should be a function call, not a plain column
5714            assert!(
5715                matches!(ci.columns[0].expr, Expr::FunctionCall { .. }),
5716                "expected function call expression in index"
5717            );
5718        } else {
5719            unreachable!("expected CreateIndex");
5720        }
5721    }
5722
5723    #[test]
5724    fn test_expression_index_planner_match() {
5725        // Parser-level: expression index with arithmetic parses correctly.
5726        // Runtime structural equality matching is in planner tests.
5727        let stmt = parse_one("CREATE INDEX idx_calc ON t (a + b * 2)");
5728        if let Statement::CreateIndex(ci) = stmt {
5729            assert_eq!(ci.columns.len(), 1);
5730            assert!(
5731                matches!(ci.columns[0].expr, Expr::BinaryOp { .. }),
5732                "expected binary op in expression index"
5733            );
5734        } else {
5735            unreachable!("expected CreateIndex");
5736        }
5737    }
5738
5739    #[test]
5740    fn test_index_collate_asc_desc() {
5741        let stmt = parse_one("CREATE INDEX idx_multi ON t (a COLLATE NOCASE ASC, b DESC, c)");
5742        if let Statement::CreateIndex(ci) = stmt {
5743            assert_eq!(ci.columns.len(), 3);
5744            // COLLATE is consumed by the expression parser as Expr::Collate
5745            assert!(
5746                matches!(
5747                    &ci.columns[0].expr,
5748                    Expr::Collate { collation, .. } if collation == "NOCASE"
5749                ),
5750                "expected Collate expr with NOCASE"
5751            );
5752            assert_eq!(ci.columns[0].direction, Some(SortDirection::Asc));
5753            assert_eq!(ci.columns[1].direction, Some(SortDirection::Desc));
5754            assert!(ci.columns[2].direction.is_none());
5755        } else {
5756            unreachable!("expected CreateIndex");
5757        }
5758    }
5759
5760    // -----------------------------------------------------------------------
5761    // bd-3kin §12.7-12.9 CREATE VIEW + CREATE TRIGGER + ALTER/DROP parsing tests
5762    // -----------------------------------------------------------------------
5763
5764    #[test]
5765    fn test_create_view_basic() {
5766        let stmt = parse_one("CREATE VIEW v AS SELECT id, name FROM users");
5767        if let Statement::CreateView(cv) = stmt {
5768            assert_eq!(cv.name.name, "v");
5769            assert!(!cv.if_not_exists);
5770            assert!(!cv.temporary);
5771            assert!(cv.columns.is_empty());
5772        } else {
5773            unreachable!("expected CreateView");
5774        }
5775    }
5776
5777    #[test]
5778    fn test_create_view_column_aliases() {
5779        let stmt = parse_one("CREATE VIEW v (user_id, user_name) AS SELECT id, name FROM users");
5780        if let Statement::CreateView(cv) = stmt {
5781            assert_eq!(cv.columns, vec!["user_id", "user_name"]);
5782        } else {
5783            unreachable!("expected CreateView");
5784        }
5785    }
5786
5787    #[test]
5788    fn test_create_view_if_not_exists() {
5789        let stmt = parse_one("CREATE VIEW IF NOT EXISTS v AS SELECT 1");
5790        if let Statement::CreateView(cv) = stmt {
5791            assert!(cv.if_not_exists);
5792        } else {
5793            unreachable!("expected CreateView");
5794        }
5795    }
5796
5797    #[test]
5798    fn test_create_temp_view() {
5799        let stmt = parse_one("CREATE TEMP VIEW tv AS SELECT 1");
5800        if let Statement::CreateView(cv) = stmt {
5801            assert!(cv.temporary);
5802        } else {
5803            unreachable!("expected CreateView");
5804        }
5805    }
5806
5807    #[test]
5808    fn test_view_inline_expansion() {
5809        // Parser-level: view defined with WHERE is captured in AST.
5810        // Runtime inline expansion (not materialization) is in the planner.
5811        let stmt =
5812            parse_one("CREATE VIEW active_users AS SELECT id, name FROM users WHERE active = 1");
5813        if let Statement::CreateView(cv) = stmt {
5814            assert_eq!(cv.name.name, "active_users");
5815        } else {
5816            unreachable!("expected CreateView");
5817        }
5818    }
5819
5820    #[test]
5821    fn test_view_read_only() {
5822        // Parser-level: views parse as SELECT-only definitions.
5823        // Runtime read-only enforcement (rejecting DML without INSTEAD OF) is in the engine.
5824        let stmt = parse_one("CREATE VIEW v AS SELECT * FROM t");
5825        assert!(matches!(stmt, Statement::CreateView(_)));
5826    }
5827
5828    #[test]
5829    fn test_view_with_recursive_cte() {
5830        // View referencing a subquery (parser does not yet support WITH directly
5831        // in CREATE VIEW ... AS context; CTE-in-view support is a planner concern).
5832        let stmt = parse_one(
5833            "CREATE VIEW tree AS \
5834             SELECT n.id, n.parent FROM nodes n \
5835             WHERE n.parent IS NULL \
5836             UNION ALL \
5837             SELECT c.id, c.parent FROM nodes c JOIN nodes p ON c.parent = p.id",
5838        );
5839        if let Statement::CreateView(cv) = stmt {
5840            assert_eq!(cv.name.name, "tree");
5841            // Compound UNION ALL captured
5842            assert!(
5843                !cv.query.body.compounds.is_empty(),
5844                "expected compound SELECT in view"
5845            );
5846        } else {
5847            unreachable!("expected CreateView");
5848        }
5849    }
5850
5851    #[test]
5852    fn test_instead_of_trigger_on_view() {
5853        let stmt = parse_one(
5854            "CREATE TRIGGER tr INSTEAD OF INSERT ON v BEGIN \
5855             INSERT INTO t (a) VALUES (NEW.a); \
5856             END",
5857        );
5858        if let Statement::CreateTrigger(ct) = stmt {
5859            assert_eq!(ct.timing, TriggerTiming::InsteadOf);
5860            assert!(matches!(ct.event, TriggerEvent::Insert));
5861            assert_eq!(ct.table, "v");
5862            assert!(!ct.body.is_empty());
5863        } else {
5864            unreachable!("expected CreateTrigger");
5865        }
5866    }
5867
5868    #[test]
5869    fn test_trigger_before_insert() {
5870        let stmt = parse_one("CREATE TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
5871        if let Statement::CreateTrigger(ct) = stmt {
5872            assert_eq!(ct.timing, TriggerTiming::Before);
5873            assert!(matches!(ct.event, TriggerEvent::Insert));
5874        } else {
5875            unreachable!("expected CreateTrigger");
5876        }
5877    }
5878
5879    #[test]
5880    fn test_trigger_after_insert() {
5881        let stmt = parse_one("CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END");
5882        if let Statement::CreateTrigger(ct) = stmt {
5883            assert_eq!(ct.timing, TriggerTiming::After);
5884            assert!(matches!(ct.event, TriggerEvent::Insert));
5885        } else {
5886            unreachable!("expected CreateTrigger");
5887        }
5888    }
5889
5890    #[test]
5891    fn test_trigger_before_update() {
5892        let stmt = parse_one("CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN SELECT OLD.a, NEW.a; END");
5893        if let Statement::CreateTrigger(ct) = stmt {
5894            assert_eq!(ct.timing, TriggerTiming::Before);
5895            assert!(matches!(ct.event, TriggerEvent::Update(_)));
5896        } else {
5897            unreachable!("expected CreateTrigger");
5898        }
5899    }
5900
5901    #[test]
5902    fn test_trigger_after_delete() {
5903        let stmt = parse_one("CREATE TRIGGER tr AFTER DELETE ON t BEGIN SELECT OLD.id; END");
5904        if let Statement::CreateTrigger(ct) = stmt {
5905            assert_eq!(ct.timing, TriggerTiming::After);
5906            assert!(matches!(ct.event, TriggerEvent::Delete));
5907        } else {
5908            unreachable!("expected CreateTrigger");
5909        }
5910    }
5911
5912    #[test]
5913    fn test_trigger_update_of_column() {
5914        let stmt =
5915            parse_one("CREATE TRIGGER tr BEFORE UPDATE OF name, email ON t BEGIN SELECT 1; END");
5916        if let Statement::CreateTrigger(ct) = stmt {
5917            if let TriggerEvent::Update(cols) = &ct.event {
5918                assert_eq!(cols, &["name", "email"]);
5919            } else {
5920                unreachable!("expected Update event with columns");
5921            }
5922        } else {
5923            unreachable!("expected CreateTrigger");
5924        }
5925    }
5926
5927    #[test]
5928    fn test_trigger_when_clause() {
5929        let stmt = parse_one(
5930            "CREATE TRIGGER tr BEFORE INSERT ON t WHEN NEW.active = 1 BEGIN SELECT 1; END",
5931        );
5932        if let Statement::CreateTrigger(ct) = stmt {
5933            assert!(ct.when.is_some(), "WHEN clause missing");
5934        } else {
5935            unreachable!("expected CreateTrigger");
5936        }
5937    }
5938
5939    #[test]
5940    fn test_trigger_old_new_pseudo_tables() {
5941        let stmt = parse_one(
5942            "CREATE TRIGGER tr BEFORE UPDATE ON t BEGIN \
5943             INSERT INTO log (old_val, new_val) VALUES (OLD.a, NEW.a); \
5944             END",
5945        );
5946        if let Statement::CreateTrigger(ct) = stmt {
5947            assert_eq!(ct.body.len(), 1);
5948            assert!(matches!(ct.body[0], Statement::Insert(_)));
5949        } else {
5950            unreachable!("expected CreateTrigger");
5951        }
5952    }
5953
5954    #[test]
5955    fn test_trigger_raise_abort() {
5956        let stmt = parse_one(
5957            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
5958             SELECT RAISE(ABORT, 'not allowed'); \
5959             END",
5960        );
5961        if let Statement::CreateTrigger(ct) = stmt {
5962            assert_eq!(ct.body.len(), 1);
5963        } else {
5964            unreachable!("expected CreateTrigger");
5965        }
5966    }
5967
5968    #[test]
5969    fn test_trigger_raise_rollback() {
5970        let stmt = parse_one(
5971            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
5972             SELECT RAISE(ROLLBACK, 'invalid'); \
5973             END",
5974        );
5975        if let Statement::CreateTrigger(ct) = stmt {
5976            assert_eq!(ct.body.len(), 1);
5977        } else {
5978            unreachable!("expected CreateTrigger");
5979        }
5980    }
5981
5982    #[test]
5983    fn test_trigger_raise_fail() {
5984        let stmt = parse_one(
5985            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
5986             SELECT RAISE(FAIL, 'bad data'); \
5987             END",
5988        );
5989        if let Statement::CreateTrigger(ct) = stmt {
5990            assert_eq!(ct.body.len(), 1);
5991        } else {
5992            unreachable!("expected CreateTrigger");
5993        }
5994    }
5995
5996    #[test]
5997    fn test_trigger_raise_ignore() {
5998        let stmt = parse_one(
5999            "CREATE TRIGGER tr BEFORE INSERT ON t BEGIN \
6000             SELECT RAISE(IGNORE); \
6001             END",
6002        );
6003        if let Statement::CreateTrigger(ct) = stmt {
6004            assert_eq!(ct.body.len(), 1);
6005        } else {
6006            unreachable!("expected CreateTrigger");
6007        }
6008    }
6009
6010    #[test]
6011    fn test_trigger_recursive() {
6012        // Parser-level: trigger referencing its own table parses normally.
6013        // Runtime recursive trigger behavior (PRAGMA recursive_triggers) is in the engine.
6014        let stmt = parse_one(
6015            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
6016             INSERT INTO t (val) VALUES (NEW.val + 1); \
6017             END",
6018        );
6019        if let Statement::CreateTrigger(ct) = stmt {
6020            assert_eq!(ct.timing, TriggerTiming::After);
6021            assert_eq!(ct.table, "t");
6022            assert_eq!(ct.body.len(), 1);
6023        } else {
6024            unreachable!("expected CreateTrigger");
6025        }
6026    }
6027
6028    #[test]
6029    fn test_trigger_max_recursion_depth() {
6030        // Parser-level: trigger with WHEN depth guard parses.
6031        // Runtime SQLITE_MAX_TRIGGER_DEPTH enforcement is in the VDBE.
6032        let stmt = parse_one(
6033            "CREATE TRIGGER tr AFTER INSERT ON t \
6034             WHEN NEW.depth < 1000 BEGIN \
6035             INSERT INTO t (depth) VALUES (NEW.depth + 1); \
6036             END",
6037        );
6038        if let Statement::CreateTrigger(ct) = stmt {
6039            assert!(ct.when.is_some());
6040            assert_eq!(ct.body.len(), 1);
6041        } else {
6042            unreachable!("expected CreateTrigger");
6043        }
6044    }
6045
6046    #[test]
6047    fn test_trigger_heap_frame_stack() {
6048        // Parser-level: trigger with UPDATE body parses correctly.
6049        // Runtime heap-allocated frame stack is in the VDBE.
6050        let stmt = parse_one(
6051            "CREATE TRIGGER tr AFTER UPDATE ON t BEGIN \
6052             UPDATE t SET counter = counter + 1 WHERE id = NEW.parent_id; \
6053             END",
6054        );
6055        if let Statement::CreateTrigger(ct) = stmt {
6056            assert_eq!(ct.body.len(), 1);
6057            assert!(matches!(ct.body[0], Statement::Update(_)));
6058        } else {
6059            unreachable!("expected CreateTrigger");
6060        }
6061    }
6062
6063    #[test]
6064    fn test_trigger_multiple_dml() {
6065        let stmt = parse_one(
6066            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN \
6067             INSERT INTO audit (action) VALUES ('insert'); \
6068             UPDATE stats SET count = count + 1; \
6069             END",
6070        );
6071        if let Statement::CreateTrigger(ct) = stmt {
6072            assert_eq!(ct.body.len(), 2);
6073            assert!(matches!(ct.body[0], Statement::Insert(_)));
6074            assert!(matches!(ct.body[1], Statement::Update(_)));
6075        } else {
6076            unreachable!("expected CreateTrigger");
6077        }
6078    }
6079
6080    #[test]
6081    fn test_alter_table_rename() {
6082        let stmt = parse_one("ALTER TABLE t RENAME TO t2");
6083        if let Statement::AlterTable(at) = stmt {
6084            assert_eq!(at.table.name, "t");
6085            assert!(matches!(at.action, AlterTableAction::RenameTo(ref n) if n == "t2"));
6086        } else {
6087            unreachable!("expected AlterTable");
6088        }
6089    }
6090
6091    #[test]
6092    fn test_alter_table_rename_column() {
6093        let stmt = parse_one("ALTER TABLE t RENAME COLUMN old_name TO new_name");
6094        if let Statement::AlterTable(at) = stmt {
6095            if let AlterTableAction::RenameColumn { old, new } = &at.action {
6096                assert_eq!(old, "old_name");
6097                assert_eq!(new, "new_name");
6098            } else {
6099                unreachable!("expected RenameColumn action");
6100            }
6101        } else {
6102            unreachable!("expected AlterTable");
6103        }
6104    }
6105
6106    #[test]
6107    fn test_alter_table_add_column() {
6108        let stmt = parse_one("ALTER TABLE t ADD COLUMN email TEXT NOT NULL DEFAULT ''");
6109        if let Statement::AlterTable(at) = stmt {
6110            if let AlterTableAction::AddColumn(col) = &at.action {
6111                assert_eq!(col.name, "email");
6112                assert!(!col.constraints.is_empty());
6113            } else {
6114                unreachable!("expected AddColumn action");
6115            }
6116        } else {
6117            unreachable!("expected AlterTable");
6118        }
6119    }
6120
6121    #[test]
6122    fn test_alter_table_remove_column() {
6123        let stmt = parse_one("ALTER TABLE t DROP COLUMN old_col");
6124        if let Statement::AlterTable(at) = stmt {
6125            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "old_col"));
6126        } else {
6127            unreachable!("expected AlterTable");
6128        }
6129    }
6130
6131    #[test]
6132    fn test_alter_remove_column_pk_fails() {
6133        // Parser-level: DROP COLUMN on a PK column parses normally.
6134        // Runtime rejection is in schema validation.
6135        let stmt = parse_one("ALTER TABLE t DROP COLUMN id");
6136        if let Statement::AlterTable(at) = stmt {
6137            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "id"));
6138        } else {
6139            unreachable!("expected AlterTable");
6140        }
6141    }
6142
6143    #[test]
6144    fn test_alter_remove_column_unique_fails() {
6145        // Parser-level: DROP COLUMN on UNIQUE column parses normally.
6146        let stmt = parse_one("ALTER TABLE t DROP COLUMN email");
6147        if let Statement::AlterTable(at) = stmt {
6148            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "email"));
6149        } else {
6150            unreachable!("expected AlterTable");
6151        }
6152    }
6153
6154    #[test]
6155    fn test_alter_remove_column_index_fails() {
6156        // Parser-level: DROP COLUMN on indexed column parses normally.
6157        let stmt = parse_one("ALTER TABLE t DROP COLUMN indexed_col");
6158        if let Statement::AlterTable(at) = stmt {
6159            assert!(matches!(
6160                at.action,
6161                AlterTableAction::DropColumn(ref c) if c == "indexed_col"
6162            ));
6163        } else {
6164            unreachable!("expected AlterTable");
6165        }
6166    }
6167
6168    #[test]
6169    fn test_alter_remove_column_check_fails() {
6170        // Parser-level: DROP COLUMN on CHECK-constrained column parses normally.
6171        let stmt = parse_one("ALTER TABLE t DROP COLUMN checked_col");
6172        if let Statement::AlterTable(at) = stmt {
6173            assert!(matches!(
6174                at.action,
6175                AlterTableAction::DropColumn(ref c) if c == "checked_col"
6176            ));
6177        } else {
6178            unreachable!("expected AlterTable");
6179        }
6180    }
6181
6182    #[test]
6183    fn test_alter_remove_column_fk_fails() {
6184        // Parser-level: DROP COLUMN on FK-constrained column parses normally.
6185        let stmt = parse_one("ALTER TABLE t DROP COLUMN fk_col");
6186        if let Statement::AlterTable(at) = stmt {
6187            assert!(matches!(at.action, AlterTableAction::DropColumn(ref c) if c == "fk_col"));
6188        } else {
6189            unreachable!("expected AlterTable");
6190        }
6191    }
6192
6193    #[test]
6194    fn test_alter_remove_only_column_fails() {
6195        // Parser-level: DROP COLUMN on only column parses normally.
6196        let stmt = parse_one("ALTER TABLE t DROP COLUMN only_col");
6197        if let Statement::AlterTable(at) = stmt {
6198            assert!(matches!(
6199                at.action,
6200                AlterTableAction::DropColumn(ref c) if c == "only_col"
6201            ));
6202        } else {
6203            unreachable!("expected AlterTable");
6204        }
6205    }
6206
6207    #[test]
6208    fn test_ddl_remove_table() {
6209        let stmt = parse_one("DROP TABLE t");
6210        if let Statement::Drop(d) = stmt {
6211            assert_eq!(d.object_type, DropObjectType::Table);
6212            assert!(!d.if_exists);
6213            assert_eq!(d.name.name, "t");
6214        } else {
6215            unreachable!("expected Drop");
6216        }
6217    }
6218
6219    #[test]
6220    fn test_ddl_remove_table_if_exists() {
6221        let stmt = parse_one("DROP TABLE IF EXISTS t");
6222        if let Statement::Drop(d) = stmt {
6223            assert_eq!(d.object_type, DropObjectType::Table);
6224            assert!(d.if_exists);
6225        } else {
6226            unreachable!("expected Drop");
6227        }
6228    }
6229
6230    #[test]
6231    fn test_ddl_remove_index() {
6232        let stmt = parse_one("DROP INDEX idx");
6233        if let Statement::Drop(d) = stmt {
6234            assert_eq!(d.object_type, DropObjectType::Index);
6235            assert_eq!(d.name.name, "idx");
6236        } else {
6237            unreachable!("expected Drop");
6238        }
6239    }
6240
6241    #[test]
6242    fn test_ddl_remove_view() {
6243        let stmt = parse_one("DROP VIEW v");
6244        if let Statement::Drop(d) = stmt {
6245            assert_eq!(d.object_type, DropObjectType::View);
6246            assert_eq!(d.name.name, "v");
6247        } else {
6248            unreachable!("expected Drop");
6249        }
6250    }
6251
6252    #[test]
6253    fn test_ddl_remove_trigger() {
6254        let stmt = parse_one("DROP TRIGGER tr");
6255        if let Statement::Drop(d) = stmt {
6256            assert_eq!(d.object_type, DropObjectType::Trigger);
6257            assert_eq!(d.name.name, "tr");
6258        } else {
6259            unreachable!("expected Drop");
6260        }
6261    }
6262
6263    // -----------------------------------------------------------------------
6264    // bd-3kin §12.7-12.9 DDL gap-fill: REINDEX, ANALYZE, qualified names,
6265    //                                   IF NOT EXISTS/TEMP triggers
6266    // -----------------------------------------------------------------------
6267
6268    #[test]
6269    fn test_reindex_global() {
6270        let stmt = parse_one("REINDEX");
6271        assert!(matches!(stmt, Statement::Reindex(None)));
6272    }
6273
6274    #[test]
6275    fn test_reindex_table() {
6276        let stmt = parse_one("REINDEX t");
6277        if let Statement::Reindex(Some(name)) = stmt {
6278            assert_eq!(name.name, "t");
6279            assert!(name.schema.is_none());
6280        } else {
6281            unreachable!("expected Reindex(Some), got {stmt:?}");
6282        }
6283    }
6284
6285    #[test]
6286    fn test_reindex_qualified() {
6287        let stmt = parse_one("REINDEX main.idx");
6288        if let Statement::Reindex(Some(name)) = stmt {
6289            assert_eq!(name.schema.as_deref(), Some("main"));
6290            assert_eq!(name.name, "idx");
6291        } else {
6292            unreachable!("expected Reindex(Some), got {stmt:?}");
6293        }
6294    }
6295
6296    #[test]
6297    fn test_analyze_global() {
6298        let stmt = parse_one("ANALYZE");
6299        assert!(matches!(stmt, Statement::Analyze(None)));
6300    }
6301
6302    #[test]
6303    fn test_analyze_table() {
6304        let stmt = parse_one("ANALYZE t");
6305        if let Statement::Analyze(Some(name)) = stmt {
6306            assert_eq!(name.name, "t");
6307            assert!(name.schema.is_none());
6308        } else {
6309            unreachable!("expected Analyze(Some), got {stmt:?}");
6310        }
6311    }
6312
6313    #[test]
6314    fn test_analyze_qualified() {
6315        let stmt = parse_one("ANALYZE main.t");
6316        if let Statement::Analyze(Some(name)) = stmt {
6317            assert_eq!(name.schema.as_deref(), Some("main"));
6318            assert_eq!(name.name, "t");
6319        } else {
6320            unreachable!("expected Analyze(Some), got {stmt:?}");
6321        }
6322    }
6323
6324    #[test]
6325    fn test_drop_view_if_exists() {
6326        let stmt = parse_one("DROP VIEW IF EXISTS v");
6327        if let Statement::Drop(d) = stmt {
6328            assert_eq!(d.object_type, DropObjectType::View);
6329            assert!(d.if_exists);
6330            assert_eq!(d.name.name, "v");
6331        } else {
6332            unreachable!("expected Drop");
6333        }
6334    }
6335
6336    #[test]
6337    fn test_drop_index_if_exists() {
6338        let stmt = parse_one("DROP INDEX IF EXISTS idx");
6339        if let Statement::Drop(d) = stmt {
6340            assert_eq!(d.object_type, DropObjectType::Index);
6341            assert!(d.if_exists);
6342        } else {
6343            unreachable!("expected Drop");
6344        }
6345    }
6346
6347    #[test]
6348    fn test_drop_trigger_if_exists_qualified() {
6349        let stmt = parse_one("DROP TRIGGER IF EXISTS main.tr");
6350        if let Statement::Drop(d) = stmt {
6351            assert_eq!(d.object_type, DropObjectType::Trigger);
6352            assert!(d.if_exists);
6353            assert_eq!(d.name.schema.as_deref(), Some("main"));
6354            assert_eq!(d.name.name, "tr");
6355        } else {
6356            unreachable!("expected Drop");
6357        }
6358    }
6359
6360    #[test]
6361    fn test_drop_table_qualified() {
6362        let stmt = parse_one("DROP TABLE main.t");
6363        if let Statement::Drop(d) = stmt {
6364            assert_eq!(d.name.schema.as_deref(), Some("main"));
6365            assert_eq!(d.name.name, "t");
6366        } else {
6367            unreachable!("expected Drop");
6368        }
6369    }
6370
6371    #[test]
6372    fn test_create_trigger_if_not_exists() {
6373        let stmt =
6374            parse_one("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
6375        if let Statement::CreateTrigger(ct) = stmt {
6376            assert!(ct.if_not_exists);
6377            assert_eq!(ct.name.name, "tr");
6378        } else {
6379            unreachable!("expected CreateTrigger");
6380        }
6381    }
6382
6383    #[test]
6384    fn test_create_temp_trigger() {
6385        let stmt = parse_one("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
6386        if let Statement::CreateTrigger(ct) = stmt {
6387            assert!(ct.temporary);
6388            assert_eq!(ct.name.name, "tr");
6389        } else {
6390            unreachable!("expected CreateTrigger");
6391        }
6392    }
6393
6394    #[test]
6395    fn test_create_view_qualified_name() {
6396        let stmt = parse_one("CREATE VIEW main.v AS SELECT 1");
6397        if let Statement::CreateView(cv) = stmt {
6398            assert_eq!(cv.name.schema.as_deref(), Some("main"));
6399            assert_eq!(cv.name.name, "v");
6400        } else {
6401            unreachable!("expected CreateView");
6402        }
6403    }
6404
6405    #[test]
6406    fn test_alter_table_qualified() {
6407        let stmt = parse_one("ALTER TABLE main.t RENAME TO u");
6408        if let Statement::AlterTable(at) = stmt {
6409            assert_eq!(at.table.schema.as_deref(), Some("main"));
6410            assert_eq!(at.table.name, "t");
6411        } else {
6412            unreachable!("expected AlterTable");
6413        }
6414    }
6415
6416    #[test]
6417    fn test_roundtrip_reindex_all() {
6418        assert_roundtrip("REINDEX");
6419        assert_roundtrip("REINDEX t");
6420        assert_roundtrip("REINDEX main.idx");
6421    }
6422
6423    #[test]
6424    fn test_roundtrip_analyze_all() {
6425        assert_roundtrip("ANALYZE");
6426        assert_roundtrip("ANALYZE t");
6427        assert_roundtrip("ANALYZE main.t");
6428    }
6429
6430    #[test]
6431    fn test_roundtrip_drop_all_types_extended() {
6432        assert_roundtrip("DROP TABLE IF EXISTS main.t");
6433        assert_roundtrip("DROP VIEW IF EXISTS v");
6434        assert_roundtrip("DROP INDEX IF EXISTS idx");
6435        assert_roundtrip("DROP TRIGGER IF EXISTS main.tr");
6436    }
6437
6438    #[test]
6439    fn test_roundtrip_create_trigger_extended() {
6440        assert_roundtrip("CREATE TRIGGER IF NOT EXISTS tr BEFORE INSERT ON t BEGIN SELECT 1; END");
6441        assert_roundtrip("CREATE TEMP TRIGGER tr BEFORE INSERT ON t BEGIN SELECT 1; END");
6442        assert_roundtrip(
6443            "CREATE TRIGGER tr INSTEAD OF UPDATE ON v BEGIN INSERT INTO log VALUES (1); END",
6444        );
6445        assert_roundtrip("CREATE TRIGGER tr BEFORE UPDATE OF a, b ON t BEGIN SELECT 1; END");
6446        assert_roundtrip(
6447            "CREATE TRIGGER tr AFTER DELETE ON \"order\" BEGIN INSERT INTO log VALUES (OLD.id); END",
6448        );
6449    }
6450
6451    #[test]
6452    fn test_roundtrip_create_view_extended() {
6453        assert_roundtrip("CREATE VIEW main.v AS SELECT 1");
6454        assert_roundtrip("CREATE VIEW v(x, y, z) AS SELECT a, b, c FROM t");
6455    }
6456
6457    #[test]
6458    fn test_roundtrip_alter_table_extended() {
6459        assert_roundtrip("ALTER TABLE t RENAME COLUMN a TO b");
6460        assert_roundtrip("ALTER TABLE main.t RENAME TO u");
6461        assert_roundtrip("ALTER TABLE t ADD COLUMN c INTEGER NOT NULL DEFAULT 0");
6462    }
6463
6464    // -----------------------------------------------------------------------
6465    // bd-7pxb §12.10-12.12 Transaction Control + ATTACH/DETACH + EXPLAIN
6466    // -----------------------------------------------------------------------
6467
6468    #[test]
6469    fn test_begin_deferred() {
6470        let stmt = parse_one("BEGIN DEFERRED TRANSACTION");
6471        if let Statement::Begin(b) = stmt {
6472            assert_eq!(b.mode, Some(TransactionMode::Deferred));
6473        } else {
6474            unreachable!("expected Begin");
6475        }
6476    }
6477
6478    #[test]
6479    fn test_begin_immediate() {
6480        let stmt = parse_one("BEGIN IMMEDIATE");
6481        if let Statement::Begin(b) = stmt {
6482            assert_eq!(b.mode, Some(TransactionMode::Immediate));
6483        } else {
6484            unreachable!("expected Begin");
6485        }
6486    }
6487
6488    #[test]
6489    fn test_begin_exclusive() {
6490        let stmt = parse_one("BEGIN EXCLUSIVE TRANSACTION");
6491        if let Statement::Begin(b) = stmt {
6492            assert_eq!(b.mode, Some(TransactionMode::Exclusive));
6493        } else {
6494            unreachable!("expected Begin");
6495        }
6496    }
6497
6498    #[test]
6499    fn test_begin_concurrent() {
6500        let stmt = parse_one("BEGIN CONCURRENT");
6501        if let Statement::Begin(b) = stmt {
6502            assert_eq!(b.mode, Some(TransactionMode::Concurrent));
6503        } else {
6504            unreachable!("expected Begin");
6505        }
6506    }
6507
6508    #[test]
6509    fn test_concurrent_no_conflict() {
6510        // Parser-level: BEGIN without mode (the concurrent entry point) parses.
6511        // Runtime concurrent writer conflict detection is in the MVCC/WAL layer.
6512        let stmt = parse_one("BEGIN");
6513        assert!(matches!(stmt, Statement::Begin(_)));
6514    }
6515
6516    #[test]
6517    fn test_concurrent_page_conflict() {
6518        // Parser-level: verify basic transaction and DML parse.
6519        // Runtime page-level conflict (SQLITE_BUSY_SNAPSHOT) is in the MVCC layer.
6520        let stmts = parse_ok("BEGIN; INSERT INTO t (a) VALUES (1)");
6521        assert_eq!(stmts.len(), 2);
6522        assert!(matches!(stmts[0], Statement::Begin(_)));
6523        assert!(matches!(stmts[1], Statement::Insert(_)));
6524    }
6525
6526    #[test]
6527    fn test_commit_end_synonym() {
6528        let stmt1 = parse_one("COMMIT");
6529        assert!(matches!(stmt1, Statement::Commit));
6530        let stmt2 = parse_one("END TRANSACTION");
6531        assert!(matches!(stmt2, Statement::Commit));
6532        let stmt3 = parse_one("COMMIT TRANSACTION");
6533        assert!(matches!(stmt3, Statement::Commit));
6534    }
6535
6536    #[test]
6537    fn test_rollback() {
6538        let stmt = parse_one("ROLLBACK");
6539        if let Statement::Rollback(r) = stmt {
6540            assert!(r.to_savepoint.is_none());
6541        } else {
6542            unreachable!("expected Rollback");
6543        }
6544    }
6545
6546    #[test]
6547    fn test_savepoint_basic() {
6548        let stmt = parse_one("SAVEPOINT sp1");
6549        assert!(matches!(stmt, Statement::Savepoint(ref name) if name == "sp1"));
6550    }
6551
6552    #[test]
6553    fn test_savepoint_release() {
6554        let stmt = parse_one("RELEASE SAVEPOINT sp1");
6555        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp1"));
6556    }
6557
6558    #[test]
6559    fn test_savepoint_release_removes_later() {
6560        // Parser-level: RELEASE without SAVEPOINT keyword also works.
6561        // Runtime savepoint stack semantics verified in engine tests.
6562        let stmt = parse_one("RELEASE sp2");
6563        assert!(matches!(stmt, Statement::Release(ref name) if name == "sp2"));
6564    }
6565
6566    #[test]
6567    fn test_savepoint_rollback_to() {
6568        let stmt = parse_one("ROLLBACK TO SAVEPOINT sp1");
6569        if let Statement::Rollback(r) = stmt {
6570            assert_eq!(r.to_savepoint.as_deref(), Some("sp1"));
6571        } else {
6572            unreachable!("expected Rollback");
6573        }
6574    }
6575
6576    #[test]
6577    fn test_savepoint_nested() {
6578        // Parser-level: multiple savepoints in sequence parse independently.
6579        // Runtime stack semantics verified in engine tests.
6580        let stmts = parse_ok("SAVEPOINT sp1; SAVEPOINT sp2; SAVEPOINT sp3");
6581        assert_eq!(stmts.len(), 3);
6582        assert!(matches!(stmts[0], Statement::Savepoint(ref n) if n == "sp1"));
6583        assert!(matches!(stmts[1], Statement::Savepoint(ref n) if n == "sp2"));
6584        assert!(matches!(stmts[2], Statement::Savepoint(ref n) if n == "sp3"));
6585    }
6586
6587    #[test]
6588    fn test_savepoint_rollback_then_continue() {
6589        // Parser-level: ROLLBACK TO followed by more DML parses.
6590        let stmts = parse_ok("ROLLBACK TO sp1; INSERT INTO t VALUES (1)");
6591        assert_eq!(stmts.len(), 2);
6592        assert!(matches!(stmts[0], Statement::Rollback(_)));
6593        assert!(matches!(stmts[1], Statement::Insert(_)));
6594    }
6595
6596    #[test]
6597    fn test_attach_database() {
6598        let stmt = parse_one("ATTACH DATABASE 'other.db' AS other");
6599        if let Statement::Attach(a) = stmt {
6600            assert_eq!(a.schema, "other");
6601        } else {
6602            unreachable!("expected Attach");
6603        }
6604    }
6605
6606    #[test]
6607    fn test_attach_schema_qualified_access() {
6608        // Parser-level: schema-qualified table reference parses correctly.
6609        let stmt = parse_one("SELECT * FROM other.t");
6610        if let Statement::Select(s) = stmt {
6611            if let SelectCore::Select { from, .. } = &s.body.select {
6612                let from = from.as_ref().expect("FROM clause");
6613                match &from.source {
6614                    TableOrSubquery::Table { name, .. } => {
6615                        assert_eq!(name.schema.as_deref(), Some("other"));
6616                        assert_eq!(name.name, "t");
6617                    }
6618                    other => unreachable!("expected Table source, got {other:?}"),
6619                }
6620            } else {
6621                unreachable!("expected Select core");
6622            }
6623        } else {
6624            unreachable!("expected Select");
6625        }
6626    }
6627
6628    #[test]
6629    fn test_detach_database() {
6630        let stmt = parse_one("DETACH DATABASE other");
6631        assert!(matches!(stmt, Statement::Detach(ref name) if name == "other"));
6632    }
6633
6634    #[test]
6635    fn test_attach_max_limit() {
6636        // Parser-level: ATTACH parses identically regardless of limit.
6637        // Runtime SQLITE_MAX_ATTACHED enforcement is in the engine.
6638        let stmt = parse_one("ATTACH 'db11.sqlite' AS db11");
6639        if let Statement::Attach(a) = stmt {
6640            assert_eq!(a.schema, "db11");
6641        } else {
6642            unreachable!("expected Attach");
6643        }
6644    }
6645
6646    #[test]
6647    fn test_cross_database_transaction() {
6648        // Parser-level: transaction with cross-database DML parses.
6649        // Runtime cross-database atomic commit is in WAL/MVCC layer.
6650        let stmts = parse_ok("BEGIN; INSERT INTO main.t SELECT * FROM other.t; COMMIT");
6651        assert_eq!(stmts.len(), 3);
6652        assert!(matches!(stmts[0], Statement::Begin(_)));
6653        assert!(matches!(stmts[1], Statement::Insert(_)));
6654        assert!(matches!(stmts[2], Statement::Commit));
6655    }
6656
6657    #[test]
6658    fn test_explain_returns_bytecode() {
6659        let stmt = parse_one("EXPLAIN SELECT 1");
6660        if let Statement::Explain { query_plan, stmt } = stmt {
6661            assert!(!query_plan);
6662            assert!(matches!(*stmt, Statement::Select(_)));
6663        } else {
6664            unreachable!("expected Explain");
6665        }
6666    }
6667
6668    #[test]
6669    fn test_explain_query_plan_columns() {
6670        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
6671        if let Statement::Explain { query_plan, stmt } = stmt {
6672            assert!(query_plan);
6673            assert!(matches!(*stmt, Statement::Select(_)));
6674        } else {
6675            unreachable!("expected Explain");
6676        }
6677    }
6678
6679    #[test]
6680    fn test_explain_query_plan_shows_index() {
6681        // Parser-level: EXPLAIN QUERY PLAN on indexed query parses.
6682        // Runtime index usage in EQP output is in the planner.
6683        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t WHERE id = 1");
6684        if let Statement::Explain { query_plan, .. } = stmt {
6685            assert!(query_plan);
6686        } else {
6687            unreachable!("expected Explain");
6688        }
6689    }
6690
6691    #[test]
6692    fn test_explain_query_plan_tree_structure() {
6693        // Parser-level: EXPLAIN QUERY PLAN on a join query parses.
6694        // Runtime tree structure in EQP output is in the planner.
6695        let stmt = parse_one("EXPLAIN QUERY PLAN SELECT * FROM t1 JOIN t2 ON t1.id = t2.t1_id");
6696        if let Statement::Explain { query_plan, stmt } = stmt {
6697            assert!(query_plan);
6698            assert!(matches!(*stmt, Statement::Select(_)));
6699        } else {
6700            unreachable!("expected Explain");
6701        }
6702    }
6703
6704    // -----------------------------------------------------------------------
6705    // bd-2kvo Phase 3 acceptance: keywords as identifiers
6706    // -----------------------------------------------------------------------
6707
6708    #[test]
6709    fn test_parser_keyword_as_column_name() {
6710        // "order" is a keyword but valid as a column name in many contexts.
6711        let stmt = parse_one("SELECT \"order\" FROM t");
6712        assert!(matches!(stmt, Statement::Select(_)));
6713    }
6714
6715    #[test]
6716    fn test_parser_keyword_as_alias() {
6717        let stmt = parse_one("SELECT 1 AS \"limit\"");
6718        assert!(matches!(stmt, Statement::Select(_)));
6719    }
6720
6721    #[test]
6722    fn test_parser_keyword_as_table_name() {
6723        let stmt = parse_one("SELECT * FROM \"group\"");
6724        assert!(matches!(stmt, Statement::Select(_)));
6725    }
6726
6727    // -----------------------------------------------------------------------
6728    // bd-2kvo Phase 3 acceptance: all statement types (Section 12 coverage)
6729    // -----------------------------------------------------------------------
6730
6731    #[test]
6732    fn test_parser_all_statement_types() {
6733        // Each statement type from Section 12 must parse without error.
6734        let statements = [
6735            // DML
6736            "SELECT 1",
6737            "INSERT INTO t VALUES (1)",
6738            "INSERT OR REPLACE INTO t VALUES (1)",
6739            "UPDATE t SET a = 1",
6740            "DELETE FROM t WHERE id = 1",
6741            "REPLACE INTO t VALUES (1)",
6742            // DDL
6743            "CREATE TABLE t (id INTEGER PRIMARY KEY)",
6744            "CREATE TEMPORARY TABLE t (id INTEGER)",
6745            "CREATE TABLE IF NOT EXISTS t (id INTEGER)",
6746            "CREATE INDEX idx ON t (a)",
6747            "CREATE UNIQUE INDEX idx ON t (a)",
6748            "CREATE VIEW v AS SELECT 1",
6749            "CREATE TRIGGER tr AFTER INSERT ON t BEGIN SELECT 1; END",
6750            "CREATE VIRTUAL TABLE t USING fts5(a, b)",
6751            "ALTER TABLE t RENAME TO t2",
6752            "ALTER TABLE t ADD COLUMN c TEXT",
6753            "ALTER TABLE t DROP COLUMN c",
6754            "ALTER TABLE t RENAME COLUMN a TO b",
6755            "DROP TABLE t",
6756            "DROP TABLE IF EXISTS t",
6757            "DROP INDEX idx",
6758            "DROP VIEW v",
6759            "DROP TRIGGER tr",
6760            // Transaction
6761            "BEGIN",
6762            "BEGIN DEFERRED",
6763            "BEGIN IMMEDIATE",
6764            "BEGIN EXCLUSIVE",
6765            "COMMIT",
6766            "END",
6767            "ROLLBACK",
6768            "SAVEPOINT sp1",
6769            "RELEASE sp1",
6770            "RELEASE SAVEPOINT sp1",
6771            "ROLLBACK TO sp1",
6772            "ROLLBACK TO SAVEPOINT sp1",
6773            // Utility
6774            "ATTACH DATABASE ':memory:' AS db2",
6775            "DETACH db2",
6776            "ANALYZE",
6777            "ANALYZE t",
6778            "VACUUM",
6779            "VACUUM INTO '/tmp/backup.db'",
6780            "REINDEX",
6781            "REINDEX t",
6782            "EXPLAIN SELECT 1",
6783            "EXPLAIN QUERY PLAN SELECT 1",
6784            // PRAGMA
6785            "PRAGMA journal_mode",
6786            "PRAGMA journal_mode = WAL",
6787            "PRAGMA table_info(t)",
6788        ];
6789
6790        for sql in &statements {
6791            let mut p = Parser::from_sql(sql);
6792            let (stmts, errs) = p.parse_all();
6793            assert!(errs.is_empty(), "failed to parse '{sql}': {errs:?}");
6794            assert_eq!(
6795                stmts.len(),
6796                1,
6797                "expected 1 statement for '{sql}', got {}",
6798                stmts.len()
6799            );
6800        }
6801    }
6802
6803    // -----------------------------------------------------------------------
6804    // bd-2kvo Phase 3 acceptance: expression precedence
6805    // -----------------------------------------------------------------------
6806
6807    #[test]
6808    fn test_parser_expression_precedence_mul_over_add() {
6809        // 1 + 2 * 3 should parse as 1 + (2 * 3)
6810        let stmt = parse_one("SELECT 1 + 2 * 3");
6811        if let Statement::Select(s) = stmt {
6812            if let SelectCore::Select { columns, .. } = &s.body.select {
6813                match &columns[0] {
6814                    ResultColumn::Expr { expr, .. } => {
6815                        // Outer expression should be Add, right side should be Multiply.
6816                        assert!(
6817                            matches!(expr, Expr::BinaryOp { .. }),
6818                            "expected BinaryOp, got {expr:?}"
6819                        );
6820                    }
6821                    other => unreachable!("expected Expr column, got {other:?}"),
6822                }
6823            } else {
6824                unreachable!("expected Select core");
6825            }
6826        } else {
6827            unreachable!("expected Select");
6828        }
6829    }
6830
6831    // -----------------------------------------------------------------------
6832    // bd-2kvo Phase 3 acceptance: INSERT with ON CONFLICT and RETURNING
6833    // -----------------------------------------------------------------------
6834
6835    #[test]
6836    fn test_parser_insert_on_conflict() {
6837        let stmt =
6838            parse_one("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a");
6839        if let Statement::Insert(i) = stmt {
6840            assert!(!i.upsert.is_empty());
6841        } else {
6842            unreachable!("expected Insert");
6843        }
6844    }
6845
6846    #[test]
6847    fn test_parser_insert_returning() {
6848        let stmt = parse_one("INSERT INTO t (a) VALUES (1) RETURNING *");
6849        if let Statement::Insert(i) = stmt {
6850            assert!(!i.returning.is_empty());
6851        } else {
6852            unreachable!("expected Insert");
6853        }
6854    }
6855
6856    #[test]
6857    fn test_parser_delete_returning() {
6858        let stmt = parse_one("DELETE FROM t WHERE id = 1 RETURNING *");
6859        if let Statement::Delete(d) = stmt {
6860            assert!(!d.returning.is_empty());
6861        } else {
6862            unreachable!("expected Delete");
6863        }
6864    }
6865
6866    #[test]
6867    fn test_parser_update_returning() {
6868        let stmt = parse_one("UPDATE t SET a = 1 RETURNING a, b");
6869        if let Statement::Update(u) = stmt {
6870            assert_eq!(u.returning.len(), 2);
6871        } else {
6872            unreachable!("expected Update");
6873        }
6874    }
6875
6876    // -----------------------------------------------------------------------
6877    // bd-2kvo Phase 3 acceptance: compound SELECT operators
6878    // -----------------------------------------------------------------------
6879
6880    #[test]
6881    fn test_parser_union() {
6882        let stmt = parse_one("SELECT 1 UNION SELECT 2");
6883        if let Statement::Select(s) = stmt {
6884            assert_eq!(s.body.compounds.len(), 1);
6885            assert_eq!(s.body.compounds[0].0, CompoundOp::Union);
6886        } else {
6887            unreachable!("expected Select");
6888        }
6889    }
6890
6891    #[test]
6892    fn test_parser_intersect() {
6893        let stmt = parse_one("SELECT 1 INTERSECT SELECT 2");
6894        if let Statement::Select(s) = stmt {
6895            assert_eq!(s.body.compounds.len(), 1);
6896            assert_eq!(s.body.compounds[0].0, CompoundOp::Intersect);
6897        } else {
6898            unreachable!("expected Select");
6899        }
6900    }
6901
6902    #[test]
6903    fn test_parser_except() {
6904        let stmt = parse_one("SELECT 1 EXCEPT SELECT 2");
6905        if let Statement::Select(s) = stmt {
6906            assert_eq!(s.body.compounds.len(), 1);
6907            assert_eq!(s.body.compounds[0].0, CompoundOp::Except);
6908        } else {
6909            unreachable!("expected Select");
6910        }
6911    }
6912
6913    // -----------------------------------------------------------------------
6914    // bd-2kvo Phase 3 acceptance: subquery in FROM
6915    // -----------------------------------------------------------------------
6916
6917    #[test]
6918    fn test_parser_subquery_in_from() {
6919        let stmt = parse_one("SELECT * FROM (SELECT 1 AS x) AS sub");
6920        assert!(matches!(stmt, Statement::Select(_)));
6921    }
6922
6923    // -----------------------------------------------------------------------
6924    // bd-2kvo Phase 3 acceptance: CREATE TABLE with constraints
6925    // -----------------------------------------------------------------------
6926
6927    #[test]
6928    fn test_parser_create_table_all_constraints() {
6929        let stmt = parse_one(
6930            "CREATE TABLE t (\
6931             id INTEGER PRIMARY KEY AUTOINCREMENT,\
6932             name TEXT NOT NULL DEFAULT '',\
6933             email TEXT UNIQUE,\
6934             age INTEGER CHECK(age >= 0),\
6935             dept_id INTEGER REFERENCES dept(id) ON DELETE CASCADE,\
6936             CONSTRAINT pk PRIMARY KEY (id),\
6937             UNIQUE (email),\
6938             CHECK (age < 200),\
6939             FOREIGN KEY (dept_id) REFERENCES dept(id)\
6940             )",
6941        );
6942        if let Statement::CreateTable(ct) = stmt {
6943            if let CreateTableBody::Columns {
6944                columns,
6945                constraints,
6946            } = ct.body
6947            {
6948                assert_eq!(columns.len(), 5);
6949                assert!(!constraints.is_empty());
6950            } else {
6951                unreachable!("expected column defs");
6952            }
6953        } else {
6954            unreachable!("expected CreateTable");
6955        }
6956    }
6957
6958    // -----------------------------------------------------------------------
6959    // bd-2kvo Phase 3 acceptance: CREATE TRIGGER with all timing/events
6960    // -----------------------------------------------------------------------
6961
6962    #[test]
6963    fn test_parser_create_trigger_before_delete() {
6964        let stmt = parse_one("CREATE TRIGGER tr BEFORE DELETE ON t BEGIN SELECT 1; END");
6965        if let Statement::CreateTrigger(tr) = stmt {
6966            assert_eq!(tr.timing, TriggerTiming::Before);
6967            assert!(matches!(tr.event, TriggerEvent::Delete));
6968        } else {
6969            unreachable!("expected CreateTrigger");
6970        }
6971    }
6972
6973    #[test]
6974    fn test_parser_create_trigger_instead_of_update() {
6975        let stmt =
6976            parse_one("CREATE TRIGGER tr INSTEAD OF UPDATE OF a, b ON v BEGIN SELECT 1; END");
6977        if let Statement::CreateTrigger(tr) = stmt {
6978            assert_eq!(tr.timing, TriggerTiming::InsteadOf);
6979            if let TriggerEvent::Update(cols) = &tr.event {
6980                assert_eq!(cols.len(), 2);
6981            } else {
6982                unreachable!("expected UpdateOf event");
6983            }
6984        } else {
6985            unreachable!("expected CreateTrigger");
6986        }
6987    }
6988
6989    // -----------------------------------------------------------------------
6990    // bd-2kvo Phase 3 acceptance: CREATE VIEW with columns
6991    // -----------------------------------------------------------------------
6992
6993    #[test]
6994    fn test_parser_create_view_with_columns() {
6995        let stmt = parse_one("CREATE VIEW v (a, b) AS SELECT 1, 2");
6996        if let Statement::CreateView(cv) = stmt {
6997            assert_eq!(cv.columns, vec!["a".to_owned(), "b".to_owned()]);
6998        } else {
6999            unreachable!("expected CreateView");
7000        }
7001    }
7002
7003    // -----------------------------------------------------------------------
7004    // bd-2kvo Phase 3 acceptance: multi-way join
7005    // -----------------------------------------------------------------------
7006
7007    #[test]
7008    fn test_parser_multi_join() {
7009        let stmt = parse_one(
7010            "SELECT a.x, b.y, c.z FROM a \
7011             JOIN b ON a.id = b.a_id \
7012             LEFT JOIN c ON b.id = c.b_id \
7013             CROSS JOIN d",
7014        );
7015        if let Statement::Select(s) = stmt {
7016            if let SelectCore::Select { from, .. } = &s.body.select {
7017                let from = from.as_ref().expect("FROM clause");
7018                assert_eq!(from.joins.len(), 3);
7019                assert_eq!(from.joins[0].join_type.kind, JoinKind::Inner);
7020                assert_eq!(from.joins[1].join_type.kind, JoinKind::Left);
7021                assert_eq!(from.joins[2].join_type.kind, JoinKind::Cross);
7022            } else {
7023                unreachable!("expected Select core");
7024            }
7025        } else {
7026            unreachable!("expected Select");
7027        }
7028    }
7029
7030    // -----------------------------------------------------------------------
7031    // bd-2kvo Phase 3 acceptance: GROUP BY / HAVING
7032    // -----------------------------------------------------------------------
7033
7034    #[test]
7035    fn test_parser_group_by_having() {
7036        let stmt = parse_one("SELECT dept, count(*) FROM emp GROUP BY dept HAVING count(*) > 5");
7037        if let Statement::Select(s) = stmt {
7038            if let SelectCore::Select {
7039                group_by, having, ..
7040            } = &s.body.select
7041            {
7042                assert!(!group_by.is_empty());
7043                assert!(having.is_some());
7044            } else {
7045                unreachable!("expected Select core");
7046            }
7047        } else {
7048            unreachable!("expected Select");
7049        }
7050    }
7051
7052    // -----------------------------------------------------------------------
7053    // bd-2kvo Phase 3 acceptance: Error recovery with line:column spans
7054    // -----------------------------------------------------------------------
7055
7056    #[test]
7057    fn test_parser_error_recovery_with_span() {
7058        // Multi-line input with an error on line 2.
7059        let sql = "SELECT 1;\nXYZZY 42;\nSELECT 3";
7060        let mut p = Parser::from_sql(sql);
7061        let (stmts, errs) = p.parse_all();
7062        assert_eq!(stmts.len(), 2, "should recover two valid statements");
7063        assert!(!errs.is_empty(), "should report at least one error");
7064
7065        let err = &errs[0];
7066        // XYZZY starts at line 2, column 1.
7067        assert_eq!(err.line, 2, "error should be on line 2");
7068        assert_eq!(err.col, 1, "error should be at column 1");
7069        // Span should be non-zero and point within the source.
7070        assert!(
7071            err.span.start < err.span.end,
7072            "error span should be non-empty"
7073        );
7074        let source_len = u32::try_from(sql.len()).unwrap();
7075        assert!(
7076            err.span.end <= source_len,
7077            "error span.end should be within source"
7078        );
7079    }
7080
7081    #[test]
7082    fn test_parser_error_span_mid_line() {
7083        // Incomplete CREATE should produce an error.
7084        let bad = Parser::from_sql("CREATE").parse_statement();
7085        assert!(bad.is_err());
7086        let err = bad.unwrap_err();
7087        assert_eq!(err.line, 1);
7088    }
7089
7090    // -----------------------------------------------------------------------
7091    // bd-2kvo Phase 3 acceptance: Keyword lookup covers 150+ keywords
7092    // -----------------------------------------------------------------------
7093
7094    #[test]
7095    #[allow(clippy::too_many_lines)]
7096    fn test_parser_keyword_lookup_all_150() {
7097        use crate::token::TokenKind;
7098
7099        // Exhaustive list of all SQL keywords in lookup_keyword.
7100        let keywords = [
7101            "ABORT",
7102            "ACTION",
7103            "ADD",
7104            "AFTER",
7105            "ALL",
7106            "ALTER",
7107            "ALWAYS",
7108            "ANALYZE",
7109            "AND",
7110            "AS",
7111            "ASC",
7112            "ATTACH",
7113            "AUTOINCREMENT",
7114            "BEFORE",
7115            "BEGIN",
7116            "BETWEEN",
7117            "BY",
7118            "CASCADE",
7119            "CASE",
7120            "CAST",
7121            "CHECK",
7122            "COLLATE",
7123            "COLUMN",
7124            "COMMIT",
7125            "CONCURRENT",
7126            "CONFLICT",
7127            "CONSTRAINT",
7128            "CREATE",
7129            "CROSS",
7130            "CURRENT_DATE",
7131            "CURRENT_TIME",
7132            "CURRENT_TIMESTAMP",
7133            "DATABASE",
7134            "DEFAULT",
7135            "DEFERRABLE",
7136            "DEFERRED",
7137            "DELETE",
7138            "DESC",
7139            "DETACH",
7140            "DISTINCT",
7141            "DO",
7142            "DROP",
7143            "EACH",
7144            "ELSE",
7145            "END",
7146            "ESCAPE",
7147            "EXCEPT",
7148            "EXCLUDE",
7149            "EXCLUSIVE",
7150            "EXISTS",
7151            "EXPLAIN",
7152            "FAIL",
7153            "FILTER",
7154            "FIRST",
7155            "FOLLOWING",
7156            "FOR",
7157            "FOREIGN",
7158            "FROM",
7159            "FULL",
7160            "GENERATED",
7161            "GLOB",
7162            "GROUP",
7163            "GROUPS",
7164            "HAVING",
7165            "IF",
7166            "IGNORE",
7167            "IMMEDIATE",
7168            "IN",
7169            "INDEX",
7170            "INDEXED",
7171            "INITIALLY",
7172            "INNER",
7173            "INSERT",
7174            "INSTEAD",
7175            "INTERSECT",
7176            "INTO",
7177            "IS",
7178            "ISNULL",
7179            "JOIN",
7180            "KEY",
7181            "LAST",
7182            "LEFT",
7183            "LIKE",
7184            "LIMIT",
7185            "MATCH",
7186            "MATERIALIZED",
7187            "NATURAL",
7188            "NO",
7189            "NOT",
7190            "NOTHING",
7191            "NOTNULL",
7192            "NULL",
7193            "NULLS",
7194            "OF",
7195            "OFFSET",
7196            "ON",
7197            "OR",
7198            "ORDER",
7199            "OTHERS",
7200            "OUTER",
7201            "OVER",
7202            "PARTITION",
7203            "PLAN",
7204            "PRAGMA",
7205            "PRECEDING",
7206            "PRIMARY",
7207            "QUERY",
7208            "RAISE",
7209            "RANGE",
7210            "RECURSIVE",
7211            "REFERENCES",
7212            "REGEXP",
7213            "REINDEX",
7214            "RELEASE",
7215            "RENAME",
7216            "REPLACE",
7217            "RESTRICT",
7218            "RETURNING",
7219            "RIGHT",
7220            "ROLLBACK",
7221            "ROW",
7222            "ROWS",
7223            "SAVEPOINT",
7224            "SELECT",
7225            "SET",
7226            "STORED",
7227            "STRICT",
7228            "TABLE",
7229            "TEMP",
7230            "TEMPORARY",
7231            "THEN",
7232            "TIES",
7233            "TO",
7234            "TRANSACTION",
7235            "TRIGGER",
7236            "TRUE",
7237            "FALSE",
7238            "UNBOUNDED",
7239            "UNION",
7240            "UNIQUE",
7241            "UPDATE",
7242            "USING",
7243            "VACUUM",
7244            "VALUES",
7245            "VIEW",
7246            "VIRTUAL",
7247            "WHEN",
7248            "WHERE",
7249            "WINDOW",
7250            "WITH",
7251            "WITHOUT",
7252        ];
7253
7254        assert!(
7255            keywords.len() >= 150,
7256            "expected 150+ keywords, got {}",
7257            keywords.len()
7258        );
7259
7260        for kw in &keywords {
7261            assert!(
7262                TokenKind::lookup_keyword(kw).is_some(),
7263                "keyword {kw} not recognized (uppercase)"
7264            );
7265            // Case-insensitive: lowercase must also work.
7266            let lower = kw.to_ascii_lowercase();
7267            assert!(
7268                TokenKind::lookup_keyword(&lower).is_some(),
7269                "keyword {kw} not recognized (lowercase)"
7270            );
7271            // Mixed case.
7272            let mixed: String = kw
7273                .chars()
7274                .enumerate()
7275                .map(|(i, c)| {
7276                    if i % 2 == 0 {
7277                        c.to_ascii_lowercase()
7278                    } else {
7279                        c.to_ascii_uppercase()
7280                    }
7281                })
7282                .collect();
7283            assert!(
7284                TokenKind::lookup_keyword(&mixed).is_some(),
7285                "keyword {kw} not recognized (mixed case: {mixed})"
7286            );
7287        }
7288
7289        // Non-keyword should return None.
7290        assert!(TokenKind::lookup_keyword("FOOBAR").is_none());
7291        assert!(TokenKind::lookup_keyword("").is_none());
7292    }
7293
7294    // -----------------------------------------------------------------------
7295    // Round-trip: parse → Display → re-parse → compare ASTs
7296    // -----------------------------------------------------------------------
7297
7298    /// Parse SQL, convert back to string via Display, re-parse, convert back
7299    /// again, and assert the two rendered strings are identical.  We compare
7300    /// rendered strings (not ASTs) because Display may normalise constructs
7301    /// (e.g. `INSERT OR REPLACE` → `REPLACE`) which changes SQL length and
7302    /// therefore Span positions, while the logical content is identical.
7303    fn assert_roundtrip(sql: &str) {
7304        let ast1 = parse_one(sql);
7305        let rendered1 = ast1.to_string();
7306        let ast2 = parse_one(&rendered1);
7307        let rendered2 = ast2.to_string();
7308        assert_eq!(
7309            rendered1, rendered2,
7310            "round-trip failed for:\n  input: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
7311        );
7312    }
7313
7314    #[test]
7315    fn test_roundtrip_select_simple() {
7316        assert_roundtrip("SELECT 1");
7317        assert_roundtrip("SELECT 1, 2, 3");
7318        assert_roundtrip("SELECT *");
7319        assert_roundtrip("SELECT * FROM t");
7320        assert_roundtrip("SELECT a, b FROM t WHERE a > 10");
7321        assert_roundtrip("SELECT a FROM t ORDER BY a DESC");
7322        assert_roundtrip("SELECT a FROM t LIMIT 10 OFFSET 5");
7323    }
7324
7325    #[test]
7326    fn test_roundtrip_select_distinct() {
7327        assert_roundtrip("SELECT DISTINCT a, b FROM t");
7328    }
7329
7330    #[test]
7331    fn test_roundtrip_select_alias() {
7332        assert_roundtrip("SELECT a AS x, b AS y FROM t AS u");
7333    }
7334
7335    #[test]
7336    fn test_roundtrip_select_join_types() {
7337        assert_roundtrip("SELECT * FROM a INNER JOIN b ON a.id = b.id");
7338        assert_roundtrip("SELECT * FROM a LEFT JOIN b ON a.id = b.id");
7339        assert_roundtrip("SELECT * FROM a RIGHT JOIN b ON a.id = b.id");
7340        assert_roundtrip("SELECT * FROM a FULL JOIN b ON a.id = b.id");
7341        assert_roundtrip("SELECT * FROM a CROSS JOIN b");
7342        assert_roundtrip("SELECT * FROM a NATURAL INNER JOIN b");
7343        assert_roundtrip("SELECT * FROM a LEFT JOIN b USING (id)");
7344    }
7345
7346    #[test]
7347    fn test_roundtrip_select_subquery() {
7348        assert_roundtrip("SELECT * FROM (SELECT 1 AS x) AS sub");
7349    }
7350
7351    #[test]
7352    fn test_roundtrip_select_group_by_having() {
7353        assert_roundtrip("SELECT a, count(*) FROM t GROUP BY a HAVING count(*) > 1");
7354    }
7355
7356    #[test]
7357    fn test_roundtrip_select_window() {
7358        assert_roundtrip("SELECT sum(x) OVER (PARTITION BY g ORDER BY x) FROM t");
7359    }
7360
7361    #[test]
7362    fn test_roundtrip_select_cte() {
7363        assert_roundtrip("WITH cte AS (SELECT 1 AS n) SELECT * FROM cte");
7364        assert_roundtrip(
7365            "WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x + 1 FROM cnt WHERE x < 10) SELECT * FROM cnt",
7366        );
7367    }
7368
7369    #[test]
7370    fn test_roundtrip_select_compound() {
7371        assert_roundtrip("SELECT 1 UNION SELECT 2");
7372        assert_roundtrip("SELECT 1 UNION ALL SELECT 2");
7373        assert_roundtrip("SELECT 1 INTERSECT SELECT 2");
7374        assert_roundtrip("SELECT 1 EXCEPT SELECT 2");
7375    }
7376
7377    #[test]
7378    fn test_roundtrip_insert() {
7379        assert_roundtrip("INSERT INTO t (a, b) VALUES (1, 2)");
7380        assert_roundtrip("INSERT INTO t DEFAULT VALUES");
7381        assert_roundtrip("INSERT INTO t SELECT * FROM u");
7382        assert_roundtrip("INSERT OR REPLACE INTO t (a) VALUES (1)");
7383        assert_roundtrip("REPLACE INTO t (a) VALUES (1)");
7384    }
7385
7386    #[test]
7387    fn test_roundtrip_insert_returning() {
7388        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING *");
7389        assert_roundtrip("INSERT INTO t (a) VALUES (1) RETURNING a, b");
7390    }
7391
7392    #[test]
7393    fn test_roundtrip_insert_on_conflict() {
7394        assert_roundtrip("INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO NOTHING");
7395        assert_roundtrip(
7396            "INSERT INTO t (a) VALUES (1) ON CONFLICT (a) DO UPDATE SET a = excluded.a",
7397        );
7398    }
7399
7400    #[test]
7401    fn test_roundtrip_update() {
7402        assert_roundtrip("UPDATE t SET a = 1");
7403        assert_roundtrip("UPDATE t SET a = 1, b = 2 WHERE c > 3");
7404        assert_roundtrip("UPDATE t SET a = 1 RETURNING *");
7405    }
7406
7407    #[test]
7408    fn test_roundtrip_delete() {
7409        assert_roundtrip("DELETE FROM t");
7410        assert_roundtrip("DELETE FROM t WHERE a = 1");
7411        assert_roundtrip("DELETE FROM t RETURNING *");
7412    }
7413
7414    #[test]
7415    fn test_roundtrip_create_table() {
7416        assert_roundtrip("CREATE TABLE t (a INTEGER, b TEXT)");
7417        assert_roundtrip("CREATE TABLE IF NOT EXISTS t (a INTEGER PRIMARY KEY)");
7418        assert_roundtrip("CREATE TEMP TABLE t (a TEXT NOT NULL, b REAL DEFAULT 0.0)");
7419    }
7420
7421    #[test]
7422    fn test_roundtrip_create_index() {
7423        assert_roundtrip("CREATE INDEX idx ON t (a)");
7424        assert_roundtrip("CREATE UNIQUE INDEX IF NOT EXISTS idx ON t (a, b DESC)");
7425        assert_roundtrip("CREATE INDEX idx ON t (a) WHERE a > 0");
7426    }
7427
7428    #[test]
7429    fn test_roundtrip_drop() {
7430        assert_roundtrip("DROP TABLE t");
7431        assert_roundtrip("DROP TABLE IF EXISTS t");
7432        assert_roundtrip("DROP INDEX idx");
7433        assert_roundtrip("DROP VIEW v");
7434    }
7435
7436    #[test]
7437    fn test_roundtrip_alter_table() {
7438        assert_roundtrip("ALTER TABLE t RENAME TO u");
7439        assert_roundtrip("ALTER TABLE t ADD COLUMN c TEXT");
7440        assert_roundtrip("ALTER TABLE t DROP COLUMN c");
7441    }
7442
7443    #[test]
7444    fn test_roundtrip_transaction() {
7445        assert_roundtrip("BEGIN");
7446        assert_roundtrip("BEGIN IMMEDIATE");
7447        assert_roundtrip("BEGIN EXCLUSIVE");
7448        assert_roundtrip("COMMIT");
7449        assert_roundtrip("ROLLBACK");
7450        assert_roundtrip("SAVEPOINT sp1");
7451        assert_roundtrip("RELEASE sp1");
7452    }
7453
7454    #[test]
7455    fn test_roundtrip_pragma() {
7456        assert_roundtrip("PRAGMA journal_mode");
7457        assert_roundtrip("PRAGMA journal_mode = wal");
7458    }
7459
7460    #[test]
7461    fn test_roundtrip_explain() {
7462        assert_roundtrip("EXPLAIN SELECT 1");
7463        assert_roundtrip("EXPLAIN QUERY PLAN SELECT * FROM t");
7464    }
7465
7466    #[test]
7467    fn test_roundtrip_expressions() {
7468        assert_roundtrip("SELECT 1 + 2 * 3");
7469        assert_roundtrip("SELECT NOT a");
7470        assert_roundtrip("SELECT -x");
7471        assert_roundtrip("SELECT ~x");
7472        assert_roundtrip("SELECT a BETWEEN 1 AND 10");
7473        assert_roundtrip("SELECT a NOT BETWEEN 1 AND 10");
7474        assert_roundtrip("SELECT a IN (1, 2, 3)");
7475        assert_roundtrip("SELECT a NOT IN (1, 2, 3)");
7476        assert_roundtrip("SELECT a LIKE '%foo%'");
7477        assert_roundtrip("SELECT a GLOB '*foo*'");
7478        assert_roundtrip("SELECT CASE WHEN a = 1 THEN 'one' ELSE 'other' END");
7479        assert_roundtrip("SELECT CASE x WHEN 1 THEN 'a' WHEN 2 THEN 'b' END");
7480        assert_roundtrip("SELECT CAST(a AS TEXT)");
7481        assert_roundtrip("SELECT EXISTS (SELECT 1)");
7482        assert_roundtrip("SELECT (SELECT 1)");
7483        assert_roundtrip("SELECT a COLLATE NOCASE");
7484    }
7485
7486    #[test]
7487    fn test_roundtrip_literals() {
7488        assert_roundtrip("SELECT NULL");
7489        assert_roundtrip("SELECT TRUE");
7490        assert_roundtrip("SELECT FALSE");
7491        assert_roundtrip("SELECT 42");
7492        assert_roundtrip("SELECT 3.14");
7493        assert_roundtrip("SELECT 'hello'");
7494        assert_roundtrip("SELECT X'DEADBEEF'");
7495        assert_roundtrip("SELECT CURRENT_TIME");
7496        assert_roundtrip("SELECT CURRENT_DATE");
7497        assert_roundtrip("SELECT CURRENT_TIMESTAMP");
7498    }
7499
7500    #[test]
7501    fn test_roundtrip_placeholders() {
7502        assert_roundtrip("SELECT ?");
7503        assert_roundtrip("SELECT ?1");
7504        assert_roundtrip("SELECT :name");
7505        assert_roundtrip("SELECT @name");
7506        assert_roundtrip("SELECT $name");
7507    }
7508
7509    #[test]
7510    fn test_roundtrip_json_arrows() {
7511        assert_roundtrip("SELECT a -> 'key'");
7512        assert_roundtrip("SELECT a ->> 'key'");
7513    }
7514
7515    #[test]
7516    fn test_roundtrip_function_calls() {
7517        assert_roundtrip("SELECT count(*)");
7518        assert_roundtrip("SELECT count(DISTINCT a)");
7519        assert_roundtrip("SELECT sum(x) FILTER (WHERE x > 0)");
7520    }
7521
7522    #[test]
7523    fn test_roundtrip_isnull_notnull() {
7524        assert_roundtrip("SELECT a ISNULL");
7525        assert_roundtrip("SELECT a IS NOT NULL");
7526    }
7527
7528    #[test]
7529    fn test_roundtrip_create_view() {
7530        assert_roundtrip("CREATE VIEW v AS SELECT * FROM t");
7531        assert_roundtrip("CREATE VIEW IF NOT EXISTS v (a, b) AS SELECT 1, 2");
7532    }
7533
7534    #[test]
7535    fn test_roundtrip_create_trigger() {
7536        assert_roundtrip(
7537            "CREATE TRIGGER tr BEFORE DELETE ON t FOR EACH ROW BEGIN DELETE FROM log WHERE id = OLD.id; END",
7538        );
7539    }
7540
7541    #[test]
7542    fn test_roundtrip_attach_detach() {
7543        assert_roundtrip("ATTACH 'file.db' AS db2");
7544        assert_roundtrip("DETACH db2");
7545    }
7546
7547    #[test]
7548    fn test_roundtrip_vacuum() {
7549        assert_roundtrip("VACUUM");
7550    }
7551
7552    #[test]
7553    fn test_roundtrip_analyze_reindex() {
7554        assert_roundtrip("ANALYZE");
7555        assert_roundtrip("ANALYZE t");
7556        assert_roundtrip("REINDEX");
7557        assert_roundtrip("REINDEX t");
7558    }
7559
7560    #[test]
7561    fn test_roundtrip_cte_materialized() {
7562        assert_roundtrip("WITH cte AS MATERIALIZED (SELECT 1) SELECT * FROM cte");
7563        assert_roundtrip("WITH cte AS NOT MATERIALIZED (SELECT 1) SELECT * FROM cte");
7564    }
7565
7566    // -----------------------------------------------------------------------
7567    // Proptest: round-trip property test (bd-2kvo acceptance criterion #12)
7568    // -----------------------------------------------------------------------
7569
7570    mod proptest_roundtrip {
7571        use super::*;
7572        use proptest::prelude::*;
7573
7574        /// Returns `true` if the string is a SQL keyword.
7575        fn is_keyword(s: &str) -> bool {
7576            TokenKind::lookup_keyword(s).is_some()
7577        }
7578
7579        /// Generate a random identifier (simple alphanumeric, not a SQL keyword).
7580        fn arb_ident() -> BoxedStrategy<String> {
7581            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
7582                .expect("valid regex")
7583                .prop_filter("must not be keyword", |s| !is_keyword(s))
7584                .boxed()
7585        }
7586
7587        /// Generate a random literal value.
7588        fn arb_literal() -> BoxedStrategy<String> {
7589            prop_oneof![
7590                any::<i32>().prop_map(|n| n.to_string()),
7591                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
7592                arb_ident().prop_map(|s| format!("'{s}'")),
7593                Just("NULL".to_string()),
7594                Just("TRUE".to_string()),
7595                Just("FALSE".to_string()),
7596            ]
7597            .boxed()
7598        }
7599
7600        /// Generate a random expression of bounded depth.
7601        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
7602            if depth == 0 {
7603                prop_oneof![
7604                    arb_literal(),
7605                    arb_ident(),
7606                    (arb_ident(), arb_ident()).prop_map(|(t, c)| format!("{t}.{c}")),
7607                ]
7608                .boxed()
7609            } else {
7610                let leaf = arb_expr(0);
7611                prop_oneof![
7612                    4 => leaf,
7613                    // Binary ops (always parenthesized by display)
7614                    2 => (arb_expr(depth - 1), prop_oneof![
7615                        Just("+"), Just("-"), Just("*"), Just("/"),
7616                        Just("="), Just("!="), Just("<"), Just("<="),
7617                        Just(">"), Just(">="), Just("AND"), Just("OR"),
7618                        Just("||"),
7619                    ], arb_expr(depth - 1))
7620                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
7621                    // Unary ops
7622                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
7623                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
7624                    // IS NULL / IS NOT NULL
7625                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NULL")),
7626                    1 => arb_expr(depth - 1).prop_map(|e| format!("{e} IS NOT NULL")),
7627                    // BETWEEN
7628                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
7629                        .prop_map(|(e, lo, hi)| format!("{e} BETWEEN {lo} AND {hi}")),
7630                    // IN list
7631                    1 => (arb_expr(depth - 1), proptest::collection::vec(arb_expr(0), 1..4))
7632                        .prop_map(|(e, items)| format!("{e} IN ({})", items.join(", "))),
7633                    // LIKE
7634                    1 => (arb_expr(depth - 1), arb_ident())
7635                        .prop_map(|(e, p)| format!("{e} LIKE '{p}'")),
7636                    // CAST
7637                    1 => arb_expr(depth - 1).prop_map(|e| format!("CAST({e} AS TEXT)")),
7638                    // CASE
7639                    1 => (arb_expr(depth - 1), arb_expr(0), arb_expr(0))
7640                        .prop_map(|(c, t, el)| format!("CASE WHEN {c} THEN {t} ELSE {el} END")),
7641                    // Function call
7642                    1 => (arb_ident(), proptest::collection::vec(arb_expr(0), 0..3))
7643                        .prop_map(|(name, args)| format!("{name}({})", args.join(", "))),
7644                    // Subquery
7645                    1 => arb_expr(0).prop_map(|e| format!("(SELECT {e})")),
7646                ]
7647                .boxed()
7648            }
7649        }
7650
7651        /// Generate a random SELECT statement.
7652        fn arb_select() -> BoxedStrategy<String> {
7653            use std::fmt::Write as _;
7654
7655            let cols =
7656                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
7657            let table = arb_ident();
7658            let where_clause = prop::option::of(arb_expr(1));
7659            let order_by = prop::option::of(arb_ident());
7660            let limit = prop::option::of(1u32..100);
7661
7662            (cols, table, where_clause, order_by, limit)
7663                .prop_map(|(cols, tbl, wh, ord, lim)| {
7664                    let mut sql = format!("SELECT {cols} FROM {tbl}");
7665                    if let Some(w) = wh {
7666                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
7667                    }
7668                    if let Some(o) = ord {
7669                        write!(sql, " ORDER BY {o}").expect("writing to String should not fail");
7670                    }
7671                    if let Some(l) = lim {
7672                        write!(sql, " LIMIT {l}").expect("writing to String should not fail");
7673                    }
7674                    sql
7675                })
7676                .boxed()
7677        }
7678
7679        /// Generate a random INSERT statement.
7680        fn arb_insert() -> BoxedStrategy<String> {
7681            let ncols = 1usize..4;
7682            ncols
7683                .prop_flat_map(|n| {
7684                    let tbl = arb_ident();
7685                    let cols = proptest::collection::vec(arb_ident(), n..=n);
7686                    let vals = proptest::collection::vec(arb_literal(), n..=n);
7687                    (tbl, cols, vals).prop_map(|(t, cs, vs): (String, Vec<String>, Vec<String>)| {
7688                        format!(
7689                            "INSERT INTO {t} ({}) VALUES ({})",
7690                            cs.join(", "),
7691                            vs.join(", ")
7692                        )
7693                    })
7694                })
7695                .boxed()
7696        }
7697
7698        /// Generate a random statement.
7699        fn arb_statement() -> BoxedStrategy<String> {
7700            prop_oneof![
7701                6 => arb_select(),
7702                3 => arb_insert(),
7703                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
7704                1 => (arb_ident(), arb_expr(1))
7705                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
7706                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
7707                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
7708            ]
7709            .boxed()
7710        }
7711
7712        /// Try to parse SQL into a single statement; returns `None` if unparseable.
7713        fn try_parse_one(sql: &str) -> Option<Statement> {
7714            let mut p = Parser::from_sql(sql);
7715            let (stmts, errs) = p.parse_all();
7716            if errs.is_empty() && stmts.len() == 1 {
7717                Some(stmts.into_iter().next().unwrap())
7718            } else {
7719                None
7720            }
7721        }
7722
7723        proptest::proptest! {
7724            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
7725
7726            #[test]
7727            fn test_parser_roundtrip_proptest(sql in arb_statement()) {
7728                // Phase 1: parse the generated SQL.
7729                let Some(ast1) = try_parse_one(&sql) else {
7730                    return Ok(()); // skip unparseable inputs
7731                };
7732
7733                // Phase 2: display the AST back to SQL text.
7734                let rendered1 = ast1.to_string();
7735
7736                // Phase 3: re-parse the rendered SQL.
7737                let Some(ast2) = try_parse_one(&rendered1) else {
7738                    let msg = format!("re-parse failed for rendered SQL: {rendered1:?}");
7739                    prop_assert!(false, "{}", msg);
7740                    unreachable!()
7741                };
7742
7743                // Phase 4: display again and compare (idempotency check).
7744                let rendered2 = ast2.to_string();
7745                let msg = format!(
7746                    "round-trip not idempotent:\n  original: {sql}\n  rendered1: {rendered1}\n  rendered2: {rendered2}"
7747                );
7748                prop_assert_eq!(rendered1, rendered2, "{}", msg);
7749            }
7750        }
7751    }
7752
7753    // -----------------------------------------------------------------------
7754    // Proptest: additional property tests (bd-1lsfu.4)
7755    // -----------------------------------------------------------------------
7756
7757    mod proptest_properties {
7758        use super::*;
7759        use proptest::prelude::*;
7760
7761        /// Reuse the statement generator from the roundtrip module.
7762        fn arb_ident() -> BoxedStrategy<String> {
7763            prop::string::string_regex("[a-z][a-z0-9]{0,5}")
7764                .expect("valid regex")
7765                .prop_filter("must not be keyword", |s| {
7766                    TokenKind::lookup_keyword(s).is_none()
7767                })
7768                .boxed()
7769        }
7770
7771        fn arb_literal() -> BoxedStrategy<String> {
7772            prop_oneof![
7773                any::<i32>().prop_map(|n| n.to_string()),
7774                (1i32..1000).prop_map(|n| format!("{n}.{}", n % 100)),
7775                arb_ident().prop_map(|s| format!("'{s}'")),
7776                Just("NULL".to_string()),
7777                Just("TRUE".to_string()),
7778                Just("FALSE".to_string()),
7779            ]
7780            .boxed()
7781        }
7782
7783        fn arb_expr(depth: u32) -> BoxedStrategy<String> {
7784            if depth == 0 {
7785                prop_oneof![arb_literal(), arb_ident(),].boxed()
7786            } else {
7787                let leaf = arb_expr(0);
7788                prop_oneof![
7789                    4 => leaf,
7790                    2 => (arb_expr(depth - 1), prop_oneof![
7791                        Just("+"), Just("-"), Just("*"), Just("/"),
7792                        Just("="), Just("!="), Just("<"), Just("<="),
7793                        Just(">"), Just(">="), Just("AND"), Just("OR"),
7794                    ], arb_expr(depth - 1))
7795                        .prop_map(|(l, op, r)| format!("({l} {op} {r})")),
7796                    1 => arb_expr(depth - 1).prop_map(|e| format!("(-{e})")),
7797                    1 => arb_expr(depth - 1).prop_map(|e| format!("(NOT {e})")),
7798                ]
7799                .boxed()
7800            }
7801        }
7802
7803        fn arb_select() -> BoxedStrategy<String> {
7804            use std::fmt::Write as _;
7805            let cols =
7806                proptest::collection::vec(arb_expr(1), 1..4).prop_map(|cols| cols.join(", "));
7807            let table = arb_ident();
7808            let where_clause = prop::option::of(arb_expr(1));
7809            (cols, table, where_clause)
7810                .prop_map(|(cols, tbl, wh)| {
7811                    let mut sql = format!("SELECT {cols} FROM {tbl}");
7812                    if let Some(w) = wh {
7813                        write!(sql, " WHERE {w}").expect("writing to String should not fail");
7814                    }
7815                    sql
7816                })
7817                .boxed()
7818        }
7819
7820        fn arb_statement() -> BoxedStrategy<String> {
7821            prop_oneof![
7822                6 => arb_select(),
7823                3 => {
7824                    let ncols = 1usize..4;
7825                    ncols
7826                        .prop_flat_map(|n| {
7827                            let tbl = arb_ident();
7828                            let cols = proptest::collection::vec(arb_ident(), n..=n);
7829                            let vals = proptest::collection::vec(arb_literal(), n..=n);
7830                            (tbl, cols, vals).prop_map(
7831                                |(t, cs, vs): (String, Vec<String>, Vec<String>)| {
7832                                    format!(
7833                                        "INSERT INTO {t} ({}) VALUES ({})",
7834                                        cs.join(", "),
7835                                        vs.join(", ")
7836                                    )
7837                                },
7838                            )
7839                        })
7840                        .boxed()
7841                },
7842                1 => arb_expr(2).prop_map(|e| format!("SELECT {e}")),
7843                1 => (arb_ident(), arb_expr(1))
7844                    .prop_map(|(t, w)| format!("DELETE FROM {t} WHERE {w}")),
7845                1 => (arb_ident(), arb_ident(), arb_literal(), arb_expr(1))
7846                    .prop_map(|(t, c, v, w)| format!("UPDATE {t} SET {c} = {v} WHERE {w}")),
7847            ]
7848            .boxed()
7849        }
7850
7851        // Property 2: Determinism — same input always produces the same AST.
7852        proptest::proptest! {
7853            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(500))]
7854
7855            #[test]
7856            fn test_parser_determinism(sql in arb_statement()) {
7857                let mut p1 = Parser::from_sql(&sql);
7858                let (stmts1, errs1) = p1.parse_all();
7859
7860                let mut p2 = Parser::from_sql(&sql);
7861                let (stmts2, errs2) = p2.parse_all();
7862
7863                // Both parses must produce the same number of statements and errors.
7864                let msg_stmt = format!("different statement counts for: {sql}");
7865                prop_assert_eq!(stmts1.len(), stmts2.len(), "{}", msg_stmt);
7866                let msg_err = format!("different error counts for: {sql}");
7867                prop_assert_eq!(errs1.len(), errs2.len(), "{}", msg_err);
7868
7869                // If successful, the rendered SQL must be identical.
7870                if errs1.is_empty() && !stmts1.is_empty() {
7871                    for (s1, s2) in stmts1.iter().zip(stmts2.iter()) {
7872                        let r1 = s1.to_string();
7873                        let r2 = s2.to_string();
7874                        let msg_det = format!("non-deterministic parse output for: {sql}");
7875                        prop_assert_eq!(r1, r2, "{}", msg_det);
7876                    }
7877                }
7878            }
7879        }
7880
7881        // Property 3: Fuzz safety — random byte strings never panic the parser.
7882        proptest::proptest! {
7883            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(2000))]
7884
7885            #[test]
7886            fn test_parser_fuzz_no_panic(input in prop::collection::vec(any::<u8>(), 0..256)) {
7887                let sql = String::from_utf8_lossy(&input);
7888                // Must not panic — errors are fine, panics are not.
7889                let mut p = Parser::from_sql(&sql);
7890                let _ = p.parse_all();
7891            }
7892        }
7893
7894        // Property 3b: Fuzz safety with near-valid SQL (more likely to trigger edge cases).
7895        proptest::proptest! {
7896            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(1000))]
7897
7898            #[test]
7899            fn test_parser_fuzz_near_valid(
7900                prefix in prop_oneof![
7901                    Just("SELECT "),
7902                    Just("INSERT INTO "),
7903                    Just("DELETE FROM "),
7904                    Just("UPDATE "),
7905                    Just("CREATE TABLE "),
7906                    Just("DROP TABLE "),
7907                    Just("BEGIN "),
7908                    Just("PRAGMA "),
7909                ],
7910                suffix in prop::string::string_regex("[a-zA-Z0-9_ ,.*=<>!()'\";+\\-/]{0,100}")
7911                    .expect("valid regex")
7912            ) {
7913                let sql = format!("{prefix}{suffix}");
7914                let mut p = Parser::from_sql(&sql);
7915                let _ = p.parse_all();
7916            }
7917        }
7918
7919        // Property 4: Unicode identifiers parse correctly.
7920        proptest::proptest! {
7921            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
7922
7923            #[test]
7924            fn test_parser_unicode_identifiers(
7925                name in prop::string::string_regex("[\\p{L}][\\p{L}\\p{N}_]{0,10}")
7926                    .expect("valid regex")
7927                    .prop_filter("must not be keyword", |s| {
7928                        TokenKind::lookup_keyword(s).is_none()
7929                    })
7930            ) {
7931                // Double-quoted identifiers with Unicode should parse.
7932                let sql = format!("SELECT \"{name}\" FROM \"{name}\"");
7933                let mut p = Parser::from_sql(&sql);
7934                let (stmts, errs) = p.parse_all();
7935                prop_assert!(
7936                    errs.is_empty(),
7937                    "Unicode identifier should parse: {sql}, errors: {errs:?}"
7938                );
7939                prop_assert_eq!(stmts.len(), 1);
7940            }
7941        }
7942
7943        // Property 5: Rejection — various forms of invalid SQL are rejected.
7944        proptest::proptest! {
7945            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(300))]
7946
7947            #[test]
7948            fn test_parser_rejects_incomplete_statements(
7949                kind in prop_oneof![
7950                    Just("SELECT"),
7951                    Just("SELECT FROM"),
7952                    Just("INSERT INTO"),
7953                    Just("DELETE"),
7954                    Just("UPDATE SET"),
7955                    Just("CREATE"),
7956                    Just("CREATE TABLE"),
7957                    Just("DROP"),
7958                ],
7959                trailing in prop::option::of(
7960                    prop::string::string_regex("[;, ]{0,3}").expect("valid regex")
7961                )
7962            ) {
7963                let sql = match trailing {
7964                    Some(t) => format!("{kind}{t}"),
7965                    None => kind.to_string(),
7966                };
7967                let mut p = Parser::from_sql(&sql);
7968                let (stmts, errs) = p.parse_all();
7969                // At least one of: parse errors, or no valid statements produced.
7970                // The parser should not silently produce a valid-looking AST from
7971                // these fundamentally incomplete inputs.
7972                prop_assert!(
7973                    !errs.is_empty() || stmts.is_empty(),
7974                    "Expected rejection of incomplete SQL: {sql}, got {stmts:?}"
7975                );
7976            }
7977        }
7978
7979        // Property 6: Statement count stability — concatenated statements produce
7980        // the right number of parsed statements.
7981        proptest::proptest! {
7982            #![proptest_config(proptest::prelude::ProptestConfig::with_cases(200))]
7983
7984            #[test]
7985            fn test_parser_multi_statement_count(
7986                stmts in proptest::collection::vec(arb_statement(), 1..4)
7987            ) {
7988                let sql = stmts.join("; ");
7989                let mut p = Parser::from_sql(&sql);
7990                let (parsed, errors) = p.parse_all();
7991                // If no errors, we should get at least as many statements as we joined.
7992                if errors.is_empty() {
7993                    prop_assert!(
7994                        parsed.len() >= stmts.len(),
7995                        "Expected at least {} statements from: {sql}, got {}",
7996                        stmts.len(),
7997                        parsed.len()
7998                    );
7999                }
8000            }
8001        }
8002    }
8003
8004    // ── bd-1702 repro tests ─────────────────────────────────────────────
8005    // Reserved-word column names in CREATE TABLE (quoted and unquoted).
8006
8007    #[test]
8008    fn create_table_quoted_reserved_word_key() {
8009        // Double-quoted "key" should parse as identifier, not KwKey.
8010        parse_ok(r#"CREATE TABLE "meta" ("key" TEXT, "val" TEXT);"#);
8011    }
8012
8013    #[test]
8014    fn create_table_unquoted_key_column() {
8015        // KEY is a non-reserved keyword — should work unquoted.
8016        parse_ok("CREATE TABLE meta (key TEXT, val TEXT);");
8017    }
8018
8019    #[test]
8020    fn create_table_quoted_order_column() {
8021        // ORDER is reserved — must work when double-quoted.
8022        parse_ok(r#"CREATE TABLE t ("order" INTEGER);"#);
8023    }
8024
8025    #[test]
8026    fn create_table_quoted_select_column() {
8027        // SELECT is reserved — must work when double-quoted.
8028        parse_ok(r#"CREATE TABLE t ("select" TEXT);"#);
8029    }
8030
8031    #[test]
8032    fn select_with_reserved_word_column_key() {
8033        // SELECT using "key" as column name — unquoted.
8034        parse_ok("SELECT key FROM meta;");
8035    }
8036
8037    #[test]
8038    fn select_with_reserved_word_column_value() {
8039        // SELECT using "value" — check if it's a keyword.
8040        parse_ok("SELECT value FROM meta;");
8041    }
8042
8043    #[test]
8044    fn select_with_reserved_word_column_order() {
8045        // ORDER is reserved — quoted should work.
8046        parse_ok(r#"SELECT "order" FROM t;"#);
8047    }
8048
8049    #[test]
8050    fn where_clause_with_reserved_word_column() {
8051        // WHERE referencing a reserved-word column.
8052        parse_ok("UPDATE meta SET val = '2.0' WHERE key = 'version';");
8053    }
8054
8055    #[test]
8056    fn update_set_reserved_word_column() {
8057        // SET reserved-word column.
8058        parse_ok(r#"UPDATE meta SET "key" = 'newkey' WHERE "key" = 'oldkey';"#);
8059    }
8060
8061    #[test]
8062    fn delete_where_reserved_word_column() {
8063        parse_ok("DELETE FROM meta WHERE key = 'version';");
8064    }
8065
8066    #[test]
8067    fn persistence_dump_with_reserved_word_columns() {
8068        // Simulates the exact SQL that build_create_table_sql generates
8069        // for a table that was originally created with reserved-word columns.
8070        let sql = concat!(
8071            r#"CREATE TABLE "meta" ("key" TEXT, "value" TEXT);"#,
8072            "\n",
8073            r#"INSERT INTO "meta" VALUES ('version', '1.0');"#,
8074            "\n",
8075            r#"INSERT INTO "meta" VALUES ('author', 'test');"#,
8076        );
8077        let mut p = Parser::from_sql(sql);
8078        let (stmts, errs) = p.parse_all();
8079        assert!(
8080            errs.is_empty(),
8081            "persistence dump with reserved-word columns should parse cleanly: {errs:?}"
8082        );
8083        assert_eq!(stmts.len(), 3);
8084    }
8085
8086    #[test]
8087    fn select_qualified_column_with_alias() {
8088        // Bug: "a.name as from_name" was being parsed with alias=None and
8089        // col_ref.column="name as" instead of alias=Some("from_name").
8090        let stmt = parse_one("SELECT a.name AS from_name FROM users a");
8091        if let Statement::Select(s) = stmt {
8092            if let SelectCore::Select { columns, .. } = &s.body.select {
8093                assert_eq!(columns.len(), 1);
8094                match &columns[0] {
8095                    ResultColumn::Expr { expr, alias } => {
8096                        // Alias should be captured as "from_name".
8097                        assert_eq!(
8098                            alias.as_deref(),
8099                            Some("from_name"),
8100                            "alias should be 'from_name', got {alias:?}"
8101                        );
8102                        // Expression should be a qualified column ref: a.name
8103                        if let Expr::Column(col_ref, _) = expr {
8104                            assert_eq!(col_ref.table.as_deref(), Some("a"));
8105                            assert_eq!(col_ref.column, "name");
8106                        } else {
8107                            panic!("expected Column expression, got {expr:?}");
8108                        }
8109                    }
8110                    other => panic!("expected Expr variant, got {other:?}"),
8111                }
8112            } else {
8113                panic!("expected Select core");
8114            }
8115        } else {
8116            panic!("expected Select statement");
8117        }
8118    }
8119
8120    #[test]
8121    fn select_qualified_column_with_implicit_alias() {
8122        // Test implicit alias syntax (without AS keyword).
8123        let stmt = parse_one("SELECT a.name from_name FROM users a");
8124        if let Statement::Select(s) = stmt {
8125            if let SelectCore::Select { columns, .. } = &s.body.select {
8126                assert_eq!(columns.len(), 1);
8127                match &columns[0] {
8128                    ResultColumn::Expr { expr, alias } => {
8129                        // Alias should be captured as "from_name" even without AS.
8130                        assert_eq!(
8131                            alias.as_deref(),
8132                            Some("from_name"),
8133                            "implicit alias should be 'from_name', got {alias:?}"
8134                        );
8135                        // Expression should be a qualified column ref: a.name
8136                        if let Expr::Column(col_ref, _) = expr {
8137                            assert_eq!(col_ref.table.as_deref(), Some("a"));
8138                            assert_eq!(col_ref.column, "name");
8139                        } else {
8140                            panic!("expected Column expression, got {expr:?}");
8141                        }
8142                    }
8143                    other => panic!("expected Expr variant, got {other:?}"),
8144                }
8145            } else {
8146                panic!("expected Select core");
8147            }
8148        } else {
8149            panic!("expected Select statement");
8150        }
8151    }
8152
8153    #[test]
8154    fn select_implicit_alias_non_reserved_keyword() {
8155        // 'action' is a non-reserved keyword (TokenKind::KwAction).
8156        // It should be accepted as an implicit alias: SELECT 1 action
8157        let stmt = parse_one("SELECT 1 action");
8158        if let Statement::Select(s) = stmt {
8159            if let SelectCore::Select { columns, .. } = &s.body.select {
8160                if let ResultColumn::Expr { alias, .. } = &columns[0] {
8161                    assert_eq!(
8162                        alias.as_deref(),
8163                        Some("action"),
8164                        "implicit alias 'action' (keyword) failed to parse"
8165                    );
8166                } else {
8167                    unreachable!("expected Expr result column");
8168                }
8169            } else {
8170                unreachable!("expected Select core");
8171            }
8172        } else {
8173            unreachable!("expected Select");
8174        }
8175    }
8176}