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

morrySnow 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 15add0fc4c4 [fix](partition_prune) Avoid incorrect convert_tz 
partition pruning across DST transitions (#63853)
15add0fc4c4 is described below

commit 15add0fc4c415b8dc0b719074e95bb1ae277080b
Author: feiniaofeiafei <[email protected]>
AuthorDate: Mon Jun 8 11:38:15 2026 +0800

    [fix](partition_prune) Avoid incorrect convert_tz partition pruning across 
DST transitions (#63853)
    
    ### What problem does this PR solve?
    
    Related PR: #40047 #64029
    
    Problem Summary:
    
    Nereids currently treats `convert_tz()` as monotonic whenever both
    timezone arguments are constant. That assumption is not always correct.
    
    When a partition predicate contains `convert_tz(partition_col, from_tz,
    to_tz)`, partition pruning folds the partition range endpoints and
    infers a value range for the function. This is only valid if
    `convert_tz()` is monotonic on the current partition range.
    
    Across DST transitions, `convert_tz()` is not monotonic:
    - during fall-back, different input timestamps can map to the same local
    time
    - during spring-forward, a local-time gap can break the endpoint-based
    range inference
    
    As a result, FE may prune partitions that still contain matching rows.
    The reported case is `convert_tz(ts, 'UTC', 'Europe/Paris')` around the
    Europe/Paris DST fall-back boundary, where a valid partition is
    incorrectly removed.
    
    This PR fixes the issue by tightening the monotonicity check for
    `convert_tz()`:
    - keep the optimization for fixed-offset timezones
    - disable the monotonic shortcut when the source local-time range
    touches a DST transition window
    - disable the monotonic shortcut when the corresponding target instant
    range touches a DST transition
    
    This keeps normal partition pruning behavior for safe cases while
    avoiding wrong pruning around DST boundaries.
---
 .../expressions/functions/scalar/ConvertTz.java    |  72 ++++++++-
 .../expressions/functions/scalar/FromUnixtime.java |  56 ++++++-
 .../org/apache/doris/nereids/util/DateUtils.java   |  15 ++
 .../nereids/rules/rewrite/PartitionPrunerTest.java |   1 -
 .../functions/scalar/ConvertTzTest.java            | 173 +++++++++++++++++++++
 .../functions/scalar/FromUnixtimeTest.java         |  99 ++++++++++++
 .../partition_prune/test_convert_tz.out            |   5 +
 .../month_quarter_cast_in_prune.groovy             |   8 +-
 .../partition_prune/test_convert_tz.groovy         |  24 ++-
 9 files changed, 441 insertions(+), 12 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
index 6a95bf0b149..195ea273af9 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTz.java
@@ -18,12 +18,16 @@
 package org.apache.doris.nereids.trees.expressions.functions.scalar;
 
 import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.common.DdlException;
+import org.apache.doris.common.util.TimeUtils;
 import org.apache.doris.nereids.trees.expressions.Cast;
 import org.apache.doris.nereids.trees.expressions.Expression;
 import 
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
 import org.apache.doris.nereids.trees.expressions.functions.Monotonic;
 import 
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
+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.Literal;
 import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
@@ -31,10 +35,15 @@ import 
org.apache.doris.nereids.trees.expressions.shape.TernaryExpression;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.DateTimeV2Type;
 import org.apache.doris.nereids.types.VarcharType;
+import org.apache.doris.nereids.util.DateUtils;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.List;
 
 /**
@@ -90,7 +99,55 @@ public class ConvertTz extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        return child(1).isConstant() && child(2).isConstant();
+        if (!(child(1) instanceof StringLikeLiteral) || !(child(2) instanceof 
StringLikeLiteral)) {
+            return false;
+        }
+        ZoneId fromZone = parseZoneId((StringLikeLiteral) child(1));
+        ZoneId toZone = parseZoneId((StringLikeLiteral) child(2));
+        if (fromZone == null || toZone == null) {
+            return false;
+        }
+        if (toZone.getRules().isFixedOffset()) {
+            return true;
+        }
+        if (lower == null || upper == null) {
+            return false;
+        }
+        LocalDateTime lowerDateTime = toLocalDateTime(lower);
+        LocalDateTime upperDateTime = toLocalDateTime(upper);
+        if (lowerDateTime == null || upperDateTime == null) {
+            return false;
+        }
+        if (lowerDateTime.equals(upperDateTime)) {
+            return true;
+        }
+        if (upperDateTime.isBefore(lowerDateTime)) {
+            return false;
+        }
+        /*
+         * convert_tz can be treated as a composition of two mappings:
+         *
+         *   source local time x -> instant by from_tz -> target local time by 
to_tz.
+         *
+         * After PR #64029, the first mapping is monotonic non-decreasing. A 
spring gap in from_tz
+         * flattens skipped local times to the transition instant, and a 
fall-back overlap uses the
+         * pre-transition offset before jumping forward at the overlap end. 
Neither case makes the
+         * instant move backward as x increases.
+         *
+         * The second mapping, instant -> to_tz local time, is also monotonic 
non-decreasing except
+         * at a to_tz fall-back transition, where the displayed local time 
jumps backward. Therefore
+         * convert_tz is non-monotonic for a partition only when the instant 
interval obtained from
+         * interpreting the partition bounds in from_tz crosses a to_tz 
fall-back transition instant.
+         *
+         * Partition pruning folds both endpoints before deriving the function 
range, so a fall-back
+         * instant inside (fromInstant(lower), fromInstant(upper)] disables 
the monotonic shortcut.
+         */
+        Instant lowerInstant = 
DateTimeLiteral.convertLocalToInstant(lowerDateTime, fromZone);
+        Instant upperInstant = 
DateTimeLiteral.convertLocalToInstant(upperDateTime, fromZone);
+        if (upperInstant.isBefore(lowerInstant)) {
+            return false;
+        }
+        return !DateUtils.hasFallbackTransitionInInstantRange(toZone, 
lowerInstant, upperInstant);
     }
 
     @Override
@@ -107,4 +164,17 @@ public class ConvertTz extends ScalarFunction
     public Expression withConstantArgs(Expression literal) {
         return new ConvertTz(literal, child(1), child(2));
     }
+
+    private ZoneId parseZoneId(StringLikeLiteral timeZone) {
+        try {
+            String standardizedTimeZone = 
TimeUtils.checkTimeZoneValidAndStandardize(timeZone.getStringValue());
+            return ZoneId.of(standardizedTimeZone, TimeUtils.timeZoneAliasMap);
+        } catch (DdlException | DateTimeException e) {
+            return null;
+        }
+    }
+
+    private LocalDateTime toLocalDateTime(Literal literal) {
+        return literal instanceof DateLiteral ? ((DateLiteral) 
literal).toJavaDateType() : null;
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
index 7492de7a243..64d571654fe 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtime.java
@@ -24,6 +24,7 @@ import 
org.apache.doris.nereids.trees.expressions.functions.Monotonic;
 import 
org.apache.doris.nereids.trees.expressions.functions.PropagateNullLiteral;
 import org.apache.doris.nereids.trees.expressions.functions.PropagateNullable;
 import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.literal.NumericLiteral;
 import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
 import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
 import org.apache.doris.nereids.types.BigIntType;
@@ -35,6 +36,11 @@ import org.apache.doris.nereids.util.DateUtils;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
 import java.util.List;
 
 /**
@@ -109,15 +115,27 @@ public class FromUnixtime extends ScalarFunction
 
     @Override
     public boolean isMonotonic(Literal lower, Literal upper) {
-        if (1 == arity()) {
+        if (arity() == 2 && !isMonotonicFormat()) {
+            return false;
+        }
+        ZoneId timeZone;
+        try {
+            timeZone = DateUtils.getTimeZone();
+        } catch (DateTimeException e) {
+            return false;
+        }
+        if (timeZone.getRules().isFixedOffset()) {
             return true;
         }
-        Expression format = child(1);
-        if (!(format instanceof StringLikeLiteral)) {
+        if (lower == null || upper == null) {
             return false;
         }
-        String str = ((StringLikeLiteral) format).getValue();
-        return DateUtils.monoFormat.contains(str);
+        Instant lowerInstant = toInstant(lower);
+        Instant upperInstant = toInstant(upper);
+        if (lowerInstant == null || upperInstant == null || 
upperInstant.isBefore(lowerInstant)) {
+            return false;
+        }
+        return !DateUtils.hasFallbackTransitionInInstantRange(timeZone, 
lowerInstant, upperInstant);
     }
 
     @Override
@@ -137,4 +155,32 @@ public class FromUnixtime extends ScalarFunction
         }
         return new FromUnixtime(literal, child(1));
     }
+
+    private boolean isMonotonicFormat() {
+        Expression format = child(1);
+        if (!(format instanceof StringLikeLiteral)) {
+            return false;
+        }
+        return DateUtils.monoFormat.contains(((StringLikeLiteral) 
format).getValue());
+    }
+
+    private Instant toInstant(Literal literal) {
+        if (!(literal instanceof NumericLiteral)) {
+            return null;
+        }
+        BigDecimal value = ((NumericLiteral) literal).getBigDecimalValue();
+        if (value.signum() < 0) {
+            return null;
+        }
+        try {
+            long seconds = value.setScale(0, 
RoundingMode.DOWN).longValueExact();
+            long nanos = value.subtract(BigDecimal.valueOf(seconds))
+                    .movePointRight(9)
+                    .setScale(0, RoundingMode.DOWN)
+                    .longValueExact();
+            return Instant.ofEpochSecond(seconds, nanos);
+        } catch (ArithmeticException | DateTimeException e) {
+            return null;
+        }
+    }
 }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java
index 9ac8ff9c1db..e339471e447 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/util/DateUtils.java
@@ -24,6 +24,7 @@ import org.apache.doris.qe.ConnectContext;
 import com.google.common.collect.ImmutableSet;
 
 import java.time.DayOfWeek;
+import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
@@ -35,6 +36,7 @@ import java.time.temporal.ChronoField;
 import java.time.temporal.IsoFields;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.WeekFields;
+import java.time.zone.ZoneOffsetTransition;
 import java.util.Locale;
 import java.util.Set;
 
@@ -406,4 +408,17 @@ public class DateUtils {
         }
         return 
ZoneId.of(ConnectContext.get().getSessionVariable().getTimeZone());
     }
+
+    /**Determine whether there is a fallback transition within the interval 
(lower, upper].
+     * @return If there is one, return true.*/
+    public static boolean hasFallbackTransitionInInstantRange(ZoneId zoneId, 
Instant lower, Instant upper) {
+        ZoneOffsetTransition transition = 
zoneId.getRules().nextTransition(lower);
+        while (transition != null && !transition.getInstant().isAfter(upper)) {
+            if (transition.isOverlap()) {
+                return true;
+            }
+            transition = 
zoneId.getRules().nextTransition(transition.getInstant());
+        }
+        return false;
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PartitionPrunerTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PartitionPrunerTest.java
index f4f78da1e30..3fe07a49f9c 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PartitionPrunerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/PartitionPrunerTest.java
@@ -354,4 +354,3 @@ public class PartitionPrunerTest extends TestWithFeService {
         return new ListPartitionItem(partitionKeys.build());
     }
 }
-
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTzTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTzTest.java
new file mode 100644
index 00000000000..f280d831a50
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/ConvertTzTest.java
@@ -0,0 +1,173 @@
+// 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.trees.expressions.functions.scalar;
+
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral;
+import org.apache.doris.nereids.types.DateTimeType;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class ConvertTzTest {
+    private final SlotReference timestampSlot = new SlotReference("ts", 
DateTimeType.INSTANCE);
+
+    @Test
+    void testIsMonotonicWithoutDstTransition() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-01-10 00:00:00"),
+                new DateTimeLiteral("2021-01-11 00:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithFixedOffsetTargetZoneAndUnboundedSourceRange() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("Europe/Paris"), new VarcharLiteral("UTC"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                null,
+                new DateTimeLiteral("2021-03-28 02:00:00")));
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-31 02:00:00"),
+                null));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackAtUpperBoundaryInTargetZone() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertFalse(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-31 00:00:00"),
+                new DateTimeLiteral("2021-10-31 01:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackAtLowerBoundaryInTargetZone() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-31 01:00:00"),
+                new DateTimeLiteral("2021-10-31 02:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackAtLowerBoundaryInTargetZone2() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-31 01:00:00"),
+                new DateTimeLiteral("2021-10-31 01:30:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackAtBoundaryInTargetZone3() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-31 01:30:00"),
+                new DateTimeLiteral("2021-10-31 02:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackAtBoundaryInTargetZone4() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-30 23:00:00"),
+                new DateTimeLiteral("2021-10-31 00:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackAtBoundaryInTargetZone5() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertFalse(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-31 00:00:00"),
+                new DateTimeLiteral("2021-10-31 02:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstSpringGapInTargetZone() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("UTC"), new VarcharLiteral("Europe/Paris"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-03-28 00:00:00"),
+                new DateTimeLiteral("2021-03-28 02:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstGapInSourceZone() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("Europe/Paris"), new VarcharLiteral("UTC"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-03-28 02:00:00"),
+                new DateTimeLiteral("2021-03-28 03:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstGapAtLowerBoundaryInSourceZone() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("Europe/Paris"), new VarcharLiteral("UTC"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-03-28 01:00:00"),
+                new DateTimeLiteral("2021-03-28 02:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstGapAtUpperBoundaryInSourceZone() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("Europe/Paris"), new VarcharLiteral("UTC"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-03-28 03:00:00"),
+                new DateTimeLiteral("2021-03-28 04:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstGapInSourceZoneOverlap() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("Europe/Paris"), new VarcharLiteral("UTC"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-03-28 02:00:00"),
+                new DateTimeLiteral("2021-03-28 04:00:00")));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackInSourceZone() {
+        ConvertTz convertTz = new ConvertTz(timestampSlot,
+                new VarcharLiteral("Europe/Paris"), new VarcharLiteral("UTC"));
+
+        Assertions.assertTrue(convertTz.isMonotonic(
+                new DateTimeLiteral("2021-10-31 01:00:00"),
+                new DateTimeLiteral("2021-10-31 03:00:00")));
+    }
+}
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtimeTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtimeTest.java
new file mode 100644
index 00000000000..9c3aabab979
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/FromUnixtimeTest.java
@@ -0,0 +1,99 @@
+// 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.trees.expressions.functions.scalar;
+
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral;
+import org.apache.doris.nereids.types.BigIntType;
+import org.apache.doris.qe.ConnectContext;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class FromUnixtimeTest {
+    private final SlotReference timestampSlot = new SlotReference("ts", 
BigIntType.INSTANCE);
+    private ConnectContext previousContext;
+
+    @BeforeEach
+    void setUp() {
+        previousContext = ConnectContext.get();
+        ConnectContext connectContext = new ConnectContext();
+        connectContext.setThreadLocalInfo();
+    }
+
+    @AfterEach
+    void tearDown() {
+        ConnectContext.remove();
+        if (previousContext != null) {
+            previousContext.setThreadLocalInfo();
+        }
+    }
+
+    @Test
+    void testIsMonotonicWithFixedOffsetTimeZone() {
+        setTimeZone("+00:00");
+        FromUnixtime fromUnixtime = new FromUnixtime(timestampSlot);
+
+        Assertions.assertTrue(fromUnixtime.isMonotonic(
+                new BigIntLiteral(1635638400L), new 
BigIntLiteral(1635642000L)));
+    }
+
+    @Test
+    void testIsMonotonicWithoutDstTransition() {
+        setTimeZone("Europe/Paris");
+        FromUnixtime fromUnixtime = new FromUnixtime(timestampSlot);
+
+        Assertions.assertTrue(fromUnixtime.isMonotonic(
+                new BigIntLiteral(1610236800L), new 
BigIntLiteral(1610323200L)));
+    }
+
+    @Test
+    void testIsMonotonicWithDstFallbackTransition() {
+        setTimeZone("Europe/Paris");
+        FromUnixtime fromUnixtime = new FromUnixtime(timestampSlot);
+
+        Assertions.assertFalse(fromUnixtime.isMonotonic(
+                new BigIntLiteral(1635638400L), new 
BigIntLiteral(1635642000L)));
+    }
+
+    @Test
+    void testIsMonotonicWithFormatAndDstFallbackTransition() {
+        setTimeZone("Europe/Paris");
+        FromUnixtime fromUnixtime = new FromUnixtime(timestampSlot,
+                new VarcharLiteral("%Y-%m-%d %H:%i:%s"));
+
+        Assertions.assertFalse(fromUnixtime.isMonotonic(
+                new BigIntLiteral(1635638400L), new 
BigIntLiteral(1635642000L)));
+    }
+
+    @Test
+    void testIsMonotonicWithNonMonotonicFormat() {
+        setTimeZone("+00:00");
+        FromUnixtime fromUnixtime = new FromUnixtime(timestampSlot, new 
VarcharLiteral("%W"));
+
+        Assertions.assertFalse(fromUnixtime.isMonotonic(
+                new BigIntLiteral(1610236800L), new 
BigIntLiteral(1610323200L)));
+    }
+
+    private void setTimeZone(String timeZone) {
+        ConnectContext.get().getSessionVariable().setTimeZone(timeZone);
+    }
+}
diff --git 
a/regression-test/data/nereids_rules_p0/partition_prune/test_convert_tz.out 
b/regression-test/data/nereids_rules_p0/partition_prune/test_convert_tz.out
new file mode 100644
index 00000000000..8d5a0dcb23c
--- /dev/null
+++ b/regression-test/data/nereids_rules_p0/partition_prune/test_convert_tz.out
@@ -0,0 +1,5 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !convert_tz_dst --
+2021-10-31T00:30
+2021-10-31T01:30
+
diff --git 
a/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
 
