1use std::cell::RefCell;
10
11use sqlparser::ast as sp;
12use sqlparser::dialect::Dialect;
13use sqlparser::parser::Parser;
14use unisql_core::ast::*;
15use unisql_core::{Allowance, Column, ColumnClass, Read, ReadError, ReadNote, Schema};
16
17pub struct Rules {
19 pub name: &'static str,
20 pub dialect: fn() -> Box<dyn Dialect>,
21 pub fold_quoted: bool,
23 pub nulls_smallest: bool,
25 pub question_placeholders: bool,
27 pub like_ci_default: bool,
29 pub like_default_escape: bool,
31 pub negative_limit_unlimited: bool,
33}
34
35pub static POSTGRES: Rules = Rules {
36 name: "postgres",
37 dialect: || Box::new(sqlparser::dialect::PostgreSqlDialect {}),
38 fold_quoted: false,
39 nulls_smallest: false,
40 question_placeholders: false,
41 like_ci_default: false,
42 like_default_escape: true,
43 negative_limit_unlimited: false,
44};
45
46pub static MYSQL: Rules = Rules {
47 name: "mysql",
48 dialect: || Box::new(sqlparser::dialect::MySqlDialect {}),
49 fold_quoted: true,
50 nulls_smallest: true,
51 question_placeholders: true,
52 like_ci_default: false,
53 like_default_escape: true,
54 negative_limit_unlimited: false,
55};
56
57pub static SQLITE: Rules = Rules {
58 name: "sqlite",
59 dialect: || Box::new(sqlparser::dialect::SQLiteDialect {}),
60 fold_quoted: true,
61 nulls_smallest: true,
62 question_placeholders: true,
63 like_ci_default: true,
64 like_default_escape: false,
65 negative_limit_unlimited: true,
66};
67
68struct Cx {
69 rules: &'static Rules,
70 max_param: u32,
71 names: Vec<(String, u32)>,
72 notes: Vec<ReadNote>,
73}
74
75thread_local! {
76 static CX: RefCell<Option<Cx>> = const { RefCell::new(None) };
77}
78
79fn rules() -> &'static Rules {
80 CX.with(|c| c.borrow().as_ref().expect("reader context").rules)
81}
82
83fn add_note(construct: &str, note: &str, allowance: Allowance) {
84 CX.with(|c| {
85 let mut b = c.borrow_mut();
86 let cx = b.as_mut().expect("reader context");
87 let n = ReadNote {
88 construct: construct.into(),
89 note: note.into(),
90 allowance,
91 };
92 if !cx.notes.contains(&n) {
93 cx.notes.push(n);
94 }
95 });
96}
97
98fn bump_param(explicit: Option<u32>, name: Option<&str>) -> u32 {
99 CX.with(|c| {
100 let mut b = c.borrow_mut();
101 let cx = b.as_mut().expect("reader context");
102 match (explicit, name) {
103 (Some(n), _) => {
104 cx.max_param = cx.max_param.max(n);
105 n
106 }
107 (None, Some(nm)) => {
108 if let Some((_, n)) = cx.names.iter().find(|(k, _)| k == nm) {
109 return *n;
110 }
111 cx.max_param += 1;
112 let n = cx.max_param;
113 cx.names.push((nm.to_string(), n));
114 n
115 }
116 (None, None) => {
117 cx.max_param += 1;
118 cx.max_param
119 }
120 }
121 })
122}
123
124type R<T> = Result<T, ReadError>;
125
126fn no<T>(construct: &str, detail: &str) -> R<T> {
127 Err(ReadError::NotInIr {
128 construct: construct.to_string(),
129 detail: detail.to_string(),
130 })
131}
132
133pub fn read(sql: &str, rules: &'static Rules) -> R<Read> {
135 CX.with(|c| {
136 *c.borrow_mut() = Some(Cx {
137 rules,
138 max_param: 0,
139 names: vec![],
140 notes: vec![],
141 })
142 });
143 let out = (|| {
144 let d = (rules.dialect)();
145 let rewritten;
149 let sql = if rules.name == "mysql" {
150 match pipes_to_or(sql, d.as_ref()) {
151 Some(s) => {
152 add_note(
153 "pipes_as_or",
154 "MySQL `||` is logical OR in the default sql_mode; under PIPES_AS_CONCAT it is concatenation",
155 Allowance::PipesAsLogicalOr,
156 );
157 rewritten = s;
158 rewritten.as_str()
159 }
160 None => sql,
161 }
162 } else {
163 sql
164 };
165 let mut stmts =
166 Parser::parse_sql(d.as_ref(), sql).map_err(|e| ReadError::Syntax(e.to_string()))?;
167 if stmts.len() != 1 {
168 return no(
169 "multiple_statements",
170 &format!("expected one statement, found {}", stmts.len()),
171 );
172 }
173 statement(stmts.remove(0))
174 })();
175 let cx = CX.with(|c| c.borrow_mut().take()).expect("reader context");
176 out.map(|stmt| Read {
177 stmt,
178 notes: cx.notes,
179 })
180}
181
182fn pipes_to_or(sql: &str, dialect: &dyn sqlparser::dialect::Dialect) -> Option<String> {
185 use sqlparser::tokenizer::{Token, Tokenizer};
186 let tokens = Tokenizer::new(dialect, sql).tokenize_with_location().ok()?;
187 let line_starts: Vec<usize> = std::iter::once(0)
188 .chain(
189 sql.chars()
190 .enumerate()
191 .filter(|(_, c)| *c == '\n')
192 .map(|(i, _)| i + 1),
193 )
194 .collect();
195 let mut spots: Vec<usize> = tokens
196 .iter()
197 .filter(|t| t.token == Token::StringConcat)
198 .filter_map(|t| {
199 let line = usize::try_from(t.span.start.line).ok()?.checked_sub(1)?;
200 let col = usize::try_from(t.span.start.column).ok()?.checked_sub(1)?;
201 Some(line_starts.get(line)? + col)
202 })
203 .collect();
204 if spots.is_empty() {
205 return None;
206 }
207 spots.sort_unstable();
208 let chars: Vec<char> = sql.chars().collect();
209 let mut out = String::new();
210 let mut at = 0;
211 for s in spots {
212 if s < at || s + 2 > chars.len() || chars[s] != '|' || chars[s + 1] != '|' {
213 return None;
214 }
215 out.extend(&chars[at..s]);
216 out.push_str(" OR ");
217 at = s + 2;
218 }
219 out.extend(&chars[at..]);
220 Some(out)
221}
222
223#[derive(Clone, Debug, Default)]
226pub struct SchemaRead {
227 pub schema: Schema,
228 pub skipped: Vec<String>,
231}
232
233fn class_of_type(dt: &sp::DataType) -> Option<ColumnClass> {
236 let t = dt.to_string().to_ascii_lowercase();
237 if t.ends_with("[]") || t.starts_with("array") {
238 return None;
239 }
240 let base: String = t
241 .chars()
242 .take_while(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == ' ')
243 .collect();
244 let base = base.trim();
245 let first = base.split_whitespace().next().unwrap_or("");
246 Some(match base {
247 "character varying" | "character" | "national character varying" | "national character" => {
248 ColumnClass::Text
249 }
250 "double precision" => ColumnClass::Numeric,
251 b if b.starts_with("timestamp")
252 || b == "time"
253 || b.starts_with("time with")
254 || b.starts_with("time without") =>
255 {
256 ColumnClass::Other
257 }
258 _ => match first {
259 "text" | "varchar" | "char" | "nchar" | "nvarchar" | "tinytext" | "mediumtext"
260 | "longtext" | "string" | "bpchar" | "clob" => ColumnClass::Text,
261 "int" | "integer" | "smallint" | "bigint" | "tinyint" | "mediumint" | "int2"
262 | "int4" | "int8" | "serial" | "bigserial" | "smallserial" | "decimal" | "numeric"
263 | "dec" | "float" | "float4" | "float8" | "real" | "double" | "number" => {
264 ColumnClass::Numeric
265 }
266 "boolean" | "bool" => ColumnClass::Boolean,
267 "date" | "datetime" | "timestamptz" | "year" | "interval" | "uuid" | "json"
268 | "jsonb" | "bytea" | "blob" | "tinyblob" | "mediumblob" | "longblob" | "binary"
269 | "varbinary" | "bit" | "inet" | "cidr" | "macaddr" => ColumnClass::Other,
270 _ => return None,
271 },
272 })
273}
274
275fn column_from_def(c: &sp::ColumnDef) -> Column {
276 let class = class_of_type(&c.data_type);
277 let collation = c.options.iter().find_map(|o| match &o.option {
278 sp::ColumnOption::Collation(n) => {
279 Some(n.to_string().trim_matches(['"', '`', '\'']).to_string())
280 }
281 _ => None,
282 });
283 let mut col = Column::new(class.unwrap_or(ColumnClass::Unknown));
284 col.collation = collation;
285 col
286}
287
288pub fn read_schema(ddl: &str, rules: &'static Rules) -> R<SchemaRead> {
292 CX.with(|c| {
294 *c.borrow_mut() = Some(Cx {
295 rules,
296 max_param: 0,
297 names: vec![],
298 notes: vec![],
299 })
300 });
301 let out = read_schema_inner(ddl, rules);
302 CX.with(|c| c.borrow_mut().take());
303 out
304}
305
306fn read_schema_inner(ddl: &str, rules: &'static Rules) -> R<SchemaRead> {
307 use sqlparser::tokenizer::{Token, Tokenizer};
308 let d = (rules.dialect)();
309 let tokens = Tokenizer::new(d.as_ref(), ddl)
310 .tokenize_with_location()
311 .map_err(|e| ReadError::Syntax(e.to_string()))?;
312 let chars: Vec<char> = ddl.chars().collect();
313 let line_starts: Vec<usize> = std::iter::once(0)
314 .chain(
315 chars
316 .iter()
317 .enumerate()
318 .filter(|(_, c)| **c == '\n')
319 .map(|(i, _)| i + 1),
320 )
321 .collect();
322 let offset = |loc: sqlparser::tokenizer::Location| -> Option<usize> {
323 let line = usize::try_from(loc.line).ok()?.checked_sub(1)?;
324 let col = usize::try_from(loc.column).ok()?.checked_sub(1)?;
325 Some(line_starts.get(line)? + col)
326 };
327 let mut cuts: Vec<usize> = tokens
328 .iter()
329 .filter(|t| t.token == Token::SemiColon)
330 .filter_map(|t| offset(t.span.start))
331 .collect();
332 cuts.push(chars.len());
333 let mut out = SchemaRead::default();
334 let mut from = 0;
335 for cut in cuts {
336 if cut < from || cut > chars.len() {
337 continue;
338 }
339 let text: String = chars[from..cut].iter().collect();
340 from = (cut + 1).min(chars.len());
341 if text.trim().is_empty() {
342 continue;
343 }
344 let stmts = match Parser::parse_sql(d.as_ref(), &text) {
345 Ok(s) => s,
346 Err(e) => {
347 let head: String = text
348 .split_whitespace()
349 .take(4)
350 .collect::<Vec<_>>()
351 .join(" ");
352 out.skipped.push(format!("{head}: {e}"));
353 continue;
354 }
355 };
356 for st in stmts {
357 apply_ddl(&mut out.schema, st);
358 }
359 }
360 Ok(out)
361}
362
363fn apply_ddl(schema: &mut Schema, st: sp::Statement) {
364 match st {
365 sp::Statement::CreateTable(ct) => {
366 if ct.query.is_some() || ct.like.is_some() {
367 return;
368 }
369 let Ok(name) = object_name(&ct.name) else {
370 return;
371 };
372 schema.drop_table(&name);
373 schema.mark_columns_complete(name.0.clone());
374 for c in &ct.columns {
375 schema.set_column(name.0.clone(), ident(&c.name), column_from_def(c));
376 for o in &c.options {
377 if matches!(
378 o.option,
379 sp::ColumnOption::PrimaryKey(_) | sp::ColumnOption::Unique(_)
380 ) {
381 schema.add_unique_key(name.0.clone(), vec![ident(&c.name)]);
382 }
383 }
384 }
385 for con in &ct.constraints {
386 add_constraint_key(schema, &name, con);
387 }
388 }
389 sp::Statement::CreateIndex(ci) => {
390 let Ok(name) = object_name(&ci.table_name) else {
391 return;
392 };
393 if ci.unique {
394 match index_columns(&ci.columns) {
395 Some(cols) if ci.predicate.is_none() => schema.add_unique_key(name.0, cols),
396 _ => schema.mark_keys_unreliable(name.0),
397 }
398 }
399 }
400 sp::Statement::AlterTable(at) => {
401 let Ok(name) = object_name(&at.name) else {
402 return;
403 };
404 for op in at.operations {
405 match op {
406 sp::AlterTableOperation::AddColumn { column_def, .. } => {
407 schema.set_column(
408 name.0.clone(),
409 ident(&column_def.name),
410 column_from_def(&column_def),
411 );
412 }
413 sp::AlterTableOperation::AddConstraint { constraint, .. } => {
414 add_constraint_key(schema, &name, &constraint);
415 }
416 sp::AlterTableOperation::DropPrimaryKey { .. }
417 | sp::AlterTableOperation::DropConstraint { .. } => {
418 schema.mark_keys_unreliable(name.0.clone());
419 }
420 sp::AlterTableOperation::DropColumn { column_names, .. } => {
421 for c in &column_names {
422 schema.drop_column(&name, &ident(c));
423 }
424 }
425 sp::AlterTableOperation::AlterColumn {
426 column_name,
427 op: sp::AlterColumnOperation::SetDataType { data_type, .. },
428 } => {
429 let class = class_of_type(&data_type).unwrap_or(ColumnClass::Unknown);
430 schema.set_column(name.0.clone(), ident(&column_name), Column::new(class));
432 }
433 _ => {}
434 }
435 }
436 }
437 sp::Statement::Drop {
438 object_type: sp::ObjectType::Index,
439 ..
440 } => schema.mark_all_keys_unreliable(),
441 sp::Statement::Drop {
442 object_type: sp::ObjectType::Table,
443 names,
444 ..
445 } => {
446 for n in &names {
447 if let Ok(name) = object_name(n) {
448 schema.drop_table(&name);
449 }
450 }
451 }
452 _ => {}
453 }
454}
455
456fn index_columns(cols: &[sp::IndexColumn]) -> Option<Vec<String>> {
458 cols.iter()
459 .map(|c| match &c.column.expr {
460 sp::Expr::Identifier(i) => Some(ident(i)),
461 _ => None,
462 })
463 .collect()
464}
465
466fn add_constraint_key(schema: &mut Schema, table: &Name, con: &sp::TableConstraint) {
469 let cols = match con {
470 sp::TableConstraint::PrimaryKey(p) => &p.columns,
471 sp::TableConstraint::Unique(u) => &u.columns,
472 _ => return,
473 };
474 match index_columns(cols) {
475 Some(c) => schema.add_unique_key(table.0.clone(), c),
476 None => schema.mark_keys_unreliable(table.0.clone()),
477 }
478}
479
480fn statement(s: sp::Statement) -> R<Statement> {
481 match s {
482 sp::Statement::Query(q)
483 if matches!(
484 &*q.body,
485 sp::SetExpr::Insert(_) | sp::SetExpr::Update(_) | sp::SetExpr::Delete(_)
486 ) =>
487 {
488 let mut q = *q;
490 let with = q.with.take();
491 if q.to_string() != q.body.to_string() {
492 return no(
493 "query_clause",
494 "clauses around a WITH .. INSERT/UPDATE/DELETE",
495 );
496 }
497 let inner = match *q.body {
498 sp::SetExpr::Insert(st) | sp::SetExpr::Update(st) | sp::SetExpr::Delete(st) => {
499 statement(st)?
500 }
501 _ => unreachable!(),
502 };
503 match with {
504 None => Ok(inner),
505 Some(w) => {
506 let (ctes, recursive) = read_ctes(w)?;
507 Ok(Statement::With(WithStatement {
508 recursive,
509 ctes,
510 stmt: Box::new(inner),
511 }))
512 }
513 }
514 }
515 sp::Statement::Query(q) => Ok(Statement::Query(Box::new(query(*q)?))),
516 sp::Statement::Insert(i) => insert(i),
517 sp::Statement::Update(u) => update(u),
518 sp::Statement::Delete(d) => delete(d),
519 other => no(
520 "statement",
521 &format!(
522 "{} is not a DML statement the first slice reads",
523 head(&other)
524 ),
525 ),
526 }
527}
528
529fn head(x: &impl std::fmt::Debug) -> String {
530 let s = format!("{x:?}");
531 s.split(|c: char| !c.is_alphanumeric() && c != '_')
532 .next()
533 .unwrap_or("")
534 .to_string()
535}
536
537fn ident(i: &sp::Ident) -> String {
540 if i.quote_style.is_some() && !rules().fold_quoted {
541 i.value.clone()
542 } else {
543 i.value.to_lowercase()
544 }
545}
546
547fn object_name(n: &sp::ObjectName) -> R<Name> {
548 let mut parts = Vec::new();
549 for p in &n.0 {
550 match p {
551 sp::ObjectNamePart::Identifier(i) => parts.push(ident(i)),
552 other => return no("object_name_part", &head(other)),
553 }
554 }
555 Ok(Name(parts))
556}
557
558fn single_name(n: &sp::ObjectName) -> R<String> {
559 let nm = object_name(n)?;
560 if nm.0.len() != 1 {
561 return no(
562 "qualified_column_target",
563 "expected an unqualified column name",
564 );
565 }
566 Ok(nm.0.into_iter().next().unwrap())
567}
568
569fn read_ctes(w: sp::With) -> R<(Vec<Cte>, bool)> {
571 let sp::With {
572 with_token: _,
573 recursive,
574 cte_tables,
575 } = w;
576 let mut ctes = Vec::new();
577 for c in cte_tables {
578 let sp::Cte {
579 alias,
580 query: cq,
581 from,
582 materialized,
583 closing_paren_token: _,
584 } = c;
585 if from.is_some() || materialized.is_some() {
586 return no("cte_option", "MATERIALIZED / FROM in a CTE");
587 }
588 let mut columns = Vec::new();
589 for col in &alias.columns {
590 if col.data_type.is_some() {
591 return no("cte_column_type", "typed CTE column");
592 }
593 columns.push(ident(&col.name));
594 }
595 let dml = matches!(
596 &*cq.body,
597 sp::SetExpr::Insert(_) | sp::SetExpr::Update(_) | sp::SetExpr::Delete(_)
598 );
599 let cte_body = if dml {
600 if cq.to_string() != cq.body.to_string() {
603 return no("cte_dml_clause", "clauses around a data-modifying CTE");
604 }
605 match *cq.body {
606 sp::SetExpr::Insert(st) | sp::SetExpr::Update(st) | sp::SetExpr::Delete(st) => {
607 CteBody::Dml(Box::new(statement(st)?))
608 }
609 _ => unreachable!(),
610 }
611 } else {
612 CteBody::Query(Box::new(query(*cq)?))
613 };
614 ctes.push(Cte {
615 name: ident(&alias.name),
616 columns,
617 body: cte_body,
618 });
619 }
620 Ok((ctes, recursive))
621}
622
623fn query(q: sp::Query) -> R<Query> {
624 let sp::Query {
625 with,
626 body,
627 order_by,
628 limit_clause,
629 fetch,
630 locks,
631 for_clause,
632 settings,
633 format_clause,
634 pipe_operators,
635 } = q;
636 if fetch.is_some() {
637 return no("fetch_first", "FETCH FIRST is not in the IR (use LIMIT)");
638 }
639 if for_clause.is_some()
640 || settings.is_some()
641 || format_clause.is_some()
642 || !pipe_operators.is_empty()
643 {
644 return no("query_clause", "FOR/SETTINGS/FORMAT/pipe operators");
645 }
646 let mut ctes = Vec::new();
647 let mut recursive = false;
648 if let Some(w) = with {
649 (ctes, recursive) = read_ctes(w)?;
650 }
651 let mut body = set_expr(*body)?;
652 let mut lock = None;
653 match locks.len() {
654 0 => {}
655 1 => {
656 let sp::LockClause {
657 lock_type,
658 of,
659 nonblock,
660 } = locks.into_iter().next().unwrap();
661 lock = Some(RowLockClause {
662 strength: match lock_type {
663 sp::LockType::Update => RowLock::Update,
664 sp::LockType::Share => RowLock::Share,
665 },
666 of: match of {
667 Some(n) => Some(object_name(&n)?),
668 None => None,
669 },
670 wait: match nonblock {
671 None => LockWait::Block,
672 Some(sp::NonBlock::Nowait) => LockWait::NoWait,
673 Some(sp::NonBlock::SkipLocked) => LockWait::SkipLocked,
674 },
675 });
676 }
677 _ => return no("row_lock_multiple", "several locking clauses"),
678 }
679 if let Some(l) = lock {
680 match &mut body {
681 SetExpr::Select(s) => s.lock = Some(l),
682 _ => return no("row_lock_on_set_op", "locking clause on a set operation"),
683 }
684 }
685 let mut order = Vec::new();
686 if let Some(ob) = order_by {
687 let sp::OrderBy { kind, interpolate } = ob;
688 if interpolate.is_some() {
689 return no("order_by_interpolate", "INTERPOLATE");
690 }
691 match kind {
692 sp::OrderByKind::Expressions(items) => {
693 for it in items {
694 order.push(order_item(it)?);
695 }
696 }
697 sp::OrderByKind::All(_) => return no("order_by_all", "ORDER BY ALL"),
698 }
699 }
700 let (mut limit, mut offset) = (None, None);
701 match limit_clause {
702 None => {}
703 Some(sp::LimitClause::LimitOffset {
704 limit: l,
705 offset: o,
706 limit_by,
707 }) => {
708 if !limit_by.is_empty() {
709 return no("limit_by", "LIMIT .. BY");
710 }
711 limit = l.map(expr).transpose()?;
712 if rules().negative_limit_unlimited {
713 let neg = matches!(&limit, Some(Expr::Unary { op: UnaryOp::Minus, expr })
715 if matches!(**expr, Expr::Literal(Literal::Number(_))));
716 if neg {
717 limit = None;
718 }
719 }
720 if let Some(o) = o {
721 let sp::Offset { value, rows } = o;
722 if !matches!(rows, sp::OffsetRows::None) {
723 return no("offset_rows", "OFFSET .. ROWS");
724 }
725 offset = Some(expr(value)?);
726 }
727 }
728 Some(sp::LimitClause::OffsetCommaLimit {
729 offset: o,
730 limit: l,
731 }) => {
732 offset = Some(expr(o)?);
733 limit = Some(expr(l)?);
734 }
735 }
736 Ok(Query {
737 with: ctes,
738 recursive,
739 body,
740 order_by: order,
741 limit,
742 offset,
743 })
744}
745
746fn order_item(o: sp::OrderByExpr) -> R<OrderItem> {
747 let sp::OrderByExpr {
748 expr: e,
749 options,
750 with_fill,
751 } = o;
752 if with_fill.is_some() {
753 return no("order_by_with_fill", "WITH FILL");
754 }
755 let desc = matches!(options.sort, Some(sp::OrderBySort::Desc));
756 let default_first = if rules().nulls_smallest { !desc } else { desc };
759 let nulls_first = options.nulls_first.unwrap_or(default_first);
760 Ok(OrderItem {
761 expr: expr(e)?,
762 desc,
763 nulls_first: Some(nulls_first),
764 })
765}
766
767fn set_expr(s: sp::SetExpr) -> R<SetExpr> {
768 match s {
769 sp::SetExpr::Select(sel) => Ok(SetExpr::Select(Box::new(select(*sel)?))),
770 sp::SetExpr::SetOperation {
771 left,
772 op,
773 set_quantifier,
774 right,
775 } => {
776 let op = match op {
777 sp::SetOperator::Union => SetOp::Union,
778 sp::SetOperator::Intersect => SetOp::Intersect,
779 sp::SetOperator::Except => SetOp::Except,
780 sp::SetOperator::Minus => return no("minus", "MINUS is not PostgreSQL"),
781 };
782 let all = match set_quantifier {
783 sp::SetQuantifier::All => true,
784 sp::SetQuantifier::Distinct | sp::SetQuantifier::None => false,
785 other => return no("set_quantifier", &head(&other)),
786 };
787 Ok(SetExpr::SetOp {
788 op,
789 all,
790 left: Box::new(set_expr(*left)?),
791 right: Box::new(set_expr(*right)?),
792 })
793 }
794 sp::SetExpr::Query(q) => {
795 let q = query(*q)?;
796 if !q.with.is_empty()
797 || !q.order_by.is_empty()
798 || q.limit.is_some()
799 || q.offset.is_some()
800 {
801 return no(
802 "parenthesised_query",
803 "parenthesised query with WITH/ORDER/LIMIT as a set operand",
804 );
805 }
806 Ok(q.body)
807 }
808 sp::SetExpr::Values(_) => no("values_query", "a bare VALUES query outside INSERT"),
809 other => no("set_expr", &head(&other)),
810 }
811}
812
813fn select(s: sp::Select) -> R<Select> {
814 let sp::Select {
815 select_token: _,
816 optimizer_hints,
817 distinct,
818 select_modifiers,
819 top,
820 top_before_distinct: _,
821 projection,
822 exclude,
823 into,
824 from,
825 lateral_views,
826 prewhere,
827 selection,
828 connect_by,
829 group_by,
830 cluster_by,
831 distribute_by,
832 sort_by,
833 having,
834 named_window,
835 qualify,
836 window_before_qualify: _,
837 value_table_mode,
838 flavor: _,
839 } = s;
840 if !optimizer_hints.is_empty()
841 || select_modifiers.is_some()
842 || top.is_some()
843 || exclude.is_some()
844 || into.is_some()
845 || !lateral_views.is_empty()
846 || prewhere.is_some()
847 || !connect_by.is_empty()
848 || !cluster_by.is_empty()
849 || !distribute_by.is_empty()
850 || !sort_by.is_empty()
851 || !named_window.is_empty()
852 || qualify.is_some()
853 || value_table_mode.is_some()
854 {
855 return no(
856 "select_clause",
857 "a SELECT clause the IR does not represent (TOP/INTO/WINDOW/QUALIFY/hints/...)",
858 );
859 }
860 let distinct = match distinct {
861 None | Some(sp::Distinct::All) => Distinct::No,
862 Some(sp::Distinct::Distinct) => Distinct::Yes,
863 Some(sp::Distinct::On(es)) => Distinct::On(es.into_iter().map(expr).collect::<R<_>>()?),
864 };
865 let mut group_by_rollup = false;
866 let group_by = match group_by {
867 sp::GroupByExpr::Expressions(es, mods) => {
868 match mods.as_slice() {
869 [] => {}
870 [sp::GroupByWithModifier::Rollup] if !es.is_empty() => group_by_rollup = true,
871 _ => return no("group_by_modifier", "CUBE/TOTALS/GROUPING SETS modifiers"),
872 }
873 es.into_iter().map(expr).collect::<R<Vec<_>>>()?
874 }
875 sp::GroupByExpr::All(_) => return no("group_by_all", "GROUP BY ALL"),
876 };
877 let mut proj = Vec::new();
878 for it in projection {
879 proj.push(select_item(it)?);
880 }
881 let mut from_refs = Vec::new();
882 for t in from {
883 from_refs.push(table_with_joins(t)?);
884 }
885 Ok(Select {
886 distinct,
887 projection: proj,
888 from: from_refs,
889 where_: selection.map(expr).transpose()?,
890 group_by,
891 group_by_rollup,
892 having: having.map(expr).transpose()?,
893 lock: None,
894 })
895}
896
897fn wildcard_plain(o: &sp::WildcardAdditionalOptions) -> R<()> {
898 let sp::WildcardAdditionalOptions {
899 wildcard_token: _,
900 opt_ilike,
901 opt_exclude,
902 opt_except,
903 opt_replace,
904 opt_rename,
905 opt_alias,
906 } = o;
907 if opt_ilike.is_some()
908 || opt_exclude.is_some()
909 || opt_except.is_some()
910 || opt_replace.is_some()
911 || opt_rename.is_some()
912 || opt_alias.is_some()
913 {
914 return no("wildcard_option", "EXCLUDE/EXCEPT/REPLACE/RENAME on *");
915 }
916 Ok(())
917}
918
919fn select_item(it: sp::SelectItem) -> R<SelectItem> {
920 match it {
921 sp::SelectItem::UnnamedExpr(e) => Ok(SelectItem::Expr {
922 expr: expr(e)?,
923 alias: None,
924 }),
925 sp::SelectItem::ExprWithAlias { expr: e, alias } => Ok(SelectItem::Expr {
926 expr: expr(e)?,
927 alias: Some(ident(&alias)),
928 }),
929 sp::SelectItem::Wildcard(o) => {
930 wildcard_plain(&o)?;
931 Ok(SelectItem::Wildcard)
932 }
933 sp::SelectItem::QualifiedWildcard(kind, o) => {
934 wildcard_plain(&o)?;
935 match kind {
936 sp::SelectItemQualifiedWildcardKind::ObjectName(n) => {
937 Ok(SelectItem::QualifiedWildcard(object_name(&n)?))
938 }
939 other => no("qualified_wildcard", &head(&other)),
940 }
941 }
942 other => no("select_item", &head(&other)),
943 }
944}
945
946fn alias_name(a: Option<sp::TableAlias>) -> R<Option<String>> {
947 match a {
948 None => Ok(None),
949 Some(sp::TableAlias {
950 explicit: _,
951 name,
952 columns,
953 at,
954 }) => {
955 if !columns.is_empty() || at.is_some() {
956 return no("table_alias_columns", "column list on a table alias");
957 }
958 Ok(Some(ident(&name)))
959 }
960 }
961}
962
963fn table_factor(t: sp::TableFactor) -> R<TableRef> {
964 match t {
965 sp::TableFactor::Table {
966 name,
967 alias,
968 args,
969 with_hints,
970 version,
971 with_ordinality,
972 partitions,
973 json_path,
974 sample,
975 index_hints,
976 } => {
977 if let Some(targs) = args {
979 let plain = with_hints.is_empty()
980 && version.is_none()
981 && !with_ordinality
982 && partitions.is_empty()
983 && json_path.is_none()
984 && sample.is_none()
985 && index_hints.is_empty()
986 && targs.settings.is_none();
987 let nm = object_name(&name)?;
988 if plain && nm.0.len() == 1 {
989 let mut out = Vec::new();
990 for a in targs.args {
991 match a {
992 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => {
993 out.push(expr(e)?)
994 }
995 _ => {
996 return no(
997 "table_function_arg",
998 "named or wildcard argument of a table function",
999 );
1000 }
1001 }
1002 }
1003 return Ok(TableRef::Function {
1004 name: nm.0[0].clone(),
1005 args: out,
1006 alias: alias_name(alias)?,
1007 });
1008 }
1009 return no(
1010 "table_option",
1011 "table function args/hints/version/sample/partitions",
1012 );
1013 }
1014 if !with_hints.is_empty()
1015 || version.is_some()
1016 || with_ordinality
1017 || !partitions.is_empty()
1018 || json_path.is_some()
1019 || sample.is_some()
1020 || !index_hints.is_empty()
1021 {
1022 return no(
1023 "table_option",
1024 "table function args/hints/version/sample/partitions",
1025 );
1026 }
1027 Ok(TableRef::Table {
1028 name: object_name(&name)?,
1029 alias: alias_name(alias)?,
1030 })
1031 }
1032 sp::TableFactor::Derived {
1033 lateral,
1034 subquery,
1035 alias,
1036 sample,
1037 } => {
1038 if lateral || sample.is_some() {
1039 return no("derived_option", "LATERAL / TABLESAMPLE");
1040 }
1041 let alias = alias_name(alias)?.ok_or_else(|| ReadError::NotInIr {
1042 construct: "unaliased_subquery".into(),
1043 detail: "PostgreSQL requires an alias on a FROM subquery".into(),
1044 })?;
1045 Ok(TableRef::Subquery {
1046 query: Box::new(query(*subquery)?),
1047 alias,
1048 })
1049 }
1050 other => no("table_factor", &head(&other)),
1051 }
1052}
1053
1054fn table_with_joins(t: sp::TableWithJoins) -> R<TableRef> {
1055 let sp::TableWithJoins { relation, joins } = t;
1056 let mut cur = table_factor(relation)?;
1057 for j in joins {
1058 let sp::Join {
1059 relation,
1060 global,
1061 join_operator,
1062 } = j;
1063 if global {
1064 return no("global_join", "GLOBAL JOIN");
1065 }
1066 let (kind, constraint) = match join_operator {
1067 sp::JoinOperator::Join(c) | sp::JoinOperator::Inner(c) => (JoinKind::Inner, c),
1068 sp::JoinOperator::Left(c) | sp::JoinOperator::LeftOuter(c) => (JoinKind::Left, c),
1069 sp::JoinOperator::Right(c) | sp::JoinOperator::RightOuter(c) => (JoinKind::Right, c),
1070 sp::JoinOperator::FullOuter(c) => (JoinKind::Full, c),
1071 sp::JoinOperator::CrossJoin(c) => (JoinKind::Cross, c),
1072 other => return no("join_kind", &head(&other)),
1073 };
1074 let (on, using, natural) = match constraint {
1075 sp::JoinConstraint::On(e) => (Some(expr(e)?), Vec::new(), false),
1076 sp::JoinConstraint::None => (None, Vec::new(), false),
1077 sp::JoinConstraint::Using(cols) => (
1078 None,
1079 cols.iter().map(single_name).collect::<R<Vec<_>>>()?,
1080 false,
1081 ),
1082 sp::JoinConstraint::Natural => (None, Vec::new(), true),
1083 };
1084 if natural && kind == JoinKind::Cross {
1085 return no("natural_join", "NATURAL CROSS JOIN");
1086 }
1087 cur = TableRef::Join {
1088 left: Box::new(cur),
1089 right: Box::new(table_factor(relation)?),
1090 kind,
1091 on,
1092 using,
1093 natural,
1094 };
1095 }
1096 Ok(cur)
1097}
1098
1099fn simple_table(t: sp::TableWithJoins) -> R<(Name, Option<String>)> {
1100 let sp::TableWithJoins { relation, joins } = t;
1101 if !joins.is_empty() {
1102 return no("dml_target_join", "joins in a DML target");
1103 }
1104 match table_factor(relation)? {
1105 TableRef::Table { name, alias } => Ok((name, alias)),
1106 _ => no("dml_target", "DML target is not a plain table"),
1107 }
1108}
1109
1110fn returning(r: Option<Vec<sp::SelectItem>>) -> R<Vec<SelectItem>> {
1111 r.unwrap_or_default().into_iter().map(select_item).collect()
1112}
1113
1114fn assignments(a: Vec<sp::Assignment>, target_name: Option<&str>) -> R<Vec<Assignment>> {
1117 let mut out = Vec::new();
1118 for sp::Assignment { target, value } in a {
1119 let column = match target {
1120 sp::AssignmentTarget::ColumnName(n) => {
1121 let nm = object_name(&n)?;
1122 match nm.0.as_slice() {
1123 [c] => c.clone(),
1124 [q, c] if Some(q.as_str()) == target_name => c.clone(),
1125 _ => {
1126 return no(
1127 "qualified_column_target",
1128 "SET column qualified with a table other than the target",
1129 );
1130 }
1131 }
1132 }
1133 sp::AssignmentTarget::Tuple(_) => return no("tuple_assignment", "SET (a, b) = ..."),
1134 };
1135 out.push(Assignment {
1136 column,
1137 value: expr(value)?,
1138 });
1139 }
1140 Ok(out)
1141}
1142
1143fn insert(i: sp::Insert) -> R<Statement> {
1144 let sp::Insert {
1145 insert_token: _,
1146 optimizer_hints,
1147 or,
1148 ignore,
1149 into: _,
1150 table,
1151 table_alias,
1152 columns,
1153 overwrite,
1154 source,
1155 assignments: asg,
1156 partitioned,
1157 after_columns,
1158 has_table_keyword: _,
1159 on,
1160 returning: ret,
1161 output,
1162 replace_into,
1163 priority,
1164 insert_alias,
1165 settings,
1166 format_clause,
1167 multi_table_insert_type,
1168 multi_table_into_clauses,
1169 multi_table_when_clauses,
1170 multi_table_else_clause,
1171 } = i;
1172 if !optimizer_hints.is_empty()
1173 || !matches!(
1174 or,
1175 None | Some(sp::SqliteOnConflict::Replace | sp::SqliteOnConflict::Ignore)
1176 )
1177 || table_alias.is_some()
1178 || overwrite
1179 || (!asg.is_empty() && (!columns.is_empty() || source.is_some()))
1180 || partitioned.is_some()
1181 || !after_columns.is_empty()
1182 || output.is_some()
1183 || priority.is_some()
1184 || insert_alias.is_some()
1185 || settings.is_some()
1186 || format_clause.is_some()
1187 || multi_table_insert_type.is_some()
1188 || !multi_table_into_clauses.is_empty()
1189 || !multi_table_when_clauses.is_empty()
1190 || multi_table_else_clause.is_some()
1191 {
1192 return no(
1193 "insert_option",
1194 "an INSERT form other than INSERT INTO t [(cols)] VALUES|query",
1195 );
1196 }
1197 let table = match table {
1198 sp::TableObject::TableName(n) => object_name(&n)?,
1199 other => return no("insert_target", &head(&other)),
1200 };
1201 let (columns, set_source) = if asg.is_empty() {
1203 (
1204 columns.iter().map(single_name).collect::<R<Vec<_>>>()?,
1205 None,
1206 )
1207 } else {
1208 let set = assignments(asg, None)?;
1209 let (cols, vals): (Vec<_>, Vec<_>) = set.into_iter().map(|a| (a.column, a.value)).unzip();
1210 (cols, Some(InsertSource::Values(vec![vals])))
1211 };
1212 let source = match source {
1213 None if set_source.is_some() => set_source.unwrap_or(InsertSource::DefaultValues),
1214 None => InsertSource::DefaultValues,
1215 Some(q) => {
1216 let q = *q;
1217 match *q.body {
1218 sp::SetExpr::Values(v)
1219 if q.with.is_none()
1220 && q.order_by.is_none()
1221 && q.limit_clause.is_none()
1222 && q.locks.is_empty() =>
1223 {
1224 let sp::Values {
1225 explicit_row,
1226 value_keyword: _,
1227 rows,
1228 } = v;
1229 if explicit_row {
1230 return no("explicit_row", "ROW(..) in VALUES");
1231 }
1232 let mut out = Vec::new();
1233 for r in rows {
1234 out.push(r.content.into_iter().map(expr).collect::<R<Vec<_>>>()?);
1235 }
1236 InsertSource::Values(out)
1237 }
1238 body => {
1239 let q2 = sp::Query {
1240 body: Box::new(body),
1241 ..q
1242 };
1243 InsertSource::Query(Box::new(query(q2)?))
1244 }
1245 }
1246 }
1247 };
1248 let on_conflict = match on {
1249 None => None,
1250 Some(sp::OnInsert::OnConflict(oc)) => {
1251 let sp::OnConflict {
1252 conflict_target,
1253 action,
1254 } = oc;
1255 let target = match conflict_target {
1256 None => Vec::new(),
1257 Some(sp::ConflictTarget::Columns(cols)) => cols.iter().map(ident).collect(),
1258 Some(sp::ConflictTarget::OnConstraint(_)) => {
1259 return no("on_conflict_constraint", "ON CONFLICT ON CONSTRAINT");
1260 }
1261 };
1262 let action = match action {
1263 sp::OnConflictAction::DoNothing => ConflictAction::DoNothing,
1264 sp::OnConflictAction::DoUpdate(sp::DoUpdate {
1265 assignments: a,
1266 selection,
1267 }) => ConflictAction::DoUpdate {
1268 set: assignments(a, None)?,
1269 where_: selection.map(expr).transpose()?,
1270 },
1271 };
1272 Some(OnConflict { target, action })
1273 }
1274 Some(sp::OnInsert::DuplicateKeyUpdate(a)) => {
1275 let set = assignments(a, None)?
1277 .into_iter()
1278 .map(|asg| Assignment {
1279 column: asg.column,
1280 value: excluded_refs(asg.value),
1281 })
1282 .collect();
1283 Some(OnConflict {
1284 target: vec![],
1285 action: ConflictAction::DoUpdate { set, where_: None },
1286 })
1287 }
1288 Some(other) => return no("on_insert", &head(&other)),
1289 };
1290 let ignore = ignore || matches!(or, Some(sp::SqliteOnConflict::Ignore));
1293 let on_conflict = if ignore {
1294 if on_conflict.is_some() {
1295 return no(
1296 "insert_option",
1297 "IGNORE together with ON DUPLICATE KEY UPDATE",
1298 );
1299 }
1300 add_note(
1301 "insert_ignore",
1302 "INSERT IGNORE downgrades every error of the statement to a warning; ON CONFLICT DO NOTHING \
1303 skips unique-key conflicts only",
1304 Allowance::IgnoreCoversOnlyUniqueConflicts,
1305 );
1306 Some(OnConflict {
1307 target: vec![],
1308 action: ConflictAction::DoNothing,
1309 })
1310 } else {
1311 on_conflict
1312 };
1313 Ok(Statement::Insert(Insert {
1314 table,
1315 replace: replace_into || matches!(or, Some(sp::SqliteOnConflict::Replace)),
1317 columns,
1318 source,
1319 on_conflict,
1320 returning: returning(ret)?,
1321 }))
1322}
1323
1324fn flatten_inner(t: sp::TableWithJoins, on: &mut Vec<Expr>) -> R<Vec<TableRef>> {
1328 let sp::TableWithJoins { relation, joins } = t;
1329 let mut out = vec![table_factor(relation)?];
1330 for j in joins {
1331 if j.global {
1332 return no("global_join", "GLOBAL JOIN");
1333 }
1334 let c = match j.join_operator {
1335 sp::JoinOperator::Join(c)
1336 | sp::JoinOperator::Inner(c)
1337 | sp::JoinOperator::CrossJoin(c) => c,
1338 other => return no("dml_outer_join", &head(&other)),
1339 };
1340 match c {
1341 sp::JoinConstraint::On(e) => on.push(expr(e)?),
1342 sp::JoinConstraint::None => {}
1343 _ => return no("dml_join_using", "USING/NATURAL join in a DML statement"),
1344 }
1345 out.push(table_factor(j.relation)?);
1346 }
1347 Ok(out)
1348}
1349
1350fn with_conditions(on: Vec<Expr>, where_: Option<Expr>) -> Option<Expr> {
1352 on.into_iter().chain(where_).reduce(|l, r| Expr::Binary {
1353 op: BinaryOp::And,
1354 left: Box::new(l),
1355 right: Box::new(r),
1356 })
1357}
1358
1359fn ref_name(t: &TableRef) -> Option<&str> {
1361 match t {
1362 TableRef::Table { name, alias } => alias.as_deref().or(name.0.last().map(String::as_str)),
1363 _ => None,
1364 }
1365}
1366
1367fn update(u: sp::Update) -> R<Statement> {
1368 let sp::Update {
1369 update_token: _,
1370 optimizer_hints,
1371 table,
1372 assignments: asg,
1373 from,
1374 selection,
1375 returning: ret,
1376 output,
1377 or,
1378 order_by,
1379 limit,
1380 } = u;
1381 if !optimizer_hints.is_empty() || output.is_some() || or.is_some() {
1382 return no("update_option", "UPDATE with hints/OUTPUT/OR");
1383 }
1384 let mut on = Vec::new();
1385 let mut sources = flatten_inner(table, &mut on)?.into_iter();
1386 let (table, alias) = match sources.next() {
1387 Some(TableRef::Table { name, alias }) => (name, alias),
1388 _ => return no("dml_target", "DML target is not a plain table"),
1389 };
1390 let joined: Vec<TableRef> = sources.collect();
1391 if alias.is_some()
1394 && table.0.len() == 1
1395 && matches!(table.0[0].as_str(), "low_priority" | "ignore")
1396 {
1397 return no("update_option", "UPDATE LOW_PRIORITY / IGNORE");
1398 }
1399 let from = match from {
1400 None => Vec::new(),
1401 Some(sp::UpdateTableFromKind::AfterSet(ts)) => ts
1402 .into_iter()
1403 .map(table_with_joins)
1404 .collect::<R<Vec<_>>>()?,
1405 Some(other) => return no("update_from_position", &head(&other)),
1406 };
1407 let from: Vec<TableRef> = joined.into_iter().chain(from).collect();
1408 let target = alias.as_deref().or(table.0.last().map(String::as_str));
1409 Ok(Statement::Update(Update {
1410 set: assignments(asg, target)?,
1411 from,
1412 where_: with_conditions(on, selection.map(expr).transpose()?),
1413 table,
1414 alias,
1415 order_by: order_by.into_iter().map(order_item).collect::<R<_>>()?,
1416 limit: limit.map(expr).transpose()?,
1417 returning: returning(ret)?,
1418 }))
1419}
1420
1421fn delete(d: sp::Delete) -> R<Statement> {
1422 let sp::Delete {
1423 delete_token: _,
1424 optimizer_hints,
1425 tables,
1426 from,
1427 using,
1428 selection,
1429 returning: ret,
1430 output,
1431 order_by,
1432 limit,
1433 } = d;
1434 if !optimizer_hints.is_empty() || output.is_some() {
1435 return no("delete_option", "DELETE with hints/OUTPUT");
1436 }
1437 let ts = match from {
1438 sp::FromTable::WithFromKeyword(ts) | sp::FromTable::WithoutKeyword(ts) => ts,
1439 };
1440 if !tables.is_empty() {
1443 if tables.len() != 1 || using.is_some() {
1444 return no("delete_multi_target", "DELETE a, b FROM ..");
1445 }
1446 let target = object_name(&tables[0])?;
1447 let mut on = Vec::new();
1448 let mut sources = Vec::new();
1449 for t in ts {
1450 sources.extend(flatten_inner(t, &mut on)?);
1451 }
1452 let at = match target.0.as_slice() {
1453 [n] => sources.iter().position(|t| ref_name(t) == Some(n.as_str())),
1454 _ => None,
1455 };
1456 let Some(at) = at else {
1457 return no("delete_target", "DELETE target is not a table of its FROM");
1458 };
1459 let (name, alias) = match sources.remove(at) {
1460 TableRef::Table { name, alias } => (name, alias),
1461 _ => return no("dml_target", "DML target is not a plain table"),
1462 };
1463 return Ok(Statement::Delete(Delete {
1464 table: name,
1465 alias,
1466 using: sources,
1467 where_: with_conditions(on, selection.map(expr).transpose()?),
1468 order_by: order_by.into_iter().map(order_item).collect::<R<_>>()?,
1469 limit: limit.map(expr).transpose()?,
1470 returning: returning(ret)?,
1471 }));
1472 }
1473 if ts.len() != 1 {
1474 return no("delete_multi_target", "DELETE FROM a, b");
1475 }
1476 let (table, alias) = simple_table(ts.into_iter().next().unwrap())?;
1477 if rules().name == "mysql" && using.is_some() {
1480 return no("delete_using_form", "MySQL DELETE FROM .. USING source");
1481 }
1482 let using = using
1483 .unwrap_or_default()
1484 .into_iter()
1485 .map(table_with_joins)
1486 .collect::<R<Vec<_>>>()?;
1487 Ok(Statement::Delete(Delete {
1488 table,
1489 alias,
1490 using,
1491 where_: selection.map(expr).transpose()?,
1492 order_by: order_by.into_iter().map(order_item).collect::<R<_>>()?,
1493 limit: limit.map(expr).transpose()?,
1494 returning: returning(ret)?,
1495 }))
1496}
1497
1498fn boxed(e: sp::Expr) -> R<Box<Expr>> {
1499 Ok(Box::new(expr(e)?))
1500}
1501
1502fn ceil_floor(name: &str, x: sp::Expr, field: sp::CeilFloorKind) -> R<Expr> {
1503 if !matches!(
1504 field,
1505 sp::CeilFloorKind::DateTimeField(sp::DateTimeField::NoDateTime)
1506 ) {
1507 return no("ceil_form", "CEIL/FLOOR(x TO unit) and the scale form");
1508 }
1509 Ok(Expr::Function(Function {
1510 name: name.to_string(),
1511 args: vec![FunctionArg::Expr(expr(x)?)],
1512 distinct: false,
1513 star: false,
1514 filter: None,
1515 over: None,
1516 order_by: vec![],
1517 }))
1518}
1519
1520fn is_bool(x: sp::Expr, value: bool, negated: bool) -> R<Expr> {
1521 Ok(Expr::IsBool {
1522 expr: boxed(x)?,
1523 value,
1524 negated,
1525 })
1526}
1527
1528fn expr(e: sp::Expr) -> R<Expr> {
1529 use sp::Expr as E;
1530 Ok(match e {
1531 E::Identifier(i) if i.quote_style.is_none() && i.value.eq_ignore_ascii_case("default") => {
1534 return no("default_value", "DEFAULT as a value is not in the IR");
1535 }
1536 E::Identifier(i) => Expr::Column(Name::simple(ident(&i))),
1537 E::CompoundIdentifier(parts) => Expr::Column(Name(parts.iter().map(ident).collect())),
1538 E::Value(v) => value(v.value)?,
1539 E::Nested(inner) => Expr::Nested(boxed(*inner)?),
1540 E::IsNull(x) => Expr::IsNull {
1541 expr: boxed(*x)?,
1542 negated: false,
1543 },
1544 E::IsNotNull(x) => Expr::IsNull {
1545 expr: boxed(*x)?,
1546 negated: true,
1547 },
1548 E::IsTrue(x) => is_bool(*x, true, false)?,
1549 E::IsNotTrue(x) => is_bool(*x, true, true)?,
1550 E::IsFalse(x) => is_bool(*x, false, false)?,
1551 E::IsNotFalse(x) => is_bool(*x, false, true)?,
1552 E::InList {
1553 expr: x,
1554 list,
1555 negated,
1556 } => Expr::InList {
1557 expr: boxed(*x)?,
1558 list: list.into_iter().map(expr).collect::<R<_>>()?,
1559 negated,
1560 },
1561 E::InSubquery {
1562 expr: x,
1563 subquery,
1564 negated,
1565 } => Expr::InSubquery {
1566 expr: boxed(*x)?,
1567 query: Box::new(query(*subquery)?),
1568 negated,
1569 },
1570 E::Between {
1571 expr: x,
1572 negated,
1573 low,
1574 high,
1575 } => Expr::Between {
1576 expr: boxed(*x)?,
1577 low: boxed(*low)?,
1578 high: boxed(*high)?,
1579 negated,
1580 },
1581 E::Like {
1582 negated,
1583 any,
1584 expr: x,
1585 pattern,
1586 escape_char,
1587 } => {
1588 if any {
1589 return no("like_option", "LIKE ANY");
1590 }
1591 let ci = rules().like_ci_default;
1592 if ci {
1593 add_note(
1594 "like",
1595 "the source LIKE is ASCII case-insensitive only while its connection setting is at the default",
1596 Allowance::LikeCaseSensitivityIsConnectionSetting,
1597 );
1598 }
1599 if rules().name == "mysql" {
1600 add_note(
1601 "like",
1602 "the source LIKE follows the column collation; read as case-sensitive (a binary collation)",
1603 Allowance::LikeCaseSensitivityIsConnectionSetting,
1604 );
1605 }
1606 like_expr(*x, *pattern, escape_char, negated, ci)?
1607 }
1608 E::RLike {
1609 negated,
1610 expr: x,
1611 pattern,
1612 ..
1613 } => Expr::Regexp {
1614 expr: boxed(*x)?,
1615 pattern: boxed(*pattern)?,
1616 negated,
1617 },
1618 E::ILike {
1619 negated,
1620 any,
1621 expr: x,
1622 pattern,
1623 escape_char,
1624 } => {
1625 if any {
1626 return no("like_option", "ILIKE ANY");
1627 }
1628 like_expr(*x, *pattern, escape_char, negated, true)?
1629 }
1630 E::UnaryOp { op, expr: x } => {
1631 let op = match op {
1632 sp::UnaryOperator::Not => UnaryOp::Not,
1633 sp::UnaryOperator::Minus => UnaryOp::Minus,
1634 sp::UnaryOperator::Plus => UnaryOp::Plus,
1635 sp::UnaryOperator::BitwiseNot => {
1637 return no(
1638 "bitwise_not",
1639 "`~x` differs in width and signedness per engine",
1640 );
1641 }
1642 other => return no("unary_operator", &head(&other)),
1643 };
1644 Expr::Unary {
1645 op,
1646 expr: boxed(*x)?,
1647 }
1648 }
1649 E::BinaryOp { left, op, right }
1650 if matches!(
1651 op,
1652 sp::BinaryOperator::PGRegexMatch | sp::BinaryOperator::PGRegexNotMatch
1653 ) =>
1654 {
1655 Expr::Regexp {
1656 expr: boxed(*left)?,
1657 pattern: boxed(*right)?,
1658 negated: matches!(op, sp::BinaryOperator::PGRegexNotMatch),
1659 }
1660 }
1661 E::BinaryOp { left, op, right } => {
1662 use sp::BinaryOperator as B;
1663 let op = match op {
1664 B::Eq => BinaryOp::Eq,
1665 B::NotEq => BinaryOp::NotEq,
1666 B::Lt => BinaryOp::Lt,
1667 B::LtEq => BinaryOp::LtEq,
1668 B::Gt => BinaryOp::Gt,
1669 B::GtEq => BinaryOp::GtEq,
1670 B::And => BinaryOp::And,
1671 B::Or => BinaryOp::Or,
1672 B::Plus => BinaryOp::Plus,
1673 B::Minus => BinaryOp::Minus,
1674 B::Multiply => BinaryOp::Multiply,
1675 B::Divide => BinaryOp::Divide,
1676 B::Modulo => BinaryOp::Modulo,
1677 B::StringConcat if rules().name == "mysql" => {
1681 return no(
1682 "pipes_operator",
1683 "MySQL `||` is logical OR (default sql_mode) but the parser gives it concatenation precedence; write OR, or CONCAT()",
1684 );
1685 }
1686 B::StringConcat => BinaryOp::Concat,
1687 B::MyIntegerDivide => BinaryOp::IntDivide,
1688 B::Spaceship => BinaryOp::NullSafeEq,
1689 B::BitwiseAnd => BinaryOp::BitAnd,
1690 B::BitwiseOr => BinaryOp::BitOr,
1691 B::BitwiseXor | B::PGBitwiseXor => BinaryOp::BitXor,
1692 B::PGBitwiseShiftLeft => BinaryOp::ShiftLeft,
1693 B::PGBitwiseShiftRight => BinaryOp::ShiftRight,
1694 B::Xor => BinaryOp::Xor,
1695 B::Arrow => BinaryOp::JsonGet,
1696 B::LongArrow => BinaryOp::JsonGetText,
1697 other => {
1698 return no(
1699 "binary_operator",
1700 &format!(
1701 "{} (JSON/array/regex/bitwise operators are not in the IR)",
1702 head(&other)
1703 ),
1704 );
1705 }
1706 };
1707 let is_hex = |e: &E| matches!(e, E::Value(v) if matches!(v.value, sp::Value::HexStringLiteral(_)));
1708 if (is_hex(&left) || is_hex(&right))
1709 && !matches!(
1710 op,
1711 BinaryOp::Eq
1712 | BinaryOp::NotEq
1713 | BinaryOp::Lt
1714 | BinaryOp::LtEq
1715 | BinaryOp::Gt
1716 | BinaryOp::GtEq
1717 | BinaryOp::NullSafeEq
1718 | BinaryOp::And
1719 | BinaryOp::Or
1720 | BinaryOp::Xor
1721 )
1722 {
1723 return no(
1724 "hex_literal_numeric",
1725 "a hex literal is a byte string; MySQL `0x41 + 1` reads it as the number 65, so arithmetic on it is not mapped",
1726 );
1727 }
1728 if op == BinaryOp::Divide && rules().name == "mysql" {
1729 add_note(
1730 "division",
1731 "the source `/` is exact (DECIMAL) division; the target divides as double precision",
1732 Allowance::DecimalDivisionAsFloat,
1733 );
1734 let l = Expr::Cast {
1735 expr: boxed(*left)?,
1736 ty: SqlType::Double,
1737 };
1738 return Ok(Expr::Binary {
1739 op,
1740 left: Box::new(l),
1741 right: boxed(*right)?,
1742 });
1743 }
1744 match (op, *left, *right) {
1748 (BinaryOp::Plus | BinaryOp::Minus, l, E::Interval(iv)) => Expr::Binary {
1749 op,
1750 left: boxed(l)?,
1751 right: Box::new(interval_call(iv)?),
1752 },
1753 (BinaryOp::Plus, E::Interval(iv), r) => Expr::Binary {
1754 op,
1755 left: boxed(r)?,
1756 right: Box::new(interval_call(iv)?),
1757 },
1758 (op, l, r) => Expr::Binary {
1759 op,
1760 left: boxed(l)?,
1761 right: boxed(r)?,
1762 },
1763 }
1764 }
1765 E::Case {
1766 case_token: _,
1767 end_token: _,
1768 operand,
1769 conditions,
1770 else_result,
1771 } => Expr::Case {
1772 operand: operand.map(|o| boxed(*o)).transpose()?,
1773 branches: conditions
1774 .into_iter()
1775 .map(|sp::CaseWhen { condition, result }| Ok((expr(condition)?, expr(result)?)))
1776 .collect::<R<_>>()?,
1777 else_: else_result.map(|o| boxed(*o)).transpose()?,
1778 },
1779 E::Cast {
1780 kind,
1781 expr: x,
1782 data_type,
1783 format,
1784 } => {
1785 if format.is_some() || !matches!(kind, sp::CastKind::Cast | sp::CastKind::DoubleColon) {
1786 return no("cast_form", "TRY_CAST / SAFE_CAST / CAST .. FORMAT");
1787 }
1788 Expr::Cast {
1789 expr: boxed(*x)?,
1790 ty: data_type_to_ir(&data_type)?,
1791 }
1792 }
1793 E::Convert {
1796 is_try: false,
1797 expr: x,
1798 data_type: Some(data_type),
1799 charset: None,
1800 target_before_value: false,
1801 styles,
1802 } if styles.is_empty() => Expr::Cast {
1803 expr: boxed(*x)?,
1804 ty: data_type_to_ir(&data_type)?,
1805 },
1806 E::Convert { .. } => {
1807 return no(
1808 "convert_form",
1809 "CONVERT(.. USING charset), TRY_CONVERT and the target-first (MSSQL) form",
1810 );
1811 }
1812 E::Ceil { expr: x, field } => ceil_floor("ceil", *x, field)?,
1814 E::Floor { expr: x, field } => ceil_floor("floor", *x, field)?,
1815 E::Position { expr: sub, r#in } => Expr::Function(Function {
1817 name: "strpos".to_string(),
1818 args: vec![
1819 FunctionArg::Expr(expr(*r#in)?),
1820 FunctionArg::Expr(expr(*sub)?),
1821 ],
1822 distinct: false,
1823 star: false,
1824 filter: None,
1825 over: None,
1826 order_by: vec![],
1827 }),
1828 E::Trim {
1829 trim_where,
1830 trim_what,
1831 expr: x,
1832 trim_characters,
1833 } => {
1834 let side = match trim_where {
1836 None | Some(sp::TrimWhereField::Both) => TrimSide::Both,
1837 Some(sp::TrimWhereField::Leading) => TrimSide::Leading,
1838 Some(sp::TrimWhereField::Trailing) => TrimSide::Trailing,
1839 };
1840 let chars = match (trim_what, trim_characters) {
1841 (Some(w), None) => Some(boxed(*w)?),
1842 (None, None) => None,
1843 (None, Some(mut cs)) if cs.len() == 1 => Some(boxed(cs.remove(0))?),
1844 _ => return no("trim_form", "TRIM with several character arguments"),
1845 };
1846 Expr::Trim {
1847 expr: boxed(*x)?,
1848 side,
1849 chars,
1850 }
1851 }
1852 E::Substring {
1853 expr: x,
1854 substring_from,
1855 substring_for,
1856 special: _,
1857 shorthand: _,
1858 } => {
1859 if rules().name == "mysql" {
1860 let positive = |e: &E, min: u64| {
1863 matches!(
1864 e,
1865 E::Value(v) if matches!(&v.value, sp::Value::Number(n, _)
1866 if n.parse::<u64>().is_ok_and(|x| x >= min))
1867 )
1868 };
1869 let start_ok = substring_from.as_deref().is_some_and(|f| positive(f, 1));
1870 let len_ok = substring_for.as_deref().is_none_or(|l| positive(l, 0));
1871 if !(start_ok && len_ok) {
1872 add_note(
1873 "fn:substr.start_position",
1874 "MySQL SUBSTRING counts a start of 0 as empty and a negative start from the end; \
1875 the target does not",
1876 Allowance::SubstringStartAssumedPositive,
1877 );
1878 }
1879 }
1880 let mut args = vec![FunctionArg::Expr(expr(*x)?)];
1882 match (substring_from, substring_for) {
1883 (Some(f), Some(l)) => {
1884 args.push(FunctionArg::Expr(expr(*f)?));
1885 args.push(FunctionArg::Expr(expr(*l)?));
1886 }
1887 (Some(f), None) => args.push(FunctionArg::Expr(expr(*f)?)),
1888 (None, Some(l)) => {
1889 args.push(FunctionArg::Expr(Expr::Literal(Literal::Number(
1891 "1".into(),
1892 ))));
1893 args.push(FunctionArg::Expr(expr(*l)?));
1894 }
1895 (None, None) => return no("substring_form", "SUBSTRING without FROM or FOR"),
1896 }
1897 Expr::Function(Function {
1898 name: "substr".into(),
1899 args,
1900 distinct: false,
1901 star: false,
1902 filter: None,
1903 over: None,
1904 order_by: vec![],
1905 })
1906 }
1907 E::AnyOp {
1908 left,
1909 compare_op,
1910 right,
1911 is_some: _,
1912 } => {
1913 if !matches!(compare_op, sp::BinaryOperator::Eq) {
1914 return no("any_operator", "only `= ANY(..)` is in the IR");
1915 }
1916 quantified(*left, *right, false)?
1917 }
1918 E::AllOp {
1919 left,
1920 compare_op,
1921 right,
1922 } => {
1923 if !matches!(compare_op, sp::BinaryOperator::NotEq) {
1924 return no("all_operator", "only `<> ALL(..)` is in the IR");
1925 }
1926 quantified(*left, *right, true)?
1927 }
1928 E::Exists { subquery, negated } => Expr::Exists {
1929 query: Box::new(query(*subquery)?),
1930 negated,
1931 },
1932 E::Subquery(q) => Expr::Subquery(Box::new(query(*q)?)),
1933 E::Extract {
1934 field,
1935 syntax: _,
1936 expr: x,
1937 } => Expr::Extract {
1938 field: field.to_string().to_ascii_lowercase(),
1939 expr: boxed(*x)?,
1940 },
1941 E::Array(arr) => Expr::ArrayLiteral(arr.elem.into_iter().map(expr).collect::<R<Vec<_>>>()?),
1942 E::AtTimeZone {
1943 timestamp,
1944 time_zone,
1945 } => Expr::AtTimeZone {
1946 expr: boxed(*timestamp)?,
1947 zone: boxed(*time_zone)?,
1948 },
1949 E::Function(f) => function(f)?,
1950 other => return no("expression", &head(&other)),
1951 })
1952}
1953
1954fn quantified(left: sp::Expr, right: sp::Expr, negated: bool) -> R<Expr> {
1956 let lhs = boxed(left)?;
1957 let mut right = right;
1959 if let sp::Expr::Cast {
1960 kind: sp::CastKind::Cast | sp::CastKind::DoubleColon,
1961 expr: inner,
1962 data_type: sp::DataType::Array(_),
1963 format: None,
1964 } = right.clone()
1965 {
1966 right = *inner;
1967 }
1968 match right {
1969 sp::Expr::Value(v) => match v.value {
1970 sp::Value::Placeholder(_) => match value(v.value)? {
1971 Expr::Param(n) => Ok(Expr::AnyParam {
1972 expr: lhs,
1973 param: n,
1974 negated,
1975 }),
1976 _ => no("array_param", "expected a numbered parameter"),
1977 },
1978 other => no("array_operand", &head(&other)),
1979 },
1980 sp::Expr::Array(sp::Array { elem, named: _ }) => Ok(Expr::InList {
1981 expr: lhs,
1982 list: elem.into_iter().map(expr).collect::<R<Vec<_>>>()?,
1983 negated,
1984 }),
1985 sp::Expr::Subquery(q) => Ok(Expr::InSubquery {
1986 expr: lhs,
1987 query: Box::new(query(*q)?),
1988 negated,
1989 }),
1990 other => no("array_operand", &head(&other)),
1991 }
1992}
1993
1994fn excluded_refs(e: Expr) -> Expr {
1996 match e {
1997 Expr::Function(f) if f.name == "values" && f.args.len() == 1 => {
1998 match f.args.into_iter().next() {
1999 Some(FunctionArg::Expr(Expr::Column(n))) => {
2000 Expr::Column(Name(vec!["excluded".to_string(), n.last().to_string()]))
2001 }
2002 other => Expr::Function(Function {
2003 name: "values".into(),
2004 args: other.into_iter().collect(),
2005 distinct: false,
2006 star: false,
2007 filter: None,
2008 over: None,
2009 order_by: vec![],
2010 }),
2011 }
2012 }
2013 Expr::Binary { op, left, right } => Expr::Binary {
2014 op,
2015 left: Box::new(excluded_refs(*left)),
2016 right: Box::new(excluded_refs(*right)),
2017 },
2018 Expr::Nested(x) => Expr::Nested(Box::new(excluded_refs(*x))),
2019 Expr::Unary { op, expr } => Expr::Unary {
2020 op,
2021 expr: Box::new(excluded_refs(*expr)),
2022 },
2023 other => other,
2024 }
2025}
2026
2027fn like_expr(
2028 x: sp::Expr,
2029 pattern: sp::Expr,
2030 escape_char: Option<Box<sp::Expr>>,
2031 negated: bool,
2032 case_insensitive: bool,
2033) -> R<Expr> {
2034 let escape = match escape_char {
2035 Some(e) => Some(boxed(*e)?),
2036 None if rules().like_default_escape => {
2037 Some(Box::new(Expr::Literal(Literal::String("\\".into()))))
2038 }
2039 None => None,
2040 };
2041 Ok(Expr::Like {
2042 expr: boxed(x)?,
2043 pattern: boxed(pattern)?,
2044 negated,
2045 case_insensitive,
2046 escape,
2047 })
2048}
2049
2050fn value(v: sp::Value) -> R<Expr> {
2051 Ok(match v {
2052 sp::Value::Number(n, long) => {
2053 if long {
2054 return no("numeric_suffix", "L suffix");
2055 }
2056 Expr::Literal(Literal::Number(n))
2057 }
2058 sp::Value::SingleQuotedString(s) => Expr::Literal(Literal::String(s)),
2059 sp::Value::EscapedStringLiteral(s) => Expr::Literal(Literal::String(s)),
2061 sp::Value::HexStringLiteral(h) if rules().name == "postgres" => {
2063 return no(
2064 "hex_literal",
2065 &format!("X'{h}' is a bit string on PostgreSQL; write '\\x{h}'::bytea"),
2066 );
2067 }
2068 sp::Value::HexStringLiteral(h) => {
2069 if h.is_empty() || h.len() % 2 != 0 || !h.bytes().all(|c| c.is_ascii_hexdigit()) {
2070 return no(
2071 "hex_literal",
2072 &format!("`{h}` is not an even-length run of hex digits"),
2073 );
2074 }
2075 let bytes = (0..h.len())
2076 .step_by(2)
2077 .map(|i| u8::from_str_radix(&h[i..i + 2], 16))
2078 .collect::<Result<Vec<u8>, _>>()
2079 .or_else(|_| no("hex_literal", "invalid hex digits"))?;
2080 Expr::Literal(Literal::Bytes(bytes))
2081 }
2082 sp::Value::SingleQuotedByteStringLiteral(b)
2084 | sp::Value::DoubleQuotedByteStringLiteral(b) => {
2085 return no(
2086 "bit_literal",
2087 &format!("B'{b}' reads differently on each engine"),
2088 );
2089 }
2090 sp::Value::Boolean(b) if rules().name == "mysql" => {
2091 Expr::Literal(Literal::Number(if b { "1" } else { "0" }.into()))
2093 }
2094 sp::Value::Boolean(b) => Expr::Literal(Literal::Bool(b)),
2095 sp::Value::Null => Expr::Literal(Literal::Null),
2096 sp::Value::Placeholder(p) => {
2097 let bad = || ReadError::NotInIr {
2098 construct: "placeholder".into(),
2099 detail: format!(
2100 "`{p}` is not a placeholder form of dialect `{}`",
2101 rules().name
2102 ),
2103 };
2104 if rules().question_placeholders {
2105 if p == "?" {
2106 Expr::Param(bump_param(None, None))
2107 } else if let Some(d) = p.strip_prefix('?') {
2108 let n = d.parse::<u32>().ok().filter(|n| *n >= 1).ok_or_else(bad)?;
2109 Expr::Param(bump_param(Some(n), None))
2110 } else if let Some(nm) = p.strip_prefix([':', '@', '$']) {
2111 Expr::Param(bump_param(None, Some(nm)))
2112 } else {
2113 return Err(bad());
2114 }
2115 } else {
2116 let n = p
2117 .strip_prefix('$')
2118 .and_then(|d| d.parse::<u32>().ok())
2119 .filter(|n| *n >= 1)
2120 .ok_or_else(bad)?;
2121 Expr::Param(n)
2122 }
2123 }
2124 other => return no("literal", &head(&other)),
2125 })
2126}
2127
2128fn function(f: sp::Function) -> R<Expr> {
2129 let sp::Function {
2130 name,
2131 uses_odbc_syntax,
2132 parameters,
2133 args,
2134 within_group,
2135 filter,
2136 null_treatment,
2137 over,
2138 } = f;
2139 if uses_odbc_syntax
2140 || !matches!(parameters, sp::FunctionArguments::None)
2141 || !within_group.is_empty()
2142 || null_treatment.is_some()
2143 {
2144 return no(
2145 "function_option",
2146 "ODBC syntax/parameters/WITHIN GROUP/FILTER/IGNORE NULLS",
2147 );
2148 }
2149 let over = match over {
2150 None => None,
2151 Some(sp::WindowType::NamedWindow(_)) => {
2152 return no(
2153 "named_window",
2154 "OVER w (a WINDOW clause) is not in the IR yet",
2155 );
2156 }
2157 Some(sp::WindowType::WindowSpec(spec)) => Some(window_spec(spec)?),
2158 };
2159 let filter = filter.map(|f| expr(*f).map(Box::new)).transpose()?;
2160 let name = object_name(&name)?;
2161 let base = name.last().to_string();
2162 if name.0.len() != 1 {
2163 return no("qualified_function", &name.0.join("."));
2164 }
2165 let args = match args {
2166 sp::FunctionArguments::None => {
2168 if base == "current_timestamp" {
2169 if rules().name == "sqlite" {
2170 add_note(
2171 "fn:now",
2172 "the source CURRENT_TIMESTAMP is text 'YYYY-MM-DD HH:MM:SS' (UTC), not a timestamptz",
2173 Allowance::NowAsText,
2174 );
2175 }
2176 return Ok(Expr::Function(Function {
2177 name: "now".to_string(),
2178 args: vec![],
2179 distinct: false,
2180 star: false,
2181 filter: None,
2182 over: None,
2183 order_by: vec![],
2184 }));
2185 }
2186 if matches!(
2188 base.as_str(),
2189 "current_user" | "session_user" | "current_role"
2190 ) {
2191 return Ok(Expr::Function(Function {
2192 name: base,
2193 args: vec![],
2194 distinct: false,
2195 star: false,
2196 filter: None,
2197 over: None,
2198 order_by: vec![],
2199 }));
2200 }
2201 if base == "current_date" {
2204 if rules().name == "sqlite" {
2205 add_note(
2206 "fn:current_date",
2207 "the source CURRENT_DATE is text 'YYYY-MM-DD' (the UTC date), not a date",
2208 Allowance::DateAsText,
2209 );
2210 }
2211 return Ok(Expr::Function(Function {
2212 name: "current_date".to_string(),
2213 args: vec![],
2214 distinct: false,
2215 star: false,
2216 filter: None,
2217 over: None,
2218 order_by: vec![],
2219 }));
2220 }
2221 return no("bare_function", &format!("`{base}` without parentheses"));
2222 }
2223 sp::FunctionArguments::Subquery(q) if base == "array" => {
2224 return Ok(Expr::ArraySubquery(Box::new(query(*q)?)));
2225 }
2226 sp::FunctionArguments::Subquery(_) => {
2227 return no("function_subquery", "function over a subquery");
2228 }
2229 sp::FunctionArguments::List(l) => l,
2230 };
2231 let sp::FunctionArgumentList {
2232 duplicate_treatment,
2233 args,
2234 clauses,
2235 } = args;
2236 let mut agg_order = Vec::new();
2237 let mut separator: Option<String> = None;
2238 for cl in clauses {
2239 match cl {
2240 sp::FunctionArgumentClause::OrderBy(items) => {
2241 for it in items {
2242 agg_order.push(order_item(it)?);
2243 }
2244 }
2245 sp::FunctionArgumentClause::Separator(v) => match v.value {
2246 sp::Value::SingleQuotedString(s) => separator = Some(s),
2247 other => return no("separator", &head(&other)),
2248 },
2249 other => {
2250 return no(
2251 "function_clause",
2252 &format!("{} inside a function call", head(&other)),
2253 );
2254 }
2255 }
2256 }
2257 let distinct = match duplicate_treatment {
2258 None | Some(sp::DuplicateTreatment::All) => false,
2259 Some(sp::DuplicateTreatment::Distinct) => true,
2260 };
2261 if rules().name == "mysql"
2265 && !distinct
2266 && agg_order.is_empty()
2267 && matches!(
2268 base.as_str(),
2269 "date_add" | "adddate" | "date_sub" | "subdate"
2270 )
2271 && args.len() == 2
2272 && matches!(
2273 args[1],
2274 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(sp::Expr::Interval(_)))
2275 )
2276 {
2277 let op = if matches!(base.as_str(), "date_add" | "adddate") {
2278 BinaryOp::Plus
2279 } else {
2280 BinaryOp::Minus
2281 };
2282 let mut it = args.into_iter();
2283 let (Some(sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(l))), Some(second)) =
2284 (it.next(), it.next())
2285 else {
2286 return no("function_argument", "first argument of a date function");
2287 };
2288 let sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(sp::Expr::Interval(iv))) = second
2289 else {
2290 unreachable!("matched above")
2291 };
2292 return Ok(Expr::Binary {
2293 op,
2294 left: Box::new(expr(l)?),
2295 right: Box::new(interval_call(iv)?),
2296 });
2297 }
2298 if rules().name == "mysql"
2300 && !distinct
2301 && agg_order.is_empty()
2302 && matches!(base.as_str(), "adddate" | "subdate")
2303 && args.len() == 2
2304 && matches!(
2305 &args[1],
2306 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(sp::Expr::Value(v)))
2307 if matches!(v.value, sp::Value::Number(..))
2308 )
2309 {
2310 let op = if base == "adddate" {
2311 BinaryOp::Plus
2312 } else {
2313 BinaryOp::Minus
2314 };
2315 let mut it = args.into_iter();
2316 let (
2317 Some(sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(l))),
2318 Some(sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(sp::Expr::Value(v)))),
2319 ) = (it.next(), it.next())
2320 else {
2321 return no("function_argument", "argument of a date function");
2322 };
2323 let sp::Value::Number(n, _) = v.value else {
2324 unreachable!("matched above")
2325 };
2326 return Ok(Expr::Binary {
2327 op,
2328 left: Box::new(expr(l)?),
2329 right: Box::new(make_interval("days", n)),
2330 });
2331 }
2332 let mut out = Vec::new();
2333 let mut star = false;
2334 for a in args {
2335 match a {
2336 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => {
2337 out.push(FunctionArg::Expr(expr(e)?))
2338 }
2339 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Wildcard) => star = true,
2340 sp::FunctionArg::Named {
2341 name,
2342 arg: sp::FunctionArgExpr::Expr(e),
2343 operator: sp::FunctionArgOperator::RightArrow,
2344 } => out.push(FunctionArg::Named {
2345 name: ident(&name),
2346 value: expr(e)?,
2347 }),
2348 sp::FunctionArg::ExprNamed {
2350 name: sp::Expr::Identifier(name),
2351 arg: sp::FunctionArgExpr::Expr(e),
2352 operator: sp::FunctionArgOperator::RightArrow,
2353 } => out.push(FunctionArg::Named {
2354 name: ident(&name),
2355 value: expr(e)?,
2356 }),
2357 other => return no("function_argument", &head(&other)),
2358 }
2359 }
2360 let mut name = base;
2361 if name == "current_timestamp" {
2362 name = "now".to_string();
2363 }
2364 if name == "ceiling" {
2366 name = "ceil".to_string();
2367 }
2368 if name == "mod" && out.len() == 2 {
2369 let mut it = out.into_iter().map(|a| match a {
2370 FunctionArg::Expr(e) => e,
2371 FunctionArg::Named { value, .. } => value,
2372 });
2373 let (l, r) = (it.next().unwrap(), it.next().unwrap());
2374 return Ok(Expr::Binary {
2375 op: BinaryOp::Modulo,
2376 left: Box::new(l),
2377 right: Box::new(r),
2378 });
2379 }
2380 if rules().name == "mysql" {
2381 match name.as_str() {
2382 "ifnull" => name = "coalesce".to_string(),
2383 "curdate" if out.is_empty() => name = "current_date".to_string(),
2384 "group_concat" => {
2385 name = "string_agg".to_string();
2386 out.push(FunctionArg::Expr(Expr::Literal(Literal::String(
2387 separator.clone().unwrap_or_else(|| ",".to_string()),
2388 ))));
2389 add_note(
2390 "fn:string_agg",
2391 "the source GROUP_CONCAT truncates silently at group_concat_max_len",
2392 Allowance::AggregateStringTruncation,
2393 );
2394 }
2395 "length" => name = "octet_length".to_string(),
2397 "locate" | "instr" if out.len() == 2 => {
2400 if name == "locate" {
2401 out.swap(0, 1);
2402 }
2403 name = "strpos".to_string();
2404 add_note(
2405 "fn:strpos",
2406 "the source LOCATE/INSTR compare by the column collation (case-insensitive unless binary)",
2407 Allowance::TextCollation,
2408 );
2409 }
2410 "concat" if out.len() >= 2 => {
2411 let mut it = out.into_iter().map(|a| match a {
2412 FunctionArg::Expr(e) => e,
2413 FunctionArg::Named { value, .. } => value,
2414 });
2415 let first = it.next().expect("two arguments");
2416 let chain = it.fold(first, |l, r| Expr::Binary {
2417 op: BinaryOp::Concat,
2418 left: Box::new(l),
2419 right: Box::new(r),
2420 });
2421 return Ok(chain);
2422 }
2423 "if" if out.len() == 3 => {
2424 let mut it = out.into_iter().map(|a| match a {
2425 FunctionArg::Expr(e) => e,
2426 FunctionArg::Named { value, .. } => value,
2427 });
2428 let (c, t, f) = (it.next().unwrap(), it.next().unwrap(), it.next().unwrap());
2429 return Ok(Expr::Case {
2430 operand: None,
2431 branches: vec![(c, t)],
2432 else_: Some(Box::new(f)),
2433 });
2434 }
2435 _ => {}
2436 }
2437 }
2438 if rules().name == "sqlite" {
2439 match name.as_str() {
2440 "ifnull" => name = "coalesce".to_string(),
2441 "instr" if out.len() == 2 => name = "strpos".to_string(),
2442 "group_concat" => {
2443 name = "string_agg".to_string();
2444 if out.len() == 1 {
2445 out.push(FunctionArg::Expr(Expr::Literal(Literal::String(
2446 ",".into(),
2447 ))));
2448 }
2449 }
2450 _ => {}
2451 }
2452 }
2453 Ok(Expr::Function(Function {
2454 name,
2455 args: out,
2456 distinct,
2457 star,
2458 filter,
2459 over,
2460 order_by: agg_order,
2461 }))
2462}
2463
2464fn interval_call(iv: sp::Interval) -> R<Expr> {
2469 let sp::Interval {
2470 value,
2471 leading_field,
2472 leading_precision,
2473 last_field,
2474 fractional_seconds_precision,
2475 } = iv;
2476 if leading_precision.is_some() || last_field.is_some() || fractional_seconds_precision.is_some()
2477 {
2478 return no(
2479 "interval",
2480 "interval ranges and precisions are not in the IR",
2481 );
2482 }
2483 let (amount, unit) = match (*value, leading_field) {
2484 (sp::Expr::Value(v), Some(field)) => (
2486 match v.value {
2487 sp::Value::Number(n, _) => n,
2488 other => return no("interval", &head(&other)),
2489 },
2490 field.to_string(),
2491 ),
2492 (sp::Expr::Value(v), None) => match v.value {
2494 sp::Value::SingleQuotedString(s) => {
2495 let mut it = s.split_whitespace();
2496 match (it.next(), it.next(), it.next()) {
2497 (Some(a), Some(u), None) => (a.to_string(), u.to_string()),
2498 _ => {
2499 return no(
2500 "interval",
2501 "only a single `<number> <unit>` interval is in the IR",
2502 );
2503 }
2504 }
2505 }
2506 other => return no("interval", &head(&other)),
2507 },
2508 (other, _) => return no("interval", &head(&other)),
2509 };
2510 let numeric = !amount.is_empty()
2511 && amount
2512 .trim_start_matches('-')
2513 .chars()
2514 .all(|c| c.is_ascii_digit() || c == '.')
2515 && amount
2516 .trim_start_matches('-')
2517 .chars()
2518 .any(|c| c.is_ascii_digit());
2519 if !numeric {
2520 return no("interval", "the interval amount is not a plain number");
2521 }
2522 let unit = match unit.to_ascii_lowercase().trim_end_matches('s') {
2523 "second" | "sec" => "secs",
2524 "minute" | "min" => "mins",
2525 "hour" => "hours",
2526 "day" => "days",
2527 "week" => "weeks",
2528 "month" | "mon" => "months",
2529 "year" => "years",
2530 other => return no("interval", &format!("unit `{other}` is not in the IR")),
2531 };
2532 Ok(make_interval(unit, amount))
2533}
2534
2535fn make_interval(unit: &str, amount: String) -> Expr {
2536 Expr::Function(Function {
2537 name: "make_interval".to_string(),
2538 args: vec![FunctionArg::Named {
2539 name: unit.to_string(),
2540 value: Expr::Literal(Literal::Number(amount)),
2541 }],
2542 distinct: false,
2543 star: false,
2544 filter: None,
2545 over: None,
2546 order_by: vec![],
2547 })
2548}
2549
2550fn window_spec(w: sp::WindowSpec) -> R<Window> {
2551 let sp::WindowSpec {
2552 window_name,
2553 partition_by,
2554 order_by,
2555 window_frame,
2556 } = w;
2557 if window_name.is_some() {
2558 return no("named_window", "OVER (w ..) refinement of a named window");
2559 }
2560 let frame = match window_frame {
2561 None => None,
2562 Some(sp::WindowFrame {
2563 units,
2564 start_bound,
2565 end_bound,
2566 }) => {
2567 let bound = |b: sp::WindowFrameBound| -> R<FrameBound> {
2568 Ok(match b {
2569 sp::WindowFrameBound::CurrentRow => FrameBound::CurrentRow,
2570 sp::WindowFrameBound::Preceding(e) => {
2571 FrameBound::Preceding(e.map(|x| expr(*x).map(Box::new)).transpose()?)
2572 }
2573 sp::WindowFrameBound::Following(e) => {
2574 FrameBound::Following(e.map(|x| expr(*x).map(Box::new)).transpose()?)
2575 }
2576 })
2577 };
2578 Some(Frame {
2579 units: match units {
2580 sp::WindowFrameUnits::Rows => FrameUnits::Rows,
2581 sp::WindowFrameUnits::Range => FrameUnits::Range,
2582 sp::WindowFrameUnits::Groups => FrameUnits::Groups,
2583 },
2584 start: bound(start_bound)?,
2585 end: end_bound.map(bound).transpose()?,
2586 })
2587 }
2588 };
2589 Ok(Window {
2590 partition_by: partition_by.into_iter().map(expr).collect::<R<_>>()?,
2591 order_by: order_by.into_iter().map(order_item).collect::<R<_>>()?,
2592 frame,
2593 })
2594}
2595
2596fn data_type_to_ir(t: &sp::DataType) -> R<SqlType> {
2597 use sp::DataType as D;
2598 Ok(match t {
2599 D::SmallInt(_) | D::Int2(_) => SqlType::SmallInt,
2600 D::Int(_) | D::Integer(_) | D::Int4(_) => SqlType::Integer,
2601 D::BigInt(_) | D::Int8(_) => SqlType::BigInt,
2602 D::Real | D::Float4 => SqlType::Real,
2603 D::Double(_) | D::DoublePrecision | D::Float8 => SqlType::Double,
2604 D::Numeric(info) | D::Decimal(info) => match info {
2605 sp::ExactNumberInfo::None => SqlType::Numeric {
2606 precision: None,
2607 scale: None,
2608 },
2609 sp::ExactNumberInfo::Precision(p) => SqlType::Numeric {
2610 precision: Some(*p as u32),
2611 scale: None,
2612 },
2613 sp::ExactNumberInfo::PrecisionAndScale(p, s) => SqlType::Numeric {
2614 precision: Some(*p as u32),
2615 scale: Some(*s as u32),
2616 },
2617 },
2618 D::Text => SqlType::Text,
2619 D::Signed | D::SignedInteger | D::Unsigned | D::UnsignedInteger => SqlType::BigInt,
2620 D::Char(None) | D::Character(None) => SqlType::Text,
2621 D::Char(Some(sp::CharacterLength::IntegerLength { length, unit: None }))
2624 | D::Character(Some(sp::CharacterLength::IntegerLength { length, unit: None }))
2625 if rules().name == "mysql" =>
2626 {
2627 SqlType::Varchar(Some(*length as u32))
2628 }
2629 D::Datetime(None) => SqlType::Timestamp { with_tz: false },
2630 D::Varchar(None) | D::CharVarying(None) => SqlType::Varchar(None),
2631 D::Varchar(Some(sp::CharacterLength::IntegerLength { length, unit: None }))
2632 | D::CharVarying(Some(sp::CharacterLength::IntegerLength { length, unit: None })) => {
2633 SqlType::Varchar(Some(*length as u32))
2634 }
2635 D::Boolean | D::Bool => SqlType::Boolean,
2636 D::Date => SqlType::Date,
2637 D::Timestamp(None, tz) => SqlType::Timestamp {
2638 with_tz: match tz {
2639 sp::TimezoneInfo::None | sp::TimezoneInfo::WithoutTimeZone => false,
2640 sp::TimezoneInfo::WithTimeZone | sp::TimezoneInfo::Tz => true,
2641 },
2642 },
2643 D::Uuid => SqlType::Uuid,
2644 D::Bytea => SqlType::Bytes,
2645 D::JSON => SqlType::Json,
2646 D::JSONB => SqlType::Jsonb,
2647 other => return no("data_type", &format!("{other}")),
2648 })
2649}