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 (rewritten, no_data_flags) = strip_matview_with_no_data(sql);
690 let stmts =
691 crate::dialect::parse_statements(&rewritten).map_err(|e| SqlError::Parse(e.to_string()))?;
692
693 if stmts.is_empty() {
694 return Err(SqlError::Parse("empty SQL".into()));
695 }
696 if stmts.len() > 1 {
697 return Err(SqlError::Unsupported("multiple statements".into()));
698 }
699
700 let mut converted = convert_statement(stmts.into_iter().next().unwrap())?;
701 apply_no_data_flags(std::slice::from_mut(&mut converted), &no_data_flags);
702 Ok(converted)
703}
704
705pub fn parse_sql_multi(sql: &str) -> Result<Vec<Statement>> {
706 let (rewritten, no_data_flags) = strip_matview_with_no_data(sql);
707 let mut out: Vec<Statement> = Vec::new();
708 for (start, end) in split_statement_spans(&rewritten) {
709 let stmt_sql = &rewritten[start..end];
710 if let Some(parsed) = try_parse_refresh_matview(stmt_sql) {
711 out.push(parsed?);
712 } else {
713 let raw = crate::dialect::parse_statements(stmt_sql)
714 .map_err(|e| SqlError::Parse(e.to_string()))?;
715 for s in raw {
716 out.push(convert_statement(s)?);
717 }
718 }
719 }
720 if out.is_empty() {
721 return Err(SqlError::Parse("empty SQL".into()));
722 }
723 apply_no_data_flags(&mut out, &no_data_flags);
724 Ok(out)
725}
726
727fn split_statement_spans(sql: &str) -> Vec<(usize, usize)> {
728 let bytes = sql.as_bytes();
729 let mut spans: Vec<(usize, usize)> = Vec::new();
730 let mut stmt_start = 0usize;
731 let mut i = 0usize;
732 while i < bytes.len() {
733 let b = bytes[i];
734 if b == b'-' && i + 1 < bytes.len() && bytes[i + 1] == b'-' {
735 while i < bytes.len() && bytes[i] != b'\n' {
736 i += 1;
737 }
738 continue;
739 }
740 if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
741 i += 2;
742 while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
743 i += 1;
744 }
745 if i + 1 < bytes.len() {
746 i += 2;
747 }
748 continue;
749 }
750 if b == b'\'' {
751 i += 1;
752 while i < bytes.len() {
753 if bytes[i] == b'\'' {
754 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
755 i += 2;
756 } else {
757 i += 1;
758 break;
759 }
760 } else {
761 i += 1;
762 }
763 }
764 continue;
765 }
766 if b == b'"' {
767 i += 1;
768 while i < bytes.len() {
769 if bytes[i] == b'"' {
770 if i + 1 < bytes.len() && bytes[i + 1] == b'"' {
771 i += 2;
772 } else {
773 i += 1;
774 break;
775 }
776 } else {
777 i += 1;
778 }
779 }
780 continue;
781 }
782 if b == b'$' {
783 let mut j = i + 1;
784 while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
785 j += 1;
786 }
787 if j < bytes.len() && bytes[j] == b'$' {
788 let tag_len = j - i + 1;
789 i = j + 1;
790 while i + tag_len <= bytes.len() {
791 if bytes[i..i + tag_len] == bytes[(j - tag_len + 1)..=j] {
792 i += tag_len;
793 break;
794 }
795 i += 1;
796 }
797 continue;
798 }
799 i += 1;
800 continue;
801 }
802 if b == b';' {
803 if !sql[stmt_start..i].trim().is_empty() {
804 spans.push((stmt_start, i));
805 }
806 i += 1;
807 stmt_start = i;
808 continue;
809 }
810 i += 1;
811 }
812 if stmt_start < bytes.len() && !sql[stmt_start..].trim().is_empty() {
813 spans.push((stmt_start, bytes.len()));
814 }
815 spans
816}
817
818fn apply_no_data_flags(stmts: &mut [Statement], flags: &[bool]) {
819 let mut iter = flags.iter();
820 for stmt in stmts.iter_mut() {
821 if let Statement::CreateMaterializedView(boxed) = stmt {
822 if let Some(&no_data) = iter.next() {
823 boxed.with_data = !no_data;
824 }
825 }
826 }
827}
828
829fn try_parse_refresh_matview(sql: &str) -> Option<Result<Statement>> {
831 let trimmed = sql.trim().trim_end_matches(';').trim();
832 let lower = trimmed.to_ascii_lowercase();
833 let after = lower.strip_prefix("refresh materialized view")?;
834 let after = after.trim_start();
835 let (concurrently, rest_lower) = match after.strip_prefix("concurrently") {
836 Some(r) if r.starts_with(char::is_whitespace) => (true, r.trim_start()),
837 _ => (false, after),
838 };
839 if rest_lower.is_empty() {
840 return Some(Err(SqlError::Parse(
841 "REFRESH MATERIALIZED VIEW requires a name".into(),
842 )));
843 }
844 let name = rest_lower
845 .split_whitespace()
846 .next()
847 .unwrap_or("")
848 .trim_matches(|c: char| c == '"' || c == ';')
849 .to_string();
850 if name.is_empty() {
851 return Some(Err(SqlError::Parse(
852 "REFRESH MATERIALIZED VIEW requires a name".into(),
853 )));
854 }
855 Some(Ok(Statement::RefreshMaterializedView(RefreshMatviewStmt {
856 name,
857 concurrently,
858 })))
859}
860
861fn strip_matview_with_no_data(sql: &str) -> (String, Vec<bool>) {
862 let bytes = sql.as_bytes();
863 let mut out = String::with_capacity(sql.len());
864 let mut flags: Vec<bool> = Vec::new();
865 let mut stmt_start = 0usize;
866 let mut i = 0usize;
867
868 let push_one = |out: &mut String, i: &mut usize, bytes: &[u8], sql: &str| {
869 let b = bytes[*i];
870 if b < 0x80 {
871 out.push(b as char);
872 *i += 1;
873 } else {
874 let len = if b >= 0xF0 {
875 4
876 } else if b >= 0xE0 {
877 3
878 } else if b >= 0xC0 {
879 2
880 } else {
881 1
882 };
883 let end = (*i + len).min(bytes.len());
884 out.push_str(&sql[*i..end]);
885 *i = end;
886 }
887 };
888
889 while i < bytes.len() {
890 let b = bytes[i];
891 if b == b'-' && i + 1 < bytes.len() && bytes[i + 1] == b'-' {
892 while i < bytes.len() && bytes[i] != b'\n' {
893 push_one(&mut out, &mut i, bytes, sql);
894 }
895 continue;
896 }
897 if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
898 out.push('/');
899 out.push('*');
900 i += 2;
901 while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
902 push_one(&mut out, &mut i, bytes, sql);
903 }
904 if i + 1 < bytes.len() {
905 out.push('*');
906 out.push('/');
907 i += 2;
908 }
909 continue;
910 }
911 if b == b'\'' {
912 out.push('\'');
913 i += 1;
914 while i < bytes.len() {
915 if bytes[i] == b'\'' {
916 if i + 1 < bytes.len() && bytes[i + 1] == b'\'' {
917 out.push('\'');
918 out.push('\'');
919 i += 2;
920 } else {
921 out.push('\'');
922 i += 1;
923 break;
924 }
925 } else {
926 push_one(&mut out, &mut i, bytes, sql);
927 }
928 }
929 continue;
930 }
931 if b == b'"' {
932 out.push('"');
933 i += 1;
934 while i < bytes.len() {
935 if bytes[i] == b'"' {
936 if i + 1 < bytes.len() && bytes[i + 1] == b'"' {
937 out.push('"');
938 out.push('"');
939 i += 2;
940 } else {
941 out.push('"');
942 i += 1;
943 break;
944 }
945 } else {
946 push_one(&mut out, &mut i, bytes, sql);
947 }
948 }
949 continue;
950 }
951 if b == b'$' {
952 let mut j = i + 1;
953 while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
954 j += 1;
955 }
956 if j < bytes.len() && bytes[j] == b'$' {
957 let tag_len = j - i + 1;
958 out.push_str(&sql[i..=j]);
959 i = j + 1;
960 while i + tag_len <= bytes.len() {
961 if bytes[i..i + tag_len] == bytes[(j - tag_len + 1)..=j] {
962 out.push_str(&sql[i..i + tag_len]);
963 i += tag_len;
964 break;
965 }
966 push_one(&mut out, &mut i, bytes, sql);
967 }
968 continue;
969 }
970 out.push('$');
971 i += 1;
972 continue;
973 }
974 if b == b';' {
975 let stmt_text = &out[stmt_start..];
976 if statement_is_create_matview(stmt_text) {
977 match find_with_no_data_suffix(stmt_text) {
978 Some(truncate_to) => {
979 out.truncate(stmt_start + truncate_to);
980 flags.push(true);
981 }
982 None => flags.push(false),
983 }
984 }
985 out.push(';');
986 i += 1;
987 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
988 out.push(bytes[i] as char);
989 i += 1;
990 }
991 stmt_start = out.len();
992 continue;
993 }
994 push_one(&mut out, &mut i, bytes, sql);
995 }
996
997 let stmt_text = &out[stmt_start..];
998 if !stmt_text.trim().is_empty() && statement_is_create_matview(stmt_text) {
999 match find_with_no_data_suffix(stmt_text) {
1000 Some(truncate_to) => {
1001 out.truncate(stmt_start + truncate_to);
1002 flags.push(true);
1003 }
1004 None => flags.push(false),
1005 }
1006 }
1007
1008 (out, flags)
1009}
1010
1011fn statement_is_create_matview(stmt: &str) -> bool {
1012 let stripped = strip_leading_ws_and_comments(stmt);
1013 let lower = stripped.to_ascii_lowercase();
1014 let s = lower.as_str();
1015 let after_create = match strip_kw_lower(s, "create") {
1016 Some(r) => r,
1017 None => return false,
1018 };
1019 let after_orrep = if let Some(r) = strip_kw_lower(after_create, "or") {
1020 if let Some(r2) = strip_kw_lower(r, "replace") {
1021 r2
1022 } else {
1023 after_create
1024 }
1025 } else {
1026 after_create
1027 };
1028 let after_temp = if let Some(r) = strip_kw_lower(after_orrep, "temporary") {
1029 r
1030 } else if let Some(r) = strip_kw_lower(after_orrep, "temp") {
1031 r
1032 } else {
1033 after_orrep
1034 };
1035 let after_mv = match strip_kw_lower(after_temp, "materialized") {
1036 Some(r) => r,
1037 None => return false,
1038 };
1039 strip_kw_lower(after_mv, "view").is_some()
1040}
1041
1042fn strip_leading_ws_and_comments(s: &str) -> &str {
1043 let mut rest = s;
1044 loop {
1045 let trimmed = rest.trim_start();
1046 if let Some(after) = trimmed.strip_prefix("--") {
1047 if let Some(nl) = after.find('\n') {
1048 rest = &after[nl..];
1049 continue;
1050 }
1051 return "";
1052 }
1053 if let Some(after) = trimmed.strip_prefix("/*") {
1054 if let Some(end) = after.find("*/") {
1055 rest = &after[end + 2..];
1056 continue;
1057 }
1058 return "";
1059 }
1060 return trimmed;
1061 }
1062}
1063
1064fn strip_kw_lower<'a>(s: &'a str, kw: &str) -> Option<&'a str> {
1065 let rest = s.strip_prefix(kw)?;
1066 if rest.is_empty() || rest.starts_with(char::is_whitespace) {
1067 Some(rest.trim_start())
1068 } else {
1069 None
1070 }
1071}
1072
1073fn find_with_no_data_suffix(stmt: &str) -> Option<usize> {
1074 let bytes = stmt.as_bytes();
1075 let mut end = bytes.len();
1076 while end > 0 && bytes[end - 1].is_ascii_whitespace() {
1077 end -= 1;
1078 }
1079 if end < 4 || !bytes[end - 4..end].eq_ignore_ascii_case(b"data") {
1080 return None;
1081 }
1082 let mut p = end - 4;
1083 let ws_start = p;
1084 while p > 0 && bytes[p - 1].is_ascii_whitespace() {
1085 p -= 1;
1086 }
1087 if p == ws_start {
1088 return None;
1089 }
1090 if p < 2 || !bytes[p - 2..p].eq_ignore_ascii_case(b"no") {
1091 return None;
1092 }
1093 p -= 2;
1094 let ws_start = p;
1095 while p > 0 && bytes[p - 1].is_ascii_whitespace() {
1096 p -= 1;
1097 }
1098 if p == ws_start {
1099 return None;
1100 }
1101 if p < 4 || !bytes[p - 4..p].eq_ignore_ascii_case(b"with") {
1102 return None;
1103 }
1104 p -= 4;
1105 let ws_start = p;
1106 while p > 0 && bytes[p - 1].is_ascii_whitespace() {
1107 p -= 1;
1108 }
1109 if p == ws_start {
1110 return None;
1111 }
1112 Some(p)
1113}
1114
1115pub fn count_params(stmt: &Statement) -> usize {
1116 let mut max_idx = 0usize;
1117 visit_exprs_stmt(stmt, &mut |e| {
1118 if let Expr::Parameter(n) = e {
1119 max_idx = max_idx.max(*n);
1120 }
1121 });
1122 max_idx
1123}
1124
1125fn visit_exprs_stmt(stmt: &Statement, visitor: &mut impl FnMut(&Expr)) {
1126 match stmt {
1127 Statement::Select(sq) => {
1128 for cte in &sq.ctes {
1129 visit_exprs_query_body(&cte.body, visitor);
1130 }
1131 visit_exprs_query_body(&sq.body, visitor);
1132 }
1133 Statement::Insert(ins) => match &ins.source {
1134 InsertSource::Values(rows) => {
1135 for row in rows {
1136 for e in row {
1137 visit_expr(e, visitor);
1138 }
1139 }
1140 }
1141 InsertSource::Select(sq) => {
1142 for cte in &sq.ctes {
1143 visit_exprs_query_body(&cte.body, visitor);
1144 }
1145 visit_exprs_query_body(&sq.body, visitor);
1146 }
1147 },
1148 Statement::Update(upd) => {
1149 for (_, e) in &upd.assignments {
1150 visit_expr(e, visitor);
1151 }
1152 if let Some(w) = &upd.where_clause {
1153 visit_expr(w, visitor);
1154 }
1155 }
1156 Statement::Delete(del) => {
1157 if let Some(w) = &del.where_clause {
1158 visit_expr(w, visitor);
1159 }
1160 }
1161 Statement::Explain(inner) => visit_exprs_stmt(inner, visitor),
1162 _ => {}
1163 }
1164}
1165
1166fn visit_exprs_query_body(body: &QueryBody, visitor: &mut impl FnMut(&Expr)) {
1167 match body {
1168 QueryBody::Select(sel) => visit_exprs_select(sel, visitor),
1169 QueryBody::Compound(comp) => {
1170 visit_exprs_query_body(&comp.left, visitor);
1171 visit_exprs_query_body(&comp.right, visitor);
1172 for o in &comp.order_by {
1173 visit_expr(&o.expr, visitor);
1174 }
1175 if let Some(l) = &comp.limit {
1176 visit_expr(l, visitor);
1177 }
1178 if let Some(o) = &comp.offset {
1179 visit_expr(o, visitor);
1180 }
1181 }
1182 QueryBody::Insert(ins) => visit_exprs_stmt(&Statement::Insert((**ins).clone()), visitor),
1183 QueryBody::Update(upd) => visit_exprs_stmt(&Statement::Update((**upd).clone()), visitor),
1184 QueryBody::Delete(del) => visit_exprs_stmt(&Statement::Delete((**del).clone()), visitor),
1185 }
1186}
1187
1188fn visit_exprs_select(sel: &SelectStmt, visitor: &mut impl FnMut(&Expr)) {
1189 for col in &sel.columns {
1190 if let SelectColumn::Expr { expr, .. } = col {
1191 visit_expr(expr, visitor);
1192 }
1193 }
1194 for j in &sel.joins {
1195 if let Some(on) = &j.on_clause {
1196 visit_expr(on, visitor);
1197 }
1198 }
1199 if let Some(w) = &sel.where_clause {
1200 visit_expr(w, visitor);
1201 }
1202 for o in &sel.order_by {
1203 visit_expr(&o.expr, visitor);
1204 }
1205 if let Some(l) = &sel.limit {
1206 visit_expr(l, visitor);
1207 }
1208 if let Some(o) = &sel.offset {
1209 visit_expr(o, visitor);
1210 }
1211 for g in &sel.group_by {
1212 visit_expr(g, visitor);
1213 }
1214 if let Some(h) = &sel.having {
1215 visit_expr(h, visitor);
1216 }
1217}
1218
1219fn visit_expr(expr: &Expr, visitor: &mut impl FnMut(&Expr)) {
1220 visitor(expr);
1221 match expr {
1222 Expr::BinaryOp { left, right, .. } => {
1223 visit_expr(left, visitor);
1224 visit_expr(right, visitor);
1225 }
1226 Expr::UnaryOp { expr: e, .. } | Expr::IsNull(e) | Expr::IsNotNull(e) => {
1227 visit_expr(e, visitor);
1228 }
1229 Expr::Function { args, .. } | Expr::Coalesce(args) => {
1230 for a in args {
1231 visit_expr(a, visitor);
1232 }
1233 }
1234 Expr::InSubquery {
1235 expr: e, subquery, ..
1236 } => {
1237 visit_expr(e, visitor);
1238 visit_exprs_select(subquery, visitor);
1239 }
1240 Expr::InList { expr: e, list, .. } => {
1241 visit_expr(e, visitor);
1242 for l in list {
1243 visit_expr(l, visitor);
1244 }
1245 }
1246 Expr::Exists { subquery, .. } => visit_exprs_select(subquery, visitor),
1247 Expr::ScalarSubquery(sq) => visit_exprs_select(sq, visitor),
1248 Expr::InSet { expr: e, .. } => visit_expr(e, visitor),
1249 Expr::Between {
1250 expr: e, low, high, ..
1251 } => {
1252 visit_expr(e, visitor);
1253 visit_expr(low, visitor);
1254 visit_expr(high, visitor);
1255 }
1256 Expr::Like {
1257 expr: e,
1258 pattern,
1259 escape,
1260 ..
1261 } => {
1262 visit_expr(e, visitor);
1263 visit_expr(pattern, visitor);
1264 if let Some(esc) = escape {
1265 visit_expr(esc, visitor);
1266 }
1267 }
1268 Expr::Case {
1269 operand,
1270 conditions,
1271 else_result,
1272 } => {
1273 if let Some(op) = operand {
1274 visit_expr(op, visitor);
1275 }
1276 for (cond, then) in conditions {
1277 visit_expr(cond, visitor);
1278 visit_expr(then, visitor);
1279 }
1280 if let Some(el) = else_result {
1281 visit_expr(el, visitor);
1282 }
1283 }
1284 Expr::Cast { expr: e, .. } => visit_expr(e, visitor),
1285 Expr::Collate { expr: e, .. } => visit_expr(e, visitor),
1286 Expr::WindowFunction { args, spec, .. } => {
1287 for a in args {
1288 visit_expr(a, visitor);
1289 }
1290 for p in &spec.partition_by {
1291 visit_expr(p, visitor);
1292 }
1293 for o in &spec.order_by {
1294 visit_expr(&o.expr, visitor);
1295 }
1296 if let Some(ref frame) = spec.frame {
1297 if let WindowFrameBound::Preceding(e) | WindowFrameBound::Following(e) =
1298 &frame.start
1299 {
1300 visit_expr(e, visitor);
1301 }
1302 if let WindowFrameBound::Preceding(e) | WindowFrameBound::Following(e) = &frame.end
1303 {
1304 visit_expr(e, visitor);
1305 }
1306 }
1307 }
1308 Expr::ArrayLiteral(elems) => {
1309 for e in elems {
1310 visit_expr(e, visitor);
1311 }
1312 }
1313 Expr::Quantified { left, right, .. } => {
1314 visit_expr(left, visitor);
1315 match right {
1316 QuantifiedRhs::Subquery(sq) => visit_exprs_select(sq, visitor),
1317 QuantifiedRhs::Array(e) => visit_expr(e, visitor),
1318 }
1319 }
1320 Expr::Literal(_)
1321 | Expr::Column(_)
1322 | Expr::QualifiedColumn { .. }
1323 | Expr::CountStar
1324 | Expr::Parameter(_)
1325 | Expr::TypedNullRecord(_) => {}
1326 }
1327}
1328
1329fn convert_statement(stmt: sp::Statement) -> Result<Statement> {
1330 match stmt {
1331 sp::Statement::CreateTable(ct) => convert_create_table(ct),
1332 sp::Statement::CreateIndex(ci) => convert_create_index(ci),
1333 sp::Statement::Drop {
1334 object_type: sp::ObjectType::Table,
1335 if_exists,
1336 names,
1337 ..
1338 } => {
1339 if names.len() != 1 {
1340 return Err(SqlError::Unsupported("multi-table DROP".into()));
1341 }
1342 Ok(Statement::DropTable(DropTableStmt {
1343 name: object_name_to_string(&names[0]),
1344 if_exists,
1345 }))
1346 }
1347 sp::Statement::Drop {
1348 object_type: sp::ObjectType::Index,
1349 if_exists,
1350 names,
1351 ..
1352 } => {
1353 if names.len() != 1 {
1354 return Err(SqlError::Unsupported("multi-index DROP".into()));
1355 }
1356 Ok(Statement::DropIndex(DropIndexStmt {
1357 index_name: object_name_to_string(&names[0]),
1358 if_exists,
1359 }))
1360 }
1361 sp::Statement::CreateView(cv) => convert_create_view(cv),
1362 sp::Statement::Drop {
1363 object_type: sp::ObjectType::View,
1364 if_exists,
1365 names,
1366 ..
1367 } => {
1368 if names.len() != 1 {
1369 return Err(SqlError::Unsupported("multi-view DROP".into()));
1370 }
1371 Ok(Statement::DropView(DropViewStmt {
1372 name: object_name_to_string(&names[0]),
1373 if_exists,
1374 }))
1375 }
1376 sp::Statement::Drop {
1377 object_type: sp::ObjectType::MaterializedView,
1378 if_exists,
1379 names,
1380 cascade,
1381 ..
1382 } => {
1383 if names.len() != 1 {
1384 return Err(SqlError::Unsupported("multi-matview DROP".into()));
1385 }
1386 Ok(Statement::DropMaterializedView(DropMatviewStmt {
1387 name: object_name_to_string(&names[0]),
1388 if_exists,
1389 cascade,
1390 }))
1391 }
1392 sp::Statement::CreateTrigger(ct) => convert_create_trigger(ct),
1393 sp::Statement::DropTrigger(dt) => Ok(Statement::DropTrigger(DropTriggerStmt {
1394 name: object_name_to_string(&dt.trigger_name),
1395 table: dt.table_name.as_ref().map(object_name_to_string),
1396 if_exists: dt.if_exists,
1397 })),
1398 sp::Statement::AlterTable(at) => convert_alter_table(at),
1399 sp::Statement::Insert(insert) => convert_insert(insert),
1400 sp::Statement::Query(query) => convert_query(*query),
1401 sp::Statement::Update(update) => convert_update(update),
1402 sp::Statement::Delete(delete) => convert_delete(delete),
1403 sp::Statement::Truncate(t) => convert_truncate(t),
1404 sp::Statement::StartTransaction { modes, .. } => {
1405 let mut access_mode = BeginAccessMode::Default;
1406 for mode in modes {
1407 if let sp::TransactionMode::AccessMode(am) = mode {
1408 access_mode = match am {
1409 sp::TransactionAccessMode::ReadOnly => BeginAccessMode::ReadOnly,
1410 sp::TransactionAccessMode::ReadWrite => BeginAccessMode::ReadWrite,
1411 };
1412 }
1413 }
1414 Ok(Statement::Begin { access_mode })
1415 }
1416 sp::Statement::Commit { chain: true, .. } => {
1417 Err(SqlError::Unsupported("COMMIT AND CHAIN".into()))
1418 }
1419 sp::Statement::Commit { .. } => Ok(Statement::Commit),
1420 sp::Statement::Rollback { chain: true, .. } => {
1421 Err(SqlError::Unsupported("ROLLBACK AND CHAIN".into()))
1422 }
1423 sp::Statement::Rollback {
1424 savepoint: Some(name),
1425 ..
1426 } => Ok(Statement::RollbackTo(name.value.to_ascii_lowercase())),
1427 sp::Statement::Rollback { .. } => Ok(Statement::Rollback),
1428 sp::Statement::Savepoint { name } => {
1429 Ok(Statement::Savepoint(name.value.to_ascii_lowercase()))
1430 }
1431 sp::Statement::ReleaseSavepoint { name } => {
1432 Ok(Statement::ReleaseSavepoint(name.value.to_ascii_lowercase()))
1433 }
1434 sp::Statement::Set(sp::Set::SetTimeZone { value, .. }) => {
1435 let zone = match value {
1437 sp::Expr::Value(v) => match &v.value {
1438 sp::Value::SingleQuotedString(s) => s.clone(),
1439 sp::Value::DoubleQuotedString(s) => s.clone(),
1440 other => other.to_string(),
1441 },
1442 sp::Expr::Identifier(ident) => ident.value.clone(),
1443 other => {
1444 return Err(SqlError::Parse(format!(
1445 "SET TIME ZONE expects a string literal or identifier, got: {other}"
1446 )))
1447 }
1448 };
1449 Ok(Statement::SetTimezone(zone))
1450 }
1451 sp::Statement::Explain {
1452 statement, analyze, ..
1453 } => {
1454 if analyze {
1455 return Err(SqlError::Unsupported("EXPLAIN ANALYZE".into()));
1456 }
1457 let inner = convert_statement(*statement)?;
1458 Ok(Statement::Explain(Box::new(inner)))
1459 }
1460 _ => Err(SqlError::Unsupported(format!("statement type: {}", stmt))),
1461 }
1462}
1463
1464fn convert_column_def(
1466 col_def: &sp::ColumnDef,
1467) -> Result<(ColumnSpec, Option<ForeignKeyDef>, bool, bool)> {
1468 let col_name = col_def.name.value.clone();
1469 let data_type = convert_data_type(&col_def.data_type)?;
1470 let mut nullable = true;
1471 let mut is_primary_key = false;
1472 let mut is_unique = false;
1473 let mut default_expr = None;
1474 let mut default_sql = None;
1475 let mut check_expr = None;
1476 let mut check_sql = None;
1477 let mut check_name = None;
1478 let mut generated_expr = None;
1479 let mut generated_sql = None;
1480 let mut generated_kind = None;
1481 let mut fk_def = None;
1482 let mut collation = crate::types::Collation::Binary;
1483
1484 for opt in &col_def.options {
1485 match &opt.option {
1486 sp::ColumnOption::Collation(name) => {
1487 let coll_name = object_name_to_string(name);
1488 collation = crate::types::Collation::from_name(&coll_name).ok_or_else(|| {
1489 SqlError::Unsupported(format!(
1490 "collation '{coll_name}' not supported (BINARY/NOCASE/RTRIM only)"
1491 ))
1492 })?;
1493 }
1494 sp::ColumnOption::NotNull => nullable = false,
1495 sp::ColumnOption::Null => nullable = true,
1496 sp::ColumnOption::PrimaryKey(_) => {
1497 is_primary_key = true;
1498 nullable = false;
1499 }
1500 sp::ColumnOption::Unique(_) => is_unique = true,
1501 sp::ColumnOption::Default(expr) => {
1502 default_sql = Some(expr.to_string());
1503 default_expr = Some(convert_expr(expr)?);
1504 }
1505 sp::ColumnOption::Check(check) => {
1506 check_sql = Some(check.expr.to_string());
1507 let converted = convert_expr(&check.expr)?;
1508 if has_subquery(&converted) {
1509 return Err(SqlError::Unsupported("subquery in CHECK constraint".into()));
1510 }
1511 check_expr = Some(converted);
1512 check_name = check.name.as_ref().map(|n| n.value.clone());
1513 }
1514 sp::ColumnOption::ForeignKey(fk) => {
1515 let (on_delete, on_update) = convert_fk_actions(&fk.on_delete, &fk.on_update)?;
1516 let (deferrable, initially_deferred) =
1517 convert_fk_characteristics(&fk.characteristics);
1518 let ftable = object_name_to_string(&fk.foreign_table).to_ascii_lowercase();
1519 let referred: Vec<String> = fk
1520 .referred_columns
1521 .iter()
1522 .map(|i| i.value.to_ascii_lowercase())
1523 .collect();
1524 fk_def = Some(ForeignKeyDef {
1525 name: fk.name.as_ref().map(|n| n.value.clone()),
1526 columns: vec![col_name.to_ascii_lowercase()],
1527 foreign_table: ftable,
1528 referred_columns: referred,
1529 on_delete,
1530 on_update,
1531 deferrable,
1532 initially_deferred,
1533 });
1534 }
1535 sp::ColumnOption::Generated {
1536 generation_expr,
1537 generation_expr_mode,
1538 sequence_options: _,
1539 ..
1540 } => {
1541 let Some(expr) = generation_expr else {
1542 return Err(SqlError::Unsupported(
1543 "identity columns not yet supported; use INTEGER PRIMARY KEY for autoincrement".into(),
1544 ));
1545 };
1546 let mode = generation_expr_mode.unwrap_or(sp::GeneratedExpressionMode::Virtual);
1547 let converted = convert_expr(expr)?;
1548 reject_aggregate_or_window(expr, "GENERATED")?;
1549 if has_subquery(&converted) {
1550 return Err(SqlError::Unsupported(
1551 "subquery in GENERATED expression".into(),
1552 ));
1553 }
1554 reject_volatile_in_generated(&converted)?;
1555 generated_sql = Some(expr.to_string());
1556 generated_expr = Some(converted);
1557 generated_kind = Some(match mode {
1558 sp::GeneratedExpressionMode::Stored => GeneratedKind::Stored,
1559 sp::GeneratedExpressionMode::Virtual => GeneratedKind::Virtual,
1560 });
1561 }
1562 _ => {}
1563 }
1564 }
1565
1566 if generated_kind.is_some() {
1567 if default_expr.is_some() {
1568 return Err(SqlError::Unsupported(
1569 "DEFAULT and GENERATED cannot be combined".into(),
1570 ));
1571 }
1572 if is_primary_key {
1573 return Err(SqlError::Unsupported(
1574 "GENERATED column cannot be PRIMARY KEY".into(),
1575 ));
1576 }
1577 }
1578
1579 let spec = ColumnSpec {
1580 name: col_name,
1581 data_type,
1582 nullable,
1583 is_primary_key,
1584 default_expr,
1585 default_sql,
1586 check_expr,
1587 check_sql,
1588 check_name,
1589 generated_expr,
1590 generated_sql,
1591 generated_kind,
1592 collation,
1593 };
1594 Ok((spec, fk_def, is_primary_key, is_unique))
1595}
1596
1597fn reject_volatile_in_generated(expr: &Expr) -> Result<()> {
1598 fn walk(e: &Expr) -> Result<()> {
1599 match e {
1600 Expr::Function { name, args, .. } => {
1601 let upper = name.to_ascii_uppercase();
1602 if matches!(
1603 upper.as_str(),
1604 "RANDOM"
1605 | "NOW"
1606 | "CURRENT_TIMESTAMP"
1607 | "CURRENT_DATE"
1608 | "CURRENT_TIME"
1609 | "CLOCK_TIMESTAMP"
1610 | "STATEMENT_TIMESTAMP"
1611 | "TRANSACTION_TIMESTAMP"
1612 | "LOCALTIMESTAMP"
1613 | "LOCALTIME"
1614 ) {
1615 return Err(SqlError::Unsupported(format!(
1616 "volatile function {name}() not allowed in GENERATED expression"
1617 )));
1618 }
1619 for a in args {
1620 walk(a)?;
1621 }
1622 Ok(())
1623 }
1624 Expr::BinaryOp { left, right, .. } => {
1625 walk(left)?;
1626 walk(right)
1627 }
1628 Expr::UnaryOp { expr, .. } => walk(expr),
1629 Expr::Cast { expr, .. } => walk(expr),
1630 Expr::Case {
1631 operand,
1632 conditions,
1633 else_result,
1634 } => {
1635 if let Some(o) = operand {
1636 walk(o)?;
1637 }
1638 for (cond, res) in conditions {
1639 walk(cond)?;
1640 walk(res)?;
1641 }
1642 if let Some(e) = else_result {
1643 walk(e)?;
1644 }
1645 Ok(())
1646 }
1647 Expr::Coalesce(items) => items.iter().try_for_each(walk),
1648 _ => Ok(()),
1649 }
1650 }
1651 walk(expr)
1652}
1653
1654fn convert_create_table(ct: sp::CreateTable) -> Result<Statement> {
1655 let name = object_name_to_string(&ct.name);
1656 let if_not_exists = ct.if_not_exists;
1657 let strict = ct.strict;
1658 let temporary = ct.temporary;
1659
1660 let mut columns = Vec::new();
1661 let mut inline_pk: Vec<String> = Vec::new();
1662 let mut foreign_keys: Vec<ForeignKeyDef> = Vec::new();
1663 let mut unique_indices: Vec<UniqueIndexDef> = Vec::new();
1664
1665 for col_def in &ct.columns {
1666 let (spec, fk_def, was_pk, was_unique) = convert_column_def(col_def)?;
1667 if was_pk {
1668 inline_pk.push(spec.name.clone());
1669 }
1670 if let Some(fk) = fk_def {
1671 foreign_keys.push(fk);
1672 }
1673 if was_unique && !was_pk {
1674 unique_indices.push(UniqueIndexDef {
1675 name: None,
1676 columns: vec![spec.name.to_ascii_lowercase()],
1677 });
1678 }
1679 columns.push(spec);
1680 }
1681
1682 let mut check_constraints: Vec<TableCheckConstraint> = Vec::new();
1683
1684 for constraint in &ct.constraints {
1685 match constraint {
1686 sp::TableConstraint::PrimaryKey(pk_constraint) => {
1687 for idx_col in &pk_constraint.columns {
1688 let col_name = match &idx_col.column.expr {
1689 sp::Expr::Identifier(ident) => ident.value.clone(),
1690 _ => continue,
1691 };
1692 if !inline_pk.contains(&col_name) {
1693 inline_pk.push(col_name.clone());
1694 }
1695 if let Some(col) = columns.iter_mut().find(|c| c.name == col_name) {
1696 col.nullable = false;
1697 col.is_primary_key = true;
1698 }
1699 }
1700 }
1701 sp::TableConstraint::Check(check) => {
1702 let sql = check.expr.to_string();
1703 let converted = convert_expr(&check.expr)?;
1704 if has_subquery(&converted) {
1705 return Err(SqlError::Unsupported("subquery in CHECK constraint".into()));
1706 }
1707 check_constraints.push(TableCheckConstraint {
1708 name: check.name.as_ref().map(|n| n.value.clone()),
1709 expr: converted,
1710 sql,
1711 });
1712 }
1713 sp::TableConstraint::ForeignKey(fk) => {
1714 let (on_delete, on_update) = convert_fk_actions(&fk.on_delete, &fk.on_update)?;
1715 let (deferrable, initially_deferred) =
1716 convert_fk_characteristics(&fk.characteristics);
1717 let cols: Vec<String> = fk
1718 .columns
1719 .iter()
1720 .map(|i| i.value.to_ascii_lowercase())
1721 .collect();
1722 let ftable = object_name_to_string(&fk.foreign_table).to_ascii_lowercase();
1723 let referred: Vec<String> = fk
1724 .referred_columns
1725 .iter()
1726 .map(|i| i.value.to_ascii_lowercase())
1727 .collect();
1728 foreign_keys.push(ForeignKeyDef {
1729 name: fk.name.as_ref().map(|n| n.value.clone()),
1730 columns: cols,
1731 foreign_table: ftable,
1732 referred_columns: referred,
1733 on_delete,
1734 on_update,
1735 deferrable,
1736 initially_deferred,
1737 });
1738 }
1739 sp::TableConstraint::Unique(u) => {
1740 let cols: Vec<String> = u
1741 .columns
1742 .iter()
1743 .filter_map(|idx_col| match &idx_col.column.expr {
1744 sp::Expr::Identifier(ident) => Some(ident.value.to_ascii_lowercase()),
1745 _ => None,
1746 })
1747 .collect();
1748 if !cols.is_empty() {
1749 unique_indices.push(UniqueIndexDef {
1750 name: u.name.as_ref().map(|n| n.value.clone()),
1751 columns: cols,
1752 });
1753 }
1754 }
1755 _ => {}
1756 }
1757 }
1758
1759 Ok(Statement::CreateTable(CreateTableStmt {
1760 name,
1761 columns,
1762 primary_key: inline_pk,
1763 if_not_exists,
1764 check_constraints,
1765 foreign_keys,
1766 unique_indices,
1767 strict,
1768 temporary,
1769 }))
1770}
1771
1772fn convert_alter_table(at: sp::AlterTable) -> Result<Statement> {
1773 let table = object_name_to_string(&at.name);
1774 if at.operations.len() != 1 {
1775 return Err(SqlError::Unsupported(
1776 "ALTER TABLE with multiple operations".into(),
1777 ));
1778 }
1779 let op = match at.operations.into_iter().next().unwrap() {
1780 sp::AlterTableOperation::AddColumn {
1781 column_def,
1782 if_not_exists,
1783 ..
1784 } => {
1785 let (spec, fk, _was_pk, _was_unique) = convert_column_def(&column_def)?;
1786 AlterTableOp::AddColumn {
1787 column: Box::new(spec),
1788 foreign_key: fk,
1789 if_not_exists,
1790 }
1791 }
1792 sp::AlterTableOperation::DropColumn {
1793 column_names,
1794 if_exists,
1795 ..
1796 } => {
1797 if column_names.len() != 1 {
1798 return Err(SqlError::Unsupported(
1799 "DROP COLUMN with multiple columns".into(),
1800 ));
1801 }
1802 AlterTableOp::DropColumn {
1803 name: column_names.into_iter().next().unwrap().value,
1804 if_exists,
1805 }
1806 }
1807 sp::AlterTableOperation::RenameColumn {
1808 old_column_name,
1809 new_column_name,
1810 } => AlterTableOp::RenameColumn {
1811 old_name: old_column_name.value,
1812 new_name: new_column_name.value,
1813 },
1814 sp::AlterTableOperation::RenameTable { table_name } => {
1815 let new_name = match table_name {
1816 sp::RenameTableNameKind::To(name) | sp::RenameTableNameKind::As(name) => {
1817 object_name_to_string(&name)
1818 }
1819 };
1820 AlterTableOp::RenameTable { new_name }
1821 }
1822 sp::AlterTableOperation::DisableTrigger { name } => {
1823 if name.value.eq_ignore_ascii_case("all") {
1824 AlterTableOp::DisableAllTriggers
1825 } else {
1826 AlterTableOp::DisableTrigger {
1827 name: name.value.to_ascii_lowercase(),
1828 }
1829 }
1830 }
1831 sp::AlterTableOperation::EnableTrigger { name } => {
1832 if name.value.eq_ignore_ascii_case("all") {
1833 AlterTableOp::EnableAllTriggers
1834 } else {
1835 AlterTableOp::EnableTrigger {
1836 name: name.value.to_ascii_lowercase(),
1837 }
1838 }
1839 }
1840 other => {
1841 return Err(SqlError::Unsupported(format!(
1842 "ALTER TABLE operation: {other}"
1843 )));
1844 }
1845 };
1846 Ok(Statement::AlterTable(Box::new(AlterTableStmt {
1847 table,
1848 op,
1849 })))
1850}
1851
1852fn convert_fk_actions(
1853 on_delete: &Option<sp::ReferentialAction>,
1854 on_update: &Option<sp::ReferentialAction>,
1855) -> Result<(ReferentialAction, ReferentialAction)> {
1856 Ok((convert_fk_action(on_delete)?, convert_fk_action(on_update)?))
1857}
1858
1859fn convert_fk_action(action: &Option<sp::ReferentialAction>) -> Result<ReferentialAction> {
1860 match action {
1861 None | Some(sp::ReferentialAction::NoAction) => Ok(ReferentialAction::NoAction),
1862 Some(sp::ReferentialAction::Restrict) => Ok(ReferentialAction::Restrict),
1863 Some(sp::ReferentialAction::Cascade) => Ok(ReferentialAction::Cascade),
1864 Some(sp::ReferentialAction::SetNull) => Ok(ReferentialAction::SetNull),
1865 Some(sp::ReferentialAction::SetDefault) => Ok(ReferentialAction::SetDefault),
1866 }
1867}
1868
1869fn convert_fk_characteristics(ch: &Option<sp::ConstraintCharacteristics>) -> (bool, bool) {
1870 let Some(c) = ch else {
1871 return (false, false);
1872 };
1873 let deferrable = c.deferrable.unwrap_or(false);
1874 let initially_deferred = matches!(c.initially, Some(sp::DeferrableInitial::Deferred));
1875 (deferrable, initially_deferred)
1876}
1877
1878fn convert_create_index(ci: sp::CreateIndex) -> Result<Statement> {
1879 let index_name = ci
1880 .name
1881 .as_ref()
1882 .map(object_name_to_string)
1883 .ok_or_else(|| SqlError::Parse("index name required".into()))?;
1884
1885 let table_name = object_name_to_string(&ci.table_name);
1886
1887 let mut columns: Vec<String> = Vec::with_capacity(ci.columns.len());
1888 let mut collations: Vec<crate::types::Collation> = Vec::with_capacity(ci.columns.len());
1889 let mut key_exprs: Vec<Option<(Expr, String)>> = Vec::with_capacity(ci.columns.len());
1890 for idx_col in &ci.columns {
1891 let (name, coll, expr_entry) = match &idx_col.column.expr {
1892 sp::Expr::Identifier(ident) => {
1893 (ident.value.clone(), crate::types::Collation::Binary, None)
1894 }
1895 sp::Expr::Collate {
1896 expr: inner,
1897 collation,
1898 } => match inner.as_ref() {
1899 sp::Expr::Identifier(ident) => {
1900 let coll_name = object_name_to_string(collation);
1901 let coll = crate::types::Collation::from_name(&coll_name).ok_or_else(|| {
1902 SqlError::Unsupported(format!(
1903 "collation '{coll_name}' not supported (BINARY/NOCASE/RTRIM only)"
1904 ))
1905 })?;
1906 (ident.value.clone(), coll, None)
1907 }
1908 inner_expr => {
1909 let sql = inner_expr.to_string();
1910 let expr = convert_expr(inner_expr)?;
1911 (
1912 sql.clone(),
1913 crate::types::Collation::Binary,
1914 Some((expr, sql)),
1915 )
1916 }
1917 },
1918 other => {
1919 let sql = other.to_string();
1920 let expr = convert_expr(other)?;
1921 (
1922 sql.clone(),
1923 crate::types::Collation::Binary,
1924 Some((expr, sql)),
1925 )
1926 }
1927 };
1928 columns.push(name);
1929 collations.push(coll);
1930 key_exprs.push(expr_entry);
1931 }
1932
1933 if columns.is_empty() {
1934 return Err(SqlError::Parse(
1935 "index must have at least one column".into(),
1936 ));
1937 }
1938
1939 let (predicate_sql, predicate_expr) = match &ci.predicate {
1940 Some(sp_expr) => {
1941 let expr = convert_expr(sp_expr)?;
1942 validate_partial_index_predicate(&expr)?;
1943 (Some(sp_expr.to_string()), Some(expr))
1944 }
1945 None => (None, None),
1946 };
1947
1948 let kind = match &ci.using {
1949 None => crate::types::IndexKind::BTree,
1950 Some(sp::IndexType::BTree) => crate::types::IndexKind::BTree,
1951 Some(sp::IndexType::GIN) => {
1952 let ops = parse_gin_with_ops(&ci.with)?;
1953 crate::types::IndexKind::Inverted(crate::types::InvertedKind::Gin(ops))
1954 }
1955 Some(sp::IndexType::Custom(ident)) if ident.value.eq_ignore_ascii_case("fts") => {
1956 let config_id = parse_fts_with_config(&ci.with)?;
1957 crate::types::IndexKind::Inverted(crate::types::InvertedKind::Fts { config_id })
1958 }
1959 Some(other) => {
1960 return Err(SqlError::Unsupported(format!(
1961 "index method {other}; supported: BTREE, GIN, FTS"
1962 )));
1963 }
1964 };
1965
1966 Ok(Statement::CreateIndex(CreateIndexStmt {
1967 index_name,
1968 table_name,
1969 columns,
1970 key_exprs,
1971 unique: ci.unique,
1972 if_not_exists: ci.if_not_exists,
1973 predicate_sql,
1974 predicate_expr,
1975 collations,
1976 kind,
1977 concurrently: ci.concurrently,
1978 }))
1979}
1980
1981fn parse_gin_with_ops(with: &[sp::Expr]) -> Result<crate::types::GinOpsClass> {
1982 use crate::types::GinOpsClass;
1983 let mut ops_name: Option<String> = None;
1984 for expr in with {
1985 match expr {
1986 sp::Expr::BinaryOp {
1987 left,
1988 op: sp::BinaryOperator::Eq,
1989 right,
1990 } => {
1991 let key = match left.as_ref() {
1992 sp::Expr::Identifier(id) => id.value.to_ascii_lowercase(),
1993 other => {
1994 return Err(SqlError::Unsupported(format!(
1995 "GIN WITH option key: {other}"
1996 )));
1997 }
1998 };
1999 if key != "ops" {
2000 return Err(SqlError::Unsupported(format!(
2001 "GIN WITH option: unknown key '{key}' (only 'ops' supported)"
2002 )));
2003 }
2004 let val = match right.as_ref() {
2005 sp::Expr::Value(v) => match &v.value {
2006 sp::Value::SingleQuotedString(s) => s.clone(),
2007 sp::Value::DoubleQuotedString(s) => s.clone(),
2008 other => {
2009 return Err(SqlError::Parse(format!(
2010 "GIN ops value must be a string literal, got: {other}"
2011 )))
2012 }
2013 },
2014 sp::Expr::Identifier(id) => id.value.clone(),
2015 other => {
2016 return Err(SqlError::Parse(format!(
2017 "GIN ops value must be a string literal, got: {other}"
2018 )));
2019 }
2020 };
2021 ops_name = Some(val);
2022 }
2023 other => {
2024 return Err(SqlError::Unsupported(format!(
2025 "GIN WITH option must be `key = value`, got: {other}"
2026 )));
2027 }
2028 }
2029 }
2030 let lower = ops_name.as_deref().map(|s| s.to_ascii_lowercase());
2031 match lower.as_deref() {
2032 None | Some("jsonb_ops") => Ok(GinOpsClass::JsonbOps),
2033 Some("jsonb_path_ops") => Ok(GinOpsClass::JsonbPathOps),
2034 Some(other) => Err(SqlError::Unsupported(format!(
2035 "GIN opclass '{other}'; supported: jsonb_ops, jsonb_path_ops"
2036 ))),
2037 }
2038}
2039
2040fn parse_fts_with_config(with: &[sp::Expr]) -> Result<u8> {
2041 let mut config_name: Option<String> = None;
2042 for expr in with {
2043 match expr {
2044 sp::Expr::BinaryOp {
2045 left,
2046 op: sp::BinaryOperator::Eq,
2047 right,
2048 } => {
2049 let key = match left.as_ref() {
2050 sp::Expr::Identifier(id) => id.value.to_ascii_lowercase(),
2051 other => {
2052 return Err(SqlError::Unsupported(format!(
2053 "FTS WITH option key: {other}"
2054 )));
2055 }
2056 };
2057 if key != "config" {
2058 return Err(SqlError::Unsupported(format!(
2059 "FTS WITH option: unknown key '{key}' (only 'config' supported)"
2060 )));
2061 }
2062 let val = match right.as_ref() {
2063 sp::Expr::Value(v) => match &v.value {
2064 sp::Value::SingleQuotedString(s) => s.clone(),
2065 sp::Value::DoubleQuotedString(s) => s.clone(),
2066 other => {
2067 return Err(SqlError::Parse(format!(
2068 "FTS config value must be a string literal, got: {other}"
2069 )))
2070 }
2071 },
2072 sp::Expr::Identifier(id) => id.value.clone(),
2073 other => {
2074 return Err(SqlError::Parse(format!(
2075 "FTS config value must be a string literal, got: {other}"
2076 )));
2077 }
2078 };
2079 config_name = Some(val);
2080 }
2081 other => {
2082 return Err(SqlError::Unsupported(format!(
2083 "FTS WITH option must be `key = value`, got: {other}"
2084 )));
2085 }
2086 }
2087 }
2088 let name = config_name.unwrap_or_else(|| "english".to_string());
2089 Ok(crate::fts::TokenizerKind::from_name(&name)?.as_config_id())
2090}
2091
2092fn validate_partial_index_predicate(expr: &Expr) -> Result<()> {
2093 let mut bad: Option<&'static str> = None;
2094 visit_expr(expr, &mut |e| {
2095 if bad.is_some() {
2096 return;
2097 }
2098 match e {
2099 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {
2100 bad = Some("subqueries");
2101 }
2102 Expr::CountStar => bad = Some("aggregates"),
2103 Expr::WindowFunction { .. } => bad = Some("window functions"),
2104 Expr::Parameter(_) => bad = Some("bound parameters"),
2105 Expr::QualifiedColumn { .. } => bad = Some("cross-table references"),
2106 Expr::Function { name, .. } => {
2107 if is_aggregate_function(name) {
2108 bad = Some("aggregates");
2109 } else if !is_immutable_function(name) {
2110 bad = Some("non-deterministic functions");
2111 }
2112 }
2113 _ => {}
2114 }
2115 });
2116 if let Some(reason) = bad {
2117 return Err(SqlError::Unsupported(format!(
2118 "partial index predicate cannot contain {reason}"
2119 )));
2120 }
2121 Ok(())
2122}
2123
2124fn is_aggregate_function(name: &str) -> bool {
2125 matches!(
2126 name.to_ascii_lowercase().as_str(),
2127 "count" | "sum" | "avg" | "min" | "max" | "total" | "group_concat" | "string_agg"
2128 )
2129}
2130
2131fn is_immutable_function(name: &str) -> bool {
2132 !matches!(
2133 name.to_ascii_lowercase().as_str(),
2134 "now"
2135 | "current_timestamp"
2136 | "current_date"
2137 | "current_time"
2138 | "localtimestamp"
2139 | "localtime"
2140 | "random"
2141 | "rand"
2142 )
2143}
2144
2145fn convert_create_trigger(ct: sp::CreateTrigger) -> Result<Statement> {
2146 let name = object_name_to_string(&ct.name);
2147 let target = object_name_to_string(&ct.table_name);
2148
2149 let timing = match ct.period {
2150 Some(sp::TriggerPeriod::Before) => TriggerTiming::Before,
2151 Some(sp::TriggerPeriod::After) => TriggerTiming::After,
2152 Some(sp::TriggerPeriod::InsteadOf) => TriggerTiming::InsteadOf,
2153 _ => {
2154 return Err(SqlError::Parse(
2155 "CREATE TRIGGER requires BEFORE, AFTER, or INSTEAD OF".into(),
2156 ));
2157 }
2158 };
2159
2160 let mut events: Vec<TriggerEvent> = Vec::with_capacity(ct.events.len());
2161 for ev in &ct.events {
2162 let mapped = match ev {
2163 sp::TriggerEvent::Insert => TriggerEvent::Insert,
2164 sp::TriggerEvent::Delete => TriggerEvent::Delete,
2165 sp::TriggerEvent::Update(cols) => {
2166 TriggerEvent::Update(cols.iter().map(|i| i.value.to_ascii_lowercase()).collect())
2167 }
2168 sp::TriggerEvent::Truncate => {
2169 return Err(SqlError::Unsupported(
2170 "TRUNCATE triggers are not supported".into(),
2171 ));
2172 }
2173 };
2174 events.push(mapped);
2175 }
2176 if events.is_empty() {
2177 return Err(SqlError::Parse(
2178 "CREATE TRIGGER requires at least one event (INSERT/UPDATE/DELETE)".into(),
2179 ));
2180 }
2181
2182 let granularity = match ct.trigger_object {
2183 Some(sp::TriggerObjectKind::For(sp::TriggerObject::Statement))
2184 | Some(sp::TriggerObjectKind::ForEach(sp::TriggerObject::Statement)) => {
2185 TriggerGranularity::ForEachStatement
2186 }
2187 _ => TriggerGranularity::ForEachRow,
2189 };
2190
2191 if timing == TriggerTiming::InsteadOf && granularity == TriggerGranularity::ForEachStatement {
2192 return Err(SqlError::Unsupported(
2193 "INSTEAD OF triggers must be FOR EACH ROW".into(),
2194 ));
2195 }
2196
2197 let mut referencing: Option<TransitionTables> = None;
2198 if !ct.referencing.is_empty() {
2199 let mut new_alias: Option<String> = None;
2200 let mut old_alias: Option<String> = None;
2201 for r in &ct.referencing {
2202 let alias = object_name_to_string(&r.transition_relation_name);
2203 match r.refer_type {
2204 sp::TriggerReferencingType::NewTable => new_alias = Some(alias),
2205 sp::TriggerReferencingType::OldTable => old_alias = Some(alias),
2206 }
2207 }
2208 referencing = Some(TransitionTables {
2209 new_table_alias: new_alias,
2210 old_table_alias: old_alias,
2211 });
2212 }
2213
2214 let when_expr = ct.condition.as_ref().map(convert_expr).transpose()?;
2215 let when_sql = ct.condition.as_ref().map(|e| e.to_string());
2216
2217 let inner_statements: &[sp::Statement] = match &ct.statements {
2218 Some(sp::ConditionalStatements::Sequence { statements })
2219 | Some(sp::ConditionalStatements::BeginEnd(sp::BeginEndStatements {
2220 statements, ..
2221 })) => statements,
2222 None => {
2223 return Err(SqlError::Parse(
2224 "CREATE TRIGGER body must contain BEGIN ... END or one or more statements".into(),
2225 ));
2226 }
2227 };
2228 let body: Vec<Statement> = inner_statements
2229 .iter()
2230 .cloned()
2231 .map(convert_statement)
2232 .collect::<Result<Vec<_>>>()?;
2233 let body_sql: String = inner_statements
2234 .iter()
2235 .map(|s| s.to_string())
2236 .collect::<Vec<_>>()
2237 .join(";");
2238
2239 Ok(Statement::CreateTrigger(Box::new(CreateTriggerStmt {
2240 name,
2241 timing,
2242 events,
2243 target,
2244 granularity,
2245 referencing,
2246 when_sql,
2247 when_expr,
2248 body_sql,
2249 body,
2250 if_not_exists: false,
2251 })))
2252}
2253
2254fn convert_create_view(cv: sp::CreateView) -> Result<Statement> {
2255 let name = object_name_to_string(&cv.name);
2256 let sql = cv.query.to_string();
2257
2258 let parsed_select = parse_select_query(&sql)?;
2259
2260 if cv.materialized {
2261 return Ok(Statement::CreateMaterializedView(Box::new(
2262 CreateMatviewStmt {
2263 name,
2264 select_sql: sql,
2265 select_parsed: parsed_select,
2266 with_data: true,
2267 if_not_exists: cv.if_not_exists,
2268 },
2269 )));
2270 }
2271
2272 let column_aliases: Vec<String> = cv
2273 .columns
2274 .iter()
2275 .map(|c| c.name.value.to_ascii_lowercase())
2276 .collect();
2277
2278 Ok(Statement::CreateView(CreateViewStmt {
2279 name,
2280 sql,
2281 column_aliases,
2282 or_replace: cv.or_replace,
2283 if_not_exists: cv.if_not_exists,
2284 }))
2285}
2286
2287fn parse_select_query(sql: &str) -> Result<SelectQuery> {
2288 let stmts = parse_sql_multi(sql)?;
2289 if stmts.len() != 1 {
2290 return Err(SqlError::Parse(
2291 "matview body must be a single SELECT statement".into(),
2292 ));
2293 }
2294 match stmts.into_iter().next().unwrap() {
2295 Statement::Select(sq) => Ok(*sq),
2296 _ => Err(SqlError::Parse(
2297 "matview body must be a SELECT statement".into(),
2298 )),
2299 }
2300}
2301
2302fn convert_insert(insert: sp::Insert) -> Result<Statement> {
2303 let table = match &insert.table {
2304 sp::TableObject::TableName(name) => object_name_to_string(name).to_ascii_lowercase(),
2305 _ => return Err(SqlError::Unsupported("INSERT into non-table object".into())),
2306 };
2307
2308 let columns: Vec<String> = insert
2309 .columns
2310 .iter()
2311 .map(|c| c.value.to_ascii_lowercase())
2312 .collect();
2313
2314 let query = insert
2315 .source
2316 .ok_or_else(|| SqlError::Parse("INSERT requires VALUES or SELECT".into()))?;
2317
2318 let source = match *query.body {
2319 sp::SetExpr::Values(sp::Values { rows, .. }) => {
2320 let mut result = Vec::new();
2321 for row in rows {
2322 let mut exprs = Vec::new();
2323 for expr in row {
2324 exprs.push(convert_expr(&expr)?);
2325 }
2326 result.push(exprs);
2327 }
2328 InsertSource::Values(result)
2329 }
2330 _ => {
2331 let (ctes, recursive) = if let Some(ref with) = query.with {
2332 convert_with(with)?
2333 } else {
2334 (vec![], false)
2335 };
2336 let body = convert_query_body(&query)?;
2337 InsertSource::Select(Box::new(SelectQuery {
2338 ctes,
2339 recursive,
2340 body,
2341 }))
2342 }
2343 };
2344
2345 let on_conflict = insert.on.as_ref().map(convert_on_insert).transpose()?;
2346 let returning = convert_returning(insert.returning.as_deref())?;
2347
2348 Ok(Statement::Insert(InsertStmt {
2349 table,
2350 columns,
2351 source,
2352 on_conflict,
2353 returning,
2354 }))
2355}
2356
2357fn convert_on_insert(on: &sp::OnInsert) -> Result<OnConflictClause> {
2358 match on {
2359 sp::OnInsert::OnConflict(oc) => {
2360 let target = oc
2361 .conflict_target
2362 .as_ref()
2363 .map(convert_conflict_target)
2364 .transpose()?;
2365 let action = convert_on_conflict_action(&oc.action)?;
2366 Ok(OnConflictClause { target, action })
2367 }
2368 sp::OnInsert::DuplicateKeyUpdate(_) => Err(SqlError::Parse(
2369 "ON DUPLICATE KEY UPDATE is MySQL-specific; use ON CONFLICT".into(),
2370 )),
2371 _ => Err(SqlError::Parse("unsupported ON INSERT clause".into())),
2372 }
2373}
2374
2375fn convert_conflict_target(target: &sp::ConflictTarget) -> Result<ConflictTarget> {
2376 match target {
2377 sp::ConflictTarget::Columns(cols) => Ok(ConflictTarget::Columns(
2378 cols.iter().map(|c| c.value.to_ascii_lowercase()).collect(),
2379 )),
2380 sp::ConflictTarget::OnConstraint(name) => {
2381 if name.0.len() > 1 {
2382 return Err(SqlError::Parse(
2383 "qualified constraint names not supported".into(),
2384 ));
2385 }
2386 Ok(ConflictTarget::Constraint(
2387 object_name_to_string(name).to_ascii_lowercase(),
2388 ))
2389 }
2390 }
2391}
2392
2393fn convert_on_conflict_action(action: &sp::OnConflictAction) -> Result<OnConflictAction> {
2394 match action {
2395 sp::OnConflictAction::DoNothing => Ok(OnConflictAction::DoNothing),
2396 sp::OnConflictAction::DoUpdate(du) => {
2397 let assignments = du
2398 .assignments
2399 .iter()
2400 .map(|a| {
2401 let col = match &a.target {
2402 sp::AssignmentTarget::ColumnName(name) => {
2403 object_name_to_string(name).to_ascii_lowercase()
2404 }
2405 _ => {
2406 return Err(SqlError::Unsupported(
2407 "tuple assignment in ON CONFLICT".into(),
2408 ))
2409 }
2410 };
2411 let expr = convert_expr(&a.value)?;
2412 Ok((col, expr))
2413 })
2414 .collect::<Result<_>>()?;
2415 let where_clause = du.selection.as_ref().map(convert_expr).transpose()?;
2416 Ok(OnConflictAction::DoUpdate {
2417 assignments,
2418 where_clause,
2419 })
2420 }
2421 }
2422}
2423
2424fn convert_select_body(select: &sp::Select) -> Result<SelectStmt> {
2425 let distinct = match &select.distinct {
2426 Some(sp::Distinct::Distinct) => true,
2427 Some(sp::Distinct::On(_)) => {
2428 return Err(SqlError::Unsupported("DISTINCT ON".into()));
2429 }
2430 _ => false,
2431 };
2432
2433 let (from, from_alias, from_subquery, from_args, from_json_table, joins) =
2434 if select.from.is_empty() {
2435 (String::new(), None, None, None, None, vec![])
2436 } else {
2437 let first_twj = &select.from[0];
2438 let (first_name, first_alias, first_sub, first_args, first_jt) =
2439 convert_from_relation(&first_twj.relation)?;
2440 let mut joins: Vec<JoinClause> = first_twj
2441 .joins
2442 .iter()
2443 .map(convert_join)
2444 .collect::<Result<Vec<_>>>()?;
2445 for extra_twj in &select.from[1..] {
2446 let (extra_name, extra_alias, extra_sub, extra_args, extra_jt) =
2447 convert_from_relation(&extra_twj.relation)?;
2448 if extra_jt.is_some() {
2449 return Err(SqlError::Unsupported(
2450 "JSON_TABLE in extra FROM positions not supported".into(),
2451 ));
2452 }
2453 joins.push(JoinClause {
2454 join_type: JoinType::Cross,
2455 table: TableRef {
2456 name: extra_name,
2457 alias: extra_alias,
2458 args: extra_args,
2459 },
2460 subquery: extra_sub,
2461 on_clause: None,
2462 });
2463 for j in &extra_twj.joins {
2464 joins.push(convert_join(j)?);
2465 }
2466 }
2467 (
2468 first_name,
2469 first_alias,
2470 first_sub,
2471 first_args,
2472 first_jt,
2473 joins,
2474 )
2475 };
2476 for j in &joins {
2477 if let Some(sub) = &j.subquery {
2478 if sub.lateral && matches!(j.join_type, JoinType::Right | JoinType::FullOuter) {
2479 return Err(SqlError::Unsupported(
2480 "LATERAL is not allowed on the right side of RIGHT JOIN or FULL OUTER JOIN"
2481 .into(),
2482 ));
2483 }
2484 }
2485 }
2486
2487 let columns: Vec<SelectColumn> = select
2488 .projection
2489 .iter()
2490 .map(convert_select_item)
2491 .collect::<Result<_>>()?;
2492
2493 let where_clause = select.selection.as_ref().map(convert_expr).transpose()?;
2494
2495 let group_by = match &select.group_by {
2496 sp::GroupByExpr::Expressions(exprs, _) => {
2497 exprs.iter().map(convert_expr).collect::<Result<_>>()?
2498 }
2499 sp::GroupByExpr::All(_) => {
2500 return Err(SqlError::Unsupported("GROUP BY ALL".into()));
2501 }
2502 };
2503
2504 let having = select.having.as_ref().map(convert_expr).transpose()?;
2505
2506 Ok(SelectStmt {
2507 columns,
2508 from,
2509 from_alias,
2510 from_subquery,
2511 from_args,
2512 from_json_table,
2513 joins,
2514 distinct,
2515 where_clause,
2516 order_by: vec![],
2517 limit: None,
2518 offset: None,
2519 group_by,
2520 having,
2521 })
2522}
2523
2524type FromRelation = (
2525 String,
2526 Option<String>,
2527 Option<Box<DerivedTable>>,
2528 Option<Vec<Expr>>,
2529 Option<Box<JsonTableSpec>>,
2530);
2531
2532fn convert_from_relation(relation: &sp::TableFactor) -> Result<FromRelation> {
2533 match relation {
2534 sp::TableFactor::Table {
2535 name, alias, args, ..
2536 } => {
2537 let table_name = object_name_to_string(name);
2538 let alias_str = alias.as_ref().map(|a| a.name.value.clone());
2539 let args_converted = match args {
2540 Some(table_args) => {
2541 let mut converted = Vec::with_capacity(table_args.args.len());
2542 for arg in &table_args.args {
2543 match arg {
2544 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => {
2545 converted.push(convert_expr(e)?);
2546 }
2547 _ => {
2548 return Err(SqlError::Unsupported(
2549 "non-positional table function argument".into(),
2550 ));
2551 }
2552 }
2553 }
2554 Some(converted)
2555 }
2556 None => None,
2557 };
2558 Ok((table_name, alias_str, None, args_converted, None))
2559 }
2560 sp::TableFactor::Derived {
2561 lateral,
2562 subquery,
2563 alias,
2564 ..
2565 } => {
2566 let alias_name = match alias {
2567 Some(a) => a.name.value.clone(),
2568 None => return Err(SqlError::Unsupported("derived table requires alias".into())),
2569 };
2570 let inner = convert_select_query(subquery)?;
2571 for cte in &inner.ctes {
2572 if matches!(
2573 &cte.body,
2574 QueryBody::Insert(_) | QueryBody::Update(_) | QueryBody::Delete(_)
2575 ) {
2576 return Err(SqlError::Unsupported(
2577 "WITH-DML inside subqueries (PG forbids)".into(),
2578 ));
2579 }
2580 }
2581 let derived = DerivedTable {
2582 query: Box::new(inner),
2583 lateral: *lateral,
2584 alias: alias_name.clone(),
2585 };
2586 Ok((alias_name, None, Some(Box::new(derived)), None, None))
2587 }
2588 sp::TableFactor::JsonTable {
2589 json_expr,
2590 json_path,
2591 columns,
2592 alias,
2593 } => {
2594 let alias_name = match alias {
2595 Some(a) => a.name.value.clone(),
2596 None => "json_table".to_string(),
2597 };
2598 let source = convert_expr(json_expr)?;
2599 let root_path = json_path_value_to_string(json_path)?;
2600 let cols = columns
2601 .iter()
2602 .map(convert_json_table_column)
2603 .collect::<Result<Vec<_>>>()?;
2604 let spec = JsonTableSpec {
2605 source,
2606 root_path,
2607 columns: cols,
2608 };
2609 Ok((alias_name, None, None, None, Some(Box::new(spec))))
2610 }
2611 _ => Err(SqlError::Unsupported("non-table FROM source".into())),
2612 }
2613}
2614
2615fn json_path_value_to_string(v: &sp::Value) -> Result<String> {
2616 use sp::Value as V;
2617 match v {
2618 V::SingleQuotedString(s)
2619 | V::DoubleQuotedString(s)
2620 | V::DollarQuotedString(sp::DollarQuotedString { value: s, .. })
2621 | V::TripleSingleQuotedString(s)
2622 | V::TripleDoubleQuotedString(s) => Ok(s.clone()),
2623 other => Err(SqlError::Unsupported(format!(
2624 "JSON_TABLE path must be a string literal, got: {other}"
2625 ))),
2626 }
2627}
2628
2629fn convert_json_table_column(c: &sp::JsonTableColumn) -> Result<JsonTableCol> {
2630 match c {
2631 sp::JsonTableColumn::Named(n) => {
2632 let path = json_path_value_to_string(&n.path)?;
2633 Ok(JsonTableCol::Named {
2634 name: n.name.value.clone(),
2635 ty: convert_data_type(&n.r#type)?,
2636 path,
2637 exists: n.exists,
2638 })
2639 }
2640 sp::JsonTableColumn::ForOrdinality(ident) => Ok(JsonTableCol::Ordinality {
2641 name: ident.value.clone(),
2642 }),
2643 sp::JsonTableColumn::Nested(n) => {
2644 let path = json_path_value_to_string(&n.path)?;
2645 let columns = n
2646 .columns
2647 .iter()
2648 .map(convert_json_table_column)
2649 .collect::<Result<Vec<_>>>()?;
2650 Ok(JsonTableCol::Nested { path, columns })
2651 }
2652 }
2653}
2654
2655fn convert_set_expr(set_expr: &sp::SetExpr) -> Result<QueryBody> {
2656 match set_expr {
2657 sp::SetExpr::Select(sel) => Ok(QueryBody::Select(Box::new(convert_select_body(sel)?))),
2658 sp::SetExpr::Insert(stmt) => match convert_statement(stmt.clone())? {
2659 Statement::Insert(ins) => Ok(QueryBody::Insert(Box::new(ins))),
2660 _ => Err(SqlError::Parse("expected INSERT in WITH-DML body".into())),
2661 },
2662 sp::SetExpr::Update(stmt) => match convert_statement(stmt.clone())? {
2663 Statement::Update(upd) => Ok(QueryBody::Update(Box::new(upd))),
2664 _ => Err(SqlError::Parse("expected UPDATE in WITH-DML body".into())),
2665 },
2666 sp::SetExpr::Delete(stmt) => match convert_statement(stmt.clone())? {
2667 Statement::Delete(del) => Ok(QueryBody::Delete(Box::new(del))),
2668 _ => Err(SqlError::Parse("expected DELETE in WITH-DML body".into())),
2669 },
2670 sp::SetExpr::SetOperation {
2671 op,
2672 set_quantifier,
2673 left,
2674 right,
2675 } => {
2676 let set_op = match op {
2677 sp::SetOperator::Union => SetOp::Union,
2678 sp::SetOperator::Intersect => SetOp::Intersect,
2679 sp::SetOperator::Except | sp::SetOperator::Minus => SetOp::Except,
2680 };
2681 let all = match set_quantifier {
2682 sp::SetQuantifier::All => true,
2683 sp::SetQuantifier::None | sp::SetQuantifier::Distinct => false,
2684 _ => {
2685 return Err(SqlError::Unsupported("BY NAME set operations".into()));
2686 }
2687 };
2688 Ok(QueryBody::Compound(Box::new(CompoundSelect {
2689 op: set_op,
2690 all,
2691 left: Box::new(convert_set_expr(left)?),
2692 right: Box::new(convert_set_expr(right)?),
2693 order_by: vec![],
2694 limit: None,
2695 offset: None,
2696 })))
2697 }
2698 _ => Err(SqlError::Unsupported("unsupported set expression".into())),
2699 }
2700}
2701
2702fn convert_query_body(query: &sp::Query) -> Result<QueryBody> {
2703 let mut body = convert_set_expr(&query.body)?;
2704
2705 let order_by = if let Some(ref ob) = query.order_by {
2706 match &ob.kind {
2707 sp::OrderByKind::Expressions(exprs) => exprs
2708 .iter()
2709 .map(convert_order_by_expr)
2710 .collect::<Result<_>>()?,
2711 sp::OrderByKind::All { .. } => {
2712 return Err(SqlError::Unsupported("ORDER BY ALL".into()));
2713 }
2714 }
2715 } else {
2716 vec![]
2717 };
2718
2719 let (limit, offset) = match &query.limit_clause {
2720 Some(sp::LimitClause::LimitOffset { limit, offset, .. }) => {
2721 let l = limit.as_ref().map(convert_expr).transpose()?;
2722 let o = offset
2723 .as_ref()
2724 .map(|o| convert_expr(&o.value))
2725 .transpose()?;
2726 (l, o)
2727 }
2728 Some(sp::LimitClause::OffsetCommaLimit { limit, offset }) => {
2729 let l = Some(convert_expr(limit)?);
2730 let o = Some(convert_expr(offset)?);
2731 (l, o)
2732 }
2733 None => (None, None),
2734 };
2735
2736 match &mut body {
2737 QueryBody::Select(sel) => {
2738 sel.order_by = order_by;
2739 sel.limit = limit;
2740 sel.offset = offset;
2741 }
2742 QueryBody::Compound(comp) => {
2743 comp.order_by = order_by;
2744 comp.limit = limit;
2745 comp.offset = offset;
2746 }
2747 QueryBody::Insert(_) | QueryBody::Update(_) | QueryBody::Delete(_) => {
2748 if !order_by.is_empty() || limit.is_some() || offset.is_some() {
2749 return Err(SqlError::Parse(
2750 "ORDER BY / LIMIT / OFFSET not allowed on DML CTE body".into(),
2751 ));
2752 }
2753 }
2754 }
2755
2756 Ok(body)
2757}
2758
2759fn convert_subquery(query: &sp::Query) -> Result<SelectStmt> {
2760 if query.with.is_some() {
2761 return Err(SqlError::Unsupported("CTEs in subqueries".into()));
2762 }
2763 match convert_query_body(query)? {
2764 QueryBody::Select(s) => Ok(*s),
2765 QueryBody::Compound(_) => Err(SqlError::Unsupported(
2766 "UNION/INTERSECT/EXCEPT in subqueries".into(),
2767 )),
2768 QueryBody::Insert(_) | QueryBody::Update(_) | QueryBody::Delete(_) => Err(
2769 SqlError::Unsupported("WITH-DML inside subqueries (PG forbids)".into()),
2770 ),
2771 }
2772}
2773
2774fn convert_with(with: &sp::With) -> Result<(Vec<CteDefinition>, bool)> {
2775 let mut names = rustc_hash::FxHashSet::default();
2776 let mut ctes = Vec::new();
2777 for cte in &with.cte_tables {
2778 let name = cte.alias.name.value.to_ascii_lowercase();
2779 if !names.insert(name.clone()) {
2780 return Err(SqlError::DuplicateCteName(name));
2781 }
2782 let column_aliases: Vec<String> = cte
2783 .alias
2784 .columns
2785 .iter()
2786 .map(|c| c.name.value.to_ascii_lowercase())
2787 .collect();
2788 let body = convert_query_body(&cte.query)?;
2789 ctes.push(CteDefinition {
2790 name,
2791 column_aliases,
2792 body,
2793 });
2794 }
2795 Ok((ctes, with.recursive))
2796}
2797
2798fn convert_query(query: sp::Query) -> Result<Statement> {
2799 let sq = convert_select_query(&query)?;
2800 Ok(Statement::Select(Box::new(sq)))
2801}
2802
2803fn convert_select_query(query: &sp::Query) -> Result<SelectQuery> {
2804 let (ctes, recursive) = if let Some(ref with) = query.with {
2805 convert_with(with)?
2806 } else {
2807 (vec![], false)
2808 };
2809 let body = convert_query_body(query)?;
2810 Ok(SelectQuery {
2811 ctes,
2812 recursive,
2813 body,
2814 })
2815}
2816
2817fn convert_join(join: &sp::Join) -> Result<JoinClause> {
2818 let (join_type, constraint) = match &join.join_operator {
2819 sp::JoinOperator::Inner(c) => (JoinType::Inner, Some(c)),
2820 sp::JoinOperator::Join(c) => (JoinType::Inner, Some(c)),
2821 sp::JoinOperator::CrossJoin(c) => (JoinType::Cross, Some(c)),
2822 sp::JoinOperator::Left(c) | sp::JoinOperator::LeftOuter(c) => (JoinType::Left, Some(c)),
2823 sp::JoinOperator::LeftSemi(c) => (JoinType::Left, Some(c)),
2824 sp::JoinOperator::LeftAnti(c) => (JoinType::Left, Some(c)),
2825 sp::JoinOperator::Right(c) | sp::JoinOperator::RightOuter(c) => (JoinType::Right, Some(c)),
2826 sp::JoinOperator::RightSemi(c) => (JoinType::Right, Some(c)),
2827 sp::JoinOperator::RightAnti(c) => (JoinType::Right, Some(c)),
2828 sp::JoinOperator::FullOuter(c) => (JoinType::FullOuter, Some(c)),
2829 other => return Err(SqlError::Unsupported(format!("join type: {other:?}"))),
2830 };
2831
2832 let (name, alias, subquery, args, json_table) = convert_from_relation(&join.relation)?;
2833 if json_table.is_some() {
2834 return Err(SqlError::Unsupported(
2835 "JSON_TABLE on right side of JOIN".into(),
2836 ));
2837 }
2838
2839 let on_clause = match constraint {
2840 Some(sp::JoinConstraint::On(expr)) => Some(convert_expr(expr)?),
2841 Some(sp::JoinConstraint::None) | None => None,
2842 Some(other) => return Err(SqlError::Unsupported(format!("join constraint: {other:?}"))),
2843 };
2844
2845 Ok(JoinClause {
2846 join_type,
2847 table: TableRef { name, alias, args },
2848 subquery,
2849 on_clause,
2850 })
2851}
2852
2853fn convert_update(update: sp::Update) -> Result<Statement> {
2854 let table = match &update.table.relation {
2855 sp::TableFactor::Table { name, .. } => object_name_to_string(name),
2856 _ => return Err(SqlError::Unsupported("non-table UPDATE target".into())),
2857 };
2858
2859 let assignments = update
2860 .assignments
2861 .iter()
2862 .map(|a| {
2863 let col = match &a.target {
2864 sp::AssignmentTarget::ColumnName(name) => object_name_to_string(name),
2865 _ => return Err(SqlError::Unsupported("tuple assignment".into())),
2866 };
2867 let expr = convert_expr(&a.value)?;
2868 Ok((col, expr))
2869 })
2870 .collect::<Result<_>>()?;
2871
2872 let where_clause = update.selection.as_ref().map(convert_expr).transpose()?;
2873 let returning = convert_returning(update.returning.as_deref())?;
2874
2875 Ok(Statement::Update(UpdateStmt {
2876 table,
2877 assignments,
2878 where_clause,
2879 returning,
2880 }))
2881}
2882
2883fn convert_truncate(t: sp::Truncate) -> Result<Statement> {
2884 if matches!(t.cascade, Some(sp::CascadeOption::Cascade)) {
2885 return Err(SqlError::Unsupported("TRUNCATE CASCADE".into()));
2886 }
2887 if t.if_exists {
2888 return Err(SqlError::Unsupported("TRUNCATE IF EXISTS".into()));
2889 }
2890 if t.partitions.is_some() {
2891 return Err(SqlError::Unsupported("TRUNCATE PARTITION".into()));
2892 }
2893 if t.on_cluster.is_some() {
2894 return Err(SqlError::Unsupported("TRUNCATE ON CLUSTER".into()));
2895 }
2896 if t.table_names.is_empty() {
2897 return Err(SqlError::Parse(
2898 "TRUNCATE requires at least one table".into(),
2899 ));
2900 }
2901
2902 let tables: Vec<String> = t
2903 .table_names
2904 .iter()
2905 .map(|tt| object_name_to_string(&tt.name))
2906 .collect();
2907
2908 Ok(Statement::Truncate(TruncateStmt { tables }))
2909}
2910
2911fn convert_delete(delete: sp::Delete) -> Result<Statement> {
2912 let table_name = match &delete.from {
2913 sp::FromTable::WithFromKeyword(tables) => {
2914 if tables.len() != 1 {
2915 return Err(SqlError::Unsupported("multi-table DELETE".into()));
2916 }
2917 match &tables[0].relation {
2918 sp::TableFactor::Table { name, .. } => object_name_to_string(name),
2919 _ => return Err(SqlError::Unsupported("non-table DELETE target".into())),
2920 }
2921 }
2922 sp::FromTable::WithoutKeyword(tables) => {
2923 if tables.len() != 1 {
2924 return Err(SqlError::Unsupported("multi-table DELETE".into()));
2925 }
2926 match &tables[0].relation {
2927 sp::TableFactor::Table { name, .. } => object_name_to_string(name),
2928 _ => return Err(SqlError::Unsupported("non-table DELETE target".into())),
2929 }
2930 }
2931 };
2932
2933 let where_clause = delete.selection.as_ref().map(convert_expr).transpose()?;
2934 let returning = convert_returning(delete.returning.as_deref())?;
2935
2936 Ok(Statement::Delete(DeleteStmt {
2937 table: table_name,
2938 where_clause,
2939 returning,
2940 }))
2941}
2942
2943fn convert_expr(expr: &sp::Expr) -> Result<Expr> {
2944 match expr {
2945 sp::Expr::Value(v) => convert_value(&v.value),
2946 sp::Expr::Identifier(ident) => Ok(Expr::Column(ident.value.to_ascii_lowercase())),
2947 sp::Expr::CompoundIdentifier(parts) => {
2948 if parts.len() == 2 {
2949 Ok(Expr::QualifiedColumn {
2950 table: parts[0].value.to_ascii_lowercase(),
2951 column: parts[1].value.to_ascii_lowercase(),
2952 })
2953 } else {
2954 Ok(Expr::Column(
2955 parts.last().unwrap().value.to_ascii_lowercase(),
2956 ))
2957 }
2958 }
2959 sp::Expr::BinaryOp { left, op, right } => {
2960 let bin_op = convert_bin_op(op)?;
2961 Ok(Expr::BinaryOp {
2962 left: Box::new(convert_expr(left)?),
2963 op: bin_op,
2964 right: Box::new(convert_expr(right)?),
2965 })
2966 }
2967 sp::Expr::UnaryOp { op, expr } => {
2968 let unary_op = match op {
2969 sp::UnaryOperator::Minus => UnaryOp::Neg,
2970 sp::UnaryOperator::Not => UnaryOp::Not,
2971 _ => return Err(SqlError::Unsupported(format!("unary op: {op}"))),
2972 };
2973 Ok(Expr::UnaryOp {
2974 op: unary_op,
2975 expr: Box::new(convert_expr(expr)?),
2976 })
2977 }
2978 sp::Expr::IsNull(e) => Ok(Expr::IsNull(Box::new(convert_expr(e)?))),
2979 sp::Expr::IsNotNull(e) => Ok(Expr::IsNotNull(Box::new(convert_expr(e)?))),
2980 sp::Expr::Nested(e) => convert_expr(e),
2981 sp::Expr::Function(func) => convert_function(func),
2982 sp::Expr::InSubquery {
2983 expr: e,
2984 subquery,
2985 negated,
2986 } => {
2987 let inner_expr = convert_expr(e)?;
2988 let stmt = convert_subquery(subquery)?;
2989 Ok(Expr::InSubquery {
2990 expr: Box::new(inner_expr),
2991 subquery: Box::new(stmt),
2992 negated: *negated,
2993 })
2994 }
2995 sp::Expr::InList {
2996 expr: e,
2997 list,
2998 negated,
2999 } => {
3000 let inner_expr = convert_expr(e)?;
3001 let items = list.iter().map(convert_expr).collect::<Result<Vec<_>>>()?;
3002 Ok(Expr::InList {
3003 expr: Box::new(inner_expr),
3004 list: items,
3005 negated: *negated,
3006 })
3007 }
3008 sp::Expr::Exists { subquery, negated } => {
3009 let stmt = convert_subquery(subquery)?;
3010 Ok(Expr::Exists {
3011 subquery: Box::new(stmt),
3012 negated: *negated,
3013 })
3014 }
3015 sp::Expr::Subquery(query) => {
3016 let stmt = convert_subquery(query)?;
3017 Ok(Expr::ScalarSubquery(Box::new(stmt)))
3018 }
3019 sp::Expr::AnyOp {
3020 left,
3021 compare_op,
3022 right,
3023 ..
3024 } => convert_quantified(left, compare_op, right, Quantifier::Any),
3025 sp::Expr::AllOp {
3026 left,
3027 compare_op,
3028 right,
3029 } => convert_quantified(left, compare_op, right, Quantifier::All),
3030 sp::Expr::Array(sp::Array { elem, .. }) => {
3031 let elems: Result<Vec<Expr>> = elem.iter().map(convert_expr).collect();
3032 Ok(Expr::ArrayLiteral(elems?))
3033 }
3034 sp::Expr::Between {
3035 expr: e,
3036 negated,
3037 low,
3038 high,
3039 } => Ok(Expr::Between {
3040 expr: Box::new(convert_expr(e)?),
3041 low: Box::new(convert_expr(low)?),
3042 high: Box::new(convert_expr(high)?),
3043 negated: *negated,
3044 }),
3045 sp::Expr::Like {
3046 expr: e,
3047 negated,
3048 pattern,
3049 escape_char,
3050 ..
3051 } => {
3052 let esc = escape_char
3053 .as_ref()
3054 .map(convert_escape_value)
3055 .transpose()?
3056 .map(Box::new);
3057 Ok(Expr::Like {
3058 expr: Box::new(convert_expr(e)?),
3059 pattern: Box::new(convert_expr(pattern)?),
3060 escape: esc,
3061 negated: *negated,
3062 })
3063 }
3064 sp::Expr::ILike {
3065 expr: e,
3066 negated,
3067 pattern,
3068 escape_char,
3069 ..
3070 } => {
3071 let esc = escape_char
3072 .as_ref()
3073 .map(convert_escape_value)
3074 .transpose()?
3075 .map(Box::new);
3076 Ok(Expr::Like {
3077 expr: Box::new(convert_expr(e)?),
3078 pattern: Box::new(convert_expr(pattern)?),
3079 escape: esc,
3080 negated: *negated,
3081 })
3082 }
3083 sp::Expr::Case {
3084 operand,
3085 conditions,
3086 else_result,
3087 ..
3088 } => {
3089 let op = operand
3090 .as_ref()
3091 .map(|e| convert_expr(e))
3092 .transpose()?
3093 .map(Box::new);
3094 let conds: Vec<(Expr, Expr)> = conditions
3095 .iter()
3096 .map(|cw| Ok((convert_expr(&cw.condition)?, convert_expr(&cw.result)?)))
3097 .collect::<Result<_>>()?;
3098 let else_r = else_result
3099 .as_ref()
3100 .map(|e| convert_expr(e))
3101 .transpose()?
3102 .map(Box::new);
3103 Ok(Expr::Case {
3104 operand: op,
3105 conditions: conds,
3106 else_result: else_r,
3107 })
3108 }
3109 sp::Expr::Cast {
3110 expr: e,
3111 data_type: dt,
3112 ..
3113 } => {
3114 if let (sp::DataType::Custom(name, modifiers), sp::Expr::Value(v)) = (dt, e.as_ref()) {
3115 if modifiers.is_empty() && name.0.len() == 1 && matches!(v.value, sp::Value::Null) {
3116 if let sp::ObjectNamePart::Identifier(id) = &name.0[0] {
3117 return Ok(Expr::TypedNullRecord(id.value.clone()));
3118 }
3119 }
3120 }
3121 let target = convert_data_type(dt)?;
3122 let inner = convert_expr(e)?;
3123 if matches!(target, DataType::Json | DataType::Jsonb) {
3124 if let Expr::Literal(Value::Text(s)) = &inner {
3125 let v = if matches!(target, DataType::Json) {
3126 crate::json::validate_text(s.as_str())?;
3127 Value::Json(s.clone())
3128 } else {
3129 crate::json::text_to_jsonb(s.as_str())?
3130 };
3131 return Ok(Expr::Literal(v));
3132 }
3133 }
3134 Ok(Expr::Cast {
3135 expr: Box::new(inner),
3136 data_type: target,
3137 })
3138 }
3139 sp::Expr::Collate {
3140 expr: e,
3141 collation: name,
3142 } => {
3143 let coll_name = object_name_to_string(name);
3144 let coll = crate::types::Collation::from_name(&coll_name).ok_or_else(|| {
3145 SqlError::Unsupported(format!(
3146 "collation '{coll_name}' not supported (BINARY/NOCASE/RTRIM only)"
3147 ))
3148 })?;
3149 Ok(Expr::Collate {
3150 expr: Box::new(convert_expr(e)?),
3151 collation: coll,
3152 })
3153 }
3154 sp::Expr::Substring {
3155 expr: e,
3156 substring_from,
3157 substring_for,
3158 ..
3159 } => {
3160 let mut args = vec![convert_expr(e)?];
3161 if let Some(from) = substring_from {
3162 args.push(convert_expr(from)?);
3163 }
3164 if let Some(f) = substring_for {
3165 args.push(convert_expr(f)?);
3166 }
3167 Ok(Expr::Function {
3168 name: "SUBSTR".into(),
3169 args,
3170 distinct: false,
3171 })
3172 }
3173 sp::Expr::Trim {
3174 expr: e,
3175 trim_where,
3176 trim_what,
3177 trim_characters,
3178 } => {
3179 let fn_name = match trim_where {
3180 Some(sp::TrimWhereField::Leading) => "LTRIM",
3181 Some(sp::TrimWhereField::Trailing) => "RTRIM",
3182 _ => "TRIM",
3183 };
3184 let mut args = vec![convert_expr(e)?];
3185 if let Some(what) = trim_what {
3186 args.push(convert_expr(what)?);
3187 } else if let Some(chars) = trim_characters {
3188 if let Some(first) = chars.first() {
3189 args.push(convert_expr(first)?);
3190 }
3191 }
3192 Ok(Expr::Function {
3193 name: fn_name.into(),
3194 args,
3195 distinct: false,
3196 })
3197 }
3198 sp::Expr::Ceil { expr: e, .. } => Ok(Expr::Function {
3199 name: "CEIL".into(),
3200 args: vec![convert_expr(e)?],
3201 distinct: false,
3202 }),
3203 sp::Expr::Floor { expr: e, .. } => Ok(Expr::Function {
3204 name: "FLOOR".into(),
3205 args: vec![convert_expr(e)?],
3206 distinct: false,
3207 }),
3208 sp::Expr::Position { expr: e, r#in } => Ok(Expr::Function {
3209 name: "INSTR".into(),
3210 args: vec![convert_expr(r#in)?, convert_expr(e)?],
3211 distinct: false,
3212 }),
3213 sp::Expr::TypedString(ts) => {
3214 let raw = match &ts.value.value {
3215 sp::Value::SingleQuotedString(s) => s.clone(),
3216 sp::Value::DoubleQuotedString(s) => s.clone(),
3217 other => other.to_string(),
3218 };
3219 convert_typed_string(&ts.data_type, &raw)
3220 }
3221 sp::Expr::Interval(iv) => convert_interval_expr(iv),
3222 sp::Expr::Extract { field, expr: e, .. } => {
3223 let field_name = match field {
3224 sp::DateTimeField::Year => "year",
3225 sp::DateTimeField::Month => "month",
3226 sp::DateTimeField::Week(_) => "week",
3227 sp::DateTimeField::Day => "day",
3228 sp::DateTimeField::Date => "day",
3229 sp::DateTimeField::Hour => "hour",
3230 sp::DateTimeField::Minute => "minute",
3231 sp::DateTimeField::Second => "second",
3232 sp::DateTimeField::Millisecond => "milliseconds",
3233 sp::DateTimeField::Microsecond => "microseconds",
3234 sp::DateTimeField::Microseconds => "microseconds",
3235 sp::DateTimeField::Milliseconds => "milliseconds",
3236 sp::DateTimeField::Dow => "dow",
3237 sp::DateTimeField::Isodow => "isodow",
3238 sp::DateTimeField::Doy => "doy",
3239 sp::DateTimeField::Epoch => "epoch",
3240 sp::DateTimeField::Quarter => "quarter",
3241 sp::DateTimeField::Decade => "decade",
3242 sp::DateTimeField::Century => "century",
3243 sp::DateTimeField::Millennium => "millennium",
3244 sp::DateTimeField::Isoyear => "isoyear",
3245 sp::DateTimeField::Julian => "julian",
3246 other => {
3247 return Err(SqlError::InvalidExtractField(format!("{other:?}")));
3248 }
3249 };
3250 Ok(Expr::Function {
3251 name: "EXTRACT".into(),
3252 args: vec![
3253 Expr::Literal(Value::Text(field_name.into())),
3254 convert_expr(e)?,
3255 ],
3256 distinct: false,
3257 })
3258 }
3259 sp::Expr::AtTimeZone {
3260 timestamp,
3261 time_zone,
3262 } => Ok(Expr::Function {
3263 name: "AT_TIMEZONE".into(),
3264 args: vec![convert_expr(timestamp)?, convert_expr(time_zone)?],
3265 distinct: false,
3266 }),
3267 _ => Err(SqlError::Unsupported(format!("expression: {expr}"))),
3268 }
3269}
3270
3271fn convert_value(val: &sp::Value) -> Result<Expr> {
3272 match val {
3273 sp::Value::Number(n, _) => {
3274 if let Ok(i) = n.parse::<i64>() {
3275 Ok(Expr::Literal(Value::Integer(i)))
3276 } else if let Ok(f) = n.parse::<f64>() {
3277 Ok(Expr::Literal(Value::Real(f)))
3278 } else {
3279 Err(SqlError::InvalidValue(format!("cannot parse number: {n}")))
3280 }
3281 }
3282 sp::Value::SingleQuotedString(s) => Ok(Expr::Literal(Value::Text(s.as_str().into()))),
3283 sp::Value::Boolean(b) => Ok(Expr::Literal(Value::Boolean(*b))),
3284 sp::Value::Null => Ok(Expr::Literal(Value::Null)),
3285 sp::Value::Placeholder(s) => {
3286 let idx_str = s
3287 .strip_prefix('$')
3288 .ok_or_else(|| SqlError::Parse(format!("invalid placeholder: {s}")))?;
3289 let idx: usize = idx_str
3290 .parse()
3291 .map_err(|_| SqlError::Parse(format!("invalid placeholder index: {s}")))?;
3292 if idx == 0 {
3293 return Err(SqlError::Parse("placeholder index must be >= 1".into()));
3294 }
3295 Ok(Expr::Parameter(idx))
3296 }
3297 _ => Err(SqlError::Unsupported(format!("value type: {val}"))),
3298 }
3299}
3300
3301fn convert_typed_string(dt: &sp::DataType, value: &str) -> Result<Expr> {
3302 let s = value.trim_matches('\'');
3303 match dt {
3304 sp::DataType::Date => {
3305 let d = crate::datetime::parse_date(s)?;
3306 Ok(Expr::Literal(Value::Date(d)))
3307 }
3308 sp::DataType::Time(_, _) => {
3309 let t = crate::datetime::parse_time(s)?;
3310 Ok(Expr::Literal(Value::Time(t)))
3311 }
3312 sp::DataType::Timestamp(_, _) => {
3313 let t = crate::datetime::parse_timestamp(s)?;
3314 Ok(Expr::Literal(Value::Timestamp(t)))
3315 }
3316 sp::DataType::Interval { .. } => {
3317 let (months, days, micros) = crate::datetime::parse_interval(s)?;
3318 Ok(Expr::Literal(Value::Interval {
3319 months,
3320 days,
3321 micros,
3322 }))
3323 }
3324 _ => {
3325 let target = convert_data_type(dt)?;
3326 Ok(Expr::Cast {
3327 expr: Box::new(Expr::Literal(Value::Text(s.into()))),
3328 data_type: target,
3329 })
3330 }
3331 }
3332}
3333
3334fn convert_interval_expr(iv: &sp::Interval) -> Result<Expr> {
3335 let raw = match iv.value.as_ref() {
3336 sp::Expr::Value(v) => match &v.value {
3337 sp::Value::SingleQuotedString(s) => s.clone(),
3338 sp::Value::Number(n, _) => n.clone(),
3339 other => {
3340 return Err(SqlError::InvalidIntervalLiteral(format!(
3341 "unsupported inner value: {other}"
3342 )))
3343 }
3344 },
3345 other => {
3346 return Err(SqlError::InvalidIntervalLiteral(format!(
3347 "unsupported inner expr: {other}"
3348 )))
3349 }
3350 };
3351
3352 let with_unit = if let Some(field) = &iv.leading_field {
3353 let unit_name = match field {
3354 sp::DateTimeField::Year => "years",
3355 sp::DateTimeField::Month => "months",
3356 sp::DateTimeField::Week(_) => "weeks",
3357 sp::DateTimeField::Day => "days",
3358 sp::DateTimeField::Hour => "hours",
3359 sp::DateTimeField::Minute => "minutes",
3360 sp::DateTimeField::Second => "seconds",
3361 _ => {
3362 return Err(SqlError::InvalidIntervalLiteral(format!(
3363 "unsupported leading field: {field:?}"
3364 )))
3365 }
3366 };
3367 format!("{raw} {unit_name}")
3368 } else {
3369 raw
3370 };
3371
3372 let (months, days, micros) = crate::datetime::parse_interval(&with_unit)?;
3373 Ok(Expr::Literal(Value::Interval {
3374 months,
3375 days,
3376 micros,
3377 }))
3378}
3379
3380fn convert_escape_value(val: &sp::Value) -> Result<Expr> {
3381 match val {
3382 sp::Value::SingleQuotedString(s) => Ok(Expr::Literal(Value::Text(s.as_str().into()))),
3383 _ => Err(SqlError::Unsupported(format!("ESCAPE value: {val}"))),
3384 }
3385}
3386
3387fn convert_quantified(
3388 left: &sp::Expr,
3389 compare_op: &sp::BinaryOperator,
3390 right: &sp::Expr,
3391 quantifier: Quantifier,
3392) -> Result<Expr> {
3393 let left_expr = convert_expr(left)?;
3394 let op = convert_bin_op(compare_op)?;
3395 if !matches!(
3396 op,
3397 BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::LtEq | BinOp::GtEq
3398 ) {
3399 return Err(SqlError::Unsupported(format!(
3400 "ANY/ALL only supports comparison operators, got {op:?}"
3401 )));
3402 }
3403 let rhs = match right {
3404 sp::Expr::Subquery(query) => {
3405 let stmt = convert_subquery(query)?;
3406 QuantifiedRhs::Subquery(Box::new(stmt))
3407 }
3408 other => QuantifiedRhs::Array(Box::new(convert_expr(other)?)),
3409 };
3410 Ok(Expr::Quantified {
3411 left: Box::new(left_expr),
3412 op,
3413 quantifier,
3414 right: rhs,
3415 })
3416}
3417
3418fn convert_bin_op(op: &sp::BinaryOperator) -> Result<BinOp> {
3419 match op {
3420 sp::BinaryOperator::Plus => Ok(BinOp::Add),
3421 sp::BinaryOperator::Minus => Ok(BinOp::Sub),
3422 sp::BinaryOperator::Multiply => Ok(BinOp::Mul),
3423 sp::BinaryOperator::Divide => Ok(BinOp::Div),
3424 sp::BinaryOperator::Modulo => Ok(BinOp::Mod),
3425 sp::BinaryOperator::Eq => Ok(BinOp::Eq),
3426 sp::BinaryOperator::NotEq => Ok(BinOp::NotEq),
3427 sp::BinaryOperator::Lt => Ok(BinOp::Lt),
3428 sp::BinaryOperator::Gt => Ok(BinOp::Gt),
3429 sp::BinaryOperator::LtEq => Ok(BinOp::LtEq),
3430 sp::BinaryOperator::GtEq => Ok(BinOp::GtEq),
3431 sp::BinaryOperator::And => Ok(BinOp::And),
3432 sp::BinaryOperator::Or => Ok(BinOp::Or),
3433 sp::BinaryOperator::StringConcat => Ok(BinOp::Concat),
3434 sp::BinaryOperator::Arrow => Ok(BinOp::JsonGet),
3435 sp::BinaryOperator::LongArrow => Ok(BinOp::JsonGetText),
3436 sp::BinaryOperator::HashArrow => Ok(BinOp::JsonPath),
3437 sp::BinaryOperator::HashLongArrow => Ok(BinOp::JsonPathText),
3438 sp::BinaryOperator::AtArrow => Ok(BinOp::JsonContains),
3439 sp::BinaryOperator::ArrowAt => Ok(BinOp::JsonContainedBy),
3440 sp::BinaryOperator::Question => Ok(BinOp::JsonHasKey),
3441 sp::BinaryOperator::QuestionPipe => Ok(BinOp::JsonHasAnyKey),
3442 sp::BinaryOperator::QuestionAnd => Ok(BinOp::JsonHasAllKeys),
3443 sp::BinaryOperator::HashMinus => Ok(BinOp::JsonDeletePath),
3444 sp::BinaryOperator::AtQuestion => Ok(BinOp::JsonPathExists),
3445 sp::BinaryOperator::AtAt => Ok(BinOp::JsonPathMatch),
3446 sp::BinaryOperator::Custom(s) if s == "@?_tz" => Ok(BinOp::JsonPathExistsTz),
3447 sp::BinaryOperator::Custom(s) if s == "@@_tz" => Ok(BinOp::JsonPathMatchTz),
3448 _ => Err(SqlError::Unsupported(format!("binary op: {op}"))),
3449 }
3450}
3451
3452fn convert_function(func: &sp::Function) -> Result<Expr> {
3453 let name = object_name_to_string(&func.name).to_ascii_uppercase();
3454
3455 let (args, is_count_star, distinct) = match &func.args {
3456 sp::FunctionArguments::List(list) => {
3457 let distinct = matches!(
3458 list.duplicate_treatment,
3459 Some(sp::DuplicateTreatment::Distinct)
3460 );
3461 if list.args.is_empty() && name == "COUNT" {
3462 (vec![], true, distinct)
3463 } else {
3464 let mut count_star = false;
3465 let args = list
3466 .args
3467 .iter()
3468 .map(|arg| match arg {
3469 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => convert_expr(e),
3470 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Wildcard) => {
3471 if name == "COUNT" {
3472 count_star = true;
3473 Ok(Expr::CountStar)
3474 } else {
3475 Err(SqlError::Unsupported(format!("{name}(*)")))
3476 }
3477 }
3478 _ => Err(SqlError::Unsupported(format!(
3479 "function arg type in {name}"
3480 ))),
3481 })
3482 .collect::<Result<Vec<_>>>()?;
3483 if name == "COUNT" && args.len() == 1 && count_star {
3484 (vec![], true, distinct)
3485 } else {
3486 (args, false, distinct)
3487 }
3488 }
3489 }
3490 sp::FunctionArguments::None => {
3491 if name == "COUNT" {
3492 (vec![], true, false)
3493 } else {
3494 (vec![], false, false)
3495 }
3496 }
3497 sp::FunctionArguments::Subquery(_) => {
3498 return Err(SqlError::Unsupported("subquery in function".into()));
3499 }
3500 };
3501
3502 if let Some(over) = &func.over {
3503 let spec = match over {
3504 sp::WindowType::WindowSpec(ws) => convert_window_spec(ws)?,
3505 sp::WindowType::NamedWindow(_) => {
3506 return Err(SqlError::Unsupported("named windows".into()));
3507 }
3508 };
3509 return Ok(Expr::WindowFunction { name, args, spec });
3510 }
3511
3512 if is_count_star {
3513 return Ok(Expr::CountStar);
3514 }
3515
3516 if name == "COALESCE" {
3517 if args.is_empty() {
3518 return Err(SqlError::Parse(
3519 "COALESCE requires at least one argument".into(),
3520 ));
3521 }
3522 return Ok(Expr::Coalesce(args));
3523 }
3524
3525 if name == "NULLIF" {
3526 if args.len() != 2 {
3527 return Err(SqlError::Parse(
3528 "NULLIF requires exactly two arguments".into(),
3529 ));
3530 }
3531 return Ok(Expr::Case {
3532 operand: None,
3533 conditions: vec![(
3534 Expr::BinaryOp {
3535 left: Box::new(args[0].clone()),
3536 op: BinOp::Eq,
3537 right: Box::new(args[1].clone()),
3538 },
3539 Expr::Literal(Value::Null),
3540 )],
3541 else_result: Some(Box::new(args[0].clone())),
3542 });
3543 }
3544
3545 if name == "IIF" {
3546 if args.len() != 3 {
3547 return Err(SqlError::Parse(
3548 "IIF requires exactly three arguments".into(),
3549 ));
3550 }
3551 return Ok(Expr::Case {
3552 operand: None,
3553 conditions: vec![(args[0].clone(), args[1].clone())],
3554 else_result: Some(Box::new(args[2].clone())),
3555 });
3556 }
3557
3558 Ok(Expr::Function {
3559 name,
3560 args,
3561 distinct,
3562 })
3563}
3564
3565fn convert_window_spec(ws: &sp::WindowSpec) -> Result<WindowSpec> {
3566 let partition_by = ws
3567 .partition_by
3568 .iter()
3569 .map(convert_expr)
3570 .collect::<Result<Vec<_>>>()?;
3571 let order_by = ws
3572 .order_by
3573 .iter()
3574 .map(convert_order_by_expr)
3575 .collect::<Result<Vec<_>>>()?;
3576 let frame = ws
3577 .window_frame
3578 .as_ref()
3579 .map(convert_window_frame)
3580 .transpose()?;
3581 Ok(WindowSpec {
3582 partition_by,
3583 order_by,
3584 frame,
3585 })
3586}
3587
3588fn convert_window_frame(wf: &sp::WindowFrame) -> Result<WindowFrame> {
3589 let units = match wf.units {
3590 sp::WindowFrameUnits::Rows => WindowFrameUnits::Rows,
3591 sp::WindowFrameUnits::Range => WindowFrameUnits::Range,
3592 sp::WindowFrameUnits::Groups => {
3593 return Err(SqlError::Unsupported("GROUPS window frame".into()));
3594 }
3595 };
3596 let start = convert_window_frame_bound(&wf.start_bound)?;
3597 let end = match &wf.end_bound {
3598 Some(b) => convert_window_frame_bound(b)?,
3599 None => WindowFrameBound::CurrentRow,
3600 };
3601 Ok(WindowFrame { units, start, end })
3602}
3603
3604fn convert_window_frame_bound(b: &sp::WindowFrameBound) -> Result<WindowFrameBound> {
3605 match b {
3606 sp::WindowFrameBound::CurrentRow => Ok(WindowFrameBound::CurrentRow),
3607 sp::WindowFrameBound::Preceding(None) => Ok(WindowFrameBound::UnboundedPreceding),
3608 sp::WindowFrameBound::Preceding(Some(e)) => {
3609 Ok(WindowFrameBound::Preceding(Box::new(convert_expr(e)?)))
3610 }
3611 sp::WindowFrameBound::Following(None) => Ok(WindowFrameBound::UnboundedFollowing),
3612 sp::WindowFrameBound::Following(Some(e)) => {
3613 Ok(WindowFrameBound::Following(Box::new(convert_expr(e)?)))
3614 }
3615 }
3616}
3617
3618fn convert_returning(items: Option<&[sp::SelectItem]>) -> Result<Option<Vec<SelectColumn>>> {
3619 match items {
3620 None => Ok(None),
3621 Some(items) => {
3622 let cols = items
3623 .iter()
3624 .map(convert_returning_item)
3625 .collect::<Result<Vec<_>>>()?;
3626 Ok(Some(cols))
3627 }
3628 }
3629}
3630
3631fn convert_returning_item(item: &sp::SelectItem) -> Result<SelectColumn> {
3632 match item {
3633 sp::SelectItem::Wildcard(_) => Ok(SelectColumn::AllColumns),
3634 sp::SelectItem::UnnamedExpr(e) => {
3635 reject_aggregate_or_window(e, "RETURNING")?;
3636 Ok(SelectColumn::Expr {
3637 expr: convert_expr(e)?,
3638 alias: None,
3639 })
3640 }
3641 sp::SelectItem::ExprWithAlias { expr, alias } => {
3642 reject_aggregate_or_window(expr, "RETURNING")?;
3643 Ok(SelectColumn::Expr {
3644 expr: convert_expr(expr)?,
3645 alias: Some(alias.value.clone()),
3646 })
3647 }
3648 sp::SelectItem::QualifiedWildcard(kind, _) => match kind {
3649 sp::SelectItemQualifiedWildcardKind::ObjectName(name) => {
3650 let s = object_name_to_string(name);
3651 if s.eq_ignore_ascii_case("old") {
3652 Ok(SelectColumn::AllFromOld)
3653 } else if s.eq_ignore_ascii_case("new") {
3654 Ok(SelectColumn::AllFromNew)
3655 } else {
3656 Err(SqlError::Unsupported(format!(
3657 "RETURNING {s}.* — only old.* and new.* qualified wildcards allowed"
3658 )))
3659 }
3660 }
3661 sp::SelectItemQualifiedWildcardKind::Expr(_) => {
3662 Err(SqlError::Unsupported("expression.* in RETURNING".into()))
3663 }
3664 },
3665 }
3666}
3667
3668fn reject_aggregate_or_window(expr: &sp::Expr, ctx: &str) -> Result<()> {
3669 use sp::Expr as E;
3670 match expr {
3671 E::Function(f) => {
3672 if f.over.is_some() {
3673 return Err(SqlError::Unsupported(format!(
3674 "window functions are not allowed in {ctx}"
3675 )));
3676 }
3677 let name = f
3678 .name
3679 .0
3680 .last()
3681 .map(|p| match p {
3682 sp::ObjectNamePart::Identifier(id) => id.value.to_ascii_uppercase(),
3683 _ => String::new(),
3684 })
3685 .unwrap_or_default();
3686 if matches!(
3687 name.as_str(),
3688 "COUNT"
3689 | "SUM"
3690 | "AVG"
3691 | "MIN"
3692 | "MAX"
3693 | "GROUP_CONCAT"
3694 | "STRING_AGG"
3695 | "ARRAY_AGG"
3696 | "BIT_AND"
3697 | "BIT_OR"
3698 | "BOOL_AND"
3699 | "BOOL_OR"
3700 | "EVERY"
3701 | "STDDEV"
3702 | "STDDEV_POP"
3703 | "STDDEV_SAMP"
3704 | "VARIANCE"
3705 | "VAR_POP"
3706 | "VAR_SAMP"
3707 ) {
3708 return Err(SqlError::Unsupported(format!(
3709 "aggregate functions are not allowed in {ctx}"
3710 )));
3711 }
3712 for arg in walk_function_args(f) {
3713 reject_aggregate_or_window(arg, ctx)?;
3714 }
3715 Ok(())
3716 }
3717 E::BinaryOp { left, right, .. } => {
3718 reject_aggregate_or_window(left, ctx)?;
3719 reject_aggregate_or_window(right, ctx)
3720 }
3721 E::UnaryOp { expr, .. } => reject_aggregate_or_window(expr, ctx),
3722 E::Cast { expr, .. } => reject_aggregate_or_window(expr, ctx),
3723 E::Nested(e) => reject_aggregate_or_window(e, ctx),
3724 E::Case {
3725 conditions,
3726 else_result,
3727 ..
3728 } => {
3729 for cwt in conditions {
3730 reject_aggregate_or_window(&cwt.condition, ctx)?;
3731 reject_aggregate_or_window(&cwt.result, ctx)?;
3732 }
3733 if let Some(e) = else_result {
3734 reject_aggregate_or_window(e, ctx)?;
3735 }
3736 Ok(())
3737 }
3738 _ => Ok(()),
3739 }
3740}
3741
3742fn walk_function_args(f: &sp::Function) -> Vec<&sp::Expr> {
3743 use sp::FunctionArguments as FA;
3744 let mut out = Vec::new();
3745 if let FA::List(args) = &f.args {
3746 for a in &args.args {
3747 if let sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) = a {
3748 out.push(e);
3749 }
3750 }
3751 }
3752 out
3753}
3754
3755fn convert_select_item(item: &sp::SelectItem) -> Result<SelectColumn> {
3756 match item {
3757 sp::SelectItem::Wildcard(_) => Ok(SelectColumn::AllColumns),
3758 sp::SelectItem::UnnamedExpr(e) => {
3759 let expr = convert_expr(e)?;
3760 Ok(SelectColumn::Expr { expr, alias: None })
3761 }
3762 sp::SelectItem::ExprWithAlias { expr, alias } => {
3763 let expr = convert_expr(expr)?;
3764 Ok(SelectColumn::Expr {
3765 expr,
3766 alias: Some(alias.value.clone()),
3767 })
3768 }
3769 sp::SelectItem::QualifiedWildcard(_, _) => {
3770 Err(SqlError::Unsupported("qualified wildcard (table.*)".into()))
3771 }
3772 }
3773}
3774
3775fn convert_order_by_expr(expr: &sp::OrderByExpr) -> Result<OrderByItem> {
3776 let e = convert_expr(&expr.expr)?;
3777 let descending = expr.options.asc.map(|asc| !asc).unwrap_or(false);
3778 let nulls_first = expr.options.nulls_first;
3779
3780 Ok(OrderByItem {
3781 expr: e,
3782 descending,
3783 nulls_first,
3784 })
3785}
3786
3787fn convert_data_type(dt: &sp::DataType) -> Result<DataType> {
3788 match dt {
3789 sp::DataType::Int(_)
3790 | sp::DataType::Integer(_)
3791 | sp::DataType::BigInt(_)
3792 | sp::DataType::SmallInt(_)
3793 | sp::DataType::TinyInt(_)
3794 | sp::DataType::Int2(_)
3795 | sp::DataType::Int4(_)
3796 | sp::DataType::Int8(_) => Ok(DataType::Integer),
3797
3798 sp::DataType::Real
3799 | sp::DataType::Double(..)
3800 | sp::DataType::DoublePrecision
3801 | sp::DataType::Float(_)
3802 | sp::DataType::Float4
3803 | sp::DataType::Float64 => Ok(DataType::Real),
3804
3805 sp::DataType::Varchar(_)
3806 | sp::DataType::Text
3807 | sp::DataType::Char(_)
3808 | sp::DataType::Character(_)
3809 | sp::DataType::String(_) => Ok(DataType::Text),
3810
3811 sp::DataType::Blob(_) | sp::DataType::Bytea => Ok(DataType::Blob),
3812
3813 sp::DataType::Boolean | sp::DataType::Bool => Ok(DataType::Boolean),
3814
3815 sp::DataType::Date => Ok(DataType::Date),
3816 sp::DataType::Time(_, _) => Ok(DataType::Time),
3817 sp::DataType::Timestamp(_, _) => Ok(DataType::Timestamp),
3818 sp::DataType::Interval { .. } => Ok(DataType::Interval),
3819
3820 sp::DataType::JSON => Ok(DataType::Json),
3821 sp::DataType::JSONB => Ok(DataType::Jsonb),
3822
3823 sp::DataType::TsVector => Ok(DataType::TsVector),
3824 sp::DataType::TsQuery => Ok(DataType::TsQuery),
3825
3826 _ => Err(SqlError::Unsupported(format!("data type: {dt}"))),
3827 }
3828}
3829
3830fn object_name_to_string(name: &sp::ObjectName) -> String {
3831 name.0
3832 .iter()
3833 .filter_map(|p| match p {
3834 sp::ObjectNamePart::Identifier(ident) => Some(ident.value.clone()),
3835 _ => None,
3836 })
3837 .collect::<Vec<_>>()
3838 .join(".")
3839}
3840
3841#[cfg(test)]
3842#[path = "parser_tests.rs"]
3843mod tests;