Skip to main content

citadel_sql/
parser.rs

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