Repository: tobymao/sqlglot

Issue:
ClickHouse quantile/quantileIf functions fail to parse
ClickHouse has a somewhat unique way of spelling these:

```
9bf006fcf914 :) select quantile(0.5)(a) from (select 1 a union all select 2);

SELECT quantile(0.5)(a)
FROM
(
    SELECT 1 AS a
    UNION ALL
    SELECT 2
)

Query id: fe37056a-efe0-45bf-9d34-95de2916ecef

┌─quantile(0.5)(a)─┐
│              1.5 │
└──────────────────┘

1 row in set. Elapsed: 0.002 sec.

9bf006fcf914 :) select quantileIf(0.5)(a, true) from (select 1 a union all select 2);

SELECT quantileIf(0.5)(a, true)
FROM
(
    SELECT 1 AS a
    UNION ALL
    SELECT 2
)

Query id: d3b2e971-c9af-4a6c-8ba3-93caacc246bc

┌─quantileIf(0.5)(a, true)─┐
│                      1.5 │
└──────────────────────────┘

1 row in set. Elapsed: 0.002 sec.

9bf006fcf914 :) select quantileIf(0.5)(a, false) from (select 1 a union all select 2);

SELECT quantileIf(0.5)(a, false)
FROM
(
    SELECT 1 AS a
    UNION ALL
    SELECT 2
)

Query id: 27307fa4-2b25-4382-8e6c-ef3d4eb1b306

┌─quantileIf(0.5)(a, false)─┐
│                       nan │
└───────────────────────────┘

1 row in set. Elapsed: 0.002 sec.
```

When parsed with `sqlglot` I get a parse error:

```
In [1]: import sqlglot as sg

In [2]: sg.__version__
Out[2]: '10.3.2'

In [3]: sg.parse_one("select quantile(0.5)(a) from (select 1 a union all select 2)", read="clickhouse")
...
ParseError: Required keyword: 'quantile' missing for <class 'sqlglot.expressions.Quantile'>. Line 1, Col: 20.
  select quantile(0.5)(a) from (select 1 a union all select 2)
```


Discussion:
I'll take a look at this soon. Besides the `quantile` argument missing in the `Quantile` expression, I don't think SQLGlot can handle the syntax `foo(a)(b)` yet.

Resolving patch:
diff --git a/sqlglot/dialects/clickhouse.py b/sqlglot/dialects/clickhouse.py
--- a/sqlglot/dialects/clickhouse.py
+++ b/sqlglot/dialects/clickhouse.py
@@ -11,6 +11,13 @@ def _lower_func(sql):
     return sql[:index].lower() + sql[index:]
 
 
+def _quantile_sql(self, expression: exp.Quantile) -> str:
+    if_suffix = "If" if len(expression.this) == 2 else ""
+    args = self.format_args(self.expressions(expression, "quantile"))
+    params = self.format_args(self.expressions(expression, "this"))
+    return f"quantile{if_suffix}({args})({params})"
+
+
 class ClickHouse(Dialect):
     normalize_functions = None
     null_ordering = "nulls_are_last"
@@ -37,6 +44,8 @@ class Parser(parser.Parser):
         FUNCTIONS = {
             **parser.Parser.FUNCTIONS,  # type: ignore
             "MAP": parse_var_map,
+            "QUANTILE": lambda args, params: exp.Quantile(this=params, quantile=args),
+            "QUANTILEIF": lambda args, params: exp.Quantile(this=params, quantile=args),
         }
 
         JOIN_KINDS = {*parser.Parser.JOIN_KINDS, TokenType.ANY, TokenType.ASOF}  # type: ignore
@@ -76,6 +85,7 @@ class Generator(generator.Generator):
             exp.Final: lambda self, e: f"{self.sql(e, 'this')} FINAL",
             exp.Map: lambda self, e: _lower_func(var_map_sql(self, e)),
             exp.VarMap: lambda self, e: _lower_func(var_map_sql(self, e)),
+            exp.Quantile: _quantile_sql,
         }
 
         EXPLICIT_UNION = True
diff --git a/sqlglot/helper.py b/sqlglot/helper.py
--- a/sqlglot/helper.py
+++ b/sqlglot/helper.py
@@ -356,6 +356,15 @@ def flatten(values: t.Iterable[t.Iterable[t.Any] | t.Any]) -> t.Generator[t.Any,
             yield value
 
 
+def count_params(function: t.Callable) -> int:
+    """
+    Returns the number of formal parameters expected by a function, without counting "self"
+    and "cls", in case of instance and class methods, respectively.
+    """
+    count = function.__code__.co_argcount
+    return count - 1 if inspect.ismethod(function) else count
+
+
 def dict_depth(d: t.Dict) -> int:
     """
     Get the nesting depth of a dictionary.
@@ -374,6 +383,7 @@ def dict_depth(d: t.Dict) -> int:
 
     Args:
         d (dict): dictionary
+
     Returns:
         int: depth
     """
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -5,7 +5,13 @@
 
 from sqlglot import exp
 from sqlglot.errors import ErrorLevel, ParseError, concat_messages, merge_errors
-from sqlglot.helper import apply_index_offset, ensure_collection, ensure_list, seq_get
+from sqlglot.helper import (
+    apply_index_offset,
+    count_params,
+    ensure_collection,
+    ensure_list,
+    seq_get,
+)
 from sqlglot.tokens import Token, Tokenizer, TokenType
 from sqlglot.trie import in_trie, new_trie
 
@@ -2049,7 +2055,18 @@ def _parse_function(self, functions=None):
             args = self._parse_csv(self._parse_lambda)
 
             if function:
-                this = function(args)
+
+                # Clickhouse supports function calls like foo(x, y)(z), so for these we need to also parse the
+                # second parameter list (i.e. "(z)") and the corresponding function will receive both arg lists.
+                if count_params(function) == 2:
+                    params = None
+                    if self._match_pair(TokenType.R_PAREN, TokenType.L_PAREN):
+                        params = self._parse_csv(self._parse_lambda)
+
+                    this = function(args, params)
+                else:
+                    this = function(args)
+
                 self.validate_expression(this, args)
             else:
                 this = self.expression(exp.Anonymous, this=this, expressions=args)


Test patch:
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
@@ -14,6 +14,7 @@ def test_clickhouse(self):
         self.validate_identity("SELECT * FROM foo LEFT ASOF JOIN bla")
         self.validate_identity("SELECT * FROM foo ASOF JOIN bla")
         self.validate_identity("SELECT * FROM foo ANY JOIN bla")
+        self.validate_identity("SELECT quantile(0.5)(a)")
 
         self.validate_all(
             "SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname",
@@ -38,3 +39,9 @@ def test_clickhouse(self):
             "SELECT x #! comment",
             write={"": "SELECT x /* comment */"},
         )
+        self.validate_all(
+            "SELECT quantileIf(0.5)(a, true)",
+            write={
+                "clickhouse": "SELECT quantileIf(0.5)(a, TRUE)",
+            },
+        )