Skip to main content

citadel_sql/
parser.rs

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