This is an automated email from the ASF dual-hosted git repository.

morrysnow pushed a commit to branch branch-3.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.1 by this push:
     new 9d0bdcd5673 branch-3.1: [feature](function) add a variant of time 
arithmetic #52375 (#52539)
9d0bdcd5673 is described below

commit 9d0bdcd567379584c86af89a72f1464de8f02741
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Jul 2 10:28:49 2025 +0800

    branch-3.1: [feature](function) add a variant of time arithmetic #52375 
(#52539)
    
    Cherry-picked from #52375
    
    Co-authored-by: morrySnow <[email protected]>
---
 .../rules/analysis/DatetimeFunctionBinder.java     |  80 +++--
 .../nereids/rules/analysis/ExpressionAnalyzer.java |  90 +++---
 .../rules/analysis/DatetimeFunctionBinderTest.java | 342 ++++++++++++++++++++-
 .../rules/analysis/ExpressionAnalyzerTest.java     |  63 ++++
 4 files changed, 499 insertions(+), 76 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinder.java
index c45301893d5..6cdc8209490 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinder.java
@@ -69,6 +69,7 @@ import 
org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
 import org.apache.doris.nereids.trees.expressions.literal.Interval;
 import org.apache.doris.nereids.trees.expressions.literal.Interval.TimeUnit;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 
 /**
@@ -117,17 +118,27 @@ public class DatetimeFunctionBinder {
     private static final ImmutableSet<String> ARRAY_RANGE_FUNCTION_NAMES
             = ImmutableSet.of("ARRAY_RANGE", "SEQUENCE");
 
-    private static final ImmutableSet<String> SUPPORT_FUNCTION_NAMES
+    static final ImmutableSet<String> 
SUPPORT_DATETIME_ARITHMETIC_FUNCTION_NAMES
             = ImmutableSet.<String>builder()
             .addAll(TIMESTAMP_SERIES_FUNCTION_NAMES)
             .addAll(DATE_SERIES_FUNCTION_NAMES)
+            .build();
+
+    @VisibleForTesting
+    static final ImmutableSet<String> SUPPORT_FUNCTION_NAMES
+            = ImmutableSet.<String>builder()
+            .addAll(SUPPORT_DATETIME_ARITHMETIC_FUNCTION_NAMES)
             .addAll(ARRAY_RANGE_FUNCTION_NAMES)
             .build();
 
-    public boolean isDatetimeFunction(String functionName) {
+    public static boolean isDatetimeFunction(String functionName) {
         return SUPPORT_FUNCTION_NAMES.contains(functionName.toUpperCase());
     }
 
+    public static boolean isDatetimeArithmeticFunction(String functionName) {
+        return 
SUPPORT_DATETIME_ARITHMETIC_FUNCTION_NAMES.contains(functionName.toUpperCase());
+    }
+
     /**
      * bind datetime functions that have non-expression arguments.
      *
@@ -156,38 +167,67 @@ public class DatetimeFunctionBinder {
             if (TIMESTAMP_DIFF_FUNCTION_NAMES.contains(functionName)) {
                 // timestampdiff(unit, start, end)
                 return processTimestampDiff(unit, unboundFunction.child(1), 
unboundFunction.child(2));
-            } else {
-                // timestampadd(unit, amount, basetime)
+            } else if (TIMESTAMP_ADD_FUNCTION_NAMES.contains(functionName)) {
+                // timestampadd(unit, amount, base_time)
                 return processDateAdd(unit, unboundFunction.child(2), 
unboundFunction.child(1));
-            }
-        } else if (DATE_SERIES_FUNCTION_NAMES.contains(functionName)) {
-            if (unboundFunction.arity() != 2) {
+            } else {
                 throw new AnalysisException("Can not found function '" + 
functionName
                         + "' with " + unboundFunction.arity() + " arguments");
             }
-            // date_add and date_sub's default unit is DAY, date_ceil and 
date_floor's default unit is SECOND
+        } else if (DATE_SERIES_FUNCTION_NAMES.contains(functionName)) {
             TimeUnit unit = TimeUnit.DAY;
+            // date_add and date_sub's default unit is DAY, date_ceil and 
date_floor's default unit is SECOND
             if (DATE_FLOOR_CEIL_SERIES_FUNCTION_NAMES.contains(functionName)) {
                 unit = TimeUnit.SECOND;
             }
-            Expression amount = unboundFunction.child(1);
-            if (unboundFunction.child(1) instanceof Interval) {
-                Interval interval = (Interval) unboundFunction.child(1);
-                unit = interval.timeUnit();
-                amount = interval.value();
+            Expression base;
+            Expression amount;
+            switch (unboundFunction.arity()) {
+                case 2:
+                    // function_name(base_time, amount / interval)
+                    amount = unboundFunction.child(1);
+                    if (unboundFunction.child(1) instanceof Interval) {
+                        Interval interval = (Interval) 
unboundFunction.child(1);
+                        unit = interval.timeUnit();
+                        amount = interval.value();
+                    }
+                    base = unboundFunction.child(0);
+                    break;
+                case 3:
+                    // function_name(unit, amount, base_time)
+                    if (!(unboundFunction.child(0) instanceof SlotReference)) {
+                        throw new AnalysisException("Can not found function '" 
+ functionName
+                                + "' with " + unboundFunction.arity() + " 
arguments");
+                    }
+                    amount = unboundFunction.child(1);
+                    base = unboundFunction.child(2);
+                    String unitName = ((SlotReference) 
unboundFunction.child(0)).getName().toUpperCase();
+                    try {
+                        unit = TimeUnit.valueOf(unitName);
+                    } catch (IllegalArgumentException e) {
+                        throw new AnalysisException("Unsupported time stamp 
diff time unit: " + unitName
+                                + ", supported time unit: 
YEAR/MONTH/WEEK/DAY/HOUR/MINUTE/SECOND");
+                    }
+                    break;
+                default:
+                    throw new AnalysisException("Can not found function '" + 
functionName
+                            + "' with " + unboundFunction.arity() + " 
arguments");
             }
             if (ADD_DATE_FUNCTION_NAMES.contains(functionName)) {
                 // date_add(date, interval amount unit | amount)
-                return processDateAdd(unit, unboundFunction.child(0), amount);
+                return processDateAdd(unit, base, amount);
             } else if (SUB_DATE_FUNCTION_NAMES.contains(functionName)) {
                 // date_add(date, interval amount unit | amount)
-                return processDateSub(unit, unboundFunction.child(0), amount);
+                return processDateSub(unit, base, amount);
             } else if (DATE_FLOOR_FUNCTION_NAMES.contains(functionName)) {
                 // date_floor(date, interval amount unit | amount)
-                return processDateFloor(unit, unboundFunction.child(0), 
amount);
-            } else {
+                return processDateFloor(unit, base, amount);
+            } else if (DATE_CEIL_FUNCTION_NAMES.contains(functionName)) {
                 // date_ceil(date, interval amount unit | amount)
-                return processDateCeil(unit, unboundFunction.child(0), amount);
+                return processDateCeil(unit, base, amount);
+            } else {
+                throw new AnalysisException("Can not found function '" + 
functionName
+                        + "' with " + unboundFunction.arity() + " arguments");
             }
         } else if (ARRAY_RANGE_FUNCTION_NAMES.contains(functionName)) {
             switch (unboundFunction.arity()) {
@@ -251,7 +291,7 @@ public class DatetimeFunctionBinder {
                 return new SecondsAdd(timestamp, amount);
             default:
                 throw new AnalysisException("Unsupported time stamp add time 
unit: " + unit
-                        + ", supported time unit: 
YEAR/QUARTER/MONTH/WEEK/DAY/HOUR/MINUTE/SECOND");
+                        + ", supported time unit: 
YEAR/MONTH/WEEK/DAY/HOUR/MINUTE/SECOND");
         }
     }
 
@@ -273,7 +313,7 @@ public class DatetimeFunctionBinder {
                 return new SecondsSub(timeStamp, amount);
             default:
                 throw new AnalysisException("Unsupported time stamp sub time 
unit: " + unit
-                        + ", supported time unit: 
YEAR/QUARTER/MONTH/WEEK/DAY/HOUR/MINUTE/SECOND");
+                        + ", supported time unit: 
YEAR/MONTH/WEEK/DAY/HOUR/MINUTE/SECOND");
         }
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
index a0d5579abdc..b27b328f756 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzer.java
@@ -372,24 +372,56 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
     /* 
********************************************************************************************
      * bind function
      * 
********************************************************************************************
 */
-    @Override
-    public Expression visitUnboundFunction(UnboundFunction unboundFunction, 
ExpressionRewriteContext context) {
+    private UnboundFunction processHighOrderFunction(UnboundFunction 
unboundFunction,
+            ExpressionRewriteContext context) {
+        int childrenSize = unboundFunction.children().size();
+        List<Expression> subChildren = new ArrayList<>();
+        for (int i = 1; i < childrenSize; i++) {
+            subChildren.add(unboundFunction.child(i).accept(this, context));
+        }
+
+        // bindLambdaFunction
+        Lambda lambda = (Lambda) unboundFunction.children().get(0);
+        Expression lambdaFunction = lambda.getLambdaFunction();
+        List<ArrayItemReference> arrayItemReferences = 
lambda.makeArguments(subChildren);
+
+        List<Slot> boundedSlots = arrayItemReferences.stream()
+                .map(ArrayItemReference::toSlot)
+                .collect(ImmutableList.toImmutableList());
+
+        ExpressionAnalyzer lambdaAnalyzer = new 
ExpressionAnalyzer(currentPlan, new Scope(Optional.of(getScope()),
+                boundedSlots), context == null ? null : 
context.cascadesContext,
+                true, true) {
+            @Override
+            protected void couldNotFoundColumn(UnboundSlot unboundSlot, String 
tableName) {
+                throw new AnalysisException("Unknown lambda slot '"
+                        + 
unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
+                        + " in lambda arguments" + 
lambda.getLambdaArgumentNames());
+            }
+        };
+        lambdaFunction = lambdaAnalyzer.analyze(lambdaFunction, context);
+
+        Lambda lambdaClosure = 
lambda.withLambdaFunctionArguments(lambdaFunction, arrayItemReferences);
+
+        // We don't add the ArrayExpression in high order function at all
+        return unboundFunction.withChildren(ImmutableList.of(lambdaClosure));
+    }
+
+    UnboundFunction preProcessUnboundFunction(UnboundFunction unboundFunction, 
ExpressionRewriteContext context) {
         if (unboundFunction.isHighOrder()) {
-            unboundFunction = bindHighOrderFunction(unboundFunction, context);
+            unboundFunction = processHighOrderFunction(unboundFunction, 
context);
         } else {
-            // NOTICE: some trick code here. because below functions
-            //  TIMESTAMPADD / DATEDIFF / TIMESTAMPADD / DATEADD
+            // NOTICE: some trick code here. because for time arithmetic 
functions,
             //  the first argument of them is TimeUnit, but is cannot 
distinguish with UnboundSlot in parser.
             //  So, convert the UnboundSlot to a fake SlotReference with 
ExprId = -1 here
             //  And, the SlotReference will be processed in 
DatetimeFunctionBinder
             if (StringUtils.isEmpty(unboundFunction.getDbName())
-                    && 
DatetimeFunctionBinder.TIMESTAMP_SERIES_FUNCTION_NAMES.contains(
-                            unboundFunction.getName().toUpperCase())
+                    && 
DatetimeFunctionBinder.isDatetimeArithmeticFunction(unboundFunction.getName())
                     && unboundFunction.arity() == 3
                     && unboundFunction.child(0) instanceof UnboundSlot) {
                 SlotReference slotReference = new SlotReference(new ExprId(-1),
                         ((UnboundSlot) unboundFunction.child(0)).getName(),
-                        TinyIntType.INSTANCE, true, ImmutableList.of());
+                        TinyIntType.INSTANCE, false, ImmutableList.of());
                 ImmutableList.Builder<Expression> newChildrenBuilder = 
ImmutableList.builder();
                 newChildrenBuilder.add(slotReference);
                 for (int i = 1; i < unboundFunction.arity(); i++) {
@@ -399,6 +431,12 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
             }
             unboundFunction = (UnboundFunction) super.visit(unboundFunction, 
context);
         }
+        return unboundFunction;
+    }
+
+    @Override
+    public Expression visitUnboundFunction(UnboundFunction unboundFunction, 
ExpressionRewriteContext context) {
+        unboundFunction = preProcessUnboundFunction(unboundFunction, context);
 
         // bind function
         FunctionRegistry functionRegistry = 
Env.getCurrentEnv().getFunctionRegistry();
@@ -428,7 +466,7 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
                     return ret;
                 }
             }
-            if 
(DatetimeFunctionBinder.INSTANCE.isDatetimeFunction(unboundFunction.getName())) 
{
+            if 
(DatetimeFunctionBinder.isDatetimeFunction(unboundFunction.getName())) {
                 Expression ret = 
DatetimeFunctionBinder.INSTANCE.bind(unboundFunction);
                 if (ret instanceof BoundFunction) {
                     return 
TypeCoercionUtils.processBoundFunction((BoundFunction) ret);
@@ -900,40 +938,6 @@ public class ExpressionAnalyzer extends 
SubExprAnalyzer<ExpressionRewriteContext
         }
     }
 
-    private UnboundFunction bindHighOrderFunction(UnboundFunction 
unboundFunction, ExpressionRewriteContext context) {
-        int childrenSize = unboundFunction.children().size();
-        List<Expression> subChildren = new ArrayList<>();
-        for (int i = 1; i < childrenSize; i++) {
-            subChildren.add(unboundFunction.child(i).accept(this, context));
-        }
-
-        // bindLambdaFunction
-        Lambda lambda = (Lambda) unboundFunction.children().get(0);
-        Expression lambdaFunction = lambda.getLambdaFunction();
-        List<ArrayItemReference> arrayItemReferences = 
lambda.makeArguments(subChildren);
-
-        List<Slot> boundedSlots = arrayItemReferences.stream()
-                .map(ArrayItemReference::toSlot)
-                .collect(ImmutableList.toImmutableList());
-
-        ExpressionAnalyzer lambdaAnalyzer = new 
ExpressionAnalyzer(currentPlan, new Scope(Optional.of(getScope()),
-                boundedSlots), context == null ? null : 
context.cascadesContext,
-                true, true) {
-            @Override
-            protected void couldNotFoundColumn(UnboundSlot unboundSlot, String 
tableName) {
-                throw new AnalysisException("Unknown lambda slot '"
-                        + 
unboundSlot.getNameParts().get(unboundSlot.getNameParts().size() - 1)
-                        + " in lambda arguments" + 
lambda.getLambdaArgumentNames());
-            }
-        };
-        lambdaFunction = lambdaAnalyzer.analyze(lambdaFunction, context);
-
-        Lambda lambdaClosure = 
lambda.withLambdaFunctionArguments(lambdaFunction, arrayItemReferences);
-
-        // We don't add the ArrayExpression in high order function at all
-        return unboundFunction.withChildren(ImmutableList.of(lambdaClosure));
-    }
-
     private boolean shouldBindSlotBy(int namePartSize, Slot boundSlot) {
         return namePartSize <= boundSlot.getQualifier().size() + 1;
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinderTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinderTest.java
index 94f772f9508..fe61353f405 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinderTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/DatetimeFunctionBinderTest.java
@@ -20,6 +20,7 @@ package org.apache.doris.nereids.rules.analysis;
 import org.apache.doris.nereids.analyzer.UnboundFunction;
 import org.apache.doris.nereids.analyzer.UnboundSlot;
 import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.ExprId;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import org.apache.doris.nereids.trees.expressions.SlotReference;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.ArrayRange;
@@ -77,15 +78,6 @@ import org.junit.jupiter.api.Test;
 
 public class DatetimeFunctionBinderTest {
 
-    private final SlotReference yearUnit = new SlotReference("year", 
TinyIntType.INSTANCE);
-    private final SlotReference monthUnit = new SlotReference("month", 
TinyIntType.INSTANCE);
-    private final SlotReference weekUnit = new SlotReference("week", 
TinyIntType.INSTANCE);
-    private final SlotReference dayUnit = new SlotReference("day", 
TinyIntType.INSTANCE);
-    private final SlotReference hourUnit = new SlotReference("hour", 
TinyIntType.INSTANCE);
-    private final SlotReference minuteUnit = new SlotReference("minute", 
TinyIntType.INSTANCE);
-    private final SlotReference secondUnit = new SlotReference("second", 
TinyIntType.INSTANCE);
-    private final SlotReference invalidUnit = new SlotReference("xyz", 
TinyIntType.INSTANCE);
-
     private final TinyIntLiteral tinyIntLiteral = new TinyIntLiteral((byte) 1);
 
     private final Interval yearInterval = new Interval(tinyIntLiteral, "YEAR");
@@ -97,6 +89,25 @@ public class DatetimeFunctionBinderTest {
     private final Interval minuteInterval = new Interval(tinyIntLiteral, 
"MINUTE");
     private final Interval secondInterval = new Interval(tinyIntLiteral, 
"SECOND");
 
+    private final SlotReference yearUnit = new SlotReference(new ExprId(-1), 
"YEAR",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference quarterUnit = new SlotReference(new 
ExprId(-1), "QUARTER",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference monthUnit = new SlotReference(new ExprId(-1), 
"MONTH",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference weekUnit = new SlotReference(new ExprId(-1), 
"WEEK",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference dayUnit = new SlotReference(new ExprId(-1), 
"DAY",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference hourUnit = new SlotReference(new ExprId(-1), 
"HOUR",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference minuteUnit = new SlotReference(new ExprId(-1), 
"MINUTE",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference secondUnit = new SlotReference(new ExprId(-1), 
"SECOND",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+    private final SlotReference invalidUnit = new SlotReference(new 
ExprId(-1), "INVALID",
+            TinyIntType.INSTANCE, false, ImmutableList.of());
+
     private final DateTimeV2Literal dateTimeV2Literal1 = new 
DateTimeV2Literal("2024-12-01");
     private final DateTimeV2Literal dateTimeV2Literal2 = new 
DateTimeV2Literal("2024-12-26");
 
@@ -161,6 +172,11 @@ public class DatetimeFunctionBinderTest {
                             new UnboundFunction(functionName, 
ImmutableList.of(invalidUnit,
                                     dateTimeV2Literal1, dateTimeV2Literal2))));
 
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, 
ImmutableList.of(quarterUnit,
+                                    dateTimeV2Literal1, dateTimeV2Literal2))));
+
             if (functionName.equalsIgnoreCase("datediff")) {
                 timeDiff = new UnboundFunction(functionName, 
ImmutableList.of(dateTimeV2Literal1, dateTimeV2Literal2));
                 result = DatetimeFunctionBinder.INSTANCE.bind(timeDiff);
@@ -255,7 +271,7 @@ public class DatetimeFunctionBinderTest {
     }
 
     @Test
-    void testDateAdd() {
+    void testTwoArgsDateAdd() {
         Expression result;
         UnboundFunction dateAdd;
         ImmutableList<String> functionNames = ImmutableList.of("adddate", 
"days_add", "date_add");
@@ -324,7 +340,76 @@ public class DatetimeFunctionBinderTest {
     }
 
     @Test
-    void testDateSub() {
+    void testThreeArgsDateAdd() {
+        Expression result;
+        UnboundFunction dateAdd;
+        ImmutableList<String> functionNames = ImmutableList.of("adddate", 
"days_add", "date_add");
+
+        for (String functionName : functionNames) {
+            dateAdd = new UnboundFunction(functionName, ImmutableList.of(
+                    yearUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateAdd);
+            Assertions.assertInstanceOf(YearsAdd.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateAdd = new UnboundFunction(functionName, ImmutableList.of(
+                    monthUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateAdd);
+            Assertions.assertInstanceOf(MonthsAdd.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateAdd = new UnboundFunction(functionName, ImmutableList.of(
+                    weekUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateAdd);
+            Assertions.assertInstanceOf(WeeksAdd.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateAdd = new UnboundFunction(functionName, ImmutableList.of(
+                    dayUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateAdd);
+            Assertions.assertInstanceOf(DaysAdd.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateAdd = new UnboundFunction(functionName, ImmutableList.of(
+                    hourUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateAdd);
+            Assertions.assertInstanceOf(HoursAdd.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateAdd = new UnboundFunction(functionName, ImmutableList.of(
+                    minuteUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateAdd);
+            Assertions.assertInstanceOf(MinutesAdd.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateAdd = new UnboundFunction(functionName, ImmutableList.of(
+                    secondUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateAdd);
+            Assertions.assertInstanceOf(SecondsAdd.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    invalidUnit, tinyIntLiteral, 
dateTimeV2Literal1))));
+
+            // invalid expression type for first arg
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    dateTimeV2Literal2, tinyIntLiteral, 
dateTimeV2Literal1))));
+        }
+    }
+
+    @Test
+    void testTwoArgsDateSub() {
         Expression result;
         UnboundFunction dateSub;
         ImmutableList<String> functionNames = ImmutableList.of("subdate", 
"days_sub", "date_sub");
@@ -393,7 +478,76 @@ public class DatetimeFunctionBinderTest {
     }
 
     @Test
-    void testDateCeil() {
+    void testThreeArgsDateSub() {
+        Expression result;
+        UnboundFunction dateSub;
+        ImmutableList<String> functionNames = ImmutableList.of("subdate", 
"days_sub", "date_sub");
+
+        for (String functionName : functionNames) {
+            dateSub = new UnboundFunction(functionName, ImmutableList.of(
+                    yearUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateSub);
+            Assertions.assertInstanceOf(YearsSub.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateSub = new UnboundFunction(functionName, ImmutableList.of(
+                    monthUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateSub);
+            Assertions.assertInstanceOf(MonthsSub.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateSub = new UnboundFunction(functionName, ImmutableList.of(
+                    weekUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateSub);
+            Assertions.assertInstanceOf(WeeksSub.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateSub = new UnboundFunction(functionName, ImmutableList.of(
+                    dayUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateSub);
+            Assertions.assertInstanceOf(DaysSub.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateSub = new UnboundFunction(functionName, ImmutableList.of(
+                    hourUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateSub);
+            Assertions.assertInstanceOf(HoursSub.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateSub = new UnboundFunction(functionName, ImmutableList.of(
+                    minuteUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateSub);
+            Assertions.assertInstanceOf(MinutesSub.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateSub = new UnboundFunction(functionName, ImmutableList.of(
+                    secondUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateSub);
+            Assertions.assertInstanceOf(SecondsSub.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    invalidUnit, tinyIntLiteral, 
dateTimeV2Literal1))));
+
+            // invalid expression type for first arg
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    dateTimeV2Literal2, tinyIntLiteral, 
dateTimeV2Literal1))));
+        }
+    }
+
+    @Test
+    void testTwoArgsDateCeil() {
         Expression result;
         UnboundFunction dateCeil;
         ImmutableList<String> functionNames = ImmutableList.of("date_ceil");
@@ -458,11 +612,94 @@ public class DatetimeFunctionBinderTest {
             Assertions.assertThrowsExactly(AnalysisException.class,
                     () -> DatetimeFunctionBinder.INSTANCE.bind(
                             new UnboundFunction(functionName, 
ImmutableList.of(dateTimeV2Literal1))));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    dateTimeV2Literal1, quarterInterval))));
+        }
+    }
+
+    @Test
+    void testThreeArgsDateCeil() {
+        Expression result;
+        UnboundFunction dateCeil;
+        ImmutableList<String> functionNames = ImmutableList.of("date_ceil");
+
+        for (String functionName : functionNames) {
+            dateCeil = new UnboundFunction(functionName, ImmutableList.of(
+                    yearUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateCeil);
+            Assertions.assertInstanceOf(YearCeil.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateCeil = new UnboundFunction(functionName, ImmutableList.of(
+                    monthUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateCeil);
+            Assertions.assertInstanceOf(MonthCeil.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateCeil = new UnboundFunction(functionName, ImmutableList.of(
+                    weekUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateCeil);
+            Assertions.assertInstanceOf(WeekCeil.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateCeil = new UnboundFunction(functionName, ImmutableList.of(
+                    dayUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateCeil);
+            Assertions.assertInstanceOf(DayCeil.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateCeil = new UnboundFunction(functionName, ImmutableList.of(
+                    hourUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateCeil);
+            Assertions.assertInstanceOf(HourCeil.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateCeil = new UnboundFunction(functionName, ImmutableList.of(
+                    minuteUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateCeil);
+            Assertions.assertInstanceOf(MinuteCeil.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateCeil = new UnboundFunction(functionName, ImmutableList.of(
+                    secondUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateCeil);
+            Assertions.assertInstanceOf(SecondCeil.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, 
ImmutableList.of(dateTimeV2Literal1))));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    invalidUnit, tinyIntLiteral, 
dateTimeV2Literal1))));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    quarterUnit, tinyIntLiteral, 
dateTimeV2Literal1))));
+
+            // invalid expression type for first arg
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    dateTimeV2Literal2, tinyIntLiteral, 
dateTimeV2Literal1))));
         }
     }
 
     @Test
-    void testDateFloor() {
+    void testTwoArgsDateFloor() {
         Expression result;
         UnboundFunction dateFloor;
         ImmutableList<String> functionNames = ImmutableList.of("date_floor");
@@ -527,6 +764,85 @@ public class DatetimeFunctionBinderTest {
             Assertions.assertThrowsExactly(AnalysisException.class,
                     () -> DatetimeFunctionBinder.INSTANCE.bind(
                             new UnboundFunction(functionName, 
ImmutableList.of(dateTimeV2Literal1))));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    dateTimeV2Literal1, quarterInterval))));
+        }
+    }
+
+    @Test
+    void testThreeArgsDateFloor() {
+        Expression result;
+        UnboundFunction dateFloor;
+        ImmutableList<String> functionNames = ImmutableList.of("date_floor");
+
+        for (String functionName : functionNames) {
+            dateFloor = new UnboundFunction(functionName, ImmutableList.of(
+                    yearUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateFloor);
+            Assertions.assertInstanceOf(YearFloor.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateFloor = new UnboundFunction(functionName, ImmutableList.of(
+                    monthUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateFloor);
+            Assertions.assertInstanceOf(MonthFloor.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateFloor = new UnboundFunction(functionName, ImmutableList.of(
+                    weekUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateFloor);
+            Assertions.assertInstanceOf(WeekFloor.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateFloor = new UnboundFunction(functionName, ImmutableList.of(
+                    dayUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateFloor);
+            Assertions.assertInstanceOf(DayFloor.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateFloor = new UnboundFunction(functionName, ImmutableList.of(
+                    hourUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateFloor);
+            Assertions.assertInstanceOf(HourFloor.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateFloor = new UnboundFunction(functionName, ImmutableList.of(
+                    minuteUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateFloor);
+            Assertions.assertInstanceOf(MinuteFloor.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            dateFloor = new UnboundFunction(functionName, ImmutableList.of(
+                    secondUnit, tinyIntLiteral, dateTimeV2Literal1));
+            result = DatetimeFunctionBinder.INSTANCE.bind(dateFloor);
+            Assertions.assertInstanceOf(SecondFloor.class, result);
+            Assertions.assertEquals(dateTimeV2Literal1, result.child(0));
+            Assertions.assertEquals(tinyIntLiteral, result.child(1));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    invalidUnit, tinyIntLiteral, 
dateTimeV2Literal1))));
+
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    quarterUnit, tinyIntLiteral, 
dateTimeV2Literal1))));
+
+            // invalid expression type for first arg
+            Assertions.assertThrowsExactly(AnalysisException.class,
+                    () -> DatetimeFunctionBinder.INSTANCE.bind(
+                            new UnboundFunction(functionName, ImmutableList.of(
+                                    dateTimeV2Literal2, tinyIntLiteral, 
dateTimeV2Literal1))));
         }
     }
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java
new file mode 100644
index 00000000000..fcd5a58cf9f
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/analysis/ExpressionAnalyzerTest.java
@@ -0,0 +1,63 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.nereids.rules.analysis;
+
+import org.apache.doris.nereids.analyzer.Scope;
+import org.apache.doris.nereids.analyzer.UnboundFunction;
+import org.apache.doris.nereids.analyzer.UnboundSlot;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
+import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ExpressionAnalyzerTest {
+
+    @Test
+    void testPreProcessUnboundFunctionForThreeArgsDataTimeFunction() {
+        ExpressionAnalyzer analyzer = new ExpressionAnalyzer(null, new 
Scope(ImmutableList.of()),
+                null, true, true);
+        UnboundSlot unboundSlot = new UnboundSlot("YEAR");
+        Expression arg1 = new TinyIntLiteral((byte) 1);
+        Expression arg2 = new DateTimeV2Literal("2020-01-01");
+        for (String functionName : 
DatetimeFunctionBinder.SUPPORT_FUNCTION_NAMES) {
+            UnboundFunction unboundFunction = new UnboundFunction(functionName,
+                    ImmutableList.of(unboundSlot, arg1, arg2));
+            if 
(DatetimeFunctionBinder.isDatetimeArithmeticFunction(functionName)) {
+                UnboundFunction ret = 
analyzer.preProcessUnboundFunction(unboundFunction, null);
+                Assertions.assertInstanceOf(SlotReference.class, 
ret.getArgument(0));
+                Assertions.assertEquals(arg1, ret.getArgument(1));
+                Assertions.assertEquals(arg2, ret.getArgument(2));
+            } else {
+                Assertions.assertThrowsExactly(AnalysisException.class,
+                        () -> 
analyzer.preProcessUnboundFunction(unboundFunction, null),
+                        " Unknown column 'YEAR' in 'table list");
+            }
+
+        }
+        UnboundFunction unboundFunction = new UnboundFunction("other_function",
+                ImmutableList.of(unboundSlot, arg1, arg2));
+        Assertions.assertThrowsExactly(AnalysisException.class,
+                () -> analyzer.preProcessUnboundFunction(unboundFunction, 
null),
+                " Unknown column 'YEAR' in 'table list");
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to