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