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