Repository: tobymao/sqlglot

Issue:
PERCENTILE_CONT and PERCENTILE_DISC expect wrong number of arguments for BigQuery dialect
observed on both version 11.7.1 and commit `e7111ba6afdb67ae7be52cf39384d7bcb86a8dac`

**Fully reproducible code snippet**
```python
sqlglot.parse_one("SELECT PERCENTILE_CONT(x, 0.5) OVER ()", read="bigquery")
sqlglot.parse_one("SELECT PERCENTILE_DISC(x, 0.5) OVER ()", read="bigquery")
```

traceback:
```
sqlglot.parse_one("SELECT PERCENTILE_CONT(x, 0.5) OVER ()", read="bigquery")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.10/site-packages/sqlglot/__init__.py", line 150, in parse_one
    result = dialect.parse(sql, **opts)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/dialects/dialect.py", line 163, in parse
    return self.parser(**opts).parse(self.tokenize(sql), sql)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 836, in parse
    return self._parse(
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 899, in _parse
    expressions.append(parse_method(self))
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 1047, in _parse_statement
    expression = self._parse_set_operations(expression) if expression else self._parse_select()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 1824, in _parse_select
    expressions = self._parse_csv(self._parse_expression)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3974, in _parse_csv
    parse_result = parse_method()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2622, in _parse_expression
    return self._parse_alias(self._parse_conjunction())
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2625, in _parse_conjunction
    return self._parse_tokens(self._parse_equality, self.CONJUNCTION)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3990, in _parse_tokens
    this = parse_method()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2628, in _parse_equality
    return self._parse_tokens(self._parse_comparison, self.EQUALITY)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3990, in _parse_tokens
    this = parse_method()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2631, in _parse_comparison
    return self._parse_tokens(self._parse_range, self.COMPARISON)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3990, in _parse_tokens
    this = parse_method()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2634, in _parse_range
    this = self._parse_bitwise()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2726, in _parse_bitwise
    this = self._parse_term()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2749, in _parse_term
    return self._parse_tokens(self._parse_factor, self.TERM)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3990, in _parse_tokens
    this = parse_method()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2752, in _parse_factor
    return self._parse_tokens(self._parse_unary, self.FACTOR)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3990, in _parse_tokens
    this = parse_method()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2757, in _parse_unary
    return self._parse_at_time_zone(self._parse_type())
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2766, in _parse_type
    this = self._parse_column()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 2911, in _parse_column
    this = self._parse_field()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3015, in _parse_field
    or self._parse_function()
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 3074, in _parse_function
    self.validate_expression(this, args)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 987, in validate_expression
    self.raise_error(error_message)
  File "/usr/local/lib/python3.10/site-packages/sqlglot/parser.py", line 945, in raise_error
    raise error
sqlglot.errors.ParseError: The number of provided arguments (2) is greater than the maximum number of supported arguments (1). Line 1, Col: 31.
  SELECT PERCENTILE_CONT(x, 0.5) OVER ()
```

**Official Documentation**
https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#percentile_cont
https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#percentile_disc


Discussion:
Thanks for reporting all these issues, we'll take a look at them soon.

Resolving patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -3930,11 +3930,11 @@ class Pow(Binary, Func):
 
 
 class PercentileCont(AggFunc):
-    pass
+    arg_types = {"this": True, "expression": False}
 
 
 class PercentileDisc(AggFunc):
-    pass
+    arg_types = {"this": True, "expression": False}
 
 
 class Quantile(AggFunc):
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -3148,12 +3148,7 @@ def _parse_lambda(self) -> t.Optional[exp.Expression]:
                 if isinstance(left, exp.Column):
                     left.replace(exp.Var(this=left.text("this")))
 
-        if self._match(TokenType.IGNORE_NULLS):
-            this = self.expression(exp.IgnoreNulls, this=this)
-        else:
-            self._match(TokenType.RESPECT_NULLS)
-
-        return self._parse_limit(self._parse_order(this))
+        return self._parse_limit(self._parse_order(self._parse_respect_or_ignore_nulls(this)))
 
     def _parse_schema(self, this: t.Optional[exp.Expression] = None) -> t.Optional[exp.Expression]:
         index = self._index
