Skip to main content

citadel_sql/
parser.rs

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