Repository: tobymao/sqlglot
Pull request: Fix(parser): handle another edge case in struct field type parser
fixes #3680

Problem statement:
sqlglot fails to parse BigQuery type cast
**Fully reproducible code snippet**

```python
import sqlglot
sqlglot.parse_one(sql="select sources::STRUCT<list ARRAY<STRUCT<element STRUCT<property STRING, dataset STRING, record_id STRING, confidence FLOAT64>>>> from bar", dialect="bigquery")
```

Produces

```
sqlglot.errors.ParseError: Required keyword: 'expression' missing for <class 'sqlglot.expressions.BitwiseRightShift'>. Line 1, Col: 134
```

Hints from issue discussion:

Commit message: Fix(parser): handle another edge case in struct field type parser

Patch to review:
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 0d2cc2b9e9..a27a47f013 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -4486,10 +4486,22 @@ def _parse_types(
 
     def _parse_struct_types(self, type_required: bool = False) -> t.Optional[exp.Expression]:
         index = self._index
-        this = (
-            self._parse_type(parse_interval=False, fallback_to_identifier=True)
-            or self._parse_id_var()
-        )
+
+        if (
+            self._curr
+            and self._next
+            and self._curr.token_type in self.TYPE_TOKENS
+            and self._next.token_type in self.TYPE_TOKENS
+        ):
+            # Care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
+            # type token. Without this, the list will be parsed as a type and we'll eventually crash
+            this = self._parse_id_var()
+        else:
+            this = (
+                self._parse_type(parse_interval=False, fallback_to_identifier=True)
+                or self._parse_id_var()
+            )
+
         self._match(TokenType.COLON)
 
         if (
diff --git a/tests/dialects/test_bigquery.py b/tests/dialects/test_bigquery.py
index 42cb27f75d..a7affe6809 100644
--- a/tests/dialects/test_bigquery.py
+++ b/tests/dialects/test_bigquery.py
@@ -103,6 +103,7 @@ def test_bigquery(self):
         select_with_quoted_udf = self.validate_identity("SELECT `p.d.UdF`(data) FROM `p.d.t`")
         self.assertEqual(select_with_quoted_udf.selects[0].name, "p.d.UdF")
 
+        self.validate_identity("CAST(x AS STRUCT<list ARRAY<INT64>>)")
         self.validate_identity("assert.true(1 = 1)")
         self.validate_identity("SELECT ARRAY_TO_STRING(list, '--') AS text")
         self.validate_identity("SELECT jsondoc['some_key']")


Code review comments:
diff_hunk: @@ -4486,10 +4486,22 @@ def _parse_types(
 
     def _parse_struct_types(self, type_required: bool = False) -> t.Optional[exp.Expression]:
         index = self._index
-        this = (
-            self._parse_type(parse_interval=False, fallback_to_identifier=True)
-            or self._parse_id_var()
-        )
+
+        if (
+            self._curr
+            and self._next
+            and self._curr.token_type in self.TYPE_TOKENS
+            and self._next.token_type in self.TYPE_TOKENS
+        ):
+            # Care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
path: sqlglot/parser.py
text: @author:
```suggestion
            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a
```