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 let Some(targs) = args {
902 let plain = with_hints.is_empty()
903 && version.is_none()
904 && !with_ordinality
905 && partitions.is_empty()
906 && json_path.is_none()
907 && sample.is_none()
908 && index_hints.is_empty()
909 && targs.settings.is_none();
910 let nm = object_name(&name)?;
911 if plain && nm.0.len() == 1 {
912 let mut out = Vec::new();
913 for a in targs.args {
914 match a {
915 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => {
916 out.push(expr(e)?)
917 }
918 _ => {
919 return no(
920 "table_function_arg",
921 "named or wildcard argument of a table function",
922 );
923 }
924 }
925 }
926 return Ok(TableRef::Function {
927 name: nm.0[0].clone(),
928 args: out,
929 alias: alias_name(alias)?,
930 });
931 }
932 return no(
933 "table_option",
934 "table function args/hints/version/sample/partitions",
935 );
936 }
937 if !with_hints.is_empty()
938 || version.is_some()
939 || with_ordinality
940 || !partitions.is_empty()
941 || json_path.is_some()
942 || sample.is_some()
943 || !index_hints.is_empty()
944 {
945 return no(
946 "table_option",
947 "table function args/hints/version/sample/partitions",
948 );
949 }
950 Ok(TableRef::Table {
951 name: object_name(&name)?,
952 alias: alias_name(alias)?,
953 })
954 }
955 sp::TableFactor::Derived {
956 lateral,
957 subquery,
958 alias,
959 sample,
960 } => {
961 if lateral || sample.is_some() {
962 return no("derived_option", "LATERAL / TABLESAMPLE");
963 }
964 let alias = alias_name(alias)?.ok_or_else(|| ReadError::NotInIr {
965 construct: "unaliased_subquery".into(),
966 detail: "PostgreSQL requires an alias on a FROM subquery".into(),
967 })?;
968 Ok(TableRef::Subquery {
969 query: Box::new(query(*subquery)?),
970 alias,
971 })
972 }
973 other => no("table_factor", &head(&other)),
974 }
975}
976
977fn table_with_joins(t: sp::TableWithJoins) -> R<TableRef> {
978 let sp::TableWithJoins { relation, joins } = t;
979 let mut cur = table_factor(relation)?;
980 for j in joins {
981 let sp::Join {
982 relation,
983 global,
984 join_operator,
985 } = j;
986 if global {
987 return no("global_join", "GLOBAL JOIN");
988 }
989 let (kind, constraint) = match join_operator {
990 sp::JoinOperator::Join(c) | sp::JoinOperator::Inner(c) => (JoinKind::Inner, c),
991 sp::JoinOperator::Left(c) | sp::JoinOperator::LeftOuter(c) => (JoinKind::Left, c),
992 sp::JoinOperator::Right(c) | sp::JoinOperator::RightOuter(c) => (JoinKind::Right, c),
993 sp::JoinOperator::FullOuter(c) => (JoinKind::Full, c),
994 sp::JoinOperator::CrossJoin(c) => (JoinKind::Cross, c),
995 other => return no("join_kind", &head(&other)),
996 };
997 let on = match constraint {
998 sp::JoinConstraint::On(e) => Some(expr(e)?),
999 sp::JoinConstraint::None => None,
1000 sp::JoinConstraint::Using(_) => {
1001 return no("join_using", "JOIN .. USING is not in the IR yet");
1002 }
1003 sp::JoinConstraint::Natural => return no("natural_join", "NATURAL JOIN"),
1004 };
1005 cur = TableRef::Join {
1006 left: Box::new(cur),
1007 right: Box::new(table_factor(relation)?),
1008 kind,
1009 on,
1010 };
1011 }
1012 Ok(cur)
1013}
1014
1015fn simple_table(t: sp::TableWithJoins) -> R<(Name, Option<String>)> {
1016 let sp::TableWithJoins { relation, joins } = t;
1017 if !joins.is_empty() {
1018 return no("dml_target_join", "joins in a DML target");
1019 }
1020 match table_factor(relation)? {
1021 TableRef::Table { name, alias } => Ok((name, alias)),
1022 _ => no("dml_target", "DML target is not a plain table"),
1023 }
1024}
1025
1026fn returning(r: Option<Vec<sp::SelectItem>>) -> R<Vec<SelectItem>> {
1027 r.unwrap_or_default().into_iter().map(select_item).collect()
1028}
1029
1030fn assignments(a: Vec<sp::Assignment>) -> R<Vec<Assignment>> {
1031 let mut out = Vec::new();
1032 for sp::Assignment { target, value } in a {
1033 let column = match target {
1034 sp::AssignmentTarget::ColumnName(n) => single_name(&n)?,
1035 sp::AssignmentTarget::Tuple(_) => return no("tuple_assignment", "SET (a, b) = ..."),
1036 };
1037 out.push(Assignment {
1038 column,
1039 value: expr(value)?,
1040 });
1041 }
1042 Ok(out)
1043}
1044
1045fn insert(i: sp::Insert) -> R<Statement> {
1046 let sp::Insert {
1047 insert_token: _,
1048 optimizer_hints,
1049 or,
1050 ignore,
1051 into: _,
1052 table,
1053 table_alias,
1054 columns,
1055 overwrite,
1056 source,
1057 assignments: asg,
1058 partitioned,
1059 after_columns,
1060 has_table_keyword: _,
1061 on,
1062 returning: ret,
1063 output,
1064 replace_into,
1065 priority,
1066 insert_alias,
1067 settings,
1068 format_clause,
1069 multi_table_insert_type,
1070 multi_table_into_clauses,
1071 multi_table_when_clauses,
1072 multi_table_else_clause,
1073 } = i;
1074 if !optimizer_hints.is_empty()
1075 || or.is_some()
1076 || table_alias.is_some()
1077 || overwrite
1078 || !asg.is_empty()
1079 || partitioned.is_some()
1080 || !after_columns.is_empty()
1081 || output.is_some()
1082 || replace_into
1083 || priority.is_some()
1084 || insert_alias.is_some()
1085 || settings.is_some()
1086 || format_clause.is_some()
1087 || multi_table_insert_type.is_some()
1088 || !multi_table_into_clauses.is_empty()
1089 || !multi_table_when_clauses.is_empty()
1090 || multi_table_else_clause.is_some()
1091 {
1092 return no(
1093 "insert_option",
1094 "an INSERT form other than INSERT INTO t [(cols)] VALUES|query",
1095 );
1096 }
1097 let table = match table {
1098 sp::TableObject::TableName(n) => object_name(&n)?,
1099 other => return no("insert_target", &head(&other)),
1100 };
1101 let columns = columns.iter().map(single_name).collect::<R<Vec<_>>>()?;
1102 let source = match source {
1103 None => InsertSource::DefaultValues,
1104 Some(q) => {
1105 let q = *q;
1106 match *q.body {
1107 sp::SetExpr::Values(v)
1108 if q.with.is_none()
1109 && q.order_by.is_none()
1110 && q.limit_clause.is_none()
1111 && q.locks.is_empty() =>
1112 {
1113 let sp::Values {
1114 explicit_row,
1115 value_keyword: _,
1116 rows,
1117 } = v;
1118 if explicit_row {
1119 return no("explicit_row", "ROW(..) in VALUES");
1120 }
1121 let mut out = Vec::new();
1122 for r in rows {
1123 out.push(r.content.into_iter().map(expr).collect::<R<Vec<_>>>()?);
1124 }
1125 InsertSource::Values(out)
1126 }
1127 body => {
1128 let q2 = sp::Query {
1129 body: Box::new(body),
1130 ..q
1131 };
1132 InsertSource::Query(Box::new(query(q2)?))
1133 }
1134 }
1135 }
1136 };
1137 let on_conflict = match on {
1138 None => None,
1139 Some(sp::OnInsert::OnConflict(oc)) => {
1140 let sp::OnConflict {
1141 conflict_target,
1142 action,
1143 } = oc;
1144 let target = match conflict_target {
1145 None => Vec::new(),
1146 Some(sp::ConflictTarget::Columns(cols)) => cols.iter().map(ident).collect(),
1147 Some(sp::ConflictTarget::OnConstraint(_)) => {
1148 return no("on_conflict_constraint", "ON CONFLICT ON CONSTRAINT");
1149 }
1150 };
1151 let action = match action {
1152 sp::OnConflictAction::DoNothing => ConflictAction::DoNothing,
1153 sp::OnConflictAction::DoUpdate(sp::DoUpdate {
1154 assignments: a,
1155 selection,
1156 }) => ConflictAction::DoUpdate {
1157 set: assignments(a)?,
1158 where_: selection.map(expr).transpose()?,
1159 },
1160 };
1161 Some(OnConflict { target, action })
1162 }
1163 Some(sp::OnInsert::DuplicateKeyUpdate(a)) => {
1164 let set = assignments(a)?
1166 .into_iter()
1167 .map(|asg| Assignment {
1168 column: asg.column,
1169 value: excluded_refs(asg.value),
1170 })
1171 .collect();
1172 Some(OnConflict {
1173 target: vec![],
1174 action: ConflictAction::DoUpdate { set, where_: None },
1175 })
1176 }
1177 Some(other) => return no("on_insert", &head(&other)),
1178 };
1179 let on_conflict = if ignore {
1182 if on_conflict.is_some() {
1183 return no(
1184 "insert_option",
1185 "IGNORE together with ON DUPLICATE KEY UPDATE",
1186 );
1187 }
1188 add_note(
1189 "insert_ignore",
1190 "INSERT IGNORE downgrades every error of the statement to a warning; ON CONFLICT DO NOTHING \
1191 skips unique-key conflicts only",
1192 Allowance::IgnoreCoversOnlyUniqueConflicts,
1193 );
1194 Some(OnConflict {
1195 target: vec![],
1196 action: ConflictAction::DoNothing,
1197 })
1198 } else {
1199 on_conflict
1200 };
1201 Ok(Statement::Insert(Insert {
1202 table,
1203 columns,
1204 source,
1205 on_conflict,
1206 returning: returning(ret)?,
1207 }))
1208}
1209
1210fn update(u: sp::Update) -> R<Statement> {
1211 let sp::Update {
1212 update_token: _,
1213 optimizer_hints,
1214 table,
1215 assignments: asg,
1216 from,
1217 selection,
1218 returning: ret,
1219 output,
1220 or,
1221 order_by,
1222 limit,
1223 } = u;
1224 if !optimizer_hints.is_empty()
1225 || output.is_some()
1226 || or.is_some()
1227 || !order_by.is_empty()
1228 || limit.is_some()
1229 {
1230 return no(
1231 "update_option",
1232 "UPDATE with hints/OUTPUT/OR/ORDER BY/LIMIT",
1233 );
1234 }
1235 let (table, alias) = simple_table(table)?;
1236 let from = match from {
1237 None => Vec::new(),
1238 Some(sp::UpdateTableFromKind::AfterSet(ts)) => ts
1239 .into_iter()
1240 .map(table_with_joins)
1241 .collect::<R<Vec<_>>>()?,
1242 Some(other) => return no("update_from_position", &head(&other)),
1243 };
1244 Ok(Statement::Update(Update {
1245 table,
1246 alias,
1247 set: assignments(asg)?,
1248 from,
1249 where_: selection.map(expr).transpose()?,
1250 returning: returning(ret)?,
1251 }))
1252}
1253
1254fn delete(d: sp::Delete) -> R<Statement> {
1255 let sp::Delete {
1256 delete_token: _,
1257 optimizer_hints,
1258 tables,
1259 from,
1260 using,
1261 selection,
1262 returning: ret,
1263 output,
1264 order_by,
1265 limit,
1266 } = d;
1267 if !optimizer_hints.is_empty()
1268 || !tables.is_empty()
1269 || output.is_some()
1270 || !order_by.is_empty()
1271 || limit.is_some()
1272 {
1273 return no(
1274 "delete_option",
1275 "DELETE with hints/multi-table/OUTPUT/ORDER BY/LIMIT",
1276 );
1277 }
1278 let ts = match from {
1279 sp::FromTable::WithFromKeyword(ts) | sp::FromTable::WithoutKeyword(ts) => ts,
1280 };
1281 if ts.len() != 1 {
1282 return no("delete_multi_target", "DELETE FROM a, b");
1283 }
1284 let (table, alias) = simple_table(ts.into_iter().next().unwrap())?;
1285 let using = using
1286 .unwrap_or_default()
1287 .into_iter()
1288 .map(table_with_joins)
1289 .collect::<R<Vec<_>>>()?;
1290 Ok(Statement::Delete(Delete {
1291 table,
1292 alias,
1293 using,
1294 where_: selection.map(expr).transpose()?,
1295 returning: returning(ret)?,
1296 }))
1297}
1298
1299fn boxed(e: sp::Expr) -> R<Box<Expr>> {
1300 Ok(Box::new(expr(e)?))
1301}
1302
1303fn expr(e: sp::Expr) -> R<Expr> {
1304 use sp::Expr as E;
1305 Ok(match e {
1306 E::Identifier(i) => Expr::Column(Name::simple(ident(&i))),
1307 E::CompoundIdentifier(parts) => Expr::Column(Name(parts.iter().map(ident).collect())),
1308 E::Value(v) => value(v.value)?,
1309 E::Nested(inner) => Expr::Nested(boxed(*inner)?),
1310 E::IsNull(x) => Expr::IsNull {
1311 expr: boxed(*x)?,
1312 negated: false,
1313 },
1314 E::IsNotNull(x) => Expr::IsNull {
1315 expr: boxed(*x)?,
1316 negated: true,
1317 },
1318 E::InList {
1319 expr: x,
1320 list,
1321 negated,
1322 } => Expr::InList {
1323 expr: boxed(*x)?,
1324 list: list.into_iter().map(expr).collect::<R<_>>()?,
1325 negated,
1326 },
1327 E::InSubquery {
1328 expr: x,
1329 subquery,
1330 negated,
1331 } => Expr::InSubquery {
1332 expr: boxed(*x)?,
1333 query: Box::new(query(*subquery)?),
1334 negated,
1335 },
1336 E::Between {
1337 expr: x,
1338 negated,
1339 low,
1340 high,
1341 } => Expr::Between {
1342 expr: boxed(*x)?,
1343 low: boxed(*low)?,
1344 high: boxed(*high)?,
1345 negated,
1346 },
1347 E::Like {
1348 negated,
1349 any,
1350 expr: x,
1351 pattern,
1352 escape_char,
1353 } => {
1354 if any {
1355 return no("like_option", "LIKE ANY");
1356 }
1357 let ci = rules().like_ci_default;
1358 if ci {
1359 add_note(
1360 "like",
1361 "the source LIKE is ASCII case-insensitive only while its connection setting is at the default",
1362 Allowance::LikeCaseSensitivityIsConnectionSetting,
1363 );
1364 }
1365 if rules().name == "mysql" {
1366 add_note(
1367 "like",
1368 "the source LIKE follows the column collation; read as case-sensitive (a binary collation)",
1369 Allowance::LikeCaseSensitivityIsConnectionSetting,
1370 );
1371 }
1372 like_expr(*x, *pattern, escape_char, negated, ci)?
1373 }
1374 E::ILike {
1375 negated,
1376 any,
1377 expr: x,
1378 pattern,
1379 escape_char,
1380 } => {
1381 if any {
1382 return no("like_option", "ILIKE ANY");
1383 }
1384 like_expr(*x, *pattern, escape_char, negated, true)?
1385 }
1386 E::UnaryOp { op, expr: x } => {
1387 let op = match op {
1388 sp::UnaryOperator::Not => UnaryOp::Not,
1389 sp::UnaryOperator::Minus => UnaryOp::Minus,
1390 sp::UnaryOperator::Plus => UnaryOp::Plus,
1391 other => return no("unary_operator", &head(&other)),
1392 };
1393 Expr::Unary {
1394 op,
1395 expr: boxed(*x)?,
1396 }
1397 }
1398 E::BinaryOp { left, op, right } => {
1399 use sp::BinaryOperator as B;
1400 let op = match op {
1401 B::Eq => BinaryOp::Eq,
1402 B::NotEq => BinaryOp::NotEq,
1403 B::Lt => BinaryOp::Lt,
1404 B::LtEq => BinaryOp::LtEq,
1405 B::Gt => BinaryOp::Gt,
1406 B::GtEq => BinaryOp::GtEq,
1407 B::And => BinaryOp::And,
1408 B::Or => BinaryOp::Or,
1409 B::Plus => BinaryOp::Plus,
1410 B::Minus => BinaryOp::Minus,
1411 B::Multiply => BinaryOp::Multiply,
1412 B::Divide => BinaryOp::Divide,
1413 B::Modulo => BinaryOp::Modulo,
1414 B::StringConcat if rules().name == "mysql" => {
1418 return no(
1419 "pipes_operator",
1420 "MySQL `||` is logical OR (default sql_mode) but the parser gives it concatenation precedence; write OR, or CONCAT()",
1421 );
1422 }
1423 B::StringConcat => BinaryOp::Concat,
1424 B::MyIntegerDivide => BinaryOp::IntDivide,
1425 B::Spaceship => BinaryOp::NullSafeEq,
1426 B::Arrow => BinaryOp::JsonGet,
1427 B::LongArrow => BinaryOp::JsonGetText,
1428 other => {
1429 return no(
1430 "binary_operator",
1431 &format!(
1432 "{} (JSON/array/regex/bitwise operators are not in the IR)",
1433 head(&other)
1434 ),
1435 );
1436 }
1437 };
1438 if op == BinaryOp::Divide && rules().name == "mysql" {
1439 add_note(
1440 "division",
1441 "the source `/` is exact (DECIMAL) division; the target divides as double precision",
1442 Allowance::DecimalDivisionAsFloat,
1443 );
1444 let l = Expr::Cast {
1445 expr: boxed(*left)?,
1446 ty: SqlType::Double,
1447 };
1448 return Ok(Expr::Binary {
1449 op,
1450 left: Box::new(l),
1451 right: boxed(*right)?,
1452 });
1453 }
1454 match (op, *left, *right) {
1458 (BinaryOp::Plus | BinaryOp::Minus, l, E::Interval(iv)) => Expr::Binary {
1459 op,
1460 left: boxed(l)?,
1461 right: Box::new(interval_call(iv)?),
1462 },
1463 (BinaryOp::Plus, E::Interval(iv), r) => Expr::Binary {
1464 op,
1465 left: boxed(r)?,
1466 right: Box::new(interval_call(iv)?),
1467 },
1468 (op, l, r) => Expr::Binary {
1469 op,
1470 left: boxed(l)?,
1471 right: boxed(r)?,
1472 },
1473 }
1474 }
1475 E::Case {
1476 case_token: _,
1477 end_token: _,
1478 operand,
1479 conditions,
1480 else_result,
1481 } => Expr::Case {
1482 operand: operand.map(|o| boxed(*o)).transpose()?,
1483 branches: conditions
1484 .into_iter()
1485 .map(|sp::CaseWhen { condition, result }| Ok((expr(condition)?, expr(result)?)))
1486 .collect::<R<_>>()?,
1487 else_: else_result.map(|o| boxed(*o)).transpose()?,
1488 },
1489 E::Cast {
1490 kind,
1491 expr: x,
1492 data_type,
1493 format,
1494 } => {
1495 if format.is_some() || !matches!(kind, sp::CastKind::Cast | sp::CastKind::DoubleColon) {
1496 return no("cast_form", "TRY_CAST / SAFE_CAST / CAST .. FORMAT");
1497 }
1498 Expr::Cast {
1499 expr: boxed(*x)?,
1500 ty: data_type_to_ir(&data_type)?,
1501 }
1502 }
1503 E::Trim {
1504 trim_where,
1505 trim_what,
1506 expr: x,
1507 trim_characters,
1508 } => {
1509 let side = match trim_where {
1511 None | Some(sp::TrimWhereField::Both) => TrimSide::Both,
1512 Some(sp::TrimWhereField::Leading) => TrimSide::Leading,
1513 Some(sp::TrimWhereField::Trailing) => TrimSide::Trailing,
1514 };
1515 let chars = match (trim_what, trim_characters) {
1516 (Some(w), None) => Some(boxed(*w)?),
1517 (None, None) => None,
1518 (None, Some(mut cs)) if cs.len() == 1 => Some(boxed(cs.remove(0))?),
1519 _ => return no("trim_form", "TRIM with several character arguments"),
1520 };
1521 Expr::Trim {
1522 expr: boxed(*x)?,
1523 side,
1524 chars,
1525 }
1526 }
1527 E::Substring {
1528 expr: x,
1529 substring_from,
1530 substring_for,
1531 special: _,
1532 shorthand: _,
1533 } => {
1534 if rules().name == "mysql" {
1535 let positive = |e: &E, min: u64| {
1538 matches!(
1539 e,
1540 E::Value(v) if matches!(&v.value, sp::Value::Number(n, _)
1541 if n.parse::<u64>().is_ok_and(|x| x >= min))
1542 )
1543 };
1544 let start_ok = substring_from.as_deref().is_some_and(|f| positive(f, 1));
1545 let len_ok = substring_for.as_deref().is_none_or(|l| positive(l, 0));
1546 if !(start_ok && len_ok) {
1547 add_note(
1548 "fn:substr.start_position",
1549 "MySQL SUBSTRING counts a start of 0 as empty and a negative start from the end; \
1550 the target does not",
1551 Allowance::SubstringStartAssumedPositive,
1552 );
1553 }
1554 }
1555 let mut args = vec![FunctionArg::Expr(expr(*x)?)];
1557 match (substring_from, substring_for) {
1558 (Some(f), Some(l)) => {
1559 args.push(FunctionArg::Expr(expr(*f)?));
1560 args.push(FunctionArg::Expr(expr(*l)?));
1561 }
1562 (Some(f), None) => args.push(FunctionArg::Expr(expr(*f)?)),
1563 (None, Some(l)) => {
1564 args.push(FunctionArg::Expr(Expr::Literal(Literal::Number(
1566 "1".into(),
1567 ))));
1568 args.push(FunctionArg::Expr(expr(*l)?));
1569 }
1570 (None, None) => return no("substring_form", "SUBSTRING without FROM or FOR"),
1571 }
1572 Expr::Function(Function {
1573 name: "substr".into(),
1574 args,
1575 distinct: false,
1576 star: false,
1577 filter: None,
1578 over: None,
1579 order_by: vec![],
1580 })
1581 }
1582 E::AnyOp {
1583 left,
1584 compare_op,
1585 right,
1586 is_some: _,
1587 } => {
1588 if !matches!(compare_op, sp::BinaryOperator::Eq) {
1589 return no("any_operator", "only `= ANY(..)` is in the IR");
1590 }
1591 quantified(*left, *right, false)?
1592 }
1593 E::AllOp {
1594 left,
1595 compare_op,
1596 right,
1597 } => {
1598 if !matches!(compare_op, sp::BinaryOperator::NotEq) {
1599 return no("all_operator", "only `<> ALL(..)` is in the IR");
1600 }
1601 quantified(*left, *right, true)?
1602 }
1603 E::Exists { subquery, negated } => Expr::Exists {
1604 query: Box::new(query(*subquery)?),
1605 negated,
1606 },
1607 E::Subquery(q) => Expr::Subquery(Box::new(query(*q)?)),
1608 E::Extract {
1609 field,
1610 syntax: _,
1611 expr: x,
1612 } => Expr::Extract {
1613 field: field.to_string().to_ascii_lowercase(),
1614 expr: boxed(*x)?,
1615 },
1616 E::Array(arr) => Expr::ArrayLiteral(arr.elem.into_iter().map(expr).collect::<R<Vec<_>>>()?),
1617 E::AtTimeZone {
1618 timestamp,
1619 time_zone,
1620 } => Expr::AtTimeZone {
1621 expr: boxed(*timestamp)?,
1622 zone: boxed(*time_zone)?,
1623 },
1624 E::Function(f) => function(f)?,
1625 other => return no("expression", &head(&other)),
1626 })
1627}
1628
1629fn quantified(left: sp::Expr, right: sp::Expr, negated: bool) -> R<Expr> {
1631 let lhs = boxed(left)?;
1632 let mut right = right;
1634 if let sp::Expr::Cast {
1635 kind: sp::CastKind::Cast | sp::CastKind::DoubleColon,
1636 expr: inner,
1637 data_type: sp::DataType::Array(_),
1638 format: None,
1639 } = right.clone()
1640 {
1641 right = *inner;
1642 }
1643 match right {
1644 sp::Expr::Value(v) => match v.value {
1645 sp::Value::Placeholder(_) => match value(v.value)? {
1646 Expr::Param(n) => Ok(Expr::AnyParam {
1647 expr: lhs,
1648 param: n,
1649 negated,
1650 }),
1651 _ => no("array_param", "expected a numbered parameter"),
1652 },
1653 other => no("array_operand", &head(&other)),
1654 },
1655 sp::Expr::Array(sp::Array { elem, named: _ }) => Ok(Expr::InList {
1656 expr: lhs,
1657 list: elem.into_iter().map(expr).collect::<R<Vec<_>>>()?,
1658 negated,
1659 }),
1660 sp::Expr::Subquery(q) => Ok(Expr::InSubquery {
1661 expr: lhs,
1662 query: Box::new(query(*q)?),
1663 negated,
1664 }),
1665 other => no("array_operand", &head(&other)),
1666 }
1667}
1668
1669fn excluded_refs(e: Expr) -> Expr {
1671 match e {
1672 Expr::Function(f) if f.name == "values" && f.args.len() == 1 => {
1673 match f.args.into_iter().next() {
1674 Some(FunctionArg::Expr(Expr::Column(n))) => {
1675 Expr::Column(Name(vec!["excluded".to_string(), n.last().to_string()]))
1676 }
1677 other => Expr::Function(Function {
1678 name: "values".into(),
1679 args: other.into_iter().collect(),
1680 distinct: false,
1681 star: false,
1682 filter: None,
1683 over: None,
1684 order_by: vec![],
1685 }),
1686 }
1687 }
1688 Expr::Binary { op, left, right } => Expr::Binary {
1689 op,
1690 left: Box::new(excluded_refs(*left)),
1691 right: Box::new(excluded_refs(*right)),
1692 },
1693 Expr::Nested(x) => Expr::Nested(Box::new(excluded_refs(*x))),
1694 Expr::Unary { op, expr } => Expr::Unary {
1695 op,
1696 expr: Box::new(excluded_refs(*expr)),
1697 },
1698 other => other,
1699 }
1700}
1701
1702fn like_expr(
1703 x: sp::Expr,
1704 pattern: sp::Expr,
1705 escape_char: Option<Box<sp::Expr>>,
1706 negated: bool,
1707 case_insensitive: bool,
1708) -> R<Expr> {
1709 let escape = match escape_char {
1710 Some(e) => Some(boxed(*e)?),
1711 None if rules().like_default_escape => {
1712 Some(Box::new(Expr::Literal(Literal::String("\\".into()))))
1713 }
1714 None => None,
1715 };
1716 Ok(Expr::Like {
1717 expr: boxed(x)?,
1718 pattern: boxed(pattern)?,
1719 negated,
1720 case_insensitive,
1721 escape,
1722 })
1723}
1724
1725fn value(v: sp::Value) -> R<Expr> {
1726 Ok(match v {
1727 sp::Value::Number(n, long) => {
1728 if long {
1729 return no("numeric_suffix", "L suffix");
1730 }
1731 Expr::Literal(Literal::Number(n))
1732 }
1733 sp::Value::SingleQuotedString(s) => Expr::Literal(Literal::String(s)),
1734 sp::Value::EscapedStringLiteral(s) => Expr::Literal(Literal::String(s)),
1736 sp::Value::Boolean(b) if rules().name == "mysql" => {
1737 Expr::Literal(Literal::Number(if b { "1" } else { "0" }.into()))
1739 }
1740 sp::Value::Boolean(b) => Expr::Literal(Literal::Bool(b)),
1741 sp::Value::Null => Expr::Literal(Literal::Null),
1742 sp::Value::Placeholder(p) => {
1743 let bad = || ReadError::NotInIr {
1744 construct: "placeholder".into(),
1745 detail: format!(
1746 "`{p}` is not a placeholder form of dialect `{}`",
1747 rules().name
1748 ),
1749 };
1750 if rules().question_placeholders {
1751 if p == "?" {
1752 Expr::Param(bump_param(None, None))
1753 } else if let Some(d) = p.strip_prefix('?') {
1754 let n = d.parse::<u32>().ok().filter(|n| *n >= 1).ok_or_else(bad)?;
1755 Expr::Param(bump_param(Some(n), None))
1756 } else if let Some(nm) = p.strip_prefix([':', '@', '$']) {
1757 Expr::Param(bump_param(None, Some(nm)))
1758 } else {
1759 return Err(bad());
1760 }
1761 } else {
1762 let n = p
1763 .strip_prefix('$')
1764 .and_then(|d| d.parse::<u32>().ok())
1765 .filter(|n| *n >= 1)
1766 .ok_or_else(bad)?;
1767 Expr::Param(n)
1768 }
1769 }
1770 other => return no("literal", &head(&other)),
1771 })
1772}
1773
1774fn function(f: sp::Function) -> R<Expr> {
1775 let sp::Function {
1776 name,
1777 uses_odbc_syntax,
1778 parameters,
1779 args,
1780 within_group,
1781 filter,
1782 null_treatment,
1783 over,
1784 } = f;
1785 if uses_odbc_syntax
1786 || !matches!(parameters, sp::FunctionArguments::None)
1787 || !within_group.is_empty()
1788 || null_treatment.is_some()
1789 {
1790 return no(
1791 "function_option",
1792 "ODBC syntax/parameters/WITHIN GROUP/FILTER/IGNORE NULLS",
1793 );
1794 }
1795 let over = match over {
1796 None => None,
1797 Some(sp::WindowType::NamedWindow(_)) => {
1798 return no(
1799 "named_window",
1800 "OVER w (a WINDOW clause) is not in the IR yet",
1801 );
1802 }
1803 Some(sp::WindowType::WindowSpec(spec)) => Some(window_spec(spec)?),
1804 };
1805 let filter = filter.map(|f| expr(*f).map(Box::new)).transpose()?;
1806 let name = object_name(&name)?;
1807 let base = name.last().to_string();
1808 if name.0.len() != 1 {
1809 return no("qualified_function", &name.0.join("."));
1810 }
1811 let args = match args {
1812 sp::FunctionArguments::None => {
1814 if base == "current_timestamp" {
1815 if rules().name == "sqlite" {
1816 add_note(
1817 "fn:now",
1818 "the source CURRENT_TIMESTAMP is text 'YYYY-MM-DD HH:MM:SS' (UTC), not a timestamptz",
1819 Allowance::NowAsText,
1820 );
1821 }
1822 return Ok(Expr::Function(Function {
1823 name: "now".to_string(),
1824 args: vec![],
1825 distinct: false,
1826 star: false,
1827 filter: None,
1828 over: None,
1829 order_by: vec![],
1830 }));
1831 }
1832 if matches!(
1834 base.as_str(),
1835 "current_user" | "session_user" | "current_role"
1836 ) {
1837 return Ok(Expr::Function(Function {
1838 name: base,
1839 args: vec![],
1840 distinct: false,
1841 star: false,
1842 filter: None,
1843 over: None,
1844 order_by: vec![],
1845 }));
1846 }
1847 if base == "current_date" {
1850 if rules().name == "sqlite" {
1851 add_note(
1852 "fn:current_date",
1853 "the source CURRENT_DATE is text 'YYYY-MM-DD' (the UTC date), not a date",
1854 Allowance::DateAsText,
1855 );
1856 }
1857 return Ok(Expr::Function(Function {
1858 name: "current_date".to_string(),
1859 args: vec![],
1860 distinct: false,
1861 star: false,
1862 filter: None,
1863 over: None,
1864 order_by: vec![],
1865 }));
1866 }
1867 return no("bare_function", &format!("`{base}` without parentheses"));
1868 }
1869 sp::FunctionArguments::Subquery(q) if base == "array" => {
1870 return Ok(Expr::ArraySubquery(Box::new(query(*q)?)));
1871 }
1872 sp::FunctionArguments::Subquery(_) => {
1873 return no("function_subquery", "function over a subquery");
1874 }
1875 sp::FunctionArguments::List(l) => l,
1876 };
1877 let sp::FunctionArgumentList {
1878 duplicate_treatment,
1879 args,
1880 clauses,
1881 } = args;
1882 let mut agg_order = Vec::new();
1883 let mut separator: Option<String> = None;
1884 for cl in clauses {
1885 match cl {
1886 sp::FunctionArgumentClause::OrderBy(items) => {
1887 for it in items {
1888 agg_order.push(order_item(it)?);
1889 }
1890 }
1891 sp::FunctionArgumentClause::Separator(v) => match v.value {
1892 sp::Value::SingleQuotedString(s) => separator = Some(s),
1893 other => return no("separator", &head(&other)),
1894 },
1895 other => {
1896 return no(
1897 "function_clause",
1898 &format!("{} inside a function call", head(&other)),
1899 );
1900 }
1901 }
1902 }
1903 let distinct = match duplicate_treatment {
1904 None | Some(sp::DuplicateTreatment::All) => false,
1905 Some(sp::DuplicateTreatment::Distinct) => true,
1906 };
1907 let mut out = Vec::new();
1908 let mut star = false;
1909 for a in args {
1910 match a {
1911 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Expr(e)) => {
1912 out.push(FunctionArg::Expr(expr(e)?))
1913 }
1914 sp::FunctionArg::Unnamed(sp::FunctionArgExpr::Wildcard) => star = true,
1915 sp::FunctionArg::Named {
1916 name,
1917 arg: sp::FunctionArgExpr::Expr(e),
1918 operator: sp::FunctionArgOperator::RightArrow,
1919 } => out.push(FunctionArg::Named {
1920 name: ident(&name),
1921 value: expr(e)?,
1922 }),
1923 sp::FunctionArg::ExprNamed {
1925 name: sp::Expr::Identifier(name),
1926 arg: sp::FunctionArgExpr::Expr(e),
1927 operator: sp::FunctionArgOperator::RightArrow,
1928 } => out.push(FunctionArg::Named {
1929 name: ident(&name),
1930 value: expr(e)?,
1931 }),
1932 other => return no("function_argument", &head(&other)),
1933 }
1934 }
1935 let mut name = base;
1936 if name == "current_timestamp" {
1937 name = "now".to_string();
1938 }
1939 if rules().name == "mysql" {
1940 match name.as_str() {
1941 "ifnull" => name = "coalesce".to_string(),
1942 "group_concat" => {
1943 name = "string_agg".to_string();
1944 out.push(FunctionArg::Expr(Expr::Literal(Literal::String(
1945 separator.clone().unwrap_or_else(|| ",".to_string()),
1946 ))));
1947 add_note(
1948 "fn:string_agg",
1949 "the source GROUP_CONCAT truncates silently at group_concat_max_len",
1950 Allowance::AggregateStringTruncation,
1951 );
1952 }
1953 "length" => name = "octet_length".to_string(),
1955 "concat" if out.len() >= 2 => {
1956 let mut it = out.into_iter().map(|a| match a {
1957 FunctionArg::Expr(e) => e,
1958 FunctionArg::Named { value, .. } => value,
1959 });
1960 let first = it.next().expect("two arguments");
1961 let chain = it.fold(first, |l, r| Expr::Binary {
1962 op: BinaryOp::Concat,
1963 left: Box::new(l),
1964 right: Box::new(r),
1965 });
1966 return Ok(chain);
1967 }
1968 "if" if out.len() == 3 => {
1969 let mut it = out.into_iter().map(|a| match a {
1970 FunctionArg::Expr(e) => e,
1971 FunctionArg::Named { value, .. } => value,
1972 });
1973 let (c, t, f) = (it.next().unwrap(), it.next().unwrap(), it.next().unwrap());
1974 return Ok(Expr::Case {
1975 operand: None,
1976 branches: vec![(c, t)],
1977 else_: Some(Box::new(f)),
1978 });
1979 }
1980 _ => {}
1981 }
1982 }
1983 if rules().name == "sqlite" {
1984 match name.as_str() {
1985 "ifnull" => name = "coalesce".to_string(),
1986 "group_concat" => {
1987 name = "string_agg".to_string();
1988 if out.len() == 1 {
1989 out.push(FunctionArg::Expr(Expr::Literal(Literal::String(
1990 ",".into(),
1991 ))));
1992 }
1993 }
1994 _ => {}
1995 }
1996 }
1997 Ok(Expr::Function(Function {
1998 name,
1999 args: out,
2000 distinct,
2001 star,
2002 filter,
2003 over,
2004 order_by: agg_order,
2005 }))
2006}
2007
2008fn interval_call(iv: sp::Interval) -> R<Expr> {
2013 let sp::Interval {
2014 value,
2015 leading_field,
2016 leading_precision,
2017 last_field,
2018 fractional_seconds_precision,
2019 } = iv;
2020 if leading_precision.is_some() || last_field.is_some() || fractional_seconds_precision.is_some()
2021 {
2022 return no(
2023 "interval",
2024 "interval ranges and precisions are not in the IR",
2025 );
2026 }
2027 let (amount, unit) = match (*value, leading_field) {
2028 (sp::Expr::Value(v), Some(field)) => (
2030 match v.value {
2031 sp::Value::Number(n, _) => n,
2032 other => return no("interval", &head(&other)),
2033 },
2034 field.to_string(),
2035 ),
2036 (sp::Expr::Value(v), None) => match v.value {
2038 sp::Value::SingleQuotedString(s) => {
2039 let mut it = s.split_whitespace();
2040 match (it.next(), it.next(), it.next()) {
2041 (Some(a), Some(u), None) => (a.to_string(), u.to_string()),
2042 _ => {
2043 return no(
2044 "interval",
2045 "only a single `<number> <unit>` interval is in the IR",
2046 );
2047 }
2048 }
2049 }
2050 other => return no("interval", &head(&other)),
2051 },
2052 (other, _) => return no("interval", &head(&other)),
2053 };
2054 let numeric = !amount.is_empty()
2055 && amount
2056 .trim_start_matches('-')
2057 .chars()
2058 .all(|c| c.is_ascii_digit() || c == '.')
2059 && amount
2060 .trim_start_matches('-')
2061 .chars()
2062 .any(|c| c.is_ascii_digit());
2063 if !numeric {
2064 return no("interval", "the interval amount is not a plain number");
2065 }
2066 let unit = match unit.to_ascii_lowercase().trim_end_matches('s') {
2067 "second" | "sec" => "secs",
2068 "minute" | "min" => "mins",
2069 "hour" => "hours",
2070 "day" => "days",
2071 "week" => "weeks",
2072 "month" | "mon" => "months",
2073 "year" => "years",
2074 other => return no("interval", &format!("unit `{other}` is not in the IR")),
2075 };
2076 Ok(Expr::Function(Function {
2077 name: "make_interval".to_string(),
2078 args: vec![FunctionArg::Named {
2079 name: unit.to_string(),
2080 value: Expr::Literal(Literal::Number(amount)),
2081 }],
2082 distinct: false,
2083 star: false,
2084 filter: None,
2085 over: None,
2086 order_by: vec![],
2087 }))
2088}
2089
2090fn window_spec(w: sp::WindowSpec) -> R<Window> {
2091 let sp::WindowSpec {
2092 window_name,
2093 partition_by,
2094 order_by,
2095 window_frame,
2096 } = w;
2097 if window_name.is_some() {
2098 return no("named_window", "OVER (w ..) refinement of a named window");
2099 }
2100 let frame = match window_frame {
2101 None => None,
2102 Some(sp::WindowFrame {
2103 units,
2104 start_bound,
2105 end_bound,
2106 }) => {
2107 let bound = |b: sp::WindowFrameBound| -> R<FrameBound> {
2108 Ok(match b {
2109 sp::WindowFrameBound::CurrentRow => FrameBound::CurrentRow,
2110 sp::WindowFrameBound::Preceding(e) => {
2111 FrameBound::Preceding(e.map(|x| expr(*x).map(Box::new)).transpose()?)
2112 }
2113 sp::WindowFrameBound::Following(e) => {
2114 FrameBound::Following(e.map(|x| expr(*x).map(Box::new)).transpose()?)
2115 }
2116 })
2117 };
2118 Some(Frame {
2119 units: match units {
2120 sp::WindowFrameUnits::Rows => FrameUnits::Rows,
2121 sp::WindowFrameUnits::Range => FrameUnits::Range,
2122 sp::WindowFrameUnits::Groups => FrameUnits::Groups,
2123 },
2124 start: bound(start_bound)?,
2125 end: end_bound.map(bound).transpose()?,
2126 })
2127 }
2128 };
2129 Ok(Window {
2130 partition_by: partition_by.into_iter().map(expr).collect::<R<_>>()?,
2131 order_by: order_by.into_iter().map(order_item).collect::<R<_>>()?,
2132 frame,
2133 })
2134}
2135
2136fn data_type_to_ir(t: &sp::DataType) -> R<SqlType> {
2137 use sp::DataType as D;
2138 Ok(match t {
2139 D::SmallInt(_) | D::Int2(_) => SqlType::SmallInt,
2140 D::Int(_) | D::Integer(_) | D::Int4(_) => SqlType::Integer,
2141 D::BigInt(_) | D::Int8(_) => SqlType::BigInt,
2142 D::Real | D::Float4 => SqlType::Real,
2143 D::Double(_) | D::DoublePrecision | D::Float8 => SqlType::Double,
2144 D::Numeric(info) | D::Decimal(info) => match info {
2145 sp::ExactNumberInfo::None => SqlType::Numeric {
2146 precision: None,
2147 scale: None,
2148 },
2149 sp::ExactNumberInfo::Precision(p) => SqlType::Numeric {
2150 precision: Some(*p as u32),
2151 scale: None,
2152 },
2153 sp::ExactNumberInfo::PrecisionAndScale(p, s) => SqlType::Numeric {
2154 precision: Some(*p as u32),
2155 scale: Some(*s as u32),
2156 },
2157 },
2158 D::Text => SqlType::Text,
2159 D::Signed | D::SignedInteger | D::Unsigned | D::UnsignedInteger => SqlType::BigInt,
2160 D::Char(None) | D::Character(None) => SqlType::Text,
2161 D::Datetime(None) => SqlType::Timestamp { with_tz: false },
2162 D::Varchar(None) | D::CharVarying(None) => SqlType::Varchar(None),
2163 D::Varchar(Some(sp::CharacterLength::IntegerLength { length, unit: None }))
2164 | D::CharVarying(Some(sp::CharacterLength::IntegerLength { length, unit: None })) => {
2165 SqlType::Varchar(Some(*length as u32))
2166 }
2167 D::Boolean | D::Bool => SqlType::Boolean,
2168 D::Date => SqlType::Date,
2169 D::Timestamp(None, tz) => SqlType::Timestamp {
2170 with_tz: match tz {
2171 sp::TimezoneInfo::None | sp::TimezoneInfo::WithoutTimeZone => false,
2172 sp::TimezoneInfo::WithTimeZone | sp::TimezoneInfo::Tz => true,
2173 },
2174 },
2175 D::Uuid => SqlType::Uuid,
2176 D::Bytea => SqlType::Bytes,
2177 D::JSON => SqlType::Json,
2178 D::JSONB => SqlType::Jsonb,
2179 other => return no("data_type", &format!("{other}")),
2180 })
2181}