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]

Reply via email to