Skip to content

Commit 04e2eaa

Browse files
committed
Allow fractional days
1 parent 14280d4 commit 04e2eaa

File tree

4 files changed

+26
-19
lines changed

4 files changed

+26
-19
lines changed

sqlglot/expressions.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8562,8 +8562,10 @@ def parse_identifier(name: str | Identifier, dialect: DialectType = None) -> Ide
85628562
INTERVAL_STRING_RE = re.compile(r"\s*(-?[0-9]+(?:\.[0-9]+)?)\s*([a-zA-Z]+)\s*")
85638563

85648564
# Matches day-time interval strings that contain a number of days, a space,
8565-
# and a time component in the format hh:[mm:[ss[.ff]]]
8566-
INTERVAL_DAY_TIME_RE = re.compile(r"^\s*(-?[0-9]+)\s+(\d{1,2}:\d{0,2}(?::\d{0,2}(?:\.\d+)?)?)\s*$")
8565+
# and a time in hh:[mm:[ss[.ff]]] format
8566+
INTERVAL_DAY_TIME_RE = re.compile(
8567+
r"\s*(-?[0-9]+(?:\.\d+)?)\s+(\d{1,2}:\d{0,2}(?::\d{0,2}(?:\.\d+)?)?)\s*"
8568+
)
85678569

85688570

85698571
def to_interval(interval: str | Literal) -> Interval:

sqlglot/generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3284,7 +3284,7 @@ def interval_sql(self, expression: exp.Interval) -> str:
32843284
if self.SINGLE_STRING_INTERVAL:
32853285
this = expression.this.name if expression.this else ""
32863286
if this:
3287-
if unit and isinstance(unit_expression, exp.IntervalSpan):
3287+
if unit_expression and isinstance(unit_expression, exp.IntervalSpan):
32883288
return f"INTERVAL '{this}'{unit}"
32893289
return f"INTERVAL '{this}{unit}'"
32903290
return f"INTERVAL{unit}"

sqlglot/parser.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5109,17 +5109,18 @@ def _parse_interval(self, match_interval: bool = True) -> t.Optional[exp.Add | e
51095109
self._retreat(index)
51105110
return None
51115111

5112-
# detect day-time interval span with omitted units:
5113-
# INTERVAL '<days> <time with colon>' [maybe explicit span `unit TO unit`]
5112+
# handle day-time format interval span with omitted units:
5113+
# INTERVAL '<number days> hh:[:mm[:ss[.ff]]]' <maybe `unit TO unit`>
5114+
day_time_literal = None
51145115
infer_interval_span_units = False
51155116
if (
51165117
this
51175118
and this.is_string
51185119
and self.INTERVAL_SPANS
51195120
and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT
51205121
):
5121-
day_time_format_literal = exp.INTERVAL_DAY_TIME_RE.match(this.name)
5122-
if day_time_format_literal:
5122+
day_time_literal = exp.INTERVAL_DAY_TIME_RE.match(this.name)
5123+
if day_time_literal:
51235124
index = self._index
51245125

51255126
# Var "TO" Var
@@ -5128,9 +5129,10 @@ def _parse_interval(self, match_interval: bool = True) -> t.Optional[exp.Add | e
51285129
if first_unit and self._match_text_seq("TO"):
51295130
second_unit = self._parse_var(any_token=True, upper=True)
51305131

5131-
self._retreat(index)
51325132
infer_interval_span_units = not (first_unit and second_unit)
51335133

5134+
self._retreat(index)
5135+
51345136
unit = (
51355137
None
51365138
if infer_interval_span_units
@@ -5158,21 +5160,17 @@ def _parse_interval(self, match_interval: bool = True) -> t.Optional[exp.Add | e
51585160
this = exp.Literal.string(parts[0][0])
51595161
unit = self.expression(exp.Var, this=parts[0][1].upper())
51605162

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:
5163+
if infer_interval_span_units and day_time_literal:
5164+
time_part = day_time_literal.group(2)
5165+
if time_part:
5166+
# two colons for 'hh:mm:[ss[.ff]]', period for 'mm:ss.ff'
51705167
seconds_present = time_part.count(":") >= 2 or "." in time_part
51715168
unit = self.expression(
51725169
exp.IntervalSpan,
51735170
this=exp.var("DAY"),
51745171
expression=exp.var("SECOND" if seconds_present else "MINUTE"),
51755172
)
5173+
51765174
if (
51775175
self.INTERVAL_SPANS
51785176
and self._match_text_seq("TO")

tests/dialects/test_postgres.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,12 +1639,19 @@ def test_begin_transaction(self):
16391639
).assert_is(exp.Transaction)
16401640

16411641
def test_interval_span(self):
1642-
for time_str in ["1 01:", "1 01:", "1 01:00", "1 01:00"]:
1642+
for time_str in ["1 01:", "1 01:00", "1.5 01:", "-0.25 01:"]:
16431643
self.validate_identity(f"INTERVAL '{time_str}'", f"INTERVAL '{time_str}' DAY TO MINUTE")
16441644
# explicit SECOND overrides inference to MINUTE
16451645
self.validate_identity(f"INTERVAL '{time_str}' DAY TO SECOND")
16461646

1647-
for time_str in ["1 01:01:", "1 01:01:", "1 01:01:01", "1 01:01:01.01"]:
1647+
for time_str in [
1648+
"1 01:01:",
1649+
"1 01:01:",
1650+
"1 01:01:01",
1651+
"1 01:01:01.01",
1652+
"1.5 01:01:",
1653+
"-0.25 01:01:",
1654+
]:
16481655
self.validate_identity(f"INTERVAL '{time_str}'", f"INTERVAL '{time_str}' DAY TO SECOND")
16491656
# explicit MINUTE overrides inference to SECOND
16501657
self.validate_identity(f"INTERVAL '{time_str}' DAY TO MINUTE")

0 commit comments

Comments
 (0)