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