Repository: tobymao/sqlglot

Issue:
Oracle: VARCHAR(xxx CHAR) results in ParseError
In Oracle, you can specify the size of a VARCHAR type as VARCHAR(1234) but you can also specify whether the 1234 are BYTE or CHAR. These last keywords give an error in sqlglot.

**Fully reproducible code snippet**
Please include a fully reproducible code snippet or the input sql, dialect, and expected output.
```
import sqlglot
sqlglot.parse_one("SELECT CAST(NULL AS VARCHAR2(2328 CHAR)) AS COL1", read="oracle")
```

**Official Documentation**
Please include links to official SQL documentation related to your issue.
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlqr/Data-Types.html#GUID-219C338B-FE60-422A-B196-2F0A01CAD9A4



Discussion:

Patch:
diff --git a/sqlglot/dialects/redshift.py b/sqlglot/dialects/redshift.py
index 1b7cf317..855c98bb 100644
--- a/sqlglot/dialects/redshift.py
+++ b/sqlglot/dialects/redshift.py
@@ -45,7 +45,7 @@ class Redshift(Postgres):
                 isinstance(this, exp.DataType)
                 and this.this == exp.DataType.Type.VARCHAR
                 and this.expressions
-                and this.expressions[0] == exp.column("MAX")
+                and this.expressions[0].this == exp.column("MAX")
             ):
                 this.set("expressions", [exp.Var(this="MAX")])
 
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index a23a50ec..4a481643 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -2985,6 +2985,10 @@ class Boolean(Condition):
     pass
 
 
+class DataTypeSize(Expression):
+    arg_types = {"this": True, "expression": False}
+
+
 class DataType(Expression):
     arg_types = {
         "this": True,
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index fee86b33..84712286 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -766,6 +766,12 @@ class Generator:
             return f"{self.byte_start}{this}{self.byte_end}"
         return this
 
+    def datatypesize_sql(self, expression: exp.DataTypeSize) -> str:
+        this = self.sql(expression, "this")
+        specifier = self.sql(expression, "expression")
+        specifier = f" {specifier}" if specifier else ""
+        return f"{this}{specifier}"
+
     def datatype_sql(self, expression: exp.DataType) -> str:
         type_value = expression.this
         type_sql = self.TYPE_MAPPING.get(type_value, type_value.value)
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 0e656b9a..bbb9e754 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -2811,6 +2811,15 @@ class Parser(metaclass=_Parser):
 
         return this
 
+    def _parse_type_size(self) -> t.Optional[exp.Expression]:
+        this = self._parse_type()
+        if not this:
+            return None
+
+        return self.expression(
+            exp.DataTypeSize, this=this, expression=self._parse_var(any_token=True)
+        )
+
     def _parse_types(self, check_func: bool = False) -> t.Optional[exp.Expression]:
         index = self._index
 
@@ -2835,7 +2844,7 @@ class Parser(metaclass=_Parser):
             elif nested:
                 expressions = self._parse_csv(self._parse_types)
             else:
-                expressions = self._parse_csv(self._parse_conjunction)
+                expressions = self._parse_csv(self._parse_type_size)
 
             if not expressions or not self._match(TokenType.R_PAREN):
                 self._retreat(index)


Test patch:
diff --git a/tests/dialects/test_oracle.py b/tests/dialects/test_oracle.py
index d5554a9c..58b3fdcd 100644
--- a/tests/dialects/test_oracle.py
+++ b/tests/dialects/test_oracle.py
@@ -7,6 +7,8 @@ class TestOracle(Validator):
     def test_oracle(self):
         self.validate_identity("SELECT STANDARD_HASH('hello')")
         self.validate_identity("SELECT STANDARD_HASH('hello', 'MD5')")
+        self.validate_identity("SELECT CAST(NULL AS VARCHAR2(2328 CHAR)) AS COL1")
+        self.validate_identity("SELECT CAST(NULL AS VARCHAR2(2328 BYTE)) AS COL1")
         self.validate_identity("SELECT * FROM table_name@dblink_name.database_link_domain")
         self.validate_identity("SELECT * FROM table_name SAMPLE (25) s")
         self.validate_identity("SELECT * FROM V$SESSION")
diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py
index 00a40368..4d37cca2 100644
--- a/tests/test_optimizer.py
+++ b/tests/test_optimizer.py
@@ -405,7 +405,7 @@ FROM READ_CSV('tests/fixtures/optimizer/tpc-h/nation.csv.gz', 'delimiter', '|')
         self.assertEqual(expression.type.this, exp.DataType.Type.TIMESTAMPTZ)
         self.assertEqual(expression.this.type.this, exp.DataType.Type.VARCHAR)
         self.assertEqual(expression.args["to"].type.this, exp.DataType.Type.TIMESTAMPTZ)
-        self.assertEqual(expression.args["to"].expressions[0].type.this, exp.DataType.Type.INT)
+        self.assertEqual(expression.args["to"].expressions[0].this.type.this, exp.DataType.Type.INT)
 
         expression = annotate_types(parse_one("ARRAY(1)::ARRAY<INT>"))
         self.assertEqual(expression.type, parse_one("ARRAY<INT>", into=exp.DataType))
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 816471eb..a4b0e076 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -292,6 +292,7 @@ class TestParser(unittest.TestCase):
         self.assertIsInstance(parse_one("TIMESTAMP('2022-01-01')"), exp.Func)
         self.assertIsInstance(parse_one("TIMESTAMP()"), exp.Func)
         self.assertIsInstance(parse_one("map.x"), exp.Column)
+        self.assertIsInstance(parse_one("CAST(x AS CHAR(5))").to.expressions[0], exp.DataTypeSize)
 
     def test_set_expression(self):
         set_ = parse_one("SET")