Skip to content

Commit 7660486

Browse files
feat(doris, starrocks): support two-argument DATE_ADD/DATE_SUB (#7514)
* feat(doris, starrocks): support two-argument DATE_ADD/DATE_SUB Doris and StarRocks natively accept DATE_ADD(date, N) and DATE_SUB(date, N) as shorthand for INTERVAL N DAY. Both dialects inherit from MySQL, which binds these functions to build_date_delta_with_interval and requires a strict INTERVAL expression, so the shorthand previously raised ParseError. - Add an optional `default_unit` parameter to build_date_delta_with_interval. When the second argument is not an Interval and default_unit is set, the builder falls back to a bare-literal AST with the default unit. The default value is None, preserving the existing strict behavior for MySQL, BigQuery, and any other existing callers. - Override DATE_ADD / DATE_SUB / ADDDATE / SUBDATE in DorisParser and StarRocksParser with default_unit="DAY". The resulting AST shape matches the explicit INTERVAL form, so existing generators (mysql.date_add_sql) emit DATE_ADD(date, INTERVAL N DAY) without further changes. - Add tests covering both the shorthand and the INTERVAL forms to guard round-trip correctness for DAY and non-DAY units. Closes #6341 * Update tests/dialects/test_doris.py * Update tests/dialects/test_starrocks.py --------- Co-authored-by: Jo <46752250+georgesittas@users.noreply.github.com>
1 parent cb91d91 commit 7660486

5 files changed

Lines changed: 44 additions & 2 deletions

File tree

sqlglot/dialects/dialect.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,7 @@ def _builder(args: BuilderArgs) -> E:
15981598

15991599
def build_date_delta_with_interval(
16001600
expression_class: Type[E],
1601+
default_unit: str | None = None,
16011602
) -> t.Callable[[BuilderArgs], E | None]:
16021603
def _builder(args: BuilderArgs) -> E | None:
16031604
if len(args) < 2:
@@ -1606,7 +1607,13 @@ def _builder(args: BuilderArgs) -> E | None:
16061607
interval = args[1]
16071608

16081609
if not isinstance(interval, exp.Interval):
1609-
raise ParseError(f"INTERVAL expression expected but got '{interval}'")
1610+
if default_unit is None:
1611+
raise ParseError(f"INTERVAL expression expected but got '{interval}'")
1612+
return expression_class(
1613+
this=args[0],
1614+
expression=interval,
1615+
unit=exp.Literal.string(default_unit),
1616+
)
16101617

16111618
return expression_class(this=args[0], expression=interval.this, unit=unit_to_str(interval))
16121619

sqlglot/parsers/doris.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
from sqlglot import exp
5+
from sqlglot.dialects.dialect import build_date_delta_with_interval
56
from sqlglot.helper import seq_get
67
from sqlglot.parsers.mysql import MySQLParser
78
from sqlglot.tokens import TokenType
@@ -26,11 +27,15 @@ def _is_unit_like(e: exp.Expr | None) -> bool:
2627
class DorisParser(MySQLParser):
2728
FUNCTIONS = {
2829
**MySQLParser.FUNCTIONS,
30+
"ADDDATE": build_date_delta_with_interval(exp.DateAdd, default_unit="DAY"),
2931
"COLLECT_SET": exp.ArrayUniqueAgg.from_arg_list,
32+
"DATE_ADD": build_date_delta_with_interval(exp.DateAdd, default_unit="DAY"),
33+
"DATE_SUB": build_date_delta_with_interval(exp.DateSub, default_unit="DAY"),
3034
"DATE_TRUNC": _build_date_trunc,
3135
"L2_DISTANCE": exp.EuclideanDistance.from_arg_list,
3236
"MONTHS_ADD": exp.AddMonths.from_arg_list,
3337
"REGEXP": exp.RegexpLike.from_arg_list,
38+
"SUBDATE": build_date_delta_with_interval(exp.DateSub, default_unit="DAY"),
3439
"TO_DATE": exp.TsOrDsToDate.from_arg_list,
3540
}
3641

sqlglot/parsers/starrocks.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33

44
from sqlglot import exp
5-
from sqlglot.dialects.dialect import build_timestamp_trunc
5+
from sqlglot.dialects.dialect import build_date_delta_with_interval, build_timestamp_trunc
66
from sqlglot.helper import seq_get
77
from sqlglot.parsers.mysql import MySQLParser
88

99

1010
class StarRocksParser(MySQLParser):
1111
FUNCTIONS = {
1212
**MySQLParser.FUNCTIONS,
13+
"ADDDATE": build_date_delta_with_interval(exp.DateAdd, default_unit="DAY"),
14+
"DATE_ADD": build_date_delta_with_interval(exp.DateAdd, default_unit="DAY"),
15+
"DATE_SUB": build_date_delta_with_interval(exp.DateSub, default_unit="DAY"),
16+
"SUBDATE": build_date_delta_with_interval(exp.DateSub, default_unit="DAY"),
1317
"DATE_TRUNC": build_timestamp_trunc,
1418
"DATEDIFF": lambda args: exp.DateDiff(
1519
this=seq_get(args, 0), expression=seq_get(args, 1), unit=exp.Literal.string("DAY")

tests/dialects/test_doris.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ def test_time(self):
105105
self.validate_identity("DATE_TRUNC('2010-12-02 19:28:30', 'HOUR')")
106106
self.validate_identity("CURRENT_DATE()")
107107

108+
def test_date_add_sub(self):
109+
# Standard INTERVAL form
110+
self.validate_identity("SELECT DATE_ADD(x, INTERVAL '3' DAY)")
111+
self.validate_identity("SELECT DATE_SUB(x, INTERVAL '3' MONTH)")
112+
113+
# Two-argument shorthand - default unit DAY
114+
self.validate_identity("SELECT DATE_SUB(x, 3)", "SELECT DATE_SUB(x, INTERVAL 3 DAY)")
115+
self.validate_identity("SELECT DATE_ADD(x, 7)", "SELECT DATE_ADD(x, INTERVAL 7 DAY)")
116+
117+
# ADDDATE / SUBDATE aliases
118+
self.validate_identity("SELECT ADDDATE(x, 7)", "SELECT DATE_ADD(x, INTERVAL 7 DAY)")
119+
self.validate_identity("SELECT SUBDATE(x, 7)", "SELECT DATE_SUB(x, INTERVAL 7 DAY)")
120+
108121
def test_regex(self):
109122
self.validate_all(
110123
"SELECT REGEXP_LIKE(abc, '%foo%')",

tests/dialects/test_starrocks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@ def test_time(self):
142142
"SELECT DATE_DIFF('MINUTE', '2010-11-30 23:59:59', '2010-11-30 20:58:59')"
143143
)
144144

145+
def test_date_add_sub(self):
146+
# Standard INTERVAL form
147+
self.validate_identity("SELECT DATE_ADD(x, INTERVAL '3' DAY)")
148+
self.validate_identity("SELECT DATE_SUB(x, INTERVAL '3' MONTH)")
149+
150+
# Two-argument shorthand - default unit DAY
151+
self.validate_identity("SELECT DATE_SUB(x, 3)", "SELECT DATE_SUB(x, INTERVAL 3 DAY)")
152+
self.validate_identity("SELECT DATE_ADD(x, 7)", "SELECT DATE_ADD(x, INTERVAL 7 DAY)")
153+
154+
# ADDDATE / SUBDATE aliases
155+
self.validate_identity("SELECT ADDDATE(x, 7)", "SELECT DATE_ADD(x, INTERVAL 7 DAY)")
156+
self.validate_identity("SELECT SUBDATE(x, 7)", "SELECT DATE_SUB(x, INTERVAL 7 DAY)")
157+
145158
def test_regex(self):
146159
self.validate_all(
147160
"SELECT REGEXP(abc, '%foo%')",

0 commit comments

Comments
 (0)