This is an automated email from the ASF dual-hosted git repository.
zclllyybb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 43800f3bf51 [Fix](nereids) Fix nullable date literal binding and date
*_diff folding (#64127)
43800f3bf51 is described below
commit 43800f3bf517704e34eceb91d7b794938d84c3ad
Author: linrrarity <[email protected]>
AuthorDate: Tue Jun 9 21:52:00 2026 +0800
[Fix](nereids) Fix nullable date literal binding and date *_diff folding
(#64127)
## Problem Summary
Two issues in the **FE constant folding** of date/time `*_diff`
functions
(`days_diff`, `weeks_diff`, `hours_diff`, `minutes_diff`,
`seconds_diff`,
`milliseconds_diff`, `microseconds_diff`). Both only affect FE folding —
the BE
results were already correct — so `debug_skip_fold_constant` on/off
could
disagree for the same query. (`datediff` / `to_days` already fold via
`calc_daynr` and are **not** changed here; this PR only brings the
remaining
`*_diff` folding in line with them.)
### 1. Nullable string/date literals were resolved to TIMESTAMPTZ
unexpectedly
During signature search an argument is only type-coerced when
`argument.isLiteral()` is true. A literal wrapped in `nullable()` /
`non_nullable()` is a function node, not a `Literal`, so the check
fails: the
datetime coercion **and** the "no time zone → don't prefer TIMESTAMPTZ"
scoring
are both skipped. With the scoring gone every candidate signature ties,
and the
first one — the TIMESTAMPTZ overload — wins. So a plain, no-time-zone
literal
that happens to be wrapped in `nullable()` is cast to `timestamptz`
instead of
`datetimev2`.
The change unwraps `nullable()` / `non_nullable()` so the inner literal
is
coerced exactly like a bare literal. Genuinely timezone-bearing literals
(e.g. `'2021-12-31 00:30:00+08:00'`) still bind to TIMESTAMPTZ.
before:
```
explain select days_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00'));
0:VUNION(11)
constant exprs:
days_diff(CAST(nullable('2021-12-31 12:23:34') AS
timestamptz(6)), CAST(nullable('0000-01-01 00:00:00') AS timestamptz(6)))
```
now:
```
0:VUNION(11)
constant exprs:
days_diff(CAST(nullable('2021-12-31 12:23:34') AS datetimev2(6)),
CAST(nullable('0000-01-01 00:00:00') AS datetimev2(6)))
```
### 2. Wrong day count when the range crosses year `0000`
The folding used Java `ChronoUnit.*.between(...)`. Java's `java.time`
follows the
ISO proleptic Gregorian calendar in which year 0 **is** a leap year
(`0000-02-29` exists). Doris/BE use the MySQL-style `calc_daynr`, in
which year 0
is **not** a leap year. So whenever the interval crosses `0000-02-29`,
FE folding
was exactly one day larger than the BE result.
The change folds these functions from `calc_daynr`
(`DateLiteral.getTotalDays()`)
for the day count, plus the sub-day remainder for the time-aware units —
matching `datediff` / `to_days` and the BE. `TimestampTzLiteral`
overloads are
added so timezone-bearing literals fold on the FE too.
before:
```
set debug_skip_fold_constant=false; -- FE folds
select days_diff('0001-01-01 00:00:00', '0000-01-01 00:00:00');
+---------------------------------------------------------+
| days_diff('0001-01-01 00:00:00', '0000-01-01 00:00:00') |
+---------------------------------------------------------+
| 366 | -- wrong
+---------------------------------------------------------+
set debug_skip_fold_constant=true; -- BE
select days_diff('0001-01-01 00:00:00', '0000-01-01 00:00:00');
+---------------------------------------------------------+
| 365 | -- correct
+---------------------------------------------------------+
```
now: FE folding matches BE, both return `365`.
---
.../expressions/functions/SearchSignature.java | 8 +-
.../executable/DateTimeExtractAndTransform.java | 104 +++++++++++++++----
.../trees/expressions/literal/DateTimeLiteral.java | 4 +
.../expressions/literal/DateTimeV2Literal.java | 30 ++++++
.../trees/expressions/literal/DateV2Literal.java | 4 +
.../apache/doris/nereids/util/ExpressionUtils.java | 9 ++
.../data/nereids_function_p0/scalar_function/D.out | 12 +++
.../nereids_function_p0/scalar_function/D.groovy | 113 +++++++++++++++++++++
8 files changed, 259 insertions(+), 25 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/SearchSignature.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/SearchSignature.java
index 22735912068..74e9e06f442 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/SearchSignature.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/SearchSignature.java
@@ -29,6 +29,7 @@ import org.apache.doris.nereids.types.DateTimeV2Type;
import org.apache.doris.nereids.types.DateType;
import org.apache.doris.nereids.types.DateV2Type;
import org.apache.doris.nereids.types.DecimalV3Type;
+import org.apache.doris.nereids.util.ExpressionUtils;
import org.apache.doris.nereids.util.TypeCoercionUtils;
import com.google.common.collect.Lists;
@@ -257,14 +258,15 @@ public class SearchSignature {
// we need to try to do string literal coercion when search
signature.
// for example, FUNC_A has two signature FUNC_A(datetime) and
FUNC_A(string)
// if SQL block is `FUNC_A('2020-02-02 00:00:00')`, we should
return signature FUNC_A(datetime).
- if (!argument.isNullLiteral() && argument.isLiteral() &&
realType.isStringLikeType()) {
- realType =
TypeCoercionUtils.characterLiteralTypeCoercion(((Literal)
argument).getStringValue(),
+ Optional<Literal> literalAfterUnwrapNullable =
ExpressionUtils.getLiteralAfterUnwrapNullable(argument);
+ if (!argument.isNullLiteral() &&
literalAfterUnwrapNullable.isPresent() && realType.isStringLikeType()) {
+ String literalValue =
literalAfterUnwrapNullable.get().getStringValue();
+ realType =
TypeCoercionUtils.characterLiteralTypeCoercion(literalValue,
sigArgType).orElse(argument).getDataType();
if (!realType.isStringLikeType()) {
stringLiteralCoersionCount++;
}
- String literalValue = ((Literal) argument).getStringValue();
if (sigArgType.isTimeStampTzType()) {
boolean hasTimeZone =
DateTimeChecker.hasTimeZone(literalValue);
if (hasTimeZone) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
index 3595010c96e..2fc25ff528f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/DateTimeExtractAndTransform.java
@@ -26,6 +26,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.FromMilliseco
import org.apache.doris.nereids.trees.expressions.functions.scalar.FromSecond;
import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.DateLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal;
import org.apache.doris.nereids.trees.expressions.literal.DateV2Literal;
@@ -38,6 +39,7 @@ import
org.apache.doris.nereids.trees.expressions.literal.SmallIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
import org.apache.doris.nereids.trees.expressions.literal.TimeV2Literal;
+import org.apache.doris.nereids.trees.expressions.literal.TimestampTzLiteral;
import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral;
import org.apache.doris.nereids.types.DateTimeV2Type;
@@ -1109,112 +1111,155 @@ public class DateTimeExtractAndTransform {
@ExecFunction(name = "microseconds_diff")
public static Expression microsecondsDiff(DateTimeV2Literal t1,
DateTimeV2Literal t2) {
- return new
BigIntLiteral(ChronoUnit.MICROS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInMicroSeconds(t1, t2));
+ }
+
+ @ExecFunction(name = "microseconds_diff")
+ public static Expression microsecondsDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInMicroSeconds(t1, t2));
}
@ExecFunction(name = "milliseconds_diff")
public static Expression millisecondsDiff(DateTimeV2Literal t1,
DateTimeV2Literal t2) {
- return new
BigIntLiteral(ChronoUnit.MILLIS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInMicroSeconds(t1, t2) / 1000L);
+ }
+
+ @ExecFunction(name = "milliseconds_diff")
+ public static Expression millisecondsDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInMicroSeconds(t1, t2) / 1000L);
}
@ExecFunction(name = "seconds_diff")
public static Expression secondsDiff(DateTimeV2Literal t1,
DateTimeV2Literal t2) {
- return new
BigIntLiteral(ChronoUnit.SECONDS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2));
+ }
+
+ @ExecFunction(name = "seconds_diff")
+ public static Expression secondsDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2));
}
@ExecFunction(name = "seconds_diff")
public static Expression secondsDiff(DateTimeV2Literal t1, DateV2Literal
t2) {
- return new
BigIntLiteral(ChronoUnit.SECONDS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2));
}
@ExecFunction(name = "seconds_diff")
public static Expression secondsDiff(DateV2Literal t1, DateTimeV2Literal
t2) {
- return new
BigIntLiteral(ChronoUnit.SECONDS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2));
}
@ExecFunction(name = "seconds_diff")
public static Expression secondsDiff(DateV2Literal t1, DateV2Literal t2) {
- return new
BigIntLiteral(ChronoUnit.SECONDS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2));
}
@ExecFunction(name = "minutes_diff")
public static Expression minutesDiff(DateTimeV2Literal t1,
DateTimeV2Literal t2) {
- return new
BigIntLiteral(ChronoUnit.MINUTES.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L);
+ }
+
+ @ExecFunction(name = "minutes_diff")
+ public static Expression minutesDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L);
}
@ExecFunction(name = "minutes_diff")
public static Expression minutesDiff(DateTimeV2Literal t1, DateV2Literal
t2) {
- return new
BigIntLiteral(ChronoUnit.MINUTES.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L);
}
@ExecFunction(name = "minutes_diff")
public static Expression minutesDiff(DateV2Literal t1, DateTimeV2Literal
t2) {
- return new
BigIntLiteral(ChronoUnit.MINUTES.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L);
}
@ExecFunction(name = "minutes_diff")
public static Expression minutesDiff(DateV2Literal t1, DateV2Literal t2) {
- return new
BigIntLiteral(ChronoUnit.MINUTES.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L);
}
@ExecFunction(name = "hours_diff")
public static Expression hoursDiff(DateTimeV2Literal t1, DateTimeV2Literal
t2) {
- return new BigIntLiteral(ChronoUnit.HOURS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L / 60L);
+ }
+
+ @ExecFunction(name = "hours_diff")
+ public static Expression hoursDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L / 60L);
}
@ExecFunction(name = "hours_diff")
public static Expression hoursDiff(DateTimeV2Literal t1, DateV2Literal t2)
{
- return new BigIntLiteral(ChronoUnit.HOURS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L / 60L);
}
@ExecFunction(name = "hours_diff")
public static Expression hoursDiff(DateV2Literal t1, DateTimeV2Literal t2)
{
- return new BigIntLiteral(ChronoUnit.HOURS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L / 60L);
}
@ExecFunction(name = "hours_diff")
public static Expression hoursDiff(DateV2Literal t1, DateV2Literal t2) {
- return new BigIntLiteral(ChronoUnit.HOURS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return new
BigIntLiteral(DateTimeV2Literal.datetimeDiffInSecondsRoundToZeroByMicroSecond(t1,
t2) / 60L / 60L);
}
@ExecFunction(name = "days_diff")
public static Expression daysDiff(DateTimeV2Literal t1, DateTimeV2Literal
t2) {
- return new BigIntLiteral(ChronoUnit.DAYS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return daysDiff((DateLiteral) t1, t2);
+ }
+
+ @ExecFunction(name = "days_diff")
+ public static Expression daysDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return daysDiff((DateLiteral) t1, t2);
}
@ExecFunction(name = "days_diff")
public static Expression daysDiff(DateTimeV2Literal t1, DateV2Literal t2) {
- return new BigIntLiteral(ChronoUnit.DAYS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return daysDiff((DateLiteral) t1, t2);
}
@ExecFunction(name = "days_diff")
public static Expression daysDiff(DateV2Literal t1, DateTimeV2Literal t2) {
- return new BigIntLiteral(ChronoUnit.DAYS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return daysDiff((DateLiteral) t1, t2);
}
@ExecFunction(name = "days_diff")
public static Expression daysDiff(DateV2Literal t1, DateV2Literal t2) {
- return new BigIntLiteral(ChronoUnit.DAYS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return daysDiff((DateLiteral) t1, t2);
+ }
+
+ private static Expression daysDiff(DateLiteral t1, DateLiteral t2) {
+ return new
BigIntLiteral(DateTimeV2Literal.dateDiffInDaysRoundToZeroByTime(t1, t2));
}
@ExecFunction(name = "weeks_diff")
public static Expression weeksDiff(DateTimeV2Literal t1, DateTimeV2Literal
t2) {
- return new BigIntLiteral(ChronoUnit.WEEKS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return weeksDiff((DateLiteral) t1, t2);
+ }
+
+ @ExecFunction(name = "weeks_diff")
+ public static Expression weeksDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return weeksDiff((DateLiteral) t1, t2);
}
@ExecFunction(name = "weeks_diff")
public static Expression weeksDiff(DateTimeV2Literal t1, DateV2Literal t2)
{
- return new BigIntLiteral(ChronoUnit.WEEKS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return weeksDiff((DateLiteral) t1, t2);
}
@ExecFunction(name = "weeks_diff")
public static Expression weeksDiff(DateV2Literal t1, DateTimeV2Literal t2)
{
- return new BigIntLiteral(ChronoUnit.WEEKS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return weeksDiff((DateLiteral) t1, t2);
}
@ExecFunction(name = "weeks_diff")
public static Expression weeksDiff(DateV2Literal t1, DateV2Literal t2) {
- return new BigIntLiteral(ChronoUnit.WEEKS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ return weeksDiff((DateLiteral) t1, t2);
+ }
+
+ private static Expression weeksDiff(DateLiteral t1, DateLiteral t2) {
+ return new
BigIntLiteral(DateTimeV2Literal.dateDiffInDaysRoundToZeroByTime(t1, t2) / 7L);
}
@ExecFunction(name = "months_diff")
@@ -1222,6 +1267,11 @@ public class DateTimeExtractAndTransform {
return new
BigIntLiteral(ChronoUnit.MONTHS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
}
+ @ExecFunction(name = "months_diff")
+ public static Expression monthsDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new
BigIntLiteral(ChronoUnit.MONTHS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ }
+
@ExecFunction(name = "months_diff")
public static Expression monthsDiff(DateTimeV2Literal t1, DateV2Literal
t2) {
return new
BigIntLiteral(ChronoUnit.MONTHS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
@@ -1242,6 +1292,11 @@ public class DateTimeExtractAndTransform {
return new
BigIntLiteral(ChronoUnit.MONTHS.between(t2.toJavaDateType(),
t1.toJavaDateType()) / 3);
}
+ @ExecFunction(name = "quarters_diff")
+ public static Expression quartersDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new
BigIntLiteral(ChronoUnit.MONTHS.between(t2.toJavaDateType(),
t1.toJavaDateType()) / 3);
+ }
+
@ExecFunction(name = "quarters_diff")
public static Expression quartersDiff(DateV2Literal t1, DateV2Literal t2) {
return new
BigIntLiteral(ChronoUnit.MONTHS.between(t2.toJavaDateType(),
t1.toJavaDateType()) / 3);
@@ -1252,6 +1307,11 @@ public class DateTimeExtractAndTransform {
return new BigIntLiteral(ChronoUnit.YEARS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
}
+ @ExecFunction(name = "years_diff")
+ public static Expression yearsDiff(TimestampTzLiteral t1,
TimestampTzLiteral t2) {
+ return new BigIntLiteral(ChronoUnit.YEARS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
+ }
+
@ExecFunction(name = "years_diff")
public static Expression yearsDiff(DateTimeV2Literal t1, DateV2Literal t2)
{
return new BigIntLiteral(ChronoUnit.YEARS.between(t2.toJavaDateType(),
t1.toJavaDateType()));
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
index 019e8d8a40f..4a8ac991168 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeLiteral.java
@@ -320,6 +320,10 @@ public class DateTimeLiteral extends DateLiteral {
return (year * 10000 + month * 100 + day) * 1000000L + hour * 10000 +
minute * 100 + second;
}
+ public long timePartToMicroSecond() {
+ return ((hour * 60L + minute) * 60L + second) * 1000L * 1000L +
microSecond;
+ }
+
@Override
public double getDouble() {
return (double) getValue();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeV2Literal.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeV2Literal.java
index 08f2c982b43..93f221d4d99 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeV2Literal.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateTimeV2Literal.java
@@ -64,6 +64,36 @@ public class DateTimeV2Literal extends DateTimeLiteral {
roundMicroSecond(dateType.getScale());
}
+ /** Date difference rounded toward zero by time part. */
+ public static long dateDiffInDaysRoundToZeroByTime(DateLiteral lhs,
DateLiteral rhs) {
+ long days = DateV2Literal.dateDiffInDays(lhs, rhs);
+ long microSecondDiff = timePartToMicroSecond(lhs) -
timePartToMicroSecond(rhs);
+ if (days > 0 && microSecondDiff < 0) {
+ days--;
+ } else if (days < 0 && microSecondDiff > 0) {
+ days++;
+ }
+ return days;
+ }
+
+ /** Datetime difference in seconds rounded toward zero by microsecond
part. */
+ public static long
datetimeDiffInSecondsRoundToZeroByMicroSecond(DateLiteral lhs, DateLiteral rhs)
{
+ return datetimeDiffInMicroSeconds(lhs, rhs) / 1000L / 1000L;
+ }
+
+ /** Datetime difference in microseconds. */
+ public static long datetimeDiffInMicroSeconds(DateLiteral lhs, DateLiteral
rhs) {
+ return DateV2Literal.dateDiffInDays(lhs, rhs) * 24L * 60L * 60L *
1000L * 1000L
+ + timePartToMicroSecond(lhs) - timePartToMicroSecond(rhs);
+ }
+
+ private static long timePartToMicroSecond(DateLiteral date) {
+ if (date instanceof DateTimeLiteral) {
+ return ((DateTimeLiteral) date).timePartToMicroSecond();
+ }
+ return 0;
+ }
+
@Override
public DateTimeV2Type getDataType() throws UnboundException {
return (DateTimeV2Type) super.getDataType();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateV2Literal.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateV2Literal.java
index 459131ddc11..1462397bb08 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateV2Literal.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateV2Literal.java
@@ -45,6 +45,10 @@ public class DateV2Literal extends DateLiteral {
super(DateV2Type.INSTANCE, year, month, day);
}
+ public static long dateDiffInDays(DateLiteral lhs, DateLiteral rhs) {
+ return lhs.getTotalDays() - rhs.getTotalDays();
+ }
+
@Override
public org.apache.doris.analysis.DateLiteral toLegacyLiteral() {
return legacyLiteral.get();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
index 5dcd65d69c6..53ccdb2d403 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/ExpressionUtils.java
@@ -71,7 +71,9 @@ import
org.apache.doris.nereids.trees.expressions.functions.generator.PosExplode
import org.apache.doris.nereids.trees.expressions.functions.generator.Unnest;
import org.apache.doris.nereids.trees.expressions.functions.scalar.If;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Length;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.NonNullable;
import org.apache.doris.nereids.trees.expressions.functions.scalar.NullIf;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Nullable;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Nvl;
import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral;
import org.apache.doris.nereids.trees.expressions.literal.ComparableLiteral;
@@ -1263,6 +1265,13 @@ public class ExpressionUtils {
return Optional.empty();
}
+ public static Optional<Literal> getLiteralAfterUnwrapNullable(Expression
expr) {
+ while (expr instanceof Nullable || expr instanceof NonNullable) {
+ expr = expr.child(0);
+ }
+ return expr instanceof Literal ? Optional.of((Literal) expr) :
Optional.empty();
+ }
+
/** analyze the unbound expression and fold it to literal */
public static Literal analyzeAndFoldToLiteral(ConnectContext ctx,
Expression expression) throws UserException {
Scope scope = new Scope(new ArrayList<>());
diff --git a/regression-test/data/nereids_function_p0/scalar_function/D.out
b/regression-test/data/nereids_function_p0/scalar_function/D.out
index cc87f9917e2..6c1ee1b0f88 100644
--- a/regression-test/data/nereids_function_p0/scalar_function/D.out
+++ b/regression-test/data/nereids_function_p0/scalar_function/D.out
@@ -492,6 +492,18 @@
0
0
+-- !sql_nullable_date_diff_utc --
+738519 738519 738519 738519 105502 24263 2021 17724468
1063468103 63808086214 63808086214000 63808086214000000
838:59:59.000000
+
+-- !sql_nullable_date_diff_shanghai --
+738519 738519 738519 738519 105502 24263 2021 17724468
1063468103 63808086214 63808086214000 63808086214000000
838:59:59.000000
+
+-- !sql_timestamptz_date_diff_utc --
+0 0 0 0 0 0 0 0 0 0
0 0 1 1 60 60 3600 3600 3600000 3600000
3600000000 3600000000
+
+-- !sql_timestamptz_date_diff_shanghai --
+0 0 0 0 0 0 0 0 0 0
0 0 1 1 60 60 3600 3600 3600000 3600000
3600000000 3600000000
+
-- !sql_datev2_DateTimeV2 --
\N
2012-03-01
diff --git
a/regression-test/suites/nereids_function_p0/scalar_function/D.groovy
b/regression-test/suites/nereids_function_p0/scalar_function/D.groovy
index fb4025cfccf..009f101e279 100644
--- a/regression-test/suites/nereids_function_p0/scalar_function/D.groovy
+++ b/regression-test/suites/nereids_function_p0/scalar_function/D.groovy
@@ -55,6 +55,119 @@ suite("nereids_scalar_fn_D") {
qt_sql_datediff_DateTimeV2_DateTime_notnull "select datediff(kdtmv2s1,
kdtm) from fn_test_not_nullable order by kdtmv2s1, kdtm"
qt_sql_datediff_DateV2_DateTime "select datediff(kdtv2, kdtm) from
fn_test order by kdtv2, kdtm"
qt_sql_datediff_DateV2_DateTime_notnull "select datediff(kdtv2, kdtm)
from fn_test_not_nullable order by kdtv2, kdtm"
+ def timeZoneOrigValue = sql("select @@time_zone")
+ try {
+ sql "set time_zone = '+00:00'"
+ qt_sql_nullable_date_diff_utc """
+ select
+ datediff('2021-12-31 12:23:34', '0000-01-01
00:00:00'),
+ datediff('2021-12-31 12:23:34',
nullable('0000-01-01 00:00:00')),
+ datediff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ days_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ weeks_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ months_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ years_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ hours_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ minutes_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ seconds_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ milliseconds_diff(nullable('2021-12-31
12:23:34'), nullable('0000-01-01 00:00:00')),
+ microseconds_diff(nullable('2021-12-31
12:23:34'), nullable('0000-01-01 00:00:00')),
+ timediff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00'))
+ """
+ sql "set time_zone = '+08:00'"
+ qt_sql_nullable_date_diff_shanghai """
+ select
+ datediff('2021-12-31 12:23:34', '0000-01-01
00:00:00'),
+ datediff('2021-12-31 12:23:34',
nullable('0000-01-01 00:00:00')),
+ datediff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ days_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ weeks_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ months_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ years_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ hours_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ minutes_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ seconds_diff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00')),
+ milliseconds_diff(nullable('2021-12-31
12:23:34'), nullable('0000-01-01 00:00:00')),
+ microseconds_diff(nullable('2021-12-31
12:23:34'), nullable('0000-01-01 00:00:00')),
+ timediff(nullable('2021-12-31 12:23:34'),
nullable('0000-01-01 00:00:00'))
+ """
+ sql "set time_zone = '+00:00'"
+ qt_sql_timestamptz_date_diff_utc """
+ select
+ datediff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ datediff(nullable('2021-12-31 00:30:00+08:00'),
nullable('2021-12-30 23:30:00+08:00')),
+ days_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ days_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ weeks_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ weeks_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ months_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ months_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ quarters_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ quarters_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ years_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ years_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ hours_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ hours_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ minutes_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ minutes_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ seconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ seconds_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ milliseconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ milliseconds_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ microseconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ microseconds_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00'))
+ """
+ sql "set time_zone = '+08:00'"
+ qt_sql_timestamptz_date_diff_shanghai """
+ select
+ datediff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ datediff(nullable('2021-12-31 00:30:00+08:00'),
nullable('2021-12-30 23:30:00+08:00')),
+ days_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ days_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ weeks_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ weeks_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ months_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ months_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ quarters_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ quarters_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ years_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ years_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ hours_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ hours_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ minutes_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ minutes_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ seconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ seconds_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ milliseconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ milliseconds_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00')),
+ microseconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ microseconds_diff(nullable('2021-12-31
00:30:00+08:00'), nullable('2021-12-30 23:30:00+08:00'))
+ """
+ testFoldConst """
+ select
+ datediff('2021-12-31 12:23:34', '0000-01-01
00:00:00'),
+ days_diff('2021-12-31 12:23:34', '0000-01-01
00:00:00')
+ """
+ testFoldConst """
+ select month_floor('2023-07-13 22:28:18.4567', 5)
+ """
+ testFoldConst """
+ select
+ datediff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ days_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ weeks_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ months_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ quarters_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ years_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ hours_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ minutes_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ seconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ milliseconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00'),
+ microseconds_diff('2021-12-31 00:30:00+08:00',
'2021-12-30 23:30:00+08:00')
+ """
+ } finally {
+ sql "set time_zone = '${timeZoneOrigValue[0][0]}'"
+ }
qt_sql_datev2_DateTimeV2 "select datev2(kdtmv2s1) from fn_test order by
kdtmv2s1"
qt_sql_datev2_DateTimeV2_notnull "select datev2(kdtmv2s1) from
fn_test_not_nullable order by kdtmv2s1"
qt_sql_day_ceil_DateTime "select day_ceil(kdtm) from fn_test order by
kdtm"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]