@@ -1561,6 +1561,10 @@ def _parse_partitioned_by_bucket_or_truncate(self) -> t.Optional[exp.Expression]
1561
1561
# Adding an ON TRUE, makes transpilation semantically correct for other dialects
1562
1562
ADD_JOIN_ON_TRUE = False
1563
1563
1564
+ # Whether INTERVAL spans with literal format '\d+ hh:[mm:[ss[.ff]]]'
1565
+ # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`
1566
+ SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = False
1567
+
1564
1568
__slots__ = (
1565
1569
"error_level" ,
1566
1570
"error_message_context" ,
@@ -5105,9 +5109,38 @@ def _parse_interval(self, match_interval: bool = True) -> t.Optional[exp.Add | e
5105
5109
self ._retreat (index )
5106
5110
return None
5107
5111
5108
- unit = self ._parse_function () or (
5109
- not self ._match (TokenType .ALIAS , advance = False )
5110
- and self ._parse_var (any_token = True , upper = True )
5112
+ # detect day-time interval span with omitted units:
5113
+ # INTERVAL '<days> <time with colon>' [maybe explicit span `unit TO unit`]
5114
+ infer_interval_span_units = False
5115
+ if (
5116
+ this
5117
+ and this .is_string
5118
+ and self .INTERVAL_SPANS
5119
+ and self .SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
5120
+ ):
5121
+ day_time_format_literal = exp .INTERVAL_DAY_TIME_RE .match (this .name )
5122
+ if day_time_format_literal :
5123
+ index = self ._index
5124
+
5125
+ # Var "TO" Var
5126
+ first_unit = self ._parse_var (any_token = True , upper = True )
5127
+ second_unit = None
5128
+ if first_unit and self ._match_text_seq ("TO" ):
5129
+ second_unit = self ._parse_var (any_token = True , upper = True )
5130
+
5131
+ self ._retreat (index )
5132
+ infer_interval_span_units = not (first_unit and second_unit )
5133
+
5134
+ unit = (
5135
+ None
5136
+ if infer_interval_span_units
5137
+ else (
5138
+ self ._parse_function ()
5139
+ or (
5140
+ not self ._match (TokenType .ALIAS , advance = False )
5141
+ and self ._parse_var (any_token = True , upper = True )
5142
+ )
5143
+ )
5111
5144
)
5112
5145
5113
5146
# Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse
@@ -5124,7 +5157,27 @@ def _parse_interval(self, match_interval: bool = True) -> t.Optional[exp.Add | e
5124
5157
if len (parts ) == 1 :
5125
5158
this = exp .Literal .string (parts [0 ][0 ])
5126
5159
unit = self .expression (exp .Var , this = parts [0 ][1 ].upper ())
5127
- if self .INTERVAL_SPANS and self ._match_text_seq ("TO" ):
5160
+
5161
+ # infer DAY TO MINUTE/SECOND omitted span units
5162
+ if (
5163
+ self .INTERVAL_SPANS
5164
+ and self .SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
5165
+ and day_time_format_literal
5166
+ ):
5167
+ time_part = day_time_format_literal .group (2 )
5168
+
5169
+ if infer_interval_span_units :
5170
+ seconds_present = time_part .count (":" ) >= 2 or "." in time_part
5171
+ unit = self .expression (
5172
+ exp .IntervalSpan ,
5173
+ this = exp .var ("DAY" ),
5174
+ expression = exp .var ("SECOND" if seconds_present else "MINUTE" ),
5175
+ )
5176
+ if (
5177
+ self .INTERVAL_SPANS
5178
+ and self ._match_text_seq ("TO" )
5179
+ and not isinstance (unit , exp .IntervalSpan )
5180
+ ):
5128
5181
unit = self .expression (
5129
5182
exp .IntervalSpan , this = unit , expression = self ._parse_var (any_token = True , upper = True )
5130
5183
)
0 commit comments