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]