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