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, 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            let not = if *negated { "NOT " } else { "" };
435            format!(
436                "{} {not}IN ({})",
437                normalize_expr_inner(expr, depth + 1, true, context)?,
438                items.join(", ")
439            )
440        }
441        Expr::InSubquery {
442            expr,
443            subquery,
444            negated,
445        } => {
446            let not = if *negated { "NOT " } else { "" };
447            format!(
448                "{} {not}IN ({})",
449                normalize_expr_inner(expr, depth + 1, true, context)?,
450                subquery_text(subquery, depth, context)?
451            )
452        }
453        Expr::Exists { subquery, negated } => {
454            let not = if *negated { "NOT " } else { "" };
455            format!("{not}EXISTS ({})", subquery_text(subquery, depth, context)?)
456        }
457        Expr::Subquery(query) => format!("({})", subquery_text(query, depth, context)?),
458        Expr::Between {
459            expr,
460            low,
461            high,
462            negated,
463        } => {
464            let not = if *negated { "NOT " } else { "" };
465            format!(
466                "{} {not}BETWEEN {} AND {}",
467                normalize_expr_inner(expr, depth + 1, true, context)?,
468                normalize_expr_inner(low, depth + 1, true, context)?,
469                normalize_expr_inner(high, depth + 1, true, context)?
470            )
471        }
472        Expr::Like {
473            negated,
474            any,
475            expr,
476            pattern,
477            escape_char,
478        } => {
479            let operator = match_operator(*negated, "LIKE");
480            let escape = escape_char.as_deref();
481            pattern_match_text(expr, &operator, *any, pattern, escape, depth, context)?
482        }
483        Expr::ILike {
484            negated,
485            any,
486            expr,
487            pattern,
488            escape_char,
489        } => {
490            let operator = match_operator(*negated, "ILIKE");
491            let escape = escape_char.as_deref();
492            pattern_match_text(expr, &operator, *any, pattern, escape, depth, context)?
493        }
494        Expr::SimilarTo {
495            negated,
496            expr,
497            pattern,
498            escape_char,
499        } => {
500            let operator = match_operator(*negated, "SIMILAR TO");
501            let escape = escape_char.as_deref();
502            pattern_match_text(expr, &operator, false, pattern, escape, depth, context)?
503        }
504        // `REGEXP` and `RLIKE` are one operator. `RLIKE` is the spelling every dialect reads
505        // back as it, because SQLite reads `REGEXP` as an operator of its own.
506        Expr::RLike {
507            negated,
508            expr,
509            pattern,
510            regexp: _,
511        } => {
512            let operator = match_operator(*negated, "RLIKE");
513            pattern_match_text(expr, &operator, false, pattern, None, depth, context)?
514        }
515        // `SOME` and `ANY` are one quantifier.
516        Expr::AnyOp {
517            left,
518            compare_op,
519            right,
520            is_some: _,
521        } => quantified_text(left, compare_op, "ANY", right, depth, context)?,
522        Expr::AllOp {
523            left,
524            compare_op,
525            right,
526        } => quantified_text(left, compare_op, "ALL", right, depth, context)?,
527        Expr::MemberOf(member) => format!(
528            "{} MEMBER OF({})",
529            normalize_expr_inner(&member.value, depth + 1, true, context)?,
530            normalize_expr_inner(&member.array, depth + 1, false, context)?
531        ),
532        Expr::AtTimeZone {
533            timestamp,
534            time_zone,
535        } => format!(
536            "{} AT TIME ZONE {}",
537            normalize_expr_inner(timestamp, depth + 1, true, context)?,
538            normalize_expr_inner(time_zone, depth + 1, true, context)?
539        ),
540        // A collation name is kept as written, because whether it is case sensitive differs
541        // by dialect and by server.
542        Expr::Collate { expr, collation } => format!(
543            "{} COLLATE {collation}",
544            normalize_expr_inner(expr, depth + 1, true, context)?
545        ),
546        Expr::MatchAgainst {
547            columns,
548            match_value,
549            opt_search_modifier,
550        } => {
551            let columns = columns
552                .iter()
553                .map(|column| object_name_text(column, NamePlace::Operand, context))
554                .collect::<Result<Vec<_>, _>>()?
555                .join(", ");
556            reject_lossy_quoting(&match_value.value)?;
557            let modifier = opt_search_modifier
558                .as_ref()
559                .map_or_else(String::new, |modifier| format!(" {modifier}"));
560            format!(
561                "MATCH ({columns}) AGAINST ({}{modifier})",
562                match_value.value
563            )
564        }
565        Expr::Nested(inner) => normalize_expr_inner(inner, depth + 1, tight_parent, context)?,
566        Expr::Identifier(identifier) => {
567            identifier_text(identifier, context.folding, NamePlace::Operand, context)?
568        }
569        Expr::CompoundIdentifier(parts) => {
570            qualified_name_text(parts.iter(), NamePlace::Operand, context)?
571        }
572        Expr::Value(value) => {
573            reject_lossy_quoting(&value.value)?;
574            format!("{}", value.value)
575        }
576        // sqlparser reads `:` as a JSON path even in dialects without one, and can build a
577        // tree its printer spells with another grouping.
578        Expr::JsonAccess { .. } => {
579            return Err(CanonicalizeError::Unsupported(
580                "JSON path access is not supported".to_string(),
581            ));
582        }
583        Expr::Function(function) => function_text(function, depth, context)?,
584        // `x::T` and `CAST(x AS T)` are one cast, and every dialect reads the `CAST` spelling.
585        Expr::Cast {
586            kind,
587            expr,
588            data_type,
589            format,
590        } => {
591            if format.is_some() {
592                return Err(CanonicalizeError::Unsupported(
593                    "CAST with FORMAT is not supported".to_string(),
594                ));
595            }
596            let keyword = match kind {
597                CastKind::Cast | CastKind::DoubleColon => "CAST",
598                CastKind::TryCast => "TRY_CAST",
599                CastKind::SafeCast => "SAFE_CAST",
600            };
601            let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
602            format!("{keyword}({expr} AS {data_type})")
603        }
604        Expr::Convert {
605            is_try,
606            expr,
607            data_type,
608            charset,
609            target_before_value,
610            styles,
611        } => {
612            // The SQL Server form puts the type first and takes styles.
613            if *target_before_value || !styles.is_empty() {
614                return Err(CanonicalizeError::Unsupported(
615                    "CONVERT with the type first is not supported".to_string(),
616                ));
617            }
618            let target = match (data_type, charset) {
619                (Some(data_type), Some(charset)) => {
620                    format!(", {data_type} CHARACTER SET {charset}")
621                }
622                (Some(data_type), None) => format!(", {data_type}"),
623                (None, Some(charset)) => format!(" USING {charset}"),
624                (None, None) => {
625                    return Err(CanonicalizeError::Unsupported(
626                        "CONVERT without a target is not supported".to_string(),
627                    ));
628                }
629            };
630            let prefix = if *is_try { "TRY_" } else { "" };
631            let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
632            format!("{prefix}CONVERT({expr}{target})")
633        }
634        Expr::Extract {
635            field,
636            syntax,
637            expr,
638        } => {
639            let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
640            match syntax {
641                ExtractSyntax::From => format!("EXTRACT({field} FROM {expr})"),
642                ExtractSyntax::Comma => format!("EXTRACT({field}, {expr})"),
643            }
644        }
645        Expr::Ceil { expr, field } => rounding_text("CEIL", expr, field, depth, context)?,
646        Expr::Floor { expr, field } => rounding_text("FLOOR", expr, field, depth, context)?,
647        // `IN` could continue the first operand, so it is enclosed like any operand.
648        Expr::Position { expr, r#in } => format!(
649            "POSITION({} IN {})",
650            normalize_expr_inner(expr, depth + 1, true, context)?,
651            normalize_expr_inner(r#in, depth + 1, false, context)?
652        ),
653        Expr::Substring {
654            expr,
655            substring_from,
656            substring_for,
657            special,
658            shorthand,
659        } => {
660            let name = if *shorthand { "SUBSTR" } else { "SUBSTRING" };
661            let (from, length) = if *special {
662                (", ", ", ")
663            } else {
664                (" FROM ", " FOR ")
665            };
666            let mut text = format!(
667                "{name}({}",
668                normalize_expr_inner(expr, depth + 1, false, context)?
669            );
670            for (separator, part) in [(from, substring_from), (length, substring_for)] {
671                if let Some(part) = part {
672                    text.push_str(separator);
673                    text.push_str(&normalize_expr_inner(part, depth + 1, false, context)?);
674                }
675            }
676            text.push(')');
677            text
678        }
679        Expr::Trim {
680            expr,
681            trim_where,
682            trim_what,
683            trim_characters,
684        } => {
685            let mut text = String::from("TRIM(");
686            if let Some(trim_where) = trim_where {
687                text.push_str(&format!("{trim_where} "));
688            }
689            if let Some(trim_what) = trim_what {
690                text.push_str(&normalize_expr_inner(trim_what, depth + 1, false, context)?);
691                text.push_str(" FROM ");
692            }
693            text.push_str(&normalize_expr_inner(expr, depth + 1, false, context)?);
694            if let Some(characters) = trim_characters {
695                text.push_str(", ");
696                text.push_str(&list_text(characters, depth, context)?);
697            }
698            text.push(')');
699            text
700        }
701        Expr::Overlay {
702            expr,
703            overlay_what,
704            overlay_from,
705            overlay_for,
706        } => {
707            let mut text = format!(
708                "OVERLAY({} PLACING {} FROM {}",
709                normalize_expr_inner(expr, depth + 1, false, context)?,
710                normalize_expr_inner(overlay_what, depth + 1, false, context)?,
711                normalize_expr_inner(overlay_from, depth + 1, false, context)?
712            );
713            if let Some(overlay_for) = overlay_for {
714                text.push_str(" FOR ");
715                text.push_str(&normalize_expr_inner(
716                    overlay_for,
717                    depth + 1,
718                    false,
719                    context,
720                )?);
721            }
722            text.push(')');
723            text
724        }
725        Expr::Case {
726            case_token: _,
727            end_token: _,
728            operand,
729            conditions,
730            else_result,
731        } => {
732            let mut text = String::from("CASE");
733            if let Some(operand) = operand {
734                text.push(' ');
735                text.push_str(&normalize_expr_inner(operand, depth + 1, false, context)?);
736            }
737            for when in conditions {
738                text.push_str(" WHEN ");
739                text.push_str(&normalize_expr_inner(
740                    &when.condition,
741                    depth + 1,
742                    false,
743                    context,
744                )?);
745                text.push_str(" THEN ");
746                text.push_str(&normalize_expr_inner(
747                    &when.result,
748                    depth + 1,
749                    false,
750                    context,
751                )?);
752            }
753            // `ELSE NULL` is what a missing `ELSE` yields.
754            if let Some(result) = else_result.as_deref().filter(|result| !is_null(result)) {
755                text.push_str(" ELSE ");
756                text.push_str(&normalize_expr_inner(result, depth + 1, false, context)?);
757            }
758            text.push_str(" END");
759            text
760        }
761        Expr::Tuple(items) => format!("({})", list_text(items, depth, context)?),
762        Expr::Array(array) => {
763            let keyword = if array.named { "ARRAY" } else { "" };
764            format!("{keyword}[{}]", list_text(&array.elem, depth, context)?)
765        }
766        Expr::Interval(interval) => interval_text(interval, depth, context)?,
767        Expr::TypedString(typed) => {
768            if typed.uses_odbc_syntax {
769                return Err(CanonicalizeError::Unsupported(
770                    "ODBC escape literals are not supported".to_string(),
771                ));
772            }
773            reject_lossy_quoting(&typed.value.value)?;
774            format!("{} {}", typed.data_type, typed.value)
775        }
776        Expr::Prefixed { prefix, value } => format!(
777            "{prefix} {}",
778            normalize_expr_inner(value, depth + 1, true, context)?
779        ),
780        Expr::CompoundFieldAccess { root, access_chain } => {
781            field_access_text(root, access_chain, depth, context)?
782        }
783        // Named statically, because printing an unbounded tree can be slow in sqlparser.
784        Expr::InUnnest { .. } => return Err(refused("IN UNNEST")),
785        Expr::Struct { .. } => return Err(refused("STRUCT")),
786        Expr::Named { .. } => return Err(refused("a named expression")),
787        Expr::Dictionary(_) => return Err(refused("a dictionary literal")),
788        Expr::Map(_) => return Err(refused("a map literal")),
789        Expr::Lambda(_) => return Err(refused("a lambda")),
790        Expr::OuterJoin(_) => return Err(refused("the (+) outer join marker")),
791        Expr::Prior(_) => return Err(refused("PRIOR")),
792        Expr::GroupingSets(_) | Expr::Cube(_) | Expr::Rollup(_) => {
793            return Err(refused("a grouping set"));
794        }
795        Expr::Wildcard(_) | Expr::QualifiedWildcard(..) => return Err(refused("a wildcard")),
796    };
797    Ok(if tight_parent && !encloses_itself(expr) {
798        format!("({text})")
799    } else {
800        text
801    })
802}
803
804/// How a binary operator treats the order of its operands.
805#[derive(Clone, Copy)]
806enum OperandOrder {
807    /// The written order is part of the meaning.
808    Written,
809    /// Either order is the same predicate or value.
810    Sorted,
811    /// Either order is the same comparison where the operands' collation does not depend on
812    /// their order.
813    Compared,
814    /// Swapping the operands takes the mirrored operator, as `a < b` is `b > a`.
815    Mirrored(&'static str),
816}
817
818/// Spells `left operator right` in parentheses, so it reads back whole wherever it is nested,
819/// with the operands in sorted order where `order` allows it.
820fn infix_text(
821    left: &Expr,
822    operator: &'static str,
823    right: &Expr,
824    order: OperandOrder,
825    depth: usize,
826    context: &Canonicalizer<'_>,
827) -> Result<String, CanonicalizeError> {
828    // Where the left operand's collation wins, as in SQLite, two operands may only swap when
829    // one of them is a literal, which has no collation of its own.
830    let swappable =
831        context.collation_is_symmetric || is_literal(left, context) || is_literal(right, context);
832    let left = normalize_expr_inner(left, depth + 1, true, context)?;
833    let right = normalize_expr_inner(right, depth + 1, true, context)?;
834    let (left, operator, right) = match order {
835        OperandOrder::Sorted if left > right => (right, operator, left),
836        OperandOrder::Compared if swappable && left > right => (right, operator, left),
837        // Equal operands take whichever of the two operators sorts first, so `b > b` and
838        // `b < b` share one spelling, which holds under any collation.
839        OperandOrder::Mirrored(mirrored)
840            if (swappable && left > right) || (left == right && mirrored < operator) =>
841        {
842            (right, mirrored, left)
843        }
844        _ => (left, operator, right),
845    };
846    Ok(format!("({left} {operator} {right})"))
847}
848
849/// Spells `operand suffix` for a postfix test such as `IS NULL`.
850fn postfix_text(
851    operand: &Expr,
852    suffix: &str,
853    depth: usize,
854    context: &Canonicalizer<'_>,
855) -> Result<String, CanonicalizeError> {
856    let operand = normalize_expr_inner(operand, depth + 1, true, context)?;
857    Ok(format!("{operand} {suffix}"))
858}
859
860/// Spells the operator of a pattern match, such as `NOT SIMILAR TO`.
861fn match_operator(negated: bool, operator: &str) -> String {
862    if negated {
863        format!("NOT {operator}")
864    } else {
865        operator.to_string()
866    }
867}
868
869/// Spells `subject operator pattern`, with `ANY` before a pattern list and `ESCAPE` after the
870/// pattern when the match has them.
871fn pattern_match_text(
872    subject: &Expr,
873    operator: &str,
874    any: bool,
875    pattern: &Expr,
876    escape: Option<&Expr>,
877    depth: usize,
878    context: &Canonicalizer<'_>,
879) -> Result<String, CanonicalizeError> {
880    let subject = normalize_expr_inner(subject, depth + 1, true, context)?;
881    let pattern = if any {
882        format!("ANY {}", argument_text(pattern, depth, context)?)
883    } else {
884        normalize_expr_inner(pattern, depth + 1, true, context)?
885    };
886    let escape = match escape {
887        Some(escape) => format!(
888            " ESCAPE {}",
889            normalize_expr_inner(escape, depth + 1, true, context)?
890        ),
891        None => String::new(),
892    };
893    Ok(format!("{subject} {operator} {pattern}{escape}"))
894}
895
896/// Spells `left operator quantifier(right)` for `ANY` and `ALL`.
897fn quantified_text(
898    left: &Expr,
899    operator: &BinaryOperator,
900    quantifier: &str,
901    right: &Expr,
902    depth: usize,
903    context: &Canonicalizer<'_>,
904) -> Result<String, CanonicalizeError> {
905    let left = normalize_expr_inner(left, depth + 1, true, context)?;
906    let operator = operator_text(operator)?;
907    let right = argument_text(right, depth, context)?;
908    Ok(format!("{left} {operator} {quantifier}{right}"))
909}
910
911/// Spells the parenthesized argument of `ANY`, `ALL` or `LIKE ANY`. A tuple or a subquery
912/// brings its own parentheses, so however many enclose it in the input, it gets one pair.
913fn argument_text(
914    argument: &Expr,
915    depth: usize,
916    context: &Canonicalizer<'_>,
917) -> Result<String, CanonicalizeError> {
918    let text = normalize_expr_inner(argument, depth + 1, false, context)?;
919    let mut inner = argument;
920    while let Expr::Nested(nested) = inner {
921        inner = nested;
922    }
923    Ok(if matches!(inner, Expr::Tuple(_) | Expr::Subquery(_)) {
924        text
925    } else {
926        format!("({text})")
927    })
928}
929
930/// Refuses a call to a function named `ANY`, `SOME` or `ALL`.
931///
932/// Once ordering puts such a call on the right of a comparison, `1 = ANY(x)` reads back as a
933/// quantified comparison, which is another predicate with the same text.
934fn reject_quantifier_name(name: &ObjectName) -> Result<(), CanonicalizeError> {
935    let quantifier = match name.0.as_slice() {
936        [part] => part.as_ident().is_some_and(|ident| {
937            ident.quote_style.is_none()
938                && ["ANY", "SOME", "ALL"]
939                    .iter()
940                    .any(|keyword| ident.value.eq_ignore_ascii_case(keyword))
941        }),
942        _ => false,
943    };
944    if quantifier {
945        Err(CanonicalizeError::Unsupported(format!(
946            "Function named {name} reads back as a quantifier"
947        )))
948    } else {
949        Ok(())
950    }
951}
952
953/// Spells a dotted name, folding the last part as a column and the others as qualifiers.
954fn qualified_name_text<'i>(
955    parts: impl ExactSizeIterator<Item = &'i Ident>,
956    place: NamePlace,
957    context: &Canonicalizer<'_>,
958) -> Result<String, CanonicalizeError> {
959    let column = parts.len().saturating_sub(1);
960    Ok(parts
961        .enumerate()
962        .map(|(index, part)| {
963            let folding = if index == column {
964                context.folding
965            } else {
966                context.qualifier_folding
967            };
968            identifier_text(part, folding, place, context)
969        })
970        .collect::<Result<Vec<_>, _>>()?
971        .join("."))
972}
973
974/// The error for an expression form no `WHERE` clause of a served query can hold.
975fn refused(form: &str) -> CanonicalizeError {
976    CanonicalizeError::Unsupported(format!("{form} is not supported in a predicate"))
977}
978
979/// Spells a subquery in the shape the crate serves, `SELECT items FROM table [WHERE filter]`.
980///
981/// Inside a subquery, grouping, ordering and aliases change which rows the outer predicate
982/// sees, so they are refused along with every clause an outer query may not carry.
983fn subquery_text(
984    query: &Query,
985    depth: usize,
986    context: &Canonicalizer<'_>,
987) -> Result<String, CanonicalizeError> {
988    let unsupported = |part: &str| {
989        CanonicalizeError::Unsupported(format!("{part} in a subquery is not supported"))
990    };
991    let select = single_table_select(query)?;
992    if query.order_by.is_some() {
993        return Err(unsupported("ORDER BY"));
994    }
995    if !matches!(&select.group_by, GroupByExpr::Expressions(exprs, modifiers) if exprs.is_empty() && modifiers.is_empty())
996    {
997        return Err(unsupported("GROUP BY"));
998    }
999    if select.having.is_some() {
1000        return Err(unsupported("HAVING"));
1001    }
1002    let TableFactor::Table {
1003        name,
1004        alias: None,
1005        args: None,
1006        with_hints,
1007        version: None,
1008        with_ordinality: false,
1009        partitions,
1010        json_path: None,
1011        sample: None,
1012        index_hints,
1013    } = &select.from[0].relation
1014    else {
1015        return Err(unsupported("a table alias or table option"));
1016    };
1017    if !with_hints.is_empty() || !partitions.is_empty() || !index_hints.is_empty() {
1018        return Err(unsupported("a table hint"));
1019    }
1020    let items = select
1021        .projection
1022        .iter()
1023        .map(|item| match item {
1024            SelectItem::UnnamedExpr(expr) => normalize_expr_inner(expr, depth + 1, false, context),
1025            SelectItem::Wildcard(options)
1026                if options.opt_ilike.is_none()
1027                    && options.opt_exclude.is_none()
1028                    && options.opt_except.is_none()
1029                    && options.opt_replace.is_none()
1030                    && options.opt_rename.is_none()
1031                    && options.opt_alias.is_none() =>
1032            {
1033                Ok("*".to_string())
1034            }
1035            _ => Err(unsupported("an alias or qualified wildcard")),
1036        })
1037        .collect::<Result<Vec<_>, _>>()?
1038        .join(", ");
1039    let table = name
1040        .0
1041        .iter()
1042        .map(|part| match part.as_ident() {
1043            Some(part) => {
1044                identifier_text(part, context.qualifier_folding, NamePlace::Operand, context)
1045            }
1046            None => Err(unsupported("a computed table name")),
1047        })
1048        .collect::<Result<Vec<_>, _>>()?
1049        .join(".");
1050    let mut text = format!("SELECT {items} FROM {table}");
1051    // `WHERE TRUE` keeps every row, as a missing `WHERE` does, where `TRUE` is reserved.
1052    if let Some(filter) = select
1053        .selection
1054        .as_ref()
1055        .filter(|filter| !(context.true_is_reserved && is_true(filter)))
1056    {
1057        text.push_str(" WHERE ");
1058        text.push_str(&normalize_expr_inner(filter, depth + 1, false, context)?);
1059    }
1060    Ok(text)
1061}
1062
1063/// Spells a plain call, refusing the aggregate and window forms no `WHERE` clause can hold.
1064fn function_text(
1065    function: &Function,
1066    depth: usize,
1067    context: &Canonicalizer<'_>,
1068) -> Result<String, CanonicalizeError> {
1069    let Function {
1070        name,
1071        uses_odbc_syntax,
1072        parameters,
1073        args,
1074        filter,
1075        null_treatment,
1076        over,
1077        within_group,
1078    } = function;
1079    reject_quantifier_name(name)?;
1080    let plain = !*uses_odbc_syntax
1081        && matches!(parameters, FunctionArguments::None)
1082        && filter.is_none()
1083        && null_treatment.is_none()
1084        && over.is_none()
1085        && within_group.is_empty();
1086    let unsupported =
1087        || CanonicalizeError::Unsupported(format!("Call to {name} is not a plain function call"));
1088    if !plain {
1089        return Err(unsupported());
1090    }
1091    let name = object_name_text(name, NamePlace::Function, context)?;
1092    match args {
1093        FunctionArguments::None => Ok(name),
1094        FunctionArguments::Subquery(_) => Err(unsupported()),
1095        FunctionArguments::List(list) => {
1096            if list.duplicate_treatment.is_some() || !list.clauses.is_empty() {
1097                return Err(unsupported());
1098            }
1099            let args = list
1100                .args
1101                .iter()
1102                .map(|arg| match arg {
1103                    // Enclosed like an operand, because a bare `a IN (b)` argument makes
1104                    // `POSITION(a IN (b))` read back as the special form of `POSITION`.
1105                    FunctionArg::Unnamed(FunctionArgExpr::Expr(arg)) => {
1106                        normalize_expr_inner(arg, depth + 1, true, context)
1107                    }
1108                    _ => Err(unsupported()),
1109                })
1110                .collect::<Result<Vec<_>, _>>()?;
1111            Ok(format!("{name}({})", args.join(", ")))
1112        }
1113    }
1114}
1115
1116/// Spells a comma separated list of expressions in their written order.
1117fn list_text(
1118    items: &[Expr],
1119    depth: usize,
1120    context: &Canonicalizer<'_>,
1121) -> Result<String, CanonicalizeError> {
1122    Ok(items
1123        .iter()
1124        .map(|item| normalize_expr_inner(item, depth + 1, false, context))
1125        .collect::<Result<Vec<_>, _>>()?
1126        .join(", "))
1127}
1128
1129/// Spells `CEIL` or `FLOOR` with its optional date part or scale.
1130fn rounding_text(
1131    name: &str,
1132    expr: &Expr,
1133    field: &CeilFloorKind,
1134    depth: usize,
1135    context: &Canonicalizer<'_>,
1136) -> Result<String, CanonicalizeError> {
1137    let expr = normalize_expr_inner(expr, depth + 1, false, context)?;
1138    Ok(match field {
1139        CeilFloorKind::DateTimeField(DateTimeField::NoDateTime) => format!("{name}({expr})"),
1140        CeilFloorKind::DateTimeField(field) => format!("{name}({expr} TO {field})"),
1141        CeilFloorKind::Scale(scale) => format!("{name}({expr}, {scale})"),
1142    })
1143}
1144
1145/// Spells an interval with its value normalized and its qualifiers as sqlparser writes them.
1146fn interval_text(
1147    interval: &Interval,
1148    depth: usize,
1149    context: &Canonicalizer<'_>,
1150) -> Result<String, CanonicalizeError> {
1151    let Interval {
1152        value,
1153        leading_field,
1154        leading_precision,
1155        last_field,
1156        fractional_seconds_precision,
1157    } = interval;
1158    let mut text = format!(
1159        "INTERVAL {}",
1160        normalize_expr_inner(value, depth + 1, true, context)?
1161    );
1162    if let (Some(DateTimeField::Second), Some(leading), Some(fractional)) = (
1163        leading_field,
1164        leading_precision,
1165        fractional_seconds_precision,
1166    ) {
1167        text.push_str(&format!(" SECOND ({leading}, {fractional})"));
1168        return Ok(text);
1169    }
1170    if let Some(leading_field) = leading_field {
1171        text.push_str(&format!(" {leading_field}"));
1172    }
1173    if let Some(leading_precision) = leading_precision {
1174        text.push_str(&format!(" ({leading_precision})"));
1175    }
1176    if let Some(last_field) = last_field {
1177        text.push_str(&format!(" TO {last_field}"));
1178    }
1179    if let Some(fractional) = fractional_seconds_precision {
1180        text.push_str(&format!(" ({fractional})"));
1181    }
1182    Ok(text)
1183}
1184
1185/// Spells a field or subscript access.
1186///
1187/// Parentheses around the root decide what it is, since `(c).f` reads a field of the value
1188/// `c` while `c.f` may read column `f` of table `c`, so a parenthesized root keeps one pair.
1189/// A bare name in the access may be a table, so it folds with the qualifier rule.
1190fn field_access_text(
1191    root: &Expr,
1192    access_chain: &[AccessExpr],
1193    depth: usize,
1194    context: &Canonicalizer<'_>,
1195) -> Result<String, CanonicalizeError> {
1196    let mut text = match root {
1197        Expr::Nested(inner) => format!(
1198            "({})",
1199            normalize_expr_inner(inner, depth + 1, false, context)?
1200        ),
1201        Expr::Identifier(name) => {
1202            identifier_text(name, context.qualifier_folding, NamePlace::Operand, context)?
1203        }
1204        Expr::Value(_) => return Err(literal_access()),
1205        root => normalize_expr_inner(root, depth + 1, true, context)?,
1206    };
1207    for access in access_chain {
1208        match access {
1209            AccessExpr::Dot(Expr::Identifier(name)) => {
1210                text.push('.');
1211                text.push_str(&identifier_text(
1212                    name,
1213                    context.qualifier_folding,
1214                    NamePlace::Operand,
1215                    context,
1216                )?);
1217            }
1218            AccessExpr::Dot(Expr::Value(_)) => return Err(literal_access()),
1219            AccessExpr::Dot(field) => {
1220                text.push('.');
1221                text.push_str(&normalize_expr_inner(field, depth + 1, true, context)?);
1222            }
1223            AccessExpr::Subscript(Subscript::Index { index }) => {
1224                text.push('[');
1225                text.push_str(&normalize_expr_inner(index, depth + 1, false, context)?);
1226                text.push(']');
1227            }
1228            AccessExpr::Subscript(Subscript::Slice {
1229                lower_bound,
1230                upper_bound,
1231                stride,
1232            }) => {
1233                text.push('[');
1234                if let Some(lower_bound) = lower_bound {
1235                    text.push_str(&normalize_expr_inner(
1236                        lower_bound,
1237                        depth + 1,
1238                        false,
1239                        context,
1240                    )?);
1241                }
1242                text.push(':');
1243                if let Some(upper_bound) = upper_bound {
1244                    text.push_str(&normalize_expr_inner(
1245                        upper_bound,
1246                        depth + 1,
1247                        false,
1248                        context,
1249                    )?);
1250                }
1251                if let Some(stride) = stride {
1252                    text.push(':');
1253                    text.push_str(&normalize_expr_inner(stride, depth + 1, false, context)?);
1254                }
1255                text.push(']');
1256            }
1257        }
1258    }
1259    Ok(text)
1260}
1261
1262/// The error for a field access on a literal or naming a field by one. Printed, `0 .l` reads
1263/// `0.l`, which a tokenizer splits into the number `0.` and a name.
1264fn literal_access() -> CanonicalizeError {
1265    CanonicalizeError::Unsupported("A field access on a literal is not supported".to_string())
1266}
1267
1268/// Reports whether `expr` is a literal, signed or not, however many parentheses enclose it.
1269///
1270/// A boolean counts only where `TRUE` is reserved, because SQLite reads `TRUE` and `FALSE` as
1271/// columns of those names when the table has them.
1272fn is_literal(expr: &Expr, context: &Canonicalizer<'_>) -> bool {
1273    match expr {
1274        Expr::Nested(inner) => is_literal(inner, context),
1275        Expr::UnaryOp {
1276            op: UnaryOperator::Minus | UnaryOperator::Plus,
1277            expr,
1278        } => is_literal(expr, context),
1279        Expr::Value(value) => context.true_is_reserved || !matches!(value.value, Value::Boolean(_)),
1280        _ => false,
1281    }
1282}
1283
1284/// Reports whether `expr` is `TRUE`, however many parentheses enclose it.
1285fn is_true(expr: &Expr) -> bool {
1286    match expr {
1287        Expr::Nested(inner) => is_true(inner),
1288        Expr::Value(value) => matches!(value.value, Value::Boolean(true)),
1289        _ => false,
1290    }
1291}
1292
1293/// Reports whether `expr` is `NULL`, however many parentheses enclose it.
1294fn is_null(expr: &Expr) -> bool {
1295    match expr {
1296        Expr::Nested(inner) => is_null(inner),
1297        Expr::Value(value) => matches!(value.value, Value::Null),
1298        _ => false,
1299    }
1300}
1301
1302/// Spells an object name that names a column, refusing a part computed by a function.
1303fn object_name_text(
1304    name: &ObjectName,
1305    place: NamePlace,
1306    context: &Canonicalizer<'_>,
1307) -> Result<String, CanonicalizeError> {
1308    let parts = name
1309        .0
1310        .iter()
1311        .map(|part| {
1312            part.as_ident().ok_or_else(|| {
1313                CanonicalizeError::Unsupported(format!("Computed name part: {part}"))
1314            })
1315        })
1316        .collect::<Result<Vec<_>, _>>()?;
1317    qualified_name_text(parts.into_iter(), place, context)
1318}
1319
1320/// Reports whether `expr` prints as text no neighbouring operator can split.
1321///
1322/// Names, literals, forms with delimiters of their own such as calls and `CASE`, and prefix
1323/// signs qualify. Every other form is enclosed in parentheses as an operand, because how
1324/// tightly it binds differs by dialect and its text could read back grouped another way,
1325/// which can give two predicates one key. The match names every variant, so a variant a later
1326/// sqlparser adds is classified before the crate builds.
1327const fn encloses_itself(expr: &Expr) -> bool {
1328    match expr {
1329        // NOT binds looser than any operator that can enclose it.
1330        Expr::UnaryOp { op, .. } => !matches!(op, UnaryOperator::Not),
1331        Expr::Exists { negated, .. } => !*negated,
1332        Expr::Identifier(_)
1333        | Expr::CompoundIdentifier(_)
1334        | Expr::CompoundFieldAccess { .. }
1335        | Expr::Value(_)
1336        | Expr::TypedString(_)
1337        | Expr::Prefixed { .. }
1338        | Expr::Nested(_)
1339        | Expr::BinaryOp { .. }
1340        | Expr::IsDistinctFrom(..)
1341        | Expr::IsNotDistinctFrom(..)
1342        | Expr::Function(_)
1343        | Expr::Cast { .. }
1344        | Expr::Convert { .. }
1345        | Expr::Extract { .. }
1346        | Expr::Ceil { .. }
1347        | Expr::Floor { .. }
1348        | Expr::Position { .. }
1349        | Expr::Substring { .. }
1350        | Expr::Trim { .. }
1351        | Expr::Overlay { .. }
1352        | Expr::Case { .. }
1353        | Expr::Subquery(_)
1354        | Expr::Tuple(_)
1355        | Expr::Array(_)
1356        | Expr::MatchAgainst { .. }
1357        | Expr::Wildcard(_)
1358        | Expr::QualifiedWildcard(..) => true,
1359        Expr::IsNull(_)
1360        | Expr::IsNotNull(_)
1361        | Expr::IsTrue(_)
1362        | Expr::IsNotTrue(_)
1363        | Expr::IsFalse(_)
1364        | Expr::IsNotFalse(_)
1365        | Expr::IsUnknown(_)
1366        | Expr::IsNotUnknown(_)
1367        | Expr::IsJson { .. }
1368        | Expr::IsNormalized { .. }
1369        | Expr::InList { .. }
1370        | Expr::InSubquery { .. }
1371        | Expr::InUnnest { .. }
1372        | Expr::Between { .. }
1373        | Expr::Like { .. }
1374        | Expr::ILike { .. }
1375        | Expr::SimilarTo { .. }
1376        | Expr::RLike { .. }
1377        | Expr::AnyOp { .. }
1378        | Expr::AllOp { .. }
1379        | Expr::MemberOf(_)
1380        | Expr::AtTimeZone { .. }
1381        | Expr::Collate { .. }
1382        | Expr::Interval(_)
1383        | Expr::JsonAccess { .. }
1384        | Expr::GroupingSets(_)
1385        | Expr::Cube(_)
1386        | Expr::Rollup(_)
1387        | Expr::Struct { .. }
1388        | Expr::Named { .. }
1389        | Expr::Dictionary(_)
1390        | Expr::Map(_)
1391        | Expr::Lambda(_)
1392        | Expr::OuterJoin(_)
1393        | Expr::Prior(_) => false,
1394    }
1395}
1396
1397/// How a dialect decides whether two spellings name the same column or table.
1398#[derive(Clone, Copy, Eq, PartialEq)]
1399enum Folding {
1400    /// An unquoted name folds to lower case, a quoted one keeps its spelling.
1401    LowerUnquoted,
1402    /// An unquoted name folds to upper case, a quoted one keeps its spelling.
1403    UpperUnquoted,
1404    /// Every spelling of the same letters is one name.
1405    CaseInsensitive,
1406    /// Unknown rule, so keep the name exactly as written and never merge two spellings.
1407    Exact,
1408}
1409
1410/// Canonicalizes predicates under one SQL dialect.
1411///
1412/// The dialect decides how identifiers fold, so it decides which two spellings mean one
1413/// column. Build this from the dialect that parsed whatever you hand it.
1414pub struct Canonicalizer<'a> {
1415    dialect: &'a dyn Dialect,
1416    folding: Folding,
1417    qualifier_folding: Folding,
1418    /// Whether a bare `TRUE` is always the boolean, and never a column named `true`.
1419    true_is_reserved: bool,
1420    /// Whether `+` only adds numbers, so `a + b` and `b + a` are one value.
1421    plus_is_numeric: bool,
1422    /// Whether comparing two operands uses the same collation in either order.
1423    collation_is_symmetric: bool,
1424}
1425
1426impl<'a> Canonicalizer<'a> {
1427    /// Reads the folding rules for `dialect`.
1428    #[must_use]
1429    pub fn new(dialect: &'a dyn Dialect) -> Self {
1430        let folding = if dialect.is::<PostgreSqlDialect>() {
1431            Folding::LowerUnquoted
1432        } else if dialect.is::<AnsiDialect>() {
1433            Folding::UpperUnquoted
1434        } else if dialect.is::<MySqlDialect>() || dialect.is::<SQLiteDialect>() {
1435            Folding::CaseInsensitive
1436        } else {
1437            Folding::Exact
1438        };
1439        // MySQL matches table and database names by case unless the server sets
1440        // `lower_case_table_names`, so folding them could merge two tables.
1441        let qualifier_folding = if dialect.is::<MySqlDialect>() {
1442            Folding::Exact
1443        } else {
1444            folding
1445        };
1446        let true_is_reserved = dialect.is::<PostgreSqlDialect>()
1447            || dialect.is::<MySqlDialect>()
1448            || dialect.is::<AnsiDialect>();
1449        let plus_is_numeric = true_is_reserved || dialect.is::<SQLiteDialect>();
1450        // PostgreSQL, MySQL and ANSI reject two conflicting implicit collations in either order,
1451        // while SQLite takes the left operand's.
1452        let collation_is_symmetric = true_is_reserved;
1453        Self {
1454            dialect,
1455            folding,
1456            qualifier_folding,
1457            true_is_reserved,
1458            plus_is_numeric,
1459            collation_is_symmetric,
1460        }
1461    }
1462}
1463
1464/// Where a name stands in canonical text.
1465#[derive(Clone, Copy, Eq, PartialEq)]
1466enum NamePlace {
1467    /// A column, table or field name, which a keyword spelling can turn into an expression.
1468    Operand,
1469    /// A function name, which keeps whether it was quoted, because MySQL looks a quoted
1470    /// function name up among stored functions and not among its own.
1471    Function,
1472}
1473
1474/// Resolves an identifier to the name the database would see, then spells it the one way
1475/// that reads back as that same name.
1476fn identifier_text(
1477    identifier: &Ident,
1478    folding: Folding,
1479    place: NamePlace,
1480    context: &Canonicalizer<'_>,
1481) -> Result<String, CanonicalizeError> {
1482    let quoted = identifier.quote_style.is_some();
1483    // Case folding rules are stated for ASCII. Anything else keeps its exact spelling,
1484    // because merging two names the database might separate is the unsafe direction.
1485    let folding = if identifier.value.is_ascii() {
1486        folding
1487    } else {
1488        Folding::Exact
1489    };
1490    let resolved = match folding {
1491        Folding::LowerUnquoted if !quoted => identifier.value.to_ascii_lowercase(),
1492        Folding::UpperUnquoted if !quoted => identifier.value.to_ascii_uppercase(),
1493        Folding::CaseInsensitive => identifier.value.to_ascii_lowercase(),
1494        _ => identifier.value.clone(),
1495    };
1496
1497    if bare_spelling_is_faithful(&resolved, quoted, folding, place) {
1498        return Ok(resolved);
1499    }
1500    // Where no folding rule ties the two spellings, quoting a bare name could name another
1501    // column, so a bare name that cannot stay bare is refused.
1502    if !quoted && folding == Folding::Exact {
1503        return Err(CanonicalizeError::Unsupported(format!(
1504            "Name {resolved} reads as a keyword out of its place"
1505        )));
1506    }
1507    // A delimited name escapes its own delimiter by doubling it, so `a"b` is written
1508    // `"a""b"`. Emitting the delimiter raw produces a name that reads back as something else.
1509    let quote = identifier
1510        .quote_style
1511        .or_else(|| context.dialect.identifier_quote_style(&resolved))
1512        .unwrap_or('"');
1513    let mut delimited = String::with_capacity(resolved.len() + 2);
1514    delimited.push(quote);
1515    for character in resolved.chars() {
1516        if character == quote {
1517            delimited.push(quote);
1518        }
1519        delimited.push(character);
1520    }
1521    delimited.push(quote);
1522    Ok(delimited)
1523}
1524
1525/// Reports whether writing `name` without quotes reads back as `name` itself.
1526///
1527/// The answer must depend only on the name, never on how canonicalization is going, or one
1528/// predicate could end up with two spellings and so two keys.
1529fn bare_spelling_is_faithful(name: &str, quoted: bool, folding: Folding, place: NamePlace) -> bool {
1530    if !quoted {
1531        // Folding only applied the change the dialect makes itself, so the bare name is the
1532        // same name as long as it reads as a name wherever canonicalization puts it. A call
1533        // keeps its name, because it is only a call once its name failed as special syntax,
1534        // and its enclosed arguments fail that syntax again.
1535        return place == NamePlace::Function || !starts_expression_syntax(name);
1536    }
1537    // A quoted function name stays quoted, because MySQL looks it up among stored functions.
1538    if place == NamePlace::Function || !is_plain_non_keyword(name) {
1539        return false;
1540    }
1541    match folding {
1542        // Dropping the quotes hands the name to the dialect's folding, so it may only go bare
1543        // when it is already in the folded form.
1544        Folding::LowerUnquoted => !name.bytes().any(|byte| byte.is_ascii_uppercase()),
1545        Folding::UpperUnquoted => !name.bytes().any(|byte| byte.is_ascii_lowercase()),
1546        Folding::CaseInsensitive => true,
1547        Folding::Exact => false,
1548    }
1549}
1550
1551/// Reports whether `name` is spelled like a keyword of any dialect.
1552fn is_keyword(name: &str) -> bool {
1553    let upper = || name.bytes().map(|byte| byte.to_ascii_uppercase());
1554    ALL_KEYWORDS
1555        .binary_search_by(|keyword| keyword.bytes().cmp(upper()))
1556        .is_ok()
1557}
1558
1559/// Words sqlparser reads as the start of an expression, taking one as a name only when the
1560/// expression it starts fails to parse. Such a name, like `NOT` inside `POSITION(NOT - x IN
1561/// y)`, reads as the keyword again once canonicalization moves it. The list is the one
1562/// `parse_expr_prefix_by_reserved_word` handles in sqlparser 0.63.
1563const EXPRESSION_KEYWORDS: &[&str] = &[
1564    "ARRAY",
1565    "BOX",
1566    "CASE",
1567    "CAST",
1568    "CEIL",
1569    "CIRCLE",
1570    "CONVERT",
1571    "CURRENT_CATALOG",
1572    "CURRENT_DATE",
1573    "CURRENT_TIME",
1574    "CURRENT_TIMESTAMP",
1575    "CURRENT_USER",
1576    "EXISTS",
1577    "EXTRACT",
1578    "FALSE",
1579    "FLOOR",
1580    "INTERVAL",
1581    "LAMBDA",
1582    "LINE",
1583    "LOCALTIME",
1584    "LOCALTIMESTAMP",
1585    "LSEG",
1586    "MAP",
1587    "MATCH",
1588    "NOT",
1589    "NULL",
1590    "OVERLAY",
1591    "PATH",
1592    "POINT",
1593    "POLYGON",
1594    "POSITION",
1595    "PRIOR",
1596    "SAFE_CAST",
1597    "SELECT",
1598    "SESSION_USER",
1599    "STRUCT",
1600    "SUBSTR",
1601    "SUBSTRING",
1602    "TRIM",
1603    "TRUE",
1604    "TRY_CAST",
1605    "TRY_CONVERT",
1606    "USER",
1607    "WITH",
1608];
1609
1610/// Reports whether sqlparser may read `name` written bare as the start of an expression.
1611fn starts_expression_syntax(name: &str) -> bool {
1612    EXPRESSION_KEYWORDS
1613        .iter()
1614        .any(|keyword| keyword.eq_ignore_ascii_case(name))
1615}
1616
1617/// Reports whether `name` is a bare word no dialect reads as a keyword.
1618///
1619/// Whether a given keyword may still name a column is dialect and position specific, so this
1620/// errs towards keeping the quotes. The cost is that a quoted keyword does not share a key
1621/// with its unquoted spelling, which splits one column into two entries rather than merging
1622/// two columns into one.
1623fn is_plain_non_keyword(name: &str) -> bool {
1624    let mut characters = name.chars();
1625    let Some(first) = characters.next() else {
1626        return false;
1627    };
1628    if !(first.is_ascii_alphabetic() || first == '_')
1629        || !characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1630    {
1631        return false;
1632    }
1633    !is_keyword(name)
1634}
1635
1636/// Rejects a literal the parser cannot print without changing its value.
1637fn reject_lossy_quoting(value: &Value) -> Result<(), CanonicalizeError> {
1638    let intact = match value {
1639        Value::SingleQuotedString(text) | Value::NationalStringLiteral(text) => {
1640            quoting_survives_printing(text, '\'')
1641        }
1642        Value::DoubleQuotedString(text) => quoting_survives_printing(text, '"'),
1643        _ => true,
1644    };
1645    if intact {
1646        Ok(())
1647    } else {
1648        Err(CanonicalizeError::NotRoundTrippable(
1649            "string literal".to_string(),
1650        ))
1651    }
1652}
1653
1654/// Reports whether printing `text` inside `quote` delimiters preserves it.
1655///
1656/// `sqlparser` leaves a quote alone when it already looks escaped, either doubled or preceded
1657/// by a backslash, so such text reads back one escape level shorter than it went in.
1658fn quoting_survives_printing(text: &str, quote: char) -> bool {
1659    let mut characters = text.chars().peekable();
1660    let mut previous = char::default();
1661    while let Some(character) = characters.next() {
1662        if character == quote
1663            && (previous == '\\' || characters.peek().is_some_and(|next| *next == quote))
1664        {
1665            return false;
1666        }
1667        previous = character;
1668    }
1669    true
1670}
1671
1672fn collect_flat_children<'a>(expr: &'a Expr, operator: &BinaryOperator) -> Vec<&'a Expr> {
1673    match expr {
1674        Expr::Nested(inner) => collect_flat_children(inner, operator),
1675        Expr::BinaryOp { left, op, right } if op == operator => {
1676            let mut children = collect_flat_children(left, operator);
1677            children.extend(collect_flat_children(right, operator));
1678            children
1679        }
1680        _ => vec![expr],
1681    }
1682}
1683
1684/// Reads how `operator` treats the order of its operands under the canonicalizer's dialect.
1685///
1686/// `AND` and `OR` never reach here, because their chains are flattened and sorted whole.
1687const fn operand_order(operator: &BinaryOperator, context: &Canonicalizer<'_>) -> OperandOrder {
1688    match operator {
1689        BinaryOperator::Eq | BinaryOperator::NotEq | BinaryOperator::Spaceship => {
1690            OperandOrder::Compared
1691        }
1692        // SQL Server also concatenates strings with `+`, where the order matters.
1693        BinaryOperator::Plus | BinaryOperator::Multiply if context.plus_is_numeric => {
1694            OperandOrder::Sorted
1695        }
1696        BinaryOperator::Lt => OperandOrder::Mirrored(">"),
1697        BinaryOperator::Gt => OperandOrder::Mirrored("<"),
1698        BinaryOperator::LtEq => OperandOrder::Mirrored(">="),
1699        BinaryOperator::GtEq => OperandOrder::Mirrored("<="),
1700        _ => OperandOrder::Written,
1701    }
1702}
1703
1704fn operator_text(operator: &BinaryOperator) -> Result<&'static str, CanonicalizeError> {
1705    match operator {
1706        BinaryOperator::And => Ok("AND"),
1707        BinaryOperator::Or => Ok("OR"),
1708        BinaryOperator::Eq => Ok("="),
1709        BinaryOperator::Spaceship => Ok("<=>"),
1710        BinaryOperator::NotEq => Ok("!="),
1711        BinaryOperator::Lt => Ok("<"),
1712        BinaryOperator::LtEq => Ok("<="),
1713        BinaryOperator::Gt => Ok(">"),
1714        BinaryOperator::GtEq => Ok(">="),
1715        BinaryOperator::Plus => Ok("+"),
1716        BinaryOperator::Minus => Ok("-"),
1717        BinaryOperator::Multiply => Ok("*"),
1718        BinaryOperator::Divide => Ok("/"),
1719        BinaryOperator::Modulo => Ok("%"),
1720        other => Err(CanonicalizeError::Unsupported(format!(
1721            "Unsupported binary operator: {other}"
1722        ))),
1723    }
1724}
1725
1726fn unary_operator_text(operator: &UnaryOperator) -> Result<&'static str, CanonicalizeError> {
1727    match operator {
1728        UnaryOperator::Not => Ok("NOT"),
1729        UnaryOperator::Plus => Ok("+"),
1730        UnaryOperator::Minus => Ok("-"),
1731        other => Err(CanonicalizeError::Unsupported(format!(
1732            "Unsupported unary operator: {other}"
1733        ))),
1734    }
1735}