Skip to main content

sqlparser_canonicalize/
canonicalize.rs

1use alloc::boxed::Box;
2use alloc::format;
3use alloc::string::{String, ToString};
4use alloc::vec;
5use alloc::vec::Vec;
6use core::hash::{Hash, Hasher};
7
8use seahash::SeaHasher;
9use sqlparser::ast::{
10    AccessExpr, BinaryOperator, CastKind, CeilFloorKind, DateTimeField, Distinct, Expr,
11    ExtractSyntax, Function, FunctionArg, FunctionArgExpr, FunctionArguments, GroupByExpr, Ident,
12    Interval, LimitClause, ObjectName, Query, Select, SelectItem, SelectModifiers, SetExpr,
13    Statement, Subscript, TableAlias, TableFactor, UnaryOperator, Value,
14};
15use sqlparser::dialect::{AnsiDialect, Dialect, MySqlDialect, PostgreSqlDialect, SQLiteDialect};
16use sqlparser::keywords::ALL_KEYWORDS;
17use sqlparser::parser::Parser;
18use sqlparser::tokenizer::Token;
19
20use crate::CanonicalizeError;
21
22const MAX_EXPR_DEPTH: usize = 128;
23const MAX_SQL_LEN: usize = 8192;
24
25impl<'a> Canonicalizer<'a> {
26    /// Parses one `SELECT` and returns canonical text for its `WHERE` clause.
27    ///
28    /// The canonical text is verified to survive being read back as itself, so a predicate
29    /// whose canonical form would read as something else is rejected instead of hashed.
30    pub fn normalize_sql(&self, sql: &str) -> Result<String, CanonicalizeError> {
31        let canonical = normalize_sql_inner(sql, self)?;
32        confirm_reads_back_as_itself(canonical, self)
33    }
34
35    /// Canonicalizes a parsed `WHERE` clause in O(n log n) time for boolean chains and O(n)
36    /// otherwise.
37    ///
38    /// `where_expr` MUST have been parsed with this canonicalizer's dialect, which decides
39    /// how names fold and which reads the canonical text back to check it.
40    pub fn normalize_where_clause(
41        &self,
42        where_expr: Option<&Expr>,
43    ) -> Result<String, CanonicalizeError> {
44        let canonical = normalize_where_clause_inner(where_expr, self)?;
45        confirm_reads_back_as_itself(canonical, self)
46    }
47}
48
49fn normalize_sql_inner(
50    sql: &str,
51    context: &Canonicalizer<'_>,
52) -> Result<String, CanonicalizeError> {
53    if sql.len() > MAX_SQL_LEN {
54        return Err(CanonicalizeError::InputTooLong { limit: MAX_SQL_LEN });
55    }
56    check_sql_sanity(sql)?;
57
58    let statements = Parser::parse_sql(context.dialect, sql)
59        .map_err(|error| CanonicalizeError::Parse(error.to_string()))?;
60    let [statement] = statements.as_slice() else {
61        return Err(CanonicalizeError::Unsupported(
62            "Expected exactly one SELECT statement".to_string(),
63        ));
64    };
65    let where_expr = extract_where(statement)?;
66    normalize_where_clause_inner(where_expr, context)
67}
68
69fn normalize_where_clause_inner(
70    where_expr: Option<&Expr>,
71    context: &Canonicalizer<'_>,
72) -> Result<String, CanonicalizeError> {
73    match where_expr {
74        Some(expr) => normalize_expr_inner(expr, 0, false, context),
75        // A missing filter keeps every row. Where `TRUE` is reserved it is spelled as
76        // `WHERE TRUE`. Elsewhere a bare `TRUE` may name a column, as in SQLite, so it is spelled
77        // as `WHERE 1 = 1`, which keeps every row in every dialect.
78        None => {
79            let every_row = if context.true_is_reserved {
80                Expr::Value(Value::Boolean(true).with_empty_span())
81            } else {
82                let one = || {
83                    Box::new(Expr::Value(
84                        Value::Number("1".into(), false).with_empty_span(),
85                    ))
86                };
87                Expr::BinaryOp {
88                    left: one(),
89                    op: BinaryOperator::Eq,
90                    right: one(),
91                }
92            };
93            normalize_expr_inner(&every_row, 0, false, context)
94        }
95    }
96}
97
98/// Returns the canonical text only if reading it back as an expression reproduces it byte
99/// for byte.
100///
101/// Canonical text is a hash key, so text that reads back as a different predicate would
102/// give two distinct predicates one hash. An expression re-read checks the whole rendering
103/// because a statement around it adds only scaffolding no caller sent.
104fn confirm_reads_back_as_itself(
105    canonical: String,
106    context: &Canonicalizer<'_>,
107) -> Result<String, CanonicalizeError> {
108    if read_back(&canonical, context).is_some_and(|again| again == canonical) {
109        Ok(canonical)
110    } else {
111        Err(CanonicalizeError::NotRoundTrippable(canonical))
112    }
113}
114
115/// Reads `canonical` back as one complete expression and returns its normalised text.
116fn read_back(canonical: &str, context: &Canonicalizer<'_>) -> Option<String> {
117    let mut parser = Parser::new(context.dialect).try_with_sql(canonical).ok()?;
118    let expr = parser.parse_expr().ok()?;
119    // parse_expr takes a leading expression and stops, so require nothing to trail it.
120    if !matches!(parser.peek_token_ref().token, Token::EOF) {
121        return None;
122    }
123    normalize_expr_inner(&expr, 0, false, context).ok()
124}
125
126/// Returns the stable 128-bit SeaHash value for canonical text.
127#[must_use]
128pub fn hash_canonical(normalized: &str) -> u128 {
129    let mut first = SeaHasher::new();
130    normalized.hash(&mut first);
131    let first = first.finish();
132
133    let mut second = SeaHasher::with_seeds(
134        first,
135        first.wrapping_add(1),
136        first.wrapping_add(2),
137        first.wrapping_add(3),
138    );
139    normalized.hash(&mut second);
140    (u128::from(first) << 64) | u128::from(second.finish())
141}
142
143fn check_sql_sanity(sql: &str) -> Result<(), CanonicalizeError> {
144    let mut paren_depth = 0usize;
145    let mut bracket_depth = 0usize;
146    let mut consecutive_ops = 0usize;
147
148    for byte in sql.bytes() {
149        match byte {
150            b'(' => {
151                paren_depth += 1;
152                consecutive_ops += 1;
153            }
154            b')' => {
155                paren_depth = paren_depth.saturating_sub(1);
156                consecutive_ops = 0;
157            }
158            b'[' => {
159                bracket_depth += 1;
160                consecutive_ops = 0;
161            }
162            b']' => {
163                bracket_depth = bracket_depth.saturating_sub(1);
164                consecutive_ops = 0;
165            }
166            b'+' | b'-' | b'*' | b'/' | b'=' | b'<' | b'>' | b'!' | b'~' => {
167                consecutive_ops += 1;
168            }
169            b' ' | b'\t' | b'\n' | b'\r' => {}
170            0x00..=0x08 | 0x0B | 0x0C | 0x0E..=0x1F | 0x7F => {
171                return Err(CanonicalizeError::Unsupported(
172                    "Control character in SQL".to_string(),
173                ));
174            }
175            _ => consecutive_ops = 0,
176        }
177
178        if paren_depth > MAX_EXPR_DEPTH
179            || bracket_depth > MAX_EXPR_DEPTH
180            || consecutive_ops > MAX_EXPR_DEPTH
181        {
182            return Err(CanonicalizeError::TooDeep {
183                limit: MAX_EXPR_DEPTH,
184            });
185        }
186    }
187
188    if paren_depth != 0 {
189        return Err(CanonicalizeError::Unsupported(
190            "Unbalanced parentheses".to_string(),
191        ));
192    }
193    if bracket_depth != 0 {
194        return Err(CanonicalizeError::Unsupported(
195            "Unbalanced square brackets".to_string(),
196        ));
197    }
198    Ok(())
199}
200
201fn extract_where(statement: &Statement) -> Result<Option<&Expr>, CanonicalizeError> {
202    let Statement::Query(query) = statement else {
203        return Err(CanonicalizeError::Unsupported(
204            "Only SELECT statements are supported".to_string(),
205        ));
206    };
207    Ok(single_table_select(query)?.selection.as_ref())
208}
209
210fn single_table_select(query: &Query) -> Result<&Select, CanonicalizeError> {
211    let SetExpr::Select(select) = query.body.as_ref() else {
212        return Err(CanonicalizeError::Unsupported(
213            "Set operations are not supported".to_string(),
214        ));
215    };
216    if select.from.len() != 1 {
217        return Err(CanonicalizeError::Unsupported(
218            "Exactly one table is required".to_string(),
219        ));
220    }
221    if !select.from[0].joins.is_empty() {
222        return Err(CanonicalizeError::Unsupported(
223            "JOINs not supported".to_string(),
224        ));
225    }
226    if !matches!(select.from[0].relation, TableFactor::Table { .. }) {
227        return Err(CanonicalizeError::Unsupported(
228            "Subqueries and derived tables not supported".to_string(),
229        ));
230    }
231    check_served_clauses(query, select)?;
232    Ok(select)
233}
234
235fn check_served_clauses(query: &Query, select: &Select) -> Result<(), CanonicalizeError> {
236    let Query {
237        with,
238        body: _,
239        order_by: _,
240        limit_clause,
241        fetch,
242        locks,
243        for_clause,
244        settings,
245        format_clause,
246        pipe_operators,
247    } = query;
248    let Select {
249        select_token: _,
250        optimizer_hints,
251        distinct,
252        select_modifiers,
253        top,
254        top_before_distinct: _,
255        projection: _,
256        exclude,
257        into,
258        from: _,
259        lateral_views,
260        prewhere,
261        selection: _,
262        connect_by,
263        group_by: _,
264        cluster_by,
265        distribute_by,
266        sort_by,
267        having: _,
268        named_window,
269        qualify,
270        window_before_qualify: _,
271        value_table_mode,
272        flavor: _,
273    } = select;
274
275    let deduplicating = !matches!(distinct, None | Some(Distinct::All));
276    for (present, clause) in [
277        (with.is_some(), "WITH"),
278        (deduplicating, "DISTINCT"),
279        (
280            limit_clause.is_some(),
281            limit_clause_name(limit_clause.as_ref()),
282        ),
283        (fetch.is_some(), "FETCH"),
284        (!locks.is_empty(), "FOR UPDATE or FOR SHARE"),
285        (for_clause.is_some(), "FOR XML or FOR JSON"),
286        (settings.is_some(), "SETTINGS"),
287        (format_clause.is_some(), "FORMAT"),
288        (!pipe_operators.is_empty(), "pipe operators"),
289        (!optimizer_hints.is_empty(), "optimizer hints"),
290        (
291            select_modifiers
292                .as_ref()
293                .is_some_and(SelectModifiers::is_any_set),
294            "SELECT modifiers",
295        ),
296        (top.is_some(), "TOP"),
297        (exclude.is_some(), "EXCLUDE"),
298        (into.is_some(), "INTO"),
299        (!lateral_views.is_empty(), "LATERAL VIEW"),
300        (prewhere.is_some(), "PREWHERE"),
301        (!connect_by.is_empty(), "CONNECT BY"),
302        (!cluster_by.is_empty(), "CLUSTER BY"),
303        (!distribute_by.is_empty(), "DISTRIBUTE BY"),
304        (!sort_by.is_empty(), "SORT BY"),
305        (!named_window.is_empty(), "WINDOW"),
306        (qualify.is_some(), "QUALIFY"),
307        (
308            value_table_mode.is_some(),
309            "SELECT AS VALUE or SELECT AS STRUCT",
310        ),
311    ] {
312        if present {
313            return Err(CanonicalizeError::Unsupported(format!(
314                "{clause} is not supported"
315            )));
316        }
317    }
318    Ok(())
319}
320
321const fn limit_clause_name(limit: Option<&LimitClause>) -> &'static str {
322    match limit {
323        Some(LimitClause::LimitOffset {
324            limit: None,
325            offset,
326            limit_by,
327        }) => {
328            if offset.is_some() {
329                "OFFSET"
330            } else if limit_by.is_empty() {
331                "LIMIT"
332            } else {
333                "LIMIT BY"
334            }
335        }
336        None | Some(LimitClause::LimitOffset { .. } | LimitClause::OffsetCommaLimit { .. }) => {
337            "LIMIT"
338        }
339    }
340}
341
342fn normalize_expr_inner(
343    expr: &Expr,
344    depth: usize,
345    tight_parent: bool,
346    context: &Canonicalizer<'_>,
347) -> Result<String, CanonicalizeError> {
348    if depth > MAX_EXPR_DEPTH {
349        return Err(CanonicalizeError::TooDeep {
350            limit: MAX_EXPR_DEPTH,
351        });
352    }
353
354    let text = match expr {
355        Expr::BinaryOp { left, op, right } => {
356            if matches!(op, BinaryOperator::And | BinaryOperator::Or) {
357                let mut children = collect_flat_children(left, op);
358                children.extend(collect_flat_children(right, op));
359                let mut child_text: Vec<String> = children
360                    .iter()
361                    .map(|child| normalize_expr_inner(child, depth + 1, false, context))
362                    .collect::<Result<_, _>>()?;
363                child_text.sort();
364                let operator = operator_text(op)?;
365                child_text
366                    .into_iter()
367                    .reduce(|left, right| format!("({left} {operator} {right})"))
368                    .unwrap_or_default()
369            } else {
370                let operator = operator_text(op)?;
371                let order = operand_order(op, context);
372                infix_text(left, operator, right, order, depth, context)?
373            }
374        }
375        Expr::IsDistinctFrom(left, right) => {
376            let order = OperandOrder::Compared;
377            infix_text(left, "IS DISTINCT FROM", right, order, depth, context)?
378        }
379        Expr::IsNotDistinctFrom(left, right) => {
380            let order = OperandOrder::Compared;
381            infix_text(left, "IS NOT DISTINCT FROM", right, order, depth, context)?
382        }
383        Expr::UnaryOp { op, expr } => format!(
384            "{} {}",
385            unary_operator_text(op)?,
386            normalize_expr_inner(expr, depth + 1, true, context)?
387        ),
388        Expr::IsNull(operand) => postfix_text(operand, "IS NULL", depth, context)?,
389        Expr::IsNotNull(operand) => postfix_text(operand, "IS NOT NULL", depth, context)?,
390        Expr::IsTrue(operand) => postfix_text(operand, "IS TRUE", depth, context)?,
391        Expr::IsNotTrue(operand) => postfix_text(operand, "IS NOT TRUE", depth, context)?,
392        Expr::IsFalse(operand) => postfix_text(operand, "IS FALSE", depth, context)?,
393        Expr::IsNotFalse(operand) => postfix_text(operand, "IS NOT FALSE", depth, context)?,
394        Expr::IsUnknown(operand) => postfix_text(operand, "IS UNKNOWN", depth, context)?,
395        Expr::IsNotUnknown(operand) => postfix_text(operand, "IS NOT UNKNOWN", depth, context)?,
396        Expr::IsJson {
397            expr,
398            kind,
399            unique_keys,
400            negated,
401        } => {
402            let not = if *negated { "NOT " } else { "" };
403            let mut suffix = format!("IS {not}JSON");
404            if let Some(kind) = kind {
405                suffix.push_str(&format!(" {kind}"));
406            }
407            if let Some(unique_keys) = unique_keys {
408                suffix.push_str(&format!(" {unique_keys}"));
409            }
410            postfix_text(expr, &suffix, depth, context)?
411        }
412        Expr::IsNormalized {
413            expr,
414            form,
415            negated,
416        } => {
417            let not = if *negated { "NOT " } else { "" };
418            let suffix = form.as_ref().map_or_else(
419                || format!("IS {not}NORMALIZED"),
420                |form| format!("IS {not}{form} NORMALIZED"),
421            );
422            postfix_text(expr, &suffix, depth, context)?
423        }
424        Expr::InList {
425            expr,
426            list,
427            negated,
428        } => {
429            let mut items: Vec<String> = list
430                .iter()
431                .map(|item| normalize_expr_inner(item, depth + 1, true, context))
432                .collect::<Result<_, _>>()?;
433            items.sort();
434            open_list(&mut items, context);
435            let not = if *negated { "NOT " } else { "" };
436            let mut text = format!(
437                "{} {not}IN (",
438                normalize_expr_inner(expr, depth + 1, true, context)?
439            );
440            for (index, item) in items.iter().enumerate() {
441                if index > 0 {
442                    text.push_str(", ");
443                }
444                text.push_str(item);
445            }
446            text.push(')');
447            text
448        }
449        Expr::InSubquery {
450            expr,
451            subquery,
452            negated,
453        } => {
454            let not = if *negated { "NOT " } else { "" };
455            format!(
456                "{} {not}IN ({})",
457                normalize_expr_inner(expr, depth + 1, true, context)?,
458                subquery_text(subquery, depth, context)?
459            )
460        }
461        Expr::Exists { subquery, negated } => {
462            let not = if *negated { "NOT " } else { "" };
463            format!("{not}EXISTS ({})", subquery_text(subquery, depth, context)?)
464        }
465        Expr::Subquery(query) => format!("({})", subquery_text(query, depth, context)?),
466        Expr::Between {
467            expr,
468            low,
469            high,
470            negated,
471        } => {
472            let not = if *negated { "NOT " } else { "" };
473            format!(
474                "{} {not}BETWEEN {} AND {}",
475                normalize_expr_inner(expr, depth + 1, true, context)?,
476                normalize_expr_inner(low, depth + 1, true, context)?,
477                normalize_expr_inner(high, depth + 1, true, context)?
478            )
479        }
480        Expr::Like {
481            negated,
482            any,
483            expr,
484            pattern,
485            escape_char,
486        } => {
487            let operator = match_operator(*negated, "LIKE");
488            let escape = escape_char.as_deref();
489            pattern_match_text(expr, &operator, *any, pattern, escape, depth, context)?
490        }
491        Expr::ILike {
492            negated,
493            any,
494            expr,
495            pattern,
496            escape_char,
497        } => {
498            let operator = match_operator(*negated, "ILIKE");
499            let escape = escape_char.as_deref();
500            pattern_match_text(expr, &operator, *any, pattern, escape, depth, context)?
501        }
502        Expr::SimilarTo {
503            negated,
504            expr,
505            pattern,
506            escape_char,
507        } => {
508            let operator = match_operator(*negated, "SIMILAR TO");
509            let escape = escape_char.as_deref();
510            pattern_match_text(expr, &operator, false, pattern, escape, depth, context)?
511        }
512        // `REGEXP` and `RLIKE` are one operator. `RLIKE` is the spelling every dialect reads
513        // back as it, because SQLite reads `REGEXP` as an operator of its own.
514        Expr::RLike {
515            negated,
516            expr,
517            pattern,
518            regexp: _,
519        } => {
520            let operator = match_operator(*negated, "RLIKE");
521            pattern_match_text(expr, &operator, false, pattern, None, depth, context)?
522        }
523        // `SOME` and `ANY` are one quantifier.
524        Expr::AnyOp {
525            left,
526            compare_op,
527            right,
528            is_some: _,
529        } => quantified_text(left, compare_op, "ANY", right, depth, context)?,
530        Expr::AllOp {
531            left,
532            compare_op,
533            right,
534        } => quantified_text(left, compare_op, "ALL", right, depth, context)?,
535        Expr::MemberOf(member) => format!(
536            "{} MEMBER OF({})",
537            normalize_expr_inner(&member.value, depth + 1, true, context)?,
538            normalize_expr_inner(&member.array, depth + 1, false, context)?
539        ),
540        Expr::AtTimeZone {
541            timestamp,
542            time_zone,
543        } => format!(
544            "{} AT TIME ZONE {}",
545            normalize_expr_inner(timestamp, depth + 1, true, context)?,
546            normalize_expr_inner(time_zone, depth + 1, true, context)?
547        ),
548        // A collation name is kept as written, because whether it is case sensitive differs
549        // by dialect and by server.
550        Expr::Collate { expr, collation } => format!(
551            "{} COLLATE {collation}",
552            normalize_expr_inner(expr, depth + 1, true, context)?
553        ),
554        Expr::MatchAgainst {
555            columns,
556            match_value,
557            opt_search_modifier,
558        } => {
559            let columns = columns
560                .iter()
561                .map(|column| object_name_text(column, NamePlace::Operand, context))
562                .collect::<Result<Vec<_>, _>>()?
563                .join(", ");
564            reject_lossy_quoting(&match_value.value)?;
565            let modifier = opt_search_modifier
566                .as_ref()
567                .map_or_else(String::new, |modifier| format!(" {modifier}"));
568            format!(
569                "MATCH ({columns}) AGAINST ({}{modifier})",
570                match_value.value
571            )
572        }
573        Expr::Nested(inner) => normalize_expr_inner(inner, depth + 1, tight_parent, context)?,
574        Expr::Identifier(identifier) => {
575            identifier_text(identifier, context.folding, NamePlace::Operand, context)?
576        }
577        Expr::CompoundIdentifier(parts) => {
578            qualified_name_text(parts.iter(), NamePlace::Operand, context)?
579        }
580        Expr::Value(value) => {
581            reject_lossy_quoting(&value.value)?;
582            format!("{}", value.value)
583        }
584        // sqlparser reads `:` as a JSON path even in dialects without one, and can build a
585        // tree its printer spells with another grouping.
586        Expr::JsonAccess { .. } => {
587            return Err(CanonicalizeError::Unsupported(
588                "JSON path access is not supported".to_string(),
589            ));
590        }
591        Expr::Function(function) => function_text(function, depth, context)?,
592        // `x::T` and `CAST(x AS T)` are one cast, and every dialect reads the `CAST` spelling.
593        Expr::Cast {
594            kind,
595            expr,
596            data_type,
597            format,
598        } => {
599            if format.is_some() {
600                return Err(CanonicalizeError::Unsupported(
601                    "CAST with FORMAT is not supported".to_string(),
602                ));
603            }
604            let keyword = match kind {
605                CastKind::Cast | CastKind::DoubleColon => "CAST",
606                CastKind::TryCast => "TRY_CAST",
607                CastKind::SafeCast => "SAFE_CAST",
608            };
609            let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
610            format!("{keyword}({expr} AS {data_type})")
611        }
612        Expr::Convert {
613            is_try,
614            expr,
615            data_type,
616            charset,
617            target_before_value,
618            styles,
619        } => {
620            // The SQL Server form puts the type first and takes styles.
621            if *target_before_value || !styles.is_empty() {
622                return Err(CanonicalizeError::Unsupported(
623                    "CONVERT with the type first is not supported".to_string(),
624                ));
625            }
626            let target = match (data_type, charset) {
627                (Some(data_type), Some(charset)) => {
628                    format!(", {data_type} CHARACTER SET {charset}")
629                }
630                (Some(data_type), None) => format!(", {data_type}"),
631                (None, Some(charset)) => format!(" USING {charset}"),
632                (None, None) => {
633                    return Err(CanonicalizeError::Unsupported(
634                        "CONVERT without a target is not supported".to_string(),
635                    ));
636                }
637            };
638            let prefix = if *is_try { "TRY_" } else { "" };
639            let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
640            format!("{prefix}CONVERT({expr}{target})")
641        }
642        Expr::Extract {
643            field,
644            syntax,
645            expr,
646        } => {
647            let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
648            match syntax {
649                ExtractSyntax::From => format!("EXTRACT({field} FROM {expr})"),
650                ExtractSyntax::Comma => format!("EXTRACT({field}, {expr})"),
651            }
652        }
653        Expr::Ceil { expr, field } => rounding_text("CEIL", expr, field, depth, context)?,
654        Expr::Floor { expr, field } => rounding_text("FLOOR", expr, field, depth, context)?,
655        // `IN` could continue the first operand, so it is enclosed like any operand.
656        Expr::Position { expr, r#in } => format!(
657            "POSITION({} IN {})",
658            normalize_expr_inner(expr, depth + 1, true, context)?,
659            normalize_expr_inner(r#in, depth + 1, false, context)?
660        ),
661        Expr::Substring {
662            expr,
663            substring_from,
664            substring_for,
665            special,
666            shorthand,
667        } => {
668            let name = if *shorthand { "SUBSTR" } else { "SUBSTRING" };
669            let (from, length) = if *special {
670                (", ", ", ")
671            } else {
672                (" FROM ", " FOR ")
673            };
674            let mut text = format!(
675                "{name}({}",
676                normalize_expr_inner(expr, depth + 1, false, context)?
677            );
678            for (separator, part) in [(from, substring_from), (length, substring_for)] {
679                if let Some(part) = part {
680                    text.push_str(separator);
681                    text.push_str(&normalize_expr_inner(part, depth + 1, false, context)?);
682                }
683            }
684            text.push(')');
685            text
686        }
687        Expr::Trim {
688            expr,
689            trim_where,
690            trim_what,
691            trim_characters,
692        } => {
693            let mut text = String::from("TRIM(");
694            if let Some(trim_where) = trim_where {
695                text.push_str(&format!("{trim_where} "));
696            }
697            if let Some(trim_what) = trim_what {
698                text.push_str(&normalize_expr_inner(trim_what, depth + 1, false, context)?);
699                text.push_str(" FROM ");
700            }
701            text.push_str(&normalize_expr_inner(expr, depth + 1, false, context)?);
702            if let Some(characters) = trim_characters {
703                text.push_str(", ");
704                text.push_str(&list_text(characters, depth, context)?);
705            }
706            text.push(')');
707            text
708        }
709        Expr::Overlay {
710            expr,
711            overlay_what,
712            overlay_from,
713            overlay_for,
714        } => {
715            let mut text = format!(
716                "OVERLAY({} PLACING {} FROM {}",
717                normalize_expr_inner(expr, depth + 1, false, context)?,
718                normalize_expr_inner(overlay_what, depth + 1, false, context)?,
719                normalize_expr_inner(overlay_from, depth + 1, false, context)?
720            );
721            if let Some(overlay_for) = overlay_for {
722                text.push_str(" FOR ");
723                text.push_str(&normalize_expr_inner(
724                    overlay_for,
725                    depth + 1,
726                    false,
727                    context,
728                )?);
729            }
730            text.push(')');
731            text
732        }
733        Expr::Case {
734            case_token: _,
735            end_token: _,
736            operand,
737            conditions,
738            else_result,
739        } => {
740            let mut text = String::from("CASE");
741            if let Some(operand) = operand {
742                text.push(' ');
743                text.push_str(&normalize_expr_inner(operand, depth + 1, false, context)?);
744            }
745            for when in conditions {
746                text.push_str(" WHEN ");
747                text.push_str(&normalize_expr_inner(
748                    &when.condition,
749                    depth + 1,
750                    false,
751                    context,
752                )?);
753                text.push_str(" THEN ");
754                text.push_str(&normalize_expr_inner(
755                    &when.result,
756                    depth + 1,
757                    false,
758                    context,
759                )?);
760            }
761            // `ELSE NULL` is what a missing `ELSE` yields.
762            if let Some(result) = else_result.as_deref().filter(|result| !is_null(result)) {
763                text.push_str(" ELSE ");
764                text.push_str(&normalize_expr_inner(result, depth + 1, false, context)?);
765            }
766            text.push_str(" END");
767            text
768        }
769        Expr::Tuple(items) => format!("({})", list_text(items, depth, context)?),
770        Expr::Array(array) => {
771            let keyword = if array.named { "ARRAY" } else { "" };
772            format!("{keyword}[{}]", list_text(&array.elem, depth, context)?)
773        }
774        Expr::Interval(interval) => interval_text(interval, depth, context)?,
775        Expr::TypedString(typed) => {
776            if typed.uses_odbc_syntax {
777                return Err(CanonicalizeError::Unsupported(
778                    "ODBC escape literals are not supported".to_string(),
779                ));
780            }
781            reject_lossy_quoting(&typed.value.value)?;
782            format!("{} {}", typed.data_type, typed.value)
783        }
784        Expr::Prefixed { prefix, value } => format!(
785            "{prefix} {}",
786            normalize_expr_inner(value, depth + 1, true, context)?
787        ),
788        Expr::CompoundFieldAccess { root, access_chain } => {
789            field_access_text(root, access_chain, depth, context)?
790        }
791        // Named statically, because printing an unbounded tree can be slow in sqlparser.
792        Expr::InUnnest { .. } => return Err(refused("IN UNNEST")),
793        Expr::Struct { .. } => return Err(refused("STRUCT")),
794        Expr::Named { .. } => return Err(refused("a named expression")),
795        Expr::Dictionary(_) => return Err(refused("a dictionary literal")),
796        Expr::Map(_) => return Err(refused("a map literal")),
797        Expr::Lambda(_) => return Err(refused("a lambda")),
798        Expr::OuterJoin(_) => return Err(refused("the (+) outer join marker")),
799        Expr::Prior(_) => return Err(refused("PRIOR")),
800        Expr::GroupingSets(_) | Expr::Cube(_) | Expr::Rollup(_) => {
801            return Err(refused("a grouping set"));
802        }
803        Expr::Wildcard(_) | Expr::QualifiedWildcard(..) => return Err(refused("a wildcard")),
804    };
805    Ok(if tight_parent && !encloses_itself(expr) {
806        format!("({text})")
807    } else {
808        text
809    })
810}
811
812/// How a binary operator treats the order of its operands.
813#[derive(Clone, Copy)]
814enum OperandOrder {
815    /// The written order is part of the meaning.
816    Written,
817    /// Either order is the same predicate or value.
818    Sorted,
819    /// Either order is the same comparison where the operands' collation does not depend on
820    /// their order.
821    Compared,
822    /// Swapping the operands takes the mirrored operator, as `a < b` is `b > a`.
823    Mirrored(&'static str),
824}
825
826/// Spells `left operator right` in parentheses, so it reads back whole wherever it is nested,
827/// with the operands in sorted order where `order` allows it.
828fn infix_text(
829    left: &Expr,
830    operator: &'static str,
831    right: &Expr,
832    order: OperandOrder,
833    depth: usize,
834    context: &Canonicalizer<'_>,
835) -> Result<String, CanonicalizeError> {
836    // Where the left operand's collation wins, as in SQLite, two operands may only swap when
837    // one of them is a literal, which has no collation of its own.
838    let swappable =
839        context.collation_is_symmetric || is_literal(left, context) || is_literal(right, context);
840    let left = normalize_expr_inner(left, depth + 1, true, context)?;
841    let right = normalize_expr_inner(right, depth + 1, true, context)?;
842    let (left, operator, right) = match order {
843        OperandOrder::Sorted if left > right => (right, operator, left),
844        OperandOrder::Compared if swappable && left > right => (right, operator, left),
845        // Equal operands take whichever of the two operators sorts first, so `b > b` and
846        // `b < b` share one spelling, which holds under any collation.
847        OperandOrder::Mirrored(mirrored)
848            if (swappable && left > right) || (left == right && mirrored < operator) =>
849        {
850            (right, mirrored, left)
851        }
852        _ => (left, operator, right),
853    };
854    Ok(format!("({left} {operator} {right})"))
855}
856
857/// Spells `operand suffix` for a postfix test such as `IS NULL`.
858fn postfix_text(
859    operand: &Expr,
860    suffix: &str,
861    depth: usize,
862    context: &Canonicalizer<'_>,
863) -> Result<String, CanonicalizeError> {
864    let operand = normalize_expr_inner(operand, depth + 1, true, context)?;
865    Ok(format!("{operand} {suffix}"))
866}
867
868/// Spells the operator of a pattern match, such as `NOT SIMILAR TO`.
869fn match_operator(negated: bool, operator: &str) -> String {
870    if negated {
871        format!("NOT {operator}")
872    } else {
873        operator.to_string()
874    }
875}
876
877/// Spells `subject operator pattern`, with `ANY` before a pattern list and `ESCAPE` after the
878/// pattern when the match has them.
879fn pattern_match_text(
880    subject: &Expr,
881    operator: &str,
882    any: bool,
883    pattern: &Expr,
884    escape: Option<&Expr>,
885    depth: usize,
886    context: &Canonicalizer<'_>,
887) -> Result<String, CanonicalizeError> {
888    let subject = normalize_expr_inner(subject, depth + 1, true, context)?;
889    let pattern = if any {
890        format!("ANY {}", argument_text(pattern, depth, context)?)
891    } else {
892        normalize_expr_inner(pattern, depth + 1, true, context)?
893    };
894    let escape = match escape {
895        Some(escape) => format!(
896            " ESCAPE {}",
897            normalize_expr_inner(escape, depth + 1, true, context)?
898        ),
899        None => String::new(),
900    };
901    Ok(format!("{subject} {operator} {pattern}{escape}"))
902}
903
904/// Spells `left operator quantifier(right)` for `ANY` and `ALL`.
905fn quantified_text(
906    left: &Expr,
907    operator: &BinaryOperator,
908    quantifier: &str,
909    right: &Expr,
910    depth: usize,
911    context: &Canonicalizer<'_>,
912) -> Result<String, CanonicalizeError> {
913    let left = normalize_expr_inner(left, depth + 1, true, context)?;
914    let operator = operator_text(operator)?;
915    let right = argument_text(right, depth, context)?;
916    Ok(format!("{left} {operator} {quantifier}{right}"))
917}
918
919/// Spells the parenthesized argument of `ANY`, `ALL` or `LIKE ANY`. A tuple or a subquery
920/// brings its own parentheses, so however many enclose it in the input, it gets one pair.
921fn argument_text(
922    argument: &Expr,
923    depth: usize,
924    context: &Canonicalizer<'_>,
925) -> Result<String, CanonicalizeError> {
926    let text = normalize_expr_inner(argument, depth + 1, false, context)?;
927    let mut inner = argument;
928    while let Expr::Nested(nested) = inner {
929        inner = nested;
930    }
931    Ok(if matches!(inner, Expr::Tuple(_) | Expr::Subquery(_)) {
932        text
933    } else {
934        format!("({text})")
935    })
936}
937
938/// Refuses a call to a function named `ANY`, `SOME` or `ALL`.
939///
940/// Once ordering puts such a call on the right of a comparison, `1 = ANY(x)` reads back as a
941/// quantified comparison, which is another predicate with the same text.
942fn reject_quantifier_name(name: &ObjectName) -> Result<(), CanonicalizeError> {
943    let quantifier = match name.0.as_slice() {
944        [part] => part.as_ident().is_some_and(|ident| {
945            ident.quote_style.is_none()
946                && ["ANY", "SOME", "ALL"]
947                    .iter()
948                    .any(|keyword| ident.value.eq_ignore_ascii_case(keyword))
949        }),
950        _ => false,
951    };
952    if quantifier {
953        Err(CanonicalizeError::Unsupported(format!(
954            "Function named {name} reads back as a quantifier"
955        )))
956    } else {
957        Ok(())
958    }
959}
960
961/// Spells a dotted name, folding the last part as a column and the others as qualifiers.
962fn qualified_name_text<'i>(
963    parts: impl ExactSizeIterator<Item = &'i Ident>,
964    place: NamePlace,
965    context: &Canonicalizer<'_>,
966) -> Result<String, CanonicalizeError> {
967    let column = parts.len().saturating_sub(1);
968    Ok(parts
969        .enumerate()
970        .map(|(index, part)| {
971            let folding = if index == column {
972                context.folding
973            } else {
974                context.qualifier_folding
975            };
976            identifier_text(part, folding, place, context)
977        })
978        .collect::<Result<Vec<_>, _>>()?
979        .join("."))
980}
981
982/// Moves to the head of a sorted `IN` list the first item that can stand there.
983///
984/// sqlparser reads a list that opens with a query as `IN (query)` and does not take that back.
985/// Enclosing hides a query that only starts an item, as in `(SELECT a FROM u)[1]`, and a bare
986/// subquery keeps its query first however many parentheses enclose it. Every other item keeps
987/// its sorted place, and a list whose items all start with a query fails to read back.
988fn open_list(items: &mut [String], context: &Canonicalizer<'_>) {
989    if items
990        .first()
991        .is_none_or(|head| !starts_with_query(head, context))
992    {
993        return;
994    }
995    let head = items.iter_mut().position(|item| {
996        if !starts_with_query(item, context) {
997            return true;
998        }
999        let enclosed = format!("({item})");
1000        let can_lead = !starts_with_query(&enclosed, context);
1001        if can_lead {
1002            *item = enclosed;
1003        }
1004        can_lead
1005    });
1006    if let Some(head) = head {
1007        items[..=head].rotate_right(1);
1008    }
1009}
1010
1011/// Reports whether sqlparser reads a query at the start of `text`, as it tries to at the start
1012/// of an `IN` list. A query starts with a parenthesis or a keyword, so any other text is
1013/// answered without parsing.
1014fn starts_with_query(text: &str, context: &Canonicalizer<'_>) -> bool {
1015    let first_word = text
1016        .split(|character: char| !(character.is_ascii_alphanumeric() || character == '_'))
1017        .next()
1018        .unwrap_or_default();
1019    (text.starts_with('(') || is_keyword(first_word))
1020        && Parser::new(context.dialect)
1021            .try_with_sql(text)
1022            .and_then(|mut parser| parser.parse_query())
1023            .is_ok()
1024}
1025
1026/// The error for an expression form no `WHERE` clause of a served query can hold.
1027fn refused(form: &str) -> CanonicalizeError {
1028    CanonicalizeError::Unsupported(format!("{form} is not supported in a predicate"))
1029}
1030
1031/// Spells a subquery in the shape the crate serves, `SELECT items FROM table [WHERE filter]`.
1032///
1033/// Inside a subquery, grouping, ordering and column aliases change which rows the outer
1034/// predicate sees, so they are refused along with every clause an outer query may not carry.
1035/// A table alias only names the table for the qualifiers that follow, so it is kept, folded as
1036/// a qualifier and written without `AS`, which Oracle refuses there.
1037fn subquery_text(
1038    query: &Query,
1039    depth: usize,
1040    context: &Canonicalizer<'_>,
1041) -> Result<String, CanonicalizeError> {
1042    let unsupported = |part: &str| {
1043        CanonicalizeError::Unsupported(format!("{part} in a subquery is not supported"))
1044    };
1045    let select = single_table_select(query)?;
1046    if query.order_by.is_some() {
1047        return Err(unsupported("ORDER BY"));
1048    }
1049    if !matches!(&select.group_by, GroupByExpr::Expressions(exprs, modifiers) if exprs.is_empty() && modifiers.is_empty())
1050    {
1051        return Err(unsupported("GROUP BY"));
1052    }
1053    if select.having.is_some() {
1054        return Err(unsupported("HAVING"));
1055    }
1056    let TableFactor::Table {
1057        name,
1058        alias,
1059        args: None,
1060        with_hints,
1061        version: None,
1062        with_ordinality: false,
1063        partitions,
1064        json_path: None,
1065        sample: None,
1066        index_hints,
1067    } = &select.from[0].relation
1068    else {
1069        return Err(unsupported("a table option"));
1070    };
1071    if !with_hints.is_empty() || !partitions.is_empty() || !index_hints.is_empty() {
1072        return Err(unsupported("a table hint"));
1073    }
1074    let items = select
1075        .projection
1076        .iter()
1077        .map(|item| match item {
1078            SelectItem::UnnamedExpr(expr) => normalize_expr_inner(expr, depth + 1, false, context),
1079            SelectItem::Wildcard(options)
1080                if options.opt_ilike.is_none()
1081                    && options.opt_exclude.is_none()
1082                    && options.opt_except.is_none()
1083                    && options.opt_replace.is_none()
1084                    && options.opt_rename.is_none()
1085                    && options.opt_alias.is_none() =>
1086            {
1087                Ok("*".to_string())
1088            }
1089            _ => Err(unsupported("an alias or qualified wildcard")),
1090        })
1091        .collect::<Result<Vec<_>, _>>()?
1092        .join(", ");
1093    let table = name
1094        .0
1095        .iter()
1096        .map(|part| match part.as_ident() {
1097            Some(part) => {
1098                identifier_text(part, context.qualifier_folding, NamePlace::Operand, context)
1099            }
1100            None => Err(unsupported("a computed table name")),
1101        })
1102        .collect::<Result<Vec<_>, _>>()?
1103        .join(".");
1104    let mut text = format!("SELECT {items} FROM {table}");
1105    if let Some(TableAlias {
1106        explicit: _,
1107        name,
1108        columns,
1109        at,
1110    }) = alias
1111    {
1112        if !columns.is_empty() || at.is_some() {
1113            return Err(unsupported("a table alias with columns or an index"));
1114        }
1115        text.push(' ');
1116        text.push_str(&identifier_text(
1117            name,
1118            context.qualifier_folding,
1119            NamePlace::Operand,
1120            context,
1121        )?);
1122    }
1123    // `WHERE TRUE` keeps every row, as a missing `WHERE` does, where `TRUE` is reserved.
1124    if let Some(filter) = select
1125        .selection
1126        .as_ref()
1127        .filter(|filter| !(context.true_is_reserved && is_true(filter)))
1128    {
1129        text.push_str(" WHERE ");
1130        text.push_str(&normalize_expr_inner(filter, depth + 1, false, context)?);
1131    }
1132    Ok(text)
1133}
1134
1135/// Spells a plain call, refusing the aggregate and window forms no `WHERE` clause can hold.
1136fn function_text(
1137    function: &Function,
1138    depth: usize,
1139    context: &Canonicalizer<'_>,
1140) -> Result<String, CanonicalizeError> {
1141    let Function {
1142        name,
1143        uses_odbc_syntax,
1144        parameters,
1145        args,
1146        filter,
1147        null_treatment,
1148        over,
1149        within_group,
1150    } = function;
1151    reject_quantifier_name(name)?;
1152    let plain = !*uses_odbc_syntax
1153        && matches!(parameters, FunctionArguments::None)
1154        && filter.is_none()
1155        && null_treatment.is_none()
1156        && over.is_none()
1157        && within_group.is_empty();
1158    let unsupported =
1159        || CanonicalizeError::Unsupported(format!("Call to {name} is not a plain function call"));
1160    if !plain {
1161        return Err(unsupported());
1162    }
1163    let name = object_name_text(name, NamePlace::Function, context)?;
1164    match args {
1165        FunctionArguments::None => Ok(name),
1166        FunctionArguments::Subquery(_) => Err(unsupported()),
1167        FunctionArguments::List(list) => {
1168            if list.duplicate_treatment.is_some() || !list.clauses.is_empty() {
1169                return Err(unsupported());
1170            }
1171            let args = list
1172                .args
1173                .iter()
1174                .map(|arg| match arg {
1175                    // Enclosed like an operand, because a bare `a IN (b)` argument makes
1176                    // `POSITION(a IN (b))` read back as the special form of `POSITION`.
1177                    FunctionArg::Unnamed(FunctionArgExpr::Expr(arg)) => {
1178                        normalize_expr_inner(arg, depth + 1, true, context)
1179                    }
1180                    _ => Err(unsupported()),
1181                })
1182                .collect::<Result<Vec<_>, _>>()?;
1183            Ok(format!("{name}({})", args.join(", ")))
1184        }
1185    }
1186}
1187
1188/// Spells a comma separated list of expressions in their written order.
1189fn list_text(
1190    items: &[Expr],
1191    depth: usize,
1192    context: &Canonicalizer<'_>,
1193) -> Result<String, CanonicalizeError> {
1194    Ok(items
1195        .iter()
1196        .map(|item| normalize_expr_inner(item, depth + 1, false, context))
1197        .collect::<Result<Vec<_>, _>>()?
1198        .join(", "))
1199}
1200
1201/// Spells `CEIL` or `FLOOR` with its optional date part or scale.
1202fn rounding_text(
1203    name: &str,
1204    expr: &Expr,
1205    field: &CeilFloorKind,
1206    depth: usize,
1207    context: &Canonicalizer<'_>,
1208) -> Result<String, CanonicalizeError> {
1209    let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
1210    Ok(match field {
1211        CeilFloorKind::DateTimeField(DateTimeField::NoDateTime) => format!("{name}({expr})"),
1212        CeilFloorKind::DateTimeField(field) => format!("{name}({expr} TO {field})"),
1213        CeilFloorKind::Scale(scale) => format!("{name}({expr}, {scale})"),
1214    })
1215}
1216
1217/// Spells an interval with its value normalized and its qualifiers as sqlparser writes them.
1218fn interval_text(
1219    interval: &Interval,
1220    depth: usize,
1221    context: &Canonicalizer<'_>,
1222) -> Result<String, CanonicalizeError> {
1223    let Interval {
1224        value,
1225        leading_field,
1226        leading_precision,
1227        last_field,
1228        fractional_seconds_precision,
1229    } = interval;
1230    let mut text = format!(
1231        "INTERVAL {}",
1232        normalize_expr_inner(value, depth + 1, true, context)?
1233    );
1234    if let (Some(DateTimeField::Second), Some(leading), Some(fractional)) = (
1235        leading_field,
1236        leading_precision,
1237        fractional_seconds_precision,
1238    ) {
1239        text.push_str(&format!(" SECOND ({leading}, {fractional})"));
1240        return Ok(text);
1241    }
1242    if let Some(leading_field) = leading_field {
1243        text.push_str(&format!(" {leading_field}"));
1244    }
1245    if let Some(leading_precision) = leading_precision {
1246        text.push_str(&format!(" ({leading_precision})"));
1247    }
1248    if let Some(last_field) = last_field {
1249        text.push_str(&format!(" TO {last_field}"));
1250    }
1251    if let Some(fractional) = fractional_seconds_precision {
1252        text.push_str(&format!(" ({fractional})"));
1253    }
1254    Ok(text)
1255}
1256
1257/// Spells a field or subscript access.
1258///
1259/// Parentheses around the root decide what it is, since `(c).f` reads a field of the value
1260/// `c` while `c.f` may read column `f` of table `c`, so a parenthesized root keeps one pair.
1261/// A bare name in the access may be a table, so it folds with the qualifier rule.
1262fn field_access_text(
1263    root: &Expr,
1264    access_chain: &[AccessExpr],
1265    depth: usize,
1266    context: &Canonicalizer<'_>,
1267) -> Result<String, CanonicalizeError> {
1268    let mut text = match root {
1269        Expr::Nested(inner) => format!(
1270            "({})",
1271            normalize_expr_inner(inner, depth + 1, false, context)?
1272        ),
1273        Expr::Identifier(name) => {
1274            identifier_text(name, context.qualifier_folding, NamePlace::Operand, context)?
1275        }
1276        Expr::Value(_) => return Err(literal_access()),
1277        root => normalize_expr_inner(root, depth + 1, true, context)?,
1278    };
1279    for access in access_chain {
1280        match access {
1281            AccessExpr::Dot(Expr::Identifier(name)) => {
1282                text.push('.');
1283                text.push_str(&identifier_text(
1284                    name,
1285                    context.qualifier_folding,
1286                    NamePlace::Operand,
1287                    context,
1288                )?);
1289            }
1290            AccessExpr::Dot(Expr::Value(_)) => return Err(literal_access()),
1291            AccessExpr::Dot(field) => {
1292                text.push('.');
1293                text.push_str(&normalize_expr_inner(field, depth + 1, true, context)?);
1294            }
1295            AccessExpr::Subscript(Subscript::Index { index }) => {
1296                text.push('[');
1297                text.push_str(&normalize_expr_inner(index, depth + 1, false, context)?);
1298                text.push(']');
1299            }
1300            AccessExpr::Subscript(Subscript::Slice {
1301                lower_bound,
1302                upper_bound,
1303                stride,
1304            }) => {
1305                text.push('[');
1306                if let Some(lower_bound) = lower_bound {
1307                    text.push_str(&normalize_expr_inner(
1308                        lower_bound,
1309                        depth + 1,
1310                        false,
1311                        context,
1312                    )?);
1313                }
1314                text.push(':');
1315                if let Some(upper_bound) = upper_bound {
1316                    text.push_str(&normalize_expr_inner(
1317                        upper_bound,
1318                        depth + 1,
1319                        false,
1320                        context,
1321                    )?);
1322                }
1323                if let Some(stride) = stride {
1324                    text.push(':');
1325                    text.push_str(&normalize_expr_inner(stride, depth + 1, false, context)?);
1326                }
1327                text.push(']');
1328            }
1329        }
1330    }
1331    Ok(text)
1332}
1333
1334/// The error for a field access on a literal or naming a field by one. Printed, `0 .l` reads
1335/// `0.l`, which a tokenizer splits into the number `0.` and a name.
1336fn literal_access() -> CanonicalizeError {
1337    CanonicalizeError::Unsupported("A field access on a literal is not supported".to_string())
1338}
1339
1340/// Reports whether `expr` is a literal, signed or not, however many parentheses enclose it.
1341///
1342/// A boolean counts only where `TRUE` is reserved, because SQLite reads `TRUE` and `FALSE` as
1343/// columns of those names when the table has them.
1344fn is_literal(expr: &Expr, context: &Canonicalizer<'_>) -> bool {
1345    match expr {
1346        Expr::Nested(inner) => is_literal(inner, context),
1347        Expr::UnaryOp {
1348            op: UnaryOperator::Minus | UnaryOperator::Plus,
1349            expr,
1350        } => is_literal(expr, context),
1351        Expr::Value(value) => context.true_is_reserved || !matches!(value.value, Value::Boolean(_)),
1352        _ => false,
1353    }
1354}
1355
1356/// Reports whether `expr` is `TRUE`, however many parentheses enclose it.
1357fn is_true(expr: &Expr) -> bool {
1358    match expr {
1359        Expr::Nested(inner) => is_true(inner),
1360        Expr::Value(value) => matches!(value.value, Value::Boolean(true)),
1361        _ => false,
1362    }
1363}
1364
1365/// Reports whether `expr` is `NULL`, however many parentheses enclose it.
1366fn is_null(expr: &Expr) -> bool {
1367    match expr {
1368        Expr::Nested(inner) => is_null(inner),
1369        Expr::Value(value) => matches!(value.value, Value::Null),
1370        _ => false,
1371    }
1372}
1373
1374/// Spells an object name that names a column, refusing a part computed by a function.
1375fn object_name_text(
1376    name: &ObjectName,
1377    place: NamePlace,
1378    context: &Canonicalizer<'_>,
1379) -> Result<String, CanonicalizeError> {
1380    let parts = name
1381        .0
1382        .iter()
1383        .map(|part| {
1384            part.as_ident().ok_or_else(|| {
1385                CanonicalizeError::Unsupported(format!("Computed name part: {part}"))
1386            })
1387        })
1388        .collect::<Result<Vec<_>, _>>()?;
1389    qualified_name_text(parts.into_iter(), place, context)
1390}
1391
1392/// Reports whether `expr` prints as text no neighbouring operator can split.
1393///
1394/// Names, literals, forms with delimiters of their own such as calls and `CASE`, and prefix
1395/// signs qualify. Every other form is enclosed in parentheses as an operand, because how
1396/// tightly it binds differs by dialect and its text could read back grouped another way,
1397/// which can give two predicates one key. The match names every variant, so a variant a later
1398/// sqlparser adds is classified before the crate builds.
1399const fn encloses_itself(expr: &Expr) -> bool {
1400    match expr {
1401        // NOT binds looser than any operator that can enclose it.
1402        Expr::UnaryOp { op, .. } => !matches!(op, UnaryOperator::Not),
1403        Expr::Exists { negated, .. } => !*negated,
1404        Expr::Identifier(_)
1405        | Expr::CompoundIdentifier(_)
1406        | Expr::CompoundFieldAccess { .. }
1407        | Expr::Value(_)
1408        | Expr::TypedString(_)
1409        | Expr::Prefixed { .. }
1410        | Expr::Nested(_)
1411        | Expr::BinaryOp { .. }
1412        | Expr::IsDistinctFrom(..)
1413        | Expr::IsNotDistinctFrom(..)
1414        | Expr::Function(_)
1415        | Expr::Cast { .. }
1416        | Expr::Convert { .. }
1417        | Expr::Extract { .. }
1418        | Expr::Ceil { .. }
1419        | Expr::Floor { .. }
1420        | Expr::Position { .. }
1421        | Expr::Substring { .. }
1422        | Expr::Trim { .. }
1423        | Expr::Overlay { .. }
1424        | Expr::Case { .. }
1425        | Expr::Subquery(_)
1426        | Expr::Tuple(_)
1427        | Expr::Array(_)
1428        | Expr::MatchAgainst { .. }
1429        | Expr::Wildcard(_)
1430        | Expr::QualifiedWildcard(..) => true,
1431        Expr::IsNull(_)
1432        | Expr::IsNotNull(_)
1433        | Expr::IsTrue(_)
1434        | Expr::IsNotTrue(_)
1435        | Expr::IsFalse(_)
1436        | Expr::IsNotFalse(_)
1437        | Expr::IsUnknown(_)
1438        | Expr::IsNotUnknown(_)
1439        | Expr::IsJson { .. }
1440        | Expr::IsNormalized { .. }
1441        | Expr::InList { .. }
1442        | Expr::InSubquery { .. }
1443        | Expr::InUnnest { .. }
1444        | Expr::Between { .. }
1445        | Expr::Like { .. }
1446        | Expr::ILike { .. }
1447        | Expr::SimilarTo { .. }
1448        | Expr::RLike { .. }
1449        | Expr::AnyOp { .. }
1450        | Expr::AllOp { .. }
1451        | Expr::MemberOf(_)
1452        | Expr::AtTimeZone { .. }
1453        | Expr::Collate { .. }
1454        | Expr::Interval(_)
1455        | Expr::JsonAccess { .. }
1456        | Expr::GroupingSets(_)
1457        | Expr::Cube(_)
1458        | Expr::Rollup(_)
1459        | Expr::Struct { .. }
1460        | Expr::Named { .. }
1461        | Expr::Dictionary(_)
1462        | Expr::Map(_)
1463        | Expr::Lambda(_)
1464        | Expr::OuterJoin(_)
1465        | Expr::Prior(_) => false,
1466    }
1467}
1468
1469/// How a dialect decides whether two spellings name the same column or table.
1470#[derive(Clone, Copy, Eq, PartialEq)]
1471enum Folding {
1472    /// An unquoted name folds to lower case, a quoted one keeps its spelling.
1473    LowerUnquoted,
1474    /// An unquoted name folds to upper case, a quoted one keeps its spelling.
1475    UpperUnquoted,
1476    /// Every spelling of the same letters is one name.
1477    CaseInsensitive,
1478    /// Unknown rule, so keep the name exactly as written and never merge two spellings.
1479    Exact,
1480}
1481
1482/// Canonicalizes predicates under one SQL dialect.
1483///
1484/// The dialect decides how identifiers fold, so it decides which two spellings mean one
1485/// column. Build this from the dialect that parsed whatever you hand it.
1486pub struct Canonicalizer<'a> {
1487    dialect: &'a dyn Dialect,
1488    folding: Folding,
1489    qualifier_folding: Folding,
1490    /// Whether a bare `TRUE` is always the boolean, and never a column named `true`.
1491    true_is_reserved: bool,
1492    /// Whether `+` only adds numbers, so `a + b` and `b + a` are one value.
1493    plus_is_numeric: bool,
1494    /// Whether comparing two operands uses the same collation in either order.
1495    collation_is_symmetric: bool,
1496}
1497
1498impl<'a> Canonicalizer<'a> {
1499    /// Reads the folding rules for `dialect`.
1500    #[must_use]
1501    pub fn new(dialect: &'a dyn Dialect) -> Self {
1502        let folding = if dialect.is::<PostgreSqlDialect>() {
1503            Folding::LowerUnquoted
1504        } else if dialect.is::<AnsiDialect>() {
1505            Folding::UpperUnquoted
1506        } else if dialect.is::<MySqlDialect>() || dialect.is::<SQLiteDialect>() {
1507            Folding::CaseInsensitive
1508        } else {
1509            Folding::Exact
1510        };
1511        // MySQL matches table and database names by case unless the server sets
1512        // `lower_case_table_names`, so folding them could merge two tables.
1513        let qualifier_folding = if dialect.is::<MySqlDialect>() {
1514            Folding::Exact
1515        } else {
1516            folding
1517        };
1518        let true_is_reserved = dialect.is::<PostgreSqlDialect>()
1519            || dialect.is::<MySqlDialect>()
1520            || dialect.is::<AnsiDialect>();
1521        let plus_is_numeric = true_is_reserved || dialect.is::<SQLiteDialect>();
1522        // PostgreSQL, MySQL and ANSI reject two conflicting implicit collations in either order,
1523        // while SQLite takes the left operand's.
1524        let collation_is_symmetric = true_is_reserved;
1525        Self {
1526            dialect,
1527            folding,
1528            qualifier_folding,
1529            true_is_reserved,
1530            plus_is_numeric,
1531            collation_is_symmetric,
1532        }
1533    }
1534}
1535
1536/// Where a name stands in canonical text.
1537#[derive(Clone, Copy, Eq, PartialEq)]
1538enum NamePlace {
1539    /// A column, table or field name, which a keyword spelling can turn into an expression.
1540    Operand,
1541    /// A function name, which keeps whether it was quoted, because MySQL looks a quoted
1542    /// function name up among stored functions and not among its own.
1543    Function,
1544}
1545
1546/// Resolves an identifier to the name the database would see, then spells it the one way
1547/// that reads back as that same name.
1548fn identifier_text(
1549    identifier: &Ident,
1550    folding: Folding,
1551    place: NamePlace,
1552    context: &Canonicalizer<'_>,
1553) -> Result<String, CanonicalizeError> {
1554    let quoted = identifier.quote_style.is_some();
1555    // Case folding rules are stated for ASCII. Anything else keeps its exact spelling,
1556    // because merging two names the database might separate is the unsafe direction.
1557    let folding = if identifier.value.is_ascii() {
1558        folding
1559    } else {
1560        Folding::Exact
1561    };
1562    let resolved = match folding {
1563        Folding::LowerUnquoted if !quoted => identifier.value.to_ascii_lowercase(),
1564        Folding::UpperUnquoted if !quoted => identifier.value.to_ascii_uppercase(),
1565        Folding::CaseInsensitive => identifier.value.to_ascii_lowercase(),
1566        _ => identifier.value.clone(),
1567    };
1568
1569    if bare_spelling_is_faithful(&resolved, quoted, folding, place) {
1570        return Ok(resolved);
1571    }
1572    // Where no folding rule ties the two spellings, quoting a bare name could name another
1573    // column, so a bare name that cannot stay bare is refused.
1574    if !quoted && folding == Folding::Exact {
1575        return Err(CanonicalizeError::Unsupported(format!(
1576            "Name {resolved} reads as a keyword out of its place"
1577        )));
1578    }
1579    // A delimited name escapes its own delimiter by doubling it, so `a"b` is written
1580    // `"a""b"`. Emitting the delimiter raw produces a name that reads back as something else.
1581    let quote = identifier
1582        .quote_style
1583        .or_else(|| context.dialect.identifier_quote_style(&resolved))
1584        .unwrap_or('"');
1585    let mut delimited = String::with_capacity(resolved.len() + 2);
1586    delimited.push(quote);
1587    for character in resolved.chars() {
1588        if character == quote {
1589            delimited.push(quote);
1590        }
1591        delimited.push(character);
1592    }
1593    delimited.push(quote);
1594    Ok(delimited)
1595}
1596
1597/// Reports whether writing `name` without quotes reads back as `name` itself.
1598///
1599/// The answer must depend only on the name, never on how canonicalization is going, or one
1600/// predicate could end up with two spellings and so two keys.
1601fn bare_spelling_is_faithful(name: &str, quoted: bool, folding: Folding, place: NamePlace) -> bool {
1602    if !quoted {
1603        // Folding only applied the change the dialect makes itself, so the bare name is the
1604        // same name as long as it reads as a name wherever canonicalization puts it. A call
1605        // keeps its name, because it is only a call once its name failed as special syntax,
1606        // and its enclosed arguments fail that syntax again.
1607        return place == NamePlace::Function || !starts_expression_syntax(name);
1608    }
1609    // A quoted function name stays quoted, because MySQL looks it up among stored functions.
1610    if place == NamePlace::Function || !is_plain_non_keyword(name) {
1611        return false;
1612    }
1613    match folding {
1614        // Dropping the quotes hands the name to the dialect's folding, so it may only go bare
1615        // when it is already in the folded form.
1616        Folding::LowerUnquoted => !name.bytes().any(|byte| byte.is_ascii_uppercase()),
1617        Folding::UpperUnquoted => !name.bytes().any(|byte| byte.is_ascii_lowercase()),
1618        Folding::CaseInsensitive => true,
1619        Folding::Exact => false,
1620    }
1621}
1622
1623/// Reports whether `name` is spelled like a keyword of any dialect.
1624fn is_keyword(name: &str) -> bool {
1625    let upper = || name.bytes().map(|byte| byte.to_ascii_uppercase());
1626    ALL_KEYWORDS
1627        .binary_search_by(|keyword| keyword.bytes().cmp(upper()))
1628        .is_ok()
1629}
1630
1631/// Words sqlparser reads as the start of an expression, taking one as a name only when the
1632/// expression it starts fails to parse. Such a name, like `NOT` inside `POSITION(NOT - x IN
1633/// y)`, reads as the keyword again once canonicalization moves it. The list is the one
1634/// `parse_expr_prefix_by_reserved_word` handles in sqlparser 0.63.
1635const EXPRESSION_KEYWORDS: &[&str] = &[
1636    "ARRAY",
1637    "BOX",
1638    "CASE",
1639    "CAST",
1640    "CEIL",
1641    "CIRCLE",
1642    "CONVERT",
1643    "CURRENT_CATALOG",
1644    "CURRENT_DATE",
1645    "CURRENT_TIME",
1646    "CURRENT_TIMESTAMP",
1647    "CURRENT_USER",
1648    "EXISTS",
1649    "EXTRACT",
1650    "FALSE",
1651    "FLOOR",
1652    "INTERVAL",
1653    "LAMBDA",
1654    "LINE",
1655    "LOCALTIME",
1656    "LOCALTIMESTAMP",
1657    "LSEG",
1658    "MAP",
1659    "MATCH",
1660    "NOT",
1661    "NULL",
1662    "OVERLAY",
1663    "PATH",
1664    "POINT",
1665    "POLYGON",
1666    "POSITION",
1667    "PRIOR",
1668    "SAFE_CAST",
1669    "SELECT",
1670    "SESSION_USER",
1671    "STRUCT",
1672    "SUBSTR",
1673    "SUBSTRING",
1674    "TRIM",
1675    "TRUE",
1676    "TRY_CAST",
1677    "TRY_CONVERT",
1678    "USER",
1679    "WITH",
1680];
1681
1682/// Reports whether sqlparser may read `name` written bare as the start of an expression.
1683fn starts_expression_syntax(name: &str) -> bool {
1684    EXPRESSION_KEYWORDS
1685        .iter()
1686        .any(|keyword| keyword.eq_ignore_ascii_case(name))
1687}
1688
1689/// Reports whether `name` is a bare word no dialect reads as a keyword.
1690///
1691/// Whether a given keyword may still name a column is dialect and position specific, so this
1692/// errs towards keeping the quotes. The cost is that a quoted keyword does not share a key
1693/// with its unquoted spelling, which splits one column into two entries rather than merging
1694/// two columns into one.
1695fn is_plain_non_keyword(name: &str) -> bool {
1696    let mut characters = name.chars();
1697    let Some(first) = characters.next() else {
1698        return false;
1699    };
1700    if !(first.is_ascii_alphabetic() || first == '_')
1701        || !characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1702    {
1703        return false;
1704    }
1705    !is_keyword(name)
1706}
1707
1708/// Rejects a literal the parser cannot print without changing its value.
1709fn reject_lossy_quoting(value: &Value) -> Result<(), CanonicalizeError> {
1710    let intact = match value {
1711        Value::SingleQuotedString(text) | Value::NationalStringLiteral(text) => {
1712            quoting_survives_printing(text, '\'')
1713        }
1714        Value::DoubleQuotedString(text) => quoting_survives_printing(text, '"'),
1715        _ => true,
1716    };
1717    if intact {
1718        Ok(())
1719    } else {
1720        Err(CanonicalizeError::NotRoundTrippable(
1721            "string literal".to_string(),
1722        ))
1723    }
1724}
1725
1726/// Reports whether printing `text` inside `quote` delimiters preserves it.
1727///
1728/// `sqlparser` leaves a quote alone when it already looks escaped, either doubled or preceded
1729/// by a backslash, so such text reads back one escape level shorter than it went in.
1730fn quoting_survives_printing(text: &str, quote: char) -> bool {
1731    let mut characters = text.chars().peekable();
1732    let mut previous = char::default();
1733    while let Some(character) = characters.next() {
1734        if character == quote
1735            && (previous == '\\' || characters.peek().is_some_and(|next| *next == quote))
1736        {
1737            return false;
1738        }
1739        previous = character;
1740    }
1741    true
1742}
1743
1744fn collect_flat_children<'a>(expr: &'a Expr, operator: &BinaryOperator) -> Vec<&'a Expr> {
1745    match expr {
1746        Expr::Nested(inner) => collect_flat_children(inner, operator),
1747        Expr::BinaryOp { left, op, right } if op == operator => {
1748            let mut children = collect_flat_children(left, operator);
1749            children.extend(collect_flat_children(right, operator));
1750            children
1751        }
1752        _ => vec![expr],
1753    }
1754}
1755
1756/// Reads how `operator` treats the order of its operands under the canonicalizer's dialect.
1757///
1758/// `AND` and `OR` never reach here, because their chains are flattened and sorted whole.
1759const fn operand_order(operator: &BinaryOperator, context: &Canonicalizer<'_>) -> OperandOrder {
1760    match operator {
1761        BinaryOperator::Eq | BinaryOperator::NotEq | BinaryOperator::Spaceship => {
1762            OperandOrder::Compared
1763        }
1764        // SQL Server also concatenates strings with `+`, where the order matters.
1765        BinaryOperator::Plus | BinaryOperator::Multiply if context.plus_is_numeric => {
1766            OperandOrder::Sorted
1767        }
1768        BinaryOperator::Lt => OperandOrder::Mirrored(">"),
1769        BinaryOperator::Gt => OperandOrder::Mirrored("<"),
1770        BinaryOperator::LtEq => OperandOrder::Mirrored(">="),
1771        BinaryOperator::GtEq => OperandOrder::Mirrored("<="),
1772        _ => OperandOrder::Written,
1773    }
1774}
1775
1776fn operator_text(operator: &BinaryOperator) -> Result<&'static str, CanonicalizeError> {
1777    match operator {
1778        BinaryOperator::And => Ok("AND"),
1779        BinaryOperator::Or => Ok("OR"),
1780        BinaryOperator::Eq => Ok("="),
1781        BinaryOperator::Spaceship => Ok("<=>"),
1782        BinaryOperator::NotEq => Ok("!="),
1783        BinaryOperator::Lt => Ok("<"),
1784        BinaryOperator::LtEq => Ok("<="),
1785        BinaryOperator::Gt => Ok(">"),
1786        BinaryOperator::GtEq => Ok(">="),
1787        BinaryOperator::Plus => Ok("+"),
1788        BinaryOperator::Minus => Ok("-"),
1789        BinaryOperator::Multiply => Ok("*"),
1790        BinaryOperator::Divide => Ok("/"),
1791        BinaryOperator::Modulo => Ok("%"),
1792        other => Err(CanonicalizeError::Unsupported(format!(
1793            "Unsupported binary operator: {other}"
1794        ))),
1795    }
1796}
1797
1798fn unary_operator_text(operator: &UnaryOperator) -> Result<&'static str, CanonicalizeError> {
1799    match operator {
1800        UnaryOperator::Not => Ok("NOT"),
1801        UnaryOperator::Plus => Ok("+"),
1802        UnaryOperator::Minus => Ok("-"),
1803        other => Err(CanonicalizeError::Unsupported(format!(
1804            "Unsupported unary operator: {other}"
1805        ))),
1806    }
1807}