Skip to main content

unisql_sqlparser_adapter/
lib.rs

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