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