Repository: tobymao/sqlglot

Issue:
invalid transpilation of clickhouse `isNaN` function
**Fully reproducible code snippet**

```
In [9]: import sqlglot as sg

In [10]: sg.__version__
Out[10]: '17.8.6'

In [11]: sg.parse_one("select isNaN(1.0)", read="clickhouse").sql(dialect="clickhouse")
Out[11]: 'SELECT IS_NAN(1.0)'
```

Expected output is:

```
Out[11]: 'SELECT isNaN(1.0)'
```

**Official Documentation**

https://clickhouse.com/docs/en/sql-reference/functions/other-functions#isnanx

Is there a way I can disable function normalization for any and all function calls for clickhouse?

These incorrect translation bugs keep popping up and I'd like for function calls to pass through unchanged if possible.



Discussion:
Looks like `startsWith` is another example with 17.8.6

Resolving patch:
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -2379,7 +2379,12 @@ def function_fallback_sql(self, expression: exp.Func) -> str:
             elif arg_value is not None:
                 args.append(arg_value)
 
-        return self.func(expression.sql_name(), *args)
+        if self.normalize_functions:
+            name = expression.sql_name()
+        else:
+            name = (expression._meta and expression.meta.get("name")) or expression.sql_name()
+
+        return self.func(name, *args)
 
     def func(
         self,
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -835,6 +835,7 @@ class Parser(metaclass=_Parser):
     UNNEST_COLUMN_ONLY: bool = False
     ALIAS_POST_TABLESAMPLE: bool = False
     STRICT_STRING_CONCAT = False
+    NORMALIZE_FUNCTIONS = "upper"
     NULL_ORDERING: str = "nulls_are_small"
     SHOW_TRIE: t.Dict = {}
     SET_TRIE: t.Dict = {}
@@ -3360,7 +3361,10 @@ def _parse_function(
             args = self._parse_csv(lambda: self._parse_lambda(alias=alias))
 
             if function and not anonymous:
-                this = self.validate_expression(function(args), args)
+                func = self.validate_expression(function(args), args)
+                if not self.NORMALIZE_FUNCTIONS:
+                    func.meta["name"] = this
+                this = func
             else:
                 this = self.expression(exp.Anonymous, this=this, expressions=args)
 


Test patch:
diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py
--- a/tests/dialects/test_bigquery.py
+++ b/tests/dialects/test_bigquery.py
@@ -390,7 +390,7 @@ def test_bigquery(self):
             },
         )
         self.validate_all(
-            "current_timestamp",
+            "CURRENT_TIMESTAMP",
             write={
                 "bigquery": "CURRENT_TIMESTAMP()",
                 "duckdb": "CURRENT_TIMESTAMP",
@@ -401,7 +401,7 @@ def test_bigquery(self):
             },
         )
         self.validate_all(
-            "current_timestamp()",
+            "CURRENT_TIMESTAMP()",
             write={
                 "bigquery": "CURRENT_TIMESTAMP()",
                 "duckdb": "CURRENT_TIMESTAMP",
diff --git a/tests/dialects/test_clickhouse.py b/tests/dialects/test_clickhouse.py
--- a/tests/dialects/test_clickhouse.py
+++ b/tests/dialects/test_clickhouse.py
@@ -6,6 +6,12 @@ class TestClickhouse(Validator):
     dialect = "clickhouse"
 
     def test_clickhouse(self):
+        expr = parse_one("count(x)")
+        self.assertEqual(expr.sql(dialect="clickhouse"), "COUNT(x)")
+        self.assertIsNone(expr._meta)
+
+        self.validate_identity("SELECT isNaN(1.0)")
+        self.validate_identity("SELECT startsWith('Spider-Man', 'Spi')")
         self.validate_identity("SELECT xor(TRUE, FALSE)")
         self.validate_identity("ATTACH DATABASE DEFAULT ENGINE = ORDINARY")
         self.validate_identity("CAST(['hello'], 'Array(Enum8(''hello'' = 1))')")
@@ -162,7 +168,7 @@ def test_clickhouse(self):
             ORDER BY loyalty ASC
             """,
             write={
-                "clickhouse": "SELECT loyalty, COUNT() FROM hits LEFT SEMI JOIN users USING (UserID)"
+                "clickhouse": "SELECT loyalty, count() FROM hits LEFT SEMI JOIN users USING (UserID)"
                 + " GROUP BY loyalty ORDER BY loyalty"
             },
         )
@@ -247,7 +253,7 @@ def test_signed_and_unsigned_types(self):
         for data_type in data_types:
             self.validate_all(
                 f"pow(2, 32)::{data_type}",
-                write={"clickhouse": f"CAST(POWER(2, 32) AS {data_type})"},
+                write={"clickhouse": f"CAST(pow(2, 32) AS {data_type})"},
             )
 
     def test_ddl(self):
@@ -304,8 +310,8 @@ def test_ddl(self):
   id,
   toStartOfDay(timestamp)
 SET
-  max_hits = MAX(max_hits),
-  sum_hits = SUM(sum_hits)""",
+  max_hits = max(max_hits),
+  sum_hits = sum(sum_hits)""",
             },
             pretty=True,
         )
@@ -447,8 +453,8 @@ def test_ddl(self):
   k1,
   k2
 SET
-  x = MAX(x),
-  y = MIN(y)""",
+  x = max(x),
+  y = min(y)""",
             },
             pretty=True,
         )
diff --git a/tests/dialects/test_drill.py b/tests/dialects/test_drill.py
--- a/tests/dialects/test_drill.py
+++ b/tests/dialects/test_drill.py
@@ -66,7 +66,7 @@ def test_validate_pivot(self):
             write={
                 "drill": "SELECT * FROM (SELECT education_level, salary, marital_status, "
                 "EXTRACT(year FROM age(birth_date)) AS age FROM cp.`employee.json`) "
-                "PIVOT(AVG(salary) AS avg_salary, AVG(age) AS avg_age FOR marital_status "
+                "PIVOT(avg(salary) AS avg_salary, avg(age) AS avg_age FOR marital_status "
                 "IN ('M' AS married, 'S' AS single))"
             },
         )