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