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