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