@@ -3741,6 +3736,15 @@ def _parse_window_clause(self) -> t.Optional[t.List[t.Optional[exp.Expression]]]
     def _parse_named_window(self) -> t.Optional[exp.Expression]:
         return self._parse_window(self._parse_id_var(), alias=True)
 
+    def _parse_respect_or_ignore_nulls(
+        self, this: t.Optional[exp.Expression]
+    ) -> t.Optional[exp.Expression]:
+        if self._match(TokenType.IGNORE_NULLS):
+            return self.expression(exp.IgnoreNulls, this=this)
+        if self._match(TokenType.RESPECT_NULLS):
+            return self.expression(exp.RespectNulls, this=this)
+        return this
+
     def _parse_window(
         self, this: t.Optional[exp.Expression], alias: bool = False
     ) -> t.Optional[exp.Expression]:
@@ -3768,10 +3772,7 @@ def _parse_window(
         #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)
         #   and Snowflake chose to do the same for familiarity
         #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes
-        if self._match(TokenType.IGNORE_NULLS):
-            this = self.expression(exp.IgnoreNulls, this=this)
-        elif self._match(TokenType.RESPECT_NULLS):
-            this = self.expression(exp.RespectNulls, this=this)
+        this = self._parse_respect_or_ignore_nulls(this)
 
         # bigquery select from window x AS (partition by ...)
         if alias:


Test patch:
diff --git a/tests/dialects/test_snowflake.py b/tests/dialects/test_snowflake.py
--- a/tests/dialects/test_snowflake.py
+++ b/tests/dialects/test_snowflake.py
@@ -401,7 +401,7 @@ def test_null_treatment(self):
         self.validate_all(
             r"SELECT FIRST_VALUE(TABLE1.COLUMN1 RESPECT NULLS) OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1",
             write={
-                "snowflake": r"SELECT FIRST_VALUE(TABLE1.COLUMN1) OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1"
+                "snowflake": r"SELECT FIRST_VALUE(TABLE1.COLUMN1 RESPECT NULLS) OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1"
             },
         )
         self.validate_all(
diff --git a/tests/fixtures/identity.sql b/tests/fixtures/identity.sql
--- a/tests/fixtures/identity.sql
+++ b/tests/fixtures/identity.sql
@@ -215,6 +215,7 @@ SELECT COUNT(DISTINCT a, b)
 SELECT COUNT(DISTINCT a, b + 1)
 SELECT SUM(DISTINCT x)
 SELECT SUM(x IGNORE NULLS) AS x
+SELECT COUNT(x RESPECT NULLS)
 SELECT TRUNCATE(a, b)
 SELECT ARRAY_AGG(DISTINCT x IGNORE NULLS ORDER BY a, b DESC LIMIT 10) AS x
 SELECT ARRAY_AGG(STRUCT(x, x AS y) ORDER BY z DESC) AS x
@@ -820,3 +821,6 @@ JSON_OBJECT('x': 1 RETURNING VARBINARY FORMAT JSON ENCODING UTF8)
 SELECT if.x
 SELECT NEXT VALUE FOR db.schema.sequence_name
 SELECT NEXT VALUE FOR db.schema.sequence_name OVER (ORDER BY foo), col
+SELECT PERCENTILE_CONT(x, 0.5) OVER ()
+SELECT PERCENTILE_CONT(x, 0.5 RESPECT NULLS) OVER ()
+SELECT PERCENTILE_CONT(x, 0.5 IGNORE NULLS) OVER ()
diff --git a/tests/test_transpile.py b/tests/test_transpile.py
--- a/tests/test_transpile.py
+++ b/tests/test_transpile.py
@@ -324,9 +324,6 @@ def test_if(self):
         )
         self.validate("SELECT IF(a > 1, 1) FROM foo", "SELECT CASE WHEN a > 1 THEN 1 END FROM foo")
 
-    def test_ignore_nulls(self):
-        self.validate("SELECT COUNT(x RESPECT NULLS)", "SELECT COUNT(x)")
-
     def test_with(self):
         self.validate(
             "WITH a AS (SELECT 1) WITH b AS (SELECT 2) SELECT *",