b/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
index 88235c9ab81..be89d056e94 100644
--- 
a/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
+++ 
b/regression-test/suites/nereids_rules_p0/partition_prune/month_quarter_cast_in_prune.groovy
@@ -199,20 +199,20 @@ suite("month_quarter_cast_in_prune") {
 
     explain {
         sql """select * from from_unixtime_t where from_unixtime(a,"%Y-%m-%d 
%T") <'2001-05-16 16:00:00'"""
-        contains("partitions=2/5 (p1,p2)")
+        contains("partitions=3/5 (p1,p2,p5)")
     }
     explain {
         sql """select * from from_unixtime_t where from_unixtime(a,"%Y-%m-%d 
%T") <='2001-05-16 16:00:00'"""
-        contains("partitions=3/5 (p1,p2,p3)")
+        contains("partitions=4/5 (p1,p2,p3,p5)")
     }
 
     explain {
         sql """select * from from_unixtime_t where from_unixtime(a,"yyyyMMdd") 
< '20330518'"""
-        contains("partitions=3/5 (p1,p2,p3)")
+        contains("partitions=4/5 (p1,p2,p3,p5)")
     }
     explain {
         sql """select * from from_unixtime_t where from_unixtime(a,"yyyyMMdd") 
> '20330518'"""
-        contains("partitions=3/5 (p1,p4,p5)")
+        contains("partitions=4/5 (p1,p2,p4,p5)")
 
     }
     explain {
diff --git 
a/regression-test/suites/nereids_rules_p0/partition_prune/test_convert_tz.groovy
 
b/regression-test/suites/nereids_rules_p0/partition_prune/test_convert_tz.groovy
index a6c62d0b0eb..fa271806e8c 100644
--- 
a/regression-test/suites/nereids_rules_p0/partition_prune/test_convert_tz.groovy
+++ 
b/regression-test/suites/nereids_rules_p0/partition_prune/test_convert_tz.groovy
@@ -95,4 +95,26 @@ suite("test_convert_tz") {
             contains("partitions=3/3 (p1,p2,p3)")
         }
     }
-}
\ No newline at end of file
+
+    sql "drop table if exists test_convert_tz_dst;"
+    sql """CREATE TABLE test_convert_tz_dst
+    (
+            ts DATETIME NOT NULL
+    )
+    ENGINE = olap
+    PARTITION BY range (ts)
+    (
+        PARTITION `p_00` VALUES [('2021-10-31 00:00:00'), ('2021-10-31 
01:00:00')),
+        PARTITION `p_01` VALUES [('2021-10-31 01:00:00'), ('2021-10-31 
02:00:00')),
+        PARTITION `p_02` VALUES [('2021-10-31 02:00:00'), ('2021-10-31 
03:00:00'))
+    ) DISTRIBUTED BY HASH (ts)
+    PROPERTIES(
+            "storage_format" = "DEFAULT",
+            "replication_num" = "1");"""
+    sql "insert into test_convert_tz_dst values('2021-10-31 
00:30:00'),('2021-10-31 01:30:00'),('2021-10-31 02:30:00')"
+    explain {
+        sql "SELECT * FROM test_convert_tz_dst WHERE convert_tz(ts, 'UTC', 
'Europe/Paris') = '2021-10-31 02:30:00';"
+        contains("partitions=2/3 (p_00,p_01)")
+    }
+    order_qt_convert_tz_dst "SELECT * FROM test_convert_tz_dst WHERE 
convert_tz(ts, 'UTC', 'Europe/Paris') = '2021-10-31 02:30:00';"
+}


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

Reply via email to