Repository: tobymao/sqlglot

Issue:
Cannot parse structs of arrays
`SELECT STRUCT<ARRAY<STRING>>(["2023-01-17"]);` executes on bigquery.

sqlglot raises ParseError `Expecting >`

(Using sqlglot-10.5.0 from pip install defaults)


Discussion:

Resolving patch:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -2127,6 +2127,7 @@ class DataType(Expression):
         "this": True,
         "expressions": False,
         "nested": False,
+        "values": False,
     }
 
     class Type(AutoName):
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -517,13 +517,19 @@ def datatype_sql(self, expression: exp.DataType) -> str:
         type_sql = self.TYPE_MAPPING.get(type_value, type_value.value)
         nested = ""
         interior = self.expressions(expression, flat=True)
+        values = ""
         if interior:
-            nested = (
-                f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
-                if expression.args.get("nested")
-                else f"({interior})"
-            )
-        return f"{type_sql}{nested}"
+            if expression.args.get("nested"):
+                nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}"
+                if expression.args.get("values") is not None:
+                    delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
+                    values = (
+                        f"{delimiters[0]}{self.expressions(expression, 'values')}{delimiters[1]}"
+                    )
+            else:
+                nested = f"({interior})"
+
+        return f"{type_sql}{nested}{values}"
 
     def directory_sql(self, expression: exp.Directory) -> str:
         local = "LOCAL " if expression.args.get("local") else ""
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -2056,6 +2056,7 @@ def _parse_types(self, check_func: bool = False) -> t.Optional[exp.Expression]:
             self._retreat(index)
             return None
 
+        values: t.Optional[t.List[t.Optional[exp.Expression]]] = None
         if nested and self._match(TokenType.LT):
             if is_struct:
                 expressions = self._parse_csv(self._parse_struct_kwargs)
@@ -2065,6 +2066,10 @@ def _parse_types(self, check_func: bool = False) -> t.Optional[exp.Expression]:
             if not self._match(TokenType.GT):
                 self.raise_error("Expecting >")
 
+            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):
+                values = self._parse_csv(self._parse_conjunction)
+                self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))
+
         value: t.Optional[exp.Expression] = None
         if type_token in self.TIMESTAMPS:
             if self._match(TokenType.WITH_TIME_ZONE) or type_token == TokenType.TIMESTAMPTZ:
@@ -2103,9 +2108,13 @@ def _parse_types(self, check_func: bool = False) -> t.Optional[exp.Expression]:
             this=exp.DataType.Type[type_token.value.upper()],
             expressions=expressions,
             nested=nested,
+            values=values,
         )
 
     def _parse_struct_kwargs(self) -> t.Optional[exp.Expression]:
+        if self._curr and self._curr.token_type in self.TYPE_TOKENS:
+            return self._parse_types()
+
         this = self._parse_id_var()
         self._match(TokenType.COLON)
         data_type = self._parse_types()


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
@@ -6,6 +6,7 @@ class TestBigQuery(Validator):
     dialect = "bigquery"
 
     def test_bigquery(self):
+        self.validate_identity("SELECT STRUCT<ARRAY<STRING>>(['2023-01-17'])")
         self.validate_identity("SELECT * FROM q UNPIVOT(values FOR quarter IN (b, c))")
         self.validate_all(
             "REGEXP_CONTAINS('foo', '.*')",
diff --git a/tests/fixtures/identity.sql b/tests/fixtures/identity.sql
--- a/tests/fixtures/identity.sql
+++ b/tests/fixtures/identity.sql
@@ -655,3 +655,13 @@ SELECT div.a FROM test_table AS div
 WITH view AS (SELECT 1 AS x) SELECT * FROM view
 CREATE TABLE asd AS SELECT asd FROM asd WITH NO DATA
 CREATE TABLE asd AS SELECT asd FROM asd WITH DATA
+ARRAY<STRUCT<INT, DOUBLE, ARRAY<INT>>>
+ARRAY<INT>[1, 2, 3]
+ARRAY<INT>[]
+STRUCT<x VARCHAR(10)>
+STRUCT<x VARCHAR(10)>("bla")
+STRUCT<VARCHAR(10)>("bla")
+STRUCT<INT>(5)
+STRUCT<DATE>("2011-05-05")
+STRUCT<x INT, y TEXT>(1, t.str_col)
+SELECT CAST(NULL AS ARRAY<INT>) IS NULL AS array_is_null