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                // The generator emits typed negated LIKE predicates directly.
115                // Keep that form stable when reparsing lowered notLike calls.
116                Expression::Like(_) | Expression::ILike(_) => expr,
117                Expression::Paren(_) => expr,
118                Expression::In(_)
119                | Expression::Between(_)
120                | Expression::Is(_)
121                | Expression::IsNull(_)
122                | Expression::IsTrue(_)
123                | Expression::IsFalse(_)
124                | Expression::IsJson(_)
125                | Expression::SimilarTo(_)
126                | Expression::Glob(_)
127                | Expression::RegexpLike(_)
128                | Expression::RegexpILike(_)
129                | Expression::MemberOf(_) => Expression::Paren(Box::new(Paren {
130                    this: expr,
131                    trailing_comments: Vec::new(),
132                })),
133                _ => expr,
134            }
135        };
136
137        let unwrap_in_array = |mut expressions: Vec<Expression>,
138                               query: &Option<Expression>,
139                               unnest: &Option<Box<Expression>>|
140         -> Vec<Expression> {
141            if query.is_none() && unnest.is_none() && expressions.len() == 1 {
142                if matches!(expressions[0], Expression::ArrayFunc(_)) {
143                    if let Expression::ArrayFunc(arr) = expressions.remove(0) {
144                        return arr.expressions;
145                    }
146                }
147            }
148            expressions
149        };
150
151        match expr {
152            // TryCast stays as TryCast (ClickHouse doesn't have TRY_CAST by default)
153            // But we can emulate with toXOrNull functions
154            Expression::TryCast(c) => {
155                // For simplicity, just use regular CAST
156                // ClickHouse has toXOrNull/toXOrZero functions for safe casts
157                Ok(Expression::Cast(c))
158            }
159
160            // SafeCast -> CAST in ClickHouse
161            Expression::SafeCast(c) => Ok(Expression::Cast(c)),
162
163            // CountIf is native in ClickHouse (lowercase)
164            Expression::CountIf(f) => Ok(Expression::Function(Box::new(Function::new(
165                "countIf".to_string(),
166                vec![f.this],
167            )))),
168
169            // UNNEST -> arrayJoin in ClickHouse
170            Expression::Unnest(f) => Ok(Expression::Function(Box::new(Function::new(
171                "arrayJoin".to_string(),
172                vec![f.this],
173            )))),
174
175            // EXPLODE -> arrayJoin in ClickHouse
176            Expression::Explode(f) => Ok(Expression::Function(Box::new(Function::new(
177                "arrayJoin".to_string(),
178                vec![f.this],
179            )))),
180
181            // ExplodeOuter -> arrayJoin in ClickHouse
182            Expression::ExplodeOuter(f) => Ok(Expression::Function(Box::new(Function::new(
183                "arrayJoin".to_string(),
184                vec![f.this],
185            )))),
186
187            // RAND -> randCanonical() in ClickHouse
188            Expression::Rand(_) => Ok(Expression::Function(Box::new(Function::new(
189                "randCanonical".to_string(),
190                vec![],
191            )))),
192
193            // Random -> randCanonical() in ClickHouse
194            Expression::Random(_) => Ok(Expression::Function(Box::new(Function::new(
195                "randCanonical".to_string(),
196                vec![],
197            )))),
198
199            // startsWith -> startsWith
200            Expression::StartsWith(f) => Ok(Expression::Function(Box::new(Function::new(
201                "startsWith".to_string(),
202                vec![f.this, f.expression],
203            )))),
204
205            // endsWith -> endsWith
206            Expression::EndsWith(f) => Ok(Expression::Function(Box::new(Function::new(
207                "endsWith".to_string(),
208                vec![f.this, f.expression],
209            )))),
210
211            // ClickHouse prefers NOT (x IN (...)) over x NOT IN (...)
212            Expression::In(in_expr) if in_expr.not => {
213                if in_expr.global {
214                    return Ok(Expression::In(in_expr));
215                }
216                let In {
217                    this,
218                    expressions,
219                    query,
220                    unnest,
221                    global,
222                    is_field,
223                    ..
224                } = *in_expr;
225                let expressions = unwrap_in_array(expressions, &query, &unnest);
226                let base = Expression::In(Box::new(In {
227                    this: wrap_predicate_left(this),
228                    expressions,
229                    query,
230                    not: false,
231                    global,
232                    unnest,
233                    is_field,
234                }));
235                Ok(Expression::Not(Box::new(UnaryOp {
236                    this: wrap_not_target(base),
237                    inferred_type: None,
238                })))
239            }
240
241            // ClickHouse prefers NOT (x IS NULL) over x IS NOT NULL
242            Expression::IsNull(is_null) if is_null.not => {
243                let IsNull { this, .. } = *is_null;
244                let base = Expression::IsNull(Box::new(IsNull {
245                    this: wrap_predicate_left(this),
246                    not: false,
247                    postfix_form: false,
248                }));
249                Ok(Expression::Not(Box::new(UnaryOp {
250                    this: wrap_not_target(base),
251                    inferred_type: None,
252                })))
253            }
254
255            Expression::In(mut in_expr) => {
256                in_expr.expressions =
257                    unwrap_in_array(in_expr.expressions, &in_expr.query, &in_expr.unnest);
258                in_expr.this = wrap_predicate_left(in_expr.this);
259                Ok(Expression::In(in_expr))
260            }
261
262            Expression::IsNull(mut is_null) => {
263                is_null.this = wrap_predicate_left(is_null.this);
264                Ok(Expression::IsNull(is_null))
265            }
266
267            // IF(cond, true, false) -> CASE WHEN cond THEN true ELSE false END
268            Expression::IfFunc(f) => {
269                let f = *f;
270                let has_aliased_arg = matches!(f.condition, Expression::Alias(_))
271                    || matches!(f.true_value, Expression::Alias(_))
272                    || matches!(f.false_value.as_ref(), Some(Expression::Alias(_)));
273                if has_aliased_arg {
274                    return Ok(Expression::IfFunc(Box::new(f)));
275                }
276                Ok(Expression::Case(Box::new(Case {
277                    operand: None,
278                    whens: vec![(f.condition, f.true_value)],
279                    else_: f.false_value,
280                    comments: Vec::new(),
281                    inferred_type: None,
282                })))
283            }
284
285            Expression::Is(mut is_expr) => {
286                is_expr.left = wrap_predicate_left(is_expr.left);
287                Ok(Expression::Is(is_expr))
288            }
289
290            Expression::Or(op) => {
291                let BinaryOp {
292                    left,
293                    right,
294                    left_comments,
295                    operator_comments,
296                    trailing_comments,
297                    ..
298                } = *op;
299                let left = if matches!(left, Expression::And(_)) {
300                    Expression::Paren(Box::new(Paren {
301                        this: left,
302                        trailing_comments: Vec::new(),
303                    }))
304                } else {
305                    left
306                };
307                let right = if matches!(right, Expression::And(_)) {
308                    Expression::Paren(Box::new(Paren {
309                        this: right,
310                        trailing_comments: Vec::new(),
311                    }))
312                } else {
313                    right
314                };
315                Ok(Expression::Or(Box::new(BinaryOp {
316                    left,
317                    right,
318                    left_comments,
319                    operator_comments,
320                    trailing_comments,
321                    inferred_type: None,
322                })))
323            }
324
325            Expression::Not(op) => {
326                let inner = wrap_not_target(op.this);
327                Ok(Expression::Not(Box::new(UnaryOp {
328                    this: inner,
329                    inferred_type: None,
330                })))
331            }
332
333            Expression::MapFunc(map) if map.curly_brace_syntax => {
334                let MapConstructor { keys, values, .. } = *map;
335                let mut args = Vec::with_capacity(keys.len() * 2);
336                for (key, value) in keys.into_iter().zip(values.into_iter()) {
337                    args.push(key);
338                    args.push(value);
339                }
340                Ok(Expression::Function(Box::new(Function::new(
341                    "map".to_string(),
342                    args,
343                ))))
344            }
345
346            Expression::Insert(mut insert) => {
347                for row in insert.values.iter_mut() {
348                    for value in row.iter_mut() {
349                        if !matches!(value, Expression::Paren(_)) {
350                            let wrapped = Expression::Paren(Box::new(Paren {
351                                this: value.clone(),
352                                trailing_comments: Vec::new(),
353                            }));
354                            *value = wrapped;
355                        }
356                    }
357                }
358                Ok(Expression::Insert(insert))
359            }
360
361            // Generic function transformations
362            Expression::Function(f) => self.transform_function(*f),
363
364            // Generic aggregate function transformations
365            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
366
367            // Cast transformations
368            Expression::Cast(c) => self.transform_cast(*c),
369
370            // TYPEOF -> toTypeName in ClickHouse
371            Expression::Typeof(f) => Ok(Expression::Function(Box::new(Function::new(
372                "toTypeName".to_string(),
373                vec![f.this],
374            )))),
375
376            // Pass through everything else
377            _ => Ok(expr),
378        }
379    }
380}
381
382#[cfg(feature = "transpile")]
383impl ClickHouseDialect {}
384
385#[cfg(feature = "transpile")]
386impl ClickHouseDialect {
387    fn transform_function(&self, f: Function) -> Result<Expression> {
388        let name_upper = f.name.to_uppercase();
389        match name_upper.as_str() {
390            // UTCTimestamp() -> CURRENT_TIMESTAMP('UTC')
391            "UTCTIMESTAMP" => Ok(Expression::UtcTimestamp(Box::new(
392                crate::expressions::UtcTimestamp { this: None },
393            ))),
394
395            "CURRENTDATABASE" | "CURRENT_DATABASE" => Ok(Expression::Function(Box::new(
396                Function::new("CURRENT_DATABASE".to_string(), f.args),
397            ))),
398            "CURRENTSCHEMAS" | "CURRENT_SCHEMAS" => Ok(Expression::Function(Box::new(
399                Function::new("CURRENT_SCHEMAS".to_string(), f.args),
400            ))),
401            "LEVENSHTEIN" | "LEVENSHTEINDISTANCE" | "EDITDISTANCE" => Ok(Expression::Function(
402                Box::new(Function::new("editDistance".to_string(), f.args)),
403            )),
404            "CHAR" | "CHR" => Ok(Expression::Function(Box::new(Function::new(
405                "CHAR".to_string(),
406                f.args,
407            )))),
408            "STR_TO_DATE" => Ok(Expression::Function(Box::new(Function::new(
409                "STR_TO_DATE".to_string(),
410                f.args,
411            )))),
412            "JSONEXTRACTSTRING" => Ok(Expression::Function(Box::new(Function::new(
413                "JSONExtractString".to_string(),
414                f.args,
415            )))),
416            "MATCH" => Ok(Expression::Function(Box::new(Function::new(
417                "match".to_string(),
418                f.args,
419            )))),
420            "LIKE" if f.args.len() == 2 => {
421                let left = f.args[0].clone();
422                let right = f.args[1].clone();
423                Ok(Expression::Like(Box::new(LikeOp::new(left, right))))
424            }
425            "NOTLIKE" if f.args.len() == 2 => {
426                let left = f.args[0].clone();
427                let right = f.args[1].clone();
428                let like = Expression::Like(Box::new(LikeOp::new(left, right)));
429                Ok(Expression::Not(Box::new(UnaryOp {
430                    this: like,
431                    inferred_type: None,
432                })))
433            }
434            "ILIKE" if f.args.len() == 2 => {
435                let left = f.args[0].clone();
436                let right = f.args[1].clone();
437                Ok(Expression::ILike(Box::new(LikeOp::new(left, right))))
438            }
439            "AND" if f.args.len() >= 2 => {
440                let mut iter = f.args.into_iter();
441                let mut expr = iter.next().unwrap();
442                for arg in iter {
443                    expr = Expression::And(Box::new(BinaryOp::new(expr, arg)));
444                }
445                Ok(expr)
446            }
447            "OR" if f.args.len() >= 2 => {
448                let mut iter = f.args.into_iter();
449                let mut expr = iter.next().unwrap();
450                for arg in iter {
451                    expr = Expression::Or(Box::new(BinaryOp::new(expr, arg)));
452                }
453                self.transform_expr(expr)
454            }
455            "XOR" if f.args.len() >= 2 => {
456                let mut iter = f.args.into_iter().map(Self::wrap_nested_xor_arg);
457                let mut expr = iter.next().unwrap();
458                for arg in iter {
459                    expr = Expression::Function(Box::new(Function::new(
460                        f.name.clone(),
461                        vec![expr, arg],
462                    )));
463                }
464                Ok(expr)
465            }
466            // TYPEOF -> toTypeName in ClickHouse
467            "TYPEOF" => Ok(Expression::Function(Box::new(Function::new(
468                "toTypeName".to_string(),
469                f.args,
470            )))),
471
472            // DATE_TRUNC: ClickHouse uses dateTrunc (camelCase)
473            "DATE_TRUNC" | "DATETRUNC" => Ok(Expression::Function(Box::new(Function::new(
474                "dateTrunc".to_string(),
475                f.args,
476            )))),
477            "TOSTARTOFDAY" if f.args.len() == 1 => {
478                Ok(Expression::Function(Box::new(Function::new(
479                    "dateTrunc".to_string(),
480                    vec![Expression::string("DAY"), f.args[0].clone()],
481                ))))
482            }
483
484            // SUBSTRING_INDEX: preserve original case (substringIndex in ClickHouse)
485            "SUBSTRING_INDEX" => Ok(Expression::Function(Box::new(Function::new(
486                f.name.clone(),
487                f.args,
488            )))),
489
490            // IS_NAN / ISNAN -> isNaN (ClickHouse camelCase)
491            "IS_NAN" | "ISNAN" => Ok(Expression::Function(Box::new(Function::new(
492                "isNaN".to_string(),
493                f.args,
494            )))),
495
496            _ => Ok(Expression::Function(Box::new(f))),
497        }
498    }
499
500    fn transform_aggregate_function(
501        &self,
502        f: Box<crate::expressions::AggregateFunction>,
503    ) -> Result<Expression> {
504        let name_upper = f.name.to_uppercase();
505        match name_upper.as_str() {
506            // COUNT_IF -> countIf
507            "COUNT_IF" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
508                "countIf".to_string(),
509                f.args,
510            )))),
511
512            // SUM_IF -> sumIf
513            "SUM_IF" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
514                "sumIf".to_string(),
515                f.args,
516            )))),
517
518            // AVG_IF -> avgIf
519            "AVG_IF" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
520                "avgIf".to_string(),
521                f.args,
522            )))),
523
524            // ANY_VALUE -> any in ClickHouse
525            "ANY_VALUE" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
526                "any".to_string(),
527                f.args,
528            )))),
529
530            // GROUP_CONCAT -> groupArray + arrayStringConcat
531            "GROUP_CONCAT" if !f.args.is_empty() => {
532                let mut args = f.args;
533                let first = args.remove(0);
534                let separator = args.pop();
535                let group_array = Expression::Function(Box::new(Function::new(
536                    "groupArray".to_string(),
537                    vec![first],
538                )));
539                if let Some(sep) = separator {
540                    Ok(Expression::Function(Box::new(Function::new(
541                        "arrayStringConcat".to_string(),
542                        vec![group_array, sep],
543                    ))))
544                } else {
545                    Ok(Expression::Function(Box::new(Function::new(
546                        "arrayStringConcat".to_string(),
547                        vec![group_array],
548                    ))))
549                }
550            }
551
552            // STRING_AGG -> groupArray + arrayStringConcat
553            "STRING_AGG" if !f.args.is_empty() => {
554                let mut args = f.args;
555                let first = args.remove(0);
556                let separator = args.pop();
557                let group_array = Expression::Function(Box::new(Function::new(
558                    "groupArray".to_string(),
559                    vec![first],
560                )));
561                if let Some(sep) = separator {
562                    Ok(Expression::Function(Box::new(Function::new(
563                        "arrayStringConcat".to_string(),
564                        vec![group_array, sep],
565                    ))))
566                } else {
567                    Ok(Expression::Function(Box::new(Function::new(
568                        "arrayStringConcat".to_string(),
569                        vec![group_array],
570                    ))))
571                }
572            }
573
574            // LISTAGG -> groupArray + arrayStringConcat
575            "LISTAGG" if !f.args.is_empty() => {
576                let mut args = f.args;
577                let first = args.remove(0);
578                let separator = args.pop();
579                let group_array = Expression::Function(Box::new(Function::new(
580                    "groupArray".to_string(),
581                    vec![first],
582                )));
583                if let Some(sep) = separator {
584                    Ok(Expression::Function(Box::new(Function::new(
585                        "arrayStringConcat".to_string(),
586                        vec![group_array, sep],
587                    ))))
588                } else {
589                    Ok(Expression::Function(Box::new(Function::new(
590                        "arrayStringConcat".to_string(),
591                        vec![group_array],
592                    ))))
593                }
594            }
595
596            // ARRAY_AGG -> groupArray
597            "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
598                "groupArray".to_string(),
599                f.args,
600            )))),
601
602            // STDDEV -> stddevSamp in ClickHouse (sample stddev)
603            "STDDEV" if !f.args.is_empty() => {
604                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
605                    source_dialect: None,
606
607                    name: "stddevSamp".to_string(),
608                    args: f.args,
609                    distinct: f.distinct,
610                    filter: f.filter,
611                    order_by: Vec::new(),
612                    limit: None,
613                    ignore_nulls: None,
614                    inferred_type: None,
615                })))
616            }
617
618            // STDDEV_POP -> stddevPop
619            "STDDEV_POP" if !f.args.is_empty() => {
620                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
621                    source_dialect: None,
622
623                    name: "stddevPop".to_string(),
624                    args: f.args,
625                    distinct: f.distinct,
626                    filter: f.filter,
627                    order_by: Vec::new(),
628                    limit: None,
629                    ignore_nulls: None,
630                    inferred_type: None,
631                })))
632            }
633
634            // VARIANCE -> varSamp in ClickHouse
635            "VARIANCE" if !f.args.is_empty() => {
636                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
637                    source_dialect: None,
638
639                    name: "varSamp".to_string(),
640                    args: f.args,
641                    distinct: f.distinct,
642                    filter: f.filter,
643                    order_by: Vec::new(),
644                    limit: None,
645                    ignore_nulls: None,
646                    inferred_type: None,
647                })))
648            }
649
650            // VAR_POP -> varPop
651            "VAR_POP" if !f.args.is_empty() => {
652                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
653                    source_dialect: None,
654
655                    name: "varPop".to_string(),
656                    args: f.args,
657                    distinct: f.distinct,
658                    filter: f.filter,
659                    order_by: Vec::new(),
660                    limit: None,
661                    ignore_nulls: None,
662                    inferred_type: None,
663                })))
664            }
665
666            // MEDIAN -> median
667            "MEDIAN" if !f.args.is_empty() => {
668                Ok(Expression::AggregateFunction(Box::new(AggregateFunction {
669                    source_dialect: None,
670
671                    name: "median".to_string(),
672                    args: f.args,
673                    distinct: f.distinct,
674                    filter: f.filter,
675                    order_by: Vec::new(),
676                    limit: None,
677                    ignore_nulls: None,
678                    inferred_type: None,
679                })))
680            }
681
682            // APPROX_COUNT_DISTINCT -> uniq in ClickHouse
683            "APPROX_COUNT_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
684                Function::new("uniq".to_string(), f.args),
685            ))),
686
687            // APPROX_DISTINCT -> uniq
688            "APPROX_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
689                Function::new("uniq".to_string(), f.args),
690            ))),
691
692            _ => Ok(Expression::AggregateFunction(f)),
693        }
694    }
695
696    fn transform_cast(&self, c: Cast) -> Result<Expression> {
697        Ok(Expression::Cast(Box::new(c)))
698    }
699
700    fn wrap_nested_xor_arg(expr: Expression) -> Expression {
701        if matches!(&expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("XOR")) {
702            Expression::Paren(Box::new(Paren {
703                this: expr,
704                trailing_comments: Vec::new(),
705            }))
706        } else {
707            expr
708        }
709    }
710}