This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new 43851c227d2 branch-4.2: [fix](nereids) Fix compound interval
arithmetic operator binding #66965 (#70062)
43851c227d2 is described below
commit 43851c227d2e6d04c7829d5284502aff20633dc3
Author: morrySnow <[email protected]>
AuthorDate: Wed Nov 4 08:18:30 2026 +0800
branch-4.2: [fix](nereids) Fix compound interval arithmetic operator
binding #66965 (#70062)
picked from master #66965
Co-authored-by: foxtail463 <[email protected]>
---
.../doris/nereids/parser/LogicalPlanBuilder.java | 23 ++++++++-
.../trees/expressions/literal/Interval.java | 5 ++
.../nereids/rules/analysis/BindExpressionTest.java | 56 ++++++++++++++++++++++
.../trees/expressions/literal/IntervalTest.java | 6 +++
4 files changed, 88 insertions(+), 2 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 58741bb3ffe..bf2bcbbca78 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -3255,7 +3255,7 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
throw new ParseException("Only supported: " +
Operator.ADD, ctx);
}
Interval interval = (Interval) left;
- return new TimestampArithmetic(Operator.ADD, right,
interval.value(), interval.timeUnit());
+ return buildDateArithmetic(right, interval, Operator.ADD);
}
if (right instanceof Interval) {
@@ -3268,7 +3268,7 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
throw new ParseException("Only supported: " + Operator.ADD
+ " and " + Operator.SUBTRACT, ctx);
}
Interval interval = (Interval) right;
- return new TimestampArithmetic(op, left, interval.value(),
interval.timeUnit());
+ return buildDateArithmetic(left, interval, op);
}
return ParserUtils.withOrigin(ctx, () -> {
@@ -3301,6 +3301,25 @@ public class LogicalPlanBuilder extends
DorisParserBaseVisitor<Object> {
});
}
+ private static Expression buildDateArithmetic(Expression date, Interval
interval, Operator operation) {
+ switch (interval.timeUnit()) {
+ case YEAR:
+ case QUARTER:
+ case MONTH:
+ case WEEK:
+ case DAY:
+ case HOUR:
+ case MINUTE:
+ case SECOND:
+ // Preserve 4.2 timestamp arithmetic plans and object/complex
type validation.
+ return new TimestampArithmetic(operation, date,
interval.value(), interval.timeUnit());
+ default:
+ // Compound units and MICROSECOND require the date function
binder.
+ String function = operation == Operator.ADD ? "DATE_ADD" :
"DATE_SUB";
+ return new UnboundFunction(function, ImmutableList.of(date,
interval));
+ }
+ }
+
@Override
public Expression visitCurrentDate(DorisParser.CurrentDateContext ctx) {
return new CurrentDate();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
index f490c225c44..597a14c371f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/Interval.java
@@ -70,6 +70,11 @@ public class Interval extends Expression implements
UnaryExpression, AlwaysNotNu
return visitor.visitInterval(this, context);
}
+ @Override
+ public String computeToSql() {
+ return "INTERVAL " + value().toSql() + " " + timeUnit;
+ }
+
@Override
public String toDigest() {
StringBuilder sb = new StringBuilder();
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
index 00cbfb2232e..95809751885 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/BindExpressionTest.java
@@ -30,6 +30,12 @@ import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.NamedExpression;
import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.TimestampArithmetic;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.DayHourAdd;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.HourSecondSub;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MicroSecondsAdd;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.MicroSecondsSub;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.YearMonthAdd;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral;
import org.apache.doris.nereids.trees.plans.JoinType;
@@ -171,6 +177,56 @@ class BindExpressionTest extends TestWithFeService
implements GeneratedPlanPatte
}
+ @Test
+ void testCompoundIntervalArithmetic() {
+ PlanChecker.from(connectContext)
+ .analyze("select cast(col1 as datetime) + interval '1-2'
year_month from t1")
+ .matches(any().when(plan -> plan.getExpressions().stream()
+ .anyMatch(expression ->
expression.anyMatch(YearMonthAdd.class::isInstance))));
+
+ PlanChecker.from(connectContext)
+ .analyze("select interval '1 5' day_hour + cast(col1 as
datetime) from t1")
+ .matches(any().when(plan -> plan.getExpressions().stream()
+ .anyMatch(expression ->
expression.anyMatch(DayHourAdd.class::isInstance))));
+
+ PlanChecker.from(connectContext)
+ .analyze("select cast(col1 as datetime) - interval '2 30'
hour_second from t1")
+ .matches(any().when(plan -> plan.getExpressions().stream()
+ .anyMatch(expression ->
expression.anyMatch(HourSecondSub.class::isInstance))));
+ }
+
+ @Test
+ void testSimpleIntervalArithmeticBinding() {
+ PlanChecker.from(connectContext)
+ .analyze("select cast(col1 as datetime) + interval 1
microsecond from t1")
+ .matches(any().when(plan -> plan.getExpressions().stream()
+ .anyMatch(expression ->
expression.anyMatch(MicroSecondsAdd.class::isInstance))));
+
+ PlanChecker.from(connectContext)
+ .analyze("select cast(col1 as datetime) - interval 1
microsecond from t1")
+ .matches(any().when(plan -> plan.getExpressions().stream()
+ .anyMatch(expression ->
expression.anyMatch(MicroSecondsSub.class::isInstance))));
+ }
+
+ @Test
+ void testSimpleIntervalKeepsTimestampArithmetic() {
+ for (String unit : ImmutableList.of("year", "quarter", "month",
"week", "day", "hour", "minute", "second")) {
+ for (String operation : ImmutableList.of("+", "-")) {
+ PlanChecker.from(connectContext)
+ .analyze("select cast(col1 as datetime) " + operation
+ " interval 1 " + unit + " from t1")
+ .matches(any().when(plan ->
plan.getExpressions().stream()
+ .anyMatch(expression ->
expression.anyMatch(TimestampArithmetic.class::isInstance))));
+ }
+ }
+ }
+
+ @Test
+ void testSimpleIntervalRejectsObjectType() {
+ AnalysisException error =
Assertions.assertThrows(AnalysisException.class,
+ () -> PlanChecker.from(connectContext).analyze("select
bitmap_empty() + interval 1 year"));
+ Assertions.assertTrue(error.getMessage().contains("timestamp
arithmetic could not contains object type"));
+ }
+
@Test
void testFilterSort() {
String sql = "select * from t1 where t1.col2 = 1 order by col2";
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/IntervalTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/IntervalTest.java
index e0ddde5f7c0..963a5fdf678 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/IntervalTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/literal/IntervalTest.java
@@ -29,4 +29,10 @@ public class IntervalTest {
Interval i2 = new Interval(new IntegerLiteral(1), TimeUnit.SECOND);
Assertions.assertNotEquals(i1, i2);
}
+
+ @Test
+ public void testIntervalToSql() {
+ Interval interval = new Interval(new IntegerLiteral(1), TimeUnit.DAY);
+ Assertions.assertEquals("INTERVAL 1 DAY", interval.toSql());
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]