Repository: tobymao/sqlglot

Issue:
`integer[3]` fails to parse into a data type for DuckDB
`integer[]` works, but `integer[3]` doesn't work.

[DuckDB docs](https://duckdb.org/docs/sql/data_types/array.html) on their fixed-length arrays.

```
In [1]: import sqlglot.expressions as sge, sqlglot as sg

In [2]: sg.__version__
Out[2]: '25.7.1'

In [3]: sg.parse_one("integer[]", into=sge.DataType, read="duckdb")
Out[3]:
DataType(
  this=Type.ARRAY,
  expressions=[
    DataType(this=Type.INT, nested=False)],
  nested=True)

In [4]: sg.parse_one("integer[3]", into=sge.DataType, read="duckdb")
...
ParseError: Failed to parse 'integer[3]' into <class 'sqlglot.expressions.DataType'>
```

Discussion:

Patch:
diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py
index ff599aa3..e8a7ac0f 100644
--- a/sqlglot/dialects/duckdb.py
+++ b/sqlglot/dialects/duckdb.py
@@ -158,7 +158,7 @@ def _struct_sql(self: DuckDB.Generator, expression: exp.Struct) -> str:
 
 def _datatype_sql(self: DuckDB.Generator, expression: exp.DataType) -> str:
     if expression.is_type("array"):
-        return f"{self.expressions(expression, flat=True)}[]"
+        return f"{self.expressions(expression, flat=True)}[{self.expressions(expression, key='values', flat=True)}]"
 
     # Type TIMESTAMP / TIME WITH TIME ZONE does not support any modifiers
     if expression.is_type("timestamptz", "timetz"):
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 30e1a9b3..2d22d188 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -673,7 +673,7 @@ class Parser(metaclass=_Parser):
         exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),
         exp.Column: lambda self: self._parse_column(),
         exp.Condition: lambda self: self._parse_assignment(),
-        exp.DataType: lambda self: self._parse_types(allow_identifiers=False),
+        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),
         exp.Expression: lambda self: self._parse_expression(),
         exp.From: lambda self: self._parse_from(joins=True),
         exp.Group: lambda self: self._parse_group(),
@@ -4615,7 +4615,11 @@ class Parser(metaclass=_Parser):
 
             matched_array = False
             values = self._parse_csv(self._parse_assignment) or None
-            if values and not schema:
+            if (
+                values
+                and not schema
+                and this.is_type(exp.DataType.Type.ARRAY, exp.DataType.Type.MAP)
+            ):
                 self._retreat(index)
                 break
 


Test patch:
diff --git a/tests/dialects/test_duckdb.py b/tests/dialects/test_duckdb.py
index 2d2e1efe..6c379423 100644
--- a/tests/dialects/test_duckdb.py
+++ b/tests/dialects/test_duckdb.py
@@ -8,6 +8,8 @@ class TestDuckDB(Validator):
     dialect = "duckdb"
 
     def test_duckdb(self):
+        self.validate_identity("x::int[3]", "CAST(x AS INT[3])")
+
         with self.assertRaises(ParseError):
             parse_one("1 //", read="duckdb")
 
diff --git a/tests/test_expressions.py b/tests/test_expressions.py
index 9ac21106..37a9720a 100644
--- a/tests/test_expressions.py
+++ b/tests/test_expressions.py
@@ -1001,6 +1001,8 @@ FROM foo""",
         self.assertEqual(exp.DataType.build("ARRAY<UNKNOWN>").sql(), "ARRAY<UNKNOWN>")
         self.assertEqual(exp.DataType.build("ARRAY<NULL>").sql(), "ARRAY<NULL>")
         self.assertEqual(exp.DataType.build("varchar(100) collate 'en-ci'").sql(), "VARCHAR(100)")
+        self.assertEqual(exp.DataType.build("int[3]").sql(dialect="duckdb"), "INT[3]")
+        self.assertEqual(exp.DataType.build("int[3][3]").sql(dialect="duckdb"), "INT[3][3]")
 
         with self.assertRaises(ParseError):
             exp.DataType.build("varchar(")