Repository: tobymao/sqlglot
Pull request: Feat(presto,oracle): add support for INTERVAL span types
Fixes #2027

Problem statement:
parsing trino `interval day to second` and `interval year to month` into a type doesn't work
```
In [9]: import sqlglot as sg

In [10]: sg.__version__
Out[10]: '17.9.1'

In [11]: sg.parse_one("interval day to second", into=sg.exp.DataType, read="trino")
...
ParseError: Failed to parse 'interval day to second' into <class 'sqlglot.expressions.DataType'>

In [12]: sg.parse_one("interval year to month", into=sg.exp.DataType, read="trino")
...
ParseError: Failed to parse 'interval year to month' into <class 'sqlglot.expressions.DataType'>
```

**Is your feature request related to a problem? Please describe.**

Not related to a problem

**Describe the solution you'd like**

Ideally these can be parsed into the `INTERVAL` type

**Describe alternatives you've considered**

Not sure what the alternatives would be

**Additional context**

N/A

Hints from issue discussion:

Commit message: Feat(presto,oracle): add support for INTERVAL span types

Patch to review:
diff --git a/sqlglot/expressions.py b/sqlglot/expressions.py
index edd72ea6bb..ee973a294e 100644
--- a/sqlglot/expressions.py
+++ b/sqlglot/expressions.py
@@ -3859,6 +3859,18 @@ def __init__(self, **args):
         super().__init__(**args)
 
 
+# https://www.oracletutorial.com/oracle-basics/oracle-interval/
+# https://trino.io/docs/current/language/types.html#interval-year-to-month
+class IntervalYearToMonthSpan(Expression):
+    arg_types = {}
+
+
+# https://www.oracletutorial.com/oracle-basics/oracle-interval/
+# https://trino.io/docs/current/language/types.html#interval-day-to-second
+class IntervalDayToSecondSpan(Expression):
+    arg_types = {}
+
+
 class Interval(TimeUnit):
     arg_types = {"this": False, "unit": False}
 
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 0ab960ac1b..0c877162a5 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -71,6 +71,8 @@ class Generator:
         exp.ExternalProperty: lambda self, e: "EXTERNAL",
         exp.HeapProperty: lambda self, e: "HEAP",
         exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}",
+        exp.IntervalDayToSecondSpan: "DAY TO SECOND",
+        exp.IntervalYearToMonthSpan: "YEAR TO MONTH",
         exp.LanguageProperty: lambda self, e: self.naked_property(e),
         exp.LocationProperty: lambda self, e: self.naked_property(e),
         exp.LogProperty: lambda self, e: f"{'NO ' if e.args.get('no') else ''}LOG",
@@ -848,6 +850,8 @@ def datatype_sql(self, expression: exp.DataType) -> str:
                     delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")")
                     values = self.expressions(expression, key="values", flat=True)
                     values = f"{delimiters[0]}{values}{delimiters[1]}"
+            elif type_value == exp.DataType.Type.INTERVAL:
+                nested = f" {interior}"
             else:
                 nested = f"({interior})"
 
diff --git a/sqlglot/parser.py b/sqlglot/parser.py
index 1847148052..d7a9dcdbf1 100644
--- a/sqlglot/parser.py
+++ b/sqlglot/parser.py
@@ -3184,10 +3184,17 @@ def _parse_types(
             elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
                 maybe_func = False
         elif type_token == TokenType.INTERVAL:
-            unit = self._parse_var()
+            span: t.Optional[t.List[exp.Expression]] = None
+            if self._match_text_seq("YEAR", "TO", "MONTH"):
+                span = [exp.IntervalYearToMonthSpan()]
+            elif self._match_text_seq("DAY", "TO", "SECOND"):
+                span = [exp.IntervalDayToSecondSpan()]
 
+            unit = not span and self._parse_var()
             if not unit:
-                value = self.expression(exp.DataType, this=exp.DataType.Type.INTERVAL)
+                value = self.expression(
+                    exp.DataType, this=exp.DataType.Type.INTERVAL, expressions=span
+                )
             else:
                 value = self.expression(exp.Interval, unit=unit)
 
diff --git a/tests/dialects/test_presto.py b/tests/dialects/test_presto.py
index ec1ad3070a..97263fa2e0 100644
--- a/tests/dialects/test_presto.py
+++ b/tests/dialects/test_presto.py
@@ -8,6 +8,23 @@ class TestPresto(Validator):
     dialect = "presto"
 
     def test_cast(self):
+        self.validate_identity("CAST(x AS IPADDRESS)")
+        self.validate_identity("CAST(x AS IPPREFIX)")
+
+        self.validate_all(
+            "CAST(x AS INTERVAL YEAR TO MONTH)",
+            write={
+                "oracle": "CAST(x AS INTERVAL YEAR TO MONTH)",
+                "presto": "CAST(x AS INTERVAL YEAR TO MONTH)",
+            },
+        )
+        self.validate_all(
+            "CAST(x AS INTERVAL DAY TO SECOND)",
+            write={
+                "oracle": "CAST(x AS INTERVAL DAY TO SECOND)",
+                "presto": "CAST(x AS INTERVAL DAY TO SECOND)",
+            },
+        )
         self.validate_all(
             "SELECT CAST('10C' AS INTEGER)",
             read={
@@ -109,8 +126,6 @@ def test_cast(self):
                 "spark": "CAST(x AS TIMESTAMP)",
             },
         )
-        self.validate_identity("CAST(x AS IPADDRESS)")
-        self.validate_identity("CAST(x AS IPPREFIX)")
 
     def test_regex(self):
         self.validate_all(


Code review comments:
diff_hunk: @@ -3184,10 +3184,17 @@ def _parse_types(
             elif self._match_text_seq("WITHOUT", "TIME", "ZONE"):
                 maybe_func = False
         elif type_token == TokenType.INTERVAL:
-            unit = self._parse_var()
+            span: t.Optional[t.List[exp.Expression]] = None
path: sqlglot/parser.py
text: @user1:
switch this to an else and put the type on the first one

@author:
Done