Skip to main content

citadel_sql/
parser.rs

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