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