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