Skip to main content

citadel_sql/
parser.rs

1//! SQL parser: converts SQL strings into the internal AST.
2
3use sqlparser::ast as sp;
4
5use crate::error::{Result, SqlError};
6use crate::types::{DataType, Value};
7
8#[derive(Debug, Clone)]
9pub enum Statement {
10    CreateTable(CreateTableStmt),
11    DropTable(DropTableStmt),
12    CreateIndex(CreateIndexStmt),
13    DropIndex(DropIndexStmt),
14    CreateView(CreateViewStmt),
15    DropView(DropViewStmt),
16    CreateMaterializedView(Box<CreateMatviewStmt>),
17    RefreshMaterializedView(RefreshMatviewStmt),
18    DropMaterializedView(DropMatviewStmt),
19    CreateTrigger(Box<CreateTriggerStmt>),
20    DropTrigger(DropTriggerStmt),
21    AlterTable(Box<AlterTableStmt>),
22    Insert(InsertStmt),
23    Select(Box<SelectQuery>),
24    Update(UpdateStmt),
25    Delete(DeleteStmt),
26    Truncate(TruncateStmt),
27    Begin { access_mode: BeginAccessMode },
28    Commit,
29    Rollback,
30    Savepoint(String),
31    ReleaseSavepoint(String),
32    RollbackTo(String),
33    SetTimezone(String),
34    Explain(Box<Statement>),
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum BeginAccessMode {
39    Default,
40    ReadWrite,
41    ReadOnly,
42}
43
44#[derive(Debug, Clone)]
45pub struct AlterTableStmt {
46    pub table: String,
47    pub op: AlterTableOp,
48}
49
50#[derive(Debug, Clone)]
51pub enum AlterTableOp {
52    AddColumn {
53        column: Box<ColumnSpec>,
54        foreign_key: Option<ForeignKeyDef>,
55        if_not_exists: bool,
56    },
57    DropColumn {
58        name: String,
59        if_exists: bool,
60    },
61    RenameColumn {
62        old_name: String,
63        new_name: String,
64    },
65    RenameTable {
66        new_name: String,
67    },
68    DisableTrigger {
69        name: String,
70    },
71    EnableTrigger {
72        name: String,
73    },
74    DisableAllTriggers,
75    EnableAllTriggers,
76}
77
78#[derive(Debug, Clone)]
79pub struct CreateTableStmt {
80    pub name: String,
81    pub columns: Vec<ColumnSpec>,
82    pub primary_key: Vec<String>,
83    pub if_not_exists: bool,
84    pub check_constraints: Vec<TableCheckConstraint>,
85    pub foreign_keys: Vec<ForeignKeyDef>,
86    pub unique_indices: Vec<UniqueIndexDef>,
87    pub strict: bool,
88    pub temporary: bool,
89}
90
91#[derive(Debug, Clone)]
92pub struct UniqueIndexDef {
93    pub name: Option<String>,
94    pub columns: Vec<String>,
95}
96
97#[derive(Debug, Clone)]
98pub struct TableCheckConstraint {
99    pub name: Option<String>,
100    pub expr: Expr,
101    pub sql: String,
102}
103
104#[derive(Debug, Clone)]
105pub struct ForeignKeyDef {
106    pub name: Option<String>,
107    pub columns: Vec<String>,
108    pub foreign_table: String,
109    pub referred_columns: Vec<String>,
110    pub on_delete: ReferentialAction,
111    pub on_update: ReferentialAction,
112    pub deferrable: bool,
113    pub initially_deferred: bool,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[repr(u8)]
118pub enum ReferentialAction {
119    NoAction = 0,
120    Restrict = 1,
121    Cascade = 2,
122    SetNull = 3,
123    SetDefault = 4,
124}
125
126impl ReferentialAction {
127    pub fn from_tag(tag: u8) -> Option<Self> {
128        match tag {
129            0 => Some(Self::NoAction),
130            1 => Some(Self::Restrict),
131            2 => Some(Self::Cascade),
132            3 => Some(Self::SetNull),
133            4 => Some(Self::SetDefault),
134            _ => None,
135        }
136    }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum GeneratedKind {
141    Stored,
142    Virtual,
143}
144
145#[derive(Debug, Clone)]
146pub struct ColumnSpec {
147    pub name: String,
148    pub data_type: DataType,
149    pub nullable: bool,
150    pub is_primary_key: bool,
151    pub default_expr: Option<Expr>,
152    pub default_sql: Option<String>,
153    pub check_expr: Option<Expr>,
154    pub check_sql: Option<String>,
155    pub check_name: Option<String>,
156    pub generated_expr: Option<Expr>,
157    pub generated_sql: Option<String>,
158    pub generated_kind: Option<GeneratedKind>,
159    pub collation: crate::types::Collation,
160}
161
162#[derive(Debug, Clone)]
163pub struct DropTableStmt {
164    pub name: String,
165    pub if_exists: bool,
166}
167
168#[derive(Debug, Clone)]
169pub struct TruncateStmt {
170    pub tables: Vec<String>,
171}
172
173#[derive(Debug, Clone)]
174pub struct CreateIndexStmt {
175    pub index_name: String,
176    pub table_name: String,
177    /// Per-key column name for `IndexColSpec::Column` entries; ignored for `Expr` entries
178    /// (those resolve through `key_exprs[i]`).
179    pub columns: Vec<String>,
180    /// Parallel to `columns`: `None` means the key is a column reference, `Some((expr, sql))`
181    /// means the key is an expression. Empty Vec = all-column index (the v0.15 norm).
182    pub key_exprs: Vec<Option<(Expr, String)>>,
183    pub unique: bool,
184    pub if_not_exists: bool,
185    pub predicate_sql: Option<String>,
186    pub predicate_expr: Option<Expr>,
187    pub collations: Vec<crate::types::Collation>,
188    pub kind: crate::types::IndexKind,
189    pub concurrently: bool,
190}
191
192#[derive(Debug, Clone)]
193pub struct CreateMatviewStmt {
194    pub name: String,
195    pub select_sql: String,
196    pub select_parsed: SelectQuery,
197    pub with_data: bool,
198    pub if_not_exists: bool,
199}
200
201#[derive(Debug, Clone)]
202pub struct RefreshMatviewStmt {
203    pub name: String,
204    pub concurrently: bool,
205}
206
207#[derive(Debug, Clone)]
208pub struct DropMatviewStmt {
209    pub name: String,
210    pub if_exists: bool,
211    pub cascade: bool,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum TriggerTiming {
216    Before,
217    After,
218    InsteadOf,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub enum TriggerEvent {
223    Insert,
224    Update(Vec<String>),
225    Delete,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub enum TriggerGranularity {
230    ForEachRow,
231    ForEachStatement,
232}
233
234#[derive(Debug, Clone)]
235pub struct TransitionTables {
236    pub new_table_alias: Option<String>,
237    pub old_table_alias: Option<String>,
238}
239
240#[derive(Debug, Clone)]
241pub struct CreateTriggerStmt {
242    pub name: String,
243    pub timing: TriggerTiming,
244    pub events: Vec<TriggerEvent>,
245    pub target: String,
246    pub granularity: TriggerGranularity,
247    pub referencing: Option<TransitionTables>,
248    pub when_sql: Option<String>,
249    pub when_expr: Option<Expr>,
250    pub body_sql: String,
251    pub body: Vec<Statement>,
252    pub if_not_exists: bool,
253}
254
255#[derive(Debug, Clone)]
256pub struct DropTriggerStmt {
257    pub name: String,
258    pub table: Option<String>,
259    pub if_exists: bool,
260}
261
262#[derive(Debug, Clone)]
263pub struct DropIndexStmt {
264    pub index_name: String,
265    pub if_exists: bool,
266}
267
268#[derive(Debug, Clone)]
269pub struct CreateViewStmt {
270    pub name: String,
271    pub sql: String,
272    pub column_aliases: Vec<String>,
273    pub or_replace: bool,
274    pub if_not_exists: bool,
275}
276
277#[derive(Debug, Clone)]
278pub struct DropViewStmt {
279    pub name: String,
280    pub if_exists: bool,
281}
282
283#[derive(Debug, Clone)]
284pub enum InsertSource {
285    Values(Vec<Vec<Expr>>),
286    Select(Box<SelectQuery>),
287}
288
289#[derive(Debug, Clone)]
290pub struct InsertStmt {
291    pub table: String,
292    pub columns: Vec<String>,
293    pub source: InsertSource,
294    pub on_conflict: Option<OnConflictClause>,
295    pub returning: Option<Vec<SelectColumn>>,
296}
297
298#[derive(Debug, Clone)]
299pub struct OnConflictClause {
300    pub target: Option<ConflictTarget>,
301    pub action: OnConflictAction,
302}
303
304#[derive(Debug, Clone)]
305pub enum ConflictTarget {
306    Columns(Vec<String>),
307    Constraint(String),
308}
309
310#[derive(Debug, Clone)]
311pub enum OnConflictAction {
312    DoNothing,
313    DoUpdate {
314        assignments: Vec<(String, Expr)>,
315        where_clause: Option<Expr>,
316    },
317}
318
319#[derive(Debug, Clone)]
320pub struct TableRef {
321    pub name: String,
322    pub alias: Option<String>,
323    pub args: Option<Vec<Expr>>,
324}
325
326#[derive(Debug, Clone)]
327pub struct DerivedTable {
328    pub query: Box<SelectQuery>,
329    pub lateral: bool,
330    pub alias: String,
331}
332
333#[derive(Debug, Clone, Copy, PartialEq)]
334pub enum JoinType {
335    Inner,
336    Cross,
337    Left,
338    Right,
339    FullOuter,
340}
341
342#[derive(Debug, Clone)]
343pub struct JoinClause {
344    pub join_type: JoinType,
345    pub table: TableRef,
346    pub subquery: Option<Box<DerivedTable>>,
347    pub on_clause: Option<Expr>,
348}
349
350#[derive(Debug, Clone)]
351pub struct SelectStmt {
352    pub columns: Vec<SelectColumn>,
353    pub from: String,
354    pub from_alias: Option<String>,
355    pub from_subquery: Option<Box<DerivedTable>>,
356    pub from_args: Option<Vec<Expr>>,
357    pub from_json_table: Option<Box<JsonTableSpec>>,
358    pub joins: Vec<JoinClause>,
359    pub distinct: bool,
360    pub where_clause: Option<Expr>,
361    pub order_by: Vec<OrderByItem>,
362    pub limit: Option<Expr>,
363    pub offset: Option<Expr>,
364    pub group_by: Vec<Expr>,
365    pub having: Option<Expr>,
366}
367
368#[derive(Debug, Clone)]
369pub struct JsonTableSpec {
370    pub source: Expr,
371    pub root_path: String,
372    pub columns: Vec<JsonTableCol>,
373}
374
375#[derive(Debug, Clone)]
376pub enum JsonTableCol {
377    Named {
378        name: String,
379        ty: DataType,
380        path: String,
381        exists: bool,
382    },
383    Ordinality {
384        name: String,
385    },
386    Nested {
387        path: String,
388        columns: Vec<JsonTableCol>,
389    },
390}
391
392#[derive(Debug, Clone)]
393pub enum SetOp {
394    Union,
395    Intersect,
396    Except,
397}
398
399#[derive(Debug, Clone)]
400pub struct CompoundSelect {
401    pub op: SetOp,
402    pub all: bool,
403    pub left: Box<QueryBody>,
404    pub right: Box<QueryBody>,
405    pub order_by: Vec<OrderByItem>,
406    pub limit: Option<Expr>,
407    pub offset: Option<Expr>,
408}
409
410#[derive(Debug, Clone)]
411pub enum QueryBody {
412    Select(Box<SelectStmt>),
413    Compound(Box<CompoundSelect>),
414    Insert(Box<InsertStmt>),
415    Update(Box<UpdateStmt>),
416    Delete(Box<DeleteStmt>),
417}
418
419#[derive(Debug, Clone)]
420pub struct CteDefinition {
421    pub name: String,
422    pub column_aliases: Vec<String>,
423    pub body: QueryBody,
424}
425
426#[derive(Debug, Clone)]
427pub struct SelectQuery {
428    pub ctes: Vec<CteDefinition>,
429    pub recursive: bool,
430    pub body: QueryBody,
431}
432
433#[derive(Debug, Clone)]
434pub struct UpdateStmt {
435    pub table: String,
436    pub assignments: Vec<(String, Expr)>,
437    pub where_clause: Option<Expr>,
438    pub returning: Option<Vec<SelectColumn>>,
439}
440
441#[derive(Debug, Clone)]
442pub struct DeleteStmt {
443    pub table: String,
444    pub where_clause: Option<Expr>,
445    pub returning: Option<Vec<SelectColumn>>,
446}
447
448#[derive(Debug, Clone)]
449pub enum SelectColumn {
450    AllColumns,
451    AllFromOld,
452    AllFromNew,
453    Expr { expr: Expr, alias: Option<String> },
454}
455
456#[derive(Debug, Clone)]
457pub struct OrderByItem {
458    pub expr: Expr,
459    pub descending: bool,
460    pub nulls_first: Option<bool>,
461}
462
463#[derive(Debug, Clone)]
464pub enum Expr {
465    Literal(Value),
466    Column(String),
467    QualifiedColumn {
468        table: String,
469        column: String,
470    },
471    BinaryOp {
472        left: Box<Expr>,
473        op: BinOp,
474        right: Box<Expr>,
475    },
476    UnaryOp {
477        op: UnaryOp,
478        expr: Box<Expr>,
479    },
480    IsNull(Box<Expr>),
481    IsNotNull(Box<Expr>),
482    Function {
483        name: String,
484        args: Vec<Expr>,
485        /// True for aggregate forms like `COUNT(DISTINCT x)`.
486        distinct: bool,
487    },
488    CountStar,
489    InSubquery {
490        expr: Box<Expr>,
491        subquery: Box<SelectStmt>,
492        negated: bool,
493    },
494    InList {
495        expr: Box<Expr>,
496        list: Vec<Expr>,
497        negated: bool,
498    },
499    Exists {
500        subquery: Box<SelectStmt>,
501        negated: bool,
502    },
503    ScalarSubquery(Box<SelectStmt>),
504    InSet {
505        expr: Box<Expr>,
506        values: rustc_hash::FxHashSet<Value>,
507        has_null: bool,
508        negated: bool,
509    },
510    Between {
511        expr: Box<Expr>,
512        low: Box<Expr>,
513        high: Box<Expr>,
514        negated: bool,
515    },
516    Like {
517        expr: Box<Expr>,
518        pattern: Box<Expr>,
519        escape: Option<Box<Expr>>,
520        negated: bool,
521    },
522    Case {
523        operand: Option<Box<Expr>>,
524        conditions: Vec<(Expr, Expr)>,
525        else_result: Option<Box<Expr>>,
526    },
527    Coalesce(Vec<Expr>),
528    Cast {
529        expr: Box<Expr>,
530        data_type: DataType,
531    },
532    Parameter(usize),
533    WindowFunction {
534        name: String,
535        args: Vec<Expr>,
536        spec: WindowSpec,
537    },
538    Collate {
539        expr: Box<Expr>,
540        collation: crate::types::Collation,
541    },
542    TypedNullRecord(String),
543    ArrayLiteral(Vec<Expr>),
544    Quantified {
545        left: Box<Expr>,
546        op: BinOp,
547        quantifier: Quantifier,
548        right: QuantifiedRhs,
549    },
550}
551
552#[derive(Debug, Clone, Copy, PartialEq, Eq)]
553pub enum Quantifier {
554    Any,
555    All,
556}
557
558#[derive(Debug, Clone)]
559pub enum QuantifiedRhs {
560    Subquery(Box<SelectStmt>),
561    Array(Box<Expr>),
562}
563
564#[derive(Debug, Clone)]
565pub struct WindowSpec {
566    pub partition_by: Vec<Expr>,
567    pub order_by: Vec<OrderByItem>,
568    pub frame: Option<WindowFrame>,
569}
570
571#[derive(Debug, Clone)]
572pub struct WindowFrame {
573    pub units: WindowFrameUnits,
574    pub start: WindowFrameBound,
575    pub end: WindowFrameBound,
576}
577
578#[derive(Debug, Clone, Copy)]
579pub enum WindowFrameUnits {
580    Rows,
581    Range,
582    Groups,
583}
584
585#[derive(Debug, Clone)]
586pub enum WindowFrameBound {
587    UnboundedPreceding,
588    Preceding(Box<Expr>),
589    CurrentRow,
590    Following(Box<Expr>),
591    UnboundedFollowing,
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
595pub enum BinOp {
596    Add,
597    Sub,
598    Mul,
599    Div,
600    Mod,
601    Eq,
602    NotEq,
603    Lt,
604    Gt,
605    LtEq,
606    GtEq,
607    And,
608    Or,
609    Concat,
610    JsonGet,
611    JsonGetText,
612    JsonPath,
613    JsonPathText,
614    JsonContains,
615    JsonContainedBy,
616    JsonHasKey,
617    JsonHasAnyKey,
618    JsonHasAllKeys,
619    JsonDeletePath,
620    JsonPathExists,
621    JsonPathMatch,
622    /// `@?_tz` — tz-aware variant of `@?`.
623    JsonPathExistsTz,
624    /// `@@_tz` — tz-aware variant of `@@`.
625    JsonPathMatchTz,
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
629pub enum UnaryOp {
630    Neg,
631    Not,
632}
633
634pub fn has_subquery(expr: &Expr) -> bool {
635    match expr {
636        Expr::InSubquery { .. } | Expr::Exists { .. } | Expr::ScalarSubquery(_) => true,
637        Expr::BinaryOp { left, right, .. } => has_subquery(left) || has_subquery(right),
638        Expr::UnaryOp { expr, .. } => has_subquery(expr),
639        Expr::IsNull(e) | Expr::IsNotNull(e) => has_subquery(e),
640        Expr::InList { expr, list, .. } => has_subquery(expr) || list.iter().any(has_subquery),
641        Expr::InSet { expr, .. } => has_subquery(expr),
642        Expr::Between {
643            expr, low, high, ..
644        } => has_subquery(expr) || has_subquery(low) || has_subquery(high),
645        Expr::Like {
646            expr,
647            pattern,
648            escape,
649            ..
650        } => {
651            has_subquery(expr)
652                || has_subquery(pattern)
653                || escape.as_ref().is_some_and(|e| has_subquery(e))
654        }
655        Expr::Case {
656            operand,
657            conditions,
658            else_result,
659        } => {
660            operand.as_ref().is_some_and(|e| has_subquery(e))
661                || conditions
662                    .iter()
663                    .any(|(c, r)| has_subquery(c) || has_subquery(r))
664                || else_result.as_ref().is_some_and(|e| has_subquery(e))
665        }
666        Expr::Coalesce(args) | Expr::Function { args, .. } => args.iter().any(has_subquery),
667        Expr::Cast { expr, .. } => has_subquery(expr),
668        Expr::ArrayLiteral(elems) => elems.iter().any(has_subquery),
669        Expr::Quantified { left, right, .. } => {
670            has_subquery(left)
671                || match right {
672                    QuantifiedRhs::Subquery(_) => true,
673                    QuantifiedRhs::Array(e) => has_subquery(e),
674                }
675        }
676        _ => false,
677    }
678}
679
680pub fn parse_sql_expr(sql: &str) -> Result<Expr> {
681    let sp_expr = crate::dialect::parse_expr(sql).map_err(|e| SqlError::Parse(e.to_string()))?;
682    convert_expr(&sp_expr)
683}
684
685pub fn parse_sql(sql: &str) -> Result<Statement> {
686    if let Some(stmt) = try_parse_refresh_matview(sql) {
687        return stmt;
688    }
689    let stmts =
690        crate::dialect::parse_statements(sql).map_err(|e| SqlError::Parse(e.to_string()))?;
691
692    if stmts.is_empty() {
693        return Err(SqlError::Parse("empty SQL".into()));
694    }
695    if stmts.len() > 1 {
696        return Err(SqlError::Unsupported("multiple statements".into()));
697    }
698
699    convert_statement(stmts.into_iter().next().unwrap())
700}
701
702pub fn parse_sql_multi(sql: &str) -> Result<Vec<Statement>> {
703    if let Some(stmt) = try_parse_refresh_matview(sql) {
704        return Ok(vec![stmt?]);
705    }
706    let stmts =
707        crate::dialect::parse_statements(sql).map_err(|e| SqlError::Parse(e.to_string()))?;
708
709    if stmts.is_empty() {
710        return Err(SqlError::Parse("empty SQL".into()));
711    }
712
713    stmts.into_iter().map(convert_statement).collect()
714}
715
716/// sqlparser 0.61 doesn't natively parse REFRESH MATERIALIZED VIEW [CONCURRENTLY] <name>.
717fn try_parse_refresh_matview(sql: &str) -> Option<Result<Statement>> {
718    let trimmed = sql.trim().trim_end_matches(';').trim();
719    let lower = trimmed.to_ascii_lowercase();
720    let after = lower.strip_prefix("refresh materialized view")?;
721    let after = after.trim_start();
722    let (concurrently, rest_lower) = match after.strip_prefix("concurrently") {
723        Some(r) if r.starts_with(char::is_whitespace) => (true, r.trim_start()),
724        _ => (false, after),
725    };
726    if rest_lower.is_empty() {
727        return Some(Err(SqlError::Parse(
728            "REFRESH MATERIALIZED VIEW requires a name".into(),
729        )));
730    }
731    let name = rest_lower
732        .split_whitespace()
733        .next()
734        .unwrap_or("")
735        .trim_matches(|c: char| c == '"' || c == ';')
736        .to_string();
737    if name.is_empty() {
738        return Some(Err(SqlError::Parse(
739            "REFRESH MATERIALIZED VIEW requires a name".into(),
740        )));
741    }
742    Some(Ok(Statement::RefreshMaterializedView(RefreshMatviewStmt {
743        name,
744        concurrently,
745    })))
746}
747
748pub fn count_params(stmt: &Statement) -> usize {
749    let mut max_idx = 0usize;
750    visit_exprs_stmt(stmt, &mut |e| {
751        if let Expr::Parameter(n) = e {
752            max_idx = max_idx.max(*n);
753        }
754    });
755    max_idx
756}
757
758fn visit_exprs_stmt(stmt: &Statement, visitor: &mut impl FnMut(&Expr)) {
759    match stmt {
760        Statement::Select(sq) => {
761            for cte in &sq.ctes {
762                visit_exprs_query_body(&cte.body, visitor);
763            }
764            visit_exprs_query_body(&sq.body, visitor);
765        }
766        Statement::Insert(ins) => match &ins.source {
767            InsertSource::Values(rows) => {
768                for row in rows {
769                    for e in row {
770                        visit_expr(e, visitor);
771                    }
772                }
773            }
774            InsertSource::Select(sq) => {
775                for cte in &sq.ctes {
776                    visit_exprs_query_body(&cte.body, visitor);
777                }
778                visit_exprs_query_body(&sq.body, visitor);
779            }
780        },
781        Statement::Update(upd) => {
782            for (_, e) in &upd.assignments {
783                visit_expr(e, visitor);
784            }
785            if let Some(w) = &upd.where_clause {
786                visit_expr(w, visitor);
787            }
788        }
789        Statement::Delete(del) => {
790            if let Some(w) = &del.where_clause {
791                visit_expr(w, visitor);
792            }
793        }
794        Statement::Explain(inner) => visit_exprs_stmt(inner, visitor),
795        _ => {}
796    }
797}
798
799fn visit_exprs_query_body(body: &QueryBody, visitor: &mut impl FnMut(&Expr)) {
800    match body {
801        QueryBody::Select(sel) => visit_exprs_select(sel, visitor),
802        QueryBody::Compound(comp) => {
803            visit_exprs_query_body(&comp.left, visitor);
804            visit_exprs_query_body(&comp.right, visitor);
805            for o in &comp.order_by {
806                visit_expr(&o.expr, visitor);
807            }
808            if let Some(l) = &comp.limit {
809                visit_expr(l, visitor);
810            }
811            if let Some(o) = &comp.offset {
812                visit_expr(o, visitor);
813            }
814        }
815        QueryBody::Insert(ins) => visit_exprs_stmt(&Statement::Insert((**ins).clone()), visitor),
816        QueryBody::Update(upd) => visit_exprs_stmt(&Statement::Update((**upd).clone()), visitor),
817        QueryBody::Delete(del) => visit_exprs_stmt(&Statement::Delete((**del).clone()), visitor),
818    }
819}
820
821fn visit_exprs_select(sel: &SelectStmt, visitor: &mut impl FnMut(&Expr)) {
822    for col in &sel.columns {
823        if let SelectColumn::Expr { expr, .. } = col {
824            visit_expr(expr, visitor);
825        }
826    }
827    for j in &sel.joins {
828        if let Some(on) = &j.on_clause {
829            visit_expr(on, visitor);
830        }
831    }
832    if let Some(w) = &sel.where_clause {
833        visit_expr(w, visitor);
834    }
835    for o in &sel.order_by {
836        visit_expr(&o.expr, visitor);
837    }
838    if let Some(l) = &sel.limit {
839        visit_expr(l, visitor);
840    }
841    if let Some(o) = &sel.offset {
842        visit_expr(o, visitor);
843    }
844    for g in &sel.group_by {
845        visit_expr(g, visitor);
846    }
847    if let Some(h) = &sel.having {
848        visit_expr(h, visitor);
849    }
850}
851
852fn visit_expr(expr: &Expr, visitor: &mut impl FnMut(&Expr)) {
853    visitor(expr);
854    match expr {
855        Expr::BinaryOp { left, right, .. } => {
856            visit_expr(left, visitor);
857            visit_expr(right, visitor);
858        }
859        Expr::UnaryOp { expr: e, .. } | Expr::IsNull(e) | Expr::IsNotNull(e) => {
860            visit_expr(e, visitor);
861        }
862        Expr::Function { args, .. } | Expr::Coalesce(args) => {
863            for a in args {
864                visit_expr(a, visitor);
865            }
866        }
867        Expr::InSubquery {
868            expr: e, subquery, ..
869        } => {
870            visit_expr(e, visitor);
871            visit_exprs_select(subquery, visitor);
872        }
873        Expr::InList { expr: e, list, .. } => {
874            visit_expr(e, visitor);
875            for l in list {
876                visit_expr(l, visitor);
877            }
878        }
879        Expr::Exists { subquery, .. } => visit_exprs_select(subquery, visitor),
880        Expr::ScalarSubquery(sq) => visit_exprs_select(sq, visitor),
881        Expr::InSet { expr: e, .. } => visit_expr(e, visitor),
882        Expr::Between {
883            expr: e, low, high, ..
884        } => {
885            visit_expr(e, visitor);
886            visit_expr(low, visitor);
887            visit_expr(high, visitor);
888        }
889        Expr::Like {
890            expr: e,
891            pattern,
892            escape,
893            ..
894        } => {
895            visit_expr(e, visitor);
896            visit_expr(pattern, visitor);
897            if let Some(esc) = escape {
898                visit_expr(esc, visitor);
899            }
900        }
901        Expr::Case {
902            operand,
903            conditions,
904            else_result,
905        } => {
906            if let Some(op) = operand {
907                visit_expr(op, visitor);
908            }
909            for (cond, then) in conditions {
910                visit_expr(cond, visitor);
911                visit_expr(then, visitor);
912            }
913            if let Some(el) = else_result {
914                visit_expr(el, visitor);
915            }
916        }
917        Expr::Cast { expr: e, .. } => visit_expr(e, visitor),
918        Expr::Collate { expr: e, .. } => visit_expr(e, visitor),
919        Expr::WindowFunction { args, spec, .. } => {
920            for a in args {
921                visit_expr(a, visitor);
922            }
923            for p in &spec.partition_by {
924                visit_expr(p, visitor);
925            }
926            for o in &spec.order_by {
927                visit_expr(&o.expr, visitor);
928            }
929            if let Some(ref frame) = spec.frame {
930                if let WindowFrameBound::Preceding(e) | WindowFrameBound::Following(e) =
931                    &frame.start
932                {
933                    visit_expr(e, visitor);
934                }
935                if let WindowFrameBound::Preceding(e) | WindowFrameBound::Following(e) = &frame.end
936                {
937                    visit_expr(e, visitor);
938                }
939            }
940        }
941        Expr::ArrayLiteral(elems) => {
942            for e in elems {
943                visit_expr(e, visitor);
944            }
945        }
946        Expr::Quantified { left, right, .. } => {
947            visit_expr(left, visitor);
948            match right {
949                QuantifiedRhs::Subquery(sq) => visit_exprs_select(sq, visitor),
950                QuantifiedRhs::Array(e) => visit_expr(e, visitor),
951            }
952        }
953        Expr::Literal(_)
954        | Expr::Column(_)
955        | Expr::QualifiedColumn { .. }
956        | Expr::CountStar
957        | Expr::Parameter(_)
958        | Expr::TypedNullRecord(_) => {}
959    }
960}
961
962fn convert_statement(stmt: sp::Statement) -> Result<Statement> {
963    match stmt {
964        sp::Statement::CreateTable(ct) => convert_create_table(ct),
965        sp::Statement::CreateIndex(ci) => convert_create_index(ci),
966        sp::Statement::Drop {
967            object_type: sp::ObjectType::Table,
968            if_exists,
969            names,
970            ..
971        } => {
972            if names.len() != 1 {
973                return Err(SqlError::Unsupported("multi-table DROP".into()));
974            }
975            Ok(Statement::DropTable(DropTableStmt {
976                name: object_name_to_string(&names[0]),
977                if_exists,
978            }))
979        }
980        sp::Statement::Drop {
981            object_type: sp::ObjectType::Index,
982            if_exists,
983            names,
984            ..
985        } => {
986            if names.len() != 1 {
987                return Err(SqlError::Unsupported("multi-index DROP".into()));
988            }
989            Ok(Statement::DropIndex(DropIndexStmt {
990                index_name: object_name_to_string(&names[0]),
991                if_exists,
992            }))
993        }
994        sp::Statement::CreateView(cv) => convert_create_view(cv),
995        sp::Statement::Drop {
996            object_type: sp::ObjectType::View,
997            if_exists,
998            names,
999            ..
1000        } => {
1001            if names.len() != 1 {
1002                return Err(SqlError::Unsupported("multi-view DROP".into()));
1003            }
1004            Ok(Statement::DropView(DropViewStmt {
1005                name: object_name_to_string(&names[0]),
1006                if_exists,
1007            }))
1008        }
1009        sp::Statement::Drop {
1010            object_type: sp::ObjectType::MaterializedView,
1011            if_exists,
1012            names,
1013            cascade,
1014            ..
1015        } => {
1016            if names.len() != 1 {
1017                return Err(SqlError::Unsupported("multi-matview DROP".into()));
1018            }
1019            Ok(Statement::DropMaterializedView(DropMatviewStmt {
1020                name: object_name_to_string(&names[0]),
1021                if_exists,
1022                cascade,
1023            }))
1024        }
1025        sp::Statement::CreateTrigger(ct) => convert_create_trigger(ct),
1026        sp::Statement::DropTrigger(dt) => Ok(Statement::DropTrigger(DropTriggerStmt {
1027            name: object_name_to_string(&dt.trigger_name),
1028            table: dt.table_name.as_ref().map(object_name_to_string),
1029            if_exists: dt.if_exists,
1030        })),
1031        sp::Statement::AlterTable(at) => convert_alter_table(at),
1032        sp::Statement::Insert(insert) => convert_insert(insert),
1033        sp::Statement::Query(query) => convert_query(*query),
1034        sp::Statement::Update(update) => convert_update(update),
1035        sp::Statement::Delete(delete) => convert_delete(delete),
1036        sp::Statement::Truncate(t) => convert_truncate(t),
1037        sp::Statement::StartTransaction { modes, .. } => {
1038            let mut access_mode = BeginAccessMode::Default;
1039            for mode in modes {
1040                if let sp::TransactionMode::AccessMode(am) = mode {
1041                    access_mode = match am {
1042                        sp::TransactionAccessMode::ReadOnly => BeginAccessMode::ReadOnly,
1043                        sp::TransactionAccessMode::ReadWrite => BeginAccessMode::ReadWrite,
1044                    };
1045                }
1046            }
1047            Ok(Statement::Begin { access_mode })
1048        }
1049        sp::Statement::Commit { chain: true, .. } => {
1050            Err(SqlError::Unsupported("COMMIT AND CHAIN".into()))
1051        }
1052        sp::Statement::Commit { .. } => Ok(Statement::Commit),
1053        sp::Statement::Rollback { chain: true, .. } => {
1054            Err(SqlError::Unsupported("ROLLBACK AND CHAIN".into()))
1055        }
1056        sp::Statement::Rollback {
1057            savepoint: Some(name),
1058            ..
1059        } => Ok(Statement::RollbackTo(name.value.to_ascii_lowercase())),
1060        sp::Statement::Rollback { .. } => Ok(Statement::Rollback),
1061        sp::Statement::Savepoint { name } => {
1062            Ok(Statement::Savepoint(name.value.to_ascii_lowercase()))
1063        }
1064        sp::Statement::ReleaseSavepoint { name } => {
1065            Ok(Statement::ReleaseSavepoint(name.value.to_ascii_lowercase()))
1066        }
1067        sp::Statement::Set(sp::Set::SetTimeZone { value, .. }) => {
1068            // Accept a string literal or bare identifier (PG allows `SET TIME ZONE UTC`).
1069            let zone = match value {
1070                sp::Expr::Value(v) => match &v.value {
1071                    sp::Value::SingleQuotedString(s) => s.clone(),
1072                    sp::Value::DoubleQuotedString(s) => s.clone(),
1073                    other => other.to_string(),
1074                },
1075                sp::Expr::Identifier(ident) => ident.value.clone(),
1076                other => {
1077                    return Err(SqlError::Parse(format!(
1078                        "SET TIME ZONE expects a string literal or identifier, got: {other}"
1079                    )))
1080                }
1081            };
1082            Ok(Statement::SetTimezone(zone))
1083        }
1084        sp::Statement::Explain {
1085            statement, analyze, ..
1086        } => {
1087            if analyze {
1088                return Err(SqlError::Unsupported("EXPLAIN ANALYZE".into()));
1089            }
1090            let inner = convert_statement(*statement)?;
1091            Ok(Statement::Explain(Box::new(inner)))
1092        }
1093        _ => Err(SqlError::Unsupported(format!("statement type: {}", stmt))),
1094    }
1095}
1096
1097/// Returns (ColumnSpec, Option<ForeignKeyDef>, was_inline_pk, was_unique).
1098fn convert_column_def(
1099    col_def: &sp::ColumnDef,
1100) -> Result<(ColumnSpec, Option<ForeignKeyDef>, bool, bool)> {
1101    let col_name = col_def.name.value.clone();
1102    let data_type = convert_data_type(&col_def.data_type)?;
1103    let mut nullable = true;
1104    let mut is_primary_key = false;
1105    let mut is_unique = false;
1106    let mut default_expr = None;
1107    let mut default_sql = None;
1108    let mut check_expr = None;
1109    let mut check_sql = None;
1110    let mut check_name = None;
1111    let mut generated_expr = None;
1112    let mut generated_sql = None;
1113    let mut generated_kind = None;
1114    let mut fk_def = None;
1115    let mut collation = crate::types::Collation::Binary;
1116
1117    for opt in &col_def.options {
1118        match &opt.option {
1119            sp::ColumnOption::Collation(name) => {
1120                let coll_name = object_name_to_string(name);
1121                collation = crate::types::Collation::from_name(&coll_name).ok_or_else(|| {
1122                    SqlError::Unsupported(format!(
1123                        "collation '{coll_name}' not supported (BINARY/NOCASE/RTRIM only)"
1124                    ))
1125                })?;
1126            }
1127            sp::ColumnOption::NotNull => nullable = false,
1128            sp::ColumnOption::Null => nullable = true,
1129            sp::ColumnOption::PrimaryKey(_) => {
1130                is_primary_key = true;
1131                nullable = false;
1132            }
1133            sp::ColumnOption::Unique(_) => is_unique = true,
1134            sp::ColumnOption::Default(expr) => {
1135                default_sql = Some(expr.to_string());
1136                default_expr = Some(convert_expr(expr)?);
1137            }
1138            sp::ColumnOption::Check(check) => {
1139                check_sql = Some(check.expr.to_string());
1140                let converted = convert_expr(&check.expr)?;
1141                if has_subquery(&converted) {
1142                    return Err(SqlError::Unsupported("subquery in CHECK constraint".into()));
1143                }
1144                check_expr = Some(converted);
1145                check_name = check.name.as_ref().map(|n| n.value.clone());
1146            }
1147            sp::ColumnOption::ForeignKey(fk) => {
1148                let (on_delete, on_update) = convert_fk_actions(&fk.on_delete, &fk.on_update)?;
1149                let (deferrable, initially_deferred) =
1150                    convert_fk_characteristics(&fk.characteristics);
1151                let ftable = object_name_to_string(&fk.foreign_table).to_ascii_lowercase();
1152                let referred: Vec<String> = fk
1153                    .referred_columns
1154                    .iter()
1155                    .map(|i| i.value.to_ascii_lowercase())
1156                    .collect();
1157                fk_def = Some(ForeignKeyDef {
1158                    name: fk.name.as_ref().map(|n| n.value.clone()),
1159                    columns: vec![col_name.to_ascii_lowercase()],
1160                    foreign_table: ftable,
1161                    referred_columns: referred,
1162                    on_delete,
1163                    on_update,
1164                    deferrable,
1165                    initially_deferred,
1166                });
1167            }
1168            sp::ColumnOption::Generated {
1169                generation_expr,
1170                generation_expr_mode,
1171                sequence_options: _,
1172                ..
1173            } => {
1174                let Some(expr) = generation_expr else {
1175                    return Err(SqlError::Unsupported(
1176                        "identity columns not yet supported; use INTEGER PRIMARY KEY for autoincrement".into(),
1177                    ));
1178                };
1179                let mode = generation_expr_mode.unwrap_or(sp::GeneratedExpressionMode::Virtual);
1180                let converted = convert_expr(expr)?;
1181                reject_aggregate_or_window(expr, "GENERATED")?;
1182                if has_subquery(&converted) {
1183                    return Err(SqlError::Unsupported(
1184                        "subquery in GENERATED expression".into(),
1185                    ));
1186                }
1187                reject_volatile_in_generated(&converted)?;
1188                generated_sql = Some(expr.to_string());
1189                generated_expr = Some(converted);
1190                generated_kind = Some(match mode {
1191                    sp::GeneratedExpressionMode::Stored => GeneratedKind::Stored,
1192                    sp::GeneratedExpressionMode::Virtual => GeneratedKind::Virtual,
1193                });
1194            }
1195            _ => {}
1196        }
1197    }
1198
1199    if generated_kind.is_some() {
1200        if default_expr.is_some() {
1201            return Err(SqlError::Unsupported(
1202                "DEFAULT and GENERATED cannot be combined".into(),
1203            ));
1204        }
1205        if is_primary_key {
1206            return Err(SqlError::Unsupported(
1207                "GENERATED column cannot be PRIMARY KEY".into(),
1208            ));
1209        }
1210    }
1211
1212    let spec = ColumnSpec {
1213        name: col_name,
1214        data_type,
1215        nullable,
1216        is_primary_key,
1217        default_expr,
1218        default_sql,
1219        check_expr,
1220        check_sql,
1221        check_name,
1222        generated_expr,
1223        generated_sql,
1224        generated_kind,
1225        collation,
1226    };
1227    Ok((spec, fk_def, is_primary_key, is_unique))
1228}
1229
1230fn reject_volatile_in_generated(expr: &Expr) -> Result<()> {
1231    fn walk(e: &Expr) -> Result<()> {
1232        match e {
1233            Expr::Function { name, args, .. } => {
1234                let upper = name.to_ascii_uppercase();
1235                if matches!(
1236                    upper.as_str(),
1237                    "RANDOM"
1238                        | "NOW"
1239                        | "CURRENT_TIMESTAMP"
1240                        | "CURRENT_DATE"
1241                        | "CURRENT_TIME"
1242                        | "CLOCK_TIMESTAMP"
1243                        | "STATEMENT_TIMESTAMP"
1244                        | "TRANSACTION_TIMESTAMP"
1245                        | "LOCALTIMESTAMP"
1246                        | "LOCALTIME"
1247                ) {
1248                    return Err(SqlError::Unsupported(format!(
1249                        "volatile function {name}() not allowed in GENERATED expression"
1250                    )));
1251                }
1252                for a in args {
1253                    walk(a)?;
1254                }
1255                Ok(())
1256            }
1257            Expr::BinaryOp { left, right, .. } => {
1258                walk(left)?;
1259                walk(right)
1260            }
1261            Expr::UnaryOp { expr, .. } => walk(expr),
1262            Expr::Cast { expr, .. } => walk(expr),
1263            Expr::Case {
1264                operand,
1265                conditions,
1266                else_result,
1267            } => {
1268                if let Some(o) = operand {
1269                    walk(o)?;
1270                }
1271                for (cond, res) in conditions {
1272                    walk(cond)?;
1273                    walk(res)?;
1274                }
1275                if let Some(e) = else_result {
1276                    walk(e)?;
1277                }
1278                Ok(())
1279            }
1280            Expr::Coalesce(items) => items.iter().try_for_each(walk),
1281            _ => Ok(()),
1282        }
1283    }
1284    walk(expr)
1285}
1286
1287fn convert_create_table(ct: sp::CreateTable) -> Result<Statement> {
1288    let name = object_name_to_string(&ct.name);
1289    let if_not_exists = ct.if_not_exists;
1290    let strict = ct.strict;
1291    let temporary = ct.temporary;
1292
1293    let mut columns = Vec::new();
1294    let mut inline_pk: Vec<String> = Vec::new();
1295    let mut foreign_keys: Vec<ForeignKeyDef> = Vec::new();
1296    let mut unique_indices: Vec<UniqueIndexDef> = Vec::new();
1297
1298    for col_def in &ct.columns {
1299        let (spec, fk_def, was_pk, was_unique) = convert_column_def(col_def)?;
1300        if was_pk {
1301            inline_pk.push(spec.name.clone());
1302        }
1303        if let Some(fk) = fk_def {
1304            foreign_keys.push(fk);
1305        }
1306        if was_unique && !was_pk {
1307            unique_indices.push(UniqueIndexDef {
1308                name: None,
1309                columns: vec![spec.name.to_ascii_lowercase()],
1310            });
1311        }
1312        columns.push(spec);
1313    }
1314
1315    let mut check_constraints: Vec<TableCheckConstraint> = Vec::new();
1316
1317    for constraint in &ct.constraints {
1318        match constraint {
1319            sp::TableConstraint::PrimaryKey(pk_constraint) => {
1320                for idx_col in &pk_constraint.columns {
1321                    let col_name = match &idx_col.column.expr {
1322                        sp::Expr::Identifier(ident) => ident.value.clone(),
1323                        _ => continue,
1324                    };
1325                    if !inline_pk.contains(&col_name) {
1326                        inline_pk.push(col_name.clone());
1327                    }
1328                    if let Some(col) = columns.iter_mut().find(|c| c.name == col_name) {
1329                        col.nullable = false;
1330                        col.is_primary_key = true;
1331                    }
1332                }
1333            }
1334            sp::TableConstraint::Check(check) => {
1335                let sql = check.expr.to_string();
1336                let converted = convert_expr(&check.expr)?;
1337                if has_subquery(&converted) {
1338                    return Err(SqlError::Unsupported("subquery in CHECK constraint".into()));
1339                }
1340                check_constraints.push(TableCheckConstraint {
1341                    name: check.name.as_ref().map(|n| n.value.clone()),
1342                    expr: converted,
1343                    sql,
1344                });
1345            }
1346            sp::TableConstraint::ForeignKey(fk) => {
1347                let (on_delete, on_update) = convert_fk_actions(&fk.on_delete, &fk.on_update)?;
1348                let (deferrable, initially_deferred) =
1349                    convert_fk_characteristics(&fk.characteristics);
1350                let cols: Vec<String> = fk
1351                    .columns
1352                    .iter()
1353                    .map(|i| i.value.to_ascii_lowercase())
1354                    .collect();
1355                let ftable = object_name_to_string(&fk.foreign_table).to_ascii_lowercase();
1356                let referred: Vec<String> = fk
1357                    .referred_columns
1358                    .iter()
1359                    .map(|i| i.value.to_ascii_lowercase())
1360                    .collect();
1361                foreign_keys.push(ForeignKeyDef {
1362                    name: fk.name.as_ref().map(|n| n.value.clone()),
1363                    columns: cols,
1364                    foreign_table: ftable,
1365                    referred_columns: referred,
1366                    on_delete,
1367                    on_update,
1368                    deferrable,
1369                    initially_deferred,
1370                });
1371            }
1372            sp::TableConstraint::Unique(u) => {
1373                let cols: Vec<String> = u
1374                    .columns
1375                    .iter()
1376                    .filter_map(|idx_col| match &idx_col.column.expr {
1377                        sp::Expr::Identifier(ident) => Some(ident.value.to_ascii_lowercase()),
1378                        _ => None,
1379                    })
1380                    .collect();
1381                if !cols.is_empty() {
1382                    unique_indices.push(UniqueIndexDef {
1383                        name: u.name.as_ref().map(|n| n.value.clone()),
1384                        columns: cols,
1385                    });
1386                }
1387            }
1388            _ => {}
1389        }
1390    }
1391
1392    Ok(Statement::CreateTable(CreateTableStmt {
1393        name,
1394        columns,
1395        primary_key: inline_pk,
1396        if_not_exists,
1397        check_constraints,
1398        foreign_keys,
1399        unique_indices,
1400        strict,
1401        temporary,
1402    }))
1403}
1404
1405fn convert_alter_table(at: sp::AlterTable) -> Result<Statement> {
1406    let table = object_name_to_string(&at.name);
1407    if at.operations.len() != 1 {
1408        return Err(SqlError::Unsupported(
1409            "ALTER TABLE with multiple operations".into(),
1410        ));
1411    }
1412    let op = match at.operations.into_iter().next().unwrap() {
1413        sp::AlterTableOperation::AddColumn {
1414            column_def,
1415            if_not_exists,
1416            ..
1417        } => {
1418            let (spec, fk, _was_pk, _was_unique) = convert_column_def(&column_def)?;
1419            AlterTableOp::AddColumn {
1420                column: Box::new(spec),
1421                foreign_key: fk,
1422                if_not_exists,
1423            }
1424        }
1425        sp::AlterTableOperation::DropColumn {
1426            column_names,
1427            if_exists,
1428            ..
1429        } => {
1430            if column_names.len() != 1 {
1431                return Err(SqlError::Unsupported(
1432                    "DROP COLUMN with multiple columns".into(),
1433                ));
1434            }
1435            AlterTableOp::DropColumn {
1436                name: column_names.into_iter().next().unwrap().value,
1437                if_exists,
1438            }
1439        }
1440        sp::AlterTableOperation::RenameColumn {
1441            old_column_name,
1442            new_column_name,
1443        } => AlterTableOp::RenameColumn {
1444            old_name: old_column_name.value,
1445            new_name: new_column_name.value,
1446        },
1447        sp::AlterTableOperation::RenameTable { table_name } => {
1448            let new_name = match table_name {
1449                sp::RenameTableNameKind::To(name) | sp::RenameTableNameKind::As(name) => {
1450                    object_name_to_string(&name)
1451                }
1452            };
1453            AlterTableOp::RenameTable { new_name }
1454        }
1455        sp::AlterTableOperation::DisableTrigger { name } => {
1456            if name.value.eq_ignore_ascii_case("all") {
1457                AlterTableOp::DisableAllTriggers
1458            } else {
1459                AlterTableOp::DisableTrigger {
1460                    name: name.value.to_ascii_lowercase(),
1461                }
1462            }
1463        }
1464        sp::AlterTableOperation::EnableTrigger { name } => {
1465            if name.value.eq_ignore_ascii_case("all") {
1466                AlterTableOp::EnableAllTriggers
1467            } else {
1468                AlterTableOp::EnableTrigger {
1469                    name: name.value.to_ascii_lowercase(),
1470                }
1471            }
1472        }
1473        other => {
1474            return Err(SqlError::Unsupported(format!(
1475                "ALTER TABLE operation: {other}"
1476            )));
1477        }
1478    };
1479    Ok(Statement::AlterTable(Box::new(AlterTableStmt {
1480        table,
1481        op,
1482    })))
1483}
1484
1485fn convert_fk_actions(
1486    on_delete: &Option<sp::ReferentialAction>,
1487    on_update: &Option<sp::ReferentialAction>,
1488) -> Result<(ReferentialAction, ReferentialAction)> {
1489    Ok((convert_fk_action(on_delete)?, convert_fk_action(on_update)?))
1490}
1491
1492fn convert_fk_action(action: &Option<sp::ReferentialAction>) -> Result<ReferentialAction> {
1493    match action {
1494        None | Some(sp::ReferentialAction::NoAction) => Ok(ReferentialAction::NoAction),
1495        Some(sp::ReferentialAction::Restrict) => Ok(ReferentialAction::Restrict),
1496        Some(sp::ReferentialAction::Cascade) => Ok(ReferentialAction::Cascade),
1497        Some(sp::ReferentialAction::SetNull) => Ok(ReferentialAction::SetNull),
1498        Some(sp::ReferentialAction::SetDefault) => Ok(ReferentialAction::SetDefault),
1499    }
1500}
1501
1502fn convert_fk_characteristics(ch: &Option<sp::ConstraintCharacteristics>) -> (bool, bool) {
1503    let Some(c) = ch else {
1504        return (false, false);
1505    };
1506    let deferrable = c.deferrable.unwrap_or(false);
1507    let initially_deferred = matches!(c.initially, Some(sp::DeferrableInitial::Deferred));
1508    (deferrable, initially_deferred)
1509}
1510
1511fn convert_create_index(ci: sp::CreateIndex) -> Result<Statement> {
1512    let index_name = ci
1513        .name
1514        .as_ref()
1515        .map(object_name_to_string)
1516        .ok_or_else(|| SqlError::Parse("index name required".into()))?;
1517
1518    let table_name = object_name_to_string(&ci.table_name);
1519
1520    let mut columns: Vec<String> = Vec::with_capacity(ci.columns.len());
1521    let mut collations: Vec<crate::types::Collation> = Vec::with_capacity(ci.columns.len());
1522    let mut key_exprs: Vec<Option<(Expr, String)>> = Vec::with_capacity(ci.columns.len());
1523    for idx_col in &ci.columns {
1524        let (name, coll, expr_entry) = match &idx_col.column.expr {
1525            sp::Expr::Identifier(ident) => {
1526                (ident.value.clone(), crate::types::Collation::Binary, None)
1527            }
1528            sp::Expr::Collate {
1529                expr: inner,
1530                collation,
1531            } => match inner.as_ref() {
1532                sp::Expr::Identifier(ident) => {
1533                    let coll_name = object_name_to_string(collation);
1534                    let coll = crate::types::Collation::from_name(&coll_name).ok_or_else(|| {
1535                        SqlError::Unsupported(format!(
1536                            "collation '{coll_name}' not supported (BINARY/NOCASE/RTRIM only)"
1537                        ))
1538                    })?;
1539                    (ident.value.clone(), coll, None)
1540                }
1541                inner_expr => {
1542                    let sql = inner_expr.to_string();
1543                    let expr = convert_expr(inner_expr)?;
1544                    (
1545                        sql.clone(),
1546                        crate::types::Collation::Binary,
1547                        Some((expr, sql)),
1548                    )
1549                }
1550            },
1551            other => {
1552                let sql = other.to_string();
1553                let expr = convert_expr(other)?;
1554                (
1555                    sql.clone(),
1556                    crate::types::Collation::Binary,
1557                    Some((expr, sql)),
1558                )
1559            }
1560        };
1561        columns.push(name);
1562        collations.push(coll);
1563        key_exprs.push(expr_entry);
1564    }
1565
1566    if columns.is_empty() {
1567        return Err(SqlError::Parse(
1568            "index must have at least one column".into(),
1569        ));
1570    }
1571
1572    let (predicate_sql, predicate_expr) = match &ci.predicate {
1573        Some(sp_expr) => {
1574            let expr = convert_expr(sp_expr)?;
1575            validate_partial_index_predicate(&expr)?;
1576            (Some(sp_expr.to_string()), Some(expr))
1577        }
1578        None => (None, None),
1579    };
1580
1581    let kind = match &ci.using {
1582        None => crate::types::IndexKind::BTree,
1583        Some(sp::IndexType::BTree) => crate::types::IndexKind::BTree,
1584        Some(sp::IndexType::GIN) => {
1585            let ops = parse_gin_with_ops(&ci.with)?;
1586            crate::types::IndexKind::Inverted(crate::types::InvertedKind::Gin(ops))
1587        }
1588        Some(sp::IndexType::Custom(ident)) if ident.value.eq_ignore_ascii_case("fts") => {
1589            let config_id = parse_fts_with_config(&ci.with)?;
1590            crate::types::IndexKind::Inverted(crate::types::InvertedKind::Fts { config_id })
1591        }
1592        Some(other) => {
1593            return Err(SqlError::Unsupported(format!(
1594                "index method {other}; supported: BTREE, GIN, FTS"
1595            )));
1596        }
1597    };
1598
1599    Ok(Statement::CreateIndex(CreateIndexStmt {
1600        index_name,
1601        table_name,
1602        columns,
1603        key_exprs,
1604        unique: ci.unique,
1605        if_not_exists: ci.if_not_exists,
1606        predicate_sql,
1607        predicate_expr,
1608        collations,
1609        kind,
1610        concurrently: ci.concurrently,
1611    }))
1612}
1613
1614fn parse_gin_with_ops(with: &[sp::Expr]) -> Result<crate::types::GinOpsClass> {
1615    use crate::types::GinOpsClass;
1616    let mut ops_name: Option<String> = None;
1617    for expr in with {
1618        match expr {
1619            sp::Expr::BinaryOp {
1620                left,
1621                op: sp::BinaryOperator::Eq,
1622                right,
1623            } => {
1624                let key = match left.as_ref() {
1625                    sp::Expr::Identifier(id) => id.value.to_ascii_lowercase(),
1626                    other => {
1627                        return Err(SqlError::Unsupported(format!(
1628                            "GIN WITH option key: {other}"
1629                        )));
1630                    }
1631                };
1632                if key != "ops" {
1633                    return Err(SqlError::Unsupported(format!(
1634                        "GIN WITH option: unknown key '{key}' (only 'ops' supported)"
1635                    )));
1636                }
1637                let val = match right.as_ref() {
1638                    sp::Expr::Value(v) => match &v.value {
1639                        sp::Value::SingleQuotedString(s) => s.clone(),
1640                        sp::Value::DoubleQuotedString(s) => s.clone(),
1641                        other => {
1642                            return Err(SqlError::Parse(format!(
1643                                "GIN ops value must be a string literal, got: {other}"
1644                            )))
1645                        }
1646                    },
1647                    sp::Expr::Identifier(id) => id.value.clone(),
1648                    other => {
1649                        return Err(SqlError::Parse(format!(
1650                            "GIN ops value must be a string literal, got: {other}"
1651                        )));
1652                    }
1653                };
1654                ops_name = Some(val);
1655            }
1656            other => {
1657                return Err(SqlError::Unsupported(format!(
1658                    "GIN WITH option must be `key = value`, got: {other}"
1659                )));
1660            }
1661        }
1662    }
1663    let lower = ops_name.as_deref().map(|s| s.to_ascii_lowercase());
1664    match lower.as_deref() {
1665        None | Some("jsonb_ops") => Ok(GinOpsClass::JsonbOps),
1666        Some("jsonb_path_ops") => Ok(GinOpsClass::JsonbPathOps),
1667        Some(other) => Err(SqlError::Unsupported(format!(
1668            "GIN opclass '{other}'; supported: jsonb_ops, jsonb_path_ops"
1669        ))),
1670    }
1671}
1672
1673fn parse_fts_with_config(with: &[sp::Expr]) -> Result<u8> {
1674    let mut config_name: Option<String> = None;
1675    for expr in with {
1676        match expr {
1677            sp::Expr::BinaryOp {
1678                left,
1679                op: sp::BinaryOperator::Eq,
1680                right,
1681            } => {
1682                let key = match left.as_ref() {
1683                    sp::Expr::Identifier(id) => id.value.to_ascii_lowercase(),
1684                    other => {
1685                        return Err(SqlError::Unsupported(format!(
1686                            "FTS WITH option key: {other}"
1687                        )));
1688                    }
1689                };
1690                if key != "config" {
1691                    return Err(SqlError::Unsupported(format!(
1692                        "FTS WITH option: unknown key '{key}' (only 'config' supported)"
1693                    )));
1694                }
1695                let val = match right.as_ref() {
1696                    sp::Expr::Value(v) => match &v.value {
1697                        sp::Value::SingleQuotedString(s) => s.clone(),
1698                        sp::Value::DoubleQuotedString(s) => s.clone(),
1699                        other => {
1700                            return Err(SqlError::Parse(format!(
1701                                "FTS config value must be a string literal, got: {other}"
1702                            )))
1703                        }
1704                    },
1705                    sp::Expr::Identifier(id) => id.value.clone(),
1706                    other => {
1707                        return Err(SqlError::Parse(format!(
1708                            "FTS config value must be a string literal, got: {other}"
1709                        )));
1710                    }
1711                };
1712                config_name = Some(val);
1713            }
1714            other => {
1715                return Err(SqlError::Unsupported(format!(
1716                    "FTS WITH option must be `key = value`, got: {other}"
1717                )));
1718            }
1719        }
1720    }
1721    let name = config_name.unwrap_or_else(|| "english".to_string());
1722    Ok(crate::fts::TokenizerKind::from_name(&name)?.as_config_id())
1723}
1724
1725fn validate_partial_index_predicate(expr: &Expr) -> Result<()> {
1726    let mut bad: Option<&'static str> = None;
1727    visit_expr(expr, &mut |e| {
1728        if bad.is_some() {
1729            return;
1730        }
1731        match e {
1732            Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
1733                bad = Some("subqueries");
1734            }
1735            Expr::CountStar => bad = Some("aggregates"),
1736            Expr::WindowFunction { .. } => bad = Some("window functions"),
1737            Expr::Parameter(_) => bad = Some("bound parameters"),
1738            Expr::QualifiedColumn { .. } => bad = Some("cross-table references"),
1739            Expr::Function { name, .. } => {
1740                if is_aggregate_function(name) {
1741                    bad = Some("aggregates");
1742                } else if !is_immutable_function(name) {
1743                    bad = Some("non-deterministic functions");
1744                }
1745            }
1746            _ => {}
1747        }
1748    });
1749    if let Some(reason) = bad {
1750        return Err(SqlError::Unsupported(format!(
1751            "partial index predicate cannot contain {reason}"
1752        )));
1753    }
1754    Ok(())
1755}
1756
1757fn is_aggregate_function(name: &str) -> bool {
1758    matches!(
1759        name.to_ascii_lowercase().as_str(),
1760        "count" | "sum" | "avg" | "min" | "max" | "total" | "group_concat" | "string_agg"
1761    )
1762}
1763
1764fn is_immutable_function(name: &str) -> bool {
1765    !matches!(
1766        name.to_ascii_lowercase().as_str(),
1767        "now"
1768            | "current_timestamp"
1769            | "current_date"
1770            | "current_time"
1771            | "localtimestamp"
1772            | "localtime"
1773            | "random"
1774            | "rand"
1775    )
1776}
1777
1778fn convert_create_trigger(ct: sp::CreateTrigger) -> Result<Statement> {
1779    let name = object_name_to_string(&ct.name);
1780    let target = object_name_to_string(&ct.table_name);
1781
1782    let timing = match ct.period {
1783        Some(sp::TriggerPeriod::Before) => TriggerTiming::Before,
1784        Some(sp::TriggerPeriod::After) => TriggerTiming::After,
1785        Some(sp::TriggerPeriod::InsteadOf) => TriggerTiming::InsteadOf,
1786        _ => {
1787            return Err(SqlError::Parse(
1788                "CREATE TRIGGER requires BEFORE, AFTER, or INSTEAD OF".into(),
1789            ));
1790        }
1791    };
1792
1793    let mut events: Vec<TriggerEvent> = Vec::with_capacity(ct.events.len());
1794    for ev in &ct.events {
1795        let mapped = match ev {
1796            sp::TriggerEvent::Insert => TriggerEvent::Insert,
1797            sp::TriggerEvent::Delete => TriggerEvent::Delete,
1798            sp::TriggerEvent::Update(cols) => {
1799                TriggerEvent::Update(cols.iter().map(|i| i.value.to_ascii_lowercase()).collect())
1800            }
1801            sp::TriggerEvent::Truncate => {
1802                return Err(SqlError::Unsupported(
1803                    "TRUNCATE triggers are not supported".into(),
1804                ));
1805            }
1806        };
1807        events.push(mapped);
1808    }
1809    if events.is_empty() {
1810        return Err(SqlError::Parse(
1811            "CREATE TRIGGER requires at least one event (INSERT/UPDATE/DELETE)".into(),
1812        ));
1813    }
1814
1815    let granularity = match ct.trigger_object {
1816        Some(sp::TriggerObjectKind::For(sp::TriggerObject::Statement))
1817        | Some(sp::TriggerObjectKind::ForEach(sp::TriggerObject::Statement)) => {
1818            TriggerGranularity::ForEachStatement
1819        }
1820        // FOR EACH ROW is the default per SQLite semantics when omitted.
1821        _ => TriggerGranularity::ForEachRow,
1822    };
1823
1824    if timing == TriggerTiming::InsteadOf && granularity == TriggerGranularity::ForEachStatement {
1825        return Err(SqlError::Unsupported(
1826            "INSTEAD OF triggers must be FOR EACH ROW".into(),
1827        ));
1828    }
1829
1830    let mut referencing: Option<TransitionTables> = None;
1831    if !ct.referencing.is_empty() {
1832        let mut new_alias: Option<String> = None;
1833        let mut old_alias: Option<String> = None;
1834        for r in &ct.referencing {
1835            let alias = object_name_to_string(&r.transition_relation_name);
1836            match r.refer_type {
1837                sp::TriggerReferencingType::NewTable => new_alias = Some(alias),
1838                sp::TriggerReferencingType::OldTable => old_alias = Some(alias),
1839            }
1840        }
1841        referencing = Some(TransitionTables {
1842            new_table_alias: new_alias,
1843            old_table_alias: old_alias,
1844        });
1845    }
1846
1847    let when_expr = ct.condition.as_ref().map(convert_expr).transpose()?;
1848    let when_sql = ct.condition.as_ref().map(|e| e.to_string());
1849
1850    let inner_statements: &[sp::Statement] = match &ct.statements {
1851        Some(sp::ConditionalStatements::Sequence { statements })
1852        | Some(sp::ConditionalStatements::BeginEnd(sp::BeginEndStatements {
1853            statements, ..
1854        })) => statements,
1855        None => {
1856            return Err(SqlError::Parse(
1857                "CREATE TRIGGER body must contain BEGIN ... END or one or more statements".into(),
1858            ));
1859        }
1860    };
1861    let body: Vec<Statement> = inner_statements
1862        .iter()
1863        .cloned()
1864        .map(convert_statement)
1865        .collect::<Result<Vec<_>>>()?;
1866    // Store body as `;`-joined statement strings (no BEGIN/END wrapper) so the body
1867    // executor can parse it via `parse_sql_multi`.
1868    let body_sql: String = inner_statements
1869        .iter()
1870        .map(|s| s.to_string())
1871        .collect::<Vec<_>>()
1872        .join(";");
1873
1874    Ok(Statement::CreateTrigger(Box::new(CreateTriggerStmt {
1875        name,
1876        timing,
1877        events,
1878        target,
1879        granularity,
1880        referencing,
1881        when_sql,
1882        when_expr,
1883        body_sql,
1884        body,
1885        if_not_exists: false,
1886    })))
1887}
1888
1889fn convert_create_view(cv: sp::CreateView) -> Result<Statement> {
1890    let name = object_name_to_string(&cv.name);
1891    let sql = cv.query.to_string();
1892
1893    let parsed_select = parse_select_query(&sql)?;
1894
1895    if cv.materialized {
1896        return Ok(Statement::CreateMaterializedView(Box::new(
1897            CreateMatviewStmt {
1898                name,
1899                select_sql: sql,
1900                select_parsed: parsed_select,
1901                with_data: true,
1902                if_not_exists: cv.if_not_exists,
1903            },
1904        )));
1905    }
1906
1907    let column_aliases: Vec<String> = cv
1908        .columns
1909        .iter()
1910        .map(|c| c.name.value.to_ascii_lowercase())
1911        .collect();
1912
1913    Ok(Statement::CreateView(CreateViewStmt {
1914        name,
1915        sql,
1916        column_aliases,
1917        or_replace: cv.or_replace,
1918        if_not_exists: cv.if_not_exists,
1919    }))
1920}
1921
1922fn parse_select_query(sql: &str) -> Result<SelectQuery> {
1923    let stmts = parse_sql_multi(sql)?;
1924    if stmts.len() != 1 {
1925        return Err(SqlError::Parse(
1926            "matview body must be a single SELECT statement".into(),
1927        ));
1928    }
1929    match stmts.into_iter().next().unwrap() {
1930        Statement::Select(sq) => Ok(*sq),
1931        _ => Err(SqlError::Parse(
1932            "matview body must be a SELECT statement".into(),
1933        )),
1934    }
1935}
1936
1937fn convert_insert(insert: sp::Insert) -> Result<Statement> {
1938    let table = match &insert.table {
1939        sp::TableObject::TableName(name) => object_name_to_string(name).to_ascii_lowercase(),
1940        _ => return Err(SqlError::Unsupported("INSERT into non-table object".into())),
1941    };
1942
1943    let columns: Vec<String> = insert
1944        .columns
1945        .iter()
1946        .map(|c| c.value.to_ascii_lowercase())
1947        .collect();
1948
1949    let query = insert
1950        .source
1951        .ok_or_else(|| SqlError::Parse("INSERT requires VALUES or SELECT".into()))?;
1952
1953    let source = match *query.body {
1954        sp::SetExpr::Values(sp::Values { rows, .. }) => {
1955            let mut result = Vec::new();
1956            for row in rows {
1957                let mut exprs = Vec::new();
1958                for expr in row {
1959                    exprs.push(convert_expr(&expr)?);
1960                }
1961                result.push(exprs);
1962            }
1963            InsertSource::Values(result)
1964        }
1965        _ => {
1966            let (ctes, recursive) = if let Some(ref with) = query.with {
1967                convert_with(with)?
1968            } else {
1969                (vec![], false)
1970            };
1971            let body = convert_query_body(&query)?;
1972            InsertSource::Select(Box::new(SelectQuery {
1973                ctes,
1974                recursive,
1975                body,
1976            }))
1977        }
1978    };
1979
1980    let on_conflict = insert.on.as_ref().map(convert_on_insert).transpose()?;
1981    let returning = convert_returning(insert.returning.as_deref())?;
1982
1983    Ok(Statement::Insert(InsertStmt {
1984        table,
1985        columns,
1986        source,
1987        on_conflict,
1988        returning,
1989    }))
1990}
1991
1992fn convert_on_insert(on: &sp::OnInsert) -> Result<OnConflictClause> {
1993    match on {
1994        sp::OnInsert::OnConflict(oc) => {
1995            let target = oc
1996                .conflict_target
1997                .as_ref()
1998                .map(convert_conflict_target)
1999                .transpose()?;
2000            let action = convert_on_conflict_action(&oc.action)?;
2001            Ok(OnConflictClause { target, action })
2002        }
2003        sp::OnInsert::DuplicateKeyUpdate(_) => Err(SqlError::Parse(
2004            "ON DUPLICATE KEY UPDATE is MySQL-specific; use ON CONFLICT".into(),
2005        )),
2006        _ => Err(SqlError::Parse("unsupported ON INSERT clause".into())),
2007    }
2008}
2009
2010fn convert_conflict_target(target: &sp::ConflictTarget) -> Result<ConflictTarget> {
2011    match target {
2012        sp::ConflictTarget::Columns(cols) => Ok(ConflictTarget::Columns(
2013            cols.iter().map(|c| c.value.to_ascii_lowercase()).collect(),
2014        )),
2015        sp::ConflictTarget::OnConstraint(name) => {
2016            if name.0.len() > 1 {
2017                return Err(SqlError::Parse(
2018                    "qualified constraint names not supported".into(),
2019                ));
2020            }
2021            Ok(ConflictTarget::Constraint(
2022                object_name_to_string(name).to_ascii_lowercase(),
2023            ))
2024        }
2025    }
2026}
2027
2028fn convert_on_conflict_action(action: &sp::OnConflictAction) -> Result<OnConflictAction> {
2029    match action {
2030        sp::OnConflictAction::DoNothing => Ok(OnConflictAction::DoNothing),
2031        sp::OnConflictAction::DoUpdate(du) => {
2032            let assignments = du
2033                .assignments
2034                .iter()
2035                .map(|a| {
2036                    let col = match &a.target {
2037                        sp::AssignmentTarget::ColumnName(name) => {
2038                            object_name_to_string(name).to_ascii_lowercase()
2039                        }
2040                        _ => {
2041                            return Err(SqlError::Unsupported(
2042                                "tuple assignment in ON CONFLICT".into(),
2043                            ))
2044                        }
2045                    };
2046                    let expr = convert_expr(&a.value)?;
2047                    Ok((col, expr))
2048                })
2049                .collect::<Result<_>>()?;
2050            let where_clause = du.selection.as_ref().map(convert_expr).transpose()?;
2051            Ok(OnConflictAction::DoUpdate {
2052                assignments,
2053                where_clause,
2054            })
2055        }
2056    }
2057}
2058
2059fn convert_select_body(select: &sp::Select) -> Result<SelectStmt> {
2060    let distinct = match &select.distinct {
2061        Some(sp::Distinct::Distinct) => true,
2062        Some(sp::Distinct::On(_)) => {
2063            return Err(SqlError::Unsupported("DISTINCT ON".into()));
2064        }
2065        _ => false,
2066    };
2067
2068    let (from, from_alias, from_subquery, from_args, from_json_table, joins) =
2069        if select.from.is_empty() {
2070            (String::new(), None, None, None, None, vec![])
2071        } else {
2072            let first_twj = &select.from[0];
2073            let (first_name, first_alias, first_sub, first_args, first_jt) =
2074                convert_from_relation(&first_twj.relation)?;
2075            let mut joins: Vec<JoinClause> = first_twj
2076                .joins
2077                .iter()
2078                .map(convert_join)
2079                .collect::<Result<Vec<_>>>()?;
2080            for extra_twj in &select.from[1..] {
2081                let (extra_name, extra_alias, extra_sub, extra_args, extra_jt) =
2082                    convert_from_relation(&extra_twj.relation)?;
2083                if extra_jt.is_some() {
2084                    return Err(SqlError::Unsupported(
2085                        "JSON_TABLE in extra FROM positions not supported".into(),
2086                    ));
2087                }
2088                joins.push(JoinClause {
2089                    join_type: JoinType::Cross,
2090                    table: TableRef {
2091                        name: extra_name,
2092                        alias: extra_alias,
2093                        args: extra_args,
2094                    },
2095                    subquery: extra_sub,
2096                    on_clause: None,
2097                });
2098                for j in &extra_twj.joins {
2099                    joins.push(convert_join(j)?);
2100                }
2101            }
2102            (
2103                first_name,
2104                first_alias,
2105                first_sub,
2106                first_args,
2107                first_jt,
2108                joins,
2109            )
2110        };
2111    for j in &joins {
2112        if let Some(sub) = &j.subquery {
2113            if sub.lateral && matches!(j.join_type, JoinType::Right | JoinType::FullOuter) {
2114                return Err(SqlError::Unsupported(
2115                    "LATERAL is not allowed on the right side of RIGHT JOIN or FULL OUTER JOIN"
2116                        .into(),
2117                ));
2118            }
2119        }
2120    }
2121
2122    let columns: Vec<SelectColumn> = select
2123        .projection
2124        .iter()
2125        .map(convert_select_item)
2126        .collect::<Result<_>>()?;
2127
2128    let where_clause = select.selection.as_ref().map(convert_expr).transpose()?;
2129
2130    let group_by = match &select.group_by {
2131        sp::GroupByExpr::Expressions(exprs, _) => {
2132            exprs.iter().map(convert_expr).collect::<Result<_>>()?
2133        }
2134        sp::GroupByExpr::All(_) => {
2135            return Err(SqlError::Unsupported("GROUP BY ALL".into()));
2136        }
2137    };
2138
2139    let having = select.having.as_ref().map(convert_expr).transpose()?;
2140
2141    Ok(SelectStmt {
2142        columns,
2143        from,
2144        from_alias,
2145        from_subquery,
2146        from_args,
2147        from_json_table,
2148        joins,
2149        distinct,
2150        where_clause,
2151        order_by: vec![],
2152        limit: None,
2153        offset: None,
2154        group_by,
2155        having,
2156    })
2157}
2158
2159type FromRelation = (
2160    String,
2161    Option<String>,
2162    Option<Box<DerivedTable>>,
2163    Option<Vec<Expr>>,
2164    Option<Box<JsonTableSpec>>,
2165);
2166
2167fn convert_from_relation(relation: &sp::TableFactor) -> Result<FromRelation> {
2168    match relation {
2169        sp::TableFactor::Table {
2170            name, alias, args, ..
2171        } => {
2172            let table_name = object_name_to_string(name);
2173            let alias_str = alias.as_ref().map(|a| a.name.value.clone());
2174            let args_converted = match args {
2175                Some(table_args) => {
2176                    let mut converted = Vec::with_capacity(table_args.args.len());
2177                    for arg in &table_args.args {
2178                        match arg {
2179                            sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => {
2180                                converted.push(convert_expr(e)?);
2181                            }
2182                            _ => {
2183                                return Err(SqlError::Unsupported(
2184                                    "non-positional table function argument".into(),
2185                                ));
2186                            }
2187                        }
2188                    }
2189                    Some(converted)
2190                }
2191                None => None,
2192            };
2193            Ok((table_name, alias_str, None, args_converted, None))
2194        }
2195        sp::TableFactor::Derived {
2196            lateral,
2197            subquery,
2198            alias,
2199            ..
2200        } => {
2201            let alias_name = match alias {
2202                Some(a) => a.name.value.clone(),
2203                None => return Err(SqlError::Unsupported("derived table requires alias".into())),
2204            };
2205            let inner = convert_select_query(subquery)?;
2206            for cte in &inner.ctes {
2207                if matches!(
2208                    &cte.body,
2209                    QueryBody::Insert(_) | QueryBody::Update(_) | QueryBody::Delete(_)
2210                ) {
2211                    return Err(SqlError::Unsupported(
2212                        "WITH-DML inside subqueries (PG forbids)".into(),
2213                    ));
2214                }
2215            }
2216            let derived = DerivedTable {
2217                query: Box::new(inner),
2218                lateral: *lateral,
2219                alias: alias_name.clone(),
2220            };
2221            Ok((alias_name, None, Some(Box::new(derived)), None, None))
2222        }
2223        sp::TableFactor::JsonTable {
2224            json_expr,
2225            json_path,
2226            columns,
2227            alias,
2228        } => {
2229            let alias_name = match alias {
2230                Some(a) => a.name.value.clone(),
2231                None => "json_table".to_string(),
2232            };
2233            let source = convert_expr(json_expr)?;
2234            let root_path = json_path_value_to_string(json_path)?;
2235            let cols = columns
2236                .iter()
2237                .map(convert_json_table_column)
2238                .collect::<Result<Vec<_>>>()?;
2239            let spec = JsonTableSpec {
2240                source,
2241                root_path,
2242                columns: cols,
2243            };
2244            Ok((alias_name, None, None, None, Some(Box::new(spec))))
2245        }
2246        _ => Err(SqlError::Unsupported("non-table FROM source".into())),
2247    }
2248}
2249
2250fn json_path_value_to_string(v: &sp::Value) -> Result<String> {
2251    use sp::Value as V;
2252    match v {
2253        V::SingleQuotedString(s)
2254        | V::DoubleQuotedString(s)
2255        | V::DollarQuotedString(sp::DollarQuotedString { value: s, .. })
2256        | V::TripleSingleQuotedString(s)
2257        | V::TripleDoubleQuotedString(s) => Ok(s.clone()),
2258        other => Err(SqlError::Unsupported(format!(
2259            "JSON_TABLE path must be a string literal, got: {other}"
2260        ))),
2261    }
2262}
2263
2264fn convert_json_table_column(c: &sp::JsonTableColumn) -> Result<JsonTableCol> {
2265    match c {
2266        sp::JsonTableColumn::Named(n) => {
2267            let path = json_path_value_to_string(&n.path)?;
2268            Ok(JsonTableCol::Named {
2269                name: n.name.value.clone(),
2270                ty: convert_data_type(&n.r#type)?,
2271                path,
2272                exists: n.exists,
2273            })
2274        }
2275        sp::JsonTableColumn::ForOrdinality(ident) => Ok(JsonTableCol::Ordinality {
2276            name: ident.value.clone(),
2277        }),
2278        sp::JsonTableColumn::Nested(n) => {
2279            let path = json_path_value_to_string(&n.path)?;
2280            let columns = n
2281                .columns
2282                .iter()
2283                .map(convert_json_table_column)
2284                .collect::<Result<Vec<_>>>()?;
2285            Ok(JsonTableCol::Nested { path, columns })
2286        }
2287    }
2288}
2289
2290fn convert_set_expr(set_expr: &sp::SetExpr) -> Result<QueryBody> {
2291    match set_expr {
2292        sp::SetExpr::Select(sel) => Ok(QueryBody::Select(Box::new(convert_select_body(sel)?))),
2293        sp::SetExpr::Insert(stmt) => match convert_statement(stmt.clone())? {
2294            Statement::Insert(ins) => Ok(QueryBody::Insert(Box::new(ins))),
2295            _ => Err(SqlError::Parse("expected INSERT in WITH-DML body".into())),
2296        },
2297        sp::SetExpr::Update(stmt) => match convert_statement(stmt.clone())? {
2298            Statement::Update(upd) => Ok(QueryBody::Update(Box::new(upd))),
2299            _ => Err(SqlError::Parse("expected UPDATE in WITH-DML body".into())),
2300        },
2301        sp::SetExpr::Delete(stmt) => match convert_statement(stmt.clone())? {
2302            Statement::Delete(del) => Ok(QueryBody::Delete(Box::new(del))),
2303            _ => Err(SqlError::Parse("expected DELETE in WITH-DML body".into())),
2304        },
2305        sp::SetExpr::SetOperation {
2306            op,
2307            set_quantifier,
2308            left,
2309            right,
2310        } => {
2311            let set_op = match op {
2312                sp::SetOperator::Union => SetOp::Union,
2313                sp::SetOperator::Intersect => SetOp::Intersect,
2314                sp::SetOperator::Except | sp::SetOperator::Minus => SetOp::Except,
2315            };
2316            let all = match set_quantifier {
2317                sp::SetQuantifier::All => true,
2318                sp::SetQuantifier::None | sp::SetQuantifier::Distinct => false,
2319                _ => {
2320                    return Err(SqlError::Unsupported("BY NAME set operations".into()));
2321                }
2322            };
2323            Ok(QueryBody::Compound(Box::new(CompoundSelect {
2324                op: set_op,
2325                all,
2326                left: Box::new(convert_set_expr(left)?),
2327                right: Box::new(convert_set_expr(right)?),
2328                order_by: vec![],
2329                limit: None,
2330                offset: None,
2331            })))
2332        }
2333        _ => Err(SqlError::Unsupported("unsupported set expression".into())),
2334    }
2335}
2336
2337fn convert_query_body(query: &sp::Query) -> Result<QueryBody> {
2338    let mut body = convert_set_expr(&query.body)?;
2339
2340    let order_by = if let Some(ref ob) = query.order_by {
2341        match &ob.kind {
2342            sp::OrderByKind::Expressions(exprs) => exprs
2343                .iter()
2344                .map(convert_order_by_expr)
2345                .collect::<Result<_>>()?,
2346            sp::OrderByKind::All { .. } => {
2347                return Err(SqlError::Unsupported("ORDER BY ALL".into()));
2348            }
2349        }
2350    } else {
2351        vec![]
2352    };
2353
2354    let (limit, offset) = match &query.limit_clause {
2355        Some(sp::LimitClause::LimitOffset { limit, offset, .. }) => {
2356            let l = limit.as_ref().map(convert_expr).transpose()?;
2357            let o = offset
2358                .as_ref()
2359                .map(|o| convert_expr(&o.value))
2360                .transpose()?;
2361            (l, o)
2362        }
2363        Some(sp::LimitClause::OffsetCommaLimit { limit, offset }) => {
2364            let l = Some(convert_expr(limit)?);
2365            let o = Some(convert_expr(offset)?);
2366            (l, o)
2367        }
2368        None => (None, None),
2369    };
2370
2371    match &mut body {
2372        QueryBody::Select(sel) => {
2373            sel.order_by = order_by;
2374            sel.limit = limit;
2375            sel.offset = offset;
2376        }
2377        QueryBody::Compound(comp) => {
2378            comp.order_by = order_by;
2379            comp.limit = limit;
2380            comp.offset = offset;
2381        }
2382        QueryBody::Insert(_) | QueryBody::Update(_) | QueryBody::Delete(_) => {
2383            if !order_by.is_empty() || limit.is_some() || offset.is_some() {
2384                return Err(SqlError::Parse(
2385                    "ORDER BY / LIMIT / OFFSET not allowed on DML CTE body".into(),
2386                ));
2387            }
2388        }
2389    }
2390
2391    Ok(body)
2392}
2393
2394fn convert_subquery(query: &sp::Query) -> Result<SelectStmt> {
2395    if query.with.is_some() {
2396        return Err(SqlError::Unsupported("CTEs in subqueries".into()));
2397    }
2398    match convert_query_body(query)? {
2399        QueryBody::Select(s) => Ok(*s),
2400        QueryBody::Compound(_) => Err(SqlError::Unsupported(
2401            "UNION/INTERSECT/EXCEPT in subqueries".into(),
2402        )),
2403        QueryBody::Insert(_) | QueryBody::Update(_) | QueryBody::Delete(_) => Err(
2404            SqlError::Unsupported("WITH-DML inside subqueries (PG forbids)".into()),
2405        ),
2406    }
2407}
2408
2409fn convert_with(with: &sp::With) -> Result<(Vec<CteDefinition>, bool)> {
2410    let mut names = rustc_hash::FxHashSet::default();
2411    let mut ctes = Vec::new();
2412    for cte in &with.cte_tables {
2413        let name = cte.alias.name.value.to_ascii_lowercase();
2414        if !names.insert(name.clone()) {
2415            return Err(SqlError::DuplicateCteName(name));
2416        }
2417        let column_aliases: Vec<String> = cte
2418            .alias
2419            .columns
2420            .iter()
2421            .map(|c| c.name.value.to_ascii_lowercase())
2422            .collect();
2423        let body = convert_query_body(&cte.query)?;
2424        ctes.push(CteDefinition {
2425            name,
2426            column_aliases,
2427            body,
2428        });
2429    }
2430    Ok((ctes, with.recursive))
2431}
2432
2433fn convert_query(query: sp::Query) -> Result<Statement> {
2434    let sq = convert_select_query(&query)?;
2435    Ok(Statement::Select(Box::new(sq)))
2436}
2437
2438fn convert_select_query(query: &sp::Query) -> Result<SelectQuery> {
2439    let (ctes, recursive) = if let Some(ref with) = query.with {
2440        convert_with(with)?
2441    } else {
2442        (vec![], false)
2443    };
2444    let body = convert_query_body(query)?;
2445    Ok(SelectQuery {
2446        ctes,
2447        recursive,
2448        body,
2449    })
2450}
2451
2452fn convert_join(join: &sp::Join) -> Result<JoinClause> {
2453    let (join_type, constraint) = match &join.join_operator {
2454        sp::JoinOperator::Inner(c) => (JoinType::Inner, Some(c)),
2455        sp::JoinOperator::Join(c) => (JoinType::Inner, Some(c)),
2456        sp::JoinOperator::CrossJoin(c) => (JoinType::Cross, Some(c)),
2457        sp::JoinOperator::Left(c) | sp::JoinOperator::LeftOuter(c) => (JoinType::Left, Some(c)),
2458        sp::JoinOperator::LeftSemi(c) => (JoinType::Left, Some(c)),
2459        sp::JoinOperator::LeftAnti(c) => (JoinType::Left, Some(c)),
2460        sp::JoinOperator::Right(c) | sp::JoinOperator::RightOuter(c) => (JoinType::Right, Some(c)),
2461        sp::JoinOperator::RightSemi(c) => (JoinType::Right, Some(c)),
2462        sp::JoinOperator::RightAnti(c) => (JoinType::Right, Some(c)),
2463        sp::JoinOperator::FullOuter(c) => (JoinType::FullOuter, Some(c)),
2464        other => return Err(SqlError::Unsupported(format!("join type: {other:?}"))),
2465    };
2466
2467    let (name, alias, subquery, args, json_table) = convert_from_relation(&join.relation)?;
2468    if json_table.is_some() {
2469        return Err(SqlError::Unsupported(
2470            "JSON_TABLE on right side of JOIN".into(),
2471        ));
2472    }
2473
2474    let on_clause = match constraint {
2475        Some(sp::JoinConstraint::On(expr)) => Some(convert_expr(expr)?),
2476        Some(sp::JoinConstraint::None) | None => None,
2477        Some(other) => return Err(SqlError::Unsupported(format!("join constraint: {other:?}"))),
2478    };
2479
2480    Ok(JoinClause {
2481        join_type,
2482        table: TableRef { name, alias, args },
2483        subquery,
2484        on_clause,
2485    })
2486}
2487
2488fn convert_update(update: sp::Update) -> Result<Statement> {
2489    let table = match &update.table.relation {
2490        sp::TableFactor::Table { name, .. } => object_name_to_string(name),
2491        _ => return Err(SqlError::Unsupported("non-table UPDATE target".into())),
2492    };
2493
2494    let assignments = update
2495        .assignments
2496        .iter()
2497        .map(|a| {
2498            let col = match &a.target {
2499                sp::AssignmentTarget::ColumnName(name) => object_name_to_string(name),
2500                _ => return Err(SqlError::Unsupported("tuple assignment".into())),
2501            };
2502            let expr = convert_expr(&a.value)?;
2503            Ok((col, expr))
2504        })
2505        .collect::<Result<_>>()?;
2506
2507    let where_clause = update.selection.as_ref().map(convert_expr).transpose()?;
2508    let returning = convert_returning(update.returning.as_deref())?;
2509
2510    Ok(Statement::Update(UpdateStmt {
2511        table,
2512        assignments,
2513        where_clause,
2514        returning,
2515    }))
2516}
2517
2518fn convert_truncate(t: sp::Truncate) -> Result<Statement> {
2519    if matches!(t.cascade, Some(sp::CascadeOption::Cascade)) {
2520        return Err(SqlError::Unsupported(
2521            "TRUNCATE CASCADE is planned for v0.13".into(),
2522        ));
2523    }
2524    if t.if_exists {
2525        return Err(SqlError::Unsupported("TRUNCATE IF EXISTS".into()));
2526    }
2527    if t.partitions.is_some() {
2528        return Err(SqlError::Unsupported("TRUNCATE PARTITION".into()));
2529    }
2530    if t.on_cluster.is_some() {
2531        return Err(SqlError::Unsupported("TRUNCATE ON CLUSTER".into()));
2532    }
2533    if t.table_names.is_empty() {
2534        return Err(SqlError::Parse(
2535            "TRUNCATE requires at least one table".into(),
2536        ));
2537    }
2538
2539    let tables: Vec<String> = t
2540        .table_names
2541        .iter()
2542        .map(|tt| object_name_to_string(&tt.name))
2543        .collect();
2544
2545    Ok(Statement::Truncate(TruncateStmt { tables }))
2546}
2547
2548fn convert_delete(delete: sp::Delete) -> Result<Statement> {
2549    let table_name = match &delete.from {
2550        sp::FromTable::WithFromKeyword(tables) => {
2551            if tables.len() != 1 {
2552                return Err(SqlError::Unsupported("multi-table DELETE".into()));
2553            }
2554            match &tables[0].relation {
2555                sp::TableFactor::Table { name, .. } => object_name_to_string(name),
2556                _ => return Err(SqlError::Unsupported("non-table DELETE target".into())),
2557            }
2558        }
2559        sp::FromTable::WithoutKeyword(tables) => {
2560            if tables.len() != 1 {
2561                return Err(SqlError::Unsupported("multi-table DELETE".into()));
2562            }
2563            match &tables[0].relation {
2564                sp::TableFactor::Table { name, .. } => object_name_to_string(name),
2565                _ => return Err(SqlError::Unsupported("non-table DELETE target".into())),
2566            }
2567        }
2568    };
2569
2570    let where_clause = delete.selection.as_ref().map(convert_expr).transpose()?;
2571    let returning = convert_returning(delete.returning.as_deref())?;
2572
2573    Ok(Statement::Delete(DeleteStmt {
2574        table: table_name,
2575        where_clause,
2576        returning,
2577    }))
2578}
2579
2580fn convert_expr(expr: &sp::Expr) -> Result<Expr> {
2581    match expr {
2582        sp::Expr::Value(v) => convert_value(&v.value),
2583        sp::Expr::Identifier(ident) => Ok(Expr::Column(ident.value.to_ascii_lowercase())),
2584        sp::Expr::CompoundIdentifier(parts) => {
2585            if parts.len() == 2 {
2586                Ok(Expr::QualifiedColumn {
2587                    table: parts[0].value.to_ascii_lowercase(),
2588                    column: parts[1].value.to_ascii_lowercase(),
2589                })
2590            } else {
2591                Ok(Expr::Column(
2592                    parts.last().unwrap().value.to_ascii_lowercase(),
2593                ))
2594            }
2595        }
2596        sp::Expr::BinaryOp { left, op, right } => {
2597            let bin_op = convert_bin_op(op)?;
2598            Ok(Expr::BinaryOp {
2599                left: Box::new(convert_expr(left)?),
2600                op: bin_op,
2601                right: Box::new(convert_expr(right)?),
2602            })
2603        }
2604        sp::Expr::UnaryOp { op, expr } => {
2605            let unary_op = match op {
2606                sp::UnaryOperator::Minus => UnaryOp::Neg,
2607                sp::UnaryOperator::Not => UnaryOp::Not,
2608                _ => return Err(SqlError::Unsupported(format!("unary op: {op}"))),
2609            };
2610            Ok(Expr::UnaryOp {
2611                op: unary_op,
2612                expr: Box::new(convert_expr(expr)?),
2613            })
2614        }
2615        sp::Expr::IsNull(e) => Ok(Expr::IsNull(Box::new(convert_expr(e)?))),
2616        sp::Expr::IsNotNull(e) => Ok(Expr::IsNotNull(Box::new(convert_expr(e)?))),
2617        sp::Expr::Nested(e) => convert_expr(e),
2618        sp::Expr::Function(func) => convert_function(func),
2619        sp::Expr::InSubquery {
2620            expr: e,
2621            subquery,
2622            negated,
2623        } => {
2624            let inner_expr = convert_expr(e)?;
2625            let stmt = convert_subquery(subquery)?;
2626            Ok(Expr::InSubquery {
2627                expr: Box::new(inner_expr),
2628                subquery: Box::new(stmt),
2629                negated: *negated,
2630            })
2631        }
2632        sp::Expr::InList {
2633            expr: e,
2634            list,
2635            negated,
2636        } => {
2637            let inner_expr = convert_expr(e)?;
2638            let items = list.iter().map(convert_expr).collect::<Result<Vec<_>>>()?;
2639            Ok(Expr::InList {
2640                expr: Box::new(inner_expr),
2641                list: items,
2642                negated: *negated,
2643            })
2644        }
2645        sp::Expr::Exists { subquery, negated } => {
2646            let stmt = convert_subquery(subquery)?;
2647            Ok(Expr::Exists {
2648                subquery: Box::new(stmt),
2649                negated: *negated,
2650            })
2651        }
2652        sp::Expr::Subquery(query) => {
2653            let stmt = convert_subquery(query)?;
2654            Ok(Expr::ScalarSubquery(Box::new(stmt)))
2655        }
2656        sp::Expr::AnyOp {
2657            left,
2658            compare_op,
2659            right,
2660            ..
2661        } => convert_quantified(left, compare_op, right, Quantifier::Any),
2662        sp::Expr::AllOp {
2663            left,
2664            compare_op,
2665            right,
2666        } => convert_quantified(left, compare_op, right, Quantifier::All),
2667        sp::Expr::Array(sp::Array { elem, .. }) => {
2668            let elems: Result<Vec<Expr>> = elem.iter().map(convert_expr).collect();
2669            Ok(Expr::ArrayLiteral(elems?))
2670        }
2671        sp::Expr::Between {
2672            expr: e,
2673            negated,
2674            low,
2675            high,
2676        } => Ok(Expr::Between {
2677            expr: Box::new(convert_expr(e)?),
2678            low: Box::new(convert_expr(low)?),
2679            high: Box::new(convert_expr(high)?),
2680            negated: *negated,
2681        }),
2682        sp::Expr::Like {
2683            expr: e,
2684            negated,
2685            pattern,
2686            escape_char,
2687            ..
2688        } => {
2689            let esc = escape_char
2690                .as_ref()
2691                .map(convert_escape_value)
2692                .transpose()?
2693                .map(Box::new);
2694            Ok(Expr::Like {
2695                expr: Box::new(convert_expr(e)?),
2696                pattern: Box::new(convert_expr(pattern)?),
2697                escape: esc,
2698                negated: *negated,
2699            })
2700        }
2701        sp::Expr::ILike {
2702            expr: e,
2703            negated,
2704            pattern,
2705            escape_char,
2706            ..
2707        } => {
2708            let esc = escape_char
2709                .as_ref()
2710                .map(convert_escape_value)
2711                .transpose()?
2712                .map(Box::new);
2713            Ok(Expr::Like {
2714                expr: Box::new(convert_expr(e)?),
2715                pattern: Box::new(convert_expr(pattern)?),
2716                escape: esc,
2717                negated: *negated,
2718            })
2719        }
2720        sp::Expr::Case {
2721            operand,
2722            conditions,
2723            else_result,
2724            ..
2725        } => {
2726            let op = operand
2727                .as_ref()
2728                .map(|e| convert_expr(e))
2729                .transpose()?
2730                .map(Box::new);
2731            let conds: Vec<(Expr, Expr)> = conditions
2732                .iter()
2733                .map(|cw| Ok((convert_expr(&cw.condition)?, convert_expr(&cw.result)?)))
2734                .collect::<Result<_>>()?;
2735            let else_r = else_result
2736                .as_ref()
2737                .map(|e| convert_expr(e))
2738                .transpose()?
2739                .map(Box::new);
2740            Ok(Expr::Case {
2741                operand: op,
2742                conditions: conds,
2743                else_result: else_r,
2744            })
2745        }
2746        sp::Expr::Cast {
2747            expr: e,
2748            data_type: dt,
2749            ..
2750        } => {
2751            if let (sp::DataType::Custom(name, modifiers), sp::Expr::Value(v)) = (dt, e.as_ref()) {
2752                if modifiers.is_empty() && name.0.len() == 1 && matches!(v.value, sp::Value::Null) {
2753                    if let sp::ObjectNamePart::Identifier(id) = &name.0[0] {
2754                        return Ok(Expr::TypedNullRecord(id.value.clone()));
2755                    }
2756                }
2757            }
2758            let target = convert_data_type(dt)?;
2759            let inner = convert_expr(e)?;
2760            if matches!(target, DataType::Json | DataType::Jsonb) {
2761                if let Expr::Literal(Value::Text(s)) = &inner {
2762                    let v = if matches!(target, DataType::Json) {
2763                        crate::json::validate_text(s.as_str())?;
2764                        Value::Json(s.clone())
2765                    } else {
2766                        crate::json::text_to_jsonb(s.as_str())?
2767                    };
2768                    return Ok(Expr::Literal(v));
2769                }
2770            }
2771            Ok(Expr::Cast {
2772                expr: Box::new(inner),
2773                data_type: target,
2774            })
2775        }
2776        sp::Expr::Collate {
2777            expr: e,
2778            collation: name,
2779        } => {
2780            let coll_name = object_name_to_string(name);
2781            let coll = crate::types::Collation::from_name(&coll_name).ok_or_else(|| {
2782                SqlError::Unsupported(format!(
2783                    "collation '{coll_name}' not supported (BINARY/NOCASE/RTRIM only)"
2784                ))
2785            })?;
2786            Ok(Expr::Collate {
2787                expr: Box::new(convert_expr(e)?),
2788                collation: coll,
2789            })
2790        }
2791        sp::Expr::Substring {
2792            expr: e,
2793            substring_from,
2794            substring_for,
2795            ..
2796        } => {
2797            let mut args = vec![convert_expr(e)?];
2798            if let Some(from) = substring_from {
2799                args.push(convert_expr(from)?);
2800            }
2801            if let Some(f) = substring_for {
2802                args.push(convert_expr(f)?);
2803            }
2804            Ok(Expr::Function {
2805                name: "SUBSTR".into(),
2806                args,
2807                distinct: false,
2808            })
2809        }
2810        sp::Expr::Trim {
2811            expr: e,
2812            trim_where,
2813            trim_what,
2814            trim_characters,
2815        } => {
2816            let fn_name = match trim_where {
2817                Some(sp::TrimWhereField::Leading) => "LTRIM",
2818                Some(sp::TrimWhereField::Trailing) => "RTRIM",
2819                _ => "TRIM",
2820            };
2821            let mut args = vec![convert_expr(e)?];
2822            if let Some(what) = trim_what {
2823                args.push(convert_expr(what)?);
2824            } else if let Some(chars) = trim_characters {
2825                if let Some(first) = chars.first() {
2826                    args.push(convert_expr(first)?);
2827                }
2828            }
2829            Ok(Expr::Function {
2830                name: fn_name.into(),
2831                args,
2832                distinct: false,
2833            })
2834        }
2835        sp::Expr::Ceil { expr: e, .. } => Ok(Expr::Function {
2836            name: "CEIL".into(),
2837            args: vec![convert_expr(e)?],
2838            distinct: false,
2839        }),
2840        sp::Expr::Floor { expr: e, .. } => Ok(Expr::Function {
2841            name: "FLOOR".into(),
2842            args: vec![convert_expr(e)?],
2843            distinct: false,
2844        }),
2845        sp::Expr::Position { expr: e, r#in } => Ok(Expr::Function {
2846            name: "INSTR".into(),
2847            args: vec![convert_expr(r#in)?, convert_expr(e)?],
2848            distinct: false,
2849        }),
2850        // Typed literal: `DATE '2024-01-15'`, `TIMESTAMP '...'`, etc.
2851        sp::Expr::TypedString(ts) => {
2852            let raw = match &ts.value.value {
2853                sp::Value::SingleQuotedString(s) => s.clone(),
2854                sp::Value::DoubleQuotedString(s) => s.clone(),
2855                other => other.to_string(),
2856            };
2857            convert_typed_string(&ts.data_type, &raw)
2858        }
2859        // INTERVAL '...' — sqlparser emits Expr::Interval with a boxed Expr value + field qualifiers
2860        sp::Expr::Interval(iv) => convert_interval_expr(iv),
2861        // EXTRACT(field FROM src)
2862        sp::Expr::Extract { field, expr: e, .. } => {
2863            let field_name = match field {
2864                sp::DateTimeField::Year => "year",
2865                sp::DateTimeField::Month => "month",
2866                sp::DateTimeField::Week(_) => "week",
2867                sp::DateTimeField::Day => "day",
2868                sp::DateTimeField::Date => "day",
2869                sp::DateTimeField::Hour => "hour",
2870                sp::DateTimeField::Minute => "minute",
2871                sp::DateTimeField::Second => "second",
2872                sp::DateTimeField::Millisecond => "milliseconds",
2873                sp::DateTimeField::Microsecond => "microseconds",
2874                sp::DateTimeField::Microseconds => "microseconds",
2875                sp::DateTimeField::Milliseconds => "milliseconds",
2876                sp::DateTimeField::Dow => "dow",
2877                sp::DateTimeField::Isodow => "isodow",
2878                sp::DateTimeField::Doy => "doy",
2879                sp::DateTimeField::Epoch => "epoch",
2880                sp::DateTimeField::Quarter => "quarter",
2881                sp::DateTimeField::Decade => "decade",
2882                sp::DateTimeField::Century => "century",
2883                sp::DateTimeField::Millennium => "millennium",
2884                sp::DateTimeField::Isoyear => "isoyear",
2885                sp::DateTimeField::Julian => "julian",
2886                other => {
2887                    return Err(SqlError::InvalidExtractField(format!("{other:?}")));
2888                }
2889            };
2890            Ok(Expr::Function {
2891                name: "EXTRACT".into(),
2892                args: vec![
2893                    Expr::Literal(Value::Text(field_name.into())),
2894                    convert_expr(e)?,
2895                ],
2896                distinct: false,
2897            })
2898        }
2899        // `AT TIME ZONE 'zone'` operator — desugars to AT_TIMEZONE(ts, zone) scalar function.
2900        sp::Expr::AtTimeZone {
2901            timestamp,
2902            time_zone,
2903        } => Ok(Expr::Function {
2904            name: "AT_TIMEZONE".into(),
2905            args: vec![convert_expr(timestamp)?, convert_expr(time_zone)?],
2906            distinct: false,
2907        }),
2908        _ => Err(SqlError::Unsupported(format!("expression: {expr}"))),
2909    }
2910}
2911
2912fn convert_value(val: &sp::Value) -> Result<Expr> {
2913    match val {
2914        sp::Value::Number(n, _) => {
2915            if let Ok(i) = n.parse::<i64>() {
2916                Ok(Expr::Literal(Value::Integer(i)))
2917            } else if let Ok(f) = n.parse::<f64>() {
2918                Ok(Expr::Literal(Value::Real(f)))
2919            } else {
2920                Err(SqlError::InvalidValue(format!("cannot parse number: {n}")))
2921            }
2922        }
2923        sp::Value::SingleQuotedString(s) => Ok(Expr::Literal(Value::Text(s.as_str().into()))),
2924        sp::Value::Boolean(b) => Ok(Expr::Literal(Value::Boolean(*b))),
2925        sp::Value::Null => Ok(Expr::Literal(Value::Null)),
2926        sp::Value::Placeholder(s) => {
2927            let idx_str = s
2928                .strip_prefix('$')
2929                .ok_or_else(|| SqlError::Parse(format!("invalid placeholder: {s}")))?;
2930            let idx: usize = idx_str
2931                .parse()
2932                .map_err(|_| SqlError::Parse(format!("invalid placeholder index: {s}")))?;
2933            if idx == 0 {
2934                return Err(SqlError::Parse("placeholder index must be >= 1".into()));
2935            }
2936            Ok(Expr::Parameter(idx))
2937        }
2938        _ => Err(SqlError::Unsupported(format!("value type: {val}"))),
2939    }
2940}
2941
2942fn convert_typed_string(dt: &sp::DataType, value: &str) -> Result<Expr> {
2943    let s = value.trim_matches('\'');
2944    match dt {
2945        sp::DataType::Date => {
2946            let d = crate::datetime::parse_date(s)?;
2947            Ok(Expr::Literal(Value::Date(d)))
2948        }
2949        sp::DataType::Time(_, _) => {
2950            let t = crate::datetime::parse_time(s)?;
2951            Ok(Expr::Literal(Value::Time(t)))
2952        }
2953        sp::DataType::Timestamp(_, _) => {
2954            let t = crate::datetime::parse_timestamp(s)?;
2955            Ok(Expr::Literal(Value::Timestamp(t)))
2956        }
2957        sp::DataType::Interval { .. } => {
2958            let (months, days, micros) = crate::datetime::parse_interval(s)?;
2959            Ok(Expr::Literal(Value::Interval {
2960                months,
2961                days,
2962                micros,
2963            }))
2964        }
2965        _ => {
2966            let target = convert_data_type(dt)?;
2967            Ok(Expr::Cast {
2968                expr: Box::new(Expr::Literal(Value::Text(s.into()))),
2969                data_type: target,
2970            })
2971        }
2972    }
2973}
2974
2975fn convert_interval_expr(iv: &sp::Interval) -> Result<Expr> {
2976    let raw = match iv.value.as_ref() {
2977        sp::Expr::Value(v) => match &v.value {
2978            sp::Value::SingleQuotedString(s) => s.clone(),
2979            sp::Value::Number(n, _) => n.clone(),
2980            other => {
2981                return Err(SqlError::InvalidIntervalLiteral(format!(
2982                    "unsupported inner value: {other}"
2983                )))
2984            }
2985        },
2986        other => {
2987            return Err(SqlError::InvalidIntervalLiteral(format!(
2988                "unsupported inner expr: {other}"
2989            )))
2990        }
2991    };
2992
2993    // SQL-standard form `INTERVAL '5' DAY` — append the unit to the literal.
2994    let with_unit = if let Some(field) = &iv.leading_field {
2995        let unit_name = match field {
2996            sp::DateTimeField::Year => "years",
2997            sp::DateTimeField::Month => "months",
2998            sp::DateTimeField::Week(_) => "weeks",
2999            sp::DateTimeField::Day => "days",
3000            sp::DateTimeField::Hour => "hours",
3001            sp::DateTimeField::Minute => "minutes",
3002            sp::DateTimeField::Second => "seconds",
3003            _ => {
3004                return Err(SqlError::InvalidIntervalLiteral(format!(
3005                    "unsupported leading field: {field:?}"
3006                )))
3007            }
3008        };
3009        format!("{raw} {unit_name}")
3010    } else {
3011        raw
3012    };
3013
3014    let (months, days, micros) = crate::datetime::parse_interval(&with_unit)?;
3015    Ok(Expr::Literal(Value::Interval {
3016        months,
3017        days,
3018        micros,
3019    }))
3020}
3021
3022fn convert_escape_value(val: &sp::Value) -> Result<Expr> {
3023    match val {
3024        sp::Value::SingleQuotedString(s) => Ok(Expr::Literal(Value::Text(s.as_str().into()))),
3025        _ => Err(SqlError::Unsupported(format!("ESCAPE value: {val}"))),
3026    }
3027}
3028
3029fn convert_quantified(
3030    left: &sp::Expr,
3031    compare_op: &sp::BinaryOperator,
3032    right: &sp::Expr,
3033    quantifier: Quantifier,
3034) -> Result<Expr> {
3035    let left_expr = convert_expr(left)?;
3036    let op = convert_bin_op(compare_op)?;
3037    if !matches!(
3038        op,
3039        BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq
3040    ) {
3041        return Err(SqlError::Unsupported(format!(
3042            "ANY/ALL only supports comparison operators, got {op:?}"
3043        )));
3044    }
3045    let rhs = match right {
3046        sp::Expr::Subquery(query) => {
3047            let stmt = convert_subquery(query)?;
3048            QuantifiedRhs::Subquery(Box::new(stmt))
3049        }
3050        other => QuantifiedRhs::Array(Box::new(convert_expr(other)?)),
3051    };
3052    Ok(Expr::Quantified {
3053        left: Box::new(left_expr),
3054        op,
3055        quantifier,
3056        right: rhs,
3057    })
3058}
3059
3060fn convert_bin_op(op: &sp::BinaryOperator) -> Result<BinOp> {
3061    match op {
3062        sp::BinaryOperator::Plus => Ok(BinOp::Add),
3063        sp::BinaryOperator::Minus => Ok(BinOp::Sub),
3064        sp::BinaryOperator::Multiply => Ok(BinOp::Mul),
3065        sp::BinaryOperator::Divide => Ok(BinOp::Div),
3066        sp::BinaryOperator::Modulo => Ok(BinOp::Mod),
3067        sp::BinaryOperator::Eq => Ok(BinOp::Eq),
3068        sp::BinaryOperator::NotEq => Ok(BinOp::NotEq),
3069        sp::BinaryOperator::Lt => Ok(BinOp::Lt),
3070        sp::BinaryOperator::Gt => Ok(BinOp::Gt),
3071        sp::BinaryOperator::LtEq => Ok(BinOp::LtEq),
3072        sp::BinaryOperator::GtEq => Ok(BinOp::GtEq),
3073        sp::BinaryOperator::And => Ok(BinOp::And),
3074        sp::BinaryOperator::Or => Ok(BinOp::Or),
3075        sp::BinaryOperator::StringConcat => Ok(BinOp::Concat),
3076        sp::BinaryOperator::Arrow => Ok(BinOp::JsonGet),
3077        sp::BinaryOperator::LongArrow => Ok(BinOp::JsonGetText),
3078        sp::BinaryOperator::HashArrow => Ok(BinOp::JsonPath),
3079        sp::BinaryOperator::HashLongArrow => Ok(BinOp::JsonPathText),
3080        sp::BinaryOperator::AtArrow => Ok(BinOp::JsonContains),
3081        sp::BinaryOperator::ArrowAt => Ok(BinOp::JsonContainedBy),
3082        sp::BinaryOperator::Question => Ok(BinOp::JsonHasKey),
3083        sp::BinaryOperator::QuestionPipe => Ok(BinOp::JsonHasAnyKey),
3084        sp::BinaryOperator::QuestionAnd => Ok(BinOp::JsonHasAllKeys),
3085        sp::BinaryOperator::HashMinus => Ok(BinOp::JsonDeletePath),
3086        sp::BinaryOperator::AtQuestion => Ok(BinOp::JsonPathExists),
3087        sp::BinaryOperator::AtAt => Ok(BinOp::JsonPathMatch),
3088        sp::BinaryOperator::Custom(s) if s == "@?_tz" => Ok(BinOp::JsonPathExistsTz),
3089        sp::BinaryOperator::Custom(s) if s == "@@_tz" => Ok(BinOp::JsonPathMatchTz),
3090        _ => Err(SqlError::Unsupported(format!("binary op: {op}"))),
3091    }
3092}
3093
3094fn convert_function(func: &sp::Function) -> Result<Expr> {
3095    let name = object_name_to_string(&func.name).to_ascii_uppercase();
3096
3097    let (args, is_count_star, distinct) = match &func.args {
3098        sp::FunctionArguments::List(list) => {
3099            let distinct = matches!(
3100                list.duplicate_treatment,
3101                Some(sp::DuplicateTreatment::Distinct)
3102            );
3103            if list.args.is_empty() && name == "COUNT" {
3104                (vec![], true, distinct)
3105            } else {
3106                let mut count_star = false;
3107                let args = list
3108                    .args
3109                    .iter()
3110                    .map(|arg| match arg {
3111                        sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => convert_expr(e),
3112                        sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Wildcard) => {
3113                            if name == "COUNT" {
3114                                count_star = true;
3115                                Ok(Expr::CountStar)
3116                            } else {
3117                                Err(SqlError::Unsupported(format!("{name}(*)")))
3118                            }
3119                        }
3120                        _ => Err(SqlError::Unsupported(format!(
3121                            "function arg type in {name}"
3122                        ))),
3123                    })
3124                    .collect::<Result<Vec<_>>>()?;
3125                if name == "COUNT" && args.len() == 1 && count_star {
3126                    (vec![], true, distinct)
3127                } else {
3128                    (args, false, distinct)
3129                }
3130            }
3131        }
3132        sp::FunctionArguments::None => {
3133            if name == "COUNT" {
3134                (vec![], true, false)
3135            } else {
3136                (vec![], false, false)
3137            }
3138        }
3139        sp::FunctionArguments::Subquery(_) => {
3140            return Err(SqlError::Unsupported("subquery in function".into()));
3141        }
3142    };
3143
3144    // Window function: check OVER before any other special handling
3145    if let Some(over) = &func.over {
3146        let spec = match over {
3147            sp::WindowType::WindowSpec(ws) => convert_window_spec(ws)?,
3148            sp::WindowType::NamedWindow(_) => {
3149                return Err(SqlError::Unsupported("named windows".into()));
3150            }
3151        };
3152        return Ok(Expr::WindowFunction { name, args, spec });
3153    }
3154
3155    // Non-window special forms
3156    if is_count_star {
3157        return Ok(Expr::CountStar);
3158    }
3159
3160    if name == "COALESCE" {
3161        if args.is_empty() {
3162            return Err(SqlError::Parse(
3163                "COALESCE requires at least one argument".into(),
3164            ));
3165        }
3166        return Ok(Expr::Coalesce(args));
3167    }
3168
3169    if name == "NULLIF" {
3170        if args.len() != 2 {
3171            return Err(SqlError::Parse(
3172                "NULLIF requires exactly two arguments".into(),
3173            ));
3174        }
3175        return Ok(Expr::Case {
3176            operand: None,
3177            conditions: vec![(
3178                Expr::BinaryOp {
3179                    left: Box::new(args[0].clone()),
3180                    op: BinOp::Eq,
3181                    right: Box::new(args[1].clone()),
3182                },
3183                Expr::Literal(Value::Null),
3184            )],
3185            else_result: Some(Box::new(args[0].clone())),
3186        });
3187    }
3188
3189    if name == "IIF" {
3190        if args.len() != 3 {
3191            return Err(SqlError::Parse(
3192                "IIF requires exactly three arguments".into(),
3193            ));
3194        }
3195        return Ok(Expr::Case {
3196            operand: None,
3197            conditions: vec![(args[0].clone(), args[1].clone())],
3198            else_result: Some(Box::new(args[2].clone())),
3199        });
3200    }
3201
3202    Ok(Expr::Function {
3203        name,
3204        args,
3205        distinct,
3206    })
3207}
3208
3209fn convert_window_spec(ws: &sp::WindowSpec) -> Result<WindowSpec> {
3210    let partition_by = ws
3211        .partition_by
3212        .iter()
3213        .map(convert_expr)
3214        .collect::<Result<Vec<_>>>()?;
3215    let order_by = ws
3216        .order_by
3217        .iter()
3218        .map(convert_order_by_expr)
3219        .collect::<Result<Vec<_>>>()?;
3220    let frame = ws
3221        .window_frame
3222        .as_ref()
3223        .map(convert_window_frame)
3224        .transpose()?;
3225    Ok(WindowSpec {
3226        partition_by,
3227        order_by,
3228        frame,
3229    })
3230}
3231
3232fn convert_window_frame(wf: &sp::WindowFrame) -> Result<WindowFrame> {
3233    let units = match wf.units {
3234        sp::WindowFrameUnits::Rows => WindowFrameUnits::Rows,
3235        sp::WindowFrameUnits::Range => WindowFrameUnits::Range,
3236        sp::WindowFrameUnits::Groups => {
3237            return Err(SqlError::Unsupported("GROUPS window frame".into()));
3238        }
3239    };
3240    let start = convert_window_frame_bound(&wf.start_bound)?;
3241    let end = match &wf.end_bound {
3242        Some(b) => convert_window_frame_bound(b)?,
3243        None => WindowFrameBound::CurrentRow,
3244    };
3245    Ok(WindowFrame { units, start, end })
3246}
3247
3248fn convert_window_frame_bound(b: &sp::WindowFrameBound) -> Result<WindowFrameBound> {
3249    match b {
3250        sp::WindowFrameBound::CurrentRow => Ok(WindowFrameBound::CurrentRow),
3251        sp::WindowFrameBound::Preceding(None) => Ok(WindowFrameBound::UnboundedPreceding),
3252        sp::WindowFrameBound::Preceding(Some(e)) => {
3253            Ok(WindowFrameBound::Preceding(Box::new(convert_expr(e)?)))
3254        }
3255        sp::WindowFrameBound::Following(None) => Ok(WindowFrameBound::UnboundedFollowing),
3256        sp::WindowFrameBound::Following(Some(e)) => {
3257            Ok(WindowFrameBound::Following(Box::new(convert_expr(e)?)))
3258        }
3259    }
3260}
3261
3262fn convert_returning(items: Option<&[sp::SelectItem]>) -> Result<Option<Vec<SelectColumn>>> {
3263    match items {
3264        None => Ok(None),
3265        Some(items) => {
3266            let cols = items
3267                .iter()
3268                .map(convert_returning_item)
3269                .collect::<Result<Vec<_>>>()?;
3270            Ok(Some(cols))
3271        }
3272    }
3273}
3274
3275fn convert_returning_item(item: &sp::SelectItem) -> Result<SelectColumn> {
3276    match item {
3277        sp::SelectItem::Wildcard(_) => Ok(SelectColumn::AllColumns),
3278        sp::SelectItem::UnnamedExpr(e) => {
3279            reject_aggregate_or_window(e, "RETURNING")?;
3280            Ok(SelectColumn::Expr {
3281                expr: convert_expr(e)?,
3282                alias: None,
3283            })
3284        }
3285        sp::SelectItem::ExprWithAlias { expr, alias } => {
3286            reject_aggregate_or_window(expr, "RETURNING")?;
3287            Ok(SelectColumn::Expr {
3288                expr: convert_expr(expr)?,
3289                alias: Some(alias.value.clone()),
3290            })
3291        }
3292        sp::SelectItem::QualifiedWildcard(kind, _) => match kind {
3293            sp::SelectItemQualifiedWildcardKind::ObjectName(name) => {
3294                let s = object_name_to_string(name);
3295                if s.eq_ignore_ascii_case("old") {
3296                    Ok(SelectColumn::AllFromOld)
3297                } else if s.eq_ignore_ascii_case("new") {
3298                    Ok(SelectColumn::AllFromNew)
3299                } else {
3300                    Err(SqlError::Unsupported(format!(
3301                        "RETURNING {s}.* — only old.* and new.* qualified wildcards allowed"
3302                    )))
3303                }
3304            }
3305            sp::SelectItemQualifiedWildcardKind::Expr(_) => {
3306                Err(SqlError::Unsupported("expression.* in RETURNING".into()))
3307            }
3308        },
3309    }
3310}
3311
3312fn reject_aggregate_or_window(expr: &sp::Expr, ctx: &str) -> Result<()> {
3313    use sp::Expr as E;
3314    match expr {
3315        E::Function(f) => {
3316            if f.over.is_some() {
3317                return Err(SqlError::Unsupported(format!(
3318                    "window functions are not allowed in {ctx}"
3319                )));
3320            }
3321            let name = f
3322                .name
3323                .0
3324                .last()
3325                .map(|p| match p {
3326                    sp::ObjectNamePart::Identifier(id) => id.value.to_ascii_uppercase(),
3327                    _ => String::new(),
3328                })
3329                .unwrap_or_default();
3330            if matches!(
3331                name.as_str(),
3332                "COUNT"
3333                    | "SUM"
3334                    | "AVG"
3335                    | "MIN"
3336                    | "MAX"
3337                    | "GROUP_CONCAT"
3338                    | "STRING_AGG"
3339                    | "ARRAY_AGG"
3340                    | "BIT_AND"
3341                    | "BIT_OR"
3342                    | "BOOL_AND"
3343                    | "BOOL_OR"
3344                    | "EVERY"
3345                    | "STDDEV"
3346                    | "STDDEV_POP"
3347                    | "STDDEV_SAMP"
3348                    | "VARIANCE"
3349                    | "VAR_POP"
3350                    | "VAR_SAMP"
3351            ) {
3352                return Err(SqlError::Unsupported(format!(
3353                    "aggregate functions are not allowed in {ctx}"
3354                )));
3355            }
3356            for arg in walk_function_args(f) {
3357                reject_aggregate_or_window(arg, ctx)?;
3358            }
3359            Ok(())
3360        }
3361        E::BinaryOp { left, right, .. } => {
3362            reject_aggregate_or_window(left, ctx)?;
3363            reject_aggregate_or_window(right, ctx)
3364        }
3365        E::UnaryOp { expr, .. } => reject_aggregate_or_window(expr, ctx),
3366        E::Cast { expr, .. } => reject_aggregate_or_window(expr, ctx),
3367        E::Nested(e) => reject_aggregate_or_window(e, ctx),
3368        E::Case {
3369            conditions,
3370            else_result,
3371            ..
3372        } => {
3373            for cwt in conditions {
3374                reject_aggregate_or_window(&cwt.condition, ctx)?;
3375                reject_aggregate_or_window(&cwt.result, ctx)?;
3376            }
3377            if let Some(e) = else_result {
3378                reject_aggregate_or_window(e, ctx)?;
3379            }
3380            Ok(())
3381        }
3382        _ => Ok(()),
3383    }
3384}
3385
3386fn walk_function_args(f: &sp::Function) -> Vec<&sp::Expr> {
3387    use sp::FunctionArguments as FA;
3388    let mut out = Vec::new();
3389    if let FA::List(args) = &f.args {
3390        for a in &args.args {
3391            if let sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) = a {
3392                out.push(e);
3393            }
3394        }
3395    }
3396    out
3397}
3398
3399fn convert_select_item(item: &sp::SelectItem) -> Result<SelectColumn> {
3400    match item {
3401        sp::SelectItem::Wildcard(_) => Ok(SelectColumn::AllColumns),
3402        sp::SelectItem::UnnamedExpr(e) => {
3403            let expr = convert_expr(e)?;
3404            Ok(SelectColumn::Expr { expr, alias: None })
3405        }
3406        sp::SelectItem::ExprWithAlias { expr, alias } => {
3407            let expr = convert_expr(expr)?;
3408            Ok(SelectColumn::Expr {
3409                expr,
3410                alias: Some(alias.value.clone()),
3411            })
3412        }
3413        sp::SelectItem::QualifiedWildcard(_, _) => {
3414            Err(SqlError::Unsupported("qualified wildcard (table.*)".into()))
3415        }
3416    }
3417}
3418
3419fn convert_order_by_expr(expr: &sp::OrderByExpr) -> Result<OrderByItem> {
3420    let e = convert_expr(&expr.expr)?;
3421    let descending = expr.options.asc.map(|asc| !asc).unwrap_or(false);
3422    let nulls_first = expr.options.nulls_first;
3423
3424    Ok(OrderByItem {
3425        expr: e,
3426        descending,
3427        nulls_first,
3428    })
3429}
3430
3431fn convert_data_type(dt: &sp::DataType) -> Result<DataType> {
3432    match dt {
3433        sp::DataType::Int(_)
3434        | sp::DataType::Integer(_)
3435        | sp::DataType::BigInt(_)
3436        | sp::DataType::SmallInt(_)
3437        | sp::DataType::TinyInt(_)
3438        | sp::DataType::Int2(_)
3439        | sp::DataType::Int4(_)
3440        | sp::DataType::Int8(_) => Ok(DataType::Integer),
3441
3442        sp::DataType::Real
3443        | sp::DataType::Double(..)
3444        | sp::DataType::DoublePrecision
3445        | sp::DataType::Float(_)
3446        | sp::DataType::Float4
3447        | sp::DataType::Float64 => Ok(DataType::Real),
3448
3449        sp::DataType::Varchar(_)
3450        | sp::DataType::Text
3451        | sp::DataType::Char(_)
3452        | sp::DataType::Character(_)
3453        | sp::DataType::String(_) => Ok(DataType::Text),
3454
3455        sp::DataType::Blob(_) | sp::DataType::Bytea => Ok(DataType::Blob),
3456
3457        sp::DataType::Boolean | sp::DataType::Bool => Ok(DataType::Boolean),
3458
3459        sp::DataType::Date => Ok(DataType::Date),
3460        sp::DataType::Time(_, _) => Ok(DataType::Time),
3461        sp::DataType::Timestamp(_, _) => Ok(DataType::Timestamp),
3462        sp::DataType::Interval { .. } => Ok(DataType::Interval),
3463
3464        sp::DataType::JSON => Ok(DataType::Json),
3465        sp::DataType::JSONB => Ok(DataType::Jsonb),
3466
3467        sp::DataType::TsVector => Ok(DataType::TsVector),
3468        sp::DataType::TsQuery => Ok(DataType::TsQuery),
3469
3470        _ => Err(SqlError::Unsupported(format!("data type: {dt}"))),
3471    }
3472}
3473
3474fn object_name_to_string(name: &sp::ObjectName) -> String {
3475    name.0
3476        .iter()
3477        .filter_map(|p| match p {
3478            sp::ObjectNamePart::Identifier(ident) => Some(ident.value.clone()),
3479            _ => None,
3480        })
3481        .collect::<Vec<_>>()
3482        .join(".")
3483}
3484
3485#[cfg(test)]
3486#[path = "parser_tests.rs"]
3487mod tests;