Repository: tobymao/sqlglot

Issue:
BigQuery `UNNEST(...) WITH OFFSET` without offset alias followed by set operator triggers ParseError
Observed on `main` at commit `f8105a7a937520119709345a3b713ba0624298a0`.

**Fully reproducible code snippet**
```python
sqlglot.parse_one("SELECT * FROM UNNEST(x) WITH OFFSET EXCEPT DISTINCT SELECT * FROM UNNEST(y) WITH OFFSET", read="bigquery")
```
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "sqlglot/__init__.py", line 125, in parse_one
    result = dialect.parse(sql, **opts)
  File "sqlglot/dialects/dialect.py", line 304, in parse
    return self.parser(**opts).parse(self.tokenize(sql), sql)
  File "sqlglot/parser.py", line 965, in parse
    return self._parse(
  File "sqlglot/parser.py", line 1034, in _parse
    self.raise_error("Invalid expression / Unexpected token")
  File "sqlglot/parser.py", line 1075, in raise_error
    raise error
sqlglot.errors.ParseError: Invalid expression / Unexpected token. Line 1, Col: 51.
  SELECT * FROM UNNEST(x) WITH OFFSET EXCEPT DISTINCT SELECT * FROM UNNEST(y) WITH OFFSET
```

**Official Documentation**
- https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unnest_operator



Discussion:

Resolving patch:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -872,6 +872,8 @@ class Parser(metaclass=_Parser):
 
     NULL_TOKENS = {TokenType.NULL}
 
+    UNNEST_OFFSET_ALIAS_TOKENS = ID_VAR_TOKENS - SET_OPERATIONS
+
     STRICT_CAST = True
 
     # A NULL arg in CONCAT yields NULL by default
@@ -2702,7 +2704,9 @@ def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]:
 
         if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):
             self._match(TokenType.ALIAS)
-            offset = self._parse_id_var() or exp.to_identifier("offset")
+            offset = self._parse_id_var(
+                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS
+            ) or exp.to_identifier("offset")
 
         return self.expression(exp.Unnest, expressions=expressions, alias=alias, offset=offset)
 


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
@@ -126,6 +126,10 @@ def test_bigquery(self):
             "CREATE OR REPLACE TABLE `a.b.c` CLONE `a.b.d`",
             "CREATE OR REPLACE TABLE a.b.c CLONE a.b.d",
         )
+        self.validate_identity(
+            "SELECT * FROM UNNEST(x) WITH OFFSET EXCEPT DISTINCT SELECT * FROM UNNEST(y) WITH OFFSET",
+            "SELECT * FROM UNNEST(x) WITH OFFSET AS offset EXCEPT DISTINCT SELECT * FROM UNNEST(y) WITH OFFSET AS offset",
+        )
 
         self.validate_all("SELECT SPLIT(foo)", write={"bigquery": "SELECT SPLIT(foo, ',')"})
         self.validate_all("SELECT 1 AS hash", write={"bigquery": "SELECT 1 AS `hash`"})
diff --git a/tests/fixtures/identity.sql b/tests/fixtures/identity.sql
--- a/tests/fixtures/identity.sql
+++ b/tests/fixtures/identity.sql
@@ -865,3 +865,4 @@ KILL '123'
 KILL CONNECTION 123
 KILL QUERY '123'
 CHR(97)
+SELECT * FROM UNNEST(x) WITH ORDINALITY UNION ALL SELECT * FROM UNNEST(y) WITH ORDINALITY