Skip to main content

fsqlite_ast/
lib.rs

1//! SQL Abstract Syntax Tree node types for FrankenSQLite.
2//!
3//! This module defines the complete AST type hierarchy for the SQLite SQL
4//! dialect. Every SQL statement parsed by `fsqlite-parser` produces a tree of
5//! these nodes. All expression nodes carry a [`Span`] for error reporting.
6//!
7//! Reference: §10.3–10.4 of the FrankenSQLite specification.
8
9mod display;
10pub use display::quote_ident_if_needed;
11pub mod rebase;
12
13use std::fmt;
14use std::sync::Arc;
15
16use fsqlite_types::{SqliteValue, TypeAffinity};
17
18// ---------------------------------------------------------------------------
19// Span — source location tracking
20// ---------------------------------------------------------------------------
21
22/// A byte-offset range into the original SQL source text.
23///
24/// Every AST node that represents user-written syntax carries a `Span` so that
25/// error messages, EXPLAIN output, and debugging tools can point back to the
26/// exact source location.
27#[derive(Clone, Copy, PartialEq, Eq, Hash)]
28pub struct Span {
29    /// Byte offset of the first character (inclusive).
30    pub start: u32,
31    /// Byte offset one past the last character (exclusive).
32    pub end: u32,
33}
34
35impl Span {
36    /// Create a new span from start (inclusive) to end (exclusive) byte offsets.
37    #[must_use]
38    pub const fn new(start: u32, end: u32) -> Self {
39        Self { start, end }
40    }
41
42    /// A zero-length span at position 0, used as a placeholder.
43    pub const ZERO: Self = Self { start: 0, end: 0 };
44
45    /// Merge two spans into one that covers both.
46    #[must_use]
47    pub const fn merge(self, other: Self) -> Self {
48        let start = if self.start < other.start {
49            self.start
50        } else {
51            other.start
52        };
53        let end = if self.end > other.end {
54            self.end
55        } else {
56            other.end
57        };
58        Self { start, end }
59    }
60
61    /// Length in bytes.
62    #[must_use]
63    pub const fn len(self) -> u32 {
64        self.end - self.start
65    }
66
67    /// Whether the span is empty.
68    #[must_use]
69    pub const fn is_empty(self) -> bool {
70        self.start == self.end
71    }
72}
73
74impl fmt::Debug for Span {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(f, "{}..{}", self.start, self.end)
77    }
78}
79
80impl fmt::Display for Span {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{}..{}", self.start, self.end)
83    }
84}
85
86// ---------------------------------------------------------------------------
87// Top-level statement
88// ---------------------------------------------------------------------------
89
90/// A single parsed SQL statement.
91///
92/// This is the top-level AST node. The parser produces one `Statement` per
93/// semicolon-delimited SQL command.
94#[derive(Debug, Clone, PartialEq)]
95pub enum Statement {
96    // DML
97    Select(SelectStatement),
98    Insert(InsertStatement),
99    Update(UpdateStatement),
100    Delete(DeleteStatement),
101
102    // DDL
103    CreateTable(CreateTableStatement),
104    CreateIndex(CreateIndexStatement),
105    CreateView(CreateViewStatement),
106    CreateTrigger(CreateTriggerStatement),
107    CreateVirtualTable(CreateVirtualTableStatement),
108    Drop(DropStatement),
109    AlterTable(AlterTableStatement),
110
111    // Transaction control
112    Begin(BeginStatement),
113    Commit,
114    Rollback(RollbackStatement),
115    Savepoint(String),
116    Release(String),
117
118    // Database operations
119    Attach(AttachStatement),
120    Detach(String),
121    Pragma(PragmaStatement),
122    Vacuum(VacuumStatement),
123
124    // Meta / utility
125    Reindex(Option<QualifiedName>),
126    Analyze(Option<QualifiedName>),
127    Explain { query_plan: bool, stmt: Box<Self> },
128}
129
130// ---------------------------------------------------------------------------
131// Qualified names
132// ---------------------------------------------------------------------------
133
134/// A possibly-schema-qualified name like `main.users` or just `users`.
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct QualifiedName {
137    /// Optional schema name (e.g. `main`, `temp`).
138    pub schema: Option<String>,
139    /// The object name.
140    pub name: String,
141}
142
143impl QualifiedName {
144    /// Create an unqualified name.
145    #[must_use]
146    pub fn bare(name: impl Into<String>) -> Self {
147        Self {
148            schema: None,
149            name: name.into(),
150        }
151    }
152
153    /// Create a schema-qualified name.
154    #[must_use]
155    pub fn qualified(schema: impl Into<String>, name: impl Into<String>) -> Self {
156        Self {
157            schema: Some(schema.into()),
158            name: name.into(),
159        }
160    }
161}
162
163impl fmt::Display for QualifiedName {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        display::write_qualified_name(f, self)
166    }
167}
168
169// ---------------------------------------------------------------------------
170// Type name
171// ---------------------------------------------------------------------------
172
173/// A column type name as written in DDL (e.g. `VARCHAR(255)`, `INTEGER`).
174///
175/// SQLite does not enforce column types strictly; they only determine affinity.
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct TypeName {
178    /// The type name tokens joined (e.g. `"VARCHAR"`).
179    pub name: String,
180    /// Optional first size parameter (e.g. `255` in `VARCHAR(255)`).
181    pub arg1: Option<String>,
182    /// Optional second size parameter (e.g. `10` in `DECIMAL(10,2)`).
183    pub arg2: Option<String>,
184}
185
186// ---------------------------------------------------------------------------
187// Literals
188// ---------------------------------------------------------------------------
189
190/// A literal value in SQL source.
191#[derive(Debug, Clone, PartialEq)]
192pub enum Literal {
193    /// Numeric integer literal.
194    Integer(i64),
195    /// Numeric float literal.
196    Float(f64),
197    /// String literal (single-quoted).
198    String(String),
199    /// Blob literal (`X'...'`).
200    Blob(Vec<u8>),
201    /// The keyword `NULL`.
202    Null,
203    /// The keyword `TRUE` (integer 1).
204    True,
205    /// The keyword `FALSE` (integer 0).
206    False,
207    /// The keyword `CURRENT_TIME`.
208    CurrentTime,
209    /// The keyword `CURRENT_DATE`.
210    CurrentDate,
211    /// The keyword `CURRENT_TIMESTAMP`.
212    CurrentTimestamp,
213}
214
215// ---------------------------------------------------------------------------
216// Column references
217// ---------------------------------------------------------------------------
218
219/// A reference to a column, possibly qualified with a table name and, for a
220/// three-part `schema.table.column` reference, a leading database/schema name.
221#[derive(Debug, Clone, PartialEq, Eq, Hash)]
222pub struct ColumnRef {
223    /// Optional database/schema qualifier — the leading segment of a
224    /// three-part `schema.table.column` reference. `None` for bare and
225    /// table-qualified references.
226    pub schema: Option<Arc<str>>,
227    /// Optional table (or alias) qualifier.
228    pub table: Option<Arc<str>>,
229    /// Column name.
230    pub column: Arc<str>,
231}
232
233impl ColumnRef {
234    /// Create an unqualified column reference (`column`).
235    #[must_use]
236    pub fn bare(column: impl Into<Arc<str>>) -> Self {
237        Self {
238            schema: None,
239            table: None,
240            column: column.into(),
241        }
242    }
243
244    /// Create a table-qualified column reference (`table.column`).
245    #[must_use]
246    pub fn qualified(table: impl Into<Arc<str>>, column: impl Into<Arc<str>>) -> Self {
247        Self {
248            schema: None,
249            table: Some(table.into()),
250            column: column.into(),
251        }
252    }
253
254    /// Create a schema-qualified column reference (`schema.table.column`).
255    ///
256    /// The leading segment names the database/schema (e.g. `main`, `temp`, or
257    /// an attached database alias), matching SQLite's three-part column syntax.
258    #[must_use]
259    pub fn schema_qualified(
260        schema: impl Into<Arc<str>>,
261        table: impl Into<Arc<str>>,
262        column: impl Into<Arc<str>>,
263    ) -> Self {
264        Self {
265            schema: Some(schema.into()),
266            table: Some(table.into()),
267            column: column.into(),
268        }
269    }
270}
271
272// ---------------------------------------------------------------------------
273// Operators
274// ---------------------------------------------------------------------------
275
276/// Binary operators.
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
278pub enum BinaryOp {
279    // Arithmetic
280    Add,
281    Subtract,
282    Multiply,
283    Divide,
284    Modulo,
285
286    // String
287    Concat,
288
289    // Comparison
290    Eq,
291    Ne,
292    Lt,
293    Le,
294    Gt,
295    Ge,
296    Is,
297    IsNot,
298
299    // Logical
300    And,
301    Or,
302
303    // Bitwise
304    BitAnd,
305    BitOr,
306    ShiftLeft,
307    ShiftRight,
308}
309
310impl fmt::Display for BinaryOp {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        f.write_str(match self {
313            Self::Add => "+",
314            Self::Subtract => "-",
315            Self::Multiply => "*",
316            Self::Divide => "/",
317            Self::Modulo => "%",
318            Self::Concat => "||",
319            Self::Eq => "=",
320            Self::Ne => "!=",
321            Self::Lt => "<",
322            Self::Le => "<=",
323            Self::Gt => ">",
324            Self::Ge => ">=",
325            Self::Is => "IS",
326            Self::IsNot => "IS NOT",
327            Self::And => "AND",
328            Self::Or => "OR",
329            Self::BitAnd => "&",
330            Self::BitOr => "|",
331            Self::ShiftLeft => "<<",
332            Self::ShiftRight => ">>",
333        })
334    }
335}
336
337/// Unary operators.
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
339pub enum UnaryOp {
340    /// Unary minus (`-expr`).
341    Negate,
342    /// Unary plus (`+expr`).
343    Plus,
344    /// Bitwise NOT (`~expr`).
345    BitNot,
346    /// Logical NOT (`NOT expr`).
347    Not,
348}
349
350impl fmt::Display for UnaryOp {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        f.write_str(match self {
353            Self::Negate => "-",
354            Self::Plus => "+",
355            Self::BitNot => "~",
356            Self::Not => "NOT",
357        })
358    }
359}
360
361/// LIKE operator variants.
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
363pub enum LikeOp {
364    Like,
365    Glob,
366    Match,
367    Regexp,
368}
369
370/// JSON access arrow types.
371#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
372pub enum JsonArrow {
373    /// `->` extracts as JSON.
374    Arrow,
375    /// `->>` extracts as text.
376    DoubleArrow,
377}
378
379impl JsonArrow {
380    /// Return the SQL function name that implements this JSON operator.
381    #[must_use]
382    pub const fn sql_function_name(self) -> &'static str {
383        match self {
384            Self::Arrow => "->",
385            Self::DoubleArrow => "->>",
386        }
387    }
388}
389
390// ---------------------------------------------------------------------------
391// Expressions (§10.3 Expr enum)
392// ---------------------------------------------------------------------------
393
394/// Collation metadata retained by a value bound from an outer query.
395///
396/// This is deliberately tri-state. A physical column with no named collation
397/// still has the known [`Self::Binary`] collation and therefore wins SQLite's
398/// left-to-right declared-collation precedence. [`Self::Unspecified`] is
399/// metadata-neutral and is reserved for synthesized values, such as a `FULL
400/// JOIN ... USING` coalesced output, that must not donate a collation.
401#[derive(Debug, Clone)]
402pub enum BoundCollation {
403    /// The synthesized value does not define a comparison collation.
404    Unspecified,
405    /// The source is known to use SQLite's default `BINARY` collation.
406    Binary,
407    /// The source uses the named collation.
408    Named(String),
409}
410
411impl BoundCollation {
412    /// Convert schema-level declared metadata into a bound-value collation.
413    ///
414    /// A missing schema declaration means the physical column is known to use
415    /// `BINARY`; it does not mean that the bound expression is metadata-neutral.
416    #[must_use]
417    pub fn from_declared_name(name: Option<String>) -> Self {
418        match name {
419            Some(name) if name.eq_ignore_ascii_case("BINARY") => Self::Binary,
420            Some(name) => Self::Named(name),
421            None => Self::Binary,
422        }
423    }
424
425    /// Return the collation name donated to comparison resolution.
426    ///
427    /// [`Self::Unspecified`] returns `None`; both other states return a known
428    /// collation, including `BINARY`.
429    #[must_use]
430    pub fn as_name(&self) -> Option<&str> {
431        match self {
432            Self::Unspecified => None,
433            Self::Binary => Some("BINARY"),
434            Self::Named(name) => Some(name),
435        }
436    }
437}
438
439impl From<Option<String>> for BoundCollation {
440    fn from(name: Option<String>) -> Self {
441        Self::from_declared_name(name)
442    }
443}
444
445impl PartialEq for BoundCollation {
446    fn eq(&self, other: &Self) -> bool {
447        match (self.as_name(), other.as_name()) {
448            (None, None) => true,
449            (Some(left), Some(right)) => left.eq_ignore_ascii_case(right),
450            (None, Some(_)) | (Some(_), None) => false,
451        }
452    }
453}
454
455impl Eq for BoundCollation {}
456
457/// An expression node in the AST.
458///
459/// Every variant carries a [`Span`] for source-location tracking. The Expr
460/// enum covers all expression forms in the SQLite SQL dialect.
461#[derive(Debug, Clone)]
462pub enum Expr {
463    /// A literal constant.
464    Literal(Literal, Span),
465
466    /// An outer-query value bound into a correlated expression.
467    ///
468    /// This is an internal execution node rather than parser output. It keeps
469    /// the source expression's comparison metadata after the runtime value has
470    /// been substituted. `affinity: None` denotes no declared affinity.
471    #[doc(hidden)]
472    BoundOuterValue {
473        /// Runtime value copied from the outer row.
474        value: SqliteValue,
475        /// Collation metadata inherited from the source expression.
476        collation: BoundCollation,
477        /// Declared source affinity, when one exists.
478        affinity: Option<TypeAffinity>,
479        /// Source span of the outer-column reference being replaced.
480        span: Span,
481    },
482
483    /// A column reference (possibly table-qualified).
484    Column(ColumnRef, Span),
485
486    /// A binary operation: `left op right`.
487    BinaryOp {
488        left: Box<Self>,
489        op: BinaryOp,
490        right: Box<Self>,
491        span: Span,
492    },
493
494    /// A unary operation: `op expr`.
495    UnaryOp {
496        op: UnaryOp,
497        expr: Box<Self>,
498        span: Span,
499    },
500
501    /// `expr [NOT] BETWEEN low AND high`.
502    Between {
503        expr: Box<Self>,
504        low: Box<Self>,
505        high: Box<Self>,
506        not: bool,
507        span: Span,
508    },
509
510    /// `expr [NOT] IN (...)`.
511    In {
512        expr: Box<Self>,
513        set: InSet,
514        not: bool,
515        span: Span,
516    },
517
518    /// `expr [NOT] LIKE/GLOB/MATCH/REGEXP pattern [ESCAPE escape]`.
519    Like {
520        expr: Box<Self>,
521        pattern: Box<Self>,
522        escape: Option<Box<Self>>,
523        op: LikeOp,
524        not: bool,
525        span: Span,
526    },
527
528    /// `CASE [operand] WHEN ... THEN ... [ELSE ...] END`.
529    Case {
530        operand: Option<Box<Self>>,
531        whens: Vec<(Self, Self)>,
532        else_expr: Option<Box<Self>>,
533        span: Span,
534    },
535
536    /// `CAST(expr AS type_name)`.
537    Cast {
538        expr: Box<Self>,
539        type_name: TypeName,
540        span: Span,
541    },
542
543    /// `[NOT] EXISTS (subquery)`.
544    Exists {
545        subquery: Box<SelectStatement>,
546        not: bool,
547        span: Span,
548    },
549
550    /// A scalar subquery: `(SELECT ...)`.
551    Subquery(Box<SelectStatement>, Span),
552
553    /// A function call, optionally with DISTINCT, FILTER, and window spec.
554    FunctionCall {
555        name: String,
556        args: FunctionArgs,
557        distinct: bool,
558        /// In-aggregate ORDER BY (SQLite 3.44+), e.g. `group_concat(x ORDER BY y)`.
559        order_by: Vec<OrderingTerm>,
560        filter: Option<Box<Self>>,
561        over: Option<WindowSpec>,
562        span: Span,
563    },
564
565    /// `expr COLLATE collation_name`.
566    Collate {
567        expr: Box<Self>,
568        collation: String,
569        span: Span,
570    },
571
572    /// `expr IS [NOT] NULL` / `expr ISNULL` / `expr NOTNULL`.
573    IsNull {
574        expr: Box<Self>,
575        not: bool,
576        span: Span,
577    },
578
579    /// `RAISE(action, message)` — used inside trigger bodies.
580    Raise {
581        action: RaiseAction,
582        message: Option<String>,
583        span: Span,
584    },
585
586    /// `expr -> path` or `expr ->> path` (JSON access).
587    JsonAccess {
588        expr: Box<Self>,
589        path: Box<Self>,
590        arrow: JsonArrow,
591        span: Span,
592    },
593
594    /// A row value `(a, b, c)` for multi-column comparisons (SQLite 3.15+).
595    RowValue(Vec<Self>, Span),
596
597    /// A bind parameter (`?`, `?NNN`, `:name`, `@name`, `$name`).
598    Placeholder(PlaceholderType, Span),
599}
600
601impl Expr {
602    /// Return the span of this expression node.
603    #[must_use]
604    pub const fn span(&self) -> Span {
605        match self {
606            Self::Literal(_, s)
607            | Self::Column(_, s)
608            | Self::Subquery(_, s)
609            | Self::RowValue(_, s)
610            | Self::Placeholder(_, s) => *s,
611            Self::BoundOuterValue { span, .. }
612            | Self::BinaryOp { span, .. }
613            | Self::UnaryOp { span, .. }
614            | Self::Between { span, .. }
615            | Self::In { span, .. }
616            | Self::Like { span, .. }
617            | Self::Case { span, .. }
618            | Self::Cast { span, .. }
619            | Self::Exists { span, .. }
620            | Self::FunctionCall { span, .. }
621            | Self::Collate { span, .. }
622            | Self::IsNull { span, .. }
623            | Self::Raise { span, .. }
624            | Self::JsonAccess { span, .. } => *span,
625        }
626    }
627}
628
629/// Span-ignoring, SQL-semantics-aware structural equality for expressions.
630///
631/// Two expressions are equal if they have the same structure and all semantic
632/// fields match, regardless of their source-location [`Span`] values.  This is
633/// essential for partial-index predicate matching and expression-index lookup,
634/// where the same logical expression is parsed from different source texts.
635///
636/// SQL identifiers and function names are case-insensitive, so comparisons of
637/// `FunctionCall::name` and `Collate::collation` use
638/// `eq_ignore_ascii_case` rather than byte-exact equality.  Column and table
639/// names in [`ColumnRef`] are compared via the derived `PartialEq` on that
640/// struct, which is currently case-sensitive — this is a known limitation but
641/// a safe default because mixed-case column references are rare in practice
642/// and would only cause a missed optimisation (silent scan fallback), never a
643/// wrong result.
644impl PartialEq for Expr {
645    #[allow(clippy::too_many_lines)]
646    fn eq(&self, other: &Self) -> bool {
647        match (self, other) {
648            (Self::Literal(a, _), Self::Literal(b, _)) => a == b,
649            (
650                Self::BoundOuterValue {
651                    value: v1,
652                    collation: c1,
653                    affinity: a1,
654                    ..
655                },
656                Self::BoundOuterValue {
657                    value: v2,
658                    collation: c2,
659                    affinity: a2,
660                    ..
661                },
662            ) => v1.storage_class() == v2.storage_class() && v1 == v2 && c1 == c2 && a1 == a2,
663            (Self::Column(a, _), Self::Column(b, _)) => a == b,
664            (
665                Self::BinaryOp {
666                    left: l1,
667                    op: o1,
668                    right: r1,
669                    ..
670                },
671                Self::BinaryOp {
672                    left: l2,
673                    op: o2,
674                    right: r2,
675                    ..
676                },
677            ) => o1 == o2 && l1 == l2 && r1 == r2,
678            (
679                Self::UnaryOp {
680                    op: o1, expr: e1, ..
681                },
682                Self::UnaryOp {
683                    op: o2, expr: e2, ..
684                },
685            ) => o1 == o2 && e1 == e2,
686            (
687                Self::Between {
688                    expr: e1,
689                    low: l1,
690                    high: h1,
691                    not: n1,
692                    ..
693                },
694                Self::Between {
695                    expr: e2,
696                    low: l2,
697                    high: h2,
698                    not: n2,
699                    ..
700                },
701            ) => n1 == n2 && e1 == e2 && l1 == l2 && h1 == h2,
702            (
703                Self::In {
704                    expr: e1,
705                    set: s1,
706                    not: n1,
707                    ..
708                },
709                Self::In {
710                    expr: e2,
711                    set: s2,
712                    not: n2,
713                    ..
714                },
715            ) => n1 == n2 && e1 == e2 && s1 == s2,
716            (
717                Self::Like {
718                    expr: e1,
719                    pattern: p1,
720                    escape: esc1,
721                    op: o1,
722                    not: n1,
723                    ..
724                },
725                Self::Like {
726                    expr: e2,
727                    pattern: p2,
728                    escape: esc2,
729                    op: o2,
730                    not: n2,
731                    ..
732                },
733            ) => o1 == o2 && n1 == n2 && e1 == e2 && p1 == p2 && esc1 == esc2,
734            (
735                Self::Case {
736                    operand: o1,
737                    whens: w1,
738                    else_expr: e1,
739                    ..
740                },
741                Self::Case {
742                    operand: o2,
743                    whens: w2,
744                    else_expr: e2,
745                    ..
746                },
747            ) => o1 == o2 && w1 == w2 && e1 == e2,
748            (
749                Self::Cast {
750                    expr: e1,
751                    type_name: t1,
752                    ..
753                },
754                Self::Cast {
755                    expr: e2,
756                    type_name: t2,
757                    ..
758                },
759            ) => e1 == e2 && t1 == t2,
760            (
761                Self::Exists {
762                    subquery: s1,
763                    not: n1,
764                    ..
765                },
766                Self::Exists {
767                    subquery: s2,
768                    not: n2,
769                    ..
770                },
771            ) => n1 == n2 && s1 == s2,
772            (Self::Subquery(s1, _), Self::Subquery(s2, _)) => s1 == s2,
773            (
774                Self::FunctionCall {
775                    name: n1,
776                    args: a1,
777                    distinct: d1,
778                    order_by: ob1,
779                    filter: f1,
780                    over: ov1,
781                    ..
782                },
783                Self::FunctionCall {
784                    name: n2,
785                    args: a2,
786                    distinct: d2,
787                    order_by: ob2,
788                    filter: f2,
789                    over: ov2,
790                    ..
791                },
792            ) => {
793                n1.eq_ignore_ascii_case(n2)
794                    && a1 == a2
795                    && d1 == d2
796                    && ob1 == ob2
797                    && f1 == f2
798                    && ov1 == ov2
799            }
800            (
801                Self::Collate {
802                    expr: e1,
803                    collation: c1,
804                    ..
805                },
806                Self::Collate {
807                    expr: e2,
808                    collation: c2,
809                    ..
810                },
811            ) => e1 == e2 && c1.eq_ignore_ascii_case(c2),
812            (
813                Self::IsNull {
814                    expr: e1, not: n1, ..
815                },
816                Self::IsNull {
817                    expr: e2, not: n2, ..
818                },
819            ) => n1 == n2 && e1 == e2,
820            (
821                Self::Raise {
822                    action: a1,
823                    message: m1,
824                    ..
825                },
826                Self::Raise {
827                    action: a2,
828                    message: m2,
829                    ..
830                },
831            ) => a1 == a2 && m1 == m2,
832            (
833                Self::JsonAccess {
834                    expr: e1,
835                    path: p1,
836                    arrow: a1,
837                    ..
838                },
839                Self::JsonAccess {
840                    expr: e2,
841                    path: p2,
842                    arrow: a2,
843                    ..
844                },
845            ) => a1 == a2 && e1 == e2 && p1 == p2,
846            (Self::RowValue(v1, _), Self::RowValue(v2, _)) => v1 == v2,
847            (Self::Placeholder(p1, _), Self::Placeholder(p2, _)) => p1 == p2,
848            _ => false,
849        }
850    }
851}
852
853/// The set of values for an IN expression.
854#[derive(Debug, Clone, PartialEq)]
855pub enum InSet {
856    /// `IN (expr, expr, ...)`
857    List(Vec<Expr>),
858    /// `IN (SELECT ...)`
859    Subquery(Box<SelectStatement>),
860    /// `IN table_name` — shorthand for `IN (SELECT * FROM table_name)`.
861    Table(QualifiedName),
862}
863
864/// Function argument list.
865#[derive(Debug, Clone, PartialEq)]
866pub enum FunctionArgs {
867    /// `func(*)` — used for `COUNT(*)`.
868    Star,
869    /// `func(arg1, arg2, ...)` or `func()`.
870    List(Vec<Expr>),
871}
872
873/// A borrowed view of SQL function arguments.
874///
875/// [`Self::Pair`] exposes the operands of [`Expr::JsonAccess`] as function
876/// arguments without allocating or changing the parser's owned AST shape.
877#[derive(Debug, Clone, Copy, PartialEq)]
878pub enum SqlFunctionArgsRef<'a> {
879    /// `func(*)` — distinct from an empty argument list even though its arity is zero.
880    Star,
881    /// A contiguous argument list borrowed from [`FunctionArgs::List`].
882    List(&'a [Expr]),
883    /// Two non-contiguous arguments, used by the JSON access operators.
884    Pair(&'a Expr, &'a Expr),
885}
886
887impl<'a> SqlFunctionArgsRef<'a> {
888    /// Return the number of expressions supplied to the function.
889    ///
890    /// `func(*)` has zero expression arguments. Use [`Self::is_star`] when the
891    /// distinction between `Star` and an empty `List` matters.
892    #[must_use]
893    pub const fn len(self) -> usize {
894        match self {
895            Self::Star => 0,
896            Self::List(args) => args.len(),
897            Self::Pair(_, _) => 2,
898        }
899    }
900
901    /// Return whether there are no expression arguments.
902    #[must_use]
903    pub const fn is_empty(self) -> bool {
904        self.len() == 0
905    }
906
907    /// Return the registry-compatible signed arity, saturating at `i32::MAX`.
908    #[must_use]
909    pub fn arity_i32(self) -> i32 {
910        i32::try_from(self.len()).unwrap_or(i32::MAX)
911    }
912
913    /// Return whether these arguments represent `func(*)`.
914    #[must_use]
915    pub const fn is_star(self) -> bool {
916        matches!(self, Self::Star)
917    }
918
919    /// Borrow the argument at `index`, if one exists.
920    #[must_use]
921    pub fn get(self, index: usize) -> Option<&'a Expr> {
922        match self {
923            Self::List(args) => args.get(index),
924            Self::Pair(first, _) if index == 0 => Some(first),
925            Self::Pair(_, second) if index == 1 => Some(second),
926            Self::Star | Self::Pair(_, _) => None,
927        }
928    }
929
930    /// Borrow the first argument, if one exists.
931    #[must_use]
932    pub fn first(self) -> Option<&'a Expr> {
933        self.get(0)
934    }
935
936    /// Iterate over the argument expressions without allocating.
937    pub fn iter(self) -> impl DoubleEndedIterator<Item = &'a Expr> + ExactSizeIterator + Clone {
938        SqlFunctionArgsIter {
939            args: self,
940            indices: 0..self.len(),
941        }
942    }
943
944    /// Return the underlying contiguous list, when this view is a list.
945    ///
946    /// `Star` and `Pair` return `None`; callers that only need traversal should
947    /// use [`Self::iter`].
948    #[must_use]
949    pub const fn as_list(self) -> Option<&'a [Expr]> {
950        match self {
951            Self::List(args) => Some(args),
952            Self::Star | Self::Pair(_, _) => None,
953        }
954    }
955
956    /// Materialize this borrowed view as the parser's owned argument type.
957    #[must_use]
958    pub fn to_owned(self) -> FunctionArgs {
959        match self {
960            Self::Star => FunctionArgs::Star,
961            Self::List(args) => FunctionArgs::List(args.to_vec()),
962            Self::Pair(first, second) => FunctionArgs::List(vec![first.clone(), second.clone()]),
963        }
964    }
965}
966
967#[derive(Debug, Clone)]
968struct SqlFunctionArgsIter<'a> {
969    args: SqlFunctionArgsRef<'a>,
970    indices: std::ops::Range<usize>,
971}
972
973impl<'a> Iterator for SqlFunctionArgsIter<'a> {
974    type Item = &'a Expr;
975
976    fn next(&mut self) -> Option<Self::Item> {
977        self.indices.next().and_then(|index| self.args.get(index))
978    }
979
980    fn size_hint(&self) -> (usize, Option<usize>) {
981        self.indices.size_hint()
982    }
983}
984
985impl DoubleEndedIterator for SqlFunctionArgsIter<'_> {
986    fn next_back(&mut self) -> Option<Self::Item> {
987        self.indices
988            .next_back()
989            .and_then(|index| self.args.get(index))
990    }
991}
992
993impl ExactSizeIterator for SqlFunctionArgsIter<'_> {
994    fn len(&self) -> usize {
995        self.indices.len()
996    }
997}
998
999impl std::iter::FusedIterator for SqlFunctionArgsIter<'_> {}
1000
1001/// A borrowed semantic view of an expression that invokes a SQL function.
1002///
1003/// This view normalizes ordinary [`Expr::FunctionCall`] nodes and JSON access
1004/// operators while retaining the metadata that applies only to explicit
1005/// function-call syntax.
1006#[derive(Debug, Clone, Copy)]
1007pub struct SqlFunctionCallRef<'a> {
1008    /// Function name used for registry lookup.
1009    pub name: &'a str,
1010    /// Borrowed function arguments.
1011    pub args: SqlFunctionArgsRef<'a>,
1012    /// Whether the explicit call uses `DISTINCT`.
1013    pub distinct: bool,
1014    /// In-aggregate `ORDER BY` terms.
1015    pub order_by: &'a [OrderingTerm],
1016    /// Optional aggregate filter expression.
1017    pub filter: Option<&'a Expr>,
1018    /// Optional window specification.
1019    pub over: Option<&'a WindowSpec>,
1020}
1021
1022impl Expr {
1023    /// View this expression as a semantic SQL function call, when applicable.
1024    ///
1025    /// JSON access operators are exposed as two-argument calls named `->` or
1026    /// `->>`. This does not alter their parser AST representation or display.
1027    #[must_use]
1028    pub fn as_sql_function_call(&self) -> Option<SqlFunctionCallRef<'_>> {
1029        match self {
1030            Self::FunctionCall {
1031                name,
1032                args,
1033                distinct,
1034                order_by,
1035                filter,
1036                over,
1037                ..
1038            } => Some(SqlFunctionCallRef {
1039                name,
1040                args: match args {
1041                    FunctionArgs::Star => SqlFunctionArgsRef::Star,
1042                    FunctionArgs::List(args) => SqlFunctionArgsRef::List(args),
1043                },
1044                distinct: *distinct,
1045                order_by,
1046                filter: filter.as_deref(),
1047                over: over.as_ref(),
1048            }),
1049            Self::JsonAccess {
1050                expr, path, arrow, ..
1051            } => Some(SqlFunctionCallRef {
1052                name: arrow.sql_function_name(),
1053                args: SqlFunctionArgsRef::Pair(expr, path),
1054                distinct: false,
1055                order_by: &[],
1056                filter: None,
1057                over: None,
1058            }),
1059            _ => None,
1060        }
1061    }
1062}
1063
1064/// Bind parameter types.
1065#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1066pub enum PlaceholderType {
1067    /// `?` — anonymous positional.
1068    Anonymous,
1069    /// `?NNN` — numbered positional.
1070    Numbered(u32),
1071    /// `:name` — colon-prefixed named parameter.
1072    ColonNamed(String),
1073    /// `@name` — at-prefixed named parameter.
1074    AtNamed(String),
1075    /// `$name` — dollar-prefixed named parameter.
1076    DollarNamed(String),
1077}
1078
1079/// RAISE action for trigger bodies.
1080#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1081pub enum RaiseAction {
1082    Ignore,
1083    Rollback,
1084    Abort,
1085    Fail,
1086}
1087
1088// ---------------------------------------------------------------------------
1089// Window specifications
1090// ---------------------------------------------------------------------------
1091
1092/// How a window specification refers to a named window.
1093///
1094/// SQLite assigns different semantics to a bare `OVER name` reference and a
1095/// leading base name inside `OVER (name ...)` or `WINDOW child AS (name ...)`.
1096/// The former selects the named definition directly, while the latter derives
1097/// a new window and is therefore subject to clause-override restrictions.
1098#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1099pub enum WindowReference {
1100    /// A bare `OVER name` reference.
1101    Direct(String),
1102    /// A base name inside a parenthesized window specification.
1103    Base(String),
1104}
1105
1106impl WindowReference {
1107    /// Return the referenced window name.
1108    #[must_use]
1109    pub fn name(&self) -> &str {
1110        match self {
1111            Self::Direct(name) | Self::Base(name) => name,
1112        }
1113    }
1114}
1115
1116/// Window specification for window functions.
1117#[derive(Debug, Clone, PartialEq)]
1118pub struct WindowSpec {
1119    /// Optional direct or derived reference to a named window.
1120    pub window_ref: Option<WindowReference>,
1121    /// PARTITION BY expressions.
1122    pub partition_by: Vec<Expr>,
1123    /// ORDER BY terms within the window.
1124    pub order_by: Vec<OrderingTerm>,
1125    /// Frame specification.
1126    pub frame: Option<FrameSpec>,
1127}
1128
1129/// Window frame specification.
1130#[derive(Debug, Clone, PartialEq)]
1131pub struct FrameSpec {
1132    /// Frame type: ROWS, RANGE, or GROUPS.
1133    pub frame_type: FrameType,
1134    /// Frame start bound.
1135    pub start: FrameBound,
1136    /// Frame end bound (None means current row for BETWEEN-less syntax).
1137    pub end: Option<FrameBound>,
1138    /// EXCLUDE clause.
1139    pub exclude: Option<FrameExclude>,
1140}
1141
1142/// Window frame type.
1143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1144pub enum FrameType {
1145    Rows,
1146    Range,
1147    Groups,
1148}
1149
1150/// Window frame boundary.
1151#[derive(Debug, Clone, PartialEq)]
1152pub enum FrameBound {
1153    /// `UNBOUNDED PRECEDING`.
1154    UnboundedPreceding,
1155    /// `expr PRECEDING`.
1156    Preceding(Box<Expr>),
1157    /// `CURRENT ROW`.
1158    CurrentRow,
1159    /// `expr FOLLOWING`.
1160    Following(Box<Expr>),
1161    /// `UNBOUNDED FOLLOWING`.
1162    UnboundedFollowing,
1163}
1164
1165/// Window frame EXCLUDE clause.
1166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1167pub enum FrameExclude {
1168    NoOthers,
1169    CurrentRow,
1170    Group,
1171    Ties,
1172}
1173
1174// ---------------------------------------------------------------------------
1175// SELECT statement
1176// ---------------------------------------------------------------------------
1177
1178/// A full SELECT statement, including WITH, ORDER BY, and LIMIT.
1179#[derive(Debug, Clone, PartialEq)]
1180pub struct SelectStatement {
1181    /// Optional common table expressions.
1182    pub with: Option<WithClause>,
1183    /// The SELECT body (core + compound operators).
1184    pub body: SelectBody,
1185    /// ORDER BY clause.
1186    pub order_by: Vec<OrderingTerm>,
1187    /// LIMIT clause.
1188    pub limit: Option<LimitClause>,
1189}
1190
1191/// WITH clause for common table expressions.
1192#[derive(Debug, Clone, PartialEq)]
1193pub struct WithClause {
1194    /// Whether this is `WITH RECURSIVE`.
1195    pub recursive: bool,
1196    /// The CTE definitions.
1197    pub ctes: Vec<Cte>,
1198}
1199
1200/// A single Common Table Expression definition.
1201#[derive(Debug, Clone, PartialEq)]
1202pub struct Cte {
1203    /// CTE name.
1204    pub name: String,
1205    /// Optional column name list.
1206    pub columns: Vec<String>,
1207    /// Materialization hint.
1208    pub materialized: Option<CteMaterialized>,
1209    /// The CTE body query.
1210    pub query: SelectStatement,
1211}
1212
1213/// CTE materialization hint.
1214#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1215pub enum CteMaterialized {
1216    Materialized,
1217    NotMaterialized,
1218}
1219
1220impl WithClause {
1221    /// SQLite treats the `RECURSIVE` keyword as optional: a CTE whose body
1222    /// references its own name as a table source is recursive whether or not
1223    /// `RECURSIVE` was written. Promote `recursive` to `true` in that case so the
1224    /// recursive-CTE machinery engages for `WITH cte AS (... FROM cte ...)`.
1225    pub fn normalize_recursive(&mut self) {
1226        if !self.recursive && self.ctes.iter().any(Cte::is_self_referential) {
1227            self.recursive = true;
1228        }
1229    }
1230}
1231
1232impl Cte {
1233    /// Whether this CTE's body references its own name as a top-level table
1234    /// source — i.e. it is a recursive CTE. Nested subqueries are deliberately
1235    /// not inspected: SQLite requires the recursive self-reference at the top
1236    /// level of the recursive term's FROM clause.
1237    #[must_use]
1238    pub fn is_self_referential(&self) -> bool {
1239        select_references_table(&self.query, &self.name)
1240    }
1241}
1242
1243fn select_references_table(select: &SelectStatement, name: &str) -> bool {
1244    std::iter::once(&select.body.select)
1245        .chain(select.body.compounds.iter().map(|(_, core)| core))
1246        .any(|core| select_core_references_table(core, name))
1247}
1248
1249fn select_core_references_table(core: &SelectCore, name: &str) -> bool {
1250    match core {
1251        SelectCore::Select {
1252            from: Some(from), ..
1253        } => from_references_table(from, name),
1254        SelectCore::Select { from: None, .. } | SelectCore::Values(_) => false,
1255    }
1256}
1257
1258fn from_references_table(from: &FromClause, name: &str) -> bool {
1259    table_source_references_table(&from.source, name)
1260        || from
1261            .joins
1262            .iter()
1263            .any(|join| table_source_references_table(&join.table, name))
1264}
1265
1266fn table_source_references_table(source: &TableOrSubquery, name: &str) -> bool {
1267    match source {
1268        TableOrSubquery::Table {
1269            name: qualified, ..
1270        } => qualified.schema.is_none() && qualified.name.eq_ignore_ascii_case(name),
1271        TableOrSubquery::ParenJoin(inner) => from_references_table(inner, name),
1272        TableOrSubquery::Subquery { .. } | TableOrSubquery::TableFunction { .. } => false,
1273    }
1274}
1275
1276/// The body of a SELECT: one or more SELECT cores connected by compound ops.
1277#[derive(Debug, Clone, PartialEq)]
1278pub struct SelectBody {
1279    /// The first SELECT core.
1280    pub select: SelectCore,
1281    /// Zero or more compound operations (UNION, INTERSECT, EXCEPT).
1282    pub compounds: Vec<(CompoundOp, SelectCore)>,
1283}
1284
1285/// Compound SELECT operators.
1286#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1287pub enum CompoundOp {
1288    Union,
1289    UnionAll,
1290    Intersect,
1291    Except,
1292}
1293
1294/// How SQLite will execute a syntactic `VALUES` clause.
1295///
1296/// SQLite may implement the rows as a coroutine or lower some rows through a
1297/// `UNION ALL` path. That choice determines which row donates comparison
1298/// affinity and collation metadata. Parsing records the first row forced onto
1299/// the `UNION ALL` path; execution later freezes the concrete donor after
1300/// consulting the active function registry.
1301#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1302pub enum ValuesRepresentation {
1303    /// Donor selection has not yet consulted the active function registry.
1304    Deferred {
1305        /// First row at which a previously parsed `WITH` clause forces the
1306        /// `UNION ALL` representation, or `None` when no such row was seen.
1307        force_union_all_from: Option<usize>,
1308    },
1309    /// Donor selection has been resolved for one execution or preparation.
1310    Frozen {
1311        /// Row donating comparison metadata, or `None` for an empty
1312        /// programmatically constructed clause.
1313        donor_row: Option<usize>,
1314    },
1315}
1316
1317/// Rows and representation metadata for a first-class `VALUES` clause.
1318///
1319/// The outer row collection is private so a frozen donor index cannot be
1320/// invalidated by inserting or removing rows. Expressions and columns remain
1321/// mutable through the slice-based accessors because those rewrites preserve
1322/// row identity.
1323#[derive(Debug, Clone, PartialEq)]
1324pub struct ValuesClause {
1325    rows: Vec<Vec<Expr>>,
1326    representation: ValuesRepresentation,
1327}
1328
1329impl ValuesClause {
1330    /// Construct a deferred clause that has not observed a preceding `WITH`.
1331    #[must_use]
1332    pub fn new(rows: Vec<Vec<Expr>>) -> Self {
1333        Self::parsed(rows, None)
1334    }
1335
1336    /// Construct a deferred clause with parser-derived representation state.
1337    ///
1338    /// # Panics
1339    ///
1340    /// Panics if `force_union_all_from` does not identify a row in `rows`.
1341    #[must_use]
1342    pub fn parsed(rows: Vec<Vec<Expr>>, force_union_all_from: Option<usize>) -> Self {
1343        assert!(
1344            force_union_all_from.is_none_or(|index| index < rows.len()),
1345            "forced VALUES row must be present"
1346        );
1347        Self {
1348            rows,
1349            representation: ValuesRepresentation::Deferred {
1350                force_union_all_from,
1351            },
1352        }
1353    }
1354
1355    /// Return the current representation state.
1356    #[must_use]
1357    pub const fn representation(&self) -> ValuesRepresentation {
1358        self.representation
1359    }
1360
1361    /// Return the first parser-forced `UNION ALL` row while still deferred.
1362    #[must_use]
1363    pub const fn force_union_all_from(&self) -> Option<usize> {
1364        match self.representation {
1365            ValuesRepresentation::Deferred {
1366                force_union_all_from,
1367            } => force_union_all_from,
1368            ValuesRepresentation::Frozen { .. } => None,
1369        }
1370    }
1371
1372    /// Return whether donor selection has been frozen.
1373    #[must_use]
1374    pub const fn is_frozen(&self) -> bool {
1375        matches!(self.representation, ValuesRepresentation::Frozen { .. })
1376    }
1377
1378    /// Freeze the row that donates comparison affinity and collation metadata.
1379    ///
1380    /// # Panics
1381    ///
1382    /// Panics when called on an already-frozen clause, when a donor index is
1383    /// outside `rows`, or when donor presence does not match row presence.
1384    pub fn freeze_donor_row(&mut self, donor_row: Option<usize>) {
1385        assert!(!self.is_frozen(), "VALUES donor is already frozen");
1386        assert_eq!(
1387            donor_row.is_some(),
1388            !self.rows.is_empty(),
1389            "non-empty VALUES clauses require a donor row"
1390        );
1391        assert!(
1392            donor_row.is_none_or(|index| index < self.rows.len()),
1393            "VALUES donor row must be present"
1394        );
1395        self.representation = ValuesRepresentation::Frozen { donor_row };
1396    }
1397
1398    /// Return the frozen donor row index, if one exists.
1399    #[must_use]
1400    pub const fn donor_row_index(&self) -> Option<usize> {
1401        match self.representation {
1402            ValuesRepresentation::Frozen { donor_row } => donor_row,
1403            ValuesRepresentation::Deferred { .. } => None,
1404        }
1405    }
1406
1407    /// Return the frozen donor row, if one exists.
1408    #[must_use]
1409    pub fn donor_row(&self) -> Option<&[Expr]> {
1410        self.donor_row_index()
1411            .and_then(|index| self.rows.get(index))
1412            .map(Vec::as_slice)
1413    }
1414
1415    /// Borrow all rows without permitting the outer collection to be resized.
1416    #[must_use]
1417    pub fn rows(&self) -> &[Vec<Expr>] {
1418        &self.rows
1419    }
1420
1421    /// Iterate over mutable row contents without exposing whole-row replacement.
1422    pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut [Expr]> + ExactSizeIterator {
1423        self.rows.iter_mut().map(Vec::as_mut_slice)
1424    }
1425
1426    /// Consume the wrapper and return its rows.
1427    #[must_use]
1428    pub fn into_rows(self) -> Vec<Vec<Expr>> {
1429        self.rows
1430    }
1431
1432    /// Replace rows while retaining representation metadata and row identity.
1433    ///
1434    /// # Panics
1435    ///
1436    /// Panics if the replacement changes the number of rows.
1437    pub fn replace_rows_preserving_representation(&mut self, rows: Vec<Vec<Expr>>) {
1438        assert_eq!(
1439            self.rows.len(),
1440            rows.len(),
1441            "VALUES rewrites must preserve row identity"
1442        );
1443        self.rows = rows;
1444    }
1445}
1446
1447impl Default for ValuesClause {
1448    fn default() -> Self {
1449        Self::new(Vec::new())
1450    }
1451}
1452
1453impl From<Vec<Vec<Expr>>> for ValuesClause {
1454    fn from(rows: Vec<Vec<Expr>>) -> Self {
1455        Self::new(rows)
1456    }
1457}
1458
1459impl std::ops::Deref for ValuesClause {
1460    type Target = [Vec<Expr>];
1461
1462    fn deref(&self) -> &Self::Target {
1463        self.rows()
1464    }
1465}
1466
1467impl<'a> IntoIterator for &'a ValuesClause {
1468    type Item = &'a Vec<Expr>;
1469    type IntoIter = std::slice::Iter<'a, Vec<Expr>>;
1470
1471    fn into_iter(self) -> Self::IntoIter {
1472        self.rows.iter()
1473    }
1474}
1475
1476/// A single SELECT core or VALUES clause.
1477#[derive(Debug, Clone, PartialEq)]
1478#[allow(clippy::large_enum_variant)]
1479pub enum SelectCore {
1480    /// `SELECT [DISTINCT|ALL] columns FROM ... WHERE ... GROUP BY ... HAVING ... WINDOW ...`
1481    Select {
1482        distinct: Distinctness,
1483        columns: Vec<ResultColumn>,
1484        from: Option<FromClause>,
1485        where_clause: Option<Box<Expr>>,
1486        group_by: Vec<Expr>,
1487        having: Option<Box<Expr>>,
1488        windows: Vec<WindowDef>,
1489    },
1490    /// `VALUES (row), (row), ...` — first-class in SQLite.
1491    Values(ValuesClause),
1492}
1493
1494/// DISTINCT / ALL modifier on SELECT.
1495#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1496pub enum Distinctness {
1497    #[default]
1498    All,
1499    Distinct,
1500}
1501
1502/// A single column in the SELECT result list.
1503#[derive(Debug, Clone, PartialEq)]
1504#[allow(clippy::large_enum_variant)]
1505pub enum ResultColumn {
1506    /// `*` — all columns from all tables.
1507    Star,
1508    /// `table.*` — all columns from a specific table.
1509    TableStar(QualifiedName),
1510    /// `expr [AS alias]`.
1511    Expr { expr: Expr, alias: Option<String> },
1512}
1513
1514/// The FROM clause.
1515#[derive(Debug, Clone, PartialEq)]
1516pub struct FromClause {
1517    /// The table sources joined together.
1518    pub source: TableOrSubquery,
1519    /// JOIN clauses.
1520    pub joins: Vec<JoinClause>,
1521}
1522
1523// ---------------------------------------------------------------------------
1524// Time-travel (SQL:2011 temporal queries)
1525// ---------------------------------------------------------------------------
1526
1527/// Target for a `FOR SYSTEM_TIME AS OF` clause.
1528///
1529/// This is the AST-level representation — decoupled from the MVCC
1530/// `TimeTravelTarget` in `fsqlite-mvcc`. Conversion happens in the
1531/// integration layer (`fsqlite-core`).
1532#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1533pub enum TimeTravelTarget {
1534    /// `FOR SYSTEM_TIME AS OF COMMITSEQ <n>` — pinned to a commit sequence number.
1535    CommitSequence(u64),
1536    /// `FOR SYSTEM_TIME AS OF '<iso8601>'` — resolved to a commit sequence later.
1537    Timestamp(String),
1538}
1539
1540/// A `FOR SYSTEM_TIME AS OF ...` clause attached to a table reference.
1541#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1542pub struct TimeTravelClause {
1543    pub target: TimeTravelTarget,
1544}
1545
1546/// A table source in a FROM clause.
1547#[derive(Debug, Clone, PartialEq)]
1548pub enum TableOrSubquery {
1549    /// A named table: `[schema.]table [AS alias] [INDEXED BY idx | NOT INDEXED] [FOR SYSTEM_TIME AS OF ...]`.
1550    Table {
1551        name: QualifiedName,
1552        alias: Option<String>,
1553        index_hint: Option<IndexHint>,
1554        time_travel: Option<TimeTravelClause>,
1555    },
1556    /// A subquery: `(SELECT ...) [AS alias]`.
1557    Subquery {
1558        query: Box<SelectStatement>,
1559        alias: Option<String>,
1560    },
1561    /// A table-valued function call: `func(args) [AS alias]`.
1562    TableFunction {
1563        name: String,
1564        args: Vec<Expr>,
1565        alias: Option<String>,
1566    },
1567    /// Parenthesized join: `(table JOIN table ...)`.
1568    ParenJoin(Box<FromClause>),
1569}
1570
1571/// Index hint on a FROM table reference.
1572#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1573pub enum IndexHint {
1574    /// `INDEXED BY index_name`.
1575    IndexedBy(String),
1576    /// `NOT INDEXED`.
1577    NotIndexed,
1578}
1579
1580/// A JOIN clause.
1581#[derive(Debug, Clone, PartialEq)]
1582pub struct JoinClause {
1583    /// Join type (INNER, LEFT, CROSS, NATURAL, etc.).
1584    pub join_type: JoinType,
1585    /// The right-hand table source.
1586    pub table: TableOrSubquery,
1587    /// Join constraint (ON or USING).
1588    pub constraint: Option<JoinConstraint>,
1589}
1590
1591/// Join type modifiers.
1592#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1593pub struct JoinType {
1594    /// Whether this is a NATURAL join.
1595    pub natural: bool,
1596    /// The join kind.
1597    pub kind: JoinKind,
1598}
1599
1600/// The kind of join.
1601#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1602pub enum JoinKind {
1603    /// `,` or `CROSS JOIN`.
1604    Cross,
1605    /// `[INNER] JOIN`.
1606    Inner,
1607    /// `LEFT [OUTER] JOIN`.
1608    Left,
1609    /// `RIGHT [OUTER] JOIN` (SQLite 3.39+).
1610    Right,
1611    /// `FULL [OUTER] JOIN` (SQLite 3.39+).
1612    Full,
1613}
1614
1615/// Join constraint: ON expression or USING column list.
1616#[derive(Debug, Clone, PartialEq)]
1617pub enum JoinConstraint {
1618    On(Expr),
1619    Using(Vec<String>),
1620}
1621
1622/// Named window definition in the WINDOW clause.
1623#[derive(Debug, Clone, PartialEq)]
1624pub struct WindowDef {
1625    /// Window name.
1626    pub name: String,
1627    /// Window specification.
1628    pub spec: WindowSpec,
1629}
1630
1631/// ORDER BY term.
1632#[derive(Debug, Clone, PartialEq)]
1633pub struct OrderingTerm {
1634    /// The expression to order by.
1635    pub expr: Expr,
1636    /// Sort direction.
1637    pub direction: Option<SortDirection>,
1638    /// NULLS FIRST or NULLS LAST.
1639    pub nulls: Option<NullsOrder>,
1640}
1641
1642/// Sort direction.
1643#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1644pub enum SortDirection {
1645    Asc,
1646    Desc,
1647}
1648
1649/// NULLS ordering.
1650#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1651pub enum NullsOrder {
1652    First,
1653    Last,
1654}
1655
1656/// LIMIT clause: `LIMIT expr [OFFSET expr]` or `LIMIT expr, expr`.
1657#[derive(Debug, Clone, PartialEq)]
1658pub struct LimitClause {
1659    pub limit: Expr,
1660    pub offset: Option<Expr>,
1661}
1662
1663// ---------------------------------------------------------------------------
1664// INSERT statement
1665// ---------------------------------------------------------------------------
1666
1667/// An INSERT statement.
1668#[derive(Debug, Clone, PartialEq)]
1669pub struct InsertStatement {
1670    /// Optional WITH clause.
1671    pub with: Option<WithClause>,
1672    /// INSERT or REPLACE or INSERT OR conflict_action.
1673    pub or_conflict: Option<ConflictAction>,
1674    /// Target table name.
1675    pub table: QualifiedName,
1676    /// Optional alias for the target table.
1677    pub alias: Option<String>,
1678    /// Optional column name list.
1679    pub columns: Vec<String>,
1680    /// The source of values.
1681    pub source: InsertSource,
1682    /// ON CONFLICT (upsert) clauses.
1683    pub upsert: Vec<UpsertClause>,
1684    /// RETURNING clause.
1685    pub returning: Vec<ResultColumn>,
1686}
1687
1688/// Source of values for INSERT.
1689#[derive(Debug, Clone, PartialEq)]
1690pub enum InsertSource {
1691    /// `VALUES (row), (row), ...`
1692    Values(Vec<Vec<Expr>>),
1693    /// `SELECT ...`
1694    Select(Box<SelectStatement>),
1695    /// `DEFAULT VALUES`
1696    DefaultValues,
1697}
1698
1699/// Conflict resolution action.
1700#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1701pub enum ConflictAction {
1702    Rollback,
1703    Abort,
1704    Fail,
1705    Ignore,
1706    Replace,
1707}
1708
1709/// An ON CONFLICT (upsert) clause.
1710#[derive(Debug, Clone, PartialEq)]
1711pub struct UpsertClause {
1712    /// Conflict target columns.
1713    pub target: Option<UpsertTarget>,
1714    /// The DO action.
1715    pub action: UpsertAction,
1716}
1717
1718/// Upsert conflict target.
1719#[derive(Debug, Clone, PartialEq)]
1720pub struct UpsertTarget {
1721    /// Indexed columns.
1722    pub columns: Vec<IndexedColumn>,
1723    /// Optional WHERE clause for partial index matching.
1724    pub where_clause: Option<Expr>,
1725}
1726
1727/// Upsert action: DO NOTHING or DO UPDATE SET ...
1728#[derive(Debug, Clone, PartialEq)]
1729pub enum UpsertAction {
1730    Nothing,
1731    Update {
1732        assignments: Vec<Assignment>,
1733        where_clause: Option<Box<Expr>>,
1734    },
1735}
1736
1737// ---------------------------------------------------------------------------
1738// UPDATE statement
1739// ---------------------------------------------------------------------------
1740
1741/// An UPDATE statement.
1742#[derive(Debug, Clone, PartialEq)]
1743pub struct UpdateStatement {
1744    /// Optional WITH clause.
1745    pub with: Option<WithClause>,
1746    /// UPDATE OR conflict_action.
1747    pub or_conflict: Option<ConflictAction>,
1748    /// Target table.
1749    pub table: QualifiedTableRef,
1750    /// SET assignments.
1751    pub assignments: Vec<Assignment>,
1752    /// Optional FROM clause (SQLite 3.33+).
1753    pub from: Option<FromClause>,
1754    /// WHERE clause.
1755    pub where_clause: Option<Expr>,
1756    /// RETURNING clause.
1757    pub returning: Vec<ResultColumn>,
1758    /// ORDER BY (only with LIMIT).
1759    pub order_by: Vec<OrderingTerm>,
1760    /// LIMIT clause.
1761    pub limit: Option<LimitClause>,
1762}
1763
1764/// A SET assignment: `column = expr` or `(col1, col2) = expr`.
1765#[derive(Debug, Clone, PartialEq)]
1766pub struct Assignment {
1767    /// Target column(s).
1768    pub target: AssignmentTarget,
1769    /// Value expression.
1770    pub value: Expr,
1771}
1772
1773/// Left-hand side of an assignment.
1774#[derive(Debug, Clone, PartialEq, Eq)]
1775pub enum AssignmentTarget {
1776    /// Single column name.
1777    Column(String),
1778    /// Column name list: `(col1, col2, ...)`.
1779    ColumnList(Vec<String>),
1780}
1781
1782/// A table reference with optional alias and index hint (for UPDATE/DELETE).
1783#[derive(Debug, Clone, PartialEq, Eq)]
1784pub struct QualifiedTableRef {
1785    pub name: QualifiedName,
1786    pub alias: Option<String>,
1787    pub index_hint: Option<IndexHint>,
1788    pub time_travel: Option<TimeTravelClause>,
1789}
1790
1791// ---------------------------------------------------------------------------
1792// DELETE statement
1793// ---------------------------------------------------------------------------
1794
1795/// A DELETE statement.
1796#[derive(Debug, Clone, PartialEq)]
1797pub struct DeleteStatement {
1798    /// Optional WITH clause.
1799    pub with: Option<WithClause>,
1800    /// Target table.
1801    pub table: QualifiedTableRef,
1802    /// WHERE clause.
1803    pub where_clause: Option<Expr>,
1804    /// RETURNING clause.
1805    pub returning: Vec<ResultColumn>,
1806    /// ORDER BY (only with LIMIT).
1807    pub order_by: Vec<OrderingTerm>,
1808    /// LIMIT clause.
1809    pub limit: Option<LimitClause>,
1810}
1811
1812// ---------------------------------------------------------------------------
1813// DDL: CREATE TABLE
1814// ---------------------------------------------------------------------------
1815
1816/// A CREATE TABLE statement.
1817#[derive(Debug, Clone, PartialEq)]
1818#[allow(clippy::struct_excessive_bools)]
1819pub struct CreateTableStatement {
1820    /// `IF NOT EXISTS` flag.
1821    pub if_not_exists: bool,
1822    /// `CREATE TEMP TABLE`.
1823    pub temporary: bool,
1824    /// Table name.
1825    pub name: QualifiedName,
1826    /// Table definition body.
1827    pub body: CreateTableBody,
1828    /// `WITHOUT ROWID` flag.
1829    pub without_rowid: bool,
1830    /// `STRICT` flag (SQLite 3.37+).
1831    pub strict: bool,
1832}
1833
1834/// The body of a CREATE TABLE.
1835#[derive(Debug, Clone, PartialEq)]
1836pub enum CreateTableBody {
1837    /// Column and constraint definitions.
1838    Columns {
1839        columns: Vec<ColumnDef>,
1840        constraints: Vec<TableConstraint>,
1841    },
1842    /// `AS SELECT ...`
1843    AsSelect(Box<SelectStatement>),
1844}
1845
1846/// A column definition.
1847#[derive(Debug, Clone, PartialEq)]
1848pub struct ColumnDef {
1849    /// Column name.
1850    pub name: String,
1851    /// Optional type name.
1852    pub type_name: Option<TypeName>,
1853    /// Column constraints.
1854    pub constraints: Vec<ColumnConstraint>,
1855}
1856
1857/// A constraint on a single column.
1858#[derive(Debug, Clone, PartialEq)]
1859pub struct ColumnConstraint {
1860    /// Optional constraint name.
1861    pub name: Option<String>,
1862    /// The constraint kind.
1863    pub kind: ColumnConstraintKind,
1864}
1865
1866/// Column constraint variants.
1867#[derive(Debug, Clone, PartialEq)]
1868pub enum ColumnConstraintKind {
1869    PrimaryKey {
1870        direction: Option<SortDirection>,
1871        conflict: Option<ConflictAction>,
1872        autoincrement: bool,
1873    },
1874    NotNull {
1875        conflict: Option<ConflictAction>,
1876    },
1877    Null,
1878    Unique {
1879        conflict: Option<ConflictAction>,
1880    },
1881    Check(Expr),
1882    Default(DefaultValue),
1883    Collate(String),
1884    ForeignKey(ForeignKeyClause),
1885    Generated {
1886        expr: Expr,
1887        storage: Option<GeneratedStorage>,
1888    },
1889}
1890
1891/// Default value for a column.
1892#[derive(Debug, Clone, PartialEq)]
1893pub enum DefaultValue {
1894    Expr(Expr),
1895    /// Parenthesized expression: `DEFAULT (expr)`.
1896    ParenExpr(Expr),
1897}
1898
1899/// Generated column storage type.
1900#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1901pub enum GeneratedStorage {
1902    Stored,
1903    Virtual,
1904}
1905
1906/// A table-level constraint.
1907#[derive(Debug, Clone, PartialEq)]
1908pub struct TableConstraint {
1909    /// Optional constraint name.
1910    pub name: Option<String>,
1911    /// The constraint kind.
1912    pub kind: TableConstraintKind,
1913}
1914
1915/// Table constraint variants.
1916#[derive(Debug, Clone, PartialEq)]
1917pub enum TableConstraintKind {
1918    PrimaryKey {
1919        columns: Vec<IndexedColumn>,
1920        conflict: Option<ConflictAction>,
1921    },
1922    Unique {
1923        columns: Vec<IndexedColumn>,
1924        conflict: Option<ConflictAction>,
1925    },
1926    Check(Expr),
1927    ForeignKey {
1928        columns: Vec<String>,
1929        clause: ForeignKeyClause,
1930    },
1931}
1932
1933/// An indexed column specification (for PRIMARY KEY, UNIQUE, CREATE INDEX).
1934#[derive(Debug, Clone, PartialEq)]
1935pub struct IndexedColumn {
1936    /// The column expression (usually just a column name).
1937    pub expr: Expr,
1938    /// Optional collation.
1939    pub collation: Option<String>,
1940    /// Optional sort direction.
1941    pub direction: Option<SortDirection>,
1942}
1943
1944/// A REFERENCES clause for foreign keys.
1945#[derive(Debug, Clone, PartialEq, Eq)]
1946pub struct ForeignKeyClause {
1947    /// Referenced table.
1948    pub table: String,
1949    /// Referenced columns (empty = implicit rowid).
1950    pub columns: Vec<String>,
1951    /// ON DELETE / ON UPDATE actions.
1952    pub actions: Vec<ForeignKeyAction>,
1953    /// DEFERRABLE clause.
1954    pub deferrable: Option<Deferrable>,
1955}
1956
1957/// Foreign key ON DELETE/UPDATE action.
1958#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1959pub struct ForeignKeyAction {
1960    pub trigger: ForeignKeyTrigger,
1961    pub action: ForeignKeyActionType,
1962}
1963
1964/// When the foreign key action fires.
1965#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1966pub enum ForeignKeyTrigger {
1967    OnDelete,
1968    OnUpdate,
1969}
1970
1971/// Foreign key action type.
1972#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1973pub enum ForeignKeyActionType {
1974    SetNull,
1975    SetDefault,
1976    Cascade,
1977    Restrict,
1978    NoAction,
1979}
1980
1981/// Deferrable constraint specification.
1982#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1983pub struct Deferrable {
1984    pub not: bool,
1985    pub initially: Option<DeferrableInitially>,
1986}
1987
1988/// INITIALLY DEFERRED or INITIALLY IMMEDIATE.
1989#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1990pub enum DeferrableInitially {
1991    Deferred,
1992    Immediate,
1993}
1994
1995// ---------------------------------------------------------------------------
1996// DDL: CREATE INDEX
1997// ---------------------------------------------------------------------------
1998
1999/// A CREATE INDEX statement.
2000#[derive(Debug, Clone, PartialEq)]
2001pub struct CreateIndexStatement {
2002    /// `CREATE UNIQUE INDEX`.
2003    pub unique: bool,
2004    /// `IF NOT EXISTS` flag.
2005    pub if_not_exists: bool,
2006    /// Index name.
2007    pub name: QualifiedName,
2008    /// Table the index is on.
2009    pub table: String,
2010    /// Indexed columns.
2011    pub columns: Vec<IndexedColumn>,
2012    /// Optional partial index WHERE clause.
2013    pub where_clause: Option<Expr>,
2014}
2015
2016// ---------------------------------------------------------------------------
2017// DDL: CREATE VIEW
2018// ---------------------------------------------------------------------------
2019
2020/// A CREATE VIEW statement.
2021#[derive(Debug, Clone, PartialEq)]
2022pub struct CreateViewStatement {
2023    /// `IF NOT EXISTS` flag.
2024    pub if_not_exists: bool,
2025    /// `CREATE TEMP VIEW`.
2026    pub temporary: bool,
2027    /// View name.
2028    pub name: QualifiedName,
2029    /// Optional column name list.
2030    pub columns: Vec<String>,
2031    /// The view's SELECT query.
2032    pub query: SelectStatement,
2033}
2034
2035// ---------------------------------------------------------------------------
2036// DDL: CREATE TRIGGER
2037// ---------------------------------------------------------------------------
2038
2039/// A CREATE TRIGGER statement.
2040#[derive(Debug, Clone, PartialEq)]
2041pub struct CreateTriggerStatement {
2042    /// `IF NOT EXISTS` flag.
2043    pub if_not_exists: bool,
2044    /// `CREATE TEMP TRIGGER`.
2045    pub temporary: bool,
2046    /// Trigger name.
2047    pub name: QualifiedName,
2048    /// When the trigger fires.
2049    pub timing: TriggerTiming,
2050    /// What event triggers it.
2051    pub event: TriggerEvent,
2052    /// Table the trigger is on.
2053    pub table: String,
2054    /// `FOR EACH ROW` (SQLite only supports row-level triggers).
2055    pub for_each_row: bool,
2056    /// Optional WHEN condition.
2057    pub when: Option<Expr>,
2058    /// Trigger body statements.
2059    pub body: Vec<Statement>,
2060}
2061
2062/// Trigger timing: BEFORE, AFTER, or INSTEAD OF.
2063#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2064pub enum TriggerTiming {
2065    Before,
2066    After,
2067    InsteadOf,
2068}
2069
2070/// Trigger event: INSERT, DELETE, or UPDATE [OF columns].
2071#[derive(Debug, Clone, PartialEq, Eq)]
2072pub enum TriggerEvent {
2073    Insert,
2074    Delete,
2075    Update(Vec<String>),
2076}
2077
2078// ---------------------------------------------------------------------------
2079// DDL: CREATE VIRTUAL TABLE
2080// ---------------------------------------------------------------------------
2081
2082/// A CREATE VIRTUAL TABLE statement.
2083#[derive(Debug, Clone, PartialEq, Eq)]
2084pub struct CreateVirtualTableStatement {
2085    /// `IF NOT EXISTS` flag.
2086    pub if_not_exists: bool,
2087    /// Table name.
2088    pub name: QualifiedName,
2089    /// Module name (e.g. `fts5`, `rtree`).
2090    pub module: String,
2091    /// Module arguments (opaque strings).
2092    pub args: Vec<String>,
2093}
2094
2095// ---------------------------------------------------------------------------
2096// DDL: DROP
2097// ---------------------------------------------------------------------------
2098
2099/// A DROP statement.
2100#[derive(Debug, Clone, PartialEq, Eq)]
2101pub struct DropStatement {
2102    /// What kind of object to drop.
2103    pub object_type: DropObjectType,
2104    /// `IF EXISTS` flag.
2105    pub if_exists: bool,
2106    /// Object name.
2107    pub name: QualifiedName,
2108}
2109
2110/// DROP target type.
2111#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2112pub enum DropObjectType {
2113    Table,
2114    View,
2115    Index,
2116    Trigger,
2117}
2118
2119// ---------------------------------------------------------------------------
2120// DDL: ALTER TABLE
2121// ---------------------------------------------------------------------------
2122
2123/// An ALTER TABLE statement.
2124#[derive(Debug, Clone, PartialEq)]
2125pub struct AlterTableStatement {
2126    /// Table name.
2127    pub table: QualifiedName,
2128    /// The alteration to perform.
2129    pub action: AlterTableAction,
2130}
2131
2132/// ALTER TABLE action variants.
2133#[derive(Debug, Clone, PartialEq)]
2134pub enum AlterTableAction {
2135    /// `RENAME TO new_name`.
2136    RenameTo(String),
2137    /// `RENAME COLUMN old TO new`.
2138    RenameColumn { old: String, new: String },
2139    /// `ADD COLUMN column_def`.
2140    AddColumn(ColumnDef),
2141    /// `DROP COLUMN column_name`.
2142    DropColumn(String),
2143}
2144
2145// ---------------------------------------------------------------------------
2146// Transaction control
2147// ---------------------------------------------------------------------------
2148
2149/// A BEGIN statement.
2150#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2151pub struct BeginStatement {
2152    /// Transaction mode.
2153    pub mode: Option<TransactionMode>,
2154}
2155
2156/// Transaction mode for BEGIN.
2157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2158pub enum TransactionMode {
2159    Deferred,
2160    Immediate,
2161    Exclusive,
2162    /// FrankenSQLite extension: MVCC concurrent writer mode with Snapshot Isolation.
2163    Concurrent,
2164}
2165
2166/// A ROLLBACK statement.
2167#[derive(Debug, Clone, PartialEq, Eq)]
2168pub struct RollbackStatement {
2169    /// Optional savepoint name to roll back to.
2170    pub to_savepoint: Option<String>,
2171}
2172
2173// ---------------------------------------------------------------------------
2174// ATTACH / DETACH
2175// ---------------------------------------------------------------------------
2176
2177/// An ATTACH statement.
2178#[derive(Debug, Clone, PartialEq)]
2179pub struct AttachStatement {
2180    /// The database file expression.
2181    pub expr: Expr,
2182    /// The schema name.
2183    pub schema: String,
2184}
2185
2186// ---------------------------------------------------------------------------
2187// PRAGMA
2188// ---------------------------------------------------------------------------
2189
2190/// A PRAGMA statement.
2191#[derive(Debug, Clone, PartialEq)]
2192pub struct PragmaStatement {
2193    /// Pragma name (possibly schema-qualified).
2194    pub name: QualifiedName,
2195    /// Pragma value or call argument.
2196    pub value: Option<PragmaValue>,
2197}
2198
2199/// PRAGMA value form.
2200#[derive(Debug, Clone, PartialEq)]
2201pub enum PragmaValue {
2202    /// `PRAGMA name = value`.
2203    Assign(Expr),
2204    /// `PRAGMA name(value)`.
2205    Call(Expr),
2206}
2207
2208// ---------------------------------------------------------------------------
2209// VACUUM
2210// ---------------------------------------------------------------------------
2211
2212/// A VACUUM statement.
2213#[derive(Debug, Clone, PartialEq)]
2214pub struct VacuumStatement {
2215    /// Optional schema name.
2216    pub schema: Option<String>,
2217    /// Optional INTO filename.
2218    pub into: Option<Expr>,
2219}
2220
2221// ---------------------------------------------------------------------------
2222// Name resolution types (§10.4)
2223// ---------------------------------------------------------------------------
2224
2225/// A resolved column reference after name resolution.
2226#[derive(Debug, Clone, PartialEq, Eq)]
2227pub struct ResolvedColumn {
2228    /// Index of the table in the FROM clause (0-based).
2229    pub table_idx: usize,
2230    /// Column index within that table's schema (0-based).
2231    pub column_idx: usize,
2232    /// The table name or alias this resolved to.
2233    pub table_name: String,
2234    /// The column name.
2235    pub column_name: String,
2236}
2237
2238/// A table schema entry used during name resolution.
2239#[derive(Debug, Clone, PartialEq, Eq)]
2240pub struct TableSchema {
2241    /// The table name as it appears in the schema.
2242    pub name: String,
2243    /// The alias bound in the FROM clause (if any).
2244    pub alias: Option<String>,
2245    /// Column names in order.
2246    pub columns: Vec<String>,
2247}
2248
2249impl TableSchema {
2250    /// The effective name for lookup (alias if present, else table name).
2251    #[must_use]
2252    pub fn effective_name(&self) -> &str {
2253        self.alias.as_deref().unwrap_or(&self.name)
2254    }
2255}
2256
2257/// Errors during name resolution.
2258#[derive(Debug, Clone, PartialEq, Eq)]
2259pub enum ResolveError {
2260    /// No such table in the FROM clause.
2261    NoSuchTable { name: String, span: Span },
2262    /// No such column in the referenced table.
2263    NoSuchColumn {
2264        table: String,
2265        column: String,
2266        span: Span,
2267    },
2268    /// Ambiguous unqualified column reference matches multiple tables.
2269    AmbiguousColumn {
2270        column: String,
2271        candidates: Vec<String>,
2272        span: Span,
2273    },
2274    /// Unqualified column name not found in any table in scope.
2275    ColumnNotFound { column: String, span: Span },
2276    /// Correlated subquery references an outer table that doesn't exist.
2277    NoOuterTable { name: String, span: Span },
2278}
2279
2280impl fmt::Display for ResolveError {
2281    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2282        match self {
2283            Self::NoSuchTable { name, span } => {
2284                write!(f, "no such table: {name} at {span}")
2285            }
2286            Self::NoSuchColumn {
2287                table,
2288                column,
2289                span,
2290            } => {
2291                write!(f, "no such column: {table}.{column} at {span}")
2292            }
2293            Self::AmbiguousColumn {
2294                column,
2295                candidates,
2296                span,
2297            } => {
2298                write!(
2299                    f,
2300                    "ambiguous column name: {column} (candidates: {}) at {span}",
2301                    candidates.join(", ")
2302                )
2303            }
2304            Self::ColumnNotFound { column, span } => {
2305                write!(f, "no such column: {column} at {span}")
2306            }
2307            Self::NoOuterTable { name, span } => {
2308                write!(f, "no such table in outer scope: {name} at {span}")
2309            }
2310        }
2311    }
2312}
2313
2314impl std::error::Error for ResolveError {}
2315
2316/// A scope for name resolution, supporting nested subquery scopes.
2317#[derive(Debug, Clone)]
2318pub struct ResolverScope {
2319    /// Tables available in this scope.
2320    pub tables: Vec<TableSchema>,
2321    /// Parent scope (for correlated subquery resolution).
2322    pub parent: Option<Box<Self>>,
2323}
2324
2325impl ResolverScope {
2326    /// Create a new root scope with the given table schemas.
2327    #[must_use]
2328    pub fn new(tables: Vec<TableSchema>) -> Self {
2329        Self {
2330            tables,
2331            parent: None,
2332        }
2333    }
2334
2335    /// Create a child scope for a subquery, with this scope as the parent.
2336    #[must_use]
2337    pub fn child(self, tables: Vec<TableSchema>) -> Self {
2338        Self {
2339            tables,
2340            parent: Some(Box::new(self)),
2341        }
2342    }
2343
2344    /// Resolve a possibly-qualified column reference.
2345    ///
2346    /// For qualified refs (`t.col`): find table `t` in scope, then verify `col`.
2347    /// For unqualified refs (`col`): search all tables; error if ambiguous.
2348    /// If not found in this scope, search parent scopes (correlated subquery).
2349    pub fn resolve(&self, col: &ColumnRef, span: Span) -> Result<ResolvedColumn, ResolveError> {
2350        match &col.table {
2351            Some(table_name) => self.resolve_qualified(table_name, &col.column, span),
2352            None => self.resolve_unqualified(&col.column, span),
2353        }
2354    }
2355
2356    fn resolve_qualified(
2357        &self,
2358        table_name: &str,
2359        column: &str,
2360        span: Span,
2361    ) -> Result<ResolvedColumn, ResolveError> {
2362        for (idx, table) in self.tables.iter().enumerate() {
2363            if table.effective_name().eq_ignore_ascii_case(table_name) {
2364                return match table
2365                    .columns
2366                    .iter()
2367                    .position(|c| c.eq_ignore_ascii_case(column))
2368                {
2369                    Some(col_idx) => Ok(ResolvedColumn {
2370                        table_idx: idx,
2371                        column_idx: col_idx,
2372                        table_name: table.effective_name().to_owned(),
2373                        column_name: table.columns[col_idx].clone(),
2374                    }),
2375                    None => Err(ResolveError::NoSuchColumn {
2376                        table: table_name.to_owned(),
2377                        column: column.to_owned(),
2378                        span,
2379                    }),
2380                };
2381            }
2382        }
2383
2384        // Try parent scope (correlated subquery).
2385        if let Some(ref parent) = self.parent {
2386            return parent.resolve_qualified(table_name, column, span);
2387        }
2388
2389        Err(ResolveError::NoSuchTable {
2390            name: table_name.to_owned(),
2391            span,
2392        })
2393    }
2394
2395    fn resolve_unqualified(
2396        &self,
2397        column: &str,
2398        span: Span,
2399    ) -> Result<ResolvedColumn, ResolveError> {
2400        let mut found: Option<ResolvedColumn> = None;
2401        let mut candidates = Vec::new();
2402
2403        for (idx, table) in self.tables.iter().enumerate() {
2404            if let Some(col_idx) = table
2405                .columns
2406                .iter()
2407                .position(|c| c.eq_ignore_ascii_case(column))
2408            {
2409                candidates.push(table.effective_name().to_owned());
2410                found = Some(ResolvedColumn {
2411                    table_idx: idx,
2412                    column_idx: col_idx,
2413                    table_name: table.effective_name().to_owned(),
2414                    column_name: table.columns[col_idx].clone(),
2415                });
2416            }
2417        }
2418
2419        match candidates.len() {
2420            0 => {
2421                // Try parent scope (correlated subquery).
2422                if let Some(ref parent) = self.parent {
2423                    return parent.resolve_unqualified(column, span);
2424                }
2425                Err(ResolveError::ColumnNotFound {
2426                    column: column.to_owned(),
2427                    span,
2428                })
2429            }
2430            1 => found.ok_or_else(|| ResolveError::ColumnNotFound {
2431                column: column.to_owned(),
2432                span,
2433            }),
2434            _ => Err(ResolveError::AmbiguousColumn {
2435                column: column.to_owned(),
2436                candidates,
2437                span,
2438            }),
2439        }
2440    }
2441
2442    /// Expand `SELECT *` to explicit column references.
2443    ///
2444    /// Returns a list of `(table_name, column_name)` pairs in order.
2445    #[must_use]
2446    pub fn expand_star(&self) -> Vec<(String, String)> {
2447        let mut result = Vec::new();
2448        for table in &self.tables {
2449            for col in &table.columns {
2450                result.push((table.effective_name().to_owned(), col.clone()));
2451            }
2452        }
2453        result
2454    }
2455
2456    /// Expand `table.*` to explicit column references.
2457    pub fn expand_table_star(
2458        &self,
2459        table_name: &str,
2460        span: Span,
2461    ) -> Result<Vec<(String, String)>, ResolveError> {
2462        for table in &self.tables {
2463            if table.effective_name().eq_ignore_ascii_case(table_name) {
2464                return Ok(table
2465                    .columns
2466                    .iter()
2467                    .map(|c| (table.effective_name().to_owned(), c.clone()))
2468                    .collect());
2469            }
2470        }
2471        Err(ResolveError::NoSuchTable {
2472            name: table_name.to_owned(),
2473            span,
2474        })
2475    }
2476}
2477
2478// ---------------------------------------------------------------------------
2479// Tests
2480// ---------------------------------------------------------------------------
2481
2482#[cfg(test)]
2483mod tests {
2484    use super::*;
2485
2486    // --- AST construction tests (§10.3) ---
2487
2488    #[test]
2489    fn test_ast_statement_variants_dml() {
2490        let _ = Statement::Select(SelectStatement {
2491            with: None,
2492            body: SelectBody {
2493                select: SelectCore::Values(
2494                    vec![vec![Expr::Literal(Literal::Integer(1), Span::ZERO)]].into(),
2495                ),
2496                compounds: vec![],
2497            },
2498            order_by: vec![],
2499            limit: None,
2500        });
2501
2502        let _ = Statement::Insert(InsertStatement {
2503            with: None,
2504            or_conflict: None,
2505            table: QualifiedName::bare("t"),
2506            alias: None,
2507            columns: vec![],
2508            source: InsertSource::DefaultValues,
2509            upsert: vec![],
2510            returning: vec![],
2511        });
2512
2513        let table_ref = QualifiedTableRef {
2514            name: QualifiedName::bare("t"),
2515            alias: None,
2516            index_hint: None,
2517            time_travel: None,
2518        };
2519        let _ = Statement::Update(UpdateStatement {
2520            with: None,
2521            or_conflict: None,
2522            table: table_ref.clone(),
2523            assignments: vec![],
2524            from: None,
2525            where_clause: None,
2526            returning: vec![],
2527            order_by: vec![],
2528            limit: None,
2529        });
2530        let _ = Statement::Delete(DeleteStatement {
2531            with: None,
2532            table: table_ref,
2533            where_clause: None,
2534            returning: vec![],
2535            order_by: vec![],
2536            limit: None,
2537        });
2538    }
2539
2540    #[test]
2541    fn test_ast_statement_variants_ddl() {
2542        let _ = Statement::CreateTable(CreateTableStatement {
2543            if_not_exists: false,
2544            temporary: false,
2545            name: QualifiedName::bare("t"),
2546            body: CreateTableBody::Columns {
2547                columns: vec![],
2548                constraints: vec![],
2549            },
2550            without_rowid: false,
2551            strict: false,
2552        });
2553
2554        let _ = Statement::CreateIndex(CreateIndexStatement {
2555            unique: false,
2556            if_not_exists: false,
2557            name: QualifiedName::bare("idx"),
2558            table: "t".to_owned(),
2559            columns: vec![],
2560            where_clause: None,
2561        });
2562
2563        let _ = Statement::CreateView(CreateViewStatement {
2564            if_not_exists: false,
2565            temporary: false,
2566            name: QualifiedName::bare("v"),
2567            columns: vec![],
2568            query: SelectStatement {
2569                with: None,
2570                body: SelectBody {
2571                    select: SelectCore::Values(vec![].into()),
2572                    compounds: vec![],
2573                },
2574                order_by: vec![],
2575                limit: None,
2576            },
2577        });
2578
2579        let _ = Statement::CreateTrigger(CreateTriggerStatement {
2580            if_not_exists: false,
2581            temporary: false,
2582            name: QualifiedName::bare("tr"),
2583            timing: TriggerTiming::Before,
2584            event: TriggerEvent::Insert,
2585            table: "t".to_owned(),
2586            for_each_row: true,
2587            when: None,
2588            body: vec![],
2589        });
2590
2591        let _ = Statement::CreateVirtualTable(CreateVirtualTableStatement {
2592            if_not_exists: false,
2593            name: QualifiedName::bare("vt"),
2594            module: "fts5".to_owned(),
2595            args: vec!["content".to_owned()],
2596        });
2597
2598        let _ = Statement::Drop(DropStatement {
2599            object_type: DropObjectType::Table,
2600            if_exists: false,
2601            name: QualifiedName::bare("t"),
2602        });
2603
2604        let _ = Statement::AlterTable(AlterTableStatement {
2605            table: QualifiedName::bare("t"),
2606            action: AlterTableAction::RenameTo("t2".to_owned()),
2607        });
2608    }
2609
2610    #[test]
2611    fn test_ast_statement_variants_txn_and_misc() {
2612        let _ = Statement::Begin(BeginStatement { mode: None });
2613        let _ = Statement::Commit;
2614        let _ = Statement::Rollback(RollbackStatement { to_savepoint: None });
2615
2616        let _ = Statement::Savepoint("sp1".to_owned());
2617        let _ = Statement::Release("sp1".to_owned());
2618
2619        let _ = Statement::Attach(AttachStatement {
2620            expr: Expr::Literal(Literal::String("file.db".to_owned()), Span::ZERO),
2621            schema: "aux".to_owned(),
2622        });
2623        let _ = Statement::Detach("aux".to_owned());
2624
2625        let _ = Statement::Pragma(PragmaStatement {
2626            name: QualifiedName::bare("cache_size"),
2627            value: None,
2628        });
2629        let _ = Statement::Vacuum(VacuumStatement {
2630            schema: None,
2631            into: None,
2632        });
2633
2634        let _ = Statement::Reindex(None);
2635        let _ = Statement::Analyze(None);
2636
2637        let _ = Statement::Explain {
2638            query_plan: true,
2639            stmt: Box::new(Statement::Commit),
2640        };
2641    }
2642
2643    #[test]
2644    fn test_ast_select_body_with_compounds() {
2645        let core1 =
2646            SelectCore::Values(vec![vec![Expr::Literal(Literal::Integer(1), Span::ZERO)]].into());
2647        let core2 =
2648            SelectCore::Values(vec![vec![Expr::Literal(Literal::Integer(2), Span::ZERO)]].into());
2649        let core3 =
2650            SelectCore::Values(vec![vec![Expr::Literal(Literal::Integer(3), Span::ZERO)]].into());
2651
2652        let body = SelectBody {
2653            select: core1,
2654            compounds: vec![(CompoundOp::Union, core2), (CompoundOp::Intersect, core3)],
2655        };
2656
2657        assert_eq!(body.compounds.len(), 2);
2658        assert_eq!(body.compounds[0].0, CompoundOp::Union);
2659        assert_eq!(body.compounds[1].0, CompoundOp::Intersect);
2660    }
2661
2662    #[test]
2663    fn test_ast_values_as_first_class() {
2664        let values = SelectCore::Values(
2665            vec![
2666                vec![
2667                    Expr::Literal(Literal::Integer(1), Span::ZERO),
2668                    Expr::Literal(Literal::Integer(2), Span::ZERO),
2669                ],
2670                vec![
2671                    Expr::Literal(Literal::Integer(3), Span::ZERO),
2672                    Expr::Literal(Literal::Integer(4), Span::ZERO),
2673                ],
2674            ]
2675            .into(),
2676        );
2677
2678        assert!(matches!(values, SelectCore::Values(ref rows) if rows.len() == 2));
2679
2680        // Values is distinct from Select.
2681        let select = SelectCore::Select {
2682            distinct: Distinctness::All,
2683            columns: vec![],
2684            from: None,
2685            where_clause: None,
2686            group_by: vec![],
2687            having: None,
2688            windows: vec![],
2689        };
2690        assert!(!matches!(select, SelectCore::Values(_)));
2691    }
2692
2693    #[test]
2694    fn values_clause_preserves_deferred_and_frozen_representation_invariants() {
2695        let rows = vec![
2696            vec![Expr::Literal(Literal::Integer(1), Span::ZERO)],
2697            vec![Expr::Literal(Literal::Integer(2), Span::ZERO)],
2698        ];
2699        let mut values = ValuesClause::parsed(rows.clone(), Some(1));
2700
2701        assert_eq!(values.rows(), rows.as_slice());
2702        assert_eq!(values.force_union_all_from(), Some(1));
2703        assert_eq!(
2704            values.representation(),
2705            ValuesRepresentation::Deferred {
2706                force_union_all_from: Some(1),
2707            }
2708        );
2709        assert!(!values.is_frozen());
2710        assert_eq!(values.donor_row_index(), None);
2711        assert_eq!(values.donor_row(), None);
2712
2713        let first_row = values.iter_mut().next().expect("first row must exist");
2714        first_row[0] = Expr::Literal(Literal::Integer(9), Span::ZERO);
2715        assert_eq!(values[0][0], Expr::Literal(Literal::Integer(9), Span::ZERO));
2716
2717        let replacement = vec![
2718            vec![Expr::Literal(Literal::Integer(3), Span::ZERO)],
2719            vec![Expr::Literal(Literal::Integer(4), Span::ZERO)],
2720        ];
2721        values.replace_rows_preserving_representation(replacement.clone());
2722        values.freeze_donor_row(Some(1));
2723
2724        assert!(values.is_frozen());
2725        assert_eq!(values.force_union_all_from(), None);
2726        assert_eq!(values.donor_row_index(), Some(1));
2727        assert_eq!(values.donor_row(), Some(replacement[1].as_slice()));
2728        assert_eq!(
2729            values.representation(),
2730            ValuesRepresentation::Frozen { donor_row: Some(1) }
2731        );
2732        assert_eq!(values.clone().into_rows(), replacement);
2733    }
2734
2735    #[test]
2736    fn empty_values_clause_can_freeze_without_a_donor() {
2737        let mut values = ValuesClause::default();
2738        values.freeze_donor_row(None);
2739
2740        assert!(values.is_empty());
2741        assert!(values.is_frozen());
2742        assert_eq!(values.donor_row(), None);
2743        assert!(values.into_rows().is_empty());
2744    }
2745
2746    #[test]
2747    #[should_panic(expected = "forced VALUES row must be present")]
2748    fn values_clause_rejects_an_invalid_forced_row() {
2749        let _ = ValuesClause::parsed(
2750            vec![vec![Expr::Literal(Literal::Integer(1), Span::ZERO)]],
2751            Some(1),
2752        );
2753    }
2754
2755    #[test]
2756    #[should_panic(expected = "VALUES donor row must be present")]
2757    fn values_clause_rejects_an_invalid_donor_row() {
2758        let mut values =
2759            ValuesClause::new(vec![vec![Expr::Literal(Literal::Integer(1), Span::ZERO)]]);
2760        values.freeze_donor_row(Some(1));
2761    }
2762
2763    #[test]
2764    #[allow(clippy::too_many_lines)]
2765    fn test_ast_expr_variants_core() {
2766        let span = Span::new(0, 10);
2767        let dummy = || Box::new(Expr::Literal(Literal::Null, span));
2768
2769        let exprs: Vec<Expr> = vec![
2770            Expr::Literal(Literal::Integer(42), span),
2771            Expr::Column(ColumnRef::bare("x"), span),
2772            Expr::BinaryOp {
2773                left: dummy(),
2774                op: BinaryOp::Add,
2775                right: dummy(),
2776                span,
2777            },
2778            Expr::UnaryOp {
2779                op: UnaryOp::Negate,
2780                expr: dummy(),
2781                span,
2782            },
2783            Expr::Between {
2784                expr: dummy(),
2785                low: dummy(),
2786                high: dummy(),
2787                not: false,
2788                span,
2789            },
2790            Expr::In {
2791                expr: dummy(),
2792                set: InSet::List(vec![]),
2793                not: false,
2794                span,
2795            },
2796            Expr::Like {
2797                expr: dummy(),
2798                pattern: dummy(),
2799                escape: None,
2800                op: LikeOp::Like,
2801                not: false,
2802                span,
2803            },
2804            Expr::Case {
2805                operand: None,
2806                whens: vec![],
2807                else_expr: None,
2808                span,
2809            },
2810            Expr::Cast {
2811                expr: dummy(),
2812                type_name: TypeName {
2813                    name: "INTEGER".to_owned(),
2814                    arg1: None,
2815                    arg2: None,
2816                },
2817                span,
2818            },
2819            Expr::Collate {
2820                expr: dummy(),
2821                collation: "NOCASE".to_owned(),
2822                span,
2823            },
2824            Expr::IsNull {
2825                expr: dummy(),
2826                not: false,
2827                span,
2828            },
2829            Expr::JsonAccess {
2830                expr: dummy(),
2831                path: dummy(),
2832                arrow: JsonArrow::Arrow,
2833                span,
2834            },
2835            Expr::RowValue(vec![], span),
2836            Expr::Placeholder(PlaceholderType::Anonymous, span),
2837        ];
2838
2839        for expr in &exprs {
2840            assert_eq!(expr.span(), span);
2841        }
2842    }
2843
2844    #[test]
2845    fn test_ast_expr_variants_subqueries_and_calls() {
2846        let span = Span::new(0, 10);
2847        let dummy = || Box::new(Expr::Literal(Literal::Null, span));
2848
2849        let empty_select = SelectStatement {
2850            with: None,
2851            body: SelectBody {
2852                select: SelectCore::Values(vec![].into()),
2853                compounds: vec![],
2854            },
2855            order_by: vec![],
2856            limit: None,
2857        };
2858
2859        let exprs: Vec<Expr> = vec![
2860            Expr::Exists {
2861                subquery: Box::new(empty_select.clone()),
2862                not: false,
2863                span,
2864            },
2865            Expr::Subquery(Box::new(empty_select), span),
2866            Expr::FunctionCall {
2867                name: "count".to_owned(),
2868                args: FunctionArgs::Star,
2869                distinct: false,
2870                order_by: vec![],
2871                filter: None,
2872                over: None,
2873                span,
2874            },
2875            Expr::Raise {
2876                action: RaiseAction::Abort,
2877                message: Some("error".to_owned()),
2878                span,
2879            },
2880            // Ensure we still cover at least one boxed expression path for spans.
2881            Expr::UnaryOp {
2882                op: UnaryOp::Negate,
2883                expr: dummy(),
2884                span,
2885            },
2886        ];
2887
2888        for expr in &exprs {
2889            assert_eq!(expr.span(), span);
2890        }
2891    }
2892
2893    #[test]
2894    fn test_ast_function_call_with_window() {
2895        let span = Span::new(0, 30);
2896        let expr = Expr::FunctionCall {
2897            name: "row_number".to_owned(),
2898            args: FunctionArgs::List(vec![]),
2899            distinct: false,
2900            order_by: vec![],
2901            filter: None,
2902            over: Some(WindowSpec {
2903                window_ref: None,
2904                partition_by: vec![Expr::Column(ColumnRef::bare("dept"), span)],
2905                order_by: vec![OrderingTerm {
2906                    expr: Expr::Column(ColumnRef::bare("salary"), span),
2907                    direction: Some(SortDirection::Desc),
2908                    nulls: None,
2909                }],
2910                frame: Some(FrameSpec {
2911                    frame_type: FrameType::Rows,
2912                    start: FrameBound::UnboundedPreceding,
2913                    end: Some(FrameBound::CurrentRow),
2914                    exclude: None,
2915                }),
2916            }),
2917            span,
2918        };
2919
2920        assert!(matches!(expr, Expr::FunctionCall { over: Some(_), .. }));
2921        if let Expr::FunctionCall {
2922            over: Some(ref win),
2923            ..
2924        } = expr
2925        {
2926            assert_eq!(win.partition_by.len(), 1);
2927            assert_eq!(win.order_by.len(), 1);
2928            assert!(win.frame.is_some());
2929        }
2930    }
2931
2932    #[test]
2933    fn test_ast_like_with_escape() {
2934        let span = Span::ZERO;
2935        let expr = Expr::Like {
2936            expr: Box::new(Expr::Column(ColumnRef::bare("name"), span)),
2937            pattern: Box::new(Expr::Literal(Literal::String("foo%".to_owned()), span)),
2938            escape: Some(Box::new(Expr::Literal(
2939                Literal::String("\\".to_owned()),
2940                span,
2941            ))),
2942            op: LikeOp::Like,
2943            not: false,
2944            span,
2945        };
2946
2947        assert!(matches!(
2948            expr,
2949            Expr::Like {
2950                escape: Some(_),
2951                ..
2952            }
2953        ));
2954        if let Expr::Like {
2955            escape: Some(ref esc),
2956            ..
2957        } = expr
2958        {
2959            assert!(matches!(esc.as_ref(), Expr::Literal(Literal::String(_), _)));
2960        }
2961    }
2962
2963    #[test]
2964    fn test_ast_json_access_arrow_types() {
2965        let span = Span::ZERO;
2966        let arrow = Expr::JsonAccess {
2967            expr: Box::new(Expr::Column(ColumnRef::bare("data"), span)),
2968            path: Box::new(Expr::Literal(Literal::String("$.name".to_owned()), span)),
2969            arrow: JsonArrow::Arrow,
2970            span,
2971        };
2972        let double_arrow = Expr::JsonAccess {
2973            expr: Box::new(Expr::Column(ColumnRef::bare("data"), span)),
2974            path: Box::new(Expr::Literal(Literal::String("$.name".to_owned()), span)),
2975            arrow: JsonArrow::DoubleArrow,
2976            span,
2977        };
2978
2979        assert!(matches!(
2980            (&arrow, &double_arrow),
2981            (
2982                Expr::JsonAccess {
2983                    arrow: JsonArrow::Arrow,
2984                    ..
2985                },
2986                Expr::JsonAccess {
2987                    arrow: JsonArrow::DoubleArrow,
2988                    ..
2989                }
2990            )
2991        ));
2992    }
2993
2994    #[test]
2995    fn test_sql_function_args_ref_preserves_shape_and_order() {
2996        let args = [
2997            Expr::Literal(Literal::Integer(10), Span::ZERO),
2998            Expr::Literal(Literal::Integer(20), Span::ZERO),
2999            Expr::Literal(Literal::Integer(30), Span::ZERO),
3000        ];
3001
3002        let star = SqlFunctionArgsRef::Star;
3003        assert_eq!(star.len(), 0);
3004        assert_eq!(star.arity_i32(), 0);
3005        assert!(star.is_empty());
3006        assert!(star.is_star());
3007        assert_eq!(star.first(), None);
3008        assert_eq!(star.iter().len(), 0);
3009        assert_eq!(star.as_list(), None);
3010        assert_eq!(star.to_owned(), FunctionArgs::Star);
3011
3012        let empty: [Expr; 0] = [];
3013        let empty_list = SqlFunctionArgsRef::List(&empty);
3014        assert!(empty_list.is_empty());
3015        assert!(!empty_list.is_star());
3016        assert_eq!(empty_list.as_list(), Some(empty.as_slice()));
3017        assert_eq!(empty_list.to_owned(), FunctionArgs::List(vec![]));
3018
3019        let list = SqlFunctionArgsRef::List(&args);
3020        assert_eq!(list.len(), 3);
3021        assert_eq!(list.arity_i32(), 3);
3022        assert_eq!(list.first(), Some(&args[0]));
3023        assert_eq!(list.get(2), Some(&args[2]));
3024        assert_eq!(list.get(3), None);
3025        assert_eq!(
3026            list.iter().collect::<Vec<_>>(),
3027            args.iter().collect::<Vec<_>>()
3028        );
3029        assert_eq!(list.as_list(), Some(args.as_slice()));
3030        assert_eq!(list.to_owned(), FunctionArgs::List(args.to_vec()));
3031
3032        let pair = SqlFunctionArgsRef::Pair(&args[0], &args[2]);
3033        assert_eq!(pair.len(), 2);
3034        assert_eq!(pair.arity_i32(), 2);
3035        assert!(!pair.is_star());
3036        assert_eq!(pair.as_list(), None);
3037        assert_eq!(pair.get(0), Some(&args[0]));
3038        assert_eq!(pair.get(1), Some(&args[2]));
3039        assert_eq!(pair.get(2), None);
3040
3041        let mut iter = pair.iter();
3042        assert_eq!(iter.len(), 2);
3043        assert_eq!(iter.next(), Some(&args[0]));
3044        assert_eq!(iter.next_back(), Some(&args[2]));
3045        assert_eq!(iter.next(), None);
3046        assert_eq!(
3047            pair.to_owned(),
3048            FunctionArgs::List(vec![args[0].clone(), args[2].clone()])
3049        );
3050    }
3051
3052    #[test]
3053    fn test_explicit_function_call_has_borrowed_semantic_view() {
3054        let span = Span::new(4, 24);
3055        let expr = Expr::FunctionCall {
3056            name: "total".to_owned(),
3057            args: FunctionArgs::List(vec![Expr::Column(ColumnRef::bare("amount"), span)]),
3058            distinct: true,
3059            order_by: vec![OrderingTerm {
3060                expr: Expr::Column(ColumnRef::bare("sequence"), span),
3061                direction: Some(SortDirection::Desc),
3062                nulls: Some(NullsOrder::Last),
3063            }],
3064            filter: Some(Box::new(Expr::Literal(Literal::Integer(1), span))),
3065            over: Some(WindowSpec {
3066                window_ref: None,
3067                partition_by: vec![],
3068                order_by: vec![],
3069                frame: None,
3070            }),
3071            span,
3072        };
3073
3074        let Some(call) = expr.as_sql_function_call() else {
3075            panic!("explicit function call must have a semantic call view");
3076        };
3077        assert_eq!(call.name, "total");
3078        assert_eq!(call.args.len(), 1);
3079        assert!(!call.args.is_star());
3080        assert!(call.distinct);
3081        assert_eq!(call.order_by.len(), 1);
3082        assert!(matches!(
3083            call.filter,
3084            Some(Expr::Literal(Literal::Integer(1), _))
3085        ));
3086        assert!(call.over.is_some());
3087
3088        let star_expr = Expr::FunctionCall {
3089            name: "count".to_owned(),
3090            args: FunctionArgs::Star,
3091            distinct: false,
3092            order_by: vec![],
3093            filter: None,
3094            over: None,
3095            span,
3096        };
3097        let Some(star_call) = star_expr.as_sql_function_call() else {
3098            panic!("star function call must have a semantic call view");
3099        };
3100        assert!(star_call.args.is_star());
3101        assert_eq!(star_call.args.len(), 0);
3102    }
3103
3104    #[test]
3105    fn test_json_access_has_borrowed_semantic_function_call_view() {
3106        for (arrow, expected_name) in [(JsonArrow::Arrow, "->"), (JsonArrow::DoubleArrow, "->>")] {
3107            assert_eq!(arrow.sql_function_name(), expected_name);
3108
3109            let expr = Expr::JsonAccess {
3110                expr: Box::new(Expr::Column(ColumnRef::bare("document"), Span::ZERO)),
3111                path: Box::new(Expr::Literal(
3112                    Literal::String("$.field".to_owned()),
3113                    Span::ZERO,
3114                )),
3115                arrow,
3116                span: Span::ZERO,
3117            };
3118
3119            let Some(call) = expr.as_sql_function_call() else {
3120                panic!("JSON access must have a semantic call view");
3121            };
3122            assert_eq!(call.name, expected_name);
3123            assert_eq!(call.args.len(), 2);
3124            assert!(!call.args.is_star());
3125            assert!(!call.distinct);
3126            assert!(call.order_by.is_empty());
3127            assert_eq!(call.filter, None);
3128            assert_eq!(call.over, None);
3129
3130            let SqlFunctionArgsRef::Pair(document, path) = call.args else {
3131                panic!("JSON access must expose its operands as a borrowed pair");
3132            };
3133            assert!(matches!(
3134                document,
3135                Expr::Column(column, _) if column.column.as_ref() == "document"
3136            ));
3137            assert!(matches!(
3138                path,
3139                Expr::Literal(Literal::String(value), _) if value == "$.field"
3140            ));
3141        }
3142
3143        let literal = Expr::Literal(Literal::Integer(1), Span::ZERO);
3144        assert!(literal.as_sql_function_call().is_none());
3145    }
3146
3147    #[test]
3148    fn test_ast_row_value() {
3149        let span = Span::ZERO;
3150        let rv = Expr::RowValue(
3151            vec![
3152                Expr::Column(ColumnRef::bare("a"), span),
3153                Expr::Column(ColumnRef::bare("b"), span),
3154                Expr::Column(ColumnRef::bare("c"), span),
3155            ],
3156            span,
3157        );
3158
3159        assert!(matches!(rv, Expr::RowValue(_, _)));
3160        if let Expr::RowValue(ref elems, _) = rv {
3161            assert_eq!(elems.len(), 3);
3162        }
3163    }
3164
3165    // --- Name resolution tests (§10.4) ---
3166
3167    fn make_scope_t1_t2() -> ResolverScope {
3168        ResolverScope::new(vec![
3169            TableSchema {
3170                name: "t1".to_owned(),
3171                alias: None,
3172                columns: vec!["a".to_owned(), "b".to_owned()],
3173            },
3174            TableSchema {
3175                name: "t2".to_owned(),
3176                alias: None,
3177                columns: vec!["c".to_owned(), "d".to_owned()],
3178            },
3179        ])
3180    }
3181
3182    #[test]
3183    fn test_resolve_unambiguous_column() {
3184        let scope = make_scope_t1_t2();
3185        let result = scope
3186            .resolve(&ColumnRef::bare("a"), Span::ZERO)
3187            .expect("should resolve");
3188        assert_eq!(result.table_name, "t1");
3189        assert_eq!(result.column_name, "a");
3190        assert_eq!(result.table_idx, 0);
3191        assert_eq!(result.column_idx, 0);
3192    }
3193
3194    #[test]
3195    fn test_resolve_ambiguous_column_error() {
3196        let scope = ResolverScope::new(vec![
3197            TableSchema {
3198                name: "t1".to_owned(),
3199                alias: None,
3200                columns: vec!["x".to_owned(), "y".to_owned()],
3201            },
3202            TableSchema {
3203                name: "t2".to_owned(),
3204                alias: None,
3205                columns: vec!["x".to_owned(), "z".to_owned()],
3206            },
3207        ]);
3208
3209        let err = scope
3210            .resolve(&ColumnRef::bare("x"), Span::ZERO)
3211            .unwrap_err();
3212        assert!(matches!(err, ResolveError::AmbiguousColumn { .. }));
3213        if let ResolveError::AmbiguousColumn {
3214            column, candidates, ..
3215        } = err
3216        {
3217            assert_eq!(column, "x");
3218            assert_eq!(candidates, vec!["t1", "t2"]);
3219        }
3220    }
3221
3222    #[test]
3223    fn test_resolve_qualified_column() {
3224        let scope = make_scope_t1_t2();
3225
3226        let result = scope
3227            .resolve(&ColumnRef::qualified("t1", "a"), Span::ZERO)
3228            .expect("should resolve");
3229        assert_eq!(result.table_name, "t1");
3230        assert_eq!(result.column_name, "a");
3231
3232        let err = scope
3233            .resolve(&ColumnRef::qualified("t1", "nonexistent"), Span::ZERO)
3234            .unwrap_err();
3235        assert!(matches!(err, ResolveError::NoSuchColumn { .. }));
3236    }
3237
3238    #[test]
3239    fn test_resolve_alias_binding() {
3240        let scope = ResolverScope::new(vec![TableSchema {
3241            name: "users".to_owned(),
3242            alias: Some("u".to_owned()),
3243            columns: vec!["id".to_owned(), "name".to_owned()],
3244        }]);
3245
3246        let result = scope
3247            .resolve(&ColumnRef::qualified("u", "name"), Span::ZERO)
3248            .expect("should resolve via alias");
3249        assert_eq!(result.table_name, "u");
3250        assert_eq!(result.column_name, "name");
3251    }
3252
3253    #[test]
3254    fn test_resolve_star_expansion() {
3255        let scope = make_scope_t1_t2();
3256        let expanded = scope.expand_star();
3257        assert_eq!(
3258            expanded,
3259            vec![
3260                ("t1".to_owned(), "a".to_owned()),
3261                ("t1".to_owned(), "b".to_owned()),
3262                ("t2".to_owned(), "c".to_owned()),
3263                ("t2".to_owned(), "d".to_owned()),
3264            ]
3265        );
3266    }
3267
3268    #[test]
3269    fn test_resolve_qualified_star() {
3270        let scope = make_scope_t1_t2();
3271        let expanded = scope.expand_table_star("t1", Span::ZERO).unwrap();
3272        assert_eq!(
3273            expanded,
3274            vec![
3275                ("t1".to_owned(), "a".to_owned()),
3276                ("t1".to_owned(), "b".to_owned()),
3277            ]
3278        );
3279    }
3280
3281    #[test]
3282    fn test_resolve_subquery_scope() {
3283        // Outer scope has t1(a, b), inner scope has t2(c, d).
3284        // Inner should be able to resolve t1.a from outer.
3285        let outer = ResolverScope::new(vec![TableSchema {
3286            name: "t1".to_owned(),
3287            alias: None,
3288            columns: vec!["a".to_owned(), "b".to_owned()],
3289        }]);
3290
3291        let inner = outer.child(vec![TableSchema {
3292            name: "t2".to_owned(),
3293            alias: None,
3294            columns: vec!["c".to_owned(), "d".to_owned()],
3295        }]);
3296
3297        // Inner can resolve t2.c directly.
3298        let result = inner
3299            .resolve(&ColumnRef::qualified("t2", "c"), Span::ZERO)
3300            .expect("inner table");
3301        assert_eq!(result.table_name, "t2");
3302
3303        // Inner can resolve t1.a from outer scope (correlated).
3304        let result = inner
3305            .resolve(&ColumnRef::qualified("t1", "a"), Span::ZERO)
3306            .expect("correlated outer reference");
3307        assert_eq!(result.table_name, "t1");
3308        assert_eq!(result.column_name, "a");
3309    }
3310
3311    #[test]
3312    fn test_resolve_scope_shadowing() {
3313        // Inner scope has t1 that shadows outer t1.
3314        let outer = ResolverScope::new(vec![TableSchema {
3315            name: "t1".to_owned(),
3316            alias: None,
3317            columns: vec!["outer_col".to_owned()],
3318        }]);
3319
3320        let inner = outer.child(vec![TableSchema {
3321            name: "t1".to_owned(),
3322            alias: None,
3323            columns: vec!["inner_col".to_owned()],
3324        }]);
3325
3326        // Resolving t1.inner_col should find the inner scope's t1.
3327        let result = inner
3328            .resolve(&ColumnRef::qualified("t1", "inner_col"), Span::ZERO)
3329            .expect("inner shadows outer");
3330        assert_eq!(result.column_name, "inner_col");
3331
3332        // Resolving t1.outer_col should fail because inner t1 shadows.
3333        let err = inner
3334            .resolve(&ColumnRef::qualified("t1", "outer_col"), Span::ZERO)
3335            .unwrap_err();
3336        assert!(matches!(err, ResolveError::NoSuchColumn { .. }));
3337    }
3338
3339    #[test]
3340    fn test_resolve_nonexistent_table_error() {
3341        let scope = make_scope_t1_t2();
3342        let err = scope
3343            .resolve(&ColumnRef::qualified("nonexistent", "a"), Span::ZERO)
3344            .unwrap_err();
3345        assert!(matches!(err, ResolveError::NoSuchTable { .. }));
3346    }
3347
3348    #[test]
3349    fn test_resolve_unqualified_column_not_found() {
3350        let scope = make_scope_t1_t2();
3351        let err = scope
3352            .resolve(&ColumnRef::bare("nonexistent"), Span::ZERO)
3353            .unwrap_err();
3354        assert!(matches!(err, ResolveError::ColumnNotFound { .. }));
3355        if let ResolveError::ColumnNotFound { column, .. } = err {
3356            assert_eq!(column, "nonexistent");
3357        }
3358    }
3359
3360    #[test]
3361    fn test_resolve_column_in_order_by() {
3362        // Test that an alias resolves when used as a virtual column.
3363        let scope = ResolverScope::new(vec![TableSchema {
3364            name: "result".to_owned(),
3365            alias: None,
3366            columns: vec!["total".to_owned()],
3367        }]);
3368
3369        let result = scope
3370            .resolve(&ColumnRef::bare("total"), Span::ZERO)
3371            .expect("order by alias");
3372        assert_eq!(result.column_name, "total");
3373    }
3374
3375    // --- Span tests ---
3376
3377    #[test]
3378    fn test_span_merge() {
3379        let a = Span::new(5, 10);
3380        let b = Span::new(15, 20);
3381        let merged = a.merge(b);
3382        assert_eq!(merged.start, 5);
3383        assert_eq!(merged.end, 20);
3384    }
3385
3386    #[test]
3387    fn test_span_len_is_empty() {
3388        let s = Span::new(10, 20);
3389        assert_eq!(s.len(), 10);
3390        assert!(!s.is_empty());
3391
3392        assert!(Span::ZERO.is_empty());
3393    }
3394
3395    // --- QualifiedName tests ---
3396
3397    #[test]
3398    fn test_qualified_name_display() {
3399        let bare = QualifiedName::bare("users");
3400        assert_eq!(bare.to_string(), "users");
3401
3402        let qual = QualifiedName::qualified("main", "users");
3403        assert_eq!(qual.to_string(), "main.users");
3404
3405        let keyword = QualifiedName::bare("order");
3406        assert_eq!(keyword.to_string(), "\"order\"");
3407
3408        let qualified_keyword = QualifiedName::qualified("main", "group");
3409        assert_eq!(qualified_keyword.to_string(), "main.\"group\"");
3410
3411        let keyword_schema = QualifiedName::qualified("order", "group");
3412        assert_eq!(keyword_schema.to_string(), "\"order\".\"group\"");
3413    }
3414
3415    // --- Operator display tests ---
3416
3417    #[test]
3418    fn test_binary_op_display() {
3419        assert_eq!(BinaryOp::Add.to_string(), "+");
3420        assert_eq!(BinaryOp::Concat.to_string(), "||");
3421        assert_eq!(BinaryOp::And.to_string(), "AND");
3422        assert_eq!(BinaryOp::IsNot.to_string(), "IS NOT");
3423    }
3424
3425    #[test]
3426    fn test_unary_op_display() {
3427        assert_eq!(UnaryOp::Negate.to_string(), "-");
3428        assert_eq!(UnaryOp::Not.to_string(), "NOT");
3429    }
3430
3431    // --- Additional coverage tests ---
3432
3433    #[test]
3434    fn test_unary_op_display_all_variants() {
3435        assert_eq!(UnaryOp::Plus.to_string(), "+");
3436        assert_eq!(UnaryOp::BitNot.to_string(), "~");
3437    }
3438
3439    #[test]
3440    fn test_binary_op_display_all_variants() {
3441        assert_eq!(BinaryOp::Subtract.to_string(), "-");
3442        assert_eq!(BinaryOp::Multiply.to_string(), "*");
3443        assert_eq!(BinaryOp::Divide.to_string(), "/");
3444        assert_eq!(BinaryOp::Modulo.to_string(), "%");
3445        assert_eq!(BinaryOp::Eq.to_string(), "=");
3446        assert_eq!(BinaryOp::Ne.to_string(), "!=");
3447        assert_eq!(BinaryOp::Lt.to_string(), "<");
3448        assert_eq!(BinaryOp::Le.to_string(), "<=");
3449        assert_eq!(BinaryOp::Gt.to_string(), ">");
3450        assert_eq!(BinaryOp::Ge.to_string(), ">=");
3451        assert_eq!(BinaryOp::Is.to_string(), "IS");
3452        assert_eq!(BinaryOp::Or.to_string(), "OR");
3453        assert_eq!(BinaryOp::BitAnd.to_string(), "&");
3454        assert_eq!(BinaryOp::BitOr.to_string(), "|");
3455        assert_eq!(BinaryOp::ShiftLeft.to_string(), "<<");
3456        assert_eq!(BinaryOp::ShiftRight.to_string(), ">>");
3457    }
3458
3459    #[test]
3460    fn test_span_debug_format() {
3461        let s = Span::new(10, 25);
3462        assert_eq!(format!("{s:?}"), "10..25");
3463    }
3464
3465    #[test]
3466    fn test_span_display_format() {
3467        let s = Span::new(0, 42);
3468        assert_eq!(format!("{s}"), "0..42");
3469    }
3470
3471    #[test]
3472    fn test_span_zero_properties() {
3473        assert_eq!(Span::ZERO.start, 0);
3474        assert_eq!(Span::ZERO.end, 0);
3475        assert_eq!(Span::ZERO.len(), 0);
3476        assert!(Span::ZERO.is_empty());
3477    }
3478
3479    #[test]
3480    fn test_span_merge_overlapping() {
3481        let a = Span::new(5, 15);
3482        let b = Span::new(10, 20);
3483        let merged = a.merge(b);
3484        assert_eq!(merged.start, 5);
3485        assert_eq!(merged.end, 20);
3486    }
3487
3488    #[test]
3489    fn test_span_merge_reversed_order() {
3490        let a = Span::new(20, 30);
3491        let b = Span::new(5, 10);
3492        let merged = a.merge(b);
3493        assert_eq!(merged.start, 5);
3494        assert_eq!(merged.end, 30);
3495    }
3496
3497    #[test]
3498    fn test_table_schema_effective_name_with_alias() {
3499        let schema = TableSchema {
3500            name: "users".to_owned(),
3501            alias: Some("u".to_owned()),
3502            columns: vec!["id".to_owned()],
3503        };
3504        assert_eq!(schema.effective_name(), "u");
3505    }
3506
3507    #[test]
3508    fn test_table_schema_effective_name_without_alias() {
3509        let schema = TableSchema {
3510            name: "users".to_owned(),
3511            alias: None,
3512            columns: vec!["id".to_owned()],
3513        };
3514        assert_eq!(schema.effective_name(), "users");
3515    }
3516
3517    #[test]
3518    fn test_resolve_case_insensitive_table() {
3519        let scope = ResolverScope::new(vec![TableSchema {
3520            name: "Users".to_owned(),
3521            alias: None,
3522            columns: vec!["Id".to_owned(), "Name".to_owned()],
3523        }]);
3524
3525        // Qualified lookup with different case should work.
3526        let result = scope
3527            .resolve(&ColumnRef::qualified("users", "id"), Span::ZERO)
3528            .expect("case-insensitive table match");
3529        assert_eq!(result.table_name, "Users");
3530        assert_eq!(result.column_name, "Id");
3531    }
3532
3533    #[test]
3534    fn test_resolve_case_insensitive_unqualified() {
3535        let scope = ResolverScope::new(vec![TableSchema {
3536            name: "T".to_owned(),
3537            alias: None,
3538            columns: vec!["COL_A".to_owned()],
3539        }]);
3540
3541        let result = scope
3542            .resolve(&ColumnRef::bare("col_a"), Span::ZERO)
3543            .expect("case-insensitive unqualified match");
3544        assert_eq!(result.column_name, "COL_A");
3545    }
3546
3547    #[test]
3548    fn test_expand_table_star_nonexistent() {
3549        let scope = make_scope_t1_t2();
3550        let err = scope
3551            .expand_table_star("nonexistent", Span::ZERO)
3552            .unwrap_err();
3553        assert!(matches!(err, ResolveError::NoSuchTable { .. }));
3554    }
3555
3556    #[test]
3557    fn test_resolve_error_display_no_such_table() {
3558        let err = ResolveError::NoSuchTable {
3559            name: "foo".to_owned(),
3560            span: Span::new(5, 8),
3561        };
3562        assert_eq!(err.to_string(), "no such table: foo at 5..8");
3563    }
3564
3565    #[test]
3566    fn test_resolve_error_display_no_such_column() {
3567        let err = ResolveError::NoSuchColumn {
3568            table: "t1".to_owned(),
3569            column: "bar".to_owned(),
3570            span: Span::new(10, 16),
3571        };
3572        assert_eq!(err.to_string(), "no such column: t1.bar at 10..16");
3573    }
3574
3575    #[test]
3576    fn test_resolve_error_display_ambiguous() {
3577        let err = ResolveError::AmbiguousColumn {
3578            column: "id".to_owned(),
3579            candidates: vec!["users".to_owned(), "orders".to_owned()],
3580            span: Span::ZERO,
3581        };
3582        let msg = err.to_string();
3583        assert!(msg.contains("ambiguous column name: id"));
3584        assert!(msg.contains("users, orders"));
3585    }
3586
3587    #[test]
3588    fn test_resolve_error_display_column_not_found() {
3589        let err = ResolveError::ColumnNotFound {
3590            column: "xyz".to_owned(),
3591            span: Span::new(0, 3),
3592        };
3593        assert_eq!(err.to_string(), "no such column: xyz at 0..3");
3594    }
3595
3596    #[test]
3597    fn test_resolve_error_display_no_outer_table() {
3598        let err = ResolveError::NoOuterTable {
3599            name: "outer_t".to_owned(),
3600            span: Span::new(1, 8),
3601        };
3602        assert_eq!(
3603            err.to_string(),
3604            "no such table in outer scope: outer_t at 1..8"
3605        );
3606    }
3607
3608    #[test]
3609    fn test_resolve_error_is_std_error() {
3610        let err: Box<dyn std::error::Error> = Box::new(ResolveError::ColumnNotFound {
3611            column: "x".to_owned(),
3612            span: Span::ZERO,
3613        });
3614        // Verify it implements std::error::Error
3615        assert!(!err.to_string().is_empty());
3616    }
3617
3618    #[test]
3619    fn test_resolve_unqualified_from_parent_scope() {
3620        let outer = ResolverScope::new(vec![TableSchema {
3621            name: "outer_t".to_owned(),
3622            alias: None,
3623            columns: vec!["outer_col".to_owned()],
3624        }]);
3625        let inner = outer.child(vec![TableSchema {
3626            name: "inner_t".to_owned(),
3627            alias: None,
3628            columns: vec!["inner_col".to_owned()],
3629        }]);
3630
3631        // Unqualified column in inner scope falls through to parent.
3632        let result = inner
3633            .resolve(&ColumnRef::bare("outer_col"), Span::ZERO)
3634            .expect("correlated unqualified from parent");
3635        assert_eq!(result.table_name, "outer_t");
3636        assert_eq!(result.column_name, "outer_col");
3637    }
3638
3639    #[test]
3640    fn test_distinctness_default_is_all() {
3641        assert_eq!(Distinctness::default(), Distinctness::All);
3642    }
3643
3644    #[test]
3645    fn test_transaction_mode_concurrent() {
3646        let begin = BeginStatement {
3647            mode: Some(TransactionMode::Concurrent),
3648        };
3649        assert_eq!(begin.mode, Some(TransactionMode::Concurrent));
3650    }
3651
3652    #[test]
3653    fn test_transaction_mode_all_variants() {
3654        let modes = [
3655            TransactionMode::Deferred,
3656            TransactionMode::Immediate,
3657            TransactionMode::Exclusive,
3658            TransactionMode::Concurrent,
3659        ];
3660        // Verify all are distinct.
3661        for (i, a) in modes.iter().enumerate() {
3662            for (j, b) in modes.iter().enumerate() {
3663                assert_eq!(i == j, a == b, "modes {i} and {j} distinctness");
3664            }
3665        }
3666    }
3667
3668    #[test]
3669    fn test_conflict_action_all_variants() {
3670        let actions = [
3671            ConflictAction::Rollback,
3672            ConflictAction::Abort,
3673            ConflictAction::Fail,
3674            ConflictAction::Ignore,
3675            ConflictAction::Replace,
3676        ];
3677        assert_eq!(actions.len(), 5);
3678        for (i, a) in actions.iter().enumerate() {
3679            for (j, b) in actions.iter().enumerate() {
3680                assert_eq!(i == j, a == b);
3681            }
3682        }
3683    }
3684
3685    #[test]
3686    fn test_compound_op_all_variants() {
3687        let ops = [
3688            CompoundOp::Union,
3689            CompoundOp::UnionAll,
3690            CompoundOp::Intersect,
3691            CompoundOp::Except,
3692        ];
3693        assert_eq!(ops.len(), 4);
3694        assert_ne!(CompoundOp::Union, CompoundOp::UnionAll);
3695    }
3696
3697    #[test]
3698    fn test_drop_object_type_variants() {
3699        let types = [
3700            DropObjectType::Table,
3701            DropObjectType::View,
3702            DropObjectType::Index,
3703            DropObjectType::Trigger,
3704        ];
3705        assert_eq!(types.len(), 4);
3706        assert_ne!(DropObjectType::Table, DropObjectType::View);
3707    }
3708
3709    #[test]
3710    fn test_like_op_variants() {
3711        let ops = [LikeOp::Like, LikeOp::Glob, LikeOp::Match, LikeOp::Regexp];
3712        assert_eq!(ops.len(), 4);
3713        assert_ne!(LikeOp::Like, LikeOp::Glob);
3714    }
3715
3716    #[test]
3717    fn test_placeholder_type_variants() {
3718        let _ = PlaceholderType::Anonymous;
3719        let _ = PlaceholderType::Numbered(1);
3720        let _ = PlaceholderType::ColonNamed("param".to_owned());
3721        let _ = PlaceholderType::AtNamed("param".to_owned());
3722        let _ = PlaceholderType::DollarNamed("param".to_owned());
3723        assert_ne!(PlaceholderType::Anonymous, PlaceholderType::Numbered(1));
3724        // Named variants with different prefixes differ.
3725        assert_ne!(
3726            PlaceholderType::ColonNamed("a".to_owned()),
3727            PlaceholderType::AtNamed("a".to_owned()),
3728        );
3729    }
3730
3731    #[test]
3732    fn test_raise_action_variants() {
3733        let actions = [
3734            RaiseAction::Ignore,
3735            RaiseAction::Rollback,
3736            RaiseAction::Abort,
3737            RaiseAction::Fail,
3738        ];
3739        assert_eq!(actions.len(), 4);
3740        assert_ne!(RaiseAction::Ignore, RaiseAction::Rollback);
3741    }
3742
3743    #[test]
3744    fn test_trigger_timing_variants() {
3745        let timings = [
3746            TriggerTiming::Before,
3747            TriggerTiming::After,
3748            TriggerTiming::InsteadOf,
3749        ];
3750        assert_eq!(timings.len(), 3);
3751        assert_ne!(TriggerTiming::Before, TriggerTiming::After);
3752    }
3753
3754    #[test]
3755    fn test_trigger_event_update_with_columns() {
3756        let ev = TriggerEvent::Update(vec!["col1".to_owned(), "col2".to_owned()]);
3757        assert!(matches!(ev, TriggerEvent::Update(ref cols) if cols.len() == 2));
3758        assert_ne!(TriggerEvent::Insert, TriggerEvent::Delete);
3759    }
3760
3761    #[test]
3762    fn test_frame_type_variants() {
3763        let types = [FrameType::Rows, FrameType::Range, FrameType::Groups];
3764        assert_eq!(types.len(), 3);
3765        assert_ne!(FrameType::Rows, FrameType::Groups);
3766    }
3767
3768    #[test]
3769    fn test_frame_exclude_variants() {
3770        let excludes = [
3771            FrameExclude::NoOthers,
3772            FrameExclude::CurrentRow,
3773            FrameExclude::Group,
3774            FrameExclude::Ties,
3775        ];
3776        assert_eq!(excludes.len(), 4);
3777    }
3778
3779    #[test]
3780    fn test_sort_direction_and_nulls_order() {
3781        assert_ne!(SortDirection::Asc, SortDirection::Desc);
3782        assert_ne!(NullsOrder::First, NullsOrder::Last);
3783    }
3784
3785    #[test]
3786    fn test_generated_storage_variants() {
3787        assert_ne!(GeneratedStorage::Stored, GeneratedStorage::Virtual);
3788    }
3789
3790    #[test]
3791    fn test_cte_materialized_variants() {
3792        assert_ne!(
3793            CteMaterialized::Materialized,
3794            CteMaterialized::NotMaterialized
3795        );
3796    }
3797
3798    #[test]
3799    fn test_in_set_table_variant() {
3800        let set = InSet::Table(QualifiedName::bare("lookup"));
3801        assert!(matches!(set, InSet::Table(ref n) if n.name == "lookup"));
3802    }
3803
3804    #[test]
3805    fn test_function_args_star_vs_list() {
3806        let star = FunctionArgs::Star;
3807        let list = FunctionArgs::List(vec![]);
3808        assert_ne!(star, list);
3809    }
3810
3811    #[test]
3812    fn test_insert_source_default_values() {
3813        let src = InsertSource::DefaultValues;
3814        assert!(matches!(src, InsertSource::DefaultValues));
3815        assert_ne!(InsertSource::DefaultValues, InsertSource::Values(vec![]),);
3816    }
3817
3818    #[test]
3819    fn test_pragma_value_variants() {
3820        let span = Span::ZERO;
3821        let assign = PragmaValue::Assign(Expr::Literal(Literal::Integer(100), span));
3822        let call = PragmaValue::Call(Expr::Literal(Literal::Integer(100), span));
3823        assert_ne!(assign, call);
3824    }
3825
3826    #[test]
3827    fn test_column_ref_constructors() {
3828        let bare = ColumnRef::bare("col");
3829        assert!(bare.table.is_none());
3830        assert_eq!(bare.column.as_ref(), "col");
3831
3832        let qual = ColumnRef::qualified("tbl", "col");
3833        assert_eq!(qual.table.as_deref(), Some("tbl"));
3834        assert_eq!(qual.column.as_ref(), "col");
3835    }
3836
3837    #[test]
3838    fn test_qualified_name_constructors() {
3839        let bare = QualifiedName::bare("t");
3840        assert!(bare.schema.is_none());
3841        assert_eq!(bare.name, "t");
3842
3843        let qual = QualifiedName::qualified("main", "t");
3844        assert_eq!(qual.schema.as_deref(), Some("main"));
3845        assert_eq!(qual.name, "t");
3846    }
3847
3848    #[test]
3849    fn test_deferrable_initially_variants() {
3850        let deferred = Deferrable {
3851            not: false,
3852            initially: Some(DeferrableInitially::Deferred),
3853        };
3854        let immediate = Deferrable {
3855            not: false,
3856            initially: Some(DeferrableInitially::Immediate),
3857        };
3858        assert_ne!(deferred, immediate);
3859
3860        let not_deferrable = Deferrable {
3861            not: true,
3862            initially: None,
3863        };
3864        assert_ne!(deferred, not_deferrable);
3865    }
3866
3867    #[test]
3868    fn test_foreign_key_action_types() {
3869        let types = [
3870            ForeignKeyActionType::SetNull,
3871            ForeignKeyActionType::SetDefault,
3872            ForeignKeyActionType::Cascade,
3873            ForeignKeyActionType::Restrict,
3874            ForeignKeyActionType::NoAction,
3875        ];
3876        assert_eq!(types.len(), 5);
3877        assert_ne!(
3878            ForeignKeyActionType::Cascade,
3879            ForeignKeyActionType::Restrict
3880        );
3881    }
3882
3883    #[test]
3884    fn test_foreign_key_trigger_variants() {
3885        assert_ne!(ForeignKeyTrigger::OnDelete, ForeignKeyTrigger::OnUpdate);
3886    }
3887
3888    #[test]
3889    fn test_index_hint_variants() {
3890        let indexed = IndexHint::IndexedBy("idx_name".to_owned());
3891        let not_indexed = IndexHint::NotIndexed;
3892        assert_ne!(indexed, not_indexed);
3893    }
3894
3895    #[test]
3896    fn test_join_kind_all_variants() {
3897        let kinds = [
3898            JoinKind::Cross,
3899            JoinKind::Inner,
3900            JoinKind::Left,
3901            JoinKind::Right,
3902            JoinKind::Full,
3903        ];
3904        assert_eq!(kinds.len(), 5);
3905        assert_ne!(JoinKind::Left, JoinKind::Right);
3906    }
3907
3908    #[test]
3909    fn test_join_type_natural_flag() {
3910        let natural_inner = JoinType {
3911            natural: true,
3912            kind: JoinKind::Inner,
3913        };
3914        let regular_inner = JoinType {
3915            natural: false,
3916            kind: JoinKind::Inner,
3917        };
3918        assert_ne!(natural_inner, regular_inner);
3919    }
3920
3921    #[test]
3922    fn test_alter_table_all_actions() {
3923        let rename = AlterTableAction::RenameTo("new_name".to_owned());
3924        let rename_col = AlterTableAction::RenameColumn {
3925            old: "old_col".to_owned(),
3926            new: "new_col".to_owned(),
3927        };
3928        let add_col = AlterTableAction::AddColumn(ColumnDef {
3929            name: "new_col".to_owned(),
3930            type_name: Some(TypeName {
3931                name: "INTEGER".to_owned(),
3932                arg1: None,
3933                arg2: None,
3934            }),
3935            constraints: vec![],
3936        });
3937        let drop_col = AlterTableAction::DropColumn("old_col".to_owned());
3938
3939        // All four variants are distinct.
3940        assert_ne!(rename, rename_col);
3941        assert_ne!(add_col, drop_col);
3942    }
3943
3944    #[test]
3945    #[allow(clippy::approx_constant)]
3946    fn test_literal_all_variants() {
3947        let _ = Literal::Integer(42);
3948        let _ = Literal::Float(3.14);
3949        let _ = Literal::String("hello".to_owned());
3950        let _ = Literal::Blob(vec![0xDE, 0xAD]);
3951        let _ = Literal::Null;
3952        let _ = Literal::True;
3953        let _ = Literal::False;
3954        let _ = Literal::CurrentTime;
3955        let _ = Literal::CurrentDate;
3956        let _ = Literal::CurrentTimestamp;
3957        assert_ne!(Literal::True, Literal::False);
3958        assert_ne!(Literal::CurrentTime, Literal::CurrentDate);
3959    }
3960
3961    #[test]
3962    fn test_json_arrow_variants() {
3963        assert_ne!(JsonArrow::Arrow, JsonArrow::DoubleArrow);
3964    }
3965
3966    #[test]
3967    fn test_upsert_action_nothing_vs_update() {
3968        let nothing = UpsertAction::Nothing;
3969        let update = UpsertAction::Update {
3970            assignments: vec![],
3971            where_clause: None,
3972        };
3973        assert_ne!(nothing, update);
3974    }
3975
3976    #[test]
3977    fn test_assignment_target_variants() {
3978        let single = AssignmentTarget::Column("col".to_owned());
3979        let multi = AssignmentTarget::ColumnList(vec!["a".to_owned(), "b".to_owned()]);
3980        assert_ne!(single, multi);
3981    }
3982
3983    #[test]
3984    fn test_type_name_with_args() {
3985        let simple = TypeName {
3986            name: "INTEGER".to_owned(),
3987            arg1: None,
3988            arg2: None,
3989        };
3990        let varchar = TypeName {
3991            name: "VARCHAR".to_owned(),
3992            arg1: Some("255".to_owned()),
3993            arg2: None,
3994        };
3995        let decimal = TypeName {
3996            name: "DECIMAL".to_owned(),
3997            arg1: Some("10".to_owned()),
3998            arg2: Some("2".to_owned()),
3999        };
4000        assert_ne!(simple, varchar);
4001        assert_ne!(varchar, decimal);
4002    }
4003
4004    #[test]
4005    fn test_frame_bound_variants() {
4006        let span = Span::ZERO;
4007        let _ = FrameBound::UnboundedPreceding;
4008        let _ = FrameBound::Preceding(Box::new(Expr::Literal(Literal::Integer(1), span)));
4009        let _ = FrameBound::CurrentRow;
4010        let _ = FrameBound::Following(Box::new(Expr::Literal(Literal::Integer(1), span)));
4011        let _ = FrameBound::UnboundedFollowing;
4012        assert_ne!(FrameBound::UnboundedPreceding, FrameBound::CurrentRow);
4013    }
4014
4015    #[test]
4016    fn test_result_column_variants() {
4017        let span = Span::ZERO;
4018        let star = ResultColumn::Star;
4019        let table_star = ResultColumn::TableStar(QualifiedName::bare("t1"));
4020        let expr = ResultColumn::Expr {
4021            expr: Expr::Literal(Literal::Integer(1), span),
4022            alias: Some("one".to_owned()),
4023        };
4024        assert_ne!(star, table_star);
4025        assert!(matches!(expr, ResultColumn::Expr { alias: Some(_), .. }));
4026    }
4027
4028    #[test]
4029    fn test_expr_eq_ignores_span() {
4030        // Two identical literal expressions at different source positions
4031        // must compare equal (spans are ignored).
4032        let a = Expr::Literal(Literal::Integer(42), Span::new(0, 2));
4033        let b = Expr::Literal(Literal::Integer(42), Span::new(100, 102));
4034        assert_eq!(a, b);
4035
4036        // Same structure, different spans, nested binary op.
4037        let c = Expr::BinaryOp {
4038            left: Box::new(Expr::Column(ColumnRef::bare("x"), Span::new(0, 1))),
4039            op: BinaryOp::Gt,
4040            right: Box::new(Expr::Literal(Literal::Integer(0), Span::new(4, 5))),
4041            span: Span::new(0, 5),
4042        };
4043        let d = Expr::BinaryOp {
4044            left: Box::new(Expr::Column(ColumnRef::bare("x"), Span::new(50, 51))),
4045            op: BinaryOp::Gt,
4046            right: Box::new(Expr::Literal(Literal::Integer(0), Span::new(55, 56))),
4047            span: Span::new(50, 56),
4048        };
4049        assert_eq!(c, d);
4050
4051        // Different values must still be unequal.
4052        let e = Expr::Literal(Literal::Integer(99), Span::ZERO);
4053        assert_ne!(a, e);
4054    }
4055
4056    #[test]
4057    fn test_bound_outer_value_equality_preserves_storage_and_metadata() {
4058        let bound = |value, collation, affinity, span| Expr::BoundOuterValue {
4059            value,
4060            collation,
4061            affinity,
4062            span,
4063        };
4064
4065        let integer = bound(
4066            SqliteValue::Integer(42),
4067            BoundCollation::Binary,
4068            Some(TypeAffinity::Integer),
4069            Span::new(0, 2),
4070        );
4071        let same_semantics = bound(
4072            SqliteValue::Integer(42),
4073            BoundCollation::Named("binary".to_owned()),
4074            Some(TypeAffinity::Integer),
4075            Span::new(100, 102),
4076        );
4077        assert_eq!(integer.span(), Span::new(0, 2));
4078        assert_eq!(integer, same_semantics);
4079
4080        let real = bound(
4081            SqliteValue::Float(42.0),
4082            BoundCollation::Binary,
4083            Some(TypeAffinity::Integer),
4084            Span::ZERO,
4085        );
4086        assert_ne!(integer, real, "INTEGER and REAL storage must stay distinct");
4087
4088        let nocase = bound(
4089            SqliteValue::Integer(42),
4090            BoundCollation::Named("NOCASE".to_owned()),
4091            Some(TypeAffinity::Integer),
4092            Span::ZERO,
4093        );
4094        assert_ne!(integer, nocase);
4095
4096        let unspecified = bound(
4097            SqliteValue::Integer(42),
4098            BoundCollation::Unspecified,
4099            Some(TypeAffinity::Integer),
4100            Span::ZERO,
4101        );
4102        assert_ne!(integer, unspecified);
4103        assert_eq!(BoundCollation::Unspecified.as_name(), None);
4104        assert_eq!(BoundCollation::Binary.as_name(), Some("BINARY"));
4105
4106        let numeric = bound(
4107            SqliteValue::Integer(42),
4108            BoundCollation::Binary,
4109            Some(TypeAffinity::Numeric),
4110            Span::ZERO,
4111        );
4112        assert_ne!(integer, numeric);
4113    }
4114}