Repository: tobymao/sqlglot

Issue:
Window frame fails to parse
```
In [27]: import sqlglot as sg

In [28]: sg.__version__
Out[28]: '10.4.3'

In [29]: sg.parse_one("SELECT avg(x) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t", read="duckdb")
...
ParseError: Expecting ). Line 1, Col: 26.
  SELECT avg(x) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t
```


Discussion:

Resolving patch:
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -1031,7 +1031,9 @@ def window_sql(self, expression: exp.Window) -> str:
         if not partition and not order and not spec and alias:
             return f"{this} {alias}"
 
-        return f"{this} ({alias}{partition_sql}{order_sql}{spec_sql})"
+        window_args = alias + partition_sql + order_sql + spec_sql
+
+        return f"{this} ({window_args.strip()})"
 
     def window_spec_sql(self, expression: exp.WindowSpec) -> str:
         kind = self.sql(expression, "kind")
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -594,6 +594,8 @@ class Parser(metaclass=_Parser):
 
     TRANSACTION_KIND = {"DEFERRED", "IMMEDIATE", "EXCLUSIVE"}
 
+    WINDOW_ALIAS_TOKENS = ID_VAR_TOKENS - {TokenType.ROWS}
+
     # allows tables to have special tokens as prefixes
     TABLE_PREFIX_TOKENS: t.Set[TokenType] = set()
 
@@ -2676,7 +2678,7 @@ def _parse_window(
         if not self._match(TokenType.L_PAREN):
             return self.expression(exp.Window, this=this, alias=self._parse_id_var(False))
 
-        window_alias = self._parse_id_var(False)
+        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)
 
         partition = None
         if self._match(TokenType.PARTITION_BY):


Test patch:
diff --git a/tests/dataframe/unit/test_window.py b/tests/dataframe/unit/test_window.py
--- a/tests/dataframe/unit/test_window.py
+++ b/tests/dataframe/unit/test_window.py
@@ -15,11 +15,11 @@ def test_window_spec_order_by(self):
 
     def test_window_spec_rows_between(self):
         rows_between = WindowSpec().rowsBetween(3, 5)
-        self.assertEqual("OVER ( ROWS BETWEEN 3 PRECEDING AND 5 FOLLOWING)", rows_between.sql())
+        self.assertEqual("OVER (ROWS BETWEEN 3 PRECEDING AND 5 FOLLOWING)", rows_between.sql())
 
     def test_window_spec_range_between(self):
         range_between = WindowSpec().rangeBetween(3, 5)
-        self.assertEqual("OVER ( RANGE BETWEEN 3 PRECEDING AND 5 FOLLOWING)", range_between.sql())
+        self.assertEqual("OVER (RANGE BETWEEN 3 PRECEDING AND 5 FOLLOWING)", range_between.sql())
 
     def test_window_partition_by(self):
         partition_by = Window.partitionBy(F.col("cola"), F.col("colb"))
@@ -31,46 +31,46 @@ def test_window_order_by(self):
 
     def test_window_rows_between(self):
         rows_between = Window.rowsBetween(3, 5)
-        self.assertEqual("OVER ( ROWS BETWEEN 3 PRECEDING AND 5 FOLLOWING)", rows_between.sql())
+        self.assertEqual("OVER (ROWS BETWEEN 3 PRECEDING AND 5 FOLLOWING)", rows_between.sql())
 
     def test_window_range_between(self):
         range_between = Window.rangeBetween(3, 5)
-        self.assertEqual("OVER ( RANGE BETWEEN 3 PRECEDING AND 5 FOLLOWING)", range_between.sql())
+        self.assertEqual("OVER (RANGE BETWEEN 3 PRECEDING AND 5 FOLLOWING)", range_between.sql())
 
     def test_window_rows_unbounded(self):
         rows_between_unbounded_start = Window.rowsBetween(Window.unboundedPreceding, 2)
         self.assertEqual(
-            "OVER ( ROWS BETWEEN UNBOUNDED PRECEDING AND 2 FOLLOWING)",
+            "OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND 2 FOLLOWING)",
             rows_between_unbounded_start.sql(),
         )
         rows_between_unbounded_end = Window.rowsBetween(1, Window.unboundedFollowing)
         self.assertEqual(
-            "OVER ( ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING)",
+            "OVER (ROWS BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING)",
             rows_between_unbounded_end.sql(),
         )
         rows_between_unbounded_both = Window.rowsBetween(
             Window.unboundedPreceding, Window.unboundedFollowing
         )
         self.assertEqual(
-            "OVER ( ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)",
+            "OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)",
             rows_between_unbounded_both.sql(),
         )
 
     def test_window_range_unbounded(self):
         range_between_unbounded_start = Window.rangeBetween(Window.unboundedPreceding, 2)
         self.assertEqual(
-            "OVER ( RANGE BETWEEN UNBOUNDED PRECEDING AND 2 FOLLOWING)",
+            "OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND 2 FOLLOWING)",
             range_between_unbounded_start.sql(),
         )
         range_between_unbounded_end = Window.rangeBetween(1, Window.unboundedFollowing)
         self.assertEqual(
-            "OVER ( RANGE BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING)",
+            "OVER (RANGE BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING)",
             range_between_unbounded_end.sql(),
         )
         range_between_unbounded_both = Window.rangeBetween(
             Window.unboundedPreceding, Window.unboundedFollowing
         )
         self.assertEqual(
-            "OVER ( RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)",
+            "OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)",
             range_between_unbounded_both.sql(),
         )
diff --git a/tests/fixtures/identity.sql b/tests/fixtures/identity.sql
--- a/tests/fixtures/identity.sql
+++ b/tests/fixtures/identity.sql
@@ -426,6 +426,7 @@ SELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT
 SELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN 1 AND 3)
 SELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN 1 FOLLOWING AND 3)
 SELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)
+SELECT AVG(x) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t
 SELECT LISTAGG(x) WITHIN GROUP (ORDER BY x) AS y
 SELECT LISTAGG(x) WITHIN GROUP (ORDER BY x DESC)
 SELECT SUM(x) FILTER(WHERE x > 1)