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 use sqlparser::tokenizer::{Token, Tokenizer};
293 let d = (rules.dialect)();
294 let tokens = Tokenizer::new(d.as_ref(), ddl)
295 .tokenize_with_location()
296 .map_err(|e| ReadError::Syntax(e.to_string()))?;
297 let chars: Vec<char> = ddl.chars().collect();
298 let line_starts: Vec<usize> = std::iter::once(0)
299 .chain(
300 chars
301 .iter()
302 .enumerate()
303 .filter(|(_, c)| **c == '\n')
304 .map(|(i, _)| i + 1),
305 )
306 .collect();
307 let offset = |loc: sqlparser::tokenizer::Location| -> Option<usize> {
308 let line = usize::try_from(loc.line).ok()?.checked_sub(1)?;
309 let col = usize::try_from(loc.column).ok()?.checked_sub(1)?;
310 Some(line_starts.get(line)? + col)
311 };
312 let mut cuts: Vec<usize> = tokens
313 .iter()
314 .filter(|t| t.token == Token::SemiColon)
315 .filter_map(|t| offset(t.span.start))
316 .collect();
317 cuts.push(chars.len());
318 let mut out = SchemaRead::default();
319 let mut from = 0;
320 for cut in cuts {
321 if cut < from || cut > chars.len() {
322 continue;
323 }
324 let text: String = chars[from..cut].iter().collect();
325 from = (cut + 1).min(chars.len());
326 if text.trim().is_empty() {
327 continue;
328 }
329 let stmts = match Parser::parse_sql(d.as_ref(), &text) {
330 Ok(s) => s,
331 Err(e) => {
332 let head: String = text
333 .split_whitespace()
334 .take(4)
335 .collect::<Vec<_>>()
336 .join(" ");
337 out.skipped.push(format!("{head}: {e}"));
338 continue;
339 }
340 };
341 for st in stmts {
342 apply_ddl(&mut out.schema, st);
343 }
344 }
345 Ok(out)
346}
347
348fn apply_ddl(schema: &mut Schema, st: sp::Statement) {
349 match st {
350 sp::Statement::CreateTable(ct) => {
351 if ct.query.is_some() || ct.like.is_some() {
352 return;
353 }
354 let Ok(name) = object_name(&ct.name) else {
355 return;
356 };
357 schema.drop_table(&name);
358 for c in &ct.columns {
359 schema.set_column(name.0.clone(), ident(&c.name), column_from_def(c));
360 }
361 }
362 sp::Statement::AlterTable(at) => {
363 let Ok(name) = object_name(&at.name) else {
364 return;
365 };
366 for op in at.operations {
367 match op {
368 sp::AlterTableOperation::AddColumn { column_def, .. } => {
369 schema.set_column(
370 name.0.clone(),
371 ident(&column_def.name),
372 column_from_def(&column_def),
373 );
374 }
375 sp::AlterTableOperation::DropColumn { column_names, .. } => {
376 for c in &column_names {
377 schema.drop_column(&name, &ident(c));
378 }
379 }
380 sp::AlterTableOperation::AlterColumn {
381 column_name,
382 op: sp::AlterColumnOperation::SetDataType { data_type, .. },
383 } => {
384 let class = class_of_type(&data_type).unwrap_or(ColumnClass::Unknown);
385 schema.set_column(name.0.clone(), ident(&column_name), Column::new(class));
387 }
388 _ => {}
389 }
390 }
391 }
392 sp::Statement::Drop {
393 object_type: sp::ObjectType::Table,
394 names,
395 ..
396 } => {
397 for n in &names {
398 if let Ok(name) = object_name(n) {
399 schema.drop_table(&name);
400 }
401 }
402 }
403 _ => {}
404 }
405}
406
407fn statement(s: sp::Statement) -> R<Statement> {
408 match s {
409 sp::Statement::Query(q)
410 if matches!(
411 &*q.body,
412 sp::SetExpr::Insert(_) | sp::SetExpr::Update(_) | sp::SetExpr::Delete(_)
413 ) =>
414 {
415 let mut q = *q;
417 let with = q.with.take();
418 if q.to_string() != q.body.to_string() {
419 return no(
420 "query_clause",
421 "clauses around a WITH .. INSERT/UPDATE/DELETE",
422 );
423 }
424 let inner = match *q.body {
425 sp::SetExpr::Insert(st) | sp::SetExpr::Update(st) | sp::SetExpr::Delete(st) => {
426 statement(st)?
427 }
428 _ => unreachable!(),
429 };
430 match with {
431 None => Ok(inner),
432 Some(w) => {
433 let (ctes, recursive) = read_ctes(w)?;
434 Ok(Statement::With(WithStatement {
435 recursive,
436 ctes,
437 stmt: Box::new(inner),
438 }))
439 }
440 }
441 }
442 sp::Statement::Query(q) => Ok(Statement::Query(Box::new(query(*q)?))),
443 sp::Statement::Insert(i) => insert(i),
444 sp::Statement::Update(u) => update(u),
445 sp::Statement::Delete(d) => delete(d),
446 other => no(
447 "statement",
448 &format!(
449 "{} is not a DML statement the first slice reads",
450 head(&other)
451 ),
452 ),
453 }
454}
455
456fn head(x: &impl std::fmt::Debug) -> String {
457 let s = format!("{x:?}");
458 s.split(|c: char| !c.is_alphanumeric() && c != '_')
459 .next()
460 .unwrap_or("")
461 .to_string()
462}
463
464fn ident(i: &sp::Ident) -> String {
467 if i.quote_style.is_some() && !rules().fold_quoted {
468 i.value.clone()
469 } else {
470 i.value.to_lowercase()
471 }
472}
473
474fn object_name(n: &sp::ObjectName) -> R<Name> {
475 let mut parts = Vec::new();
476 for p in &n.0 {
477 match p {
478 sp::ObjectNamePart::Identifier(i) => parts.push(ident(i)),
479 other => return no("object_name_part", &head(other)),
480 }
481 }
482 Ok(Name(parts))
483}
484
485fn single_name(n: &sp::ObjectName) -> R<String> {
486 let nm = object_name(n)?;
487 if nm.0.len() != 1 {
488 return no(
489 "qualified_column_target",
490 "expected an unqualified column name",
491 );
492 }
493 Ok(nm.0.into_iter().next().unwrap())
494}
495
496fn read_ctes(w: sp::With) -> R<(Vec<Cte>, bool)> {
498 let sp::With {
499 with_token: _,
500 recursive,
501 cte_tables,
502 } = w;
503 let mut ctes = Vec::new();
504 for c in cte_tables {
505 let sp::Cte {
506 alias,
507 query: cq,
508 from,
509 materialized,
510 closing_paren_token: _,
511 } = c;
512 if from.is_some() || materialized.is_some() {
513 return no("cte_option", "MATERIALIZED / FROM in a CTE");
514 }
515 let mut columns = Vec::new();
516 for col in &alias.columns {
517 if col.data_type.is_some() {
518 return no("cte_column_type", "typed CTE column");
519 }
520 columns.push(ident(&col.name));
521 }
522 let dml = matches!(
523 &*cq.body,
524 sp::SetExpr::Insert(_) | sp::SetExpr::Update(_) | sp::SetExpr::Delete(_)
525 );
526 let cte_body = if dml {
527 if cq.to_string() != cq.body.to_string() {
530 return no("cte_dml_clause", "clauses around a data-modifying CTE");
531 }
532 match *cq.body {
533 sp::SetExpr::Insert(st) | sp::SetExpr::Update(st) | sp::SetExpr::Delete(st) => {
534 CteBody::Dml(Box::new(statement(st)?))
535 }
536 _ => unreachable!(),
537 }
538 } else {
539 CteBody::Query(Box::new(query(*cq)?))
540 };
541 ctes.push(Cte {
542 name: ident(&alias.name),
543 columns,
544 body: cte_body,
545 });
546 }
547 Ok((ctes, recursive))
548}
549
550fn query(q: sp::Query) -> R<Query> {
551 let sp::Query {
552 with,
553 body,
554 order_by,
555 limit_clause,
556 fetch,
557 locks,
558 for_clause,
559 settings,
560 format_clause,
561 pipe_operators,
562 } = q;
563 if fetch.is_some() {
564 return no("fetch_first", "FETCH FIRST is not in the IR (use LIMIT)");
565 }
566 if for_clause.is_some()
567 || settings.is_some()
568 || format_clause.is_some()
569 || !pipe_operators.is_empty()
570 {
571 return no("query_clause", "FOR/SETTINGS/FORMAT/pipe operators");
572 }
573 let mut ctes = Vec::new();
574 let mut recursive = false;
575 if let Some(w) = with {
576 (ctes, recursive) = read_ctes(w)?;
577 }
578 let mut body = set_expr(*body)?;
579 let mut lock = None;
580 match locks.len() {
581 0 => {}
582 1 => {
583 let sp::LockClause {
584 lock_type,
585 of,
586 nonblock,
587 } = locks.into_iter().next().unwrap();
588 lock = Some(RowLockClause {
589 strength: match lock_type {
590 sp::LockType::Update => RowLock::Update,
591 sp::LockType::Share => RowLock::Share,
592 },
593 of: match of {
594 Some(n) => Some(object_name(&n)?),
595 None => None,
596 },
597 wait: match nonblock {
598 None => LockWait::Block,
599 Some(sp::NonBlock::Nowait) => LockWait::NoWait,
600 Some(sp::NonBlock::SkipLocked) => LockWait::SkipLocked,
601 },
602 });
603 }
604 _ => return no("row_lock_multiple", "several locking clauses"),
605 }
606 if let Some(l) = lock {
607 match &mut body {
608 SetExpr::Select(s) => s.lock = Some(l),
609 _ => return no("row_lock_on_set_op", "locking clause on a set operation"),
610 }
611 }
612 let mut order = Vec::new();
613 if let Some(ob) = order_by {
614 let sp::OrderBy { kind, interpolate } = ob;
615 if interpolate.is_some() {
616 return no("order_by_interpolate", "INTERPOLATE");
617 }
618 match kind {
619 sp::OrderByKind::Expressions(items) => {
620 for it in items {
621 order.push(order_item(it)?);
622 }
623 }
624 sp::OrderByKind::All(_) => return no("order_by_all", "ORDER BY ALL"),
625 }
626 }
627 let (mut limit, mut offset) = (None, None);
628 match limit_clause {
629 None => {}
630 Some(sp::LimitClause::LimitOffset {
631 limit: l,
632 offset: o,
633 limit_by,
634 }) => {
635 if !limit_by.is_empty() {
636 return no("limit_by", "LIMIT .. BY");
637 }
638 limit = l.map(expr).transpose()?;
639 if rules().negative_limit_unlimited {
640 let neg = matches!(&limit, Some(Expr::Unary { op: UnaryOp::Minus, expr })
642 if matches!(**expr, Expr::Literal(Literal::Number(_))));
643 if neg {
644 limit = None;
645 }
646 }
647 if let Some(o) = o {
648 let sp::Offset { value, rows } = o;
649 if !matches!(rows, sp::OffsetRows::None) {
650 return no("offset_rows", "OFFSET .. ROWS");
651 }
652 offset = Some(expr(value)?);
653 }
654 }
655 Some(sp::LimitClause::OffsetCommaLimit {
656 offset: o,
657 limit: l,
658 }) => {
659 offset = Some(expr(o)?);
660 limit = Some(expr(l)?);
661 }
662 }
663 Ok(Query {
664 with: ctes,
665 recursive,
666 body,
667 order_by: order,
668 limit,
669 offset,
670 })
671}
672
673fn order_item(o: sp::OrderByExpr) -> R<OrderItem> {
674 let sp::OrderByExpr {
675 expr: e,
676 options,
677 with_fill,
678 } = o;
679 if with_fill.is_some() {
680 return no("order_by_with_fill", "WITH FILL");
681 }
682 let desc = matches!(options.sort, Some(sp::OrderBySort::Desc));
683 let default_first = if rules().nulls_smallest { !desc } else { desc };
686 let nulls_first = options.nulls_first.unwrap_or(default_first);
687 Ok(OrderItem {
688 expr: expr(e)?,
689 desc,
690 nulls_first: Some(nulls_first),
691 })
692}
693
694fn set_expr(s: sp::SetExpr) -> R<SetExpr> {
695 match s {
696 sp::SetExpr::Select(sel) => Ok(SetExpr::Select(Box::new(select(*sel)?))),
697 sp::SetExpr::SetOperation {
698 left,
699 op,
700 set_quantifier,
701 right,
702 } => {
703 let op = match op {
704 sp::SetOperator::Union => SetOp::Union,
705 sp::SetOperator::Intersect => SetOp::Intersect,
706 sp::SetOperator::Except => SetOp::Except,
707 sp::SetOperator::Minus => return no("minus", "MINUS is not PostgreSQL"),
708 };
709 let all = match set_quantifier {
710 sp::SetQuantifier::All => true,
711 sp::SetQuantifier::Distinct | sp::SetQuantifier::None => false,
712 other => return no("set_quantifier", &head(&other)),
713 };
714 Ok(SetExpr::SetOp {
715 op,
716 all,
717 left: Box::new(set_expr(*left)?),
718 right: Box::new(set_expr(*right)?),
719 })
720 }
721 sp::SetExpr::Query(q) => {
722 let q = query(*q)?;
723 if !q.with.is_empty()
724 || !q.order_by.is_empty()
725 || q.limit.is_some()
726 || q.offset.is_some()
727 {
728 return no(
729 "parenthesised_query",
730 "parenthesised query with WITH/ORDER/LIMIT as a set operand",
731 );
732 }
733 Ok(q.body)
734 }
735 sp::SetExpr::Values(_) => no("values_query", "a bare VALUES query outside INSERT"),
736 other => no("set_expr", &head(&other)),
737 }
738}
739
740fn select(s: sp::Select) -> R<Select> {
741 let sp::Select {
742 select_token: _,
743 optimizer_hints,
744 distinct,
745 select_modifiers,
746 top,
747 top_before_distinct: _,
748 projection,
749 exclude,
750 into,
751 from,
752 lateral_views,
753 prewhere,
754 selection,
755 connect_by,
756 group_by,
757 cluster_by,
758 distribute_by,
759 sort_by,
760 having,
761 named_window,
762 qualify,
763 window_before_qualify: _,
764 value_table_mode,
765 flavor: _,
766 } = s;
767 if !optimizer_hints.is_empty()
768 || select_modifiers.is_some()
769 || top.is_some()
770 || exclude.is_some()
771 || into.is_some()
772 || !lateral_views.is_empty()
773 || prewhere.is_some()
774 || !connect_by.is_empty()
775 || !cluster_by.is_empty()
776 || !distribute_by.is_empty()
777 || !sort_by.is_empty()
778 || !named_window.is_empty()
779 || qualify.is_some()
780 || value_table_mode.is_some()
781 {
782 return no(
783 "select_clause",
784 "a SELECT clause the IR does not represent (TOP/INTO/WINDOW/QUALIFY/hints/...)",
785 );
786 }
787 let distinct = match distinct {
788 None | Some(sp::Distinct::All) => Distinct::No,
789 Some(sp::Distinct::Distinct) => Distinct::Yes,
790 Some(sp::Distinct::On(es)) => Distinct::On(es.into_iter().map(expr).collect::<R<_>>()?),
791 };
792 let group_by = match group_by {
793 sp::GroupByExpr::Expressions(es, mods) => {
794 if !mods.is_empty() {
795 return no("group_by_modifier", "ROLLUP/CUBE/TOTALS modifiers");
796 }
797 es.into_iter().map(expr).collect::<R<Vec<_>>>()?
798 }
799 sp::GroupByExpr::All(_) => return no("group_by_all", "GROUP BY ALL"),
800 };
801 let mut proj = Vec::new();
802 for it in projection {
803 proj.push(select_item(it)?);
804 }
805 let mut from_refs = Vec::new();
806 for t in from {
807 from_refs.push(table_with_joins(t)?);
808 }
809 Ok(Select {
810 distinct,
811 projection: proj,
812 from: from_refs,
813 where_: selection.map(expr).transpose()?,
814 group_by,
815 having: having.map(expr).transpose()?,
816 lock: None,
817 })
818}
819
820fn wildcard_plain(o: &sp::WildcardAdditionalOptions) -> R<()> {
821 let sp::WildcardAdditionalOptions {
822 wildcard_token: _,
823 opt_ilike,
824 opt_exclude,
825 opt_except,
826 opt_replace,
827 opt_rename,
828 opt_alias,
829 } = o;
830 if opt_ilike.is_some()
831 || opt_exclude.is_some()
832 || opt_except.is_some()
833 || opt_replace.is_some()
834 || opt_rename.is_some()
835 || opt_alias.is_some()
836 {
837 return no("wildcard_option", "EXCLUDE/EXCEPT/REPLACE/RENAME on *");
838 }
839 Ok(())
840}
841
842fn select_item(it: sp::SelectItem) -> R<SelectItem> {
843 match it {
844 sp::SelectItem::UnnamedExpr(e) => Ok(SelectItem::Expr {
845 expr: expr(e)?,
846 alias: None,
847 }),
848 sp::SelectItem::ExprWithAlias { expr: e, alias } => Ok(SelectItem::Expr {
849 expr: expr(e)?,
850 alias: Some(ident(&alias)),
851 }),
852 sp::SelectItem::Wildcard(o) => {
853 wildcard_plain(&o)?;
854 Ok(SelectItem::Wildcard)
855 }
856 sp::SelectItem::QualifiedWildcard(kind, o) => {
857 wildcard_plain(&o)?;
858 match kind {
859 sp::SelectItemQualifiedWildcardKind::ObjectName(n) => {
860 Ok(SelectItem::QualifiedWildcard(object_name(&n)?))
861 }
862 other => no("qualified_wildcard", &head(&other)),
863 }
864 }
865 other => no("select_item", &head(&other)),
866 }
867}
868
869fn alias_name(a: Option<sp::TableAlias>) -> R<Option<String>> {
870 match a {
871 None => Ok(None),
872 Some(sp::TableAlias {
873 explicit: _,
874 name,
875 columns,
876 at,
877 }) => {
878 if !columns.is_empty() || at.is_some() {
879 return no("table_alias_columns", "column list on a table alias");
880 }
881 Ok(Some(ident(&name)))
882 }
883 }
884}
885
886fn table_factor(t: sp::TableFactor) -> R<TableRef> {
887 match t {
888 sp::TableFactor::Table {
889 name,
890 alias,
891 args,
892 with_hints,
893 version,
894 with_ordinality,
895 partitions,
896 json_path,
897 sample,
898 index_hints,
899 } => {
900 if args.is_some()
901 || !with_hints.is_empty()
902 || version.is_some()
903 || with_ordinality
904 || !partitions.is_empty()
905 || json_path.is_some()
906 || sample.is_some()
907 || !index_hints.is_empty()
908 {
909 return no(
910 "table_option",
911 "table function args/hints/version/sample/partitions",
912 );
913 }
914 Ok(TableRef::Table {
915 name: object_name(&name)?,
916 alias: alias_name(alias)?,
917 })
918 }
919 sp::TableFactor::Derived {
920 lateral,
921 subquery,
922 alias,
923 sample,
924 } => {
925 if lateral || sample.is_some() {
926 return no("derived_option", "LATERAL / TABLESAMPLE");
927 }
928 let alias = alias_name(alias)?.ok_or_else(|| ReadError::NotInIr {
929 construct: "unaliased_subquery".into(),
930 detail: "PostgreSQL requires an alias on a FROM subquery".into(),
931 })?;
932 Ok(TableRef::Subquery {
933 query: Box::new(query(*subquery)?),
934 alias,
935 })
936 }
937 other => no("table_factor", &head(&other)),
938 }
939}
940
941fn table_with_joins(t: sp::TableWithJoins) -> R<TableRef> {
942 let sp::TableWithJoins { relation, joins } = t;
943 let mut cur = table_factor(relation)?;
944 for j in joins {
945 let sp::Join {
946 relation,
947 global,
948 join_operator,
949 } = j;
950 if global {
951 return no("global_join", "GLOBAL JOIN");
952 }
953 let (kind, constraint) = match join_operator {
954 sp::JoinOperator::Join(c) | sp::JoinOperator::Inner(c) => (JoinKind::Inner, c),
955 sp::JoinOperator::Left(c) | sp::JoinOperator::LeftOuter(c) => (JoinKind::Left, c),
956 sp::JoinOperator::Right(c) | sp::JoinOperator::RightOuter(c) => (JoinKind::Right, c),
957 sp::JoinOperator::FullOuter(c) => (JoinKind::Full, c),
958 sp::JoinOperator::CrossJoin(c) => (JoinKind::Cross, c),
959 other => return no("join_kind", &head(&other)),
960 };
961 let on = match constraint {
962 sp::JoinConstraint::On(e) => Some(expr(e)?),
963 sp::JoinConstraint::None => None,
964 sp::JoinConstraint::Using(_) => {
965 return no("join_using", "JOIN .. USING is not in the IR yet");
966 }
967 sp::JoinConstraint::Natural => return no("natural_join", "NATURAL JOIN"),
968 };
969 cur = TableRef::Join {
970 left: Box::new(cur),
971 right: Box::new(table_factor(relation)?),
972 kind,
973 on,
974 };
975 }
976 Ok(cur)
977}
978
979fn simple_table(t: sp::TableWithJoins) -> R<(Name, Option<String>)> {
980 let sp::TableWithJoins { relation, joins } = t;
981 if !joins.is_empty() {
982 return no("dml_target_join", "joins in a DML target");
983 }
984 match table_factor(relation)? {
985 TableRef::Table { name, alias } => Ok((name, alias)),
986 _ => no("dml_target", "DML target is not a plain table"),
987 }
988}
989
990fn returning(r: Option<Vec<sp::SelectItem>>) -> R<Vec<SelectItem>> {
991 r.unwrap_or_default().into_iter().map(select_item).collect()
992}
993
994fn assignments(a: Vec<sp::Assignment>) -> R<Vec<Assignment>> {
995 let mut out = Vec::new();
996 for sp::Assignment { target, value } in a {
997 let column = match target {
998 sp::AssignmentTarget::ColumnName(n) => single_name(&n)?,
999 sp::AssignmentTarget::Tuple(_) => return no("tuple_assignment", "SET (a, b) = ..."),
1000 };
1001 out.push(Assignment {
1002 column,
1003 value: expr(value)?,
1004 });
1005 }
1006 Ok(out)
1007}
1008
1009fn insert(i: sp::Insert) -> R<Statement> {
1010 let sp::Insert {
1011 insert_token: _,
1012 optimizer_hints,
1013 or,
1014 ignore,
1015 into: _,
1016 table,
1017 table_alias,
1018 columns,
1019 overwrite,
1020 source,
1021 assignments: asg,
1022 partitioned,
1023 after_columns,
1024 has_table_keyword: _,
1025 on,
1026 returning: ret,
1027 output,
1028 replace_into,
1029 priority,
1030 insert_alias,
1031 settings,
1032 format_clause,
1033 multi_table_insert_type,
1034 multi_table_into_clauses,
1035 multi_table_when_clauses,
1036 multi_table_else_clause,
1037 } = i;
1038 if !optimizer_hints.is_empty()
1039 || or.is_some()
1040 || table_alias.is_some()
1041 || overwrite
1042 || !asg.is_empty()
1043 || partitioned.is_some()
1044 || !after_columns.is_empty()
1045 || output.is_some()
1046 || replace_into
1047 || priority.is_some()
1048 || insert_alias.is_some()
1049 || settings.is_some()
1050 || format_clause.is_some()
1051 || multi_table_insert_type.is_some()
1052 || !multi_table_into_clauses.is_empty()
1053 || !multi_table_when_clauses.is_empty()
1054 || multi_table_else_clause.is_some()
1055 {
1056 return no(
1057 "insert_option",
1058 "an INSERT form other than INSERT INTO t [(cols)] VALUES|query",
1059 );
1060 }
1061 let table = match table {
1062 sp::TableObject::TableName(n) => object_name(&n)?,
1063 other => return no("insert_target", &head(&other)),
1064 };
1065 let columns = columns.iter().map(single_name).collect::<R<Vec<_>>>()?;
1066 let source = match source {
1067 None => InsertSource::DefaultValues,
1068 Some(q) => {
1069 let q = *q;
1070 match *q.body {
1071 sp::SetExpr::Values(v)
1072 if q.with.is_none()
1073 && q.order_by.is_none()
1074 && q.limit_clause.is_none()
1075 && q.locks.is_empty() =>
1076 {
1077 let sp::Values {
1078 explicit_row,
1079 value_keyword: _,
1080 rows,
1081 } = v;
1082 if explicit_row {
1083 return no("explicit_row", "ROW(..) in VALUES");
1084 }
1085 let mut out = Vec::new();
1086 for r in rows {
1087 out.push(r.content.into_iter().map(expr).collect::<R<Vec<_>>>()?);
1088 }
1089 InsertSource::Values(out)
1090 }
1091 body => {
1092 let q2 = sp::Query {
1093 body: Box::new(body),
1094 ..q
1095 };
1096 InsertSource::Query(Box::new(query(q2)?))
1097 }
1098 }
1099 }
1100 };
1101 let on_conflict = match on {
1102 None => None,
1103 Some(sp::OnInsert::OnConflict(oc)) => {
1104 let sp::OnConflict {
1105 conflict_target,
1106 action,
1107 } = oc;
1108 let target = match conflict_target {
1109 None => Vec::new(),
1110 Some(sp::ConflictTarget::Columns(cols)) => cols.iter().map(ident).collect(),
1111 Some(sp::ConflictTarget::OnConstraint(_)) => {
1112 return no("on_conflict_constraint", "ON CONFLICT ON CONSTRAINT");
1113 }
1114 };
1115 let action = match action {
1116 sp::OnConflictAction::DoNothing => ConflictAction::DoNothing,
1117 sp::OnConflictAction::DoUpdate(sp::DoUpdate {
1118 assignments: a,
1119 selection,
1120 }) => ConflictAction::DoUpdate {
1121 set: assignments(a)?,
1122 where_: selection.map(expr).transpose()?,
1123 },
1124 };
1125 Some(OnConflict { target, action })
1126 }
1127 Some(sp::OnInsert::DuplicateKeyUpdate(a)) => {
1128 let set = assignments(a)?
1130 .into_iter()
1131 .map(|asg| Assignment {
1132 column: asg.column,
1133 value: excluded_refs(asg.value),
1134 })
1135 .collect();
1136 Some(OnConflict {
1137 target: vec![],
1138 action: ConflictAction::DoUpdate { set, where_: None },
1139 })
1140 }
1141 Some(other) => return no("on_insert", &head(&other)),
1142 };
1143 let on_conflict = if ignore {
1146 if on_conflict.is_some() {
1147 return no(
1148 "insert_option",
1149 "IGNORE together with ON DUPLICATE KEY UPDATE",
1150 );
1151 }
1152 add_note(
1153 "insert_ignore",
1154 "INSERT IGNORE downgrades every error of the statement to a warning; ON CONFLICT DO NOTHING \
1155 skips unique-key conflicts only",
1156 Allowance::IgnoreCoversOnlyUniqueConflicts,
1157 );
1158 Some(OnConflict {
1159 target: vec![],
1160 action: ConflictAction::DoNothing,
1161 })
1162 } else {
1163 on_conflict
1164 };
1165 Ok(Statement::Insert(Insert {
1166 table,
1167 columns,
1168 source,
1169 on_conflict,
1170 returning: returning(ret)?,
1171 }))
1172}
1173
1174fn update(u: sp::Update) -> R<Statement> {
1175 let sp::Update {
1176 update_token: _,
1177 optimizer_hints,
1178 table,
1179 assignments: asg,
1180 from,
1181 selection,
1182 returning: ret,
1183 output,
1184 or,
1185 order_by,
1186 limit,
1187 } = u;
1188 if !optimizer_hints.is_empty()
1189 || output.is_some()
1190 || or.is_some()
1191 || !order_by.is_empty()
1192 || limit.is_some()
1193 {
1194 return no(
1195 "update_option",
1196 "UPDATE with hints/OUTPUT/OR/ORDER BY/LIMIT",
1197 );
1198 }
1199 let (table, alias) = simple_table(table)?;
1200 let from = match from {
1201 None => Vec::new(),
1202 Some(sp::UpdateTableFromKind::AfterSet(ts)) => ts
1203 .into_iter()
1204 .map(table_with_joins)
1205 .collect::<R<Vec<_>>>()?,
1206 Some(other) => return no("update_from_position", &head(&other)),
1207 };
1208 Ok(Statement::Update(Update {
1209 table,
1210 alias,
1211 set: assignments(asg)?,
1212 from,
1213 where_: selection.map(expr).transpose()?,
1214 returning: returning(ret)?,
1215 }))
1216}
1217
1218fn delete(d: sp::Delete) -> R<Statement> {
1219 let sp::Delete {
1220 delete_token: _,
1221 optimizer_hints,
1222 tables,
1223 from,
1224 using,
1225 selection,
1226 returning: ret,
1227 output,
1228 order_by,
1229 limit,
1230 } = d;
1231 if !optimizer_hints.is_empty()
1232 || !tables.is_empty()
1233 || output.is_some()
1234 || !order_by.is_empty()
1235 || limit.is_some()
1236 {
1237 return no(
1238 "delete_option",
1239 "DELETE with hints/multi-table/OUTPUT/ORDER BY/LIMIT",
1240 );
1241 }
1242 let ts = match from {
1243 sp::FromTable::WithFromKeyword(ts) | sp::FromTable::WithoutKeyword(ts) => ts,
1244 };
1245 if ts.len() != 1 {
1246 return no("delete_multi_target", "DELETE FROM a, b");
1247 }
1248 let (table, alias) = simple_table(ts.into_iter().next().unwrap())?;
1249 let using = using
1250 .unwrap_or_default()
1251 .into_iter()
1252 .map(table_with_joins)
1253 .collect::<R<Vec<_>>>()?;
1254 Ok(Statement::Delete(Delete {
1255 table,
1256 alias,
1257 using,
1258 where_: selection.map(expr).transpose()?,
1259 returning: returning(ret)?,
1260 }))
1261}
1262
1263fn boxed(e: sp::Expr) -> R<Box<Expr>> {
1264 Ok(Box::new(expr(e)?))
1265}
1266
1267fn expr(e: sp::Expr) -> R<Expr> {
1268 use sp::Expr as E;
1269 Ok(match e {
1270 E::Identifier(i) => Expr::Column(Name::simple(ident(&i))),
1271 E::CompoundIdentifier(parts) => Expr::Column(Name(parts.iter().map(ident).collect())),
1272 E::Value(v) => value(v.value)?,
1273 E::Nested(inner) => Expr::Nested(boxed(*inner)?),
1274 E::IsNull(x) => Expr::IsNull {
1275 expr: boxed(*x)?,
1276 negated: false,
1277 },
1278 E::IsNotNull(x) => Expr::IsNull {
1279 expr: boxed(*x)?,
1280 negated: true,
1281 },
1282 E::InList {
1283 expr: x,
1284 list,
1285 negated,
1286 } => Expr::InList {
1287 expr: boxed(*x)?,
1288 list: list.into_iter().map(expr).collect::<R<_>>()?,
1289 negated,
1290 },
1291 E::InSubquery {
1292 expr: x,
1293 subquery,
1294 negated,
1295 } => Expr::InSubquery {
1296 expr: boxed(*x)?,
1297 query: Box::new(query(*subquery)?),
1298 negated,
1299 },
1300 E::Between {
1301 expr: x,
1302 negated,
1303 low,
1304 high,
1305 } => Expr::Between {
1306 expr: boxed(*x)?,
1307 low: boxed(*low)?,
1308 high: boxed(*high)?,
1309 negated,
1310 },
1311 E::Like {
1312 negated,
1313 any,
1314 expr: x,
1315 pattern,
1316 escape_char,
1317 } => {
1318 if any {
1319 return no("like_option", "LIKE ANY");
1320 }
1321 let ci = rules().like_ci_default;
1322 if ci {
1323 add_note(
1324 "like",
1325 "the source LIKE is ASCII case-insensitive only while its connection setting is at the default",
1326 Allowance::LikeCaseSensitivityIsConnectionSetting,
1327 );
1328 }
1329 if rules().name == "mysql" {
1330 add_note(
1331 "like",
1332 "the source LIKE follows the column collation; read as case-sensitive (a binary collation)",
1333 Allowance::LikeCaseSensitivityIsConnectionSetting,
1334 );
1335 }
1336 like_expr(*x, *pattern, escape_char, negated, ci)?
1337 }
1338 E::ILike {
1339 negated,
1340 any,
1341 expr: x,
1342 pattern,
1343 escape_char,
1344 } => {
1345 if any {
1346 return no("like_option", "ILIKE ANY");
1347 }
1348 like_expr(*x, *pattern, escape_char, negated, true)?
1349 }
1350 E::UnaryOp { op, expr: x } => {
1351 let op = match op {
1352 sp::UnaryOperator::Not => UnaryOp::Not,
1353 sp::UnaryOperator::Minus => UnaryOp::Minus,
1354 sp::UnaryOperator::Plus => UnaryOp::Plus,
1355 other => return no("unary_operator", &head(&other)),
1356 };
1357 Expr::Unary {
1358 op,
1359 expr: boxed(*x)?,
1360 }
1361 }
1362 E::BinaryOp { left, op, right } => {
1363 use sp::BinaryOperator as B;
1364 let op = match op {
1365 B::Eq => BinaryOp::Eq,
1366 B::NotEq => BinaryOp::NotEq,
1367 B::Lt => BinaryOp::Lt,
1368 B::LtEq => BinaryOp::LtEq,
1369 B::Gt => BinaryOp::Gt,
1370 B::GtEq => BinaryOp::GtEq,
1371 B::And => BinaryOp::And,
1372 B::Or => BinaryOp::Or,
1373 B::Plus => BinaryOp::Plus,
1374 B::Minus => BinaryOp::Minus,
1375 B::Multiply => BinaryOp::Multiply,
1376 B::Divide => BinaryOp::Divide,
1377 B::Modulo => BinaryOp::Modulo,
1378 B::StringConcat if rules().name == "mysql" => {
1382 return no(
1383 "pipes_operator",
1384 "MySQL `||` is logical OR (default sql_mode) but the parser gives it concatenation precedence; write OR, or CONCAT()",
1385 );
1386 }
1387 B::StringConcat => BinaryOp::Concat,
1388 B::MyIntegerDivide => BinaryOp::IntDivide,
1389 B::Spaceship => BinaryOp::NullSafeEq,
1390 B::Arrow => BinaryOp::JsonGet,
1391 B::LongArrow => BinaryOp::JsonGetText,
1392 other => {
1393 return no(
1394 "binary_operator",
1395 &format!(
1396 "{} (JSON/array/regex/bitwise operators are not in the IR)",
1397 head(&other)
1398 ),
1399 );
1400 }
1401 };
1402 if op == BinaryOp::Divide && rules().name == "mysql" {
1403 add_note(
1404 "division",
1405 "the source `/` is exact (DECIMAL) division; the target divides as double precision",
1406 Allowance::DecimalDivisionAsFloat,
1407 );
1408 let l = Expr::Cast {
1409 expr: boxed(*left)?,
1410 ty: SqlType::Double,
1411 };
1412 return Ok(Expr::Binary {
1413 op,
1414 left: Box::new(l),
1415 right: boxed(*right)?,
1416 });
1417 }
1418 match (op, *left, *right) {
1422 (BinaryOp::Plus | BinaryOp::Minus, l, E::Interval(iv)) => Expr::Binary {
1423 op,
1424 left: boxed(l)?,
1425 right: Box::new(interval_call(iv)?),
1426 },
1427 (BinaryOp::Plus, E::Interval(iv), r) => Expr::Binary {
1428 op,
1429 left: boxed(r)?,
1430 right: Box::new(interval_call(iv)?),
1431 },
1432 (op, l, r) => Expr::Binary {
1433 op,
1434 left: boxed(l)?,
1435 right: boxed(r)?,
1436 },
1437 }
1438 }
1439 E::Case {
1440 case_token: _,
1441 end_token: _,
1442 operand,
1443 conditions,
1444 else_result,
1445 } => Expr::Case {
1446 operand: operand.map(|o| boxed(*o)).transpose()?,
1447 branches: conditions
1448 .into_iter()
1449 .map(|sp::CaseWhen { condition, result }| Ok((expr(condition)?, expr(result)?)))
1450 .collect::<R<_>>()?,
1451 else_: else_result.map(|o| boxed(*o)).transpose()?,
1452 },
1453 E::Cast {
1454 kind,
1455 expr: x,
1456 data_type,
1457 format,
1458 } => {
1459 if format.is_some() || !matches!(kind, sp::CastKind::Cast | sp::CastKind::DoubleColon) {
1460 return no("cast_form", "TRY_CAST / SAFE_CAST / CAST .. FORMAT");
1461 }
1462 Expr::Cast {
1463 expr: boxed(*x)?,
1464 ty: data_type_to_ir(&data_type)?,
1465 }
1466 }
1467 E::Trim {
1468 trim_where,
1469 trim_what,
1470 expr: x,
1471 trim_characters,
1472 } => {
1473 let side = match trim_where {
1475 None | Some(sp::TrimWhereField::Both) => TrimSide::Both,
1476 Some(sp::TrimWhereField::Leading) => TrimSide::Leading,
1477 Some(sp::TrimWhereField::Trailing) => TrimSide::Trailing,
1478 };
1479 let chars = match (trim_what, trim_characters) {
1480 (Some(w), None) => Some(boxed(*w)?),
1481 (None, None) => None,
1482 (None, Some(mut cs)) if cs.len() == 1 => Some(boxed(cs.remove(0))?),
1483 _ => return no("trim_form", "TRIM with several character arguments"),
1484 };
1485 Expr::Trim {
1486 expr: boxed(*x)?,
1487 side,
1488 chars,
1489 }
1490 }
1491 E::Substring {
1492 expr: x,
1493 substring_from,
1494 substring_for,
1495 special: _,
1496 shorthand: _,
1497 } => {
1498 if rules().name == "mysql" {
1499 let positive = |e: &E, min: u64| {
1502 matches!(
1503 e,
1504 E::Value(v) if matches!(&v.value, sp::Value::Number(n, _)
1505 if n.parse::<u64>().is_ok_and(|x| x >= min))
1506 )
1507 };
1508 let start_ok = substring_from.as_deref().is_some_and(|f| positive(f, 1));
1509 let len_ok = substring_for.as_deref().is_none_or(|l| positive(l, 0));
1510 if !(start_ok && len_ok) {
1511 add_note(
1512 "fn:substr.start_position",
1513 "MySQL SUBSTRING counts a start of 0 as empty and a negative start from the end; \
1514 the target does not",
1515 Allowance::SubstringStartAssumedPositive,
1516 );
1517 }
1518 }
1519 let mut args = vec![FunctionArg::Expr(expr(*x)?)];
1521 match (substring_from, substring_for) {
1522 (Some(f), Some(l)) => {
1523 args.push(FunctionArg::Expr(expr(*f)?));
1524 args.push(FunctionArg::Expr(expr(*l)?));
1525 }
1526 (Some(f), None) => args.push(FunctionArg::Expr(expr(*f)?)),
1527 (None, Some(l)) => {
1528 args.push(FunctionArg::Expr(Expr::Literal(Literal::Number(
1530 "1".into(),
1531 ))));
1532 args.push(FunctionArg::Expr(expr(*l)?));
1533 }
1534 (None, None) => return no("substring_form", "SUBSTRING without FROM or FOR"),
1535 }
1536 Expr::Function(Function {
1537 name: "substr".into(),
1538 args,
1539 distinct: false,
1540 star: false,
1541 filter: None,
1542 over: None,
1543 order_by: vec![],
1544 })
1545 }
1546 E::AnyOp {
1547 left,
1548 compare_op,
1549 right,
1550 is_some: _,
1551 } => {
1552 if !matches!(compare_op, sp::BinaryOperator::Eq) {
1553 return no("any_operator", "only `= ANY(..)` is in the IR");
1554 }
1555 quantified(*left, *right, false)?
1556 }
1557 E::AllOp {
1558 left,
1559 compare_op,
1560 right,
1561 } => {
1562 if !matches!(compare_op, sp::BinaryOperator::NotEq) {
1563 return no("all_operator", "only `<> ALL(..)` is in the IR");
1564 }
1565 quantified(*left, *right, true)?
1566 }
1567 E::Exists { subquery, negated } => Expr::Exists {
1568 query: Box::new(query(*subquery)?),
1569 negated,
1570 },
1571 E::Subquery(q) => Expr::Subquery(Box::new(query(*q)?)),
1572 E::Extract {
1573 field,
1574 syntax: _,
1575 expr: x,
1576 } => Expr::Extract {
1577 field: field.to_string().to_ascii_lowercase(),
1578 expr: boxed(*x)?,
1579 },
1580 E::Array(arr) => Expr::ArrayLiteral(arr.elem.into_iter().map(expr).collect::<R<Vec<_>>>()?),
1581 E::AtTimeZone {
1582 timestamp,
1583 time_zone,
1584 } => Expr::AtTimeZone {
1585 expr: boxed(*timestamp)?,
1586 zone: boxed(*time_zone)?,
1587 },
1588 E::Function(f) => function(f)?,
1589 other => return no("expression", &head(&other)),
1590 })
1591}
1592
1593fn quantified(left: sp::Expr, right: sp::Expr, negated: bool) -> R<Expr> {
1595 let lhs = boxed(left)?;
1596 let mut right = right;
1598 if let sp::Expr::Cast {
1599 kind: sp::CastKind::Cast | sp::CastKind::DoubleColon,
1600 expr: inner,
1601 data_type: sp::DataType::Array(_),
1602 format: None,
1603 } = right.clone()
1604 {
1605 right = *inner;
1606 }
1607 match right {
1608 sp::Expr::Value(v) => match v.value {
1609 sp::Value::Placeholder(_) => match value(v.value)? {
1610 Expr::Param(n) => Ok(Expr::AnyParam {
1611 expr: lhs,
1612 param: n,
1613 negated,
1614 }),
1615 _ => no("array_param", "expected a numbered parameter"),
1616 },
1617 other => no("array_operand", &head(&other)),
1618 },
1619 sp::Expr::Array(sp::Array { elem, named: _ }) => Ok(Expr::InList {
1620 expr: lhs,
1621 list: elem.into_iter().map(expr).collect::<R<Vec<_>>>()?,
1622 negated,
1623 }),
1624 sp::Expr::Subquery(q) => Ok(Expr::InSubquery {
1625 expr: lhs,
1626 query: Box::new(query(*q)?),
1627 negated,
1628 }),
1629 other => no("array_operand", &head(&other)),
1630 }
1631}
1632
1633fn excluded_refs(e: Expr) -> Expr {
1635 match e {
1636 Expr::Function(f) if f.name == "values" && f.args.len() == 1 => {
1637 match f.args.into_iter().next() {
1638 Some(FunctionArg::Expr(Expr::Column(n))) => {
1639 Expr::Column(Name(vec!["excluded".to_string(), n.last().to_string()]))
1640 }
1641 other => Expr::Function(Function {
1642 name: "values".into(),
1643 args: other.into_iter().collect(),
1644 distinct: false,
1645 star: false,
1646 filter: None,
1647 over: None,
1648 order_by: vec![],
1649 }),
1650 }
1651 }
1652 Expr::Binary { op, left, right } => Expr::Binary {
1653 op,
1654 left: Box::new(excluded_refs(*left)),
1655 right: Box::new(excluded_refs(*right)),
1656 },
1657 Expr::Nested(x) => Expr::Nested(Box::new(excluded_refs(*x))),
1658 Expr::Unary { op, expr } => Expr::Unary {
1659 op,
1660 expr: Box::new(excluded_refs(*expr)),
1661 },
1662 other => other,
1663 }
1664}
1665
1666fn like_expr(
1667 x: sp::Expr,
1668 pattern: sp::Expr,
1669 escape_char: Option<Box<sp::Expr>>,
1670 negated: bool,
1671 case_insensitive: bool,
1672) -> R<Expr> {
1673 let escape = match escape_char {
1674 Some(e) => Some(boxed(*e)?),
1675 None if rules().like_default_escape => {
1676 Some(Box::new(Expr::Literal(Literal::String("\\".into()))))
1677 }
1678 None => None,
1679 };
1680 Ok(Expr::Like {
1681 expr: boxed(x)?,
1682 pattern: boxed(pattern)?,
1683 negated,
1684 case_insensitive,
1685 escape,
1686 })
1687}
1688
1689fn value(v: sp::Value) -> R<Expr> {
1690 Ok(match v {
1691 sp::Value::Number(n, long) => {
1692 if long {
1693 return no("numeric_suffix", "L suffix");
1694 }
1695 Expr::Literal(Literal::Number(n))
1696 }
1697 sp::Value::SingleQuotedString(s) => Expr::Literal(Literal::String(s)),
1698 sp::Value::EscapedStringLiteral(s) => Expr::Literal(Literal::String(s)),
1700 sp::Value::Boolean(b) if rules().name == "mysql" => {
1701 Expr::Literal(Literal::Number(if b { "1" } else { "0" }.into()))
1703 }
1704 sp::Value::Boolean(b) => Expr::Literal(Literal::Bool(b)),
1705 sp::Value::Null => Expr::Literal(Literal::Null),
1706 sp::Value::Placeholder(p) => {
1707 let bad = || ReadError::NotInIr {
1708 construct: "placeholder".into(),
1709 detail: format!(
1710 "`{p}` is not a placeholder form of dialect `{}`",
1711 rules().name
1712 ),
1713 };
1714 if rules().question_placeholders {
1715 if p == "?" {
1716 Expr::Param(bump_param(None, None))
1717 } else if let Some(d) = p.strip_prefix('?') {
1718 let n = d.parse::<u32>().ok().filter(|n| *n >= 1).ok_or_else(bad)?;
1719 Expr::Param(bump_param(Some(n), None))
1720 } else if let Some(nm) = p.strip_prefix([':', '@', '$']) {
1721 Expr::Param(bump_param(None, Some(nm)))
1722 } else {
1723 return Err(bad());
1724 }
1725 } else {
1726 let n = p
1727 .strip_prefix('$')
1728 .and_then(|d| d.parse::<u32>().ok())
1729 .filter(|n| *n >= 1)
1730 .ok_or_else(bad)?;
1731 Expr::Param(n)
1732 }
1733 }
1734 other => return no("literal", &head(&other)),
1735 })
1736}
1737
1738fn function(f: sp::Function) -> R<Expr> {
1739 let sp::Function {
1740 name,
1741 uses_odbc_syntax,
1742 parameters,
1743 args,
1744 within_group,
1745 filter,
1746 null_treatment,
1747 over,
1748 } = f;
1749 if uses_odbc_syntax
1750 || !matches!(parameters, sp::FunctionArguments::None)
1751 || !within_group.is_empty()
1752 || null_treatment.is_some()
1753 {
1754 return no(
1755 "function_option",
1756 "ODBC syntax/parameters/WITHIN GROUP/FILTER/IGNORE NULLS",
1757 );
1758 }
1759 let over = match over {
1760 None => None,
1761 Some(sp::WindowType::NamedWindow(_)) => {
1762 return no(
1763 "named_window",
1764 "OVER w (a WINDOW clause) is not in the IR yet",
1765 );
1766 }
1767 Some(sp::WindowType::WindowSpec(spec)) => Some(window_spec(spec)?),
1768 };
1769 let filter = filter.map(|f| expr(*f).map(Box::new)).transpose()?;
1770 let name = object_name(&name)?;
1771 let base = name.last().to_string();
1772 if name.0.len() != 1 {
1773 return no("qualified_function", &name.0.join("."));
1774 }
1775 let args = match args {
1776 sp::FunctionArguments::None => {
1778 if base == "current_timestamp" {
1779 if rules().name == "sqlite" {
1780 add_note(
1781 "fn:now",
1782 "the source CURRENT_TIMESTAMP is text 'YYYY-MM-DD HH:MM:SS' (UTC), not a timestamptz",
1783 Allowance::NowAsText,
1784 );
1785 }
1786 return Ok(Expr::Function(Function {
1787 name: "now".to_string(),
1788 args: vec![],
1789 distinct: false,
1790 star: false,
1791 filter: None,
1792 over: None,
1793 order_by: vec![],
1794 }));
1795 }
1796 if matches!(
1798 base.as_str(),
1799 "current_user" | "session_user" | "current_role"
1800 ) {
1801 return Ok(Expr::Function(Function {
1802 name: base,
1803 args: vec![],
1804 distinct: false,
1805 star: false,
1806 filter: None,
1807 over: None,
1808 order_by: vec![],
1809 }));
1810 }
1811 if base == "current_date" {
1814 if rules().name == "sqlite" {
1815 add_note(
1816 "fn:current_date",
1817 "the source CURRENT_DATE is text 'YYYY-MM-DD' (the UTC date), not a date",
1818 Allowance::DateAsText,
1819 );
1820 }
1821 return Ok(Expr::Function(Function {
1822 name: "current_date".to_string(),
1823 args: vec![],
1824 distinct: false,
1825 star: false,
1826 filter: None,
1827 over: None,
1828 order_by: vec![],
1829 }));
1830 }
1831 return no("bare_function", &format!("`{base}` without parentheses"));
1832 }
1833 sp::FunctionArguments::Subquery(q) if base == "array" => {
1834 return Ok(Expr::ArraySubquery(Box::new(query(*q)?)));
1835 }
1836 sp::FunctionArguments::Subquery(_) => {
1837 return no("function_subquery", "function over a subquery");
1838 }
1839 sp::FunctionArguments::List(l) => l,
1840 };
1841 let sp::FunctionArgumentList {
1842 duplicate_treatment,
1843 args,
1844 clauses,
1845 } = args;
1846 let mut agg_order = Vec::new();
1847 let mut separator: Option<String> = None;
1848 for cl in clauses {
1849 match cl {
1850 sp::FunctionArgumentClause::OrderBy(items) => {
1851 for it in items {
1852 agg_order.push(order_item(it)?);
1853 }
1854 }
1855 sp::FunctionArgumentClause::Separator(v) => match v.value {
1856 sp::Value::SingleQuotedString(s) => separator = Some(s),
1857 other => return no("separator", &head(&other)),
1858 },
1859 other => {
1860 return no(
1861 "function_clause",
1862 &format!("{} inside a function call", head(&other)),
1863 );
1864 }
1865 }
1866 }
1867 let distinct = match duplicate_treatment {
1868 None | Some(sp::DuplicateTreatment::All) => false,
1869 Some(sp::DuplicateTreatment::Distinct) => true,
1870 };
1871 let mut out = Vec::new();
1872 let mut star = false;
1873 for a in args {
1874 match a {
1875 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => {
1876 out.push(FunctionArg::Expr(expr(e)?))
1877 }
1878 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Wildcard) => star = true,
1879 sp::FunctionArg::Named {
1880 name,
1881 arg: sp::FunctionArgExpr::Expr(e),
1882 operator: sp::FunctionArgOperator::RightArrow,
1883 } => out.push(FunctionArg::Named {
1884 name: ident(&name),
1885 value: expr(e)?,
1886 }),
1887 sp::FunctionArg::ExprNamed {
1889 name: sp::Expr::Identifier(name),
1890 arg: sp::FunctionArgExpr::Expr(e),
1891 operator: sp::FunctionArgOperator::RightArrow,
1892 } => out.push(FunctionArg::Named {
1893 name: ident(&name),
1894 value: expr(e)?,
1895 }),
1896 other => return no("function_argument", &head(&other)),
1897 }
1898 }
1899 let mut name = base;
1900 if name == "current_timestamp" {
1901 name = "now".to_string();
1902 }
1903 if rules().name == "mysql" {
1904 match name.as_str() {
1905 "ifnull" => name = "coalesce".to_string(),
1906 "group_concat" => {
1907 name = "string_agg".to_string();
1908 out.push(FunctionArg::Expr(Expr::Literal(Literal::String(
1909 separator.clone().unwrap_or_else(|| ",".to_string()),
1910 ))));
1911 add_note(
1912 "fn:string_agg",
1913 "the source GROUP_CONCAT truncates silently at group_concat_max_len",
1914 Allowance::AggregateStringTruncation,
1915 );
1916 }
1917 "length" => name = "octet_length".to_string(),
1919 "concat" if out.len() >= 2 => {
1920 let mut it = out.into_iter().map(|a| match a {
1921 FunctionArg::Expr(e) => e,
1922 FunctionArg::Named { value, .. } => value,
1923 });
1924 let first = it.next().expect("two arguments");
1925 let chain = it.fold(first, |l, r| Expr::Binary {
1926 op: BinaryOp::Concat,
1927 left: Box::new(l),
1928 right: Box::new(r),
1929 });
1930 return Ok(chain);
1931 }
1932 "if" if out.len() == 3 => {
1933 let mut it = out.into_iter().map(|a| match a {
1934 FunctionArg::Expr(e) => e,
1935 FunctionArg::Named { value, .. } => value,
1936 });
1937 let (c, t, f) = (it.next().unwrap(), it.next().unwrap(), it.next().unwrap());
1938 return Ok(Expr::Case {
1939 operand: None,
1940 branches: vec![(c, t)],
1941 else_: Some(Box::new(f)),
1942 });
1943 }
1944 _ => {}
1945 }
1946 }
1947 if rules().name == "sqlite" {
1948 match name.as_str() {
1949 "ifnull" => name = "coalesce".to_string(),
1950 "group_concat" => {
1951 name = "string_agg".to_string();
1952 if out.len() == 1 {
1953 out.push(FunctionArg::Expr(Expr::Literal(Literal::String(
1954 ",".into(),
1955 ))));
1956 }
1957 }
1958 _ => {}
1959 }
1960 }
1961 Ok(Expr::Function(Function {
1962 name,
1963 args: out,
1964 distinct,
1965 star,
1966 filter,
1967 over,
1968 order_by: agg_order,
1969 }))
1970}
1971
1972fn interval_call(iv: sp::Interval) -> R<Expr> {
1977 let sp::Interval {
1978 value,
1979 leading_field,
1980 leading_precision,
1981 last_field,
1982 fractional_seconds_precision,
1983 } = iv;
1984 if leading_precision.is_some() || last_field.is_some() || fractional_seconds_precision.is_some()
1985 {
1986 return no(
1987 "interval",
1988 "interval ranges and precisions are not in the IR",
1989 );
1990 }
1991 let (amount, unit) = match (*value, leading_field) {
1992 (sp::Expr::Value(v), Some(field)) => (
1994 match v.value {
1995 sp::Value::Number(n, _) => n,
1996 other => return no("interval", &head(&other)),
1997 },
1998 field.to_string(),
1999 ),
2000 (sp::Expr::Value(v), None) => match v.value {
2002 sp::Value::SingleQuotedString(s) => {
2003 let mut it = s.split_whitespace();
2004 match (it.next(), it.next(), it.next()) {
2005 (Some(a), Some(u), None) => (a.to_string(), u.to_string()),
2006 _ => {
2007 return no(
2008 "interval",
2009 "only a single `<number> <unit>` interval is in the IR",
2010 );
2011 }
2012 }
2013 }
2014 other => return no("interval", &head(&other)),
2015 },
2016 (other, _) => return no("interval", &head(&other)),
2017 };
2018 let numeric = !amount.is_empty()
2019 && amount
2020 .trim_start_matches('-')
2021 .chars()
2022 .all(|c| c.is_ascii_digit() || c == '.')
2023 && amount
2024 .trim_start_matches('-')
2025 .chars()
2026 .any(|c| c.is_ascii_digit());
2027 if !numeric {
2028 return no("interval", "the interval amount is not a plain number");
2029 }
2030 let unit = match unit.to_ascii_lowercase().trim_end_matches('s') {
2031 "second" | "sec" => "secs",
2032 "minute" | "min" => "mins",
2033 "hour" => "hours",
2034 "day" => "days",
2035 "week" => "weeks",
2036 "month" | "mon" => "months",
2037 "year" => "years",
2038 other => return no("interval", &format!("unit `{other}` is not in the IR")),
2039 };
2040 Ok(Expr::Function(Function {
2041 name: "make_interval".to_string(),
2042 args: vec![FunctionArg::Named {
2043 name: unit.to_string(),
2044 value: Expr::Literal(Literal::Number(amount)),
2045 }],
2046 distinct: false,
2047 star: false,
2048 filter: None,
2049 over: None,
2050 order_by: vec![],
2051 }))
2052}
2053
2054fn window_spec(w: sp::WindowSpec) -> R<Window> {
2055 let sp::WindowSpec {
2056 window_name,
2057 partition_by,
2058 order_by,
2059 window_frame,
2060 } = w;
2061 if window_name.is_some() {
2062 return no("named_window", "OVER (w ..) refinement of a named window");
2063 }
2064 let frame = match window_frame {
2065 None => None,
2066 Some(sp::WindowFrame {
2067 units,
2068 start_bound,
2069 end_bound,
2070 }) => {
2071 let bound = |b: sp::WindowFrameBound| -> R<FrameBound> {
2072 Ok(match b {
2073 sp::WindowFrameBound::CurrentRow => FrameBound::CurrentRow,
2074 sp::WindowFrameBound::Preceding(e) => {
2075 FrameBound::Preceding(e.map(|x| expr(*x).map(Box::new)).transpose()?)
2076 }
2077 sp::WindowFrameBound::Following(e) => {
2078 FrameBound::Following(e.map(|x| expr(*x).map(Box::new)).transpose()?)
2079 }
2080 })
2081 };
2082 Some(Frame {
2083 units: match units {
2084 sp::WindowFrameUnits::Rows => FrameUnits::Rows,
2085 sp::WindowFrameUnits::Range => FrameUnits::Range,
2086 sp::WindowFrameUnits::Groups => FrameUnits::Groups,
2087 },
2088 start: bound(start_bound)?,
2089 end: end_bound.map(bound).transpose()?,
2090 })
2091 }
2092 };
2093 Ok(Window {
2094 partition_by: partition_by.into_iter().map(expr).collect::<R<_>>()?,
2095 order_by: order_by.into_iter().map(order_item).collect::<R<_>>()?,
2096 frame,
2097 })
2098}
2099
2100fn data_type_to_ir(t: &sp::DataType) -> R<SqlType> {
2101 use sp::DataType as D;
2102 Ok(match t {
2103 D::SmallInt(_) | D::Int2(_) => SqlType::SmallInt,
2104 D::Int(_) | D::Integer(_) | D::Int4(_) => SqlType::Integer,
2105 D::BigInt(_) | D::Int8(_) => SqlType::BigInt,
2106 D::Real | D::Float4 => SqlType::Real,
2107 D::Double(_) | D::DoublePrecision | D::Float8 => SqlType::Double,
2108 D::Numeric(info) | D::Decimal(info) => match info {
2109 sp::ExactNumberInfo::None => SqlType::Numeric {
2110 precision: None,
2111 scale: None,
2112 },
2113 sp::ExactNumberInfo::Precision(p) => SqlType::Numeric {
2114 precision: Some(*p as u32),
2115 scale: None,
2116 },
2117 sp::ExactNumberInfo::PrecisionAndScale(p, s) => SqlType::Numeric {
2118 precision: Some(*p as u32),
2119 scale: Some(*s as u32),
2120 },
2121 },
2122 D::Text => SqlType::Text,
2123 D::Signed | D::SignedInteger | D::Unsigned | D::UnsignedInteger => SqlType::BigInt,
2124 D::Char(None) | D::Character(None) => SqlType::Text,
2125 D::Datetime(None) => SqlType::Timestamp { with_tz: false },
2126 D::Varchar(None) | D::CharVarying(None) => SqlType::Varchar(None),
2127 D::Varchar(Some(sp::CharacterLength::IntegerLength { length, unit: None }))
2128 | D::CharVarying(Some(sp::CharacterLength::IntegerLength { length, unit: None })) => {
2129 SqlType::Varchar(Some(*length as u32))
2130 }
2131 D::Boolean | D::Bool => SqlType::Boolean,
2132 D::Date => SqlType::Date,
2133 D::Timestamp(None, tz) => SqlType::Timestamp {
2134 with_tz: match tz {
2135 sp::TimezoneInfo::None | sp::TimezoneInfo::WithoutTimeZone => false,
2136 sp::TimezoneInfo::WithTimeZone | sp::TimezoneInfo::Tz => true,
2137 },
2138 },
2139 D::Uuid => SqlType::Uuid,
2140 D::Bytea => SqlType::Bytes,
2141 D::JSON => SqlType::Json,
2142 D::JSONB => SqlType::Jsonb,
2143 other => return no("data_type", &format!("{other}")),
2144 })
2145}