Skip to main content

polyglot_sql/dialects/
clickhouse.rs

1//! ClickHouse Dialect
2//!
3//! ClickHouse-specific transformations based on sqlglot patterns.
4//! ClickHouse is case-sensitive and has unique function naming conventions.
5
6use super::{DialectImpl, DialectType};
7#[cfg(feature = "transpile")]
8use crate::error::Result;
9#[cfg(feature = "transpile")]
10use crate::expressions::{
11    AggregateFunction, BinaryOp, Case, Cast, Expression, Function, In, IsNull, LikeOp,
12    MapConstructor, Paren, UnaryOp,
13};
14#[cfg(feature = "generate")]
15use crate::generator::GeneratorConfig;
16use crate::tokens::TokenizerConfig;
17
18/// ClickHouse dialect
19pub struct ClickHouseDialect;
20
21impl DialectImpl for ClickHouseDialect {
22    fn dialect_type(&self) -> DialectType {
23        DialectType::ClickHouse
24    }
25
26    fn tokenizer_config(&self) -> TokenizerConfig {
27        let mut config = TokenizerConfig::default();
28        // ClickHouse uses double quotes and backticks for identifiers
29        config.identifiers.insert('"', '"');
30        config.identifiers.insert('`', '`');
31        config.identifier_backslash_escapes = true;
32        // ClickHouse supports nested comments
33        config.nested_comments = true;
34        // ClickHouse allows identifiers to start with digits
35        config.identifiers_can_start_with_digit = true;
36        // ClickHouse uses backslash escaping in strings
37        config.string_escapes.push('\\');
38        // ClickHouse supports # as single-line comment
39        config.hash_comments = true;
40        // ClickHouse allows $ in identifiers
41        config.dollar_sign_is_identifier = true;
42        // ClickHouse: INSERT ... FORMAT <name> is followed by raw data
43        config.insert_format_raw_data = true;
44        // ClickHouse supports 0xDEADBEEF hex integer literals
45        config.hex_number_strings = true;
46        config.hex_string_is_integer_type = true;
47        // ClickHouse allows underscores as digit separators in numeric literals
48        config.numbers_can_be_underscore_separated = true;
49        // SQLGlot tokenizes malformed SHOW LIKE probes such as `'a\' or 1=1`.
50        config.recover_terminal_backslash_quote = true;
51        // The ClickHouse corpus includes partial string probes extracted from shell tests.
52        config.recover_unterminated_string = true;
53        config
54    }
55
56    #[cfg(feature = "generate")]
57
58    fn generator_config(&self) -> GeneratorConfig {
59        use crate::generator::{IdentifierQuoteStyle, NormalizeFunctions};
60        let mut complexity_guard = crate::guard::ComplexityGuardOptions::default();
61        complexity_guard.max_ast_depth = Some(4_096);
62        complexity_guard.max_function_call_depth = Some(512);
63
64        GeneratorConfig {
65            identifier_quote: '"',
66            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
67            dialect: Some(DialectType::ClickHouse),
68            complexity_guard,
69            // ClickHouse uses uppercase keywords (matching Python SQLGlot behavior)
70            uppercase_keywords: true,
71            // ClickHouse function names are case-sensitive and typically camelCase
72            normalize_functions: NormalizeFunctions::None,
73            // ClickHouse identifiers are case-sensitive
74            case_sensitive_identifiers: true,
75            tablesample_keywords: "SAMPLE",
76            tablesample_requires_parens: false,
77            identifiers_can_start_with_digit: true,
78            // ClickHouse uses bracket-only notation for arrays: [1, 2, 3]
79            array_bracket_only: true,
80            ..Default::default()
81        }
82    }
83
84    #[cfg(feature = "transpile")]
85
86    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
87        let wrap_predicate_left = |expr: Expression| -> Expression {
88            let needs_parens = matches!(
89                expr,
90                Expression::Add(_)
91                    | Expression::Sub(_)
92                    | Expression::Mul(_)
93                    | Expression::Div(_)
94                    | Expression::Mod(_)
95                    | Expression::Concat(_)
96                    | Expression::And(_)
97                    | Expression::Or(_)
98                    | Expression::Not(_)
99                    | Expression::Case(_)
100            );
101
102            if needs_parens {
103                Expression::Paren(Box::new(Paren {
104                    this: expr,
105                    trailing_comments: Vec::new(),
106                }))
107            } else {
108                expr
109            }
110        };
111
112        let wrap_not_target = |expr: Expression| -> Expression {
113            match expr {
114                Expression::Paren(_) => expr,
115                Expression::In(_)
116                | Expression::Between(_)
117                | Expression::Is(_)
118                | Expression::IsNull(_)
119                | Expression::IsTrue(_)
120                | Expression::IsFalse(_)
121                | Expression::IsJson(_)
122                | Expression::Like(_)
123                | Expression::ILike(_)
124                | Expression::SimilarTo(_)
125                | Expression::Glob(_)
126                | Expression::RegexpLike(_)
127                | Expression::RegexpILike(_)
128                | Expression::MemberOf(_) => Expression::Paren(Box::new(Paren {
129                    this: expr,
130                    trailing_comments: Vec::new(),
131                })),
132                _ => expr,
133            }
134        };
135
136        let unwrap_in_array = |mut expressions: Vec<Expression>,
137                               query: &Option<Expression>,
138                               unnest: &Option<Box<Expression>>|
139         -> Vec<Expression> {
140            if query.is_none() && unnest.is_none() && expressions.len() == 1 {
141                if matches!(expressions[0], Expression::ArrayFunc(_)) {
142                    if let Expression::ArrayFunc(arr) = expressions.remove(0) {
143                        return arr.expressions;
144                    }
145                }
146            }
147            expressions
148        };
149
150        match expr {
151            // TryCast stays as TryCast (ClickHouse doesn't have TRY_CAST by default)
152            // But we can emulate with toXOrNull functions
153            Expression::TryCast(c) => {
154                // For simplicity, just use regular CAST
155                // ClickHouse has toXOrNull/toXOrZero functions for safe casts
156                Ok(Expression::Cast(c))
157            }
158
159            // SafeCast -> CAST in ClickHouse
160            Expression::SafeCast(c) => Ok(Expression::Cast(c)),
161
162            // CountIf is native in ClickHouse (lowercase)
163            Expression::CountIf(f) => Ok(Expression::Function(Box::new(Function::new(
164                "countIf".to_string(),
165                vec![f.this],
166            )))),
167
168            // UNNEST -> arrayJoin in ClickHouse
169            Expression::Unnest(f) => Ok(Expression::Function(Box::new(Function::new(
170                "arrayJoin".to_string(),
171                vec![f.this],
172            )))),
173
174            // EXPLODE -> arrayJoin in ClickHouse
175            Expression::Explode(f) => Ok(Expression::Function(Box::new(Function::new(
176                "arrayJoin".to_string(),
177                vec![f.this],
178            )))),
179
180            // ExplodeOuter -> arrayJoin in ClickHouse
181            Expression::ExplodeOuter(f) => Ok(Expression::Function(Box::new(Function::new(
182                "arrayJoin".to_string(),
183                vec![f.this],
184            )))),
185
186            // RAND -> randCanonical() in ClickHouse
187            Expression::Rand(_) => Ok(Expression::Function(Box::new(Function::new(
188                "randCanonical".to_string(),
189                vec![],
190            )))),
191
192            // Random -> randCanonical() in ClickHouse
193            Expression::Random(_) => Ok(Expression::Function(Box::new(Function::new(
194                "randCanonical".to_string(),
195                vec![],
196            )))),
197
198            // startsWith -> startsWith
199            Expression::StartsWith(f) => Ok(Expression::Function(Box::new(Function::new(
200                "startsWith".to_string(),
201                vec![f.this, f.expression],
202            )))),
203
204            // endsWith -> endsWith
205            Expression::EndsWith(f) => Ok(Expression::Function(Box::new(Function::new(
206                "endsWith".to_string(),
207                vec![f.this, f.expression],
208            )))),
209
210            // ClickHouse prefers NOT (x IN (...)) over x NOT IN (...)
211            Expression::In(in_expr) if in_expr.not => {
212                if in_expr.global {
213                    return Ok(Expression::In(in_expr));
214                }
215                let In {
216                    this,
217                    expressions,
218                    query,
219                    unnest,
220                    global,
221                    is_field,
222                    ..
223                } = *in_expr;
224                let expressions = unwrap_in_array(expressions, &query, &unnest);
225                let base = Expression::In(Box::new(In {
226                    this: wrap_predicate_left(this),
227                    expressions,
228                    query,
229                    not: false,
230                    global,
231                    unnest,
232                    is_field,
233                }));
234                Ok(Expression::Not(Box::new(UnaryOp {
235                    this: wrap_not_target(base),
236                    inferred_type: None,
237                })))
238            }
239
240            // ClickHouse prefers NOT (x IS NULL) over x IS NOT NULL
241            Expression::IsNull(is_null) if is_null.not => {
242                let IsNull { this, .. } = *is_null;
243                let base = Expression::IsNull(Box::new(IsNull {
244                    this: wrap_predicate_left(this),
245                    not: false,
246                    postfix_form: false,
247                }));
248                Ok(Expression::Not(Box::new(UnaryOp {
249                    this: wrap_not_target(base),
250                    inferred_type: None,
251                })))
252            }
253
254            Expression::In(mut in_expr) => {
255                in_expr.expressions =
256                    unwrap_in_array(in_expr.expressions, &in_expr.query, &in_expr.unnest);
257                in_expr.this = wrap_predicate_left(in_expr.this);
258                Ok(Expression::In(in_expr))
259            }
260
261            Expression::IsNull(mut is_null) => {
262                is_null.this = wrap_predicate_left(is_null.this);
263                Ok(Expression::IsNull(is_null))
264            }
265
266            // IF(cond, true, false) -> CASE WHEN cond THEN true ELSE false END
267            Expression::IfFunc(f) => {
268                let f = *f;
269                let has_aliased_arg = matches!(f.condition, Expression::Alias(_))
270                    || matches!(f.true_value, Expression::Alias(_))
271                    || matches!(f.false_value.as_ref(), Some(Expression::Alias(_)));
272                if has_aliased_arg {
273                    return Ok(Expression::IfFunc(Box::new(f)));
274                }
275                Ok(Expression::Case(Box::new(Case {
276                    operand: None,
277                    whens: vec![(f.condition, f.true_value)],
278                    else_: f.false_value,
279                    comments: Vec::new(),
280                    inferred_type: None,
281                })))
282            }
283
284            Expression::Is(mut is_expr) => {
285                is_expr.left = wrap_predicate_left(is_expr.left);
286                Ok(Expression::Is(is_expr))
287            }
288
289            Expression::Or(op) => {
290                let BinaryOp {
291                    left,
292                    right,
293                    left_comments,
294                    operator_comments,
295                    trailing_comments,
296                    ..
297                } = *op;
298                let left = if matches!(left, Expression::And(_)) {
299                    Expression::Paren(Box::new(Paren {
300                        this: left,
301                        trailing_comments: Vec::new(),
302                    }))
303                } else {
304                    left
305                };
306                let right = if matches!(right, Expression::And(_)) {
307                    Expression::Paren(Box::new(Paren {
308                        this: right,
309                        trailing_comments: Vec::new(),
310                    }))
311                } else {
312                    right
313                };
314                Ok(Expression::Or(Box::new(BinaryOp {
315                    left,
316                    right,
317                    left_comments,
318                    operator_comments,
319                    trailing_comments,
320                    inferred_type: None,
321                })))
322            }
323
324            Expression::Not(op) => {
325                let inner = wrap_not_target(op.this);
326                Ok(Expression::Not(Box::new(UnaryOp {
327                    this: inner,
328                    inferred_type: None,
329                })))
330            }
331
332            Expression::MapFunc(map) if map.curly_brace_syntax => {
333                let MapConstructor { keys, values, .. } = *map;
334                let mut args = Vec::with_capacity(keys.len() * 2);
335                for (key, value) in keys.into_iter().zip(values.into_iter()) {
336                    args.push(key);
337                    args.push(value);
338                }
339                Ok(Expression::Function(Box::new(Function::new(
340                    "map".to_string(),
341                    args,
342                ))))
343            }
344
345            Expression::Insert(mut insert) => {
346                for row in insert.values.iter_mut() {
347                    for value in row.iter_mut() {
348                        if !matches!(value, Expression::Paren(_)) {
349                            let wrapped = Expression::Paren(Box::new(Paren {
350                                this: value.clone(),
351                                trailing_comments: Vec::new(),
352                            }));
353                            *value = wrapped;
354                        }
355                    }
356                }
357                Ok(Expression::Insert(insert))
358            }
359
360            // Generic function transformations
361            Expression::Function(f) => self.transform_function(*f),
362
363            // Generic aggregate function transformations
364            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
365
366            // Cast transformations
367            Expression::Cast(c) => self.transform_cast(*c),
368
369            // TYPEOF -> toTypeName in ClickHouse
370            Expression::Typeof(f) => Ok(Expression::Function(Box::new(Function::new(
371                "toTypeName".to_string(),
372                vec![f.this],
373            )))),
374
375            // Pass through everything else
376            _ => Ok(expr),
377        }
378    }
379}
380
381#[cfg(feature = "transpile")]
382impl ClickHouseDialect {}
383
384#[cfg(feature = "transpile")]
385impl ClickHouseDialect {
386    fn transform_function(&self, f: Function) -> Result<Expression> {
387        let name_upper = f.name.to_uppercase();
388        match name_upper.as_str() {
389            // UTCTimestamp() -> CURRENT_TIMESTAMP('UTC')
390            "UTCTIMESTAMP" => Ok(Expression::UtcTimestamp(Box::new(
391                crate::expressions::UtcTimestamp { this: None },
392            ))),
393
394            "CURRENTDATABASE" | "CURRENT_DATABASE" => Ok(Expression::Function(Box::new(
395                Function::new("CURRENT_DATABASE".to_string(), f.args),
396            ))),
397            "CURRENTSCHEMAS" | "CURRENT_SCHEMAS" => Ok(Expression::Function(Box::new(
398                Function::new("CURRENT_SCHEMAS".to_string(), f.args),
399            ))),
400            "LEVENSHTEIN" | "LEVENSHTEINDISTANCE" | "EDITDISTANCE" => Ok(Expression::Function(
401                Box::new(Function::new("editDistance".to_string(), f.args)),
402            )),
403            "CHAR" | "CHR" => Ok(Expression::Function(Box::new(Function::new(
404                "CHAR".to_string(),
405                f.args,
406            )))),
407            "STR_TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
408                "STR_TO_DATE".to_string(),
409                f.args,
410            )))),
411            "JSONEXTRACTSTRING" => Ok(Expression::Function(Box::new(Function::new(
412                "JSONExtractString".to_string(),
413                f.args,
414            )))),
415            "MATCH" => Ok(Expression::Function(Box::new(Function::new(
416                "match".to_string(),
417                f.args,
418            )))),
419            "LIKE" if f.args.len() == 2 => {
420                let left = f.args[0].clone();
421                let right = f.args[1].clone();
422                Ok(Expression::Like(Box::new(LikeOp::new(left, right))))
423            }
424            "NOTLIKE" if f.args.len() == 2 => {
425                let left = f.args[0].clone();
426                let right = f.args[1].clone();
427                let like = Expression::Like(Box::new(LikeOp::new(left, right)));
428                Ok(Expression::Not(Box::new(UnaryOp {
429                    this: like,
430                    inferred_type: None,
431                })))
432            }
433            "ILIKE" if f.args.len() == 2 => {
434                let left = f.args[0].clone();
435                let right = f.args[1].clone();
436                Ok(Expression::ILike(Box::new(LikeOp::new(left, right))))
437            }
438            "AND" if f.args.len() >= 2 => {
439                let mut iter = f.args.into_iter();
440                let mut expr = iter.next().unwrap();
441                for arg in iter {
442                    expr = Expression::And(Box::new(BinaryOp::new(expr, arg)));
443                }
444                Ok(expr)
445            }
446            "OR" if f.args.len() >= 2 => {
447                let mut iter = f.args.into_iter();
448                let mut expr = iter.next().unwrap();
449                for arg in iter {
450                    expr = Expression::Or(Box::new(BinaryOp::new(expr, arg)));
451                }
452                self.transform_expr(expr)
453            }
454            "XOR" if f.args.len() >= 2 => {
455                let mut iter = f.args.into_iter().map(Self::wrap_nested_xor_arg);
456                let mut expr = iter.next().unwrap();
457                for arg in iter {
458                    expr = Expression::Function(Box::new(Function::new(
459                        f.name.clone(),
460                        vec![expr, arg],
461                    )));
462                }
463                Ok(expr)
464            }
465            // TYPEOF -> toTypeName in ClickHouse
466            "TYPEOF" => Ok(Expression::Function(Box::new(Function::new(
467                "toTypeName".to_string(),
468                f.args,
469            )))),
470
471            // DATE_TRUNC: ClickHouse uses dateTrunc (camelCase)
472            "DATE_TRUNC" | "DATETRUNC" => Ok(Expression::Function(Box::new(Function::new(
473                "dateTrunc".to_string(),
474                f.args,
475            )))),
476            "TOSTARTOFDAY" if f.args.len() == 1 => {
477                Ok(Expression::Function(Box::new(Function::new(
478                    "dateTrunc".to_string(),
479                    vec![Expression::string("DAY"), f.args[0].clone()],
480                ))))
481            }
482
483            // SUBSTRING_INDEX: preserve original case (substringIndex in ClickHouse)
484            "SUBSTRING_INDEX" => Ok(Expression::Function(Box::new(Function::new(
485                f.name.clone(),
486                f.args,
487            )))),
488
489            // IS_NAN / ISNAN -> isNaN (ClickHouse camelCase)
490            "IS_NAN" | "ISNAN" => Ok(Expression::Function(Box::new(Function::new(
491                "isNaN".to_string(),
492                f.args,
493            )))),
494
495            _ => Ok(Expression::Function(Box::new(f))),
496        }
497    }
498
499    fn transform_aggregate_function(
500        &self,
501        f: Box<crate::expressions::AggregateFunction>,
502    ) -> Result<Expression> {
503        let name_upper = f.name.to_uppercase();
504        match name_upper.as_str() {
505            // COUNT_IF -> countIf
506            "COUNT_IF" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
507                "countIf".to_string(),
508                f.args,
509            )))),
510
511            // SUM_IF -> sumIf
512            "SUM_IF" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
513                "sumIf".to_string(),
514                f.args,
515            )))),
516
517            // AVG_IF -> avgIf
518            "AVG_IF" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
519                "avgIf".to_string(),
520                f.args,
521            )))),
522
523            // ANY_VALUE -> any in ClickHouse
524            "ANY_VALUE" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
525                "any".to_string(),
526                f.args,
527            )))),
528
529            // GROUP_CONCAT -> groupArray + arrayStringConcat
530            "GROUP_CONCAT" if !f.args.is_empty() => {
531                let mut args = f.args;
532                let first = args.remove(0);
533                let separator = args.pop();
534                let group_array = Expression::Function(Box::new(Function::new(
535                    "groupArray".to_string(),
536                    vec![first],
537                )));
538                if let Some(sep) = separator {
539                    Ok(Expression::Function(Box::new(Function::new(
540                        "arrayStringConcat".to_string(),
541                        vec![group_array, sep],
542                    ))))
543                } else {
544                    Ok(Expression::Function(Box::new(Function::new(
545                        "arrayStringConcat".to_string(),
546                        vec![group_array],
547                    ))))
548                }
549            }
550
551            // STRING_AGG -> groupArray + arrayStringConcat
552            "STRING_AGG" if !f.args.is_empty() => {
553                let mut args = f.args;
554                let first = args.remove(0);
555                let separator = args.pop();
556                let group_array = Expression::Function(Box::new(Function::new(
557                    "groupArray".to_string(),
558                    vec![first],
559                )));
560                if let Some(sep) = separator {
561                    Ok(Expression::Function(Box::new(Function::new(
562                        "arrayStringConcat".to_string(),
563                        vec![group_array, sep],
564                    ))))
565                } else {
566                    Ok(Expression::Function(Box::new(Function::new(
567                        "arrayStringConcat".to_string(),
568                        vec![group_array],
569                    ))))
570                }
571            }
572
573            // LISTAGG -> groupArray + arrayStringConcat
574            "LISTAGG" if !f.args.is_empty() => {
575                let mut args = f.args;
576                let first = args.remove(0);
577                let separator = args.pop();
578                let group_array = Expression::Function(Box::new(Function::new(
579                    "groupArray".to_string(),
580                    vec![first],
581                )));
582                if let Some(sep) = separator {
583                    Ok(Expression::Function(Box::new(Function::new(
584                        "arrayStringConcat".to_string(),
585                        vec![group_array, sep],
586                    ))))
587                } else {
588                    Ok(Expression::Function(Box::new(Function::new(
589                        "arrayStringConcat".to_string(),
590                        vec![group_array],
591                    ))))
592                }
593            }
594
595            // ARRAY_AGG -> groupArray
596            "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
597                "groupArray".to_string(),
598                f.args,
599            )))),
600
601            // STDDEV -> stddevSamp in ClickHouse (sample stddev)
602            "STDDEV" if !f.args.is_empty() => {
603                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
604                    name: "stddevSamp".to_string(),
605                    args: f.args,
606                    distinct: f.distinct,
607                    filter: f.filter,
608                    order_by: Vec::new(),
609                    limit: None,
610                    ignore_nulls: None,
611                    inferred_type: None,
612                })))
613            }
614
615            // STDDEV_POP -> stddevPop
616            "STDDEV_POP" if !f.args.is_empty() => {
617                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
618                    name: "stddevPop".to_string(),
619                    args: f.args,
620                    distinct: f.distinct,
621                    filter: f.filter,
622                    order_by: Vec::new(),
623                    limit: None,
624                    ignore_nulls: None,
625                    inferred_type: None,
626                })))
627            }
628
629            // VARIANCE -> varSamp in ClickHouse
630            "VARIANCE" if !f.args.is_empty() => {
631                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
632                    name: "varSamp".to_string(),
633                    args: f.args,
634                    distinct: f.distinct,
635                    filter: f.filter,
636                    order_by: Vec::new(),
637                    limit: None,
638                    ignore_nulls: None,
639                    inferred_type: None,
640                })))
641            }
642
643            // VAR_POP -> varPop
644            "VAR_POP" if !f.args.is_empty() => {
645                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
646                    name: "varPop".to_string(),
647                    args: f.args,
648                    distinct: f.distinct,
649                    filter: f.filter,
650                    order_by: Vec::new(),
651                    limit: None,
652                    ignore_nulls: None,
653                    inferred_type: None,
654                })))
655            }
656
657            // MEDIAN -> median
658            "MEDIAN" if !f.args.is_empty() => {
659                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
660                    name: "median".to_string(),
661                    args: f.args,
662                    distinct: f.distinct,
663                    filter: f.filter,
664                    order_by: Vec::new(),
665                    limit: None,
666                    ignore_nulls: None,
667                    inferred_type: None,
668                })))
669            }
670
671            // APPROX_COUNT_DISTINCT -> uniq in ClickHouse
672            "APPROX_COUNT_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
673                Function::new("uniq".to_string(), f.args),
674            ))),
675
676            // APPROX_DISTINCT -> uniq
677            "APPROX_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
678                Function::new("uniq".to_string(), f.args),
679            ))),
680
681            _ => Ok(Expression::AggregateFunction(f)),
682        }
683    }
684
685    fn transform_cast(&self, c: Cast) -> Result<Expression> {
686        Ok(Expression::Cast(Box::new(c)))
687    }
688
689    fn wrap_nested_xor_arg(expr: Expression) -> Expression {
690        if matches!(&expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("XOR")) {
691            Expression::Paren(Box::new(Paren {
692                this: expr,
693                trailing_comments: Vec::new(),
694            }))
695        } else {
696            expr
697        }
698    }
